TAChart: Simplification of color handling in TOpenHighLowCloseSeries

This commit is contained in:
wp_xyz 2023-12-07 23:43:07 +01:00
parent e2c8faccd9
commit e3d2e3d9de

View File

@ -191,9 +191,35 @@ type
property Source; property Source;
end; end;
TOHLCDownPen = class(TPen) 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 published
property Color default clTAColor; 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; end;
TOHLCMode = (mOHLC, mCandleStick); TOHLCMode = (mOHLC, mCandleStick);
@ -201,11 +227,8 @@ type
TOpenHighLowCloseSeries = class(TBasicPointSeries) TOpenHighLowCloseSeries = class(TBasicPointSeries)
private private
FCandlestickDownBrush: TBrush; FPen: array[TOHLCPenKind] of TOHLCPen;
FCandleStickLinePen: TPen; FBrush: array[TOHLCBrushKind] of TOHLCBrush;
FCandlestickUpBrush: TBrush;
FDownLinePen: TOHLCDownPen;
FLinePen: TPen;
FTickWidth: Integer; FTickWidth: Integer;
FTickWidthStyle: TTickWidthStyle; FTickWidthStyle: TTickWidthStyle;
FYIndexClose: Integer; FYIndexClose: Integer;
@ -213,11 +236,10 @@ type
FYIndexLow: Integer; FYIndexLow: Integer;
FYIndexOpen: Integer; FYIndexOpen: Integer;
FMode: TOHLCMode; FMode: TOHLCMode;
procedure SetCandlestickLinePen(AValue: TPen); function GetBrush(AIndex: TOHLCBrushKind): TOHLCBrush;
procedure SetCandlestickDownBrush(AValue: TBrush); function GetPen(AIndex: TOHLCPenKind): TOHLCPen;
procedure SetCandlestickUpBrush(AValue: TBrush); procedure SetBrush(AIndex: TOHLCBrushKind; AValue: TOHLCBrush);
procedure SetDownLinePen(AValue: TOHLCDownPen); procedure SetPen(AIndex: TOHLCPenKind; AValue: TOHLCPen);
procedure SetLinePen(AValue: TPen);
procedure SetOHLCMode(AValue: TOHLCMode); procedure SetOHLCMode(AValue: TOHLCMode);
procedure SetTickWidth(AValue: Integer); procedure SetTickWidth(AValue: Integer);
procedure SetTickWidthStyle(AValue: TTickWidthStyle); procedure SetTickWidthStyle(AValue: TTickWidthStyle);
@ -226,6 +248,7 @@ type
procedure SetYIndexLow(AValue: Integer); procedure SetYIndexLow(AValue: Integer);
procedure SetYIndexOpen(AValue: Integer); procedure SetYIndexOpen(AValue: Integer);
protected protected
function CalcTickWidth(AX: Double; AIndex: Integer): Double;
procedure GetLegendItems(AItems: TChartLegendItems); override; procedure GetLegendItems(AItems: TChartLegendItems); override;
function GetSeriesColor: TColor; override; function GetSeriesColor: TColor; override;
class procedure GetXYCountNeeded(out AXCount, AYCount: Cardinal); override; class procedure GetXYCountNeeded(out AXCount, AYCount: Cardinal); override;
@ -238,6 +261,7 @@ type
procedure Assign(ASource: TPersistent); override; procedure Assign(ASource: TPersistent); override;
constructor Create(AOwner: TComponent); override; constructor Create(AOwner: TComponent); override;
destructor Destroy; override; destructor Destroy; override;
public
function AddXOHLC( function AddXOHLC(
AX, AOpen, AHigh, ALow, AClose: Double; AX, AOpen, AHigh, ALow, AClose: Double;
ALabel: String = ''; AColor: TColor = clTAColor): Integer; inline; ALabel: String = ''; AColor: TColor = clTAColor): Integer; inline;
@ -246,14 +270,13 @@ type
function GetNearestPoint(const AParams: TNearestPointParams; function GetNearestPoint(const AParams: TNearestPointParams;
out AResults: TNearestPointResults): Boolean; override; out AResults: TNearestPointResults): Boolean; override;
published published
property CandlestickDownBrush: TBrush property CandlestickDownBrush: TOHLCBrush index obkCandleDown read GetBrush write SetBrush;
read FCandlestickDownBrush write SetCandlestickDownBrush; property CandlestickDownPen: TOHLCPen index opkCandleDown read GetPen write SetPen;
property CandlestickLinePen: TPen property CandlestickLinePen: TOHLCPen index opkCandleLine read GetPen write SetPen;
read FCandlestickLinePen write FCandleStickLinePen; property CandlestickUpBrush: TOHLCBrush index obkCandleUp read GetBrush write SetBrush;
property CandlestickUpBrush: TBrush property CandlestickUpPen: TOHLCPen index opkCandleUp read GetPen write Setpen;
read FCandlestickUpBrush write SetCandlestickUpBrush; property DownLinePen: TOHLCPen index opkLineDown read GetPen write SetPen;
property DownLinePen: TOHLCDownPen read FDownLinePen write SetDownLinePen; property LinePen: TOHLCPen index opkLineUp read GetPen write SetPen;
property LinePen: TPen read FLinePen write SetLinePen;
property Mode: TOHLCMode read FMode write SetOHLCMode default mOHLC; property Mode: TOHLCMode read FMode write SetOHLCMode default mOHLC;
property TickWidth: integer property TickWidth: integer
read FTickWidth write SetTickWidth default DEF_OHLC_TICK_WIDTH; read FTickWidth write SetTickWidth default DEF_OHLC_TICK_WIDTH;
@ -1634,6 +1657,31 @@ begin
FYDataLayout := bwlCustom; FYDataLayout := bwlCustom;
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;
{ TOpenHighLowCloseSeries } { TOpenHighLowCloseSeries }
@ -1664,14 +1712,16 @@ begin
end; end;
procedure TOpenHighLowCloseSeries.Assign(ASource: TPersistent); procedure TOpenHighLowCloseSeries.Assign(ASource: TPersistent);
var
bk: TOHLCBrushKind;
pk: TOHLCPenKind;
begin begin
if ASource is TOpenHighLowCloseSeries then if ASource is TOpenHighLowCloseSeries then
with TOpenHighLowCloseSeries(ASource) do begin with TOpenHighLowCloseSeries(ASource) do begin
Self.FCandlestickDownBrush := FCandlestickDownBrush; for bk in TOHLCBrushKind do
Self.FCandlestickLinePen := FCandlestickLinePen; Self.FBrush[bk] := FBrush[bk];
Self.FCandlestickUpBrush := FCandlestickUpBrush; for pk in TOHLCPenKind do
Self.FDownLinePen := FDownLinePen; Self.FPen[pk] := FPen[pk];
Self.FLinePen := FLinePen;
Self.FMode := FMode; Self.FMode := FMode;
Self.FTickWidth := FTickWidth; Self.FTickWidth := FTickWidth;
Self.FYIndexClose := FYIndexClose; Self.FYIndexClose := FYIndexClose;
@ -1685,58 +1735,73 @@ end;
constructor TOpenHighLowCloseSeries.Create(AOwner: TComponent); constructor TOpenHighLowCloseSeries.Create(AOwner: TComponent);
begin begin
inherited Create(AOwner); inherited Create(AOwner);
ToolTargets := [nptPoint, nptYList, nptCustom]; ToolTargets := [nptPoint, nptYList, nptCustom];
FOptimizeX := false; FOptimizeX := false;
FStacked := false; FStacked := false;
FCandlestickDownBrush := TBrush.Create;
with FCandlestickDownBrush do begin
Color := clRed;
OnChange := @StyleChanged;
end;
FCandlestickLinePen := TPen.Create;
with FCandlestickLinePen do begin
Color := clBlack;
OnChange := @StyleChanged;
end;
FCandlestickUpBrush := TBrush.Create;
with FCandlestickUpBrush do begin
Color := clLime;
OnChange := @StyleChanged;
end;
FDownLinePen := TOHLCDownPen.Create;
with FDownLinePen do begin
Color := clTAColor;
OnChange := @StyleChanged;
end;
FLinePen := TPen.Create;
with FLinePen do
OnChange := @StyleChanged;
FTickWidth := DEF_OHLC_TICK_WIDTH; FTickWidth := DEF_OHLC_TICK_WIDTH;
FYIndexClose := DEF_YINDEX_CLOSE; FYIndexClose := DEF_YINDEX_CLOSE;
FYIndexHigh := DEF_YINDEX_HIGH; FYIndexHigh := DEF_YINDEX_HIGH;
FYIndexLow := DEF_YINDEX_LOW; FYIndexLow := DEF_YINDEX_LOW;
FYIndexOpen := DEF_YINDEX_OPEN; 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; end;
destructor TOpenHighLowCloseSeries.Destroy; destructor TOpenHighLowCloseSeries.Destroy;
var
bk: TOHLCBrushKind;
pk: TOHLCPenKind;
begin begin
FreeAndNil(FCandlestickDownBrush); for bk in TOHLCBrushKind do
FreeAndNil(FCandlestickLinePen); FreeAndNil(FBrush[bk]);
FreeAndNil(FCandlestickUpBrush); for pk in TOHLCPenKind do
FreeAndNil(FDownLinePen); FreeAndNil(FPen[pk]);
FreeAndNil(FLinePen);
inherited; inherited;
end; end;
procedure TOpenHighLowCloseSeries.Draw(ADrawer: IChartDrawer); function TOpenHighLowCloseSeries.CalcTickWidth(AX: Double; AIndex: Integer): Double;
begin
function CalcTickWidth(AX: Double; AIndex: Integer): Double; case FTickWidthStyle of
begin twsPercent:
case FTickWidthStyle of Result := GetXRange(AX, AIndex) * PERCENT * TickWidth;
twsPercent: Result := GetXRange(AX, AIndex) * PERCENT * TickWidth; twsPercentMin:
twsPercentMin: Result := FMinXRange * PERCENT * TickWidth; begin
end; if FMinXRange = 0 then
UpdateMinXRange;
Result := FMinXRange * PERCENT * TickWidth;
end;
end; end;
end;
procedure TOpenHighLowCloseSeries.Draw(ADrawer: IChartDrawer);
function MaybeRotate(AX, AY: Double): TPoint; function MaybeRotate(AX, AY: Double): TPoint;
begin begin
@ -1770,29 +1835,27 @@ procedure TOpenHighLowCloseSeries.Draw(ADrawer: IChartDrawer);
DoLine(x - tw, yopen, x, yopen); DoLine(x - tw, yopen, x, yopen);
end; end;
procedure DrawCandleStick(x, yopen, yhigh, ylow, yclose, tw: Double); procedure DrawCandleStick(x, yopen, yhigh, ylow, yclose, tw: Double; APenIdx: Integer);
var
clr: TColor;
begin begin
ADrawer.Pen := FCandlestickLinePen; if CandleStickLinePen.Color = clDefault then
if FCandleStickLinePen.Color = clDefault then begin // use linepen and linedown pen for range line
if yopen <= yclose then ADrawer.Pen := FPen[TOHLCPenKind(APenIdx + 3)]
clr := FCandleStickUpBrush.Color else
else ADrawer.Pen := CandleStickLinePen;
clr := FCandleStickDownBrush.Color;
end else
clr := FCandlestickLinePen.Color;
ADrawer.SetPenParams(FCandleStickLinePen.Style, clr);
DoLine(x, yhigh, x, ylow); DoLine(x, yhigh, x, ylow);
ADrawer.Pen := FPen[TOHLCPenKind(APenIdx)];
DoRect(x - tw, yopen, x + tw, yclose); DoRect(x - tw, yopen, x + tw, yclose);
end; end;
const
UP_INDEX = 0;
DOWN_INDEX = 1;
var var
my: Cardinal; my: Cardinal;
ext2: TDoubleRect; ext2: TDoubleRect;
i: Integer; i: Integer;
x, tw, yopen, yhigh, ylow, yclose: Double; x, tw, yopen, yhigh, ylow, yclose, prevclose: Double;
p: TPen; idx: Integer;
nx, ny: Cardinal; nx, ny: Cardinal;
begin begin
if IsEmpty or (not Active) then exit; if IsEmpty or (not Active) then exit;
@ -1803,11 +1866,9 @@ begin
ExpandRange(ext2.a.X, ext2.b.X, 1.0); ExpandRange(ext2.a.X, ext2.b.X, 1.0);
ExpandRange(ext2.a.Y, ext2.b.Y, 1.0); ExpandRange(ext2.a.Y, ext2.b.Y, 1.0);
if TickWidthStyle = twsPercentMin then
UpdateMinXRange;
PrepareGraphPoints(ext2, true); PrepareGraphPoints(ext2, true);
prevclose := -Infinity;
for i := FLoBound to FUpBound do begin for i := FLoBound to FUpBound do begin
x := GetGraphPointX(i); x := GetGraphPointX(i);
if IsNaN(x) then Continue; if IsNaN(x) then Continue;
@ -1820,25 +1881,36 @@ begin
yclose := GetGraphPointY(i, YIndexClose); yclose := GetGraphPointY(i, YIndexClose);
if IsNaN(yclose) then Continue; if IsNaN(yclose) then Continue;
tw := CalcTickWidth(x, i); tw := CalcTickWidth(x, i);
if (not IsNaN(yopen) and (yopen <= yclose)) then begin
p := LinePen; if IsNaN(yopen) then
ADrawer.Brush := FCandleStickUpBrush; begin
ADrawer.SetBrushColor(FCandleStickUpBrush.Color); // HLC chart: compare with close value of previous data point
end if prevclose < yclose then
else begin idx := UP_INDEX
p := DownLinePen; else
ADrawer.Brush := FCandleStickDownBrush; idx := DOWN_INDEX;
ADrawer.SetBrushColor(FCandleStickDownBrush.Color); 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; end;
ADrawer.Pen := p;
with Source[i]^ do
if Color <> clTAColor then
ADrawer.SetPenParams(p.Style, Color);
case FMode of case FMode of
mOHLC: DrawOHLC(x, yopen, yhigh, ylow, yclose, tw); mOHLC: DrawOHLC(x, yopen, yhigh, ylow, yclose, tw);
mCandleStick: DrawCandleStick(x, yopen, yhigh, ylow, yclose, tw); mCandleStick: DrawCandleStick(x, yopen, yhigh, ylow, yclose, tw, idx);
end; end;
prevclose := yclose;
end; end;
GetXYCountNeeded(nx, ny); GetXYCountNeeded(nx, ny);
@ -1855,6 +1927,10 @@ var
j: Integer; j: Integer;
begin begin
Result := Source.ExtentList; // axis units Result := Source.ExtentList; // axis units
// Enforce recalculation of tick/candlebox width
FMinXRange := 0;
// Show first and last open/close ticks and candle boxes fully. // Show first and last open/close ticks and candle boxes fully.
j := -1; j := -1;
x := NaN; x := NaN;
@ -1862,7 +1938,7 @@ begin
inc(j); inc(j);
x := GetGraphPointX(j); // graph units x := GetGraphPointX(j); // graph units
end; end;
tw := GetXRange(x, j) * PERCENT * TickWidth; tw := CalcTickWidth(x, j);
Result.a.X := Min(Result.a.X, GraphToAxisX(x - tw)); // axis units Result.a.X := Min(Result.a.X, GraphToAxisX(x - tw)); // axis units
// Result.a.X := Min(Result.a.X, x - tw); // Result.a.X := Min(Result.a.X, x - tw);
j := Count; j := Count;
@ -1871,11 +1947,16 @@ begin
dec(j); dec(j);
x := GetGraphPointX(j); x := GetGraphPointX(j);
end; end;
tw := GetXRange(x, j) * PERCENT * TickWidth; tw := CalcTickWidth(x, j);
Result.b.X := Max(Result.b.X, AxisToGraphX(x + tw)); Result.b.X := Max(Result.b.X, AxisToGraphX(x + tw));
// Result.b.X := Max(Result.b.X, x + tw); // Result.b.X := Max(Result.b.X, x + tw);
end; end;
function TOpenHighLowCloseSeries.GetBrush(AIndex: TOHLCBrushKind): TOHLCBrush;
begin
Result := FBrush[AIndex];
end;
procedure TOpenHighLowCloseSeries.GetLegendItems(AItems: TChartLegendItems); procedure TOpenHighLowCloseSeries.GetLegendItems(AItems: TChartLegendItems);
begin begin
AItems.Add(TLegendItemOHLCLine.Create(Self, LegendTextSingle)); AItems.Add(TLegendItemOHLCLine.Create(Self, LegendTextSingle));
@ -1918,7 +1999,7 @@ begin
yhigh := GetGraphPointY(i, YIndexHigh); yhigh := GetGraphPointY(i, YIndexHigh);
ylow := GetGraphPointY(i, YIndexLow); ylow := GetGraphPointY(i, YIndexLow);
yclose := GetGraphPointY(i, YIndexClose); yclose := GetGraphPointY(i, YIndexClose);
tw := GetXRange(x, i) * PERCENT * TickWidth; tw := CalcTickWidth(x, i);
dist := MaxInt; dist := MaxInt;
@ -1954,6 +2035,11 @@ begin
Result := AResults.FIndex > -1; Result := AResults.FIndex > -1;
end; end;
function TOpenHighLowCloseSeries.GetPen(AIndex: TOHLCPenKind): TOHLCPen;
begin
Result := FPen[AIndex];
end;
function TOpenHighLowCloseSeries.GetSeriesColor: TColor; function TOpenHighLowCloseSeries.GetSeriesColor: TColor;
begin begin
Result := LinePen.Color; Result := LinePen.Color;
@ -1965,38 +2051,17 @@ begin
AYCount := 4; AYCount := 4;
end; end;
procedure TOpenHighLowCloseSeries.SetCandlestickLinePen(AValue: TPen); procedure TOpenHighLowCloseSeries.SetBrush(AIndex: TOHLCBrushKind; AValue: TOHLCBrush);
begin begin
if FCandleStickLinePen = AValue then exit; if GetBrush(AIndex) = AValue then exit;
FCandleStickLinePen.Assign(AValue); FBrush[AIndex].Assign(AValue);
UpdateParentChart; UpdateParentChart;
end; end;
procedure TOpenHighLowCloseSeries.SetCandlestickDownBrush(AValue: TBrush); procedure TOpenHighLowCloseSeries.SetPen(AIndex: TOHLCPenKind; AValue: TOHLCPen);
begin begin
if FCandlestickDownBrush = AValue then exit; if GetPen(AIndex) = AValue then exit;
FCandlestickDownBrush.Assign(AValue); FPen[AIndex].Assign(AValue);
UpdateParentChart;
end;
procedure TOpenHighLowCloseSeries.SetCandlestickUpBrush(AValue: TBrush);
begin
if FCandlestickUpBrush = AValue then exit;
FCandlestickUpBrush.Assign(AValue);
UpdateParentChart;
end;
procedure TOpenHighLowCloseSeries.SetDownLinePen(AValue: TOHLCDownPen);
begin
if FDownLinePen = AValue then exit;
FDownLinePen.Assign(AValue);
UpdateParentChart;
end;
procedure TOpenHighLowCloseSeries.SetLinePen(AValue: TPen);
begin
if FLinePen = AValue then exit;
FLinePen.Assign(AValue);
UpdateParentChart; UpdateParentChart;
end; end;
@ -2089,7 +2154,7 @@ begin
end else end else
clickPt := AParams.FPoint; clickPt := AParams.FPoint;
w := GetXRange(AGraphPt.X, APointIdx) * PERCENT * TickWidth; w := CalcTickWidth(AGraphPt.X, APointIdx);
x1 := ParentChart.XGraphToImage(AGraphPt.X - w); x1 := ParentChart.XGraphToImage(AGraphPt.X - w);
x2 := ParentChart.XGraphToImage(AGraphPt.X + w); x2 := ParentChart.XGraphToImage(AGraphPt.X + w);
p := ParentChart.GraphToImage(AGraphPt); p := ParentChart.GraphToImage(AGraphPt);