1 {
2  *****************************************************************************
3   See the file COPYING.modifiedLGPL.txt, included in this distribution,
4   for details about the license.
5  *****************************************************************************
6 
7   Basic types for TAChart series.
8 
9   Authors: Alexander Klenin
10 
11 }
12 unit TACustomSeries;
13 
14 {$H+}
15 {$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
16 interface
17 
18 uses
19   Classes, GraphType, Graphics, IntfGraphics, SysUtils,
20   TAChartAxis, TAChartUtils, TACustomSource, TADrawUtils, TAGraph, TALegend,
21   TASources, TAStyles, TATextElements, TATypes;
22 
23 const
24   DEF_AXIS_INDEX = -1;
25   DEF_ERR_ENDLENGTH = 5;
26 
27 type
28   TNearestPointParams = record
29     FDistFunc: TPointDistFunc;
30     FOptimizeX: Boolean;
31     FPoint: TPoint;
32     FRadius: Integer;
33     FTargets: TNearestPointTargets;
34   end;
35 
36   TNearestPointResults = record
37     FDist: Integer;
38     FImg: TPoint;
39     FIndex: Integer;        // Point index
40     FXIndex: Integer;       // Index to be used in Source.GetX()
41     FYIndex: Integer;       // Index to be used in Source.GetY()
42     FValue: TDoublePoint;
43   end;
44 
45   TChartAxisIndex = -1..MaxInt;
46 
47   { TCustomChartSeries }
48 
49   TCustomChartSeries = class(TBasicChartSeries)
50   strict private
51     FAxisIndexX: TChartAxisIndex;
52     FAxisIndexY: TChartAxisIndex;
53     FDepthBrightnessDelta: Integer;
54     FLegend: TChartSeriesLegend;
55     FToolTargets: TNearestPointTargets;
56     FTitle: String;
57     procedure SetAxisIndexX(AValue: TChartAxisIndex);
58     procedure SetAxisIndexY(AValue: TChartAxisIndex);
59     procedure SetDepthBrightnessDelta(AValue: Integer);
60     procedure SetLegend(AValue: TChartSeriesLegend);
61 
62   protected
63     procedure AfterAdd; override;
64     procedure GetLegendItems(AItems: TChartLegendItems); virtual; abstract;
65     procedure GetLegendItemsBasic(AItems: TChartLegendItems); override;
GetShowInLegendnull66     function GetShowInLegend: Boolean; override;
67     procedure SetActive(AValue: Boolean); override;
68     procedure SetDepth(AValue: TChartDistance); override;
69     procedure SetShadow(AValue: TChartShadow); override;
70     procedure SetShowInLegend(AValue: Boolean); override;
71     procedure SetTitle(AValue: String); virtual;
72     procedure SetTransparency(AValue: TChartTransparency); override;
73     procedure SetZPosition(AValue: TChartDistance); override;
74     procedure StyleChanged(Sender: TObject);
75     procedure UpdateParentChart;
76 
77   protected
78     procedure ReadState(Reader: TReader); override;
79     procedure SetParentComponent(AParent: TComponent); override;
80 
81   strict protected
82     // Set series bounds in axis coordinates.
83     // Some or all bounds may be left unset, in which case they will be ignored.
84     procedure GetBounds(var ABounds: TDoubleRect); virtual; abstract;
GetIndexnull85     function GetIndex: Integer; override;
LegendTextSinglenull86     function LegendTextSingle: String;
LegendTextStylenull87     function LegendTextStyle(AStyle: TChartStyle): String;
88     procedure SetIndex(AValue: Integer); override;
TitleIsStorednull89     function TitleIsStored: Boolean; virtual;
90     property DepthBrightnessDelta: Integer
91       read FDepthBrightnessDelta write SetDepthBrightnessDelta default 0;
92     property ToolTargets: TNearestPointTargets
93       read FToolTargets write FToolTargets default [nptPoint];
94 
95   public
AxisToGraphnull96     function AxisToGraph(const APoint: TDoublePoint): TDoublePoint; inline;
AxisToGraphXnull97     function AxisToGraphX(AX: Double): Double; override;
AxisToGraphYnull98     function AxisToGraphY(AY: Double): Double; override;
GetAxisXnull99     function GetAxisX: TChartAxis;
GetAxisYnull100     function GetAxisY: TChartAxis;
GetAxisBoundsnull101     function GetAxisBounds(AAxis: TChartAxis; out AMin, AMax: Double): Boolean; override;
GetDepthColornull102     function GetDepthColor(AColor: Integer; Opposite: boolean = false): Integer; virtual;
GetGraphBoundsnull103     function GetGraphBounds: TDoubleRect; override;
GraphToAxisnull104     function GraphToAxis(APoint: TDoublePoint): TDoublePoint;
GraphToAxisXnull105     function GraphToAxisX(AX: Double): Double; override;
GraphToAxisYnull106     function GraphToAxisY(AY: Double): Double; override;
IsRotatednull107     function IsRotated: Boolean;
108 
109   public
110     procedure Assign(ASource: TPersistent); override;
111     constructor Create(AOwner: TComponent); override;
112     destructor Destroy; override;
GetNearestPointnull113     function GetNearestPoint(
114       const AParams: TNearestPointParams;
115       out AResults: TNearestPointResults): Boolean; virtual;
GetParentComponentnull116     function GetParentComponent: TComponent; override;
117     procedure GetSingleLegendItem(AItems: TChartLegendItems);
HasParentnull118     function HasParent: Boolean; override;
119 
120     property AxisIndexX: TChartAxisIndex
121       read FAxisIndexX write SetAxisIndexX default DEF_AXIS_INDEX;
122     property AxisIndexY: TChartAxisIndex
123       read FAxisIndexY write SetAxisIndexY default DEF_AXIS_INDEX;
124     property Title: String read FTitle write SetTitle stored TitleIsStored;
125 
126   published
127     property Legend: TChartSeriesLegend read FLegend write SetLegend;
128     property Shadow;
129     property ShowInLegend: Boolean
130       read GetShowInLegend write SetShowInLegend stored false default true;
131       deprecated;
132     property Transparency;
133   end;
134 
135   TChartGetMarkEvent = procedure (
136     out AFormattedMark: String; AIndex: Integer) of object;
137 
138   { TChartSeries }
139 
140   TChartSeries = class(TCustomChartSeries)
141   strict private
142     FBuiltinSource: TCustomChartSource;
143     FListener: TListener;
144     FMarks: TChartMarks;
145     FOnGetMark: TChartGetMarkEvent;
146     FSource: TCustomChartSource;
147     FStyles: TChartStyles;
148     FStylesListener: TListener;
149 
GetSourcenull150     function GetSource: TCustomChartSource;
IsSourceStorednull151     function IsSourceStored: boolean;
152     procedure SetMarks(AValue: TChartMarks);
153     procedure SetOnGetMark(AValue: TChartGetMarkEvent);
154     procedure SetSource(AValue: TCustomChartSource);
155     procedure SetStyles(AValue: TChartStyles);
156   protected
157     procedure AfterAdd; override;
158     procedure AfterDraw; override;
159     procedure BeforeDraw; override;
160     procedure CheckSource(ASource: TCustomChartSource);
161     procedure GetBounds(var ABounds: TDoubleRect); override;
GetGraphPointnull162     function GetGraphPoint(AIndex: Integer): TDoublePoint; overload;
GetGraphPointnull163     function GetGraphPoint(AIndex, AXIndex, AYIndex: Integer): TDoublePoint; overload;
GetGraphPointXnull164     function GetGraphPointX(AIndex: Integer): Double; overload; inline;
GetGraphPointXnull165     function GetGraphPointX(AIndex, AXIndex: Integer): Double; overload; inline;
GetGraphPointYnull166     function GetGraphPointY(AIndex: Integer): Double; overload; inline;
GetGraphPointYnull167     function GetGraphPointY(AIndex, AYIndex: Integer): Double; overload; inline;
GetSeriesColornull168     function GetSeriesColor: TColor; virtual;
GetXMaxValnull169     function GetXMaxVal: Double;
170     procedure SourceChanged(ASender: TObject); virtual;
171     procedure VisitSources(
172       AVisitor: TChartOnSourceVisitor; AAxis: TChartAxis; var AData); override;
173     class procedure GetXYCountNeeded(out AXCount, AYCount: Cardinal); virtual;
174   strict protected
LegendTextPointnull175     function LegendTextPoint(AIndex: Integer): String; inline;
176   protected
177     property Styles: TChartStyles read FStyles write SetStyles;
178   public
179     procedure Assign(ASource: TPersistent); override;
180     constructor Create(AOwner: TComponent); override;
181     destructor Destroy; override;
182 
183   public
GetColornull184     function  GetColor(AIndex: Integer): TColor;
185     procedure GetMax(out X, Y: Double);
186     procedure GetMin(out X, Y: Double);
GetXImgValuenull187     function  GetXImgValue(AIndex: Integer): Integer;
GetXMaxnull188     function  GetXMax: Double;
GetXMinnull189     function  GetXMin: Double;
GetXValuenull190     function  GetXValue(AIndex: Integer): Double;
GetXValuesnull191     function  GetXValues(AIndex, AXIndex: Integer): Double;
GetYImgValuenull192     function  GetYImgValue(AIndex: Integer): Integer;
GetYMaxnull193     function  GetYMax: Double;
GetYMinnull194     function  GetYMin: Double;
GetYValuenull195     function  GetYValue(AIndex: Integer): Double;
GetYValuesnull196     function  GetYValues(AIndex, AYIndex: Integer): Double;
197     procedure SetColor(AIndex: Integer; AColor: TColor); inline;
198     procedure SetText(AIndex: Integer; AValue: String); inline;
199     procedure SetXValue(AIndex: Integer; AValue: Double); inline;
200     procedure SetXValues(AIndex, AXIndex: Integer; AValue: Double);
201     procedure SetYValue(AIndex: Integer; AValue: Double); inline;
202     procedure SetYValues(AIndex, AYIndex: Integer; AValue: Double);
203   public
Addnull204     function Add(
205       AValue: Double;
206       AXLabel: String = ''; AColor: TColor = clTAColor): Integer; inline;
AddArraynull207     function AddArray(const AValues: array of Double): Integer;
AddNullnull208     function AddNull(ALabel: String = ''; AColor: TColor = clTAColor): Integer; inline;
AddXnull209     function AddX(
210       AX: Double; ALabel: String = ''; AColor: TColor = clTAColor): Integer; inline;
AddXYnull211     function AddXY(
212       AX, AY: Double;
213       AXLabel: String = ''; AColor: TColor = clTAColor): Integer; overload; inline;
AddXYnull214     function AddXY(
215       AX, AY: Double; const AYList: array of Double;
216       AXLabel: String = ''; AColor: TColor = clTAColor): Integer; overload;
AddYnull217     function AddY(
218       AY: Double; ALabel: String = ''; AColor: TColor = clTAColor): Integer; inline;
219     procedure BeginUpdate;
220     procedure Clear; virtual;
Countnull221     function Count: Integer; inline;
222     procedure Delete(AIndex: Integer); virtual;
223     procedure EndUpdate;
Extentnull224     function Extent: TDoubleRect; virtual;
225     procedure FindYRange(AXMin, AXMax: Double; var AYMin, AYMax: Double); virtual;
FormattedMarknull226     function FormattedMark(
227       AIndex: Integer; AFormat: String = ''; AYIndex: Integer = 0): String;
IsEmptynull228     function IsEmpty: Boolean; override;
ListSourcenull229     function ListSource: TListChartSource;
230     property Marks: TChartMarks
231       read FMarks write SetMarks;
232     property Source: TCustomChartSource
233       read GetSource write SetSource stored IsSourceStored;
234   public
235     // for Delphi compatibility
LastValueIndexnull236     function LastValueIndex: Integer; inline;
MaxXValuenull237     function MaxXValue: Double;
MinXValuenull238     function MinXValue: Double;
MaxYValuenull239     function MaxYValue: Double;
MinYValuenull240     function MinYValue: Double;
241     property XValue[AIndex: Integer]: Double read GetXValue write SetXValue;
242     property XValues[AIndex, AXIndex: Integer]: Double read GetXValues write SetXValues;
243     property YValue[AIndex: Integer]: Double read GetYValue write SetYValue;
244     property YValues[AIndex, AYIndex: Integer]: Double read GetYValues write SetYValues;
245   published
246     property Active default true;
247     property ShowInLegend;
248     property Title;
249     property ZPosition;
250   published
251     property OnGetMark: TChartGetMarkEvent read FOnGetMark write SetOnGetMark;
252   end;
253 
254   TLabelDirection = (ldLeft, ldTop, ldRight, ldBottom);
255 
256   TLinearMarkPositions = (lmpOutside, lmpPositive, lmpNegative, lmpInside);
257 
258   TSeriesPointerCustomDrawEvent = procedure (
259     ASender: TChartSeries; ADrawer: IChartDrawer; AIndex: Integer;
260     ACenter: TPoint) of object;
261 
262   TSeriesPointerStyleEvent = procedure (ASender: TChartSeries;
263     AValueIndex: Integer; var AStyle: TSeriesPointerStyle) of object;
264 
265   TStackedNaN = (snReplaceByZero, snDoNotDraw);
266 
267   { TBasicPointSeries }
268 
269   TBasicPointSeries = class(TChartSeries)
270   strict private
271     FMarkPositions: TLinearMarkPositions;
272     FErrorBars: array[0..1] of TChartErrorBar;
273     FOnCustomDrawPointer: TSeriesPointerCustomDrawEvent;
274     FOnGetPointerStyle: TSeriesPointerStyleEvent;
GetErrorBarsnull275     function GetErrorBars(AIndex: Integer): TChartErrorBar;
IsErrorBarsStorednull276     function IsErrorBarsStored(AIndex: Integer): Boolean;
277     procedure SetErrorBars(AIndex: Integer; AValue: TChartErrorBar);
278     procedure SetMarkPositionCentered(AValue: Boolean);
279     procedure SetMarkPositions(AValue: TLinearMarkPositions);
280     procedure SetPointer(AValue: TSeriesPointer);
281     procedure SetStacked(AValue: Boolean);
282     procedure SetStackedNaN(AValue: TStackedNaN);
283   //strict
284   protected
285     FGraphPoints: array of TDoublePoint;
286     FLoBound: Integer;
287     FMinXRange: Double;
288     FPointer: TSeriesPointer;
289     FStacked: Boolean;
290     FStackedNaN: TStackedNaN;
291     FUpBound: Integer;
292     FOptimizeX: Boolean;
293     FSupportsZeroLevel: Boolean;
294     FMarkPositionCentered: Boolean;
295 
296     procedure AfterDrawPointer(
297       ADrawer: IChartDrawer; AIndex: Integer; const APos: TPoint); virtual;
298     procedure DrawErrorBars(ADrawer: IChartDrawer);
299     procedure DrawLabels(ADrawer: IChartDrawer; AYIndex: Integer = -1);
300     procedure DrawPointers(ADrawer: IChartDrawer; AStyleIndex: Integer = 0;
301       UseDataColors: Boolean = false);
302     procedure FindExtentInterval(
303       const AExtent: TDoubleRect; AFilterByExtent: Boolean);
GetLabelDataPointnull304     function GetLabelDataPoint(AIndex, AYIndex: Integer): TDoublePoint; virtual;
GetLabelDirectionnull305     function GetLabelDirection(AValue: Double;
306       const ACenterLevel: Double): TLabelDirection;
307     procedure GetLegendItemsRect(AItems: TChartLegendItems; ABrush: TBrush; APen: TPen);
GetXRangenull308     function GetXRange(AX: Double; AIndex: Integer): Double;
GetZeroLevelnull309     function GetZeroLevel: Double; virtual;
HasMissingYValuenull310     function HasMissingYValue(AIndex: Integer; AMaxYIndex: Integer = MaxInt): Boolean;
NearestXNumbernull311     function NearestXNumber(var AIndex: Integer; ADir: Integer): Double;
312     procedure PrepareGraphPoints(
313       const AExtent: TDoubleRect; AFilterByExtent: Boolean);
SkipMissingValuesnull314     function SkipMissingValues(AIndex: Integer): Boolean; virtual;
ToolTargetDistancenull315     function ToolTargetDistance(const AParams: TNearestPointParams;
316       AGraphPt: TDoublePoint; APointIdx, AXIdx, AYIdx: Integer): Integer; virtual;
317     procedure UpdateGraphPoints(AIndex: Integer; ACumulative: Boolean); overload; inline;
318     procedure UpdateGraphPoints(AIndex, ALo, AUp: Integer; ACumulative: Boolean); overload;
319     procedure UpdateLabelDirectionReferenceLevel(AIndex, AYIndex: Integer;
320       var ALevel: Double); virtual;
321     procedure UpdateMinXRange;
322 
323     property Pointer: TSeriesPointer read FPointer write SetPointer;
324     property Stacked: Boolean read FStacked write SetStacked;
325     property StackedNaN: TStackedNaN read FStackedNaN write SetStackedNaN default snReplaceByZero;
326 
327   protected
328     procedure AfterAdd; override;
329     procedure SourceChanged(ASender: TObject); override;
330     procedure UpdateMargins(ADrawer: IChartDrawer; var AMargins: TRect); override;
331 
332     property MarkPositionCentered: Boolean
333       read FMarkPositionCentered write SetMarkPositionCentered default false;
334     property MarkPositions: TLinearMarkPositions
335       read FMarkPositions write SetMarkPositions default lmpOutside;
336     property XErrorBars: TChartErrorBar index 0 read GetErrorBars
337       write SetErrorBars stored IsErrorBarsStored;
338     property YErrorBars: TChartErrorBar index 1 read GetErrorBars
339       write SetErrorBars stored IsErrorBarsStored;
340     property OnCustomDrawPointer: TSeriesPointerCustomDrawEvent
341       read FOnCustomDrawPointer write FOnCustomDrawPointer;
342     property OnGetPointerStyle: TSeriesPointerStyleEvent
343       read FOnGetPointerStyle write FOnGetPointerStyle;
344 
345   public
346     constructor Create(AOwner: TComponent); override;
347     destructor Destroy; override;
348   public
349     procedure Assign(ASource: TPersistent); override;
Extentnull350     function Extent: TDoubleRect; override;
351     procedure FindYRange(AXMin, AXMax: Double; var AYMin, AYMax: Double); override;
GetNearestPointnull352     function GetNearestPoint(
353       const AParams: TNearestPointParams;
354       out AResults: TNearestPointResults): Boolean; override;
355     procedure MovePoint(var AIndex: Integer; const ANewPos: TDoublePoint); override;
356     procedure MovePointEx(var AIndex: Integer; AXIndex, AYIndex: Integer;
357       const ANewPos: TDoublePoint); override;
358     property ToolTargets default [nptPoint, nptYList];
359     property ExtentPointIndexFirst: Integer read FLoBound;
360     property ExtentPointIndexLast: Integer read FUpBound;
361   end;
362 
CreateLazIntfImagenull363   function CreateLazIntfImage(
364     out ARawImage: TRawImage; const ASize: TPoint): TLazIntfImage;
365 
366 implementation
367 
368 uses
369   Math, PropEdits, StrUtils, LResources, Types, GraphUtil,
370   TAChartStrConsts, TAGeometry, TAMath;
371 
CreateLazIntfImagenull372 function CreateLazIntfImage(
373   out ARawImage: TRawImage; const ASize: TPoint): TLazIntfImage;
374 begin
375   ARawImage.Init;
376   ARawImage.Description.Init_BPP32_B8G8R8A8_BIO_TTB(ASize.X, ASize.Y);
377   ARawImage.CreateData(true);
378   Result := TLazIntfImage.Create(0, 0);
379   Result.SetRawImage(ARawImage);
380 end;
381 
382 { TCustomChartSeries }
383 
384 procedure TCustomChartSeries.AfterAdd;
385 begin
386   Legend.SetOwner(FChart);
387   Shadow.SetOwner(FChart);
388 end;
389 
390 procedure TCustomChartSeries.Assign(ASource: TPersistent);
391 begin
392   if ASource is TCustomChartSeries then
393     with TCustomChartSeries(ASource) do begin
394       Self.FAxisIndexX := FAxisIndexX;
395       Self.FAxisIndexY := FAxisIndexY;
396       Self.FDepthBrightnessDelta := FDepthBrightnessDelta;
397       Self.Legend := FLegend;
398       Self.FTitle := FTitle;
399       Self.FToolTargets := FToolTargets;
400     end;
401   inherited Assign(ASource);
402 end;
403 
AxisToGraphnull404 function TCustomChartSeries.AxisToGraph(
405   const APoint: TDoublePoint): TDoublePoint;
406 begin
407   Result := DoublePoint(AxisToGraphX(APoint.X), AxisToGraphY(APoint.Y));
408   if IsRotated then
409     Exchange(Result.X, Result.Y);
410 end;
411 
TCustomChartSeries.AxisToGraphXnull412 function TCustomChartSeries.AxisToGraphX(AX: Double): Double;
413 begin
414   Result := TransformByAxis(FChart.AxisList, AxisIndexX).AxisToGraph(AX)
415 end;
416 
TCustomChartSeries.AxisToGraphYnull417 function TCustomChartSeries.AxisToGraphY(AY: Double): Double;
418 begin
419   Result := TransformByAxis(FChart.AxisList, AxisIndexY).AxisToGraph(AY)
420 end;
421 
422 constructor TCustomChartSeries.Create(AOwner: TComponent);
423 begin
424   inherited Create(AOwner);
425   FActive := true;
426   FAxisIndexX := DEF_AXIS_INDEX;
427   FAxisIndexY := DEF_AXIS_INDEX;
428   FLegend := TChartSeriesLegend.Create(FChart);
429   FToolTargets := [nptPoint];
430   FShadow := TChartShadow.Create(FChart);
431 end;
432 
433 destructor TCustomChartSeries.Destroy;
434 begin
435   FreeAndNil(FLegend);
436   FreeAndNil(FShadow);
437   inherited;
438 end;
439 
GetAxisBoundsnull440 function TCustomChartSeries.GetAxisBounds(AAxis: TChartAxis;
441   out AMin, AMax: Double): Boolean;
442 var
443   ex: TDoubleRect;
444   axIndexX, axIndexY: Integer;
445 begin
446   axIndexX := GetAxisX.Index;
447   axIndexY := GetAxisY.Index;
448 
449   if (AAxis.Index = axIndexX) or (AAxis.Index = axIndexY) then begin
450     ex := EmptyExtent;
451     GetBounds(ex);
452     with ex do begin
453       UpdateBoundsByAxisRange(FChart.AxisList, axIndexX, a.X, b.X);
454       UpdateBoundsByAxisRange(FChart.AxisList, axIndexY, a.Y, b.Y);
455       if IsRotated then begin
456         Exchange(a.X, a.Y);
457         Exchange(b.X, b.Y);
458       end;
459     end;
460     AMin := TDoublePointBoolArr(ex.a)[AAxis.IsVertical];
461     AMax := TDoublePointBoolArr(ex.b)[AAxis.IsVertical];
462     Result := true;
463   end else
464     Result := false;
465 end;
466 
GetAxisXnull467 function TCustomChartSeries.GetAxisX: TChartAxis;
468 begin
469   if InRange(AxisIndexX, 0, FChart.AxisList.Count - 1) then
470     Result := FChart.AxisList[AxisIndexX]
471   else
472     Result := FChart.BottomAxis;
473 end;
474 
TCustomChartSeries.GetAxisYnull475 function TCustomChartSeries.GetAxisY: TChartAxis;
476 begin
477   if InRange(AxisIndexY, 0, FChart.AxisList.Count - 1) then
478     Result := FChart.AxisList[AxisIndexY]
479   else
480     Result := FChart.LeftAxis;
481 end;
482 
GetDepthColornull483 function TCustomChartSeries.GetDepthColor(AColor: Integer;
484   Opposite: Boolean = false): Integer;
485 var
486   h, l, s: Byte;
487 begin
488   ColorToHLS(AColor, h, l, s);
489   if Opposite then
490     Result := HLSToColor(h, EnsureRange(Integer(l) - FDepthBrightnessDelta, 0, 255), s)
491   else
492     Result := HLSToColor(h, EnsureRange(Integer(l) + FDepthBrightnessDelta, 0, 255), s);
493 end;
494 
TCustomChartSeries.GetGraphBoundsnull495 function TCustomChartSeries.GetGraphBounds: TDoubleRect;
496 begin
497   Result := EmptyExtent;
498   if Active then GetBounds(Result);
499   with Result do begin
500     UpdateBoundsByAxisRange(FChart.AxisList, AxisIndexX, a.X, b.X);
501     UpdateBoundsByAxisRange(FChart.AxisList, AxisIndexY, a.Y, b.Y);
502     TransformByAxis(FChart.AxisList, AxisIndexX).UpdateBounds(a.X, b.X);
503     TransformByAxis(FChart.AxisList, AxisIndexY).UpdateBounds(a.Y, b.Y);
504     if IsRotated then begin
505       Exchange(a.X, a.Y);
506       Exchange(b.X, b.Y);
507     end;
508   end;
509 end;
510 
TCustomChartSeries.GetIndexnull511 function TCustomChartSeries.GetIndex: Integer;
512 begin
513   Result := FChart.Series.List.IndexOf(Self);
514 end;
515 
516 procedure TCustomChartSeries.GetLegendItemsBasic(AItems: TChartLegendItems);
517 var
518   i, oldCount: Integer;
519 begin
520   oldCount := AItems.Count;
521   if Assigned(Legend.OnDraw) then
522     for i := 0 to Legend.UserItemsCount - 1 do
523       AItems.Add(TLegendItemUserDrawn.Create(i, Legend.OnDraw, LegendTextSingle))
524   else
525     GetLegendItems(AItems);
526   for i := oldCount to AItems.Count - 1 do begin
527     AItems[i].Owner := Self;
528     Legend.InitItem(AItems[i], i - oldCount, FChart.Legend);
529   end;
530 end;
531 
TCustomChartSeries.GetNearestPointnull532 function TCustomChartSeries.GetNearestPoint(
533   const AParams: TNearestPointParams;
534   out AResults: TNearestPointResults): Boolean;
535 begin
536   Unused(AParams);
537   AResults.FDist := MaxInt;
538   AResults.FImg := Point(0, 0);
539   AResults.FIndex := 0;
540   AResults.FValue := ZeroDoublePoint;
541   Result := false;
542 end;
543 
GetParentComponentnull544 function TCustomChartSeries.GetParentComponent: TComponent;
545 begin
546   Result := FChart;
547 end;
548 
GetShowInLegendnull549 function TCustomChartSeries.GetShowInLegend: Boolean;
550 begin
551   Result := Legend.Visible;
552 end;
553 
554 procedure TCustomChartSeries.GetSingleLegendItem(AItems: TChartLegendItems);
555 var
556   oldMultiplicity: TLegendMultiplicity;
557 begin
558   ParentChart.DisableRedrawing;
559   oldMultiplicity := Legend.Multiplicity;
560   try
561     Legend.Multiplicity := lmSingle;
562     GetLegendItemsBasic(AItems);
563   finally
564     Legend.Multiplicity := oldMultiplicity;
565     ParentChart.EnableRedrawing;
566   end;
567 end;
568 
TCustomChartSeries.GraphToAxisnull569 function TCustomChartSeries.GraphToAxis(APoint: TDoublePoint): TDoublePoint;
570 begin
571   if IsRotated then
572     Exchange(APoint.X, APoint.Y);
573   Result := DoublePoint(GraphToAxisX(APoint.X), GraphToAxisY(APoint.Y));
574 end;
575 
GraphToAxisXnull576 function TCustomChartSeries.GraphToAxisX(AX: Double): Double;
577 begin
578   Result := TransformByAxis(FChart.AxisList, AxisIndexX).GraphToAxis(AX)
579 end;
580 
TCustomChartSeries.GraphToAxisYnull581 function TCustomChartSeries.GraphToAxisY(AY: Double): Double;
582 begin
583   Result := TransformByAxis(FChart.AxisList, AxisIndexY).GraphToAxis(AY)
584 end;
585 
HasParentnull586 function TCustomChartSeries.HasParent: Boolean;
587 begin
588   Result := true;
589 end;
590 
TCustomChartSeries.IsRotatednull591 function TCustomChartSeries.IsRotated: Boolean;
592 var
593   x_normal, y_normal: Boolean;
594   x_axis: TChartAxis = nil;
595   y_axis: TChartAxis = nil;
596 begin
597   if InRange(AxisIndexX, 0, FChart.AxisList.Count-1) then
598     x_axis := FChart.AxisList[AxisIndexX];
599   if InRange(AxisIndexY, 0, FChart.AxisList.Count-1) then
600     y_axis := FChart.AxisList[AxisIndexY];
601   x_normal := (x_axis = nil) or (not x_axis.IsVertical);
602   y_normal := (y_axis = nil) or y_axis.IsVertical;
603   Result := (not x_normal) and (not y_normal);
604 end;
605 
LegendTextSinglenull606 function TCustomChartSeries.LegendTextSingle: String;
607 begin
608   if Legend.Format = '' then
609     Result := Title
610   else
611     Result := Format(Legend.Format, [Title, Index]);
612 end;
613 
TCustomChartSeries.LegendTextStylenull614 function TCustomChartSeries.LegendTextStyle(AStyle: TChartStyle): String;
615 begin
616   if Legend.Format = '' then
617     Result := AStyle.Text
618   else
619     Result := Format(Legend.Format, [AStyle.Text, AStyle.Index]);
620 end;
621 
622 procedure TCustomChartSeries.ReadState(Reader: TReader);
623 begin
624   inherited ReadState(Reader);
625   if Reader.Parent is TChart then
626     TChart(Reader.Parent).AddSeries(Self);
627 end;
628 
629 procedure TCustomChartSeries.SetActive(AValue: Boolean);
630 begin
631   if FActive = AValue then exit;
632   FActive := AValue;
633   UpdateParentChart;
634 end;
635 
636 procedure TCustomChartSeries.SetAxisIndexX(AValue: TChartAxisIndex);
637 begin
638   if FAxisIndexX = AValue then exit;
639   FAxisIndexX := AValue;
640   UpdateParentChart;
641 end;
642 
643 procedure TCustomChartSeries.SetAxisIndexY(AValue: TChartAxisIndex);
644 begin
645   if FAxisIndexY = AValue then exit;
646   FAxisIndexY := AValue;
647   UpdateParentChart;
648 end;
649 
650 procedure TCustomChartSeries.SetDepth(AValue: TChartDistance);
651 begin
652   if FDepth = AValue then exit;
653   FDepth := AValue;
654   UpdateParentChart;
655 end;
656 
657 procedure TCustomChartSeries.SetDepthBrightnessDelta(AValue: Integer);
658 begin
659   if FDepthBrightnessDelta = AValue then exit;
660   FDepthBrightnessDelta := AValue;
661   UpdateParentChart;
662 end;
663 
664 procedure TCustomChartSeries.SetIndex(AValue: Integer);
665 begin
666   with FChart.Series.List do
667     Move(Index, EnsureRange(AValue, 0, Count - 1));
668 end;
669 
670 procedure TCustomChartSeries.SetLegend(AValue: TChartSeriesLegend);
671 begin
672   if FLegend = AValue then exit;
673   FLegend.Assign(AValue);
674   UpdateParentChart;
675 end;
676 
677 procedure TCustomChartSeries.SetParentComponent(AParent: TComponent);
678 begin
679   if not (csLoading in ComponentState) then
680     (AParent as TChart).AddSeries(Self);
681 end;
682 
683 procedure TCustomChartSeries.SetShadow(AValue: TChartShadow);
684 begin
685   if FShadow = AValue then exit;
686   FShadow.Assign(AValue);
687   UpdateParentChart;
688 end;
689 
690 procedure TCustomChartSeries.SetShowInLegend(AValue: Boolean);
691 begin
692   Legend.Visible := AValue;
693 end;
694 
695 procedure TCustomChartSeries.SetTitle(AValue: String);
696 begin
697   if FTitle = AValue then exit;
698   FTitle := AValue;
699   UpdateParentChart;
700 end;
701 
702 procedure TCustomChartSeries.SetTransparency(AValue: TChartTransparency);
703 begin
704   if FTransparency = AValue then exit;
705   FTransparency := AValue;
706   UpdateParentChart;
707 end;
708 
709 procedure TCustomChartSeries.SetZPosition(AValue: TChartDistance);
710 begin
711   if FZPosition = AValue then exit;
712   FZPosition := AValue;
713   UpdateParentChart;
714 end;
715 
716 procedure TCustomChartSeries.StyleChanged(Sender: TObject);
717 begin
718   if ParentChart <> nil then
719     ParentChart.StyleChanged(Sender);
720 end;
721 
TCustomChartSeries.TitleIsStorednull722 function TCustomChartSeries.TitleIsStored: Boolean;
723 begin
724   Result := Title <> '';
725 end;
726 
727 procedure TCustomChartSeries.UpdateParentChart;
728 begin
729   if ParentChart <> nil then
730     ParentChart.StyleChanged(Self);
731 end;
732 
733 { TChartSeries }
734 
Addnull735 function TChartSeries.Add(AValue: Double; AXLabel: String; AColor: TColor): Integer;
736 begin
737   Result := AddXY(GetXMaxVal + 1, AValue, AXLabel, AColor);
738 end;
739 
AddArraynull740 function TChartSeries.AddArray(const AValues: array of Double): Integer;
741 var
742   a: Double;
743 begin
744   Result := ListSource.Count;
745   for a in AValues do
746     Add(a);
747 end;
748 
AddNullnull749 function TChartSeries.AddNull(ALabel: String; AColor: TColor): Integer;
750 begin
751   Result := ListSource.Add(SafeNan, SafeNan, ALabel, AColor);
752 end;
753 
AddXnull754 function TChartSeries.AddX(AX: Double; ALabel: String; AColor: TColor): Integer;
755 begin
756   Result := ListSource.Add(AX, SafeNan, ALabel, AColor);
757 end;
758 
AddXYnull759 function TChartSeries.AddXY(
760   AX, AY: Double; const AYList: array of Double;
761   AXLabel: String; AColor: TColor): Integer;
762 begin
763   Result := ListSource.Add(AX, AY, AXLabel, AColor);
764   ListSource.SetYList(Result, AYList);
765 end;
766 
AddXYnull767 function TChartSeries.AddXY(
768   AX, AY: Double; AXLabel: String; AColor: TColor): Integer;
769 begin
770   Result := ListSource.Add(AX, AY, AXLabel, AColor);
771 end;
772 
AddYnull773 function TChartSeries.AddY(AY: Double; ALabel: String; AColor: TColor): Integer;
774 begin
775   Result := Add(AY, ALabel, AColor);
776 end;
777 
778 procedure TChartSeries.AfterAdd;
779 begin
780   inherited;
781   Marks.SetOwner(FChart);
782   Marks.Arrow.SetOwner(FChart);
783   Marks.Margins.SetOwner(FChart);
784 end;
785 
786 procedure TChartSeries.AfterDraw;
787 begin
788   inherited AfterDraw;
789   Source.AfterDraw;
790 end;
791 
792 procedure TChartSeries.Assign(ASource: TPersistent);
793 begin
794   if ASource is TChartSeries then
795     with TChartSeries(ASource) do begin
796       Self.Marks.Assign(FMarks);
797       Self.FOnGetMark := FOnGetMark;
798       Self.Source := FSource;
799       Self.Styles := FStyles;
800     end;
801   inherited Assign(ASource);
802 end;
803 
804 procedure TChartSeries.BeforeDraw;
805 begin
806   inherited BeforeDraw;
807   Source.BeforeDraw;
808 end;
809 
810 procedure TChartSeries.BeginUpdate;
811 begin
812   ListSource.BeginUpdate;
813 end;
814 
815 procedure TChartSeries.CheckSource(ASource: TCustomChartSource);
816 var
817   nx, ny: Cardinal;
818 begin
819   if ASource = nil then
820     exit;
821   GetXYCountNeeded(nx, ny);
822   if ASource.XCount < nx then
823     raise EXCountError.CreateFmt(rsSourceCountError, [ClassName, nx, 'x']);
824   if ASource.YCount < ny then
825     raise EYCountError.CreateFmt(rsSourceCountError, [ClassName, ny, 'y']);
826 end;
827 
828 procedure TChartSeries.Clear;
829 begin
830   ListSource.Clear;
831 end;
832 
TChartSeries.Countnull833 function TChartSeries.Count: Integer;
834 begin
835   Result := Source.Count;
836 end;
837 
LastValueIndexnull838 function TChartSeries.LastValueIndex: Integer;
839 begin
840   Result := Source.Count - 1;
841 end;
842 
843 constructor TChartSeries.Create(AOwner: TComponent);
844 const
845   BUILTIN_SOURCE_NAME = 'Builtin';
846 var
847   nx, ny: Cardinal;
848 begin
849   inherited Create(AOwner);
850 
851   FListener := TListener.Create(@FSource,  @SourceChanged);
852   GetXYCountNeeded(nx, ny);
853   FBuiltinSource := TBuiltinListChartSource.Create(Self, nx, ny);
854   FBuiltinSource.Name := BUILTIN_SOURCE_NAME;
855   FBuiltinSource.Broadcaster.Subscribe(FListener);
856   FMarks := TChartMarks.Create(FChart);
857   FStylesListener := TListener.Create(@FStyles,  @StyleChanged);
858 end;
859 
860 procedure TChartSeries.Delete(AIndex: Integer);
861 begin
862   ListSource.Delete(AIndex);
863 end;
864 
865 destructor TChartSeries.Destroy;
866 begin
867   FreeAndNil(FListener);
868   FreeAndNil(FBuiltinSource);
869   FreeAndNil(FMarks);
870   FreeAndNil(FStylesListener);
871   inherited;
872 end;
873 
874 procedure TChartSeries.EndUpdate;
875 begin
876   ListSource.EndUpdate;
877   UpdateParentChart;
878 end;
879 
Extentnull880 function TChartSeries.Extent: TDoubleRect;
881 begin
882   Result := Source.ExtentCumulative;
883 end;
884 
885 procedure TChartSeries.FindYRange(AXMin, AXMax: Double;
886   var AYMin, AYMax: Double);
887 begin
888   Source.FindYRange(AXMin, AXMax, false, AYMin, AYMax);
889 end;
890 
FormattedMarknull891 function TChartSeries.FormattedMark(
892   AIndex: Integer; AFormat: String; AYIndex: Integer): String;
893 begin
894   if Assigned(FOnGetMark) then
895     FOnGetMark(Result, AIndex)
896   else
897     Result := Source.FormatItem(
898       IfThen(AFormat = '', Marks.Format, AFormat), AIndex, AYIndex);
899 end;
900 
901 procedure TChartSeries.GetBounds(var ABounds: TDoubleRect);
902 var
903   i: Integer;
904 begin
905   if IsEmpty or (not Active) then exit;
906   with Extent do
907     for i := Low(coords) to High(coords) do
908       if not IsInfinite(coords[i]) then
909         ABounds.coords[i] := coords[i];
910 end;
911 
GetColornull912 function TChartSeries.GetColor(AIndex: Integer): TColor;
913 begin
914   Result := ColorDef(Source[AIndex]^.Color, GetSeriesColor);
915 end;
916 
TChartSeries.GetGraphPointnull917 function TChartSeries.GetGraphPoint(AIndex: Integer): TDoublePoint;
918 begin
919   Result.X := GetGraphPointX(AIndex);
920   Result.Y := GetGraphPointY(AIndex);
921   if IsRotated then
922     Exchange(Result.X, Result.Y);
923 end;
924 
TChartSeries.GetGraphPointnull925 function TChartSeries.GetGraphPoint(AIndex, AXIndex, AYIndex: Integer): TDoublePoint;
926 begin
927   Result.X := GetGraphPointX(AIndex, AXIndex);
928   Result.Y := GetGraphPointY(AIndex, AYIndex);
929   if IsRotated then
930     Exchange(Result.X, Result.Y);
931 end;
932 
GetGraphPointXnull933 function TChartSeries.GetGraphPointX(AIndex: Integer): Double;
934 begin
935   if Source.XCount = 0 then
936     Result := AxisToGraphX(AIndex)
937   else
938     Result := AxisToGraphX(Source[AIndex]^.X);
939 end;
940 
GetGraphPointXnull941 function TChartSeries.GetGraphPointX(AIndex, AXIndex: Integer): Double;
942 begin
943   if Source.XCount = 0 then
944     Result := AxisToGraphX(AIndex)
945   else
946     Result := AxisToGraphX(Source[AIndex]^.GetX(AXIndex));
947 end;
948 
TChartSeries.GetGraphPointYnull949 function TChartSeries.GetGraphPointY(AIndex: Integer): Double;
950 begin
951   Result := AxisToGraphY(Source[AIndex]^.Y);
952 end;
953 
TChartSeries.GetGraphPointYnull954 function TChartSeries.GetGraphPointY(AIndex, AYIndex: Integer): Double;
955 begin
956   Result := AxisToGraphY(Source[AIndex]^.GetY(AYIndex));
957 end;
958 
959 procedure TChartSeries.GetMax(out X, Y: Double);
960 begin
961   X := Source.XOfMax;
962   Y := Extent.b.Y;
963 end;
964 
965 procedure TChartSeries.GetMin(out X, Y: Double);
966 begin
967   X := Source.XOfMin;
968   Y := Extent.a.Y;
969 end;
970 
GetSeriesColornull971 function TChartSeries.GetSeriesColor: TColor;
972 begin
973   Result := clTAColor;
974 end;
975 
TChartSeries.GetSourcenull976 function TChartSeries.GetSource: TCustomChartSource;
977 begin
978   if Assigned(FSource) then
979     Result := FSource
980   else
981     Result := FBuiltinSource;
982 end;
983 
TChartSeries.GetXImgValuenull984 function TChartSeries.GetXImgValue(AIndex: Integer): Integer;
985 begin
986   Result := ParentChart.XGraphToImage(Source[AIndex]^.X);
987 end;
988 
TChartSeries.GetXMaxnull989 function TChartSeries.GetXMax: Double;
990 begin
991   Result := Extent.b.X;
992 end;
993 
TChartSeries.MaxXValuenull994 function TChartSeries.MaxXValue: Double;
995 begin
996   Result := Extent.b.X;
997 end;
998 
GetXMaxValnull999 function TChartSeries.GetXMaxVal: Double;
1000 begin
1001   if Count > 0 then
1002     Result := Source[Count - 1]^.X
1003   else
1004     Result := 0;
1005 end;
1006 
1007 class procedure TChartSeries.GetXYCountNeeded(out AXCount, AYCount: Cardinal);
1008 begin
1009   AXCount := 0;
1010   AYCount := 1;
1011 end;
1012 
TChartSeries.GetXMinnull1013 function TChartSeries.GetXMin: Double;
1014 begin
1015   Result := Extent.a.X;
1016 end;
1017 
TChartSeries.MinXValuenull1018 function TChartSeries.MinXValue: Double;
1019 begin
1020   Result := Extent.a.X;
1021 end;
1022 
TChartSeries.GetXValuenull1023 function TChartSeries.GetXValue(AIndex: Integer): Double;
1024 begin
1025   if Source.XCount > 0 then
1026     Result := Source[AIndex]^.X
1027   else
1028     Result := AIndex;
1029 end;
1030 
GetXValuesnull1031 function TChartSeries.GetXValues(AIndex, AXIndex: Integer): Double;
1032 begin
1033   if AXIndex > 0 then
1034     Result := Source[AIndex]^.XList[AXIndex - 1]
1035   else
1036     Result := Source[AIndex]^.X;
1037 end;
1038 
GetYImgValuenull1039 function TChartSeries.GetYImgValue(AIndex: Integer): Integer;
1040 begin
1041   Result := ParentChart.YGraphToImage(Source[AIndex]^.Y);
1042 end;
1043 
TChartSeries.GetYMaxnull1044 function TChartSeries.GetYMax: Double;
1045 begin
1046   Result := Extent.b.Y;
1047 end;
1048 
MaxYValuenull1049 function TChartSeries.MaxYValue: Double;
1050 begin
1051   Result := Extent.b.Y;
1052 end;
1053 
TChartSeries.GetYMinnull1054 function TChartSeries.GetYMin: Double;
1055 begin
1056   Result := Extent.a.Y;
1057 end;
1058 
MinYValuenull1059 function TChartSeries.MinYValue: Double;
1060 begin
1061   Result := Extent.a.Y;
1062 end;
1063 
GetYValuenull1064 function TChartSeries.GetYValue(AIndex: Integer): Double;
1065 begin
1066   Result := Source[AIndex]^.Y;
1067 end;
1068 
TChartSeries.GetYValuesnull1069 function TChartSeries.GetYValues(AIndex, AYIndex: Integer): Double;
1070 begin
1071   if AYIndex = 0 then
1072     Result := GetYValue(AIndex)
1073   else
1074     Result := Source[AIndex]^.YList[AYIndex - 1];
1075 end;
1076 
IsEmptynull1077 function TChartSeries.IsEmpty: Boolean;
1078 begin
1079   Result := Count = 0;
1080 end;
1081 
IsSourceStorednull1082 function TChartSeries.IsSourceStored: boolean;
1083 begin
1084   Result := FSource <> nil;
1085 end;
1086 
TChartSeries.LegendTextPointnull1087 function TChartSeries.LegendTextPoint(AIndex: Integer): String;
1088 begin
1089   Result := FormattedMark(AIndex, Legend.Format);
1090 end;
1091 
ListSourcenull1092 function TChartSeries.ListSource: TListChartSource;
1093 begin
1094   if not (Source is TListChartSource) then
1095     raise EEditableSourceRequired.Create(rsSourceNotEditable);
1096   Result := TListChartSource(Source);
1097 end;
1098 
1099 procedure TChartSeries.SetColor(AIndex: Integer; AColor: TColor);
1100 begin
1101   ListSource.SetColor(AIndex, AColor);
1102 end;
1103 
1104 procedure TChartSeries.SetMarks(AValue: TChartMarks);
1105 begin
1106   if FMarks = AValue then exit;
1107   FMarks.Assign(AValue);
1108 end;
1109 
1110 procedure TChartSeries.SetOnGetMark(AValue: TChartGetMarkEvent);
1111 begin
1112   if TMethod(FOnGetMark) = TMethod(AValue) then exit;
1113   FOnGetMark := AValue;
1114   UpdateParentChart;
1115 end;
1116 
1117 procedure TChartSeries.SetSource(AValue: TCustomChartSource);
1118 begin
1119   if AValue = FBuiltinSource then
1120     AValue := nil;
1121   if FSource = AValue then
1122     exit;
1123   CheckSource(AValue);
1124   if FListener.IsListening then
1125     Source.Broadcaster.Unsubscribe(FListener);
1126   FSource := AValue;
1127   Source.Broadcaster.Subscribe(FListener);
1128   SourceChanged(Self);
1129 end;
1130 
1131 procedure TChartSeries.SetStyles(AValue: TChartStyles);
1132 begin
1133   if FStyles = AValue then exit;
1134   if FStylesListener.IsListening then
1135     Styles.Broadcaster.Unsubscribe(FStylesListener);
1136   FStyles := AValue;
1137   if Styles <> nil then
1138     Styles.Broadcaster.Subscribe(FStylesListener);
1139   UpdateParentChart;
1140 end;
1141 
1142 procedure TChartSeries.SetText(AIndex: Integer; AValue: String);
1143 begin
1144   ListSource.SetText(AIndex, AValue);
1145 end;
1146 
1147 procedure TChartSeries.SetXValue(AIndex: Integer; AValue: Double); inline;
1148 begin
1149   ListSource.SetXValue(AIndex, AValue);
1150 end;
1151 
1152 procedure TChartSeries.SetXValues(AIndex, AXIndex: Integer; AValue: Double);
1153 begin
1154   if AXIndex = 0 then
1155     ListSource.SetXValue(AIndex, AValue)
1156   else
1157     ListSource.Item[AIndex]^.XList[AXIndex - 1] := AValue;
1158 end;
1159 
1160 procedure TChartSeries.SetYValue(AIndex: Integer; AValue: Double); inline;
1161 begin
1162   ListSource.SetYValue(AIndex, AValue);
1163 end;
1164 
1165 procedure TChartSeries.SetYValues(AIndex, AYIndex: Integer; AValue: Double);
1166 begin
1167   if AYIndex = 0 then
1168     ListSource.SetYValue(AIndex, AValue)
1169   else
1170     ListSource.Item[AIndex]^.YList[AYIndex - 1] := AValue;
1171 end;
1172 
1173 procedure TChartSeries.SourceChanged(ASender: TObject);
1174 begin
1175   if (ASender <> FBuiltinSource) and (ASender is TCustomChartSource) then
1176     try
1177       CheckSource(TCustomChartSource(ASender));
1178     except
1179       Source := nil; // revert to built-in source
1180       raise;
1181     end;
1182   StyleChanged(ASender);
1183 end;
1184 
1185 procedure TChartSeries.VisitSources(
1186   AVisitor: TChartOnSourceVisitor; AAxis: TChartAxis; var AData);
1187 begin
1188   if (AAxis = GetAxisX) or (AAxis = GetAxisY) then
1189     AVisitor(Source, AData);
1190 end;
1191 
1192 { TBasicPointSeries }
1193 
1194 procedure TBasicPointSeries.AfterAdd;
1195 var
1196   i: Integer;
1197 begin
1198   inherited AfterAdd;
1199   if Pointer <> nil then
1200     Pointer.SetOwner(ParentChart);
1201   for i := 0 to 1 do
1202     if FErrorBars[i] <> nil then
1203       FErrorBars[i].SetOwner(ParentChart);
1204 end;
1205 
1206 procedure TBasicPointSeries.AfterDrawPointer(
1207   ADrawer: IChartDrawer; AIndex: Integer; const APos: TPoint);
1208 begin
1209   Unused(ADrawer);
1210   Unused(AIndex, APos);
1211 end;
1212 
1213 procedure TBasicPointSeries.Assign(ASource: TPersistent);
1214 begin
1215   if ASource is TBasicPointSeries then
1216     with TBasicPointSeries(ASource) do begin
1217       Self.FMarkPositions := MarkPositions;
1218       if Self.FPointer <> nil then
1219         Self.FPointer.Assign(Pointer);
1220       Self.Stacked := Stacked;
1221       Self.FSupportsZeroLevel := FSupportsZeroLevel;
1222       Self.FMarkPositionCentered := FMarkPositionCentered;
1223     end;
1224   inherited Assign(ASource);
1225 end;
1226 
1227 constructor TBasicPointSeries.Create(AOwner: TComponent);
1228 begin
1229   inherited;
1230   FErrorBars[0] := TChartErrorBar.Create(FChart);
1231   FErrorBars[1] := TChartErrorBar.Create(FChart);
1232   FOptimizeX := true;
1233   FLoBound := 0;
1234   FUpBound := Count - 1;
1235   ToolTargets := [nptPoint, nptYList];
1236 end;
1237 
1238 destructor TBasicPointSeries.Destroy;
1239 begin
1240   FreeAndNil(FErrorBars[0]);
1241   FreeAndNil(FErrorBars[1]);
1242   FreeAndNil(FPointer);
1243   inherited;
1244 end;
1245 
1246 procedure TBasicPointSeries.DrawErrorBars(ADrawer: IChartDrawer);
1247 
1248   procedure EndBar(p: TPoint; w: Integer; IsHorBar: Boolean);
1249   begin
1250     if IsHorBar then
1251       ADrawer.Line(Point(p.x, p.y-w), Point(p.x, p.y+w))
1252     else
1253       ADrawer.Line(Point(p.x-w, p.y), Point(p.x+w, p.y));
1254   end;
1255 
1256   procedure DrawErrorBar(p: TDoublePoint; vp, vn: Double; w: Integer;
1257     IsXError: Boolean);
1258   var
1259     p1, p2: TDoublePoint;
1260     imgPt1, imgPt2: TPoint;
1261     isHorBar: Boolean;
1262   begin
1263     isHorBar := (IsXError and not IsRotated) or (IsRotated and not IsXError);
1264 
1265     if IsHorBar then begin
1266       p1 := DoublePoint(vp, p.Y);
1267       p2 := DoublePoint(vn, p.Y);
1268     end else begin
1269       p1 := DoublePoint(p.X, vp);
1270       p2 := DoublePoint(p.X, vn);
1271     end;
1272     imgPt1 := ParentChart.GraphToImage(p1);
1273     imgPt2 := ParentChart.GraphToImage(p2);
1274     ADrawer.Line(imgPt1, imgPt2);
1275 
1276     EndBar(imgPt1, w, isHorBar);
1277     EndBar(imgPt2, w, isHorBar);
1278   end;
1279 
1280   procedure InternalDrawErrorBars(IsXError: Boolean);
1281   var
1282     i: Integer;
1283     p: TDoublePoint;
1284     vp, vn: Double;
1285     w, w0: Integer;
1286     errbar: TChartErrorBar;
1287   begin
1288     if Assigned(Pointer) then
1289       w0 := IfThen(IsXError, Pointer.VertSize, Pointer.HorizSize)
1290     else
1291       w0 := DEF_ERR_ENDLENGTH;
1292     errbar := TChartErrorBar(IfThen(IsXError, XErrorBars, YErrorBars));
1293     w := ADrawer.Scale(IfThen(errBar.Width = -1, w0, errBar.Width));
1294 
1295     for i := FLoBound to FUpBound do begin
1296       p := FGraphPoints[i - FLoBound];
1297       if not ParentChart.IsPointInViewPort(p) then continue;
1298       if IsXError then begin
1299         if Source.GetXErrorBarLimits(i, vp, vn) then
1300           DrawErrorBar(p, AxisToGraphX(vp), AxisToGraphX(vn), w, true);
1301       end else begin
1302         if Source.GetYErrorBarLimits(i, vp, vn) then
1303           DrawErrorBar(p, AxisTographY(vp), AxisToGraphY(vn), w, false);
1304       end;
1305     end;
1306   end;
1307 
1308 begin
1309   // Draw x error bars
1310   if Assigned(XErrorBars) and XErrorBars.Visible and Source.HasXErrorBars then
1311   begin
1312     ADrawer.Pen := XErrorBars.Pen;
1313     InternalDrawErrorBars(true);
1314   end;
1315 
1316   // Draw y error bars
1317   if Assigned(YErrorBars) and YErrorBars.Visible and Source.HasYErrorBars then
1318   begin
1319     ADrawer.Pen := YErrorBars.Pen;
1320     InternalDrawErrorBars(false);
1321   end;
1322 end;
1323 
1324 procedure TBasicPointSeries.DrawLabels(ADrawer: IChartDrawer; AYIndex: Integer = -1);
1325 // Using AYIndex is workaround for issue #35077
1326 var
1327   prevLabelPoly: TPointArray;
1328 
1329   procedure DrawLabel(
1330     const AText: String; const ADataPoint: TPoint; ADir: TLabelDirection);
1331   const
1332     OFFSETS: array [TLabelDirection] of TPoint =
1333       ((X: -1; Y: 0), (X: 0; Y: -1), (X: 1; Y: 0), (X: 0; Y: 1));
1334   var
1335     center: TPoint;
1336   begin
1337     if AText = '' then exit;
1338 
1339     if Marks.RotationCenter = rcCenter then
1340       center := ADataPoint + OFFSETS[ADir] * Marks.CenterOffset(ADrawer, AText)
1341     else
1342       center := ADataPoint + OFFSETS[ADir] * Marks.CenterHeightOffset(ADrawer, AText);
1343     Marks.DrawLabel(ADrawer, ADataPoint, center, AText, prevLabelPoly);
1344   end;
1345 
1346 var
1347   y, ysum: Double;
1348   g: TDoublePoint;
1349   i, si: Integer;
1350   style: TChartStyle;
1351   lfont: TFont;
1352   curr, prev: Double;
1353   ext: TDoubleRect;
1354   yIsNaN: Boolean;
1355   centerLvl: Double;
1356 begin
1357   if not Marks.IsMarkLabelsVisible then exit;
1358 
1359   lfont := TFont.Create;
1360   try
1361     lfont.Assign(Marks.LabelFont);
1362     ParentChart.DisableRedrawing;
1363     ext := Extent;
1364     centerLvl := AxisToGraphY((ext.a.y + ext.b.y) * 0.5);
1365     UpdateLabelDirectionReferenceLevel(0, 0, centerLvl);
1366 
1367     for i := FLoBound to FUpBound do begin
1368       if SkipMissingValues(i) then
1369         continue;
1370       prev := IfThen(FSupportsZeroLevel, GetZeroLevel, 0.0);
1371       for si := 0 to Source.YCount - 1 do begin
1372         g := GetLabelDataPoint(i, si);
1373         if FStacked then begin
1374           if si = 0 then begin
1375             y := Source[i]^.Y;
1376             yIsNaN := IsNaN(y);
1377             ysum := IfThen(yIsNaN, prev, y);
1378           end else begin
1379             y := Source[i]^.YList[si-1];
1380             yIsNaN := IsNaN(y);
1381             if yIsNaN then y := 0.0;
1382             if Stacked then begin
1383               ysum += y;
1384               y := ysum;
1385             end;
1386           end;
1387           if IsRotated then
1388             g.X := AxisToGraphY(y)
1389             // Axis-to-graph transformation is independent of axis rotation ->
1390             // Using AxisToGraph_Y_ is correct!
1391           else
1392             g.Y := AxisToGraphY(y);
1393         end else
1394           yIsNaN := IsNaN(g.y);
1395 
1396         curr := TDoublePointBoolArr(g)[not IsRotated];
1397         if FMarkPositionCentered then begin
1398           if IsRotated then
1399             g := DoublePoint((curr + prev) * 0.5, g.y)
1400           else
1401             g := DoublePoint(g.x, (curr + prev) * 0.5);
1402         end;
1403         if Stacked then
1404           prev := curr;
1405 
1406         // Draw only the requested y index
1407         if (AYIndex >= 0) then begin
1408           if si < AYIndex then
1409             Continue
1410           else if si > AYIndex then
1411             break;
1412         end;
1413 
1414         with ParentChart do
1415           if
1416             ((Marks.YIndex = MARKS_YINDEX_ALL) or (Marks.YIndex = si)) and
1417             IsPointInViewPort(g) and (not yIsNaN)
1418           then begin
1419             if Styles <> nil then begin
1420               style := Styles.StyleByIndex(si);
1421               if style.UseFont then
1422                 Marks.LabelFont.Assign(style.Font)
1423               else
1424                 Marks.LabelFont.Assign(lfont);
1425             end;
1426             UpdateLabelDirectionReferenceLevel(i, si, centerLvl);
1427             DrawLabel(
1428               FormattedMark(i, '', si),
1429               GraphToImage(g),
1430               GetLabelDirection(IfThen(IsRotated, g.X, g.Y), centerLvl)
1431             );
1432           end;
1433       end;
1434     end;
1435 
1436   finally
1437     Marks.LabelFont.Assign(lfont);
1438     ParentChart.EnableRedrawing;
1439     lfont.Free;
1440   end;
1441 end;
1442 
1443 { Draws the pointers of the series.
1444   If ChartStyles are attached to the series then the pointer brush is determined
1445   by the style with the specified index. }
1446 procedure TBasicPointSeries.DrawPointers(ADrawer: IChartDrawer;
1447   AStyleIndex: Integer = 0; UseDataColors: Boolean = false);
1448 var
1449   i: Integer;
1450   p: TDoublePoint;
1451   ai: TPoint;
1452   ps, saved_ps: TSeriesPointerStyle;
1453   brushAlreadySet: boolean;
1454   c: TColor;
1455   style: TChartStyle;
1456 begin
1457   Assert(Pointer <> nil, 'Series pointer');
1458   if (not Pointer.Visible) or (Length(FGraphPoints) = 0) then exit;
1459   for i := FLoBound to FUpBound do begin
1460     p := FGraphPoints[i - FLoBound];
1461     if not ParentChart.IsPointInViewPort(p) then continue;
1462     ai := ParentChart.GraphToImage(p);
1463     if Assigned(FOnCustomDrawPointer) then
1464       FOnCustomDrawPointer(Self, ADrawer, i, ai)
1465     else begin
1466       if Assigned(FOnGetPointerStyle) then begin
1467         saved_ps := Pointer.Style;
1468         ps := saved_ps;
1469         FOnGetPointerStyle(self, i, ps);
1470         Pointer.SetOwner(nil);   // avoid recursion
1471         Pointer.Style := ps;
1472       end;
1473       brushAlreadySet := false;
1474       if (Styles <> nil) then
1475       begin
1476         style := Styles.StyleByIndex(AStyleIndex);
1477         if style <> nil then brushAlreadySet := style.UseBrush;
1478       end;
1479       if brushAlreadySet then
1480         Styles.Apply(ADrawer, AStyleIndex);
1481       if UseDataColors then c := Source[i]^.Color else c := clTAColor;
1482       Pointer.Draw(ADrawer, ai, c, brushAlreadySet);
1483       AfterDrawPointer(ADrawer, i, ai);
1484       if Assigned(FOnGetPointerStyle) then begin
1485         Pointer.Style := saved_ps;
1486         Pointer.SetOwner(ParentChart);
1487       end;
1488     end;
1489   end;
1490 end;
1491 
TBasicPointSeries.Extentnull1492 function TBasicPointSeries.Extent: TDoubleRect;
1493 begin
1494   if FStacked then
1495     Result := Source.ExtentCumulative
1496   else
1497     Result := Source.ExtentList;
1498 end;
1499 
1500 // Find an interval of x-values intersecting the extent.
1501 // Requires monotonic (but not necessarily increasing) axis transformation.
1502 procedure TBasicPointSeries.FindExtentInterval(
1503   const AExtent: TDoubleRect; AFilterByExtent: Boolean);
1504 var
1505   axisExtent: TDoubleInterval;
1506 begin
1507   FLoBound := 0;
1508   FUpBound := Count - 1;
1509   if AFilterByExtent then begin
1510     with AExtent do
1511       if IsRotated then
1512         axisExtent := DoubleInterval(GraphToAxisX(a.Y), GraphToAxisX(b.Y))
1513       else
1514         axisExtent := DoubleInterval(GraphToAxisX(a.X), GraphToAxisX(b.X));
1515     Source.FindBounds(axisExtent.FStart, axisExtent.FEnd, FLoBound, FUpBound);
1516     FLoBound := Max(FLoBound - 1, 0);
1517     FUpBound := Min(FUpBound + 1, Count - 1);
1518   end;
1519 end;
1520 
1521 procedure TBasicPointSeries.FindYRange(AXMin, AXMax: Double; var AYMin, AYMax: Double);
1522 begin
1523   Source.FindYRange(AXMin, AXMax, FStacked, AYMin, AYMax);
1524 end;
1525 
GetErrorBarsnull1526 function TBasicPointSeries.GetErrorBars(AIndex: Integer): TChartErrorBar;
1527 begin
1528   Result := FErrorBars[AIndex];
1529 end;
1530 
GetLabelDataPointnull1531 function TBasicPointSeries.GetLabelDataPoint(AIndex, AYIndex: Integer): TDoublePoint;
1532 begin
1533   Result := GetGraphPoint(AIndex, 0, AYIndex);
1534 end;
1535 
TBasicPointSeries.GetLabelDirectionnull1536 function TBasicPointSeries.GetLabelDirection(AValue: Double;
1537   const ACenterLevel: Double): TLabelDirection;
1538 const
1539   DIR: array [Boolean, Boolean] of TLabelDirection =
1540     ((ldTop, ldBottom), (ldRight, ldLeft));
1541 var
1542   isNeg: Boolean;
1543   ref: Double;
1544 begin
1545   case MarkPositions of
1546     lmpPositive: isNeg := false;
1547     lmpNegative: isNeg := true;
1548     lmpOutside,
1549     lmpInside :
1550       begin
1551         ref := IfThen(FSupportsZeroLevel, AxisToGraphY(GetZeroLevel), ACenterLevel);
1552         if AValue < ref then
1553           isNeg := true
1554         else
1555         if AValue > ref then
1556           isNeg := false
1557         else
1558         if not FSupportsZeroLevel then
1559           isNeg := false
1560         else
1561           isNeg := AValue < ACenterLevel;
1562         if MarkPositions = lmpInside then
1563           isNeg := not isNeg;
1564       end;
1565   end;
1566   if Assigned(GetAxisY) then
1567     if (IsRotated and ParentChart.IsRightToLeft) xor GetAxisY.Inverted then
1568       isNeg := not isNeg;
1569   Result := DIR[IsRotated, isNeg];
1570 end;
1571 
1572 procedure TBasicPointSeries.GetLegendItemsRect(
1573   AItems: TChartLegendItems; ABrush: TBrush; APen: TPen);
1574 var
1575   i: Integer;
1576   li: TLegendItemBrushPenRect;
1577   s: TChartStyle;
1578 begin
1579   case Legend.Multiplicity of
1580     lmSingle:
1581       begin
1582         li := TLegendItemBrushPenRect.Create(ABrush, APen, LegendTextSingle);
1583         li.TextFormat := Legend.TextFormat;
1584         AItems.Add(li);
1585       end;
1586     lmPoint:
1587       for i := 0 to Count - 1 do begin
1588         li := TLegendItemBrushPenRect.Create(ABrush, APen, LegendTextPoint(i));
1589         li.Color := GetColor(i);
1590         li.TextFormat := Legend.TextFormat;
1591         AItems.Add(li);
1592       end;
1593     lmStyle:
1594       if Styles <> nil then
1595         for s in Styles.Styles do
1596           AItems.Add(TLegendItemBrushPenRect.Create(
1597             IfThen(s.UseBrush, s.Brush, ABrush) as TBrush,
1598             IfThen(s.UsePen, s.Pen, APen) as TPen,
1599             LegendTextStyle(s)
1600           ));
1601   end;
1602 end;
1603 
GetNearestPointnull1604 function TBasicPointSeries.GetNearestPoint(
1605   const AParams: TNearestPointParams;
1606   out AResults: TNearestPointResults): Boolean;
1607 
GetGrabBoundnull1608   function GetGrabBound(ARadius: Integer): Double;
1609   begin
1610     if IsRotated then
1611       Result := ParentChart.YImageToGraph(AParams.FPoint.Y + ARadius)
1612     else
1613       Result := ParentChart.XImageToGraph(AParams.FPoint.X + ARadius);
1614     Result := GraphToAxisX(Result);
1615   end;
1616 
1617 var
1618   dist, tmpDist, i, j, lb, ub: Integer;
1619   sp, tmpSp: TDoublePoint;
1620   pt, tmpPt: TDoublePoint;
1621 begin
1622   AResults.FDist := Sqr(AParams.FRadius) + 1;  // the dist func does not calc sqrt
1623   AResults.FIndex := -1;
1624   AResults.FXIndex := 0;
1625   AResults.FYIndex := 0;
1626   if IsEmpty then exit(false);
1627   if not RequestValidChartScaling then exit(false);
1628 
1629   if FOptimizeX and AParams.FOptimizeX then
1630     Source.FindBounds(
1631       GetGrabBound(-AParams.FRadius),
1632       GetGrabBound( AParams.FRadius), lb, ub)
1633   else begin
1634     lb := 0;
1635     ub := Count - 1;
1636   end;
1637 
1638   dist := AResults.FDist;
1639   for i := lb to ub do begin
1640     sp := Source[i]^.Point;
1641     if IsNan(sp) then
1642       continue;
1643 
1644     // Since axis transformation may be non-linear, the distance should be
1645     // measured in screen coordinates. With high zoom ratios this may lead to
1646     // an integer overflow, so ADistFunc should use saturation arithmetics.
1647 
1648     // Find nearest point of datapoint at (x, y)
1649     if (nptPoint in AParams.FTargets) and (nptPoint in ToolTargets) then
1650     begin
1651       pt := AxisToGraph(sp);
1652       dist := Min(dist, ToolTargetDistance(AParams, pt, i, 0, 0));
1653     end;
1654 
1655     // Find nearest point to additional y values (at x).
1656     // In case of stacked data points check the stacked values.
1657     if (dist > 0) and (nptYList in AParams.FTargets) and (nptYList in ToolTargets)
1658     then begin
1659       tmpSp := sp;
1660       for j := 0 to Source.YCount - 2 do begin
1661         if FStacked then
1662           tmpSp.Y += Source[i]^.YList[j] else
1663           tmpSp.Y := Source[i]^.YList[j];
1664         tmpPt := AxisToGraph(tmpSp);
1665         tmpDist := ToolTargetDistance(AParams, tmpPt, i, 0, j + 1);
1666         if tmpDist < dist then begin
1667           dist := tmpDist;
1668           sp := tmpSp;
1669           pt := tmpPt;
1670           AResults.FYIndex := j + 1;    // FYIndex = 0 refers to the regular y
1671         end;
1672       end;
1673     end;
1674 
1675     // Find nearest point of additional x values (at y)
1676     if (dist > 0) and (nptXList in AParams.FTargets) and (nptXList in ToolTargets)
1677     then begin
1678       tmpSp := sp;
1679       for j := 0 to Source.XCount - 2 do begin
1680         tmpSp.X := Source[i]^.XList[j];
1681         tmpPt := AxisToGraph(tmpSp);
1682         tmpDist := ToolTargetDistance(AParams, tmpPt, i, j + 1, 0);
1683         if tmpDist < dist then begin
1684           dist := tmpDist;
1685           sp := tmpSp;
1686           pt := tmpPt;
1687           AResults.FXIndex := j + 1;   // FXindex = 0 refers to the regular x
1688         end;
1689       end;
1690     end;
1691 
1692     // The case nptCustom is not handled here, it depends on the series type.
1693     // TBarSeries, for example, checks whether AParams.FPoint is inside a bar.
1694 
1695     if dist >= AResults.FDist then
1696       continue;
1697 
1698     AResults.FDist := dist;
1699     AResults.FIndex := i;
1700     AResults.FValue := sp;
1701     AResults.FImg := ParentChart.GraphToImage(pt);
1702     if dist = 0 then break;
1703   end;
1704   Result := AResults.FIndex >= 0;
1705 end;
1706 
GetXRangenull1707 function TBasicPointSeries.GetXRange(AX: Double; AIndex: Integer): Double;
1708 var
1709   wl, wr: Double;
1710   i: Integer;
1711 begin
1712   if Source.XCount > 0 then begin
1713     i := AIndex - 1;
1714     wl := Abs(AX - NearestXNumber(i, -1));
1715     i := AIndex + 1;
1716     wr := Abs(AX - NearestXNumber(i, +1));
1717     Result := NumberOr(SafeMin(wl, wr), 1.0);
1718   end else
1719     Result := 1.0;
1720 end;
1721 
TBasicPointSeries.GetZeroLevelnull1722 function TBasicPointSeries.GetZeroLevel: Double;
1723 begin
1724   Result := 0.0;
1725 end;
1726 
1727 { Returns true if the data point at the given index has at least one missing
1728   y value (NaN) }
TBasicPointSeries.HasMissingYValuenull1729 function TBasicPointSeries.HasMissingYValue(AIndex: Integer;
1730   AMaxYIndex: Integer = MaxInt): Boolean;
1731 var
1732   j: Integer;
1733 begin
1734   Result := IsNaN(Source[AIndex]^.Y);
1735   if not Result then
1736     for j := 0 to Min(AMaxYIndex, Source.YCount)-2 do
1737       if IsNaN(Source[AIndex]^.YList[j]) then
1738         exit(true);
1739 end;
1740 
TBasicPointSeries.IsErrorBarsStorednull1741 function TBasicPointSeries.IsErrorBarsStored(AIndex: Integer): Boolean;
1742 begin
1743   with FErrorBars[AIndex] do
1744     Result := Visible or (Width <> -1) or (Pen.Color <> clBlack) or
1745       (not Pen.Cosmetic) or (Pen.EndCap <> pecRound) or
1746       (Pen.JoinStyle <> pjsRound) or (Pen.Mode <> pmCopy) or
1747       (Pen.Style <> psSolid) or (Pen.Width <> 1);
1748 end;
1749 
1750 procedure TBasicPointSeries.MovePoint(
1751   var AIndex: Integer; const ANewPos: TDoublePoint);
1752 var
1753   p: TDoublePoint;
1754 begin
1755   if not InRange(AIndex, 0, Count - 1) then exit;
1756   p := GraphToAxis(ANewPos);
1757   with ListSource do begin
1758     AIndex := SetXValue(AIndex, p.X);
1759     SetYValue(AIndex, p.Y);
1760   end;
1761 end;
1762 
1763 procedure TBasicPointSeries.MovePointEx(var AIndex: Integer;
1764   AXIndex, AYIndex: Integer; const ANewPos: TDoublePoint);
1765 var
1766   sp: TDoublePoint;
1767   sum: Double;
1768   j: Integer;
1769 begin
1770   Unused(AXIndex);
1771 
1772   if not InRange(AIndex, 0, Count - 1) then
1773     exit;
1774 
1775   sp := GraphToAxis(ANewPos);
1776   case AYIndex of
1777    -1: begin
1778         // ListSource.SetXValue(AIndex, sp.X);
1779         // ListSource.SetYValue(AIndex, sp.Y);
1780        end;
1781     0: begin
1782          ListSource.SetXValue(AIndex, sp.X);
1783          ListSource.SetYValue(AIndex, sp.Y);
1784        end;
1785     else
1786       if FStacked then begin
1787         sum := 0;
1788         for j := 0 to AYIndex - 1 do
1789           sum := sum + YValues[AIndex, j];
1790         YValues[AIndex, AYIndex] := sp.Y - sum;
1791       end else
1792         YValues[AIndex, AYIndex] := sp.Y;
1793       UpdateParentChart;
1794   end;
1795 end;
1796 
TBasicPointSeries.NearestXNumbernull1797 function TBasicPointSeries.NearestXNumber(
1798   var AIndex: Integer; ADir: Integer): Double;
1799 begin
1800   while InRange(AIndex, 0, Count - 1) do
1801     with Source[AIndex]^ do
1802       if IsNan(X) then
1803         AIndex += ADir
1804       else
1805         exit(AxisToGraphX(X));
1806   Result := SafeNan;
1807 end;
1808 
1809 procedure TBasicPointSeries.PrepareGraphPoints(
1810   const AExtent: TDoubleRect; AFilterByExtent: Boolean);
1811 var
1812   i: Integer;
1813 begin
1814   FindExtentInterval(AExtent, AFilterByExtent);
1815 
1816   SetLength(FGraphPoints, Max(FUpBound - FLoBound + 1, 0));
1817   if (AxisIndexX < 0) and (AxisIndexY < 0) then begin
1818     // Optimization: bypass transformations in the default case.
1819     if Source.XCount > 0 then
1820       for i := FLoBound to FUpBound do
1821         with Source[i]^ do
1822           FGraphPoints[i - FLoBound] := DoublePoint(X, Y)
1823     else
1824       for i := FLoBound to FUpBound do
1825         with Source[i]^ do
1826           FGraphPoints[i - FLoBound] := DoublePoint(i, Y);
1827   end else
1828     for i := FLoBound to FUpBound do
1829       FGraphPoints[i - FLoBound] := GetGraphPoint(i);
1830 end;
1831 
1832 procedure TBasicPointSeries.SetErrorBars(AIndex: Integer;
1833   AValue: TChartErrorBar);
1834 begin
1835   FErrorBars[AIndex] := AValue;
1836   UpdateParentChart;
1837 end;
1838 
1839 procedure TBasicPointSeries.SetMarkPositionCentered(AValue: Boolean);
1840 begin
1841   if FMarkPositionCentered = AValue then exit;
1842   FMarkPositionCentered := AValue;
1843   UpdateParentChart;
1844 end;
1845 
1846 procedure TBasicPointSeries.SetMarkPositions(AValue: TLinearMarkPositions);
1847 begin
1848   if FMarkPositions = AValue then exit;
1849   FMarkPositions := AValue;
1850   UpdateParentChart;
1851 end;
1852 
1853 procedure TBasicPointSeries.SetPointer(AValue: TSeriesPointer);
1854 begin
1855   FPointer.Assign(AValue);
1856   UpdateParentChart;
1857 end;
1858 
1859 procedure TBasicPointSeries.SetStacked(AValue: Boolean);
1860 begin
1861   if FStacked = AValue then exit;
1862   FStacked := AValue;
1863   UpdateParentChart;
1864 end;
1865 
1866 procedure TBasicPointSeries.SetStackedNaN(AValue: TStackedNaN);
1867 begin
1868   if FStackedNaN = AValue then exit;
1869   FStackedNaN := AValue;
1870   UpdateParentChart;
1871 end;
1872 
1873 { Returns true when the data point at the specified index contains missing
1874   values in a way such that the point cannot be drawn. }
TBasicPointSeries.SkipMissingValuesnull1875 function TBasicPointSeries.SkipMissingValues(AIndex: Integer): Boolean;
1876 begin
1877   Result := IsNan(Source[AIndex]^.X);
1878   if not Result then
1879     Result := FStacked and (FStackedNaN = snDoNotDraw) and HasMissingYValue(AIndex);
1880 end;
1881 
TBasicPointSeries.ToolTargetDistancenull1882 function TBasicPointSeries.ToolTargetDistance(const AParams: TNearestPointParams;
1883   AGraphPt: TDoublePoint; APointIdx, AXIdx, AYIdx: Integer): Integer;
1884 var
1885   pt: TPoint;
1886 begin
1887   Unused(APointIdx);
1888   Unused(AXIdx, AYIdx);
1889   if IsNaN(AGraphPt) then
1890     exit(MaxInt)
1891   else begin
1892     pt := ParentChart.GraphToImage(AGraphPt);
1893     Result := AParams.FDistFunc(AParams.FPoint, pt);
1894   end;
1895 end;
1896 
1897 // AIndex refers to the index into YList here.
1898 // The ordinary Y value has Index = -1.
1899 procedure TBasicPointSeries.UpdateGraphPoints(AIndex, ALo, AUp: Integer;
1900   ACumulative: Boolean);
1901 var
1902   i, j: Integer;
1903   y: Double;
1904 begin
1905   if IsRotated then begin
1906     if ACumulative then begin
1907       if FStacked and (FStackedNaN = snReplaceByZero) then
1908         for i := ALo to AUp do
1909         begin
1910           y := NumberOr(Source[i]^.Y, IfThen(FSupportsZeroLevel, GetZeroLevel, 0.0));
1911           for j := 0 to AIndex do
1912             y += NumberOr(Source[i]^.YList[j], 0.0);
1913           FGraphPoints[i - ALo].X := AxisToGraphY(y)
1914         end
1915       else
1916         for i := ALo to AUp do
1917         begin
1918           y := Source[i]^.Y;
1919           for j := 0 to AIndex do
1920             y += Source[i]^.YList[j];
1921           FGraphPoints[i - ALo].X := AxisToGraphY(y);
1922         end;
1923     end else
1924     if AIndex = -1 then
1925       for i := ALo to AUp do
1926         FGraphPoints[i - ALo].X := AxisToGraphY(Source[i]^.Y)
1927     else
1928       for i := ALo to AUp do
1929         FGraphPoints[i - ALo].X := AxisToGraphY(Source[i]^.YList[AIndex]);
1930   end
1931   else begin
1932     if ACumulative then begin
1933       if FStacked and (FStackedNaN = snReplaceByZero) then
1934         for i := ALo to AUp do
1935         begin
1936           y := NumberOr(Source[i]^.Y, IfThen(FSupportsZeroLevel, GetZeroLevel, 0.0));
1937           for j := 0 to AIndex do
1938             y += NumberOr(Source[i]^.YList[j], 0.0);
1939           FGraphPoints[i - ALo].Y := AxisToGraphY(y);
1940         end
1941       else
1942         for i := ALo to AUp do begin
1943           y := Source[i]^.Y;
1944           for j := 0 to AIndex do
1945             y += Source[i]^.YList[j];
1946           FGraphPoints[i - ALo].Y := AxisToGraphY(y);
1947         end;
1948     end else
1949     if AIndex = -1 then
1950       for i := ALo to AUp do
1951         FGraphPoints[i - ALo].Y := AxisToGraphY(Source[i]^.Y)
1952     else
1953       for i := ALo to AUp do
1954         FGraphPoints[i - ALo].Y := AxisToGraphY(Source[i]^.YList[AIndex]);
1955   end;
1956 end;
1957 
1958 procedure TBasicPointSeries.UpdateGraphPoints(AIndex: Integer;
1959   ACumulative: Boolean);
1960 begin
1961   UpdateGraphPoints(AIndex, FLoBound, FUpBound, ACumulative);
1962 end;
1963 
1964 procedure TBasicPointSeries.SourceChanged(ASender: TObject);
1965 begin
1966   FLoBound := 0;
1967   FUpBound := Count - 1;
1968   inherited;
1969 end;
1970 
1971 procedure TBasicPointSeries.UpdateMargins(
1972   ADrawer: IChartDrawer; var AMargins: TRect);
1973 var
1974   i, dist, j: Integer;
1975   labelText: String;
1976   dir: TLabelDirection;
1977   m: array [TLabelDirection] of Integer absolute AMargins;
1978   gp: TDoublePoint;
1979   scMarksDistance: Integer;
1980   center: Double;
1981   ysum: Double;
1982 begin
1983   if not Marks.IsMarkLabelsVisible or not Marks.AutoMargins then exit;
1984   if IsEmpty then exit;
1985 
1986   {FLoBound and FUpBound fields may be outdated here (if axis' range has been
1987    changed after the last series' painting). FLoBound and FUpBound will be fully
1988    updated later, in a PrepareGraphPoints() call. But we need them now. If data
1989    source is sorted by X in the ascending order, obtaining FLoBound and FUpBound
1990    is very fast (binary search) - so we call FindExtentInterval() with True as
1991    the second parameter. Otherwise, obtaining FLoBound and FUpBound requires
1992    enumerating all the data points to see, if they are in the current chart's
1993    viewport. But this is exactly what we are going to do in the loop below, so
1994    obtaining true FLoBound and FUpBound values makes no sense in this case - so
1995    we call FindExtentInterval() with False as the second parameter, thus setting
1996    FLoBound to 0 and FUpBound to Count-1}
1997   FindExtentInterval(ParentChart.CurrentExtent, Source.IsSortedByXAsc);
1998 
1999   with Extent do
2000     center := AxisToGraphY((a.y + b.y) * 0.5);
2001   UpdateLabelDirectionReferenceLevel(0, 0, center);
2002   scMarksDistance := ADrawer.Scale(Marks.Distance);
2003   for i := FLoBound to FUpBound do begin
2004     j := 0;
2005     gp := GetLabelDataPoint(i, 0);
2006     while true do begin
2007       if not ParentChart.IsPointInViewPort(gp) then break;
2008       labelText := FormattedMark(i, '', j);
2009       if labelText = '' then break;
2010 
2011       UpdateLabelDirectionReferenceLevel(i, j, center);
2012       dir := GetLabelDirection(TDoublePointBoolArr(gp)[not IsRotated], center);
2013       with Marks.MeasureLabel(ADrawer, labelText) do
2014         dist := IfThen(dir in [ldLeft, ldRight], cx, cy);
2015       if Marks.DistanceToCenter then
2016         dist := dist div 2;
2017 
2018       m[dir] := Max(m[dir], dist + scMarksDistance);
2019 
2020       if (Source.YCount > 1) and (j = 0) then begin
2021         if FStacked then begin
2022           ysum := 0;
2023           for j := 0 to Source.YCount-1 do
2024             ysum += NumberOr(Source.Item[i]^.GetY(j), 0.0);
2025           TDoublePointBoolArr(gp)[not IsRotated] := AxisToGraphY(ysum);
2026         end else
2027           gp := GetLabelDataPoint(i, Source.YCount-1);
2028         j := Source.YCount-1;
2029       end else
2030         break;
2031     end;
2032   end;
2033 end;
2034 
2035 { Can be overridden for a data-point dependent reference level, such as in
2036   TBubbleSeries. AIndex refers to chart source. }
2037 procedure TBasicPointSeries.UpdateLabelDirectionReferenceLevel(AIndex, AYIndex: Integer;
2038   var ALevel: Double);
2039 begin
2040   Unused(AIndex, AYIndex, ALevel);
2041 end;
2042 
2043 procedure TBasicPointSeries.UpdateMinXRange;
2044 var
2045   x, prevX: Double;
2046   i: Integer;
2047 begin
2048   if (Count < 2) or (Source.XCount = 0) then begin
2049     FMinXRange := 1.0;
2050     exit;
2051   end;
2052   x := Source[0]^.X;
2053   prevX := Source[1]^.X;
2054   FMinXRange := Abs(x - prevX);
2055   for i := 2 to Count - 1 do begin
2056     x := Source[i]^.X;
2057     FMinXRange := SafeMin(Abs(x - prevX), FMinXRange);
2058     prevX := x;
2059   end;
2060 end;
2061 
2062 procedure SkipObsoleteProperties;
2063 const
2064   LEGEND_NOTE = 'Obsolete, use TCustomChartSeries.ShowInLegend instead';
2065 begin
2066   RegisterPropertyToSkip(TCustomChartSeries, 'ShowInLegend', LEGEND_NOTE, '');
2067 end;
2068 
2069 initialization
2070   SkipObsoleteProperties;
2071 
2072 end.
2073 
2074