TAChart: Fix TDatapointTools acting on TBoxAndWhiskerSeries and TOpenHighLowCloseSeries with transformed axes.

git-svn-id: trunk@53943 -
This commit is contained in:
wp 2017-01-14 16:49:08 +00:00
parent 437e9605f2
commit 1fb630f063
3 changed files with 139 additions and 88 deletions

View File

@ -1320,7 +1320,7 @@ begin
tmpSp.Y += Source[i]^.YList[j] else
tmpSp.Y := Source[i]^.YList[j];
tmpPt := AxisToGraph(tmpSp);
tmpDist := ToolTargetDistance(AParams, tmpPt, i, 0, j);
tmpDist := ToolTargetDistance(AParams, tmpPt, i, 0, j + 1);
if tmpDist < dist then begin
dist := tmpDist;
sp := tmpSp;
@ -1336,7 +1336,7 @@ begin
for j := 0 to Source.XCount - 2 do begin
tmpSp.X := Source[i]^.XList[j];
tmpPt := AxisToGraph(tmpSp);
tmpDist := ToolTargetDistance(AParams, tmpPt, i, j, 0);
tmpDist := ToolTargetDistance(AParams, tmpPt, i, j + 1, 0);
if tmpDist < dist then begin
dist := tmpDist;
sp := tmpSp;

View File

