TAChart: Refactor Reticules. Remove circular dependency TAGraph -> TASeries

patch by: Alexander Klenin
part 2 of issue #13163

git-svn-id: trunk@18671 -
This commit is contained in:
vincents 2009-02-13 20:51:16 +00:00
parent eb20e8d8cd
commit 0d8393edf7
3 changed files with 156 additions and 206 deletions

View File

@ -29,7 +29,7 @@ unit TAChartUtils;
interface
uses
Graphics;
Graphics, Types;
const
MaxColor = 15;
@ -38,6 +38,12 @@ const
clTeal, clNavy, clMaroon, clLime, clOlive, clPurple, clSilver, clAqua);
type
TDoublePoint = record
X, Y: Double;
end;
TPointDistFunc = function (const A, B: TPoint): Integer;
TAxisScale = (asIncreasing, asDecreasing, asLogIncreasing, asLogDecreasing);
TPenBrushFont = set of (pbfPen, pbfBrush, pbfFont);
@ -59,9 +65,14 @@ type
procedure CalculateIntervals(
AMin, AMax: Double; AxisScale: TAxisScale; out AStart, AStep: Double);
function EqualPoints(const A, B: TPoint): Boolean; inline;
procedure Exchange(var A, B: Integer); overload;
procedure Exchange(var A, B: Double); overload;
function PointDist(const A, B: TPoint): Integer; inline;
function PointDistX(const A, B: TPoint): Integer; inline;
procedure RotateLabel(
Canvas: TCanvas; x, y: Integer; const St: String; RotDegree: Integer);
@ -139,6 +150,11 @@ begin
end; {case AxisScale}
end;
function EqualPoints(const A, B: TPoint): Boolean;
begin
Result := (A.X = B.X) and (A.Y = B.Y);
end;
procedure Exchange(var A, B: Integer); overload;
var
t: Integer;
@ -157,6 +173,16 @@ begin
B := t;
end;
function PointDist(const A, B: TPoint): Integer;
begin
Result := Sqr(A.X - B.X) + Sqr(A.Y - B.Y);
end;
function PointDistX(const A, B: TPoint): Integer;
begin
Result := Abs(A.X - B.X);
end;
procedure RotateLabel(
Canvas: TCanvas; x, y: Integer; const St: String; RotDegree: Integer);
var

View File

