TAChart: Activate painting of box/whisker and OHLC series marks. Fix missing value issues in these series.

git-svn-id: trunk@60445 -
This commit is contained in:
wp 2019-02-18 15:23:02 +00:00
parent 6cb99018d7
commit cf88b3536a
2 changed files with 115 additions and 17 deletions

View File

@ -302,7 +302,7 @@ type
procedure GetLegendItemsRect(AItems: TChartLegendItems; ABrush: TBrush); procedure GetLegendItemsRect(AItems: TChartLegendItems; ABrush: TBrush);
function GetXRange(AX: Double; AIndex: Integer): Double; function GetXRange(AX: Double; AIndex: Integer): Double;
function GetZeroLevel: Double; virtual; function GetZeroLevel: Double; virtual;
function HasMissingYValue(AIndex: Integer): Boolean; function HasMissingYValue(AIndex: Integer; AMaxYIndex: Integer = MaxInt): Boolean;
function NearestXNumber(var AIndex: Integer; ADir: Integer): Double; function NearestXNumber(var AIndex: Integer; ADir: Integer): Double;
procedure PrepareGraphPoints( procedure PrepareGraphPoints(
const AExtent: TDoubleRect; AFilterByExtent: Boolean); const AExtent: TDoubleRect; AFilterByExtent: Boolean);
@ -1655,13 +1655,14 @@ end;
{ Returns true if the data point at the given index has at least one missing { Returns true if the data point at the given index has at least one missing
y value (NaN) } y value (NaN) }
function TBasicPointSeries.HasMissingYValue(AIndex: Integer): Boolean; function TBasicPointSeries.HasMissingYValue(AIndex: Integer;
AMaxYIndex: Integer = MaxInt): Boolean;
var var
j: Integer; j: Integer;
begin begin
Result := IsNaN(Source[AIndex]^.Y); Result := IsNaN(Source[AIndex]^.Y);
if not Result then if not Result then
for j := 0 to Source.YCount-1 do for j := 0 to Min(AMaxYIndex, Source.YCount)-2 do
if IsNaN(Source[AIndex]^.YList[j]) then if IsNaN(Source[AIndex]^.YList[j]) then
exit(true); exit(true);
end; end;
@ -1959,7 +1960,7 @@ begin
end; end;
{ Can be overridden for a data-point dependent reference level, such as in { Can be overridden for a data-point dependent reference level, such as in
TBubbleSeries. AIndex is relative to FLoBound } TBubbleSeries. AIndex refers to chart source. }
procedure TBasicPointSeries.UpdateLabelDirectionReferenceLevel(AIndex, AYIndex: Integer; procedure TBasicPointSeries.UpdateLabelDirectionReferenceLevel(AIndex, AYIndex: Integer;
var ALevel: Double); var ALevel: Double);
begin begin

View File

