fpspreadsheet: Add the ohlcseries of Laz/main as TStockseries in unit fpsstockseries for supporting the HLC series of Excel and Calc.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@9075 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2023-12-07 22:53:48 +00:00
parent e8a24a0cf0
commit 9914514709
6 changed files with 839 additions and 62 deletions

View File

@ -2,17 +2,17 @@ object Form1: TForm1
Left = 314 Left = 314
Height = 527 Height = 527
Top = 130 Top = 130
Width = 1101 Width = 1351
Caption = 'Form1' Caption = 'Form1'
ClientHeight = 527 ClientHeight = 527
ClientWidth = 1101 ClientWidth = 1351
LCLVersion = '3.99.0.0' LCLVersion = '3.99.0.0'
OnCreate = FormCreate OnCreate = FormCreate
object sWorksheetGrid1: TsWorksheetGrid object sWorksheetGrid1: TsWorksheetGrid
Left = 0 Left = 0
Height = 489 Height = 489
Top = 38 Top = 38
Width = 402 Width = 563
FrozenCols = 0 FrozenCols = 0
FrozenRows = 0 FrozenRows = 0
PageBreakPen.Color = clBlue PageBreakPen.Color = clBlue
@ -28,16 +28,16 @@ object Form1: TForm1
TabOrder = 0 TabOrder = 0
end end
object Splitter1: TSplitter object Splitter1: TSplitter
Left = 402 Left = 563
Height = 489 Height = 489
Top = 38 Top = 38
Width = 5 Width = 5
end end
object Chart1: TChart object Chart1: TChart
Left = 407 Left = 568
Height = 489 Height = 489
Top = 38 Top = 38
Width = 694 Width = 783
AxisList = < AxisList = <
item item
Marks.LabelBrush.Style = bsClear Marks.LabelBrush.Style = bsClear
@ -60,12 +60,12 @@ object Form1: TForm1
Left = 0 Left = 0
Height = 38 Height = 38
Top = 0 Top = 0
Width = 1101 Width = 1351
Align = alTop Align = alTop
AutoSize = True AutoSize = True
BevelOuter = bvNone BevelOuter = bvNone
ClientHeight = 38 ClientHeight = 38
ClientWidth = 1101 ClientWidth = 1351
TabOrder = 3 TabOrder = 3
object Label1: TLabel object Label1: TLabel
AnchorSideLeft.Control = Panel1 AnchorSideLeft.Control = Panel1
@ -87,9 +87,10 @@ object Form1: TForm1
Left = 63 Left = 63
Height = 23 Height = 23
Top = 8 Top = 8
Width = 911 Width = 1161
Anchors = [akTop, akLeft, akRight] Anchors = [akTop, akLeft, akRight]
BorderSpacing.Around = 6 BorderSpacing.Around = 6
DropDownCount = 32
ItemHeight = 15 ItemHeight = 15
Items.Strings = ( Items.Strings = (
'../../../other/chart/area.ods' '../../../other/chart/area.ods'
@ -111,7 +112,7 @@ object Form1: TForm1
AnchorSideTop.Control = Panel1 AnchorSideTop.Control = Panel1
AnchorSideTop.Side = asrCenter AnchorSideTop.Side = asrCenter
AnchorSideRight.Control = Button2 AnchorSideRight.Control = Button2
Left = 980 Left = 1230
Height = 25 Height = 25
Top = 7 Top = 7
Width = 35 Width = 35
@ -127,7 +128,7 @@ object Form1: TForm1
AnchorSideTop.Side = asrCenter AnchorSideTop.Side = asrCenter
AnchorSideRight.Control = Panel1 AnchorSideRight.Control = Panel1
AnchorSideRight.Side = asrBottom AnchorSideRight.Side = asrBottom
Left = 1021 Left = 1271
Height = 25 Height = 25
Top = 7 Top = 7
Width = 74 Width = 74

View File

@ -21,7 +21,7 @@
It provides graphical components like a grid and chart."/> It provides graphical components like a grid and chart."/>
<License Value="LGPL with static linking exception. This is the same license as is used in the LCL (Lazarus Component Library)."/> <License Value="LGPL with static linking exception. This is the same license as is used in the LCL (Lazarus Component Library)."/>
<Version Major="1" Minor="17"/> <Version Major="1" Minor="17"/>
<Files Count="5"> <Files Count="6">
<Item1> <Item1>
<Filename Value="source\visual\fpspreadsheetchart.pas"/> <Filename Value="source\visual\fpspreadsheetchart.pas"/>
<UnitName Value="fpspreadsheetchart"/> <UnitName Value="fpspreadsheetchart"/>
@ -42,6 +42,10 @@ It provides graphical components like a grid and chart."/>
<Filename Value="source\visual\fpsactions.pas"/> <Filename Value="source\visual\fpsactions.pas"/>
<UnitName Value="fpsActions"/> <UnitName Value="fpsActions"/>
</Item5> </Item5>
<Item6>
<Filename Value="source\visual\fpsstockseries.pas"/>
<UnitName Value="fpsstockseries"/>
</Item6>
</Files> </Files>
<CompatibilityMode Value="True"/> <CompatibilityMode Value="True"/>
<RequiredPkgs Count="4"> <RequiredPkgs Count="4">

View File

@ -266,6 +266,7 @@ type
public public
constructor Create(AChart: TsChart); constructor Create(AChart: TsChart);
destructor Destroy; override; destructor Destroy; override;
function OtherAxis: TsChartAxis;
property AutomaticMax: Boolean read FAutomaticMax write FAutomaticMax; property AutomaticMax: Boolean read FAutomaticMax write FAutomaticMax;
property AutomaticMin: Boolean read FAutomaticMin write FAutomaticMin; property AutomaticMin: Boolean read FAutomaticMin write FAutomaticMin;
property AutomaticMajorInterval: Boolean read FAutomaticMajorInterval write FAutomaticMajorInterval; property AutomaticMajorInterval: Boolean read FAutomaticMajorInterval write FAutomaticMajorInterval;
@ -351,11 +352,11 @@ type
FYAxis: TsChartAxisLink; FYAxis: TsChartAxisLink;
FTitleAddr: TsChartCellAddr; FTitleAddr: TsChartCellAddr;
FLabelFormat: String; FLabelFormat: String;
FLine: TsChartLine;
FFill: TsChartFill;
FDataLabels: TsChartDataLabels; FDataLabels: TsChartDataLabels;
FDataPointStyles: TsChartDataPointStyleList; FDataPointStyles: TsChartDataPointStyleList;
protected protected
FLine: TsChartLine;
FFill: TsChartFill;
function GetChartType: TsChartType; virtual; function GetChartType: TsChartType; virtual;
public public
constructor Create(AChart: TsChart); virtual; constructor Create(AChart: TsChart); virtual;
@ -551,7 +552,9 @@ type
FHighRange: TsChartRange; FHighRange: TsChartRange;
FLowRange: TsChartRange; // close = normal y range FLowRange: TsChartRange; // close = normal y range
FCandleStickDownFill: TsChartFill; FCandleStickDownFill: TsChartFill;
FRangeLine: TsChartLine; FCandleStickDownBorder: TsChartLine;
FCandleStickUpBorder: TsChartLine;
// fill is CandleStickUpFill, line is RangeLine
public public
constructor Create(AChart: TsChart); override; constructor Create(AChart: TsChart); override;
destructor Destroy; override; destructor Destroy; override;
@ -560,8 +563,11 @@ type
procedure SetLowRange (ASheet1: String; ARow1, ACol1: Cardinal; ASheet2: String; ARow2, ACol2: Cardinal); procedure SetLowRange (ASheet1: String; ARow1, ACol1: Cardinal; ASheet2: String; ARow2, ACol2: Cardinal);
procedure SetCloseRange(ASheet1: String; ARow1, ACol1: Cardinal; ASheet2: String; ARow2, ACol2: Cardinal); procedure SetCloseRange(ASheet1: String; ARow1, ACol1: Cardinal; ASheet2: String; ARow2, ACol2: Cardinal);
property CandleStick: Boolean read FCandleStick write FCandleStick; property CandleStick: Boolean read FCandleStick write FCandleStick;
property CandleStickDownFill: TsChartfill read FCandleStickDownFill write FCandleStickDownFill; property CandleStickDownFill: TsChartFill read FCandleStickDownFill write FCandleStickDownFill;
property RangeLine: TsChartLine read FRangeLine write FRangeLine; property CandleStickUpFill: TsChartFill read FFill write FFill;
property CandleStickDownBorder: TsChartLine read FCandleStickDownBorder write FCandleStickDownBorder;
property CandleStickUpBorder: TsChartLine read FCandleStickUpBorder write FCandleStickUpBorder;
property RangeLine: TsChartLine read FLine write FLine;
property OpenRange: TsChartRange read FOpenRange; property OpenRange: TsChartRange read FOpenRange;
property HighRange: TsChartRange read FHighRange; property HighRange: TsChartRange read FHighRange;
property LowRange: TsChartRange read FLowRange; property LowRange: TsChartRange read FLowRange;
@ -1230,6 +1236,21 @@ begin
inherited; inherited;
end; end;
{@@ ----------------------------------------------------------------------------
Returns the other axis in the same direction, i.e. when the axis is the
primary x axis the function returns the secondary x axis, etc.
-------------------------------------------------------------------------------}
function TsChartAxis.OtherAxis: TsChartAxis;
begin
if Chart.XAxis = self then
Result := Chart.X2Axis
else if Chart.X2Axis = self then
Result := Chart.XAxis
else if Chart.YAxis = self then
Result := Chart.Y2Axis
else if Chart.Y2Axis = self then
Result := Chart.YAxis;
end;
{ TsChartLegend } { TsChartLegend }
@ -1772,18 +1793,24 @@ begin
FHighRange := TsChartRange.Create(AChart); FHighRange := TsChartRange.Create(AChart);
FLowRange := TsChartRange.Create(AChart); FLowRange := TsChartRange.Create(AChart);
// FFill is CandleStickUp // FFill is CandleStickUp, FLine is RangeLine
FCandleStickDownFill := TsChartFill.Create; FCandleStickDownFill := TsChartFill.Create;
FCandleStickDownFill.Style := cfsSolid; FCandleStickDownFill.Style := cfsSolid;
FCandleStickDownFill.Color := scBlack; FCandleStickDownFill.Color := scBlack;
FRangeLine := TsChartLine.Create; FCandleStickDownBorder := TsChartLine.Create;
FRangeLine.Style := clsSolid; FCandleStickDownBorder.Style := clsSolid;
FRangeLine.Color := scBlack; FCandleStickDownBorder.Color := scBlack;
FCandleStickUpBorder := TsChartLine.Create;
FCandleStickUpBorder.Style := clsSolid;
FCandleStickUpBorder.Color := scBlack;
FLine.Style := clsSolid;
FLine.Color := scBlack;
end; end;
destructor TsStockSeries.Destroy; destructor TsStockSeries.Destroy;
begin begin
FRangeLine.Free; FCandleStickUpBorder.Free;
FCandleStickDownBorder.Free;
FCandleStickDownFill.Free; FCandleStickDownFill.Free;
FOpenRange.Free; FOpenRange.Free;
FHighRange.Free; FHighRange.Free;

View File

@ -1327,15 +1327,16 @@ begin
ReadChartCellAddr(ANode, 'chart:label-cell-address', series.TitleAddr); ReadChartCellAddr(ANode, 'chart:label-cell-address', series.TitleAddr);
if (series is TsStockSeries) then if (series is TsStockSeries) then
begin begin
// The file contains the range in the order Open-Low-High-Close
if FStockSeries.OpenRange.IsEmpty and FStockSeries.CandleStick then if FStockSeries.OpenRange.IsEmpty and FStockSeries.CandleStick then
ReadChartCellRange(ANode, 'chart:values-cell-range-address', FStockSeries.OpenRange) ReadChartCellRange(ANode, 'chart:values-cell-range-address', FStockSeries.OpenRange)
else else
if FStockSeries.HighRange.IsEmpty then
ReadChartCellRange(ANode, 'chart:values-cell-range-address', FStockSeries.HighRange)
else
if FStockSeries.LowRange.IsEmpty then if FStockSeries.LowRange.IsEmpty then
ReadChartCellRange(ANode, 'chart:values-cell-range-address', FStockSeries.LowRange) ReadChartCellRange(ANode, 'chart:values-cell-range-address', FStockSeries.LowRange)
else else
if FStockSeries.HighRange.IsEmpty then
ReadChartCellRange(ANode, 'chart:values-cell-range-address', FStockSeries.HighRange)
else
if FStockSeries.CloseRange.IsEmpty then if FStockSeries.CloseRange.IsEmpty then
ReadChartCellRange(ANode, 'chart:values-cell-range-address', FStockSeries.CloseRange); ReadChartCellRange(ANode, 'chart:values-cell-range-address', FStockSeries.CloseRange);
end end
@ -1563,11 +1564,15 @@ begin
if nodeName = 'style:graphic-properties' then if nodeName = 'style:graphic-properties' then
begin begin
if ANodeName = 'chart:stock-gain-marker' then if ANodeName = 'chart:stock-gain-marker' then
GetChartFillProps(AStyleNode, AChart, ASeries.Fill) begin
else GetChartFillProps(AStyleNode, AChart, ASeries.CandleStickUpFill);
GetChartLineProps(AStyleNode, AChart, ASeries.CandleStickUpBorder);
end else
if ANodeName = 'chart:stock-loss-marker' then if ANodeName = 'chart:stock-loss-marker' then
GetChartFillProps(AStyleNode, AChart, ASeries.CandleStickDownFill) begin
else GetChartFillProps(AStyleNode, AChart, ASeries.CandleStickDownFill);
GetChartLineProps(AStyleNode, AChart, ASeries.CandleStickDownBorder);
end else
if ANodeName = 'chart:stock-range-line' then if ANodeName = 'chart:stock-range-line' then
GetChartLineProps(AStyleNode, AChart, ASeries.RangeLine); GetChartLineProps(AStyleNode, AChart, ASeries.RangeLine);
end; end;

View File

@ -27,7 +27,7 @@ uses
TASeries, TARadialSeries, TAFitUtils, TAFuncSeries, TAMultiSeries, TASeries, TARadialSeries, TAFitUtils, TAFuncSeries, TAMultiSeries,
TATransformations, TAChartAxisUtils, TAChartAxis, TAStyles, TATools, TAGraph, TATransformations, TAChartAxisUtils, TAChartAxis, TAStyles, TATools, TAGraph,
// FPSpreadsheet // FPSpreadsheet
fpsTypes, fpSpreadsheet, fpsUtils, fpsNumFormat, fpsChart, fpsTypes, fpSpreadsheet, fpsUtils, fpsNumFormat, fpsChart, fpsStockSeries,
// FPSpreadsheet Visual // FPSpreadsheet Visual
fpSpreadsheetCtrls, fpSpreadsheetGrid, fpsVisualUtils; fpSpreadsheetCtrls, fpSpreadsheetGrid, fpsVisualUtils;
@ -158,10 +158,10 @@ type
procedure UpdateBarSeries(AWorkbookSeries: TsBarSeries; AChartSeries: TBarSeries); procedure UpdateBarSeries(AWorkbookSeries: TsBarSeries; AChartSeries: TBarSeries);
procedure UpdateBubbleSeries(AWorkbookSeries: TsBubbleSeries; AChartSeries: TBubbleSeries); procedure UpdateBubbleSeries(AWorkbookSeries: TsBubbleSeries; AChartSeries: TBubbleSeries);
procedure UpdateCustomLineSeries(AWorkbookSeries: TsCustomLineSeries; AChartSeries: TLineSeries); procedure UpdateCustomLineSeries(AWorkbookSeries: TsCustomLineSeries; AChartSeries: TLineSeries);
procedure UpdateOHLCSeries(AWorkbookSeries: TsStockSeries; AChartSeries: TOpenHighLowCloseSeries);
procedure UpdatePieSeries(AWorkbookSeries: TsPieSeries; AChartSeries: TPieSeries); procedure UpdatePieSeries(AWorkbookSeries: TsPieSeries; AChartSeries: TPieSeries);
procedure UpdatePolarSeries(AWorkbookSeries: TsRadarSeries; AChartSeries: TPolarSeries); procedure UpdatePolarSeries(AWorkbookSeries: TsRadarSeries; AChartSeries: TPolarSeries);
procedure UpdateScatterSeries(AWorkbookSeries: TsScatterSeries; AChartSeries: TLineSeries); procedure UpdateScatterSeries(AWorkbookSeries: TsScatterSeries; AChartSeries: TLineSeries);
procedure UpdateStockSeries(AWorkbookSeries: TsStockSeries; AChartSeries: TStockSeries);
public public
constructor Create(AOwner: TComponent); override; constructor Create(AOwner: TComponent); override;
@ -468,7 +468,7 @@ var
begin begin
for i := 0 to XCount-1 do for i := 0 to XCount-1 do
begin begin
if FRanges[rngX, i] <> nil then if (FRanges[rngX, i] <> nil) then
begin begin
GetXYItem(rngX, i, AIndex, value, tmpLabel); GetXYItem(rngX, i, AIndex, value, tmpLabel);
if FIntegerX then if FIntegerX then
@ -1100,7 +1100,7 @@ begin
Result := TPieSeries.Create(FChart); Result := TPieSeries.Create(FChart);
ctStock: ctStock:
begin begin
Result := TOpenHighLowCloseSeries.Create(FChart); Result := TStockSeries.Create(FChart);
src.YCount := 4; src.YCount := 4;
src.IntegerX := true; src.IntegerX := true;
src.SetXRange(0, ASeries.Chart.XAxis.CategoryRange); src.SetXRange(0, ASeries.Chart.XAxis.CategoryRange);
@ -1114,7 +1114,7 @@ begin
end; end;
// Get x and y ranges (except for OHLC which already has been handled) // Get x and y ranges (except for OHLC which already has been handled)
if not (Result is TOpenHighLowCloseSeries) then if not (Result is TStockSeries) then
begin begin
if not ASeries.XRange.IsEmpty then if not ASeries.XRange.IsEmpty then
src.SetXRange(0, ASeries.XRange); src.SetXRange(0, ASeries.XRange);
@ -1201,7 +1201,7 @@ begin
ctScatter: ctScatter:
UpdateScatterSeries(TsScatterSeries(ASeries), TLineSeries(ser)); UpdateScatterSeries(TsScatterSeries(ASeries), TLineSeries(ser));
ctStock: ctStock:
UpdateOHLCSeries(TsStockSeries(ASeries), TOpenHighLowCloseSeries(ser)); UpdateStockSeries(TsStockSeries(ASeries), TStockSeries(ser));
ctPie, ctRing: ctPie, ctRing:
UpdatePieSeries(TsPieSeries(ASeries), TPieSeries(ser)); UpdatePieSeries(TsPieSeries(ASeries), TPieSeries(ser));
ctRadar, ctFilledRadar: ctRadar, ctFilledRadar:
@ -1525,18 +1525,19 @@ function TsWorkbookChartLink.GetAxisTransform(AChartAxis: TChartAxis;
var var
T: TAxisTransform; T: TAxisTransform;
begin begin
for T in AChartAxis.Transformations.List do if AChartAxis.Transformations <> nil then
if T is AClass then for T in AChartAxis.Transformations.List do
begin if T is AClass then
Result := T; begin
exit; Result := T;
end; exit;
end;
Result := nil; Result := nil;
end; end;
function TsWorkbookChartLink.GetLogAxisTransform(AChartAxis: TChartAxis): TLogarithmAxisTransform; function TsWorkbookChartLink.GetLogAxisTransform(AChartAxis: TChartAxis): TLogarithmAxisTransform;
begin begin
Result := TLogarithmAxisTransform(GetAxisTransform(AChartAxis, TLogarithmAxisTransform)); Result := TLogarithmAxisTransform(GetAxisTransform(AChartAxis, TLogarithmAxisTransform))
end; end;
function TsWorkbookChartLink.GetWorkbookChart: TsChart; function TsWorkbookChartLink.GetWorkbookChart: TsChart;
@ -1759,7 +1760,7 @@ begin
// Usually not needed, but axis handling is simplified when there is // Usually not needed, but axis handling is simplified when there is
// an axis transformations object at each axis with all transforms prepared. // an axis transformations object at each axis with all transforms prepared.
if axis.Transformations = nil then if (axis.Transformations = nil) then
begin begin
axis.Transformations := TChartAxisTransformations.Create(FChart); axis.Transformations := TChartAxisTransformations.Create(FChart);
@ -1772,8 +1773,7 @@ begin
// Autoscale transformation for primary and secondary axes // Autoscale transformation for primary and secondary axes
T := TAutoScaleAxisTransform.Create(axis.Transformations); T := TAutoScaleAxisTransform.Create(axis.Transformations);
T.Transformations := axis.Transformations; T.Transformations := axis.Transformations;
if AWorkbookAxis.Logarithmic or (AWorkbookAxis.Chart.GetChartType in [ctRadar, ctFilledRadar]) then T.Enabled := AWorkbookAxis.Visible and AWorkbookAxis.OtherAxis.Visible;
T.Enabled := false;
end; end;
// Axis title // Axis title
@ -1828,7 +1828,8 @@ begin
// Logarithmic // Logarithmic
logTransf := GetLogAxisTransform(axis); logTransf := GetLogAxisTransform(axis);
logTransf.Enabled := AWorkbookAxis.Logarithmic; if logTransf <> nil then
logTransf.Enabled := AWorkbookAxis.Logarithmic;
if AWorkbookAxis.Logarithmic then if AWorkbookAxis.Logarithmic then
begin begin
axis.Intervals.Options := axis.Intervals.Options + [aipGraphCoords]; axis.Intervals.Options := axis.Intervals.Options + [aipGraphCoords];
@ -2114,22 +2115,6 @@ begin
TCalculatedChartSource(AChartSeries.Source).Percentage := (AWorkbookSeries.Chart.StackMode = csmStackedPercentage); TCalculatedChartSource(AChartSeries.Source).Percentage := (AWorkbookSeries.Chart.StackMode = csmStackedPercentage);
end; end;
procedure TsWorkbookChartLink.UpdateOHLCSeries(AWorkbookSeries: TsStockSeries;
AChartSeries: TOpenHighLowCloseSeries);
begin
if AWorkbookSeries.CandleStick then
begin
AChartSeries.Mode := mCandleStick;
UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.Fill, AChartSeries.CandleStickUpBrush);
UpdateChartBrush(AWorkbookseries.Chart, AWorkbookseries.CandleStickDownFill, AChartSeries.CandleStickDownBrush);
end else
begin
AChartSeries.Mode := mOHLC;
end;
UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.RangeLine, AChartSeries.LinePen);
UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.RangeLine, AChartSeries.DownLinePen);
end;
procedure TsWorkbookChartLink.UpdatePieSeries(AWorkbookSeries: TsPieSeries; procedure TsWorkbookChartLink.UpdatePieSeries(AWorkbookSeries: TsPieSeries;
AChartSeries: TPieSeries); AChartSeries: TPieSeries);
begin begin
@ -2232,4 +2217,23 @@ begin
end; end;
end; end;
procedure TsWorkbookChartLink.UpdateStockSeries(AWorkbookSeries: TsStockSeries;
AChartSeries: TStockSeries);
begin
if AWorkbookSeries.CandleStick then
begin
AChartSeries.Mode := mCandleStick;
UpdateChartBrush(AWorkbookseries.Chart, AWorkbookseries.CandleStickDownFill, AChartSeries.CandleStickDownBrush);
UpdateChartBrush(AWorkbookSeries.Chart, AWorkbookSeries.CandleStickUpFill, AChartSeries.CandleStickUpBrush);
UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.CandleStickDownBorder, AChartSeries.CandleStickDownPen);
UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.CandleStickUpBorder, AChartSeries.CandleStickUpPen);
end else
begin
AChartSeries.Mode := mOHLC;
end;
UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.RangeLine, AChartSeries.LinePen);
UpdateChartPen(AWorkbookSeries.Chart, AWorkbookSeries.RangeLine, AChartSeries.DownLinePen);
AChartSeries.TickWidthStyle := twsPercentMin;
end;
end. end.