@ -185,6 +185,10 @@ type
var ANumPoints: Integer; var AXMin, AYMin, AXMax, AYMax: Double);
virtual; abstract;
procedure AfterAdd; virtual;
function GetNearestPoint(
ADistFunc: TPointDistFunc; const APoint: TPoint;
out AIndex: Integer; out AImg: TPoint; out AValue: TDoublePoint): Boolean;
virtual;
public
function Count: Integer; virtual; abstract;
procedure DrawIfActive(ACanvas: TCanvas); virtual; abstract;
@ -221,7 +225,6 @@ type
Zoom: Boolean;
Fixed: Boolean;
XDown, YDown, XOld, YOld: Integer;
XVMarkOld, XMarkOld, YMarkOld: Integer;
ZoomRect: TRect;
FShowReticule: Boolean;
@ -230,7 +233,8 @@ type
FDrawVertReticule: TDrawVertReticule;
FDrawReticule: TDrawReticule;
XReticule, YReticule: Integer;
FReticulePos: TPoint;
FVertReticuleX: Integer;
FFrame: TChartPen;
@ -238,6 +242,7 @@ type
FAxisVisible: Boolean;
procedure PrepareXorPen;
procedure SetAutoUpdateXMin(Value: Boolean);
procedure SetAutoUpdateXMax(Value: Boolean);
procedure SetAutoUpdateYMin(Value: Boolean);
@ -251,16 +256,11 @@ type
procedure SetTitle(Value: TChartTitle);
procedure SetFoot(Value: TChartTitle);
function GetLegendWidth(ACanvas: TCanvas): Integer;
procedure GetPointNextTo(
X, Y: Integer; var SerieNumberOut, PointNumberOut, XOut, YOut: Integer);
procedure GetXPointNextTo(
X, Y: Integer; out SerieNumberOut, PointNumberOut, XOut, YOut: Integer);
procedure GetYPointNextTo(
X, Y: Integer; var SerieNumberOut, PointNumberOut, XOut, YOut: Integer);
procedure DrawReticule(ACanvas: TCanvas; X, Y: Integer);
procedure DrawVerticalReticule(ACanvas: TCanvas; X: Integer);
procedure SetShowVerticalReticule(Value: Boolean);
procedure SetShowReticule(Value: Boolean);
procedure MaybeDrawReticules;
procedure DrawReticule(ACanvas: TCanvas; const APos: TPoint);
procedure DrawVerticalReticule(ACanvas: TCanvas; AX: Integer);
procedure SetShowVerticalReticule(AValue: Boolean);
procedure SetShowReticule(AValue: Boolean);
procedure SetLegend(Value: TChartLegend);
procedure SetLeftAxis(Value: TChartAxis);
@ -281,9 +281,9 @@ type
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure DoDrawVertReticule(
IndexSerie, Index, Xi, Yi: Integer; Xg, Yg: Double); virtual;
IndexSerie, Index: Integer; const APoint: TPoint; Xg, Yg: Double); virtual;
procedure DoDrawReticule(
IndexSerie, Index, Xi, Yi: Integer; Xg, Yg: Double); virtual;
IndexSerie, Index: Integer; const APoint: TPoint; Xg, Yg: Double); virtual;
procedure EraseBackground(DC: HDC); override;
public
XImageMin, YImageMin: Integer; // Image coordinates of limits
@ -391,7 +391,7 @@ procedure Register;
implementation
uses
Math, TASeries;
Math;
const
MinDouble = -1.7e308;
@ -681,9 +681,8 @@ begin
Width := 400;
Height := 300;
XVMarkOld := -1;
XMarkOld := -1;
YMarkOld := -1;
FVertReticuleX := -1;
FReticulePos := Point(-1, -1);
Series := TFPList.Create;
@ -818,6 +817,16 @@ begin
Refresh(ACanvas, ARect);
end;
procedure TChart.PrepareXorPen;
begin
Canvas.Brush.Style := bsClear;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Mode := pmXor;
Canvas.Pen.Color := clWhite;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Width := 1;
end;
procedure TChart.Clean(ACanvas: TCanvas; ARect: TRect);
begin
ACanvas.Pen.Mode := pmCopy;
@ -1241,8 +1250,7 @@ end;
procedure TChart.AddSerie(ASerie: TBasicChartSeries);
begin
if FShowVerticalReticule then DrawVerticalReticule(Canvas, XVMarkOld);
if FShowReticule then DrawReticule(Canvas, XMarkOld, YMarkOld);
MaybeDrawReticules;
Series.Add(ASerie);
ASerie.ParentChart := Self;
ASerie.AfterAdd;
@ -1300,8 +1308,7 @@ var
XMinSeries, XMaxSeries, YMinSeries, YMaxSeries: Double;
LeftAxisScale, BottomAxisScale: TAxisScale;
begin
if FShowVerticalReticule then DrawVerticalReticule(ACanvas, XVMarkOld);
if FShowReticule then DrawReticule(ACanvas, XMarkOld, YMarkOld);
MaybeDrawReticules;
// Check AxisScale for both axes
case LeftAxis.Inverted of
true : LeftAxisScale := asDecreasing;
@ -1417,8 +1424,7 @@ begin
DisplaySeries(ACanvas);
DrawTitleFoot(ACanvas, ARect);
if FLegend.Visible then DrawLegend(ACanvas, ARect);
if FShowVerticalReticule then DrawVerticalReticule(ACanvas, XVMarkOld);
if FShowReticule then DrawReticule(ACanvas, XMarkOld, YMarkOld);
MaybeDrawReticules;
end;
procedure TChart.XGraphToImage(Xin: Double; out XOut: Integer);
@ -1519,6 +1525,12 @@ begin
end;
end;
procedure TChart.MaybeDrawReticules;
begin
if FShowVerticalReticule then DrawVerticalReticule(Canvas, FVertReticuleX);
if FShowReticule then DrawReticule(Canvas, FReticulePos);
end;
procedure TChart.SaveToBitmapFile(const FileName: String);
var
tmpR: TRect;
@ -1575,121 +1587,24 @@ begin
SelectClipRgn(ACanvas.Handle, 0);
end;
procedure TChart.SetShowVerticalReticule(Value: Boolean);
procedure TChart.SetShowVerticalReticule(AValue: Boolean);
begin
if FShowVerticalReticule then begin
DrawVerticalReticule(Canvas, XVMarkOld);
DrawVerticalReticule(Canvas, FVertReticuleX);
FShowVerticalReticule := false;
end;
FShowVerticalReticule := Value;
FShowVerticalReticule := AValue;
Invalidate;
end;
procedure TChart.SetShowReticule(Value: Boolean);
procedure TChart.SetShowReticule(AValue: Boolean);
begin
if not Value then
DrawReticule(Canvas, XVMarkOld, YMarkOld);
FShowReticule := Value;
if not AValue then
DrawReticule(Canvas, FReticulePos);
FShowReticule := AValue;
Invalidate;
end;
procedure TChart.GetPointNextTo(
X, Y: Integer; var SerieNumberOut, PointNumberOut, XOut, YOut: Integer);
var
XPoint, YPoint, SerieNumber, PointNumber: Integer;
Mini, Dist, XgOut, YgOut: Double;
Serie: TComponent;
TASerie: TSerie;
T1, T2: Double;
begin
Mini := MaxDouble;
for SerieNumber := 0 to Series.Count - 1 do begin
Serie := Series[SerieNumber];
if Serie is TSerie then begin
TASerie := TSerie(Serie);
for PointNumber := 0 to TASerie.Count - 1 do begin
XPoint := TASerie.GetXImgValue(PointNumber);
YPoint := TASerie.GetYImgValue(PointNumber);
T1 := X - XPoint;
T2 := Y - YPoint;
Dist := Sqrt(Sqr(T1) + Sqr(T2));
if Dist <= Mini then begin
Mini := Dist;
SerieNumberOut := SerieNumber;
PointNumberOut := PointNumber;
XOut := XPoint;
YOut := YPoint;
XgOut := TASerie.GetXValue(PointNumber);
YgOut := TASerie.GetYValue(PointNumber);
end;
end;
if SerieNumberOut = SerieNumber then
DoDrawReticule(SerieNumberOut, PointNumberOut, XOut, YOut, XgOut, YgOut);
end;
end;
end;
procedure TChart.GetXPointNextTo(
X, Y: Integer; out SerieNumberOut, PointNumberOut, XOut, YOut: Integer);
var
XPoint, SerieNumber, PointNumber: Integer;
Mini, Dist, Xg, Yg: Double;
Serie: TComponent;
TASerie: TSerie;
begin
Mini := MaxDouble;
SerieNumberOut := -1;
for SerieNumber := 0 to Series.Count-1 do begin
Serie := Series[SerieNumber];
if Serie is TSerie then begin
TASerie := TSerie(Serie);
for PointNumber := 0 to TASerie.Count - 1 do begin
XPoint := TASerie.GetXImgValue(PointNumber);
Dist := Abs(X - XPoint);
if Dist <= Mini then begin
Mini := Dist;
SerieNumberOut := SerieNumber;
PointNumberOut := PointNumber;
XOut := XPoint;
YOut := TASerie.GetYImgValue(PointNumber);
Xg := TASerie.GetXValue(PointNumber);
Yg := TASerie.GetYValue(PointNumber);
end;
end;
if SerieNumberOut = SerieNumber then
DoDrawVertReticule(SerieNumberOut, PointNumberOut, XOut, YOut, Xg, Yg);
end;
end;
end;
procedure TChart.GetYPointNextTo(
X, Y: Integer; var SerieNumberOut, PointNumberOut, XOut, YOut: Integer);
var
XPoint, YPoint, SerieNumber, PointNumber: Integer;
Mini, Dist: Double;
Serie: TComponent;
TASerie: TSerie;
begin
Mini := MaxDouble;
for SerieNumber := 0 to Series.Count-1 do begin
Serie := Series[SerieNumber];
if Serie is TSerie then begin
TASerie := TSerie(Serie);
for PointNumber := 0 to TASerie.Count-1 do begin
YPoint := TASerie.GetYImgValue(PointNumber);
Dist := Abs(Y - YPoint);
if Dist <= Mini then begin
Mini := Dist;
SerieNumberOut := SerieNumber;
PointNumberOut := PointNumber;
XOut := XPoint;
YOut := YPoint;
end;
end;
end;
end;
end;
procedure TChart.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if
@ -1704,44 +1619,32 @@ begin
end;
end;
procedure TChart.DrawReticule(ACanvas: TCanvas; X, Y: Integer);
procedure TChart.DrawReticule(ACanvas: TCanvas; const APos: TPoint);
begin
ACanvas.Pen.Style := psSolid;
ACanvas.Pen.Mode := pmXor;
ACanvas.Pen.Color := ClWhite;
ACanvas.Pen.Style := psSolid;
ACanvas.Pen.Width := 1;
ACanvas.MoveTo(X, YImageMin);
ACanvas.LineTo(X, YImageMax);
ACanvas.MoveTo(XImageMin, Y);
ACanvas.LineTo(XImageMax, Y);
PrepareXorPen;
ACanvas.MoveTo(APos.X, YImageMin);
ACanvas.LineTo(APos.X, YImageMax);
ACanvas.MoveTo(XImageMin, APos.Y);
ACanvas.LineTo(XImageMax, APos.Y);
end;
procedure TChart.DrawVerticalReticule(ACanvas: TCanvas; X: Integer);
procedure TChart.DrawVerticalReticule(ACanvas: TCanvas; AX: Integer);
begin
ACanvas.Pen.Style := psSolid;
ACanvas.Pen.Mode := pmXor;
ACanvas.Pen.Color := clWhite;
ACanvas.Pen.Style := psSolid;
ACanvas.Pen.Width := 1;
ACanvas.MoveTo(X, YImageMin);
ACanvas.LineTo(X, YImageMax);
PrepareXorPen;
ACanvas.MoveTo(AX, YImageMin);
ACanvas.LineTo(AX, YImageMax);
end;
procedure TChart.MouseMove(Shift: TShiftState; X, Y: Integer);
var
i, SerieNumber, PointNumber, XMin, Xmax, YMin, YMax: Integer;
i, pointIndex: Integer;
r: TRect;
pt, newRetPos: TPoint;
serie: TBasicChartSeries;
value: TDoublePoint;
begin
if Down then begin
Canvas.Brush.Style := bsClear;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Mode := pmXor;
Canvas.Pen.Color := clWhite;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Width := 1;
PrepareXorPen;
Canvas.Rectangle(XDown, YDown, XOld, YOld);
Canvas.Rectangle(XDown, YDown, X, Y);
@ -1749,40 +1652,35 @@ begin
YOld := Y;
exit;
end;
XMin := XImageMin;
XMax := XImageMax;
YMin := YImageMin;
YMax := YImageMax;
if XMin > XMax then
Exchange(Xmin, Xmax);
if YMin > YMax then
Exchange(YMin, YMax);
r := Rect(XImageMin, YImageMin, XImageMax, YImageMax);
if r.Top > r.Bottom then
Exchange(r.Top, r.Bottom);
if r.Left > r.Right then
Exchange(r.Left, r.Right);
pt := Point(X, Y);
for i := 0 to SeriesCount - 1 do begin
if FShowVerticalReticule then begin
GetXPointNextTo(X, Y, SerieNumber, PointNumber, XReticule, YReticule);
if
(XReticule <> XVMarkOld) and (XReticule > XMin) and (XReticule < XMax)
then begin
DrawVerticalReticule(Canvas, XVMarkOld);
DrawVerticalReticule(Canvas, XReticule);
FShowVerticalReticule := True;
XVMarkOld := XReticule;
end;
serie := TBasicChartSeries(Series[i]);
if
FShowVerticalReticule and
serie.GetNearestPoint(@PointDistX, pt, pointIndex, newRetPos, value) and
(newRetPos.X <> FVertReticuleX) and
InRange(newRetPos.X, r.Left, r.Right)
then begin
DoDrawVertReticule(i, pointIndex, newRetPos, value.X, value.Y);
DrawVerticalReticule(Canvas, FVertReticuleX);
DrawVerticalReticule(Canvas, newRetPos.X);
FVertReticuleX := newRetPos.X;
end;
if FShowReticule then begin
GetPointNextTo(X, Y, SerieNumber, PointNumber, XReticule, YReticule);
if (XReticule <> XMarkOld) or (YReticule <> YMarkOld) then
if
(XReticule >= XMin) and (XReticule <= XMax) and
(YReticule >= YMin) and (YReticule <= YMax)
then begin
DrawReticule(Canvas, XMarkOld, YMarkOld);
DrawReticule(Canvas, XReticule, YReticule);
FShowReticule := true;
XMarkOld := XReticule;
YMarkOld := YReticule;
end;
if
FShowReticule and
serie.GetNearestPoint(@PointDistX, pt, pointIndex, newRetPos, value) and
not EqualPoints(newRetPos, FReticulePos) and PtInRect(r, newRetPos)
then begin
DoDrawReticule(i, pointIndex, newRetPos, value.X, value.Y);
DrawReticule(Canvas, FReticulePos);
DrawReticule(Canvas, newRetPos);
FReticulePos := newRetPos;
end;
end;
end;
@ -1790,16 +1688,9 @@ end;
procedure TChart.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if not Down then exit;
XMarkOld := X;
YMarkOld := Y;
Canvas.Brush.Style := bsClear;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Mode := pmXor;
Canvas.Pen.Color := clWhite;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Width := 1;
FReticulePos := Point(X, Y);
PrepareXorPen;
Canvas.Rectangle(XDown, YDown, XOld, YOld);
Down := false;
@ -1830,17 +1721,17 @@ begin
end;
procedure TChart.DoDrawVertReticule(
IndexSerie, Index, Xi, Yi: Integer; Xg, Yg: Double);
IndexSerie, Index: Integer; const APoint: TPoint; Xg, Yg: Double);
begin
if Assigned(FDrawVertReticule) then
FDrawVertReticule(Self, IndexSerie, Index, Xi, Yi, Xg, Yg);
FDrawVertReticule(Self, IndexSerie, Index, APoint.X, APoint.Y, Xg, Yg);
end;
procedure TChart.DoDrawReticule(
IndexSerie, Index, Xi, Yi: Integer; Xg, Yg: Double);
IndexSerie, Index: Integer; const APoint: TPoint; Xg, Yg: Double);
begin
if Assigned(FDrawReticule) then
FDrawReticule(Self, IndexSerie, Index, Xi, Yi, Xg, Yg);
FDrawReticule(Self, IndexSerie, Index, APoint.X, APoint.Y, Xg, Yg);
end;
function TChart.GetNewColor: TColor;
@ -1934,6 +1825,13 @@ procedure TBasicChartSeries.AfterAdd;
begin
end;
function TBasicChartSeries.GetNearestPoint(
ADistFunc: TPointDistFunc; const APoint: TPoint;
out AIndex: Integer; out AImg: TPoint; out AValue: TDoublePoint): Boolean;
begin
Result := false;
end;
procedure Register;
begin
RegisterComponents('Additional', [TChart]);