@ -112,8 +112,11 @@ type
procedure GetLegendItems(AItems: TChartLegendItems); override; procedure GetLegendItems(AItems: TChartLegendItems); override;
function GetSeriesColor: TColor; override; function GetSeriesColor: TColor; override;
class procedure GetXYCountNeeded(out AXCount, AYCount: Integer); override; class procedure GetXYCountNeeded(out AXCount, AYCount: Integer); override;
function SkipMissingValues(AIndex: Integer): Boolean; override;
function ToolTargetDistance(const AParams: TNearestPointParams; function ToolTargetDistance(const AParams: TNearestPointParams;
AGraphPt: TDoublePoint; APointIdx, AXIdx, AYIdx: Integer): Integer; override; AGraphPt: TDoublePoint; APointIdx, AXIdx, AYIdx: Integer): Integer; override;
procedure UpdateLabelDirectionReferenceLevel(AIndex, AYIndex: Integer;
var ALevel: Double); override;
public public
function AddXY( function AddXY(
AX, AYLoWhisker, AYLoBox, AY, AYHiBox, AYHiWhisker: Double; AX, AYLoWhisker, AYLoBox, AY, AYHiBox, AYHiWhisker: Double;
@ -142,6 +145,8 @@ type
published published
property AxisIndexX; property AxisIndexX;
property AxisIndexY; property AxisIndexY;
property MarkPositions;
property Marks;
property Source; property Source;
end; end;
@ -180,8 +185,11 @@ type
procedure GetLegendItems(AItems: TChartLegendItems); override; procedure GetLegendItems(AItems: TChartLegendItems); override;
function GetSeriesColor: TColor; override; function GetSeriesColor: TColor; override;
class procedure GetXYCountNeeded(out AXCount, AYCount: Integer); override; class procedure GetXYCountNeeded(out AXCount, AYCount: Integer); override;
function SkipMissingValues(AIndex: Integer): Boolean; override;
function ToolTargetDistance(const AParams: TNearestPointParams; function ToolTargetDistance(const AParams: TNearestPointParams;
AGraphPt: TDoublePoint; APointIdx, AXIdx, AYIdx: Integer): Integer; override; AGraphPt: TDoublePoint; APointIdx, AXIdx, AYIdx: Integer): Integer; override;
procedure UpdateLabelDirectionReferenceLevel(AIndex, AYIndex: Integer;
var ALevel: Double); override;
public public
procedure Assign(ASource: TPersistent); override; procedure Assign(ASource: TPersistent); override;
constructor Create(AOwner: TComponent); override; constructor Create(AOwner: TComponent); override;
@ -217,6 +225,8 @@ type
published published
property AxisIndexX; property AxisIndexX;
property AxisIndexY; property AxisIndexY;
property MarkPositions;
property Marks;
property Source; property Source;
end; end;
@ -505,8 +515,10 @@ var
irect: TRect; irect: TRect;
dummyR: TRect = (Left:0; Top:0; Right:0; Bottom:0); dummyR: TRect = (Left:0; Top:0; Right:0; Bottom:0);
ext: TDoubleRect; ext: TDoubleRect;
nx, ny: Integer;
begin begin
if Source.YCount < 2 then exit; GetXYCountNeeded(nx, ny);
if Source.YCount < ny then exit;
ADrawer.Pen := BubblePen; ADrawer.Pen := BubblePen;
ADrawer.Brush := BubbleBrush; ADrawer.Brush := BubbleBrush;
@ -529,8 +541,10 @@ begin
ADrawer.SetBrushColor(ColorDef(item^.Color, BubbleBrush.Color)); ADrawer.SetBrushColor(ColorDef(item^.Color, BubbleBrush.Color));
ADrawer.Ellipse(irect.Left, irect.Top, irect.Right, irect.Bottom); ADrawer.Ellipse(irect.Left, irect.Top, irect.Right, irect.Bottom);
end; end;
for i := 0 to Min(1, Source.YCount) do if Source.YCount > ny then
DrawLabels(ADrawer, i); for i := 0 to ny - 1 do DrawLabels(ADrawer, i)
else
DrawLabels(ADrawer);
ADrawer.ClippingStop; ADrawer.ClippingStop;
end; end;
@ -970,8 +984,10 @@ var
ext2: TDoubleRect; ext2: TDoubleRect;
x, ymin, yqmin, ymed, yqmax, ymax, wb, ww, w: Double; x, ymin, yqmin, ymed, yqmax, ymax, wb, ww, w: Double;
i: Integer; i: Integer;
nx, ny: Integer;
begin begin
if IsEmpty or (Source.YCount < 5) then GetXYCountNeeded(nx, ny);
if IsEmpty or (Source.YCount < ny) then
exit; exit;
if FWidthStyle = bwsPercentMin then if FWidthStyle = bwsPercentMin then
UpdateMinXRange; UpdateMinXRange;
@ -1016,11 +1032,17 @@ begin
ADrawer.SetBrushParams(bsClear, clTAColor); ADrawer.SetBrushParams(bsClear, clTAColor);
DoLine(x - wb, ymed, x + wb, ymed); DoLine(x - wb, ymed, x + wb, ymed);
end; end;
if Source.YCount > ny then
for i := 0 to ny-1 do DrawLabels(ADrawer, ny)
else
DrawLabels(ADrawer);
end; end;
function TBoxAndWhiskerSeries.Extent: TDoubleRect; function TBoxAndWhiskerSeries.Extent: TDoubleRect;
var var
x: Double; x: Double;
j: Integer;
function ExtraWidth(AIndex: Integer): Double; function ExtraWidth(AIndex: Integer): Double;
begin begin
@ -1031,10 +1053,20 @@ begin
if Source.YCount < 5 then exit(EmptyExtent); if Source.YCount < 5 then exit(EmptyExtent);
Result := Source.ExtentList; Result := Source.ExtentList;
// Show first and last boxes fully. // Show first and last boxes fully.
x := GetGraphPointX(0); j := -1;
Result.a.X := Min(Result.a.X, GraphToAxisX(x - ExtraWidth(0))); x := NaN;
x := GetGraphPointX(Count - 1); while IsNaN(x) and (j < Source.Count-1) do begin
Result.b.X := Max(Result.b.X, GraphToAxisX(x + ExtraWidth(Count - 1))); inc(j);
x := GetGraphPointX(j);
end;
Result.a.X := Min(Result.a.X, GraphToAxisX(x - ExtraWidth(j)));
j := Count;
x := NaN;
while IsNaN(x) and (j > 0) do begin
dec(j);
x := GetGraphPointX(j);
end;
Result.b.X := Max(Result.b.X, GraphToAxisX(x + ExtraWidth(j)));
end; end;
procedure TBoxAndWhiskerSeries.GetLegendItems(AItems: TChartLegendItems); procedure TBoxAndWhiskerSeries.GetLegendItems(AItems: TChartLegendItems);
@ -1178,6 +1210,13 @@ begin
UpdateParentChart; UpdateParentChart;
end; end;
function TBoxAndWhiskerSeries.SkipMissingValues(AIndex: Integer): Boolean;
begin
Result := IsNaN(Source[AIndex]^.Point);
if not Result then
Result := HasMissingYValue(AIndex, 5);
end;
function TBoxAndWhiskerSeries.ToolTargetDistance( function TBoxAndWhiskerSeries.ToolTargetDistance(
const AParams: TNearestPointParams; AGraphPt: TDoublePoint; const AParams: TNearestPointParams; AGraphPt: TDoublePoint;
APointIdx, AXIdx, AYIdx: Integer): Integer; APointIdx, AXIdx, AYIdx: Integer): Integer;
@ -1231,6 +1270,20 @@ begin
end; end;
end; end;
procedure TBoxAndWhiskerSeries.UpdateLabelDirectionReferenceLevel(
AIndex, AYIndex: Integer; var ALevel: Double);
var
item: PChartDataItem;
begin
case AYIndex of
0: ALevel := +Infinity;
3: ALevel := -Infinity;
else
item := Source.Item[AIndex];
ALevel := (AxisToGraphY(item^.GetY(0)) + AxisToGraphY(item^.GetY(3)))*0.5;
end;
end;
{ TOpenHighLowCloseSeries } { TOpenHighLowCloseSeries }
@ -1382,6 +1435,7 @@ var
i: Integer; i: Integer;
x, tw, yopen, yhigh, ylow, yclose: Double; x, tw, yopen, yhigh, ylow, yclose: Double;
p: TPen; p: TPen;
nx, ny: Integer;
begin begin
my := MaxIntValue([YIndexOpen, YIndexHigh, YIndexLow, YIndexClose]); my := MaxIntValue([YIndexOpen, YIndexHigh, YIndexLow, YIndexClose]);
if IsEmpty or (my >= Source.YCount) then exit; if IsEmpty or (my >= Source.YCount) then exit;
@ -1394,12 +1448,16 @@ begin
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;
yopen := GetGraphPointY(i, YIndexOpen); yopen := GetGraphPointY(i, YIndexOpen);
if IsNaN(yopen) then Continue;
yhigh := GetGraphPointY(i, YIndexHigh); yhigh := GetGraphPointY(i, YIndexHigh);
if IsNaN(yhigh) then Continue;
ylow := GetGraphPointY(i, YIndexLow); ylow := GetGraphPointY(i, YIndexLow);
if IsNaN(ylow) then Continue;
yclose := GetGraphPointY(i, YIndexClose); yclose := GetGraphPointY(i, YIndexClose);
if IsNaN(yclose) then Continue;
tw := GetXRange(x, i) * PERCENT * TickWidth; tw := GetXRange(x, i) * PERCENT * TickWidth;
if (yopen <= yclose) then begin if (yopen <= yclose) then begin
p := LinePen; p := LinePen;
ADrawer.Brush := FCandleStickUpBrush; ADrawer.Brush := FCandleStickUpBrush;
@ -1418,22 +1476,39 @@ begin
mCandleStick: DrawCandleStick(x, yopen, yhigh, ylow, yclose, tw); mCandleStick: DrawCandleStick(x, yopen, yhigh, ylow, yclose, tw);
end; end;
end; end;
GetXYCountNeeded(nx, ny);
if Source.YCount > ny then
for i := 0 to ny-1 do DrawLabels(ADrawer, i)
else
DrawLabels(ADrawer);
end; end;
function TOpenHighLowCloseSeries.Extent: TDoubleRect; function TOpenHighLowCloseSeries.Extent: TDoubleRect;
var var
x: Double; x: Double;
tw: Double; tw: Double;
j: Integer;
begin begin
if Source.YCount < 4 then exit(EmptyExtent); if Source.YCount < 4 then exit(EmptyExtent);
Result := Source.ExtentList; // axis units Result := Source.ExtentList; // axis units
// Show first and last open/close ticks and candle boxes fully. // Show first and last open/close ticks and candle boxes fully.
x := GetGraphPointX(0); // graph units j := -1;
tw := GetXRange(x, 0) * PERCENT * TickWidth; x := NaN;
while IsNaN(x) and (j < Source.Count-1) do begin
inc(j);
x := GetGraphPointX(j); // graph units
end;
tw := GetXRange(x, j) * PERCENT * TickWidth;
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);
x := GetGraphPointX(Count - 1); j := Count;
tw := GetXRange(x, Count - 1) * PERCENT * TickWidth; x := NaN;
While IsNaN(x) and (j > 0) do begin
dec(j);
x := GetGraphPointX(j);
end;
tw := GetXRange(x, j) * PERCENT * TickWidth;
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;
@ -1604,6 +1679,13 @@ begin
UpdateParentChart; UpdateParentChart;
end; end;
function TOpenHighLowCloseSeries.SkipMissingValues(AIndex: Integer): Boolean;
begin
Result := IsNaN(Source[AIndex]^.Point);
if not Result then
Result := HasMissingYValue(AIndex, 4);
end;
function TOpenHighLowCloseSeries.ToolTargetDistance( function TOpenHighLowCloseSeries.ToolTargetDistance(
const AParams: TNearestPointParams; AGraphPt: TDoublePoint; const AParams: TNearestPointParams; AGraphPt: TDoublePoint;
APointIdx, AXIdx, AYIdx: Integer): Integer; APointIdx, AXIdx, AYIdx: Integer): Integer;
@ -1664,6 +1746,21 @@ begin
end; end;
end; end;
procedure TOpenHighLowCloseSeries.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;
{ TFieldSeries } { TFieldSeries }