TAChart: Support NaNs in bar series

git-svn-id: trunk@32321 -
This commit is contained in:
ask 2011-09-13 16:40:44 +00:00
parent b1dc4a2a6c
commit 6cad306a34
4 changed files with 66 additions and 24 deletions

View File

@ -275,6 +275,7 @@ function FormatIfNotEmpty(AFormat, AStr: String): String; inline;
function InterpolateRGB(AColor1, AColor2: Integer; ACoeff: Double): Integer;
function IntToColorHex(AColor: Integer): String; inline;
function IsNan(const APoint: TDoublePoint): Boolean; overload; inline;
function NumberOr(ANum: Double; ADefault: Double = 0.0): Double; inline;
function OrientToRad(AOrient: Integer): Double; inline;
@ -285,6 +286,7 @@ function RoundChecked(A: Double): Integer; inline;
function SafeInfinity: Double; inline;
function SafeInRange(AValue, ABound1, ABound2: Double): Boolean;
function SafeMin(A, B: Double): Double;
function SafeNan: Double; inline;
procedure SetPropDefaults(AObject: TPersistent; APropNames: array of String);
@ -421,6 +423,11 @@ begin
Result := IsNan(APoint.X) or IsNan(APoint.Y);
end;
function NumberOr(ANum: Double; ADefault: Double): Double;
begin
Result := IfThen(IsNan(ANum), ADefault, ANum);
end;
function OrientToRad(AOrient: Integer): Double;
begin
Result := DegToRad(AOrient / ORIENTATION_UNITS_PER_DEG);
@ -454,6 +461,18 @@ begin
Result := InRange(AValue, ABound1, ABound2);
end;
function SafeMin(A, B: Double): Double;
begin
if IsNan(A) then
Result := B
else if IsNan(B) then
Result := A
else if A < B then
Result := A
else
Result := B;
end;
function SafeNan: Double;
begin
{$PUSH}{$R-}{$Q-}

View File

@ -219,6 +219,7 @@ type
procedure DrawLabels(ADrawer: IChartDrawer);
procedure DrawPointers(ADrawer: IChartDrawer);
procedure GetLegendItemsRect(AItems: TChartLegendItems; ABrush: TBrush);
function NearestXNumber(var AIndex: Integer; ADir: Integer): Double;
function GetXRange(AX: Double; AIndex: Integer): Double;
function GetZeroLevel: Double; virtual;
procedure PrepareGraphPoints(
@ -931,15 +932,15 @@ begin
end;
function TBasicPointSeries.GetXRange(AX: Double; AIndex: Integer): Double;
var
wl, wr: Double;
i: Integer;
begin
case CASE_OF_TWO[AIndex > 0, AIndex < Count - 1] of
cotNone: Result := 1.0;
cotFirst: Result := Abs(AX - GetGraphPointX(AIndex - 1));
cotSecond: Result := Abs(AX - GetGraphPointX(AIndex + 1));
cotBoth: Result := Min(
Abs(AX - GetGraphPointX(AIndex - 1)),
Abs(AX - GetGraphPointX(AIndex + 1)));
end;
i := AIndex - 1;
wl := Abs(AX - NearestXNumber(i, -1));
i := AIndex + 1;
wr := Abs(AX - NearestXNumber(i, +1));
Result := NumberOr(SafeMin(wl, wr), 1.0);
end;
function TBasicPointSeries.GetZeroLevel: Double;
@ -960,6 +961,18 @@ begin
end;
end;
function TBasicPointSeries.NearestXNumber(
var AIndex: Integer; ADir: Integer): Double;
begin
while InRange(AIndex, 0, Count - 1) do
with Source[AIndex]^ do
if IsNan(X) then
AIndex += ADir
else
exit(AxisToGraphX(X));
Result := SafeNan;
end;
procedure TBasicPointSeries.PrepareGraphPoints(
const AExtent: TDoubleRect; AFilterByExtent: Boolean);
var
@ -1062,7 +1075,7 @@ begin
FMinXRange := Abs(x - prevX);
for i := 2 to Count - 1 do begin
x := Source[i]^.X;
FMinXRange := Min(Abs(x - prevX), FMinXRange);
FMinXRange := SafeMin(Abs(x - prevX), FMinXRange);
prevX := x;
end;
end;

View File

@ -552,9 +552,9 @@ begin
Result := Extent;
if YCount < 2 then exit;
for i := 0 to Count - 1 do begin
h := Item[i]^.Y;
h := NumberOr(Item[i]^.Y);
for j := 0 to YCount - 2 do begin
h += Item[i]^.YList[j];
h += NumberOr(Item[i]^.YList[j]);
// If some of Y values are negative, h may be non-monotonic.
UpdateMinMax(h, Result.a.Y, Result.b.Y);
end;
@ -607,10 +607,10 @@ begin
end
else begin
ALB := 0;
while (ALB < Count) and (Item[ALB]^.X < AXMin) do
while (ALB < Count) and (IsNan(Item[ALB]^.X) or (Item[ALB]^.X < AXMin)) do
Inc(ALB);
AUB := Count - 1;
while (AUB >= 0) and (Item[AUB]^.X > AXMax) do
while (AUB >= 0) and (IsNan(Item[AUB]^.X) or (Item[AUB]^.X > AXMax)) do
Dec(AUB);
end;
end;

View File

@ -873,7 +873,7 @@ var
end;
var
z, ofs: Double;
z, ofs, y: Double;
begin
if IsEmpty then exit;
@ -894,12 +894,15 @@ begin
p := FGraphPoints[pointIndex - FLoBound];
if IsRotated then
Exchange(p.X, p.Y);
if IsNan(p.X) then continue;
p.X += ofs;
heights[0] := z;
heights[1] := p.Y;
for stackIndex := 1 to Source.YCount - 1 do
heights[stackIndex + 1] :=
heights[stackIndex] + Source[pointIndex]^.YList[stackIndex - 1];
heights[1] := NumberOr(p.Y, z);
for stackIndex := 1 to Source.YCount - 1 do begin
y := Source[pointIndex]^.YList[stackIndex - 1];
if not IsNan(y) then
heights[stackIndex + 1] := heights[stackIndex] + y;
end;
for stackIndex := 0 to Source.YCount - 1 do
BuildBar;
end;
@ -910,6 +913,7 @@ end;
function TBarSeries.Extent: TDoubleRect;
var
x, ofs, w: Double;
i: Integer;
begin
Result := inherited Extent;
if IsEmpty then exit;
@ -917,12 +921,18 @@ begin
UpdateMinXRange;
UpdateMinMax(ZeroLevel, Result.a.Y, Result.b.Y);
// Show first and last bars fully.
x := GetGraphPointX(0);
BarOffsetWidth(x, 0, ofs, w);
Result.a.X := Min(Result.a.X, x + ofs - w);
x := GetGraphPointX(Count - 1);
BarOffsetWidth(x, Count - 1, ofs, w);
Result.b.X := Max(Result.b.X, x + ofs + w);
i := 0;
x := NearestXNumber(i, +1);
if not IsNan(x) then begin
BarOffsetWidth(x, i, ofs, w);
Result.a.X := Min(Result.a.X, x + ofs - w);
end;
i := Count - 1;
x := NearestXNumber(i, -1);
if not IsNan(x) then begin
BarOffsetWidth(x, i, ofs, w);
Result.b.X := Max(Result.b.X, x + ofs + w);
end;
end;
procedure TBarSeries.GetLegendItems(AItems: TChartLegendItems);