From 12987e232a25b73ff410d13f8443b1c49af6523d Mon Sep 17 00:00:00 2001 From: wp Date: Thu, 12 Jan 2017 21:13:14 +0000 Subject: [PATCH] TAChart: Activate TNearestPointTarget in TOpenHighLowCloseSeries. Fix DataPointTools using nptYList correctly for horizontal bar series. git-svn-id: trunk@53934 - --- .../tachart/demo/barseriestools/main.lfm | 5 +- .../tachart/demo/barseriestools/main.pas | 10 +- components/tachart/tacustomseries.pas | 33 +++-- components/tachart/tamultiseries.pas | 121 +++++++++++++++--- components/tachart/taseries.pas | 41 +++--- 5 files changed, 155 insertions(+), 55 deletions(-) diff --git a/components/tachart/demo/barseriestools/main.lfm b/components/tachart/demo/barseriestools/main.lfm index bc85065c39..1f01a98f98 100644 --- a/components/tachart/demo/barseriestools/main.lfm +++ b/components/tachart/demo/barseriestools/main.lfm @@ -125,9 +125,9 @@ object MainForm: TMainForm Left = 8 Height = 45 Top = 29 - Width = 415 + Width = 472 BorderSpacing.Top = 10 - Caption = 'Left-Click --> DataPointClickTool --> Log message'#13#10'CTRL --> DataPointHintTool --> Hint window'#13#10'Right-Drag --> DataPointDragTool --> change bar height ( for BLUE bars only!)' + Caption = 'Left-Click inside bar --> DataPointClickTool --> Log message'#13#10'CTRL inside bar --> DataPointHintTool --> Hint window'#13#10'Right-Drag of bar end --> DataPointDragTool --> change bar height ( for BLUE bars only!)' ParentColor = False end end @@ -225,6 +225,7 @@ object MainForm: TMainForm object DataPointDragTool: TDataPointDragTool Shift = [ssRight] AffectedSeries = '1' + Targets = [nptPoint, nptXList, nptYList] OnDrag = DataPointDragToolDrag OnDragStart = DataPointDragToolDragStart end diff --git a/components/tachart/demo/barseriestools/main.pas b/components/tachart/demo/barseriestools/main.pas index 7514b9a8d8..6f81032bc5 100644 --- a/components/tachart/demo/barseriestools/main.pas +++ b/components/tachart/demo/barseriestools/main.pas @@ -122,7 +122,8 @@ begin ttl := TBarSeries(ser).Styles.Styles[yidx].Text; Log.Lines.Add(Format('"%s" clicked:', [ttl])); Log.Lines.Add(Format(' point #%d, x = %.1f (%s), y = %.1f', - [ idx, ser.GetXValue(idx), ser.Source[idx]^.Text, ser.Source[idx]^.GetY(yidx) ] + [ idx, ser.GetXValue(idx), ListChartSourceLABELS.Item[idx]^.Text, + ser.Source[idx]^.GetY(yidx) ] )); Log.SelStart := Length(Log.Lines.Text); end; @@ -132,9 +133,6 @@ procedure TMainForm.DataPointDragToolDrag(ASender: TDataPointDragTool; var AGraphPoint: TDoublePoint); begin AGraphPoint.X := ASender.Origin.X; // Keep x value fixed - - // Optionally remove next line to see the difference... - AGraphPoint.Y := ASender.Origin.Y + (AGraphPoint.Y - FDragStart.Y); end; procedure TMainForm.DataPointDragToolDragStart(ASender: TDataPointDragTool; @@ -163,7 +161,8 @@ begin ttl := TBarSeries(ser).Styles.Styles[yidx].Text; AHint := Format( 'Mouse over "%s": point #%d, x value %.1f (%s), y value %.1f', - [ ttl, idx, ser.GetXValue(idx), ser.Source[idx]^.Text, ser.Source[idx]^.GetY(yidx) ] + [ ttl, idx, ser.GetXValue(idx), ListChartSourceLABELS.Item[idx]^.Text, + ser.Source[idx]^.GetY(yidx) ] ); end else AHint := ''; @@ -180,6 +179,7 @@ begin ListChartSourceBlue.Add(i, Random*80); ListChartSourceLABELS.Add(i, i, char(ord('A') + i)); end; + CbHorizontalChange(nil); end; procedure TMainForm.ToolTargetChanged(Sender: TObject); diff --git a/components/tachart/tacustomseries.pas b/components/tachart/tacustomseries.pas index 89af1f3fc2..f28dd80284 100644 --- a/components/tachart/tacustomseries.pas +++ b/components/tachart/tacustomseries.pas @@ -283,7 +283,7 @@ type procedure PrepareGraphPoints( const AExtent: TDoubleRect; AFilterByExtent: Boolean); function ToolTargetDistance(const AParams: TNearestPointParams; - APoint: TDoublePoint; APointIndex: Integer): Integer; virtual; + AGraphPt: TDoublePoint; APointIdx, AXIdx, AYIdx: Integer): Integer; virtual; procedure UpdateGraphPoints(AIndex: Integer; ACumulative: Boolean); overload; inline; procedure UpdateGraphPoints(AIndex, ALo, AUp: Integer; ACumulative: Boolean); overload; procedure UpdateMinXRange; @@ -1277,11 +1277,9 @@ function TBasicPointSeries.GetNearestPoint( end; var - dist, i, j, lb, ub: Integer; - sp: TDoublePoint; - tmpPt: TPoint; - tmpSP: TDoublePoint; - tmpDist: Integer; + dist, tmpDist, i, j, lb, ub: Integer; + sp, tmpSp: TDoublePoint; + pt, tmpPt: TDoublePoint; begin AResults.FDist := Sqr(AParams.FRadius) + 1; // the dist func does not calc sqrt AResults.FIndex := -1; @@ -1307,8 +1305,10 @@ begin // an integer overflow, so ADistFunc should use saturation arithmetics. // Find nearest point of datapoint at (x, y) - if (nptPoint in AParams.FTargets) then - dist := Min(dist, ToolTargetDistance(AParams, sp, i)); + if (nptPoint in AParams.FTargets) then begin + pt := AxisToGraph(sp); + dist := Min(dist, ToolTargetDistance(AParams, pt, i, 0, 0)); + end; // Find nearest point to additional y values (at x). // In case of stacked data points check the stacked values. @@ -1318,10 +1318,12 @@ begin if FStacked then tmpSp.Y += Source[i]^.YList[j] else tmpSp.Y := Source[i]^.YList[j]; - tmpDist := ToolTargetDistance(AParams, tmpSp, i); + tmpPt := AxisToGraph(tmpSp); + tmpDist := ToolTargetDistance(AParams, tmpPt, i, 0, j); if tmpDist < dist then begin dist := tmpDist; sp := tmpSp; + pt := tmpPt; AResults.FYIndex := j + 1; // FYIndex = 0 refers to the regular y end; end; @@ -1332,10 +1334,12 @@ begin tmpSp := sp; for j := 0 to Source.XCount - 2 do begin tmpSp.X := Source[i]^.XList[j]; - tmpDist := ToolTargetDistance(AParams, tmpSp, i); + tmpPt := AxisToGraph(tmpSp); + tmpDist := ToolTargetDistance(AParams, tmpPt, i, j, 0); if tmpDist < dist then begin dist := tmpDist; sp := tmpSp; + pt := tmpPt; AResults.FXIndex := j + 1; // FXindex = 0 refers to the regular x end; end; @@ -1350,7 +1354,7 @@ begin AResults.FDist := dist; AResults.FIndex := i; AResults.FValue := sp; - AResults.FImg := ParentChart.GraphToImage(AxisToGraph(sp)); + AResults.FImg := ParentChart.GraphToImage(pt); if dist = 0 then break; end; Result := AResults.FIndex >= 0; @@ -1444,12 +1448,13 @@ begin end; function TBasicPointSeries.ToolTargetDistance(const AParams: TNearestPointParams; - APoint: TDoublePoint; APointIndex: Integer): Integer; + AGraphPt: TDoublePoint; APointIdx, AXIdx, AYIdx: Integer): Integer; var pt: TPoint; begin - Unused(APointIndex); - pt := ParentChart.GraphToImage(AxisToGraph(APoint)); + Unused(APointIdx); + Unused(AXIdx, AYIdx); + pt := ParentChart.GraphToImage(AGraphPt); Result := AParams.FDistFunc(AParams.FPoint, pt); end; diff --git a/components/tachart/tamultiseries.pas b/components/tachart/tamultiseries.pas index 9fbdeabbf9..bcf25d78b7 100644 --- a/components/tachart/tamultiseries.pas +++ b/components/tachart/tamultiseries.pas @@ -150,6 +150,8 @@ type protected procedure GetLegendItems(AItems: TChartLegendItems); override; function GetSeriesColor: TColor; override; + function ToolTargetDistance(const AParams: TNearestPointParams; + AGraphPt: TDoublePoint; APointIdx, AXIdx, AYIdx: Integer): Integer; override; public procedure Assign(ASource: TPersistent); override; constructor Create(AOwner: TComponent); override; @@ -158,7 +160,8 @@ type 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: TBrush read FCandlestickDownBrush write SetCandlestickDownBrush; @@ -783,6 +786,7 @@ end; constructor TOpenHighLowCloseSeries.Create(AOwner: TComponent); begin inherited Create(AOwner); + FStacked := false; FCandlestickDownBrush := TBrush.Create; with FCandlestickDownBrush do begin Color := clRed; @@ -849,14 +853,6 @@ procedure TOpenHighLowCloseSeries.Draw(ADrawer: IChartDrawer); ADrawer.Rectangle(r); end; - function GetGraphPointYIndex(AIndex, AYIndex: Integer): Double; - begin - if AYIndex = 0 then - Result := GetGraphPointY(AIndex) - else - Result := AxisToGraphY(Source[AIndex]^.YList[AYIndex - 1]); - end; - procedure DrawOHLC(x, yopen, yhigh, ylow, yclose, tw: Double); begin DoLine(x, yhigh, x, ylow); @@ -898,10 +894,10 @@ begin for i := FLoBound to FUpBound do begin x := GetGraphPointX(i); - yopen := GetGraphPointYIndex(i, YIndexOpen); - yhigh := GetGraphPointYIndex(i, YIndexHigh); - ylow := GetGraphPointYIndex(i, YIndexLow); - yclose := GetGraphPointYIndex(i, YIndexClose); + yopen := GetGraphPointY(i, YIndexOpen); + yhigh := GetGraphPointY(i, YIndexHigh); + ylow := GetGraphPointY(i, YIndexLow); + yclose := GetGraphPointY(i, YIndexClose); tw := GetXRange(x, i) * PERCENT * TickWidth; if (yopen <= yclose) then begin @@ -924,16 +920,57 @@ begin end; end; -function TOpenHighLowCloseSeries.Extent: TDoubleRect; -begin - Result := Source.ExtentList; -end; - procedure TOpenHighLowCloseSeries.GetLegendItems(AItems: TChartLegendItems); begin AItems.Add(TLegendItemOHLCLine.Create(Self, LegendTextSingle)); end; +function TOpenHighLowCloseSeries.GetNearestPoint(const AParams: TNearestPointParams; + out AResults: TNearestPointResults): Boolean; +var + i: Integer; + graphClickPt: TDoublePoint; + x, yopen, yhigh, ylow, yclose, tw: Double; + R: TDoubleRect; +begin + Result := false; + AResults.FDist := Sqr(AParams.FRadius) + 1; + AResults.FIndex := -1; + AResults.FXIndex := 0; + AResults.FYIndex := 0; + + if not (nptCustom in AParams.FTargets) then begin + Result := inherited; + exit; + end; + + graphClickPt := ParentChart.ImageToGraph(AParams.FPoint); + if IsRotated then + Exchange(graphclickpt.X, graphclickpt.Y); + + // 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 := GetXRange(x, i) * PERCENT * TickWidth; + R.a := DoublePoint(x - tw, MinValue([yopen, yhigh, ylow, yclose])); + R.b := DoublePoint(x + tw, MaxValue([yopen, yhigh, ylow, yclose])); + if InRange(graphClickPt.X, R.a.x, R.b.x) and + InRange(graphClickPt.Y, R.a.Y, R.b.Y) then + begin + AResults.FDist := 0; + AResults.FIndex := i; + AResults.FValue := DoublePoint(x, yopen); + AResults.FImg := ParentChart.GraphToImage(AResults.FValue); + Result := true; + exit; + end; + end; +end; + function TOpenHighLowCloseSeries.GetSeriesColor: TColor; begin Result := LinePen.Color; @@ -1016,6 +1053,54 @@ begin UpdateParentChart; end; +function TOpenHighLowCloseSeries.ToolTargetDistance( + const AParams: TNearestPointParams; AGraphPt: TDoublePoint; + APointIdx, AXIdx, AYIdx: Integer): Integer; + + function DistanceToLine(x1, x2, y: Integer): Integer; + begin + if InRange(AParams.FPoint.X, x1, x2) then + Result := sqr(AParams.FPoint.Y - y) // FDistFunc does not calc sqrt + else + Result := Min( + AParams.FDistFunc(AParams.FPoint, Point(x1, y)), + AParams.FDistFunc(AParams.FPoint, Point(x2, y)) + ); + end; + +var + x1, x2, w: Double; + p: TPoint; +begin + Unused(AXIdx); + + p := ParentChart.GraphToImage(AGraphPt); + w := GetXRange(AGraphPt.X, APointIdx) * PERCENT * TickWidth; + x1 := AGraphPt.X - w; + x2 := AGraphPt.X + w; + + case FMode of + mOHLC: + with ParentChart do + if (AYIdx = YIndexOpen) or (AYIdx = YIndexClose) then + Result := DistanceToLine(XGraphToImage(x1), XGraphToImage(x2), p.y) + else if (AYIdx = YIndexHigh) or (AYIdx = YIndexLow) then + Result := AParams.FDistFunc(AParams.FPoint, p) + else + raise Exception.Create('TOpenHighLowCloseSeries.ToolTargetDistance: Illegal YIndex.'); + mCandleStick: + with ParentChart do + if (AYIdx = YIndexOpen) then + Result := DistanceToLine(XGraphToImage(x1), p.x, p.y) + else if (AYIdx = YIndexClose) then + Result := DistanceToLine(p.x, XGraphToImage(x2), p.y) + else if (AYIdx = YIndexHigh) or (AYIdx = YIndexLow) then + Result := AParams.FDistFunc(AParams.FPoint, p) + else + raise Exception.Create('TOpenHighLowCloseSeries.ToolTargetDistance: Illegal YIndex.'); + end; +end; + { TFieldSeries } diff --git a/components/tachart/taseries.pas b/components/tachart/taseries.pas index bd853ec665..04ca6933fd 100644 --- a/components/tachart/taseries.pas +++ b/components/tachart/taseries.pas @@ -65,7 +65,7 @@ type strict protected function GetLabelDataPoint(AIndex: Integer): TDoublePoint; override; function ToolTargetDistance(const AParams: TNearestPointParams; - APoint: TDoublePoint; APointIndex: Integer): Integer; override; + AGraphPt: TDoublePoint; APointIdx, AXIdx, AYIdx: Integer): Integer; override; protected procedure BarOffsetWidth( AX: Double; AIndex: Integer; out AOffset, AWidth: Double); @@ -1267,33 +1267,42 @@ begin end; function TBarSeries.ToolTargetDistance(const AParams: TNearestPointParams; - APoint: TDoublePoint; APointIndex: Integer): Integer; + AGraphPt: TDoublePoint; APointIdx, AXIdx, AYIdx: Integer): Integer; var - spt: TDoublePoint; - tpt, pt1, pt2: TPoint; + sp1, sp2: TDoublePoint; + clickPt, pt1, pt2: TPoint; ofs, w: Double; dist1, dist2: Integer; begin - BarOffsetWidth(APoint.X, APointIndex, ofs, w); - spt := DoublePoint(APoint.X + ofs - w, APoint.Y); - pt1 := ParentChart.GraphToImage(AxisToGraph(spt)); + Unused(APointIdx); + Unused(AXIdx, AYIdx); - spt := DoublePoint(APoint.X + ofs + w, APoint.Y); - pt2 := ParentChart.GraphToImage(AxisToGraph(spt)); + clickPt := AParams.FPoint; + if IsRotated then begin + Exchange(clickPt.X, clickPt.Y); + Exchange(AGraphPt.X, AGraphPt.Y); + end; - tpt := AParams.FPoint; + BarOffsetWidth(AGraphPt.X, APointIdx, ofs, w); + sp1 := DoublePoint(AGraphPt.X + ofs - w, AGraphPt.Y); + sp2 := DoublePoint(AGraphPt.X + ofs + w, AGraphPt.Y); + if IsRotated then begin + Exchange(sp1.X, sp1.Y); + Exchange(sp2.X, sp2.Y); + end; + pt1 := ParentChart.GraphToImage(sp1); + pt2 := ParentChart.GraphToImage(sp2); if IsRotated then begin Exchange(pt1.X, pt1.Y); Exchange(pt2.X, pt2.Y); - Exchange(tpt.X, tpt.Y); + if pt1.X > pt2.X then Exchange(pt1.X, pt2.X); end; - if pt2.X < pt1.x then Exchange(pt2.X, pt1.X); - if InRange(tpt.X, pt1.x, pt2.x) then - Result := sqr(tpt.Y - pt1.Y) // DistFunc does not calculate sqrt! + if InRange(clickPt.X, pt1.X, pt2.X) then + Result := sqr(clickPt.Y - pt1.Y) else begin - dist1 := AParams.FDistFunc(tpt, pt1); - dist2 := AParams.FDistFunc(tpt, pt2); + dist1 := AParams.FDistFunc(clickPt, pt1); + dist2 := AParams.FDistFunc(clickPt, pt2); Result := Min(dist1, dist2); end; end;