View File

@ -35,7 +35,7 @@ uses
{$ELSE}
Windows,
{$ENDIF}
Classes, Dialogs, Graphics, sysutils, TAGraph;
Classes, Dialogs, Graphics, sysutils, TAGraph, TAChartUtils;
type
@ -263,6 +263,11 @@ type
procedure SetShowPoints(Value: Boolean);
procedure SetShowLines(Value: Boolean);
procedure SetPointer(Value: TSeriesPointer);
protected
function GetNearestPoint(
ADistFunc: TPointDistFunc; const APoint: TPoint;
out AIndex: Integer; out AImg: TPoint; out AValue: TDoublePoint): Boolean;
override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
@ -330,8 +335,7 @@ type
implementation
uses
math, types,
TAChartUtils;
math, types;
constructor TChartSeries.Create(AOwner: TComponent);
begin
@ -917,6 +921,28 @@ begin
Y := YGraphMin;
end;
function TSerie.GetNearestPoint(
ADistFunc: TPointDistFunc; const APoint: TPoint;
out AIndex: Integer; out AImg: TPoint; out AValue: TDoublePoint): Boolean;
var
dist, minDist, i: Integer;
pt: TPoint;
begin
Result := Count > 0;
minDist := MaxInt;
for i := 0 to Count - 1 do begin
pt := Point(GetXImgValue(i), GetYImgValue(i));
dist := ADistFunc(APoint, pt);
if dist >= minDist then
Continue;
minDist := dist;
AIndex := i;
AImg := pt;
AValue.X := GetXValue(i);
AValue.Y := GetYValue(i);
end;
end;
procedure TSerie.SetColor(Index: Integer; AColor: TColor);
begin
PChartCoord(FCoordList.items[Index])^.Color := AColor;
@ -1624,8 +1650,8 @@ begin
inherited DrawLegend(ACanvas, ARect);
ACanvas.Pen.Color := SeriesColor;
y := (ARect.Top + ARect.Bottom) div 2;
ACanvas.MoveTo(ARect.Left, ARect.Top + 5);
ACanvas.LineTo(ARect.Right, ARect.Top + 5);
ACanvas.MoveTo(ARect.Left, y);
ACanvas.LineTo(ARect.Right, y);
end;
end.