3
*****************************************************************************
5
* See the file COPYING.modifiedLGPL.txt, included in this distribution, *
6
* for details about the copyright. *
8
* This program is distributed in the hope that it will be useful, *
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
12
*****************************************************************************
14
Authors: Alexander Klenin
17
unit TAChartAxisUtils;
25
TAChartUtils, TACustomSource, TADrawUtils, TAIntervalSources, TAStyles,
29
DEF_TITLE_DISTANCE = 4;
32
TChartAxisBrush = class(TBrush)
34
property Style default bsClear;
37
TChartAxisFramePen = class(TChartPen)
39
property Style default psClear;
42
{$IFNDEF fpdoc} // Workaround for issue #18549.
43
TCustomChartAxisTitle =
44
specialize TGenericChartMarks<TChartAxisBrush, TChartPen, TChartAxisFramePen>;
49
TChartAxisTitle = class(TCustomChartAxisTitle)
52
FPositionOnMarks: Boolean;
54
function GetFont: TFont;
55
procedure SetCaption(AValue: String);
56
procedure SetFont(AValue: TFont);
57
procedure SetPositionOnMarks(AValue: Boolean);
59
constructor Create(AOwner: TCustomChart);
62
procedure Assign(Source: TPersistent); override;
64
property Caption: String read FCaption write SetCaption;
65
property Distance default DEF_TITLE_DISTANCE;
66
// Use LabelFont instead.
67
property Font: TFont read GetFont write SetFont stored false; deprecated;
70
property PositionOnMarks: Boolean
71
read FPositionOnMarks write SetPositionOnMarks default false;
72
property Visible default false;
75
ICoordTransformer = interface
76
['{6EDA0F9F-ED59-4CA6-BA68-E247EB88AE3D}']
77
function XGraphToImage(AX: Double): Integer;
78
function YGraphToImage(AY: Double): Integer;
81
TChartAxisAlignment = (calLeft, calTop, calRight, calBottom);
82
TChartAxisMargins = array [TChartAxisAlignment] of Integer;
83
TChartAxisMarkToTextEvent =
84
procedure (var AText: String; AMark: Double) of object;
86
{$IFNDEF fpdoc} // Workaround for issue #18549.
87
TBasicChartAxisMarks =
88
specialize TGenericChartMarks<TChartAxisBrush, TChartPen, TChartAxisFramePen>;
91
TCustomChartAxisMarks = class(TBasicChartAxisMarks)
93
FDefaultListener: TListener;
94
FDefaultSource: TIntervalChartSource;
95
FStripes: TChartStyles;
96
procedure SetStripes(AValue: TChartStyles);
98
function IsFormatStored: Boolean;
100
constructor Create(AOwner: TCustomChart);
101
destructor Destroy; override;
103
ADrawer: IChartDrawer; AIsVertical: Boolean; ATickLength: Integer;
104
AValues: TChartValueTextArray): Integer;
105
property DefaultSource: TIntervalChartSource read FDefaultSource;
106
property Stripes: TChartStyles read FStripes write SetStripes;
109
TChartMinorAxisMarks = class(TCustomChartAxisMarks)
111
constructor Create(AOwner: TCustomChart);
113
property Distance default 1;
117
property OverlapPolicy;
118
property Style default smsNone;
123
TChartAxisMarks = class(TCustomChartAxisMarks)
125
FAtDataOnly: Boolean;
126
FListener: TListener;
128
FSource: TCustomChartSource;
130
procedure SetAtDataOnly(AValue: Boolean);
131
procedure SetRange(AValue: TChartRange);
132
procedure SetSource(AValue: TCustomChartSource);
134
constructor Create(AOwner: TCustomChart);
135
destructor Destroy; override;
137
function SourceDef: TCustomChartSource;
139
property AtDataOnly: Boolean
140
read FAtDataOnly write SetAtDataOnly default false;
141
property Distance default 1;
142
property Format stored IsFormatStored;
145
property OverlapPolicy;
146
property Range: TChartRange read FRange write SetRange;
147
property Source: TCustomChartSource read FSource write SetSource;
149
property Style default smsValue;
153
TChartAxisGridPen = class(TChartPen)
155
property Style default psDot;
158
TChartBasicAxis = class(TCollectionItem)
161
FGrid: TChartAxisGridPen;
163
FTickLength: Integer;
165
function GetIntervals: TChartAxisIntervalParams;
166
procedure SetArrow(AValue: TChartArrow);
167
procedure SetGrid(AValue: TChartAxisGridPen);
168
procedure SetIntervals(AValue: TChartAxisIntervalParams);
169
procedure SetTickColor(AValue: TColor);
170
procedure SetTickLength(AValue: Integer);
171
procedure SetVisible(AValue: Boolean);
173
FMarks: TCustomChartAxisMarks;
174
function GetAlignment: TChartAxisAlignment; virtual; abstract;
175
procedure SetAlignment(AValue: TChartAxisAlignment); virtual; abstract;
176
procedure SetMarks(AValue: TCustomChartAxisMarks);
177
procedure StyleChanged(ASender: TObject); virtual; abstract;
179
constructor Create(ACollection: TCollection; AChart: TCustomChart); overload;
180
destructor Destroy; override;
182
procedure Assign(ASource: TPersistent); override;
183
function TryApplyStripes(
184
ADrawer: IChartDrawer; var AIndex: Cardinal): Boolean;
186
property Alignment: TChartAxisAlignment
187
read GetAlignment write SetAlignment;
188
property Arrow: TChartArrow read FArrow write SetArrow;
189
property Marks: TCustomChartAxisMarks read FMarks write SetMarks;
191
property Grid: TChartAxisGridPen read FGrid write SetGrid;
192
property Intervals: TChartAxisIntervalParams
193
read GetIntervals write SetIntervals;
194
property TickColor: TColor read FTickColor write SetTickColor default clBlack;
195
property TickLength: Integer read FTickLength write SetTickLength;
196
property Visible: Boolean read FVisible write SetVisible default true;
201
TAxisDrawHelper = class
203
FPrevLabelPoly: TPointArray;
205
procedure BarZ(AX1, AY1, AX2, AY2: Integer); inline;
206
procedure DrawLabel(ALabelCenter: TPoint; const AText: String); inline;
207
procedure DrawLabelAndTick(
208
ACoord, AFixedCoord: Integer; const AText: String); virtual; abstract;
209
procedure GridLine(ACoord: Integer); virtual; abstract;
210
procedure InternalAxisLine(
211
APen: TChartPen; const AStart, AEnd: TPoint; AAngle: Double);
212
function IsInClipRange(ACoord: Integer): Boolean;
213
procedure LineZ(AP1, AP2: TPoint); inline;
214
function TryApplyStripes: Boolean; inline;
216
FAxis: TChartBasicAxis;
217
FAxisTransf: TTransformFunc;
218
FClipRangeDelta: Integer;
220
FDrawer: IChartDrawer;
222
FScaledTickLength: Integer;
223
FStripeIndex: Cardinal;
224
FTransf: ICoordTransformer;
229
procedure BeginDrawing; virtual;
230
function Clone: TAxisDrawHelper;
231
constructor Create; virtual;
232
procedure DrawAxisLine(
233
APen: TChartPen; AFixedCoord: Integer); virtual; abstract;
235
AFixedCoord: Integer; AMark: Double; const AText: String);
236
procedure EndDrawing; virtual; abstract;
237
procedure GetClipRange(out AMin, AMax: Integer); virtual; abstract;
238
function GraphToImage(AGraph: Double): Integer; virtual; abstract;
241
TAxisDrawHelperClass = class of TAxisDrawHelper;
245
TAxisDrawHelperX = class(TAxisDrawHelper)
247
procedure DrawLabelAndTick(
248
ACoord, AFixedCoord: Integer; const AText: String); override;
249
procedure GridLine(ACoord: Integer); override;
251
procedure BeginDrawing; override;
252
procedure DrawAxisLine(APen: TChartPen; AFixedCoord: Integer); override;
253
procedure EndDrawing; override;
254
procedure GetClipRange(out AMin, AMax: Integer); override;
255
function GraphToImage(AGraph: Double): Integer; override;
260
TAxisDrawHelperY = class(TAxisDrawHelper)
262
procedure DrawLabelAndTick(
263
ACoord, AFixedCoord: Integer; const AText: String); override;
264
procedure GridLine(ACoord: Integer); override;
266
procedure BeginDrawing; override;
267
procedure DrawAxisLine(APen: TChartPen; AFixedCoord: Integer); override;
268
procedure EndDrawing; override;
269
procedure GetClipRange(out AMin, AMax: Integer); override;
270
function GraphToImage(AGraph: Double): Integer; override;
279
{ TChartMinorAxisMarks }
281
constructor TChartMinorAxisMarks.Create(AOwner: TCustomChart);
283
inherited Create(AOwner);
285
FFormat := SERIES_MARK_FORMATS[FStyle];
290
procedure TAxisDrawHelper.BarZ(AX1, AY1, AX2, AY2: Integer);
293
FDrawer.FillRect(AX1 + X, AY1 + Y, AX2 + X, AY2 + Y);
296
procedure TAxisDrawHelper.BeginDrawing;
298
FScaledTickLength := FDrawer.Scale(FAxis.TickLength);
301
function TAxisDrawHelper.Clone: TAxisDrawHelper;
303
Result := TAxisDrawHelperClass(ClassType).Create;
304
Result.FAxis := FAxis;
305
Result.FAxisTransf := FAxisTransf;
306
Result.FClipRect := FClipRect;
307
Result.FDrawer := FDrawer;
308
Result.FTransf := FTransf;
309
Result.FValueMax := FValueMax;
310
Result.FValueMin := FValueMin;
311
Result.FZOffset := FZOffset;
314
constructor TAxisDrawHelper.Create;
316
inherited; // Empty -- just to enforce a virtual constructor.
319
procedure TAxisDrawHelper.DrawLabel(ALabelCenter: TPoint; const AText: String);
321
ALabelCenter += FZOffset;
322
FAxis.Marks.DrawLabel(
323
FDrawer, ALabelCenter, ALabelCenter, AText, FPrevLabelPoly);
326
procedure TAxisDrawHelper.DrawMark(
327
AFixedCoord: Integer; AMark: Double; const AText: String);
331
coord := GraphToImage(AMark);
333
not IsInClipRange(coord) or not InRangeUlps(AMark, FValueMin, FValueMax, 2)
336
if FAxis.Grid.Visible then begin
337
FDrawer.Pen := FAxis.Grid;
338
FDrawer.SetBrushParams(bsClear, clTAColor);
343
if FAxis.Marks.Visible then begin
344
FDrawer.PrepareSimplePen(FAxis.TickColor);
345
DrawLabelAndTick(coord, AFixedCoord, AText);
349
procedure TAxisDrawHelper.InternalAxisLine(
350
APen: TChartPen; const AStart, AEnd: TPoint; AAngle: Double);
352
if not APen.Visible and not FAxis.Arrow.Visible then exit;
356
if FAxis.Arrow.Visible then
357
FAxis.Arrow.Draw(FDrawer, AEnd + FZOffset, AAngle, APen);
360
function TAxisDrawHelper.IsInClipRange(ACoord: Integer): Boolean;
364
GetClipRange(rmin, rmax);
365
Result := InRange(ACoord, rmin + FClipRangeDelta, rmax - FClipRangeDelta);
368
procedure TAxisDrawHelper.LineZ(AP1, AP2: TPoint);
370
FDrawer.Line(AP1 + FZOffset, AP2 + FZOffset);
373
function TAxisDrawHelper.TryApplyStripes: Boolean;
375
Result := FAxis.TryApplyStripes(FDrawer, FStripeIndex);
380
procedure TAxisDrawHelperX.BeginDrawing;
383
FPrevCoord := FClipRect^.Left;
386
procedure TAxisDrawHelperX.DrawAxisLine(APen: TChartPen; AFixedCoord: Integer);
390
p := Point(FClipRect^.Right, AFixedCoord);
391
if FAxis.Arrow.Visible then
392
p.X += FDrawer.Scale(FAxis.Arrow.Length);
393
InternalAxisLine(APen, Point(FClipRect^.Left, AFixedCoord), p, 0);
396
procedure TAxisDrawHelperX.DrawLabelAndTick(
397
ACoord, AFixedCoord: Integer; const AText: String);
401
d := FScaledTickLength + FAxis.Marks.CenterOffset(FDrawer, AText).cy;
402
if FAxis.Alignment = calTop then
405
Point(ACoord, AFixedCoord - FScaledTickLength),
406
Point(ACoord, AFixedCoord + FScaledTickLength));
407
DrawLabel(Point(ACoord, AFixedCoord + d), AText);
410
procedure TAxisDrawHelperX.EndDrawing;
412
if FAxis.Grid.Visible and TryApplyStripes then
413
BarZ(FPrevCoord + 1, FClipRect^.Top + 1, FClipRect^.Right, FClipRect^.Bottom);
416
procedure TAxisDrawHelperX.GetClipRange(out AMin, AMax: Integer);
418
AMin := FClipRect^.Left;
419
AMax := FClipRect^.Right;
422
function TAxisDrawHelperX.GraphToImage(AGraph: Double): Integer;
424
Result := FTransf.XGraphToImage(AGraph);
427
procedure TAxisDrawHelperX.GridLine(ACoord: Integer);
429
if TryApplyStripes then
430
BarZ(FPrevCoord + 1, FClipRect^.Top + 1, ACoord, FClipRect^.Bottom);
431
LineZ(Point(ACoord, FClipRect^.Top), Point(ACoord, FClipRect^.Bottom));
436
procedure TAxisDrawHelperY.BeginDrawing;
439
FPrevCoord := FClipRect^.Bottom;
442
procedure TAxisDrawHelperY.DrawAxisLine(APen: TChartPen; AFixedCoord: Integer);
446
p := Point(AFixedCoord, FClipRect^.Top);
447
if FAxis.Arrow.Visible then
448
p.Y -= FDrawer.Scale(FAxis.Arrow.Length);
449
InternalAxisLine(APen, Point(AFixedCoord, FClipRect^.Bottom), p, -Pi / 2);
452
procedure TAxisDrawHelperY.DrawLabelAndTick(
453
ACoord, AFixedCoord: Integer; const AText: String);
457
d := FScaledTickLength + FAxis.Marks.CenterOffset(FDrawer, AText).cx;
458
if FAxis.Alignment = calLeft then
461
Point(AFixedCoord - FScaledTickLength, ACoord),
462
Point(AFixedCoord + FScaledTickLength, ACoord));
463
DrawLabel(Point(AFixedCoord + d, ACoord), AText);
466
procedure TAxisDrawHelperY.EndDrawing;
468
if FAxis.Grid.Visible and TryApplyStripes then
469
BarZ(FClipRect^.Left + 1, FClipRect^.Top + 1, FClipRect^.Right, FPrevCoord);
472
procedure TAxisDrawHelperY.GetClipRange(out AMin, AMax: Integer);
474
AMin := FClipRect^.Top;
475
AMax := FClipRect^.Bottom;
478
function TAxisDrawHelperY.GraphToImage(AGraph: Double): Integer;
480
Result := FTransf.YGraphToImage(AGraph);
483
procedure TAxisDrawHelperY.GridLine(ACoord: Integer);
485
if TryApplyStripes then
486
BarZ(FClipRect^.Left + 1, FPrevCoord, FClipRect^.Right, ACoord);
487
LineZ(Point(FClipRect^.Left, ACoord), Point(FClipRect^.Right, ACoord));
492
procedure TChartAxisTitle.Assign(Source: TPersistent);
494
if Source is TChartAxisTitle then
495
with TChartAxisTitle(Source) do begin
496
Self.FLabelBrush.Assign(FLabelBrush);
497
Self.FLabelFont.Assign(FLabelFont);
498
Self.FLinkPen.Assign(FLinkPen);
499
Self.FCaption := FCaption;
501
inherited Assign(Source);
504
constructor TChartAxisTitle.Create(AOwner: TCustomChart);
506
inherited Create(AOwner);
507
FDistance := DEF_TITLE_DISTANCE;
508
FLabelBrush.Style := bsClear;
512
function TChartAxisTitle.GetFont: TFont;
517
procedure TChartAxisTitle.SetCaption(AValue: String);
519
if FCaption = AValue then exit;
524
procedure TChartAxisTitle.SetFont(AValue: TFont);
529
procedure TChartAxisTitle.SetPositionOnMarks(AValue: Boolean);
531
if FPositionOnMarks = AValue then exit;
532
FPositionOnMarks := AValue;
536
{ TCustomChartAxisMarks }
538
constructor TCustomChartAxisMarks.Create(AOwner: TCustomChart);
540
inherited Create(AOwner);
541
FDefaultListener := TListener.Create(nil, @StyleChanged);
542
FDefaultSource := TIntervalChartSource.Create(AOwner);
543
FDefaultSource.Broadcaster.Subscribe(FDefaultListener);
545
FLabelBrush.Style := bsClear;
548
destructor TCustomChartAxisMarks.Destroy;
550
FreeAndNil(FDefaultListener);
551
FreeAndNil(FDefaultSource);
555
function TCustomChartAxisMarks.IsFormatStored: Boolean;
557
Result := FStyle <> smsValue;
560
function TCustomChartAxisMarks.Measure(ADrawer: IChartDrawer;
561
AIsVertical: Boolean; ATickLength: Integer;
562
AValues: TChartValueTextArray): Integer;
567
if not Visible then exit;
569
// Workaround for issue #19780, fix after upgrade to FPC 2.6.
570
with MeasureLabel(ADrawer, t.FText) do
571
Result := Max(IfThen(AIsVertical, cy, cx), Result);
572
if Result = 0 then exit;
573
if DistanceToCenter then
574
Result := Result div 2;
575
Result += ADrawer.Scale(ATickLength) + ADrawer.Scale(Distance);
578
procedure TCustomChartAxisMarks.SetStripes(AValue: TChartStyles);
580
if FStripes = AValue then exit;
587
constructor TChartAxisMarks.Create(AOwner: TCustomChart);
589
inherited Create(AOwner);
590
FListener := TListener.Create(@FSource, @StyleChanged);
591
FRange := TChartRange.Create(AOwner);
593
FFormat := SERIES_MARK_FORMATS[FStyle];
596
destructor TChartAxisMarks.Destroy;
599
FreeAndNil(FListener);
603
procedure TChartAxisMarks.SetAtDataOnly(AValue: Boolean);
605
if FAtDataOnly = AValue then exit;
606
FAtDataOnly := AValue;
610
procedure TChartAxisMarks.SetRange(AValue: TChartRange);
612
if FRange = AValue then exit;
613
FRange.Assign(AValue);
617
procedure TChartAxisMarks.SetSource(AValue: TCustomChartSource);
619
if FSource = AValue then exit;
620
if FListener.IsListening then
621
FSource.Broadcaster.Unsubscribe(FListener);
623
if FSource <> nil then
624
FSource.Broadcaster.Subscribe(FListener);
628
function TChartAxisMarks.SourceDef: TCustomChartSource;
632
Result := DefaultSource;
637
procedure TChartBasicAxis.Assign(ASource: TPersistent);
639
if ASource is TChartBasicAxis then
640
with TChartBasicAxis(ASource) do begin
641
Self.FGrid.Assign(Grid);
642
Self.FMarks.Assign(Marks);
643
Self.FTickColor := TickColor;
644
Self.FTickLength := TickLength;
645
Self.FVisible := Visible;
648
inherited Assign(ASource);
651
constructor TChartBasicAxis.Create(
652
ACollection: TCollection; AChart: TCustomChart);
654
inherited Create(ACollection);
655
FArrow := TChartArrow.Create(AChart);
656
FGrid := TChartAxisGridPen.Create;
657
FGrid.OnChange := @StyleChanged;
658
// FMarks must be created in descendants.
659
FTickColor := clBlack;
663
destructor TChartBasicAxis.Destroy;
671
function TChartBasicAxis.GetIntervals: TChartAxisIntervalParams;
673
Result := Marks.DefaultSource.Params;
676
procedure TChartBasicAxis.SetArrow(AValue: TChartArrow);
678
FArrow.Assign(AValue);
682
procedure TChartBasicAxis.SetGrid(AValue: TChartAxisGridPen);
684
FGrid.Assign(AValue);
688
procedure TChartBasicAxis.SetIntervals(AValue: TChartAxisIntervalParams);
690
Marks.DefaultSource.Params := AValue;
693
procedure TChartBasicAxis.SetMarks(AValue: TCustomChartAxisMarks);
695
FMarks.Assign(AValue);
699
procedure TChartBasicAxis.SetTickColor(AValue: TColor);
701
if FTickColor = AValue then exit;
702
FTickColor := AValue;
706
procedure TChartBasicAxis.SetTickLength(AValue: Integer);
708
if FTickLength = AValue then exit;
709
FTickLength := AValue;
713
procedure TChartBasicAxis.SetVisible(AValue: Boolean);
715
if FVisible = AValue then exit;
720
function TChartBasicAxis.TryApplyStripes(
721
ADrawer: IChartDrawer; var AIndex: Cardinal): Boolean;
723
Result := Marks.Stripes <> nil;
724
if not Result then exit;
725
Marks.Stripes.Apply(ADrawer, AIndex);