Sign in Join
Wiki copytemporary

TAChart Runtime FAQ: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: TAChart Runtime FAQ

OldNewLine
1+This article answers frequently asked questions related to usage of TAChart at runtime.
2+
3+## Series
4+
5+### How to add a series at runtime?
6+
7+Just create the series, set its properties and call the chart method `AddSeries`
8+
9+``` pascal
10+uses
11+ TATypes, TASeries;
12+
13+function AddLineSeries(AChart: TChart; ATitle: String; AColor: TColor): TChartSeries;
14+begin
15+ Result := TLineSeries.Create(AChart);
16+ with TLineSeries(Result) do
17+ begin
18+ // Series title for the legend
19+ SeriesTitle := ATitle;
20+ // Show data point markers (specified fill color, black border)
21+ ShowPoints := true;
22+ Pointer.Brush.Color := AColor;
23+ Pointer.Pen.Color := clBlack;
24+ Pointer.Style := psCircle;
25+ // Show line segments connecting the data points, color as specified.
26+ ShowLines := true;
27+ LinePen.Style := psSolid;
28+ SeriesColor := AColor;
29+ end;
30+ // Add new series to the chart
31+ AChart.AddSeries(Result);
32+end;
33+```
34+
35+### How to plot x/y columns from a text file in a series?
36+
37+Suppose a text file consisting of two comma-separated columns, just like this (data from <https://www.worldometers.info/world-population/world-population-by-year/>):
38+
39+ Year,World Population
40+ 2023,8045311447
41+ 2022,7975105156
42+ 2021,7909295151
43+ 2020,7840952880
44+ ...
45+
46+and you want to plot the two columns as x and y in a series. There are several ways to read the file, here is the classical one using a TextFile:
47+
48+``` pascal
49+uses
50+ TACustomSeries;
51+
52+procedure PlotFile(AFileName: String; ASeries: TChartSeries);
53+var
54+ F: TextFile;
55+ s: String;
56+ p: Integer;
57+ x: Integer;
58+ y: Double;
59+begin
60+ if ASeries = nil then
61+ raise Exception.Create('ASeries has not yet been created.');
62+
63+ ASeries.BeginUpdate;
64+ ASeries.Clear;
65+ AssignFile(F, AFilename);
66+ Reset(F);
67+ // Read the non-numeric title line
68+ ReadLn(F, s);
69+ // Read the data lines, skip empty and invalid lines.
70+ while not EoF(F) do
71+ begin
72+ ReadLn(F, s);
73+ if s <> '' then
74+ begin
75+ p := Pos(',', s);
76+ if p <> 0 then
77+ if TryStrToInt(copy(s, 1, p-1), x) and TryStrToFloat(copy(s, p+1, Length(s)), y) then
78+ ASeries.AddXY(x, y);
79+ end;
80+ end;
81+ CloseFile(F);
82+ ASeries.EndUpdate;
83+end;
84+```
85+
86+### How to iterate through the series of a chart?
87+
88+The series are accessible via the array-like property `Series` of the chart. Note that this returns only the most fundamental series type, TBasicChartSeries, and a type-cast may be necessage before being able to access the series properties.
89+
90+The following example re-colors all line series in red:
91+
92+``` pascal
93+uses
94+ TASeries;
95+var
96+ i: Integer;
97+begin
98+ for i := 0 to AChart.SeriesCount-1 do
99+ if AChart.Series[i] is TLineSeries then
100+ TLineSeries(AChart.Series[i]).SeriesColor := clRed;
101+end;
102+```
103+
104+If you prefer the more modern iterators you can use the syntax
105+
106+``` pascal
107+uses
108+ TASeries, TAEnumerators;
109+var
110+ ser: TCustomSeries;
111+begin
112+ for ser in CustomSeries(AChart) do
113+ TLineSeries(ser).Series.Color := clRed;
114+end;
115+```
116+
117+### How to delete/hide a series at runtime?
118+
119+- If you don't need the series any more just destroy it: `series.Free`.
120+- If you want to keept it for other usage, for example to insert it into another chart, call `Chart.DeleteSeries(series)`.
121+- If you want to hide it, but keep it in the chart, set the property `Active` of the series to `false`.
122+
123+## Axes and axis transformations
124+
125+### Fixed axis limits
126+
127+Normally the limits of the chart axes are determined such that the chart covers the available area as much as possible. But sometimes it is better to keep the axis limit frozen, independent of the data range.
128+
129+This code uses the Extent of the chart to define a constant range for the y axis between 0 and 100. The first example manipulates the Extent consecutively, the second one simultaneously by using an intermediate variable. Note that the first method may crash the program in the third line if, for some reason, the new maximum is smaller than the currently active minimum. Note that the second code freezes also the x axis; you'll have to call `Chart1.Extent.UseXMin := false` and `Chart1.Extent.UseXmax := false` to release it again.
130+
131+``` pascal
132+Chart1.Extent.YMax := 100;
133+Chart1.Extent.YMin := 0;
134+Chart1.Extent.UseYMax := true;
135+Chart1.Extent.UseYMin := true;
136+```
137+
138+or:
139+
140+``` pascal
141+var
142+ ex: TDoubleRect;
143+begin
144+ ex := Chart1.GetFullExtent;
145+ ex.b.y := 100;
146+ ex.a.y := 0;
147+ Chart1.Extent.FixTo(ex);
148+end;
149+```
150+
151+### How to add an axis at runtime?
152+
153+A chart can contain several axes which are stored in its `AxisList`. Since `AxisList` inherits from `TCollection` you can use its `Add` method to create a new axis:
154+
155+``` pascal
156+uses
157+ TAChartAxis, TAChartUtils;
158+
159+function AddAxis(AParentChart: TChart; ATitle: String; AAlignment: TChartAxisAlignment): TChartAxis;
160+begin
161+ // Create a new axis
162+ Result := AParentChart.AxisList.Add;
163+ // Axis orientation: calLeft, calTop, calRight or calBottom
164+ Result.Alignment := AAlignment;
165+ // Axis title
166+ Result.Title.Caption := ATitle;
167+ Result.Title.Visible := true;
168+ // Rotate title of a vertical axis
169+ case AAlignment of
170+ calLeft : Result.Title.LabelFont.Orientation := +900;
171+ calRight : Result.Title.LabelFont.Orientation := -900;
172+ end;
173+ // Show axis line
174+ Result.AxisPen.Visible := true;
175+end;
176+```
177+
178+If you want to assign the new axis to AxisIndexX or AxisIndexY of a series you can use the property `Index` of the newly created axis.
179+
180+### How to add an axis transformation?
181+
182+TChartAxisTransformations is a container for several TAxisTransform instances. It must be assigned to the `Transformations` property of an axis in order to be operative. It is created at runtime like any other component:
183+
184+``` pascal
185+uses
186+ TATransformations;
187+
188+procedure TForm1.CreateAxisTransformationOfAxis(AChart: TChart; AxisIndex: Integer);
189+begin
190+ AChart.AxisList[AxisIndex].Transformations := TChartAxisTransformations.Create(Self);
191+end;
192+```
193+
194+However, a bare TChartAxisTransformations component is useless. It must contain at least one TAxisTransform object. In the following code, we will add a TAutoScaleAxisTransform which is needed for paned charts. Please note that, although the TAxisTransform objects are collected in the TChartAxisTransformations property `List` which derives from TFPList, new objects must not be added by calling `List.Add`. Instead, assign the `Transformations` property of the new TAxisTransform to the TChartAxisTransformations component:
195+
196+``` pascal
197+uses
198+ TATransformations;
199+
200+procedure TForm1.CreateAutoScaleAxisTransformOfAxis(AChart: TChart; AxisIndex: Integer);
201+var
202+ T: TChartAxisTransformations;
203+ transf: TAxisTransform;
204+begin
205+ // Create the TChartAxisTransformations component (or use the one dropped in the form at designtime)
206+ T := TChartAxisTransformations.Create(self);
207+ // and assign it to the Transformations property of the corresponding axis
208+ AChart.AxisList[AxisIndex].Transformations := T;
209+
210+ // Create the AutoScaleAxisTransform object
211+ transf := TAutoscaleAxisTransform.Create(T);
212+ // and add it to the TChartAxisTransformations component
213+ transf.Transformations := T;
214+end;
215+```
216+
217+### How to remove an axis transformation?
218+
219+Simply destroy it:
220+
221+``` pascal
222+uses
223+ TATransformations;
224+...
225+ ChartAxisTransformations1AutoscaleAxisTransform1.Free;
226+```
227+
228+### Logarithmic axis
229+
230+Data varying over several orders of magnitude are usually plotted on a logarithmic axis. For achieving a logarithmic scale a ChartTransformations component containing a TLogarithmAxisTransform must be assigned to the corresponding axis. Some optimization must be done with the axis marks in order to get "nice" labels. Don't forget to set the index of the log axis to the `AxisIndexX` or `AxisIndexY` property of each series.
231+
232+The following code shows how an axis can be toggled between logarithmic and linear scale:
233+
234+``` pascal
235+uses
236+ TAChartAxis, TAChartTransformations, TACustomSource;
237+
238+procedure LogAxis(AChart: TChart; AxisIndex: Integer; Enable: Boolean);
239+var
240+ axis: TChartAxis;
241+ transf: TLogarithmAxisTransform;
242+begin
243+ // Determine the axis to be processed
244+ axis := AChart.AxisList[AxisIndex];
245+
246+ // If an AxisTransformations component has not yet been assigned to the axis, we must create one
247+ if axis.Transformations = nil then
248+ axis.Transformations := TChartAxisTransformations.Create(AChart.Owner);
249+
250+ if axis.Transformations.List.Count = 0 then
251+ begin
252+ // Create the log transform...
253+ transf := TLogarithmAxisTransform.Create(Axis.Transformations);
254+ transf.Base := 10;
255+ transf.Transformations := Axis.Transformations;
256+ end else
257+ transf := (TAxisTransform(axis.Transformations.List[0]) as TLogarithmAxisTransform);
258+
259+ // Enable the transformation for a log scale, or disable it for a linear scale
260+ transf.Enabled := Enable;
261+
262+ // Find "nice" axis labels
263+ if Enable then begin
264+ axis.Intervals.Options := axis.Intervals.Options + [aipGraphCoords];
265+ // Depending on the size of the chart and the data extent you may have to
266+ // play with the following numbers to get "nice" log labels.
267+ axis.Intervals.MaxLength := 200;
268+ axis.Intervals.Tolerance := 100;
269+ end else begin
270+ // Adapt these numbers, too. These are the defaults of a linear scale.
271+ // Often the Intervals.MaxLength has to be increased to avoid overlapping labels.
272+ axis.Intervals.Options := axis.Intervals.Options - [aipGraphCoords];
273+ axis.Intervals.MaxLength := 50;
274+ axis.Intervals.Tolerance := 1;
275+ end;
276+end;
277+```
278+
279+In order to toggle between log and linear scale it must be known whether the axis currently is logarithmic or linear:
280+
281+``` pascal
282+function IsLogAxis(AChart: TChart; AxisIndex: Integer): Boolean;
283+var
284+ axis: TChartAxis;
285+ transf: TAxisTransform;
286+begin
287+ Result := false;
288+ if Assigned(AChart) and (AxisIndex < AChart.AxisList.Count) then
289+ begin
290+ axis := AChart.AxisList[AxisIndex];
291+ if Assigned(axis.Transformations) and (axis.Transformations.List.Count > 0) then
292+ begin
293+ transf := TAxisTransform(axis.Transformations.List[0]);
294+ Result := transf.Enabled and (transf is TLogarithmAxisTransform);
295+ end;
296+ end;
297+end;
298+```
299+
300+In total, these routines could be called in a button's OnClick event handler to switch the left axis (index 0) between log and linear:
301+
302+``` pascal
303+procedure TForm1.Button1Click(Sender: TObject);
304+begin
305+ LogAxis(Chart1, 0, not IsLogAxis(Chart1, 0));
306+end;
307+```
308+
309+### How to find the pixel coordinates of the point of the y axis?
310+
311+Essentially, there are three coordinate systems which are involved when drawing a chart on the screen:
312+
313+- The inner-most coordinates are the pixels on the screen, TAChart calls them **image coordinates**.
314+- The outer-most coordinates are the values plotted on the axes; these are the units of the data values provided by the chart source. In TAChart speak these are called **axis coordinates**.
315+- In between these two systems are the **graph coordinates**. This is an inivisible linear coordinate system layed over the chart. The connection between graph and image coordinates is made by the chart methods `GraphToImage` and `ImageToGraph`. The connection between graph and axis coordinates is defined by axis transformations. The axis transformations provide methods `GraphToAxis` and `AxisToGraph`. Normally, where there is no transformation, TAChart uses an identity transformation, i.e. assumes graph and axis coorindates to be equal. When the transformations object has been assigned to an axis you can also call the axis' `GraphToAxis` and `AxisToGraph` methods. And when a series has been assigned to an axis (by setting its `AxisIndexX` and/or `AxisIndexY`) you can also use the series' methods `GraphToAxis` and `AxisToGraph`.
316+
317+Suppose you have a chart with two y axes, the right one displaying values between 0 and 100, and you want to determine the y pixel coordinates of some y axis value, say 27. Knowing the axis coordinates (27) you must determine the graph coordinate. Since the right axis is subject to an axis transformation you can call `Chart.AxisList[2].GetTransform` in order to dermine the `TAChartTransformations` applicable to this axis; this also takes care of the identity transform when no external transformations object is used. Then call the transformations method `AxisToGraph` to get the graph y value. Finally apply `Chart.YGraphToImage` to determine the pixel value:
318+
319+``` pas
320+uses
321+ TAChartAxis, TATransformations;
322+
323+function YAxisToPixels(AChart: TChart; AxisIndex: Integer; y: Double): Integer;
324+var
325+ axis: TChartAxis;
326+ grY: Double;
327+begin
328+ axis := AChart.AxisList[AxisIndex];
329+ grY := axis.GetTransform.AxisToGraph(Y);
330+ Result := AChart.YGraphToImage(grY);
331+end;
332+
333+procedure TForm1.Button1Click(Sender: TObject);
334+begin
335+ ShowMessage(YAxisToPixels(Chart1, 1, 0.2).ToString);
336+end;
337+```
338+
339+## Miscellaneous
340+
341+### What can I do to avoid compiler error *"Identifier not found 'TDoublePoint'"*?
342+
343+`TDoublePoint` is declared in unit `TAChartUtils`, add it to the `uses` clause of the unit. It also contains the declaration of
344+
345+- `TDoubleRect` which is needed for chart extents.
346+
347+Other declarations, often not found when working with run-time code, are
348+
349+- `TSeriesMarksStyle` for the `Style` of axis and series `Marks` --\> in unit `TAChartUtils`
350+- `TSeriesPointerStyle` for the series pointer styles, e.g. `psCircle, psRectangle` --\> in unit `TATypes`
351+- geometrical functions (creation of `TDoublePoint` and `TDoubleRect`, overloaded operators for `TPoint` and `TDoublePoints`) are in unit `TAGeometry`
352+
353+---
354+*From the Free Pascal wiki, page "TAChart Runtime FAQ", as it stood on 2023-08-29 after a change by Wp, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
355+Category: Components
356+Category: Graphics
357+Category: Lazarus
358+Category: TAChart

Back to history