@ -576,6 +576,7 @@ end;
constructor TBoxAndWhiskerSeries.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FOptimizeX := false;
FBoxBrush := TBrush.Create;
FBoxBrush.OnChange := @StyleChanged;
FBoxPen := TPen.Create;
@ -702,61 +703,66 @@ function TBoxAndWhiskerSeries.GetNearestPoint(const AParams: TNearestPointParams
out AResults: TNearestPointResults): Boolean;
var
i, j: Integer;
graphClickPt: TDoublePoint;
xp, w, wb: Double;
yp: Array[0..4] of Double;
graphClickPt, pt: TDoublePoint;
x, w, wb: Double;
y: Array[0..4] of Double;
pImg: TPoint;
R: TDoubleRect;
xImg, dist: Integer;
begin
Result := inherited;
if Result and ([nptPoint, nptYList] * AParams.FTargets = [nptPoint, nptYList]) then
exit;
if not (nptCustom in AParams.FTargets) then
exit;
if Result and (AResults.FDist = 0) then
exit;
graphClickPt := ParentChart.ImageToGraph(AParams.FPoint);
pImg := AParams.FPoint;
graphClickPt := ParentChart.ImageToGraph(AParams.FPoint);
if IsRotated then begin
Exchange(graphclickpt.X, graphclickpt.Y);
Exchange(pImg.X, pImg.Y);
pImg := ParentChart.GraphToImage(graphclickPt);
end;
// Iterate through all points of the series
for i := 0 to Count - 1 do begin
xp := GetGraphPointX(i);
for j := 0 to High(yp) do
yp[j] := GetGraphPointY(i, j);
x := GetGraphPointX(i);
for j := 0 to High(y) do
y[j] := GetGraphPointY(i, j);
case FWidthStyle of
bwsPercent : w := GetXRange(xp, i) * PERCENT / 2;
bwsPercent : w := GetXRange(x, i) * PERCENT / 2;
bwsPercentMin : w := FMinXRange * PERCENT / 2;
end;
wb := w * BoxWidth;
dist := AResults.FDist;
dist := MaxInt;
// click inside box
R.a := DoublePoint(xp - wb, yp[1]); // index 1 --> lower quartile
R.b := DoublePoint(xp + wb, yp[3]); // index 3 --> upper quartile
if InRange(graphClickPt.X, R.a.x, R.b.x) and InRange(graphClickPt.Y, R.a.Y, R.b.Y)
then dist := 0;
R.a := DoublePoint(x - wb, y[1]); // index 1 --> lower quartile
R.b := DoublePoint(x + wb, y[3]); // index 3 --> upper quartile
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;
// click on whisker line
xImg := IfThen(IsRotated, ParentChart.YGraphToImage(xp), ParentChart.XGraphToImage(xp));
if InRange(graphClickPt.Y, yp[0], yp[1]) or InRange(graphClickPt.Y, yp[3], yp[4])
then dist := sqr(pImg.X - xImg);
xImg := ParentChart.XGraphToImage(x);
if InRange(graphClickPt.Y, y[0], y[1]) or InRange(graphClickPt.Y, y[3], y[4])
then begin
dist := sqr(pImg.X - xImg);
AResults.FYIndex := -1;
end;
// Sufficiently close?
if dist < AResults.FDist then begin
AResults.FDist := dist;
AResults.FIndex := i;
AResults.FYIndex := 2;
AResults.FValue := DoublePoint(xp, yp[2]);
AResults.FImg := ParentChart.GraphToImage(AResults.FValue);
if dist = 0 then
break;
pt := DoublePoint(x, y[2]); // Median
AResults.FValue := pt;
if IsRotated then Exchange(pt.X, pt.Y);
AResults.FImg := ParentChart.GraphToImage(pt);
if dist = 0 then break;
end;
end;
Result := AResults.FIndex > -1;
@ -821,23 +827,34 @@ function TBoxAndWhiskerSeries.ToolTargetDistance(
const AParams: TNearestPointParams; AGraphPt: TDoublePoint;
APointIdx, AXIdx, AYIdx: Integer): Integer;
function DistanceToLine(x1, x2, y: Integer): Integer;
// All in image coordinates traansformed to have a horizontal x axis
function DistanceToLine(Pt: TPoint; 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
if InRange(Pt.X, x1, x2) then
Result := sqr(Pt.Y - y) // FDistFunc does not calc sqrt
else
Result := Min(
AParams.FDistFunc(AParams.FPoint, Point(x1, y)),
AParams.FDistFunc(AParams.FPoint, Point(x2, y))
AParams.FDistFunc(Pt, Point(x1, y)),
AParams.FDistFunc(Pt, Point(x2, y))
);
end;
var
xw1, xw2, xb1, xb2, w, wb, ww: Double;
p: TPoint;
xw1, xw2, xb1, xb2, y: Integer;
w, wb, ww: Double;
clickPt: TPoint;
gp: TDoublePoint;
begin
Unused(AXIdx);
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;
case FWidthStyle of
bwsPercent : w := GetXRange(AGraphPt.X, APointIdx) * PERCENT / 2;
bwsPercentMin : w := FMinXRange * PERCENT / 2;
@ -845,19 +862,18 @@ begin
wb := w * BoxWidth;
ww := w * WhiskersWidth;
p := ParentChart.GraphToImage(AGraphPt);
xw1 := AGraphPt.X - ww;
xw2 := AGraphPt.X + ww;
xb1 := AGraphPt.X - wb;
xb2 := AGraphPt.X + wb;
xw1 := ParentChart.XGraphToImage(AGraphPt.X - ww);
xw2 := ParentChart.XGraphToImage(AGraphPt.X + ww);
xb1 := ParentChart.XGraphToImage(AGraphPt.X - wb);
xb2 := ParentChart.XGraphToImage(AGraphPt.X + wb);
y := ParentChart.YGraphToImage(AGraphPt.Y);
with ParentChart do
case AYIdx of
0, 4: // Min, Max --> Whisker
Result := DistanceToLine(XGraphToImage(xw1), XGraphToImage(xw2), p.y);
1, 2, 3: // Box lines
Result := DistancetoLine(XGraphToImage(xb1), XGraphToImage(xb2), p.y);
end;
case AYIdx of
0, 4: // Min, Max --> Whisker
Result := DistanceToLine(clickPt, xw1, xw2, y);
1, 2, 3: // Box lines
Result := DistancetoLine(clickPt, xb1, xb2, y);
end;
end;
@ -899,6 +915,7 @@ end;
constructor TOpenHighLowCloseSeries.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FOptimizeX := false;
FStacked := false;
FCandlestickDownBrush := TBrush.Create;
with FCandlestickDownBrush do begin
@ -1060,24 +1077,26 @@ function TOpenHighLowCloseSeries.GetNearestPoint(const AParams: TNearestPointPar
out AResults: TNearestPointResults): Boolean;
var
i: Integer;
graphClickPt: TDoublePoint;
graphClickPt, p: TDoublePoint;
pImg: TPoint;
x, yopen, yhigh, ylow, yclose, tw: Double;
xImg, dist: Integer;
R: TDoubleRect;
begin
Result := false;
AResults.FDist := Sqr(AParams.FRadius) + 1;
AResults.FIndex := -1;
AResults.FXIndex := 0;
AResults.FYIndex := 0;
Result := inherited;
if not (nptCustom in AParams.FTargets) then begin
Result := inherited;
if Result and ([nptPoint, nptYList] * AParams.FTargets = [nptPoint, nptYList]) then
exit;
if not (nptCustom in AParams.FTargets) then
exit;
end;
graphClickPt := ParentChart.ImageToGraph(AParams.FPoint);
if IsRotated then
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
@ -1087,19 +1106,39 @@ begin
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;
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;
AResults.FValue := DoublePoint(x, yopen);
AResults.FImg := ParentChart.GraphToImage(AResults.FValue);
Result := true;
exit;
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 TOpenHighLowCloseSeries.GetSeriesColor: TColor;
@ -1188,47 +1227,59 @@ function TOpenHighLowCloseSeries.ToolTargetDistance(
const AParams: TNearestPointParams; AGraphPt: TDoublePoint;
APointIdx, AXIdx, AYIdx: Integer): Integer;
function DistanceToLine(x1, x2, y: 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(AParams.FPoint.X, x1, x2) then
Result := sqr(AParams.FPoint.Y - y) // FDistFunc does not calc sqrt
if InRange(Pt.X, x1, x2) then // FDistFunc does not calculate sqrt
Result := sqr(Pt.Y - y)
else
Result := Min(
AParams.FDistFunc(AParams.FPoint, Point(x1, y)),
AParams.FDistFunc(AParams.FPoint, Point(x2, y))
AParams.FDistFunc(Pt, Point(x1, y)),
AParams.FDistFunc(Pt, Point(x2, y))
);
end;
var
x1, x2, w: Double;
p: TPoint;
x1, x2: Integer;
w: Double;
p, clickPt: TPoint;
gp: TDoublePoint;
begin
Unused(AXIdx);
p := ParentChart.GraphToImage(AGraphPt);
// 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 := GetXRange(AGraphPt.X, APointIdx) * PERCENT * TickWidth;
x1 := AGraphPt.X - w;
x2 := AGraphPt.X + w;
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) or (AYIdx = YIndexClose) then
Result := DistanceToLine(XGraphToImage(x1), XGraphToImage(x2), p.y)
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(AParams.FPoint, p)
Result := AParams.FDistFunc(clickPt, p)
else
raise Exception.Create('TOpenHighLowCloseSeries.ToolTargetDistance: Illegal YIndex.');
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)
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(AParams.FPoint, p)
Result := AParams.FDistFunc(clickPt, p)
else
raise Exception.Create('TOpenHighLowCloseSeries.ToolTargetDistance: Illegal YIndex.');
raise Exception.Create('TOpenHighLowCloseSeries.ToolTargetDistance: Illegal YIndex.');
end;
end;

View File

@ -1041,7 +1041,7 @@ var
end;
var
z, ofs, y: Double;
ofs, y: Double;
begin
if IsEmpty then exit;
@ -1136,7 +1136,7 @@ function TBarSeries.GetNearestPoint(const AParams: TNearestPointParams;
var
pointIndex: Integer;
graphClickPt: TDoublePoint;
sp, p: TDoublePoint;
sp: TDoublePoint;
ofs, w: Double;
heights: TDoubleDynArray;
y: Double;