View File

@ -0,0 +1,736 @@
unit fpsStockSeries;
{$mode objfpc}{$H+}
interface
uses
LCLVersion, Classes, SysUtils, Graphics, Math,
TAChartUtils, TAMath, TAGeometry, TADrawUtils, TALegend,
TACustomSource, TACustomSeries, TAMultiSeries;
type
{$IF LCL_FullVersion >= 3990000}
TStockSeries = class(TOpenHighLowCloseSeries);
{$ELSE}
TOHLCBrushKind = (obkCandleUp, obkCandleDown);
TOHLCPenKind = (opkCandleUp, opkCandleDown, opkCandleLine, opkLineUp, opkLineDown);
TOHLCBrush = class(TBrush)
private
const
DEFAULT_COLORS: array[TOHLCBrushKind] of TColor = (clLime, clRed);
private
FBrushKind: TOHLCBrushKind;
function IsColorStored: Boolean;
procedure SetBrushKind(AValue: TOHLCBrushKind);
public
property BrushKind: TOHLCBrushKind read FBrushKind write SetBrushKind;
published
property Color stored IsColorStored;
end;
TOHLCPen = class(TPen)
private
const
DEFAULT_COLORS: array[TOHLCPenKind] of TColor = (clGreen, clMaroon, clDefault, clLime, clRed);
private
FPenKind: TOHLCPenKind;
function IsColorStored: Boolean;
procedure SetPenKind(AValue: TOHLCPenKind);
public
property PenKind: TOHLCPenKind read FPenKind write SetPenKind;
published
property Color stored IsColorStored;
end;
TOHLCMode = (mOHLC, mCandleStick);
TTickWidthStyle = (twsPercent, twsPercentMin);
TStockSeries = class(TBasicPointSeries)
private
FPen: array[TOHLCPenKind] of TOHLCPen;
FBrush: array[TOHLCBrushKind] of TOHLCBrush;
FTickWidth: Integer;
FTickWidthStyle: TTickWidthStyle;
FYIndexClose: Integer;
FYIndexHigh: Integer;
FYIndexLow: Integer;
FYIndexOpen: Integer;
FMode: TOHLCMode;
function GetBrush(AIndex: TOHLCBrushKind): TOHLCBrush;
function GetPen(AIndex: TOHLCPenKind): TOHLCPen;
procedure SetBrush(AIndex: TOHLCBrushKind; AValue: TOHLCBrush);
procedure SetPen(AIndex: TOHLCPenKind; AValue: TOHLCPen);
procedure SetOHLCMode(AValue: TOHLCMode);
procedure SetTickWidth(AValue: Integer);
procedure SetTickWidthStyle(AValue: TTickWidthStyle);
procedure SetYIndexClose(AValue: Integer);
procedure SetYIndexHigh(AValue: Integer);
procedure SetYIndexLow(AValue: Integer);
procedure SetYIndexOpen(AValue: Integer);
protected
function CalcTickWidth(AX: Double; AIndex: Integer): Double;
procedure GetLegendItems(AItems: TChartLegendItems); override;
function GetSeriesColor: TColor; override;
class procedure GetXYCountNeeded(out AXCount, AYCount: Cardinal); override;
function SkipMissingValues(AIndex: Integer): Boolean; override;
function ToolTargetDistance(const AParams: TNearestPointParams;
AGraphPt: TDoublePoint; APointIdx, AXIdx, AYIdx: Integer): Integer; override;
procedure UpdateLabelDirectionReferenceLevel(AIndex, AYIndex: Integer;
var ALevel: Double); override;
public
procedure Assign(ASource: TPersistent); override;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
public
function AddXOHLC(
AX, AOpen, AHigh, ALow, AClose: Double;
ALabel: String = ''; AColor: TColor = clTAColor): Integer; inline;
procedure Draw(ADrawer: IChartDrawer); override;
function Extent: TDoubleRect; override;
function GetNearestPoint(const AParams: TNearestPointParams;
out AResults: TNearestPointResults): Boolean; override;
published
property CandlestickDownBrush: TOHLCBrush index obkCandleDown read GetBrush write SetBrush;
property CandlestickDownPen: TOHLCPen index opkCandleDown read GetPen write SetPen;
property CandlestickLinePen: TOHLCPen index opkCandleLine read GetPen write SetPen;
property CandlestickUpBrush: TOHLCBrush index obkCandleUp read GetBrush write SetBrush;
property CandlestickUpPen: TOHLCPen index opkCandleUp read GetPen write Setpen;
property DownLinePen: TOHLCPen index opkLineDown read GetPen write SetPen;
property LinePen: TOHLCPen index opkLineUp read GetPen write SetPen;
property Mode: TOHLCMode read FMode write SetOHLCMode default mOHLC;
property TickWidth: integer
read FTickWidth write SetTickWidth default DEF_OHLC_TICK_WIDTH;
property TickWidthStyle: TTickWidthStyle
read FTickWidthStyle write SetTickWidthStyle default twsPercent;
property ToolTargets default [nptPoint, nptYList, nptCustom];
property YIndexClose: integer
read FYIndexClose write SetYIndexClose default DEF_YINDEX_CLOSE;
property YIndexHigh: Integer
read FYIndexHigh write SetYIndexHigh default DEF_YINDEX_HIGH;
property YIndexLow: Integer
read FYIndexLow write SetYIndexLow default DEF_YINDEX_LOW;
property YIndexOpen: Integer
read FYIndexOpen write SetYIndexOpen default DEF_YINDEX_OPEN;
published
property AxisIndexX;
property AxisIndexY;
property MarkPositions;
property Marks;
property Source;
end;
{$ENDIF}
implementation
{$IF LCL_FullVersion < 3990000}
uses
FPCanvas;
type
TLegendItemOHLCLine = class(TLegendItemLine)
strict private
FMode: TOHLCMode;
FCandleStickUpColor: TColor;
FCandleStickDownColor: TColor;
public
constructor Create(ASeries: TStockSeries; const AText: String);
procedure Draw(ADrawer: IChartDrawer; const ARect: TRect); override;
end;
constructor TLegendItemOHLCLine.Create(ASeries: TStockSeries; const AText: String);
var
pen: TFPCustomPen;
begin
case ASeries.Mode of
mOHLC : pen := ASeries.LinePen;
mCandleStick : pen := ASeries.CandleStickLinePen;
end;
inherited Create(pen, AText);
FMode := ASeries.Mode;
FCandlestickUpColor := ASeries.CandlestickUpBrush.Color;
FCandlestickDownColor := ASeries.CandlestickDownBrush.Color;
end;
procedure TLegendItemOHLCLine.Draw(ADrawer: IChartDrawer; const ARect: TRect);
const
TICK_LENGTH = 3;
var
dx, dy, x, y: Integer;
pts: array[0..3] of TPoint;
begin
inherited Draw(ADrawer, ARect);
y := (ARect.Top + ARect.Bottom) div 2;
dx := (ARect.Right - ARect.Left) div 3;
x := ARect.Left + dx;
case FMode of
mOHLC:
begin
dy := ADrawer.Scale(TICK_LENGTH);
ADrawer.Line(x, y, x, y + dy);
x += dx;
ADrawer.Line(x, y, x, y - dy);
end;
mCandlestick:
begin
dy := (ARect.Bottom - ARect.Top) div 4;
pts[0] := Point(x, y-dy);
pts[1] := Point(x, y+dy);
pts[2] := Point(x+dx, y+dy);
pts[3] := pts[0];
ADrawer.SetBrushParams(bsSolid, FCandlestickUpColor);
ADrawer.Polygon(pts, 0, 4);
pts[0] := Point(x+dx, y+dy);
pts[1] := Point(x+dx, y-dy);
pts[2] := Point(x, y-dy);
pts[3] := pts[0];
ADrawer.SetBrushParams(bsSolid, FCandlestickDownColor);
ADrawer.Polygon(pts, 0, 4);
end;
end;
end;
{ TOHLCBrush }
function TOHLCBrush.IsColorStored: Boolean;
begin
Result := (Color = DEFAULT_COLORS[FBrushKind]);
end;
procedure TOHLCBrush.SetBrushKind(AValue: TOHLCBrushKind);
begin
FBrushKind := AValue;
Color := DEFAULT_COLORS[FBrushKind];
end;
{ TOHLCPen }
function TOHLCPen.IsColorStored: Boolean;
begin
Result := (Color = DEFAULT_COLORS[FPenKind]);
end;
procedure TOHLCPen.SetPenKind(AValue: TOHLCPenKind);
begin
FPenKind := AValue;
Color := DEFAULT_COLORS[FPenKind];
end;
{ TStockSeries }
function TStockSeries.AddXOHLC(
AX, AOpen, AHigh, ALow, AClose: Double;
ALabel: String; AColor: TColor): Integer;
var
y: Double;
begin
if YIndexOpen = 0 then
y := AOpen
else if YIndexHigh = 0 then
y := AHigh
else if YIndexLow = 0 then
y := ALow
else if YIndexClose = 0 then
y := AClose
else
raise Exception.Create('TOpenHighLowCloseSeries: Ordinary y value missing');
Result := ListSource.Add(AX, y, ALabel, AColor);
with ListSource.Item[Result]^ do begin
SetY(YIndexOpen, AOpen);
SetY(YIndexHigh, AHigh);
SetY(YIndexLow, ALow);
SetY(YIndexClose, AClose);
end;
end;
procedure TStockSeries.Assign(ASource: TPersistent);
var
bk: TOHLCBrushKind;
pk: TOHLCPenKind;
begin
if ASource is TStockSeries then
with TOpenHighLowCloseSeries(ASource) do begin
for bk in TOHLCBrushKind do
Self.FBrush[bk] := FBrush[bk];
for pk in TOHLCPenKind do
Self.FPen[pk] := FPen[pk];
Self.FMode := FMode;
Self.FTickWidth := FTickWidth;
Self.FYIndexClose := FYIndexClose;
Self.FYIndexHigh := FYIndexHigh;
Self.FYIndexLow := FYIndexLow;
Self.FYIndexOpen := FYIndexOpen;
end;
inherited Assign(ASource);
end;
constructor TStockSeries.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ToolTargets := [nptPoint, nptYList, nptCustom];
FOptimizeX := false;
FStacked := false;
FTickWidth := DEF_OHLC_TICK_WIDTH;
FYIndexClose := DEF_YINDEX_CLOSE;
FYIndexHigh := DEF_YINDEX_HIGH;
FYIndexLow := DEF_YINDEX_LOW;
FYIndexOpen := DEF_YINDEX_OPEN;
// Candlestick up brush
FBrush[obkCandleUp] := TOHLCBrush.Create;
FBrush[obkCandleUp].BrushKind := obkCandleUp;
FBrush[obkCandleUp].OnChange := @StyleChanged;
// Candlestick down brush
FBrush[obkCandleDown] := TOHLCBrush.Create;
FBrush[obkCandleDown].BrushKind := obkCandleDown;
FBrush[obkCandleDown].OnChange := @StyleChanged;
// Candlestick up border pen
FPen[opkCandleUp] := TOHLCPen.Create;
FPen[opkCandleUp].PenKind := opkCandleUp;
FPen[opkCandleUp].OnChange := @StyleChanged;
// Candlestick down border pen
FPen[opkCandleDown] := TOHLCPen.Create;
FPen[opkCandleDown].PenKind := opkCandleDown;
FPen[opkCandleDown].OnChange := @StyleChanged;
// Candlestick range pen
FPen[opkCandleLine] := TOHLCPen.Create;
FPen[opkCandleLine].PenKind := opkCandleLine;
FPen[opkCandleLine].OnChange := @StyleChanged;
// OHLC up pen
FPen[opkLineUp] := TOHLCPen.Create;
FPen[opkLineUp].PenKind := opkLineUp;
FPen[opkLineUp].OnChange := @StyleChanged;
// OHLC down pen
FPen[opkLineDown] := TOHLCPen.Create;
FPen[opkLineDown].PenKind := opkLineDown;
FPen[opkLineDown].OnChange := @StyleChanged;
end;
destructor TStockSeries.Destroy;
var
bk: TOHLCBrushKind;
pk: TOHLCPenKind;
begin
for bk in TOHLCBrushKind do
FreeAndNil(FBrush[bk]);
for pk in TOHLCPenKind do
FreeAndNil(FPen[pk]);
inherited;
end;
function TStockSeries.CalcTickWidth(AX: Double; AIndex: Integer): Double;
begin
case FTickWidthStyle of
twsPercent:
Result := GetXRange(AX, AIndex) * PERCENT * TickWidth;
twsPercentMin:
begin
if FMinXRange = 0 then
UpdateMinXRange;
Result := FMinXRange * PERCENT * TickWidth;
end;
end;
end;
procedure TStockSeries.Draw(ADrawer: IChartDrawer);
function MaybeRotate(AX, AY: Double): TPoint;
begin
if IsRotated then
Exchange(AX, AY);
Result := ParentChart.GraphToImage(DoublePoint(AX, AY));
end;
procedure DoLine(AX1, AY1, AX2, AY2: Double);
begin
ADrawer.Line(MaybeRotate(AX1, AY1), MaybeRotate(AX2, AY2));
end;
procedure DoRect(AX1, AY1, AX2, AY2: Double);
var
r: TRect;
begin
with ParentChart do begin
r.TopLeft := MaybeRotate(AX1, AY1);
r.BottomRight := MaybeRotate(AX2, AY2);
end;
ADrawer.FillRect(r.Left, r.Top, r.Right, r.Bottom);
ADrawer.Rectangle(r);
end;
procedure DrawOHLC(x, yopen, yhigh, ylow, yclose, tw: Double);
begin
DoLine(x, yhigh, x, ylow);
DoLine(x, yclose, x + tw, yclose);
if not IsNaN(yopen) then
DoLine(x - tw, yopen, x, yopen);
end;
procedure DrawCandleStick(x, yopen, yhigh, ylow, yclose, tw: Double; APenIdx: Integer);
begin
if CandleStickLinePen.Color = clDefault then
// use linepen and linedown pen for range line
ADrawer.Pen := FPen[TOHLCPenKind(APenIdx + 3)]
else
ADrawer.Pen := CandleStickLinePen;
DoLine(x, yhigh, x, ylow);
ADrawer.Pen := FPen[TOHLCPenKind(APenIdx)];
DoRect(x - tw, yopen, x + tw, yclose);
end;
const
UP_INDEX = 0;
DOWN_INDEX = 1;
var
my: Cardinal;
ext2: TDoubleRect;
i: Integer;
x, tw, yopen, yhigh, ylow, yclose, prevclose: Double;
idx: Integer;
nx, ny: Cardinal;
begin
if IsEmpty or (not Active) then exit;
my := MaxIntValue([YIndexOpen, YIndexHigh, YIndexLow, YIndexClose]);
if my >= Source.YCount then exit;
ext2 := ParentChart.CurrentExtent;
ExpandRange(ext2.a.X, ext2.b.X, 1.0);
ExpandRange(ext2.a.Y, ext2.b.Y, 1.0);
PrepareGraphPoints(ext2, true);
prevclose := -Infinity;
for i := FLoBound to FUpBound do begin
x := GetGraphPointX(i);
if IsNaN(x) then Continue;
yopen := GetGraphPointY(i, YIndexOpen);
if IsNaN(yopen) and (FMode = mCandleStick) then Continue;
yhigh := GetGraphPointY(i, YIndexHigh);
if IsNaN(yhigh) then Continue;
ylow := GetGraphPointY(i, YIndexLow);
if IsNaN(ylow) then Continue;
yclose := GetGraphPointY(i, YIndexClose);
if IsNaN(yclose) then Continue;
tw := CalcTickWidth(x, i);
if IsNaN(yopen) then
begin
// HLC chart: compare with close value of previous data point
if prevclose < yclose then
idx := UP_INDEX
else
idx := DOWN_INDEX;
end else
if (yopen <= yclose) then
idx := UP_INDEX
else
idx := DOWN_INDEX;
ADrawer.Brush := FBrush[TOHLCBrushKind(idx)];
case FMode of
mOHLC: ADrawer.Pen := FPen[TOHLCPenKind(idx + 3)];
mCandlestick: ADrawer.Pen := FPen[TOHLCPenKind(idx)];
end;
if Source[i]^.Color <> clTAColor then
begin
ADrawer.SetPenParams(FPen[TOHLCPenKind(idx)].Style, Source[i]^.Color, FPen[TOHLCPenKind(idx)].Width);
ADrawer.SetBrushParams(FBrush[TOHLCBrushKind(idx)].Style, Source[i]^.Color);
end;
case FMode of
mOHLC: DrawOHLC(x, yopen, yhigh, ylow, yclose, tw);
mCandleStick: DrawCandleStick(x, yopen, yhigh, ylow, yclose, tw, idx);
end;
prevclose := yclose;
end;
GetXYCountNeeded(nx, ny);
if Source.YCount > ny then
for i := 0 to ny-1 do DrawLabels(ADrawer, i)
else
DrawLabels(ADrawer);
end;
function TStockSeries.Extent: TDoubleRect;
var
x: Double;
tw: Double;
j: Integer;
begin
Result := Source.ExtentList; // axis units
// Enforce recalculation of tick/candlebox width
FMinXRange := 0;
// Show first and last open/close ticks and candle boxes fully.
j := -1;
x := NaN;
while IsNaN(x) and (j < Source.Count-1) do begin
inc(j);
x := GetGraphPointX(j); // graph units
end;
tw := CalcTickWidth(x, j);
Result.a.X := Min(Result.a.X, GraphToAxisX(x - tw)); // axis units
// Result.a.X := Min(Result.a.X, x - tw);
j := Count;
x := NaN;
While IsNaN(x) and (j > 0) do begin
dec(j);
x := GetGraphPointX(j);
end;
tw := CalcTickWidth(x, j);
Result.b.X := Max(Result.b.X, AxisToGraphX(x + tw));
// Result.b.X := Max(Result.b.X, x + tw);
end;
function TStockSeries.GetBrush(AIndex: TOHLCBrushKind): TOHLCBrush;
begin
Result := FBrush[AIndex];
end;
procedure TStockSeries.GetLegendItems(AItems: TChartLegendItems);
begin
AItems.Add(TLegendItemOHLCLine.Create(Self, LegendTextSingle));
end;
function TStockSeries.GetNearestPoint(const AParams: TNearestPointParams;
out AResults: TNearestPointResults): Boolean;
var
i: Integer;
graphClickPt, p: TDoublePoint;
pImg: TPoint;
x, yopen, yhigh, ylow, yclose, tw: Double;
xImg, dist: Integer;
R: TDoubleRect;
begin
Result := inherited;
if Result then begin
if (nptPoint in AParams.FTargets) and (nptPoint in ToolTargets) then
exit;
if (nptYList in AParams.FTargets) and (nptYList in ToolTargets) then
exit;
end;
if not ((nptCustom in AParams.FTargets) and (nptCustom in ToolTargets))
then
exit;
graphClickPt := ParentChart.ImageToGraph(AParams.FPoint);
pImg := AParams.FPoint;
if IsRotated then begin
// Exchange(pImg.X, pImg.Y);
Exchange(graphclickpt.X, graphclickpt.Y);
pImg := ParentChart.GraphToImage(graphClickPt);
end;
// Iterate through all points of the series
for i := 0 to Count - 1 do begin
x := GetGraphPointX(i);
yopen := GetGraphPointY(i, YIndexOpen);
yhigh := GetGraphPointY(i, YIndexHigh);
ylow := GetGraphPointY(i, YIndexLow);
yclose := GetGraphPointY(i, YIndexClose);
tw := CalcTickWidth(x, i);
dist := MaxInt;
// click on vertical line
if InRange(graphClickPt.Y, ylow, yhigh) then begin
xImg := ParentChart.XGraphToImage(x);
dist := sqr(pImg.X - xImg);
AResults.FYIndex := -1;
end;
// click on candle box
if FMode = mCandlestick then begin
R.a := DoublePoint(x - tw, Min(yopen, yclose));
R.b := DoublePoint(x + tw, Max(yopen, yclose));
if InRange(graphClickPt.X, R.a.x, R.b.x) and InRange(graphClickPt.Y, R.a.Y, R.b.Y) then
begin
dist := 0;
AResults.FYIndex := -1;
end;
end;
// Sufficiently close?
if dist < AResults.FDist then begin
AResults.FDist := dist;
AResults.FIndex := i;
p := DoublePoint(x, yclose); // "Close" value
AResults.FValue := p;
if IsRotated then Exchange(p.X, p.Y);
AResults.FImg := ParentChart.GraphToImage(p);
if dist = 0 then break;
end;
end;
Result := AResults.FIndex > -1;
end;
function TStockSeries.GetPen(AIndex: TOHLCPenKind): TOHLCPen;
begin
Result := FPen[AIndex];
end;
function TStockSeries.GetSeriesColor: TColor;
begin
Result := LinePen.Color;
end;
class procedure TStockSeries.GetXYCountNeeded(out AXCount, AYCount: Cardinal);
begin
AXCount := 0;
AYCount := 4;
end;
procedure TStockSeries.SetBrush(AIndex: TOHLCBrushKind; AValue: TOHLCBrush);
begin
if GetBrush(AIndex) = AValue then exit;
FBrush[AIndex].Assign(AValue);
UpdateParentChart;
end;
procedure TStockSeries.SetPen(AIndex: TOHLCPenKind; AValue: TOHLCPen);
begin
if GetPen(AIndex) = AValue then exit;
FPen[AIndex].Assign(AValue);
UpdateParentChart;
end;
procedure TStockSeries.SetOHLCMode(AValue: TOHLCMode);
begin
if FMode = AValue then exit;
FMode := AValue;
UpdateParentChart;
end;
procedure TStockSeries.SetTickWidth(AValue: Integer);
begin
if FTickWidth = AValue then exit;
FTickWidth := AValue;
UpdateParentChart;
end;
procedure TStockSeries.SetTickWidthStyle(AValue: TTickWidthStyle);
begin
if FTickWidthStyle = AValue then exit;
FTickWidthStyle := AValue;
UpdateParentChart;
end;
procedure TStockSeries.SetYIndexClose(AValue: Integer);
begin
if FYIndexClose = AValue then exit;
FYIndexClose := AValue;
UpdateParentChart;
end;
procedure TStockSeries.SetYIndexHigh(AValue: Integer);
begin
if FYIndexHigh = AValue then exit;
FYIndexHigh := AValue;
UpdateParentChart;
end;
procedure TStockSeries.SetYIndexLow(AValue: Integer);
begin
if FYIndexLow = AValue then exit;
FYIndexLow := AValue;
UpdateParentChart;
end;
procedure TStockSeries.SetYIndexOpen(AValue: Integer);
begin
if FYIndexOpen = AValue then exit;
FYIndexOpen := AValue;
UpdateParentChart;
end;
function TStockSeries.SkipMissingValues(AIndex: Integer): Boolean;
begin
Result := IsNaN(Source[AIndex]^.Point);
if not Result then
Result := HasMissingYValue(AIndex, 4);
end;
function TStockSeries.ToolTargetDistance(
const AParams: TNearestPointParams; AGraphPt: TDoublePoint;
APointIdx, AXIdx, AYIdx: Integer): Integer;
// All in image coordinates transformed to have a horizontal x axis
function DistanceToLine(Pt: TPoint; x1, x2, y: Integer): Integer;
begin
if InRange(Pt.X, x1, x2) then // FDistFunc does not calculate sqrt
Result := sqr(Pt.Y - y)
else
Result := Min(
AParams.FDistFunc(Pt, Point(x1, y)),
AParams.FDistFunc(Pt, Point(x2, y))
);
end;
var
x1, x2: Integer;
w: Double;
p, clickPt: TPoint;
gp: TDoublePoint;
begin
Unused(AXIdx);
// Convert the "clicked" and "test" point to non-rotated axes
if IsRotated then begin
gp := ParentChart.ImageToGraph(AParams.FPoint);
Exchange(gp.X, gp.Y);
clickPt := ParentChart.GraphToImage(gp);
Exchange(AGraphPt.X, AGraphPt.Y);
end else
clickPt := AParams.FPoint;
w := CalcTickWidth(AGraphPt.X, APointIdx);
x1 := ParentChart.XGraphToImage(AGraphPt.X - w);
x2 := ParentChart.XGraphToImage(AGraphPt.X + w);
p := ParentChart.GraphToImage(AGraphPt);
case FMode of
mOHLC:
with ParentChart do
if (AYIdx = YIndexOpen) then
Result := DistanceToLine(clickPt, x1, p.x, p.y)
else if (AYIdx = YIndexClose) then
Result := DistanceToLine(clickPt, p.x, x2, p.y)
else if (AYIdx = YIndexHigh) or (AYIdx = YIndexLow) then
Result := AParams.FDistFunc(clickPt, p)
else
raise Exception.Create('TOpenHighLowCloseSeries.ToolTargetDistance: Illegal YIndex.');
mCandleStick:
with ParentChart do
if (AYIdx = YIndexOpen) or (AYIdx = YIndexClose) then
Result := DistanceToLine(clickPt, x1, x2, p.y)
else if (AYIdx = YIndexHigh) or (AYIdx = YIndexLow) then
Result := AParams.FDistFunc(clickPt, p)
else
raise Exception.Create('TOpenHighLowCloseSeries.ToolTargetDistance: Illegal YIndex.');
end;
end;
procedure TStockSeries.UpdateLabelDirectionReferenceLevel(
AIndex, AYIndex: Integer; var ALevel: Double);
var
item: PChartDataItem;
begin
if AYIndex = FYIndexLow then
ALevel := +Infinity
else if AYIndex = FYIndexHigh then
ALevel := -Infinity
else begin
item := Source.Item[AIndex];
ALevel := (AxisToGraphY(item^.GetY(FYIndexLow)) + AxisToGraphY(item^.GetY(FYIndexHigh)))*0.5;
end;
end;
{$ENDIF}
end.