mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-08-24 01:29:51 +02:00
TAChart: Bar drawing improvements.
* Extract RectIntersectsRect and ExpandRange procedures. * Fix various issues with zoomed display of bar series. * Allow variable-width bars. * Replace hack in extent calculation with correct code. * Remove auto-adjustment of bar width to a multiple bar series. This was an ugly hack and should be re-implemented properly later. git-svn-id: trunk@20633 -
This commit is contained in:
parent
26309ffc92
commit
e8953985b2
@ -129,8 +129,7 @@ procedure Exchange(var A, B: Integer); overload;
|
|||||||
procedure Exchange(var A, B: Double); overload;
|
procedure Exchange(var A, B: Double); overload;
|
||||||
procedure Exchange(var A, B: TDoublePoint); overload;
|
procedure Exchange(var A, B: TDoublePoint); overload;
|
||||||
|
|
||||||
// True if float ranges [A, B] and [C, D] have at least one common point.
|
procedure ExpandRange(var ALo, AHi: Double; ACoeff: Double); inline;
|
||||||
function FloatRangesOverlap(A, B, C, D: Double): Boolean; inline;
|
|
||||||
|
|
||||||
function GetIntervals(AMin, AMax: Double; AInverted: Boolean): TDoubleDynArray;
|
function GetIntervals(AMin, AMax: Double; AInverted: Boolean): TDoubleDynArray;
|
||||||
|
|
||||||
@ -141,6 +140,9 @@ function PointDist(const A, B: TPoint): Integer; inline;
|
|||||||
function PointDistX(const A, B: TPoint): Integer; inline;
|
function PointDistX(const A, B: TPoint): Integer; inline;
|
||||||
function PointDistY(const A, B: TPoint): Integer; inline;
|
function PointDistY(const A, B: TPoint): Integer; inline;
|
||||||
|
|
||||||
|
function RectIntersectsRect(
|
||||||
|
var ARect: TDoubleRect; const AFixed: TDoubleRect): Boolean;
|
||||||
|
|
||||||
function RoundChecked(A: Double): Integer; inline;
|
function RoundChecked(A: Double): Integer; inline;
|
||||||
|
|
||||||
// Call this to silence 'parameter is unused' hint
|
// Call this to silence 'parameter is unused' hint
|
||||||
@ -274,9 +276,13 @@ begin
|
|||||||
B := t;
|
B := t;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function FloatRangesOverlap(A, B, C, D: Double): Boolean; inline;
|
procedure ExpandRange(var ALo, AHi: Double; ACoeff: Double); inline;
|
||||||
|
var
|
||||||
|
d: Double;
|
||||||
begin
|
begin
|
||||||
Result := (A <= D) and (C <= B);
|
d := AHi - ALo;
|
||||||
|
ALo -= d * ACoeff;
|
||||||
|
AHi += d * ACoeff;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function GetIntervals(AMin, AMax: Double; AInverted: Boolean): TDoubleDynArray;
|
function GetIntervals(AMin, AMax: Double; AInverted: Boolean): TDoubleDynArray;
|
||||||
@ -320,13 +326,13 @@ var
|
|||||||
|
|
||||||
procedure AdjustX(var AP: TDoublePoint; ANewX: Double); inline;
|
procedure AdjustX(var AP: TDoublePoint; ANewX: Double); inline;
|
||||||
begin
|
begin
|
||||||
AP.Y += dy / dx * (ANewX - AA.X);
|
AP.Y += dy / dx * (ANewX - AP.X);
|
||||||
AP.X := ANewX;
|
AP.X := ANewX;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure AdjustY(var AP: TDoublePoint; ANewY: Double); inline;
|
procedure AdjustY(var AP: TDoublePoint; ANewY: Double); inline;
|
||||||
begin
|
begin
|
||||||
AP.X += dx / dy * (ANewY - AA.Y);
|
AP.X += dx / dy * (ANewY - AP.Y);
|
||||||
AP.Y := ANewY;
|
AP.Y := ANewY;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -373,6 +379,25 @@ end;
|
|||||||
|
|
||||||
{$HINTS OFF}
|
{$HINTS OFF}
|
||||||
|
|
||||||
|
function RectIntersectsRect(
|
||||||
|
var ARect: TDoubleRect; const AFixed: TDoubleRect): Boolean;
|
||||||
|
|
||||||
|
function RangesIntersect(L1, R1, L2, R2: Double; out L, R: Double): Boolean;
|
||||||
|
begin
|
||||||
|
if L1 > R1 then Exchange(L1, R1);
|
||||||
|
if L2 > R2 then Exchange(L2, R2);
|
||||||
|
L := Max(L1, L2);
|
||||||
|
R := Min(R1, R2);
|
||||||
|
Result := L <= R;
|
||||||
|
end;
|
||||||
|
|
||||||
|
begin
|
||||||
|
with ARect do
|
||||||
|
Result :=
|
||||||
|
RangesIntersect(a.X, b.X, AFixed.a.X, AFixed.b.X, a.X, b.X) and
|
||||||
|
RangesIntersect(a.Y, b.Y, AFixed.a.Y, AFixed.b.Y, a.Y, b.Y);
|
||||||
|
end;
|
||||||
|
|
||||||
function RoundChecked(A: Double): Integer;
|
function RoundChecked(A: Double): Integer;
|
||||||
begin
|
begin
|
||||||
Result := Round(EnsureRange(A, -MaxInt, MaxInt));
|
Result := Round(EnsureRange(A, -MaxInt, MaxInt));
|
||||||
|
@ -111,8 +111,7 @@ type
|
|||||||
FBarPen: TPen;
|
FBarPen: TPen;
|
||||||
FBarWidthPercent: Integer;
|
FBarWidthPercent: Integer;
|
||||||
|
|
||||||
procedure ExamineAllBarSeries(out ATotalNumber, AMyPos: Integer);
|
function CalcBarWidth(AX: Double; AIndex: Integer): Double;
|
||||||
procedure SetAdjustBarWidth(AValue: Boolean);
|
|
||||||
procedure SetBarBrush(Value: TBrush);
|
procedure SetBarBrush(Value: TBrush);
|
||||||
procedure SetBarPen(Value: TPen);
|
procedure SetBarPen(Value: TPen);
|
||||||
procedure SetBarWidthPercent(Value: Integer);
|
procedure SetBarWidthPercent(Value: Integer);
|
||||||
@ -127,8 +126,6 @@ type
|
|||||||
procedure Draw(ACanvas: TCanvas); override;
|
procedure Draw(ACanvas: TCanvas); override;
|
||||||
function Extent: TDoubleRect; override;
|
function Extent: TDoubleRect; override;
|
||||||
published
|
published
|
||||||
property AdjustBarWidth: Boolean
|
|
||||||
read FAdjustBarWidth write SetAdjustBarWidth default false;
|
|
||||||
property BarBrush: TBrush read FBarBrush write SetBarBrush;
|
property BarBrush: TBrush read FBarBrush write SetBarBrush;
|
||||||
property BarPen: TPen read FBarPen write SetBarPen;
|
property BarPen: TPen read FBarPen write SetBarPen;
|
||||||
property BarWidthPercent: Integer
|
property BarWidthPercent: Integer
|
||||||
@ -662,7 +659,6 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TLineSeries.AfterAdd;
|
procedure TLineSeries.AfterAdd;
|
||||||
begin
|
begin
|
||||||
inherited AfterAdd;
|
inherited AfterAdd;
|
||||||
@ -941,6 +937,19 @@ end;
|
|||||||
|
|
||||||
{ TBarSeries }
|
{ TBarSeries }
|
||||||
|
|
||||||
|
function TBarSeries.CalcBarWidth(AX: Double; AIndex: Integer): Double;
|
||||||
|
begin
|
||||||
|
case CASE_OF_TWO[AIndex > 0, AIndex < Count - 1] of
|
||||||
|
cotNone: Result := 1.0;
|
||||||
|
cotFirst: Result := Abs(AX - Source[AIndex - 1]^.X);
|
||||||
|
cotSecond: Result := Abs(AX - Source[AIndex + 1]^.X);
|
||||||
|
cotBoth: Result := Min(
|
||||||
|
Abs(AX - Source[AIndex - 1]^.X),
|
||||||
|
Abs(AX - Source[AIndex + 1]^.X));
|
||||||
|
end;
|
||||||
|
Result *= FBarWidthPercent * 0.01 / 2;
|
||||||
|
end;
|
||||||
|
|
||||||
constructor TBarSeries.Create(AOwner: TComponent);
|
constructor TBarSeries.Create(AOwner: TComponent);
|
||||||
begin
|
begin
|
||||||
inherited Create(AOwner);
|
inherited Create(AOwner);
|
||||||
@ -988,57 +997,13 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TBarSeries.Draw(ACanvas: TCanvas);
|
procedure TBarSeries.Draw(ACanvas: TCanvas);
|
||||||
var
|
|
||||||
barTop: TDoublePoint;
|
|
||||||
i, barWidth, totalbarWidth, totalBarSeries, myPos: Integer;
|
|
||||||
r: TRect;
|
|
||||||
|
|
||||||
function PrepareBar: Boolean;
|
procedure DrawBar(const AR: TRect);
|
||||||
var
|
var
|
||||||
barBottomY: Double;
|
sz: TSize;
|
||||||
begin
|
begin
|
||||||
barTop := DoublePoint(Source[i]^);
|
sz := Size(AR);
|
||||||
barBottomY := 0;
|
if (sz.cx > 2) and (sz.cy > 2) then
|
||||||
if barTop.Y < barBottomY then
|
|
||||||
Exchange(barTop.Y, barBottomY);
|
|
||||||
|
|
||||||
with ParentChart do begin
|
|
||||||
// Check if bar is in view port.
|
|
||||||
Result :=
|
|
||||||
InRange(barTop.X, XGraphMin, XGraphMax) and
|
|
||||||
FloatRangesOverlap(barBottomY, barTop.Y, YGraphMin, YGraphMax);
|
|
||||||
if not Result then exit;
|
|
||||||
|
|
||||||
// Only draw to the limits.
|
|
||||||
if barTop.Y > YGraphMax then barTop.Y := YGraphMax;
|
|
||||||
if barBottomY < YGraphMin then barBottomY := YGraphMin;
|
|
||||||
|
|
||||||
r.TopLeft := GraphToImage(barTop);
|
|
||||||
r.Bottom := YGraphToImage(barBottomY);
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Adjust for multiple bar series.
|
|
||||||
r.Left += myPos * barWidth - totalbarWidth div 2;
|
|
||||||
r.Right := r.Left + barWidth;
|
|
||||||
end;
|
|
||||||
|
|
||||||
begin
|
|
||||||
if IsEmpty then exit;
|
|
||||||
|
|
||||||
totalbarWidth :=
|
|
||||||
Round(FBarWidthPercent * 0.01 * ParentChart.ChartWidth / Count);
|
|
||||||
ExamineAllBarSeries(totalBarSeries, myPos);
|
|
||||||
barWidth := totalbarWidth div totalBarSeries;
|
|
||||||
|
|
||||||
ACanvas.Brush.Assign(BarBrush);
|
|
||||||
for i := 0 to Count - 1 do begin
|
|
||||||
if not PrepareBar then continue;
|
|
||||||
// Draw a line instead of an empty rectangle.
|
|
||||||
if r.Bottom = r.Top then Inc(r.Bottom);
|
|
||||||
if r.Left = r.Right then Inc(r.Right);
|
|
||||||
|
|
||||||
ACanvas.Brush.Color := ColorOrDefault(Source[i]^.Color);
|
|
||||||
if (barWidth > 2) and (r.Bottom - r.Top > 2) then
|
|
||||||
ACanvas.Pen.Assign(BarPen)
|
ACanvas.Pen.Assign(BarPen)
|
||||||
else begin
|
else begin
|
||||||
// Bars are too small to distinguish border from interior.
|
// Bars are too small to distinguish border from interior.
|
||||||
@ -1046,21 +1011,56 @@ begin
|
|||||||
ACanvas.Pen.Style := psSolid;
|
ACanvas.Pen.Style := psSolid;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
ACanvas.Rectangle(r);
|
ACanvas.Rectangle(AR);
|
||||||
if Depth > 0 then begin
|
|
||||||
DrawLineDepth(ACanvas, r.Left, r.Top, r.Right - 1, r.Top, Depth);
|
if Depth = 0 then exit;
|
||||||
DrawLineDepth(
|
DrawLineDepth(ACanvas, AR.Left, AR.Top, AR.Right - 1, AR.Top, Depth);
|
||||||
ACanvas, r.Right - 1, r.Top, r.Right - 1, r.Bottom - 1, Depth);
|
DrawLineDepth(
|
||||||
|
ACanvas, AR.Right - 1, AR.Top, AR.Right - 1, AR.Bottom - 1, Depth);
|
||||||
|
end;
|
||||||
|
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
ext2, graphBar: TDoubleRect;
|
||||||
|
imageBar: TRect;
|
||||||
|
w: Double;
|
||||||
|
p: TDoublePoint;
|
||||||
|
begin
|
||||||
|
if IsEmpty then exit;
|
||||||
|
|
||||||
|
ext2 := ParentChart.CurrentExtent;
|
||||||
|
ExpandRange(ext2.a.X, ext2.b.X, 1.0);
|
||||||
|
ExpandRange(ext2.a.Y, ext2.b.Y, 1.0);
|
||||||
|
|
||||||
|
ACanvas.Brush.Assign(BarBrush);
|
||||||
|
for i := 0 to Count - 1 do begin
|
||||||
|
p := DoublePoint(Source[i]^);
|
||||||
|
w := CalcBarWidth(p.X, i);
|
||||||
|
graphBar := DoubleRect(p.X - w, 0, p.X + w, p.Y);
|
||||||
|
if not RectIntersectsRect(graphBar, ext2) then continue;
|
||||||
|
|
||||||
|
with imageBar do begin
|
||||||
|
TopLeft := ParentChart.GraphToImage(graphBar.a);
|
||||||
|
BottomRight := ParentChart.GraphToImage(graphBar.b);
|
||||||
|
if Left > Right then
|
||||||
|
Exchange(Left, Right);
|
||||||
|
if Top > Bottom then
|
||||||
|
Exchange(Top, Bottom);
|
||||||
|
|
||||||
|
// Draw a line instead of an empty rectangle.
|
||||||
|
if Bottom = Top then Dec(Top);
|
||||||
|
if Left = Right then Inc(Right);
|
||||||
end;
|
end;
|
||||||
|
ACanvas.Brush.Color := ColorOrDefault(Source[i]^.Color);
|
||||||
|
DrawBar(imageBar);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if not Marks.IsMarkLabelsVisible then exit;
|
if not Marks.IsMarkLabelsVisible then exit;
|
||||||
for i := 0 to Count - 1 do
|
for i := 0 to Count - 1 do begin
|
||||||
if PrepareBar then
|
p := DoublePoint(Source[i]^);
|
||||||
DrawLabel(
|
if ParentChart.IsPointInViewPort(p) then
|
||||||
ACanvas, i,
|
DrawLabel(ACanvas, i, ParentChart.GraphToImage(p), p.Y < 0);
|
||||||
Point((r.Left + r.Right) div 2, IfThen(barTop.Y = 0, r.Bottom, r.Top)),
|
end;
|
||||||
barTop.Y = 0);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TBarSeries.DrawLegend(ACanvas: TCanvas; const ARect: TRect);
|
procedure TBarSeries.DrawLegend(ACanvas: TCanvas; const ARect: TRect);
|
||||||
@ -1071,34 +1071,16 @@ begin
|
|||||||
ACanvas.Rectangle(ARect);
|
ACanvas.Rectangle(ARect);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TBarSeries.ExamineAllBarSeries(out ATotalNumber, AMyPos: Integer);
|
|
||||||
var
|
|
||||||
i: Integer;
|
|
||||||
begin
|
|
||||||
if not AdjustBarWidth then begin
|
|
||||||
ATotalNumber := 1;
|
|
||||||
AMyPos := 0;
|
|
||||||
exit;
|
|
||||||
end;
|
|
||||||
ATotalNumber := 0;
|
|
||||||
AMyPos := -1;
|
|
||||||
for i := 0 to ParentChart.SeriesCount - 1 do begin
|
|
||||||
if ParentChart.Series[i] = Self then
|
|
||||||
AMyPos := ATotalNumber;
|
|
||||||
if ParentChart.Series[i] is TBarSeries then
|
|
||||||
Inc(ATotalNumber);
|
|
||||||
end;
|
|
||||||
Assert(AMyPos >= 0);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TBarSeries.Extent: TDoubleRect;
|
function TBarSeries.Extent: TDoubleRect;
|
||||||
begin
|
begin
|
||||||
Result := inherited Extent;
|
Result := inherited Extent;
|
||||||
Result.a.Y := Min(Result.a.Y, 0);
|
if IsEmpty then exit;
|
||||||
Result.b.Y := Max(Result.b.Y, 0);
|
UpdateMinMax(0, Result.a.Y, Result.b.Y);
|
||||||
// The 0.6 is a hack to allow the bars to have some space apart
|
// Show first and last bars fully.
|
||||||
Result.a.X -= 0.6;
|
with Source[0]^ do
|
||||||
Result.b.X += 0.6;
|
Result.a.X := Min(Result.a.X, X - CalcBarWidth(X, 0));
|
||||||
|
with Source[Count - 1]^ do
|
||||||
|
Result.b.X := Max(Result.b.X, X + CalcBarWidth(X, Count - 1));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TBarSeries.GetSeriesColor: TColor;
|
function TBarSeries.GetSeriesColor: TColor;
|
||||||
@ -1106,13 +1088,6 @@ begin
|
|||||||
Result := FBarBrush.Color;
|
Result := FBarBrush.Color;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TBarSeries.SetAdjustBarWidth(AValue: Boolean);
|
|
||||||
begin
|
|
||||||
if FAdjustBarWidth = AValue then exit;
|
|
||||||
FAdjustBarWidth := AValue;
|
|
||||||
UpdateParentChart;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TPieSeries }
|
{ TPieSeries }
|
||||||
|
|
||||||
function TPieSeries.AddPie(Value: Double; Text: String; Color: TColor): Longint;
|
function TPieSeries.AddPie(Value: Double; Text: String; Color: TColor): Longint;
|
||||||
|
Loading…
Reference in New Issue
Block a user