Sign in Join
Wiki copytemporary

TAChart Tutorial: Background design: what changed

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

OldNewLine
11## Introduction
22
33![](/wiki-images/3/35/Tachart_background_finished.png)
44
55Standard charts created by the TAChart package have a gray background. Using the `Color` property it is possibly to modify the color of the entire chart background, and the `BackColor` controls the color of the chart area enclosed by the axes, the so-called "back wall". In any case, the design is quite conventional compared to other charting packages and applications. Is it possible to give the chart background a more interesting design?
66
77In this tutorial, we will demonstrate that this is a relatively easy task by taking advantage of one or two events of TChart. We will create a plot charting changes in the number of Lazarus downloads per year over recent years. To highlight the connection with Lazarus we plan to show the Lazarus splash screen as a background image of the chart.
88
99## Preparation
1010
1111### Data
1212
1313Sourceforge provides a list of the Lazarus download counts per month. From <http://sourceforge.net/projects/lazarus/files/Lazarus%20Windows%2032%20bits/stats/timeline?dates=2003-01-01+to+2014-09-02> I extracted the download count per year for the windows-32 installation file:
1414
1515| Year | 2005 | 2006 | 2007 | 2008 | 2009 | 2010 | 2011 | 2012 | 2013 |
1616|-----------|-------|--------|--------|--------|--------|--------|--------|--------|--------|
1717| Downloads | 53299 | 119613 | 158060 | 218915 | 190567 | 230108 | 267858 | 298335 | 280586 |
1818
1919These data will be basis of our chart.
2020
2121### Setting up the chart
2222
2323Create a new project and add a `TChart` to the form. Client-align it such that the chart fills the entire form. Drag the form borders such that the window is about 400 pixels wide and 300 pixels high.
2424
2525We will plot the "Years" along the `x` axis, and the "Download count" along the `y` axis of the chart. Therefore, go to bottom axis, find the property `Title` and enter "Years" in the field for `Caption`. Show the title by setting its property `Visible` to `true`. Adapt other properties to your liking: I often prefer to have the title in bold font and larger size (12). And sometimes the grid is annoying, you can turn if off by setting the `Visible` property of the axis' `Grid` to `false`. Repeat with the vertical axis, the title should be named "Downloads per year".
2626
2727We should also show a title above the chart. Select the text "Lazarus downloads" for the `Text` of the chart's `Title`. Again, set the property `Visible` to `true` in order to show the title. You may also want to switch the title font to bold and increase its size a bit.
2828
2929![](/wiki-images/1/1b/Tachart_background_no_data.png) ![](/wiki-images/c/cb/Tachart_background_datapoints_editor.png)
3030
3131A good series type for time-series data is a bar series. At first we add a `TBarSeries` to the chart: Double-click on the chart to open the series editor, click "Add" and select "Bar series" from the dropdown list. We do not see the series because it has no data yet.
3232
3333There are various ways to assign data to the series. Since we have static data which do not change during the program it is convenient to use a `TListSource` which provides a datapoint editor for entering data at design-time. You find the ListChartSource in the "Chart" component palette, it is the second icon. In order to enter data click on the ellipsis button next to the property `DataPoints` of the component - this opens the DataPoints editor. Enter the data from the table above into the editor grid: the "year" goes into the "X" column, the "downloads" go into the "Y" column. There is no need to enter anything into the "Color" and "Text" columns.
3434
3535Finally, we connect the ListChartSource to the BarSeries. Each series has a property `Source`. Click on the dropdown arrow and select the ListChartSource from the list. Immediately the bar chart shows up.
3636
3737![](/wiki-images/d/d5/Tachart_background_first_version.png)
3838
3939## The TChart painting events
4040
4141So far, the project has been standard. Now let's intercept the drawing process...
4242
4343`TChart` offers several events where our own code can be hooked in:
4444
4545- At the beginning of the drawing pipeline there is `OnChartPaint`. This event is marked as experimental and can only be reached from code, not from the object inspector. It fires before anything is drawn by the chart and offers a `var` parameter `ADoDefaultDrawing`. Setting this one to `false`, therefore, by-passes the entire built-in drawing process and allows to paint the chart completely on your own. A very exciting feature, but too drastic for our purpose...
4646- The next event happens before the background of the entire chart is drawn: `OnBeforeDrawBackground`. It, again, has the `ADoDefaultDrawing` parameter. If we set it to `false` we can replace the background drawn by the chart by our own procedures. Very good for our purpose. If left at `true` the chart will paint its standard background that we can control by the object inspector.
4747- After the background has been painted - either the default or the custom one - there is another event, `OnAfterDrawBackground`. It can be used to paint something on the background which is always underneath the series or axes which are painted later.
4848- Still in the very early stages, we have the `OnBeforeDrawBackwall` event. It fires before the area enclosed by the axis rectangle ("back wall") is filled by its background color/pattern. Taking advantage of the `ADoDefaultDrawing` parameter again we can replace the back wall by our own procedure. Exactly what we need!
4949- Then the chart paints the titles, the axes, the grid, the series, and the legend. Unfortunately, there are no events to intercept their painting (except for the legend which provides an `OnDrawLegend` event). But we do have some control of the drawing process within a group of chart elements by means of the their `ZPosition` property. If, for example, you have a chart with a bar and a line series and do not want the line series to be partly covered by the bars you have to give the line series a greater `ZPosition` than the bar series - the element with the larger `ZPosition` is drawn later, i.e. on top of the element with the smaller `ZPosition`.
5050- Only at the very end of the drawing process there is another event, `OnAfterDraw`. It is usually intended for administrative purposes, for example measuring the painting time by reading a clock that was started in the `OnChartPaint` event.
5151
5252### Showing the background image
5353
5454The Lazarus logo makes a nice background for our chart of Lazarus download counts. We want to paint it in the chart area enclosed by the axis, the back wall. As you saw above in the listing of chart event there is an event which perfectly fits our need: `OnBeforeDrawBackwall`.
5555
5656At first we have to make the logo available to the program. The logo file has the name "splash_logo.png" and can be found in the folder "images" of your Lazarus installation. For simplicity, copy it to the folder which will hold the exe file of our project (or use the full file path in the code below). Since the logo is a png file we need an instance of a TPortableNetworkGraphic, or - which is more flexible - a TPicture. Let's declare a variable `FBackImage` of the latter type to the form and add code in the form's OnCreate event to load the image file. Of course, don't forget to free the image when the program closes:
5757
5858``` pascal
5959type
6060 TForm1 = class(TForm)
6161 ...
6262 private
6363 FBackImage: TPicture;
6464 ...
6565
6666procedure TForm1.FormCreate(Sender: TObject);
6767begin
6868 FBackImage := TPicture.Create;
6969 FBackImage.LoadFromFile('splash_logo.png');
7070 // or: FBackImage.LoadFromFile('c:\lazarus\images\splash_logo.png');
7171end;
7272
7373procedure TForm1.FormDestroy(Sender: TObject);
7474begin
7575 FBackImage.Free;
7676end;
7777```
7878
7979Loading the image directly from file requires that the image is distributed along with the exe file. This can be avoided if the image is added as a resource to the binary. For this purpose you have to create a resource file containing the image - see the wiki article .... on how to do that. The Lazarus images folder contains a ready-made resource file with the logo, "splash_logo.res". If you want to use this approach, copy this file to the project folder and use this alternative `OnCreate` code (don't forget to add `{$R splash_logo.res}` at the beginning of the `implemenation` section of the unit):
8080
8181``` pascal
8282...
8383implementation
8484
8585{$R *.lfm}
8686{$R splash_logo.res}
8787
8888procedure TForm1.FormCreate(Sender: TObject);
8989begin
9090 FBackImage := TPicture.Create;
9191 FBackImage.LoadFromResourceName(HInstance, 'splash_logo');
9292end;
9393```
9494
9595Whichever method you decide to use - now it's the time to add code to the `OnBeforeDrawBackwall` event of the chart. It is very simple: we just "stretch-draw" the image on the canvas into the rectangle provided as a parameter, and - of course - we set the `ADeDefaultDrawing` to `false` to by-pass the default painting method. It is important that you draw on the canvas provided as a parameter, not on the chart's canvas, because painting of the chart is executed by special backend classes which can provide different canvases, for example, if the chart is to be printed.
9696
9797``` pascal
9898procedure TForm1.Chart1BeforeDrawBackWall(ASender: TChart; ACanvas: TCanvas;
9999 const ARect: TRect; var ADoDefaultDrawing: Boolean);
100100begin
101101 ACanvas.StretchDraw(ARect, FBackImage.Graphic);
102102 ADoDefaultDrawing := false;
103103end;
104104```
105105
106106Because the event handler is run-time code you have to compile the program to see the current state of the chart:
107107
108108![](/wiki-images/0/03/Tachart_background_backwall.png)
109109
110110### Showing a background gradient
111111
112112In the outer region of the chart we still see the famous gray background. How about replacing it by a decent gradient? From the predominant blue colors of the Lazarus logo we could select a vertical transition from bright blue (`clSkyBlue`) at the top to white a the bottom.
113113
114114We know from the summary of painting events that the `OnBeforeDrawBackground` is the right event which we can use to draw the chart background on our own. But how to paint a gradient? Very easy: the canvas has a method `GradientFill` which accepts start and end colors and the gradient direction.
115115
116116``` pascal
117117procedure TForm1.Chart1BeforeDrawBackground(ASender: TChart; ACanvas: TCanvas;
118118 const ARect: TRect; var ADoDefaultDrawing: Boolean);
119119begin
120120 ACanvas.GradientFill(ARect, clSkyBlue, clWhite, gdVertical);
121121 ADoDefaultDrawing := false;
122122end;
123123```
124124
125125![](/wiki-images/2/29/Tachart_background_backwall_gradient.png)
126126
127127### A note on Lazarus version 1.9 or newer
128128
129129If you have Lazarus version 1.9 or newer you many notice that the events `OnBeforeDrawBackground` and `OnBeforeDrawBackWall` are marked as deprecated, it is recommended to use `OnBeforeCustomDrawBackground` and `OnBeforeCustomDrawBackWall`. This was introduced because the new events support the TAChart drawing backend architecture which enables drawing on devices which do not have an LCL-compatible canvas (e.g. an OpenGL rendering context, or an SVG file). A chart drawer essentially provides the same commands as TCanvas, however, some differences exist here and there - unfortunately also in drawing of images and gradients: Images are requested as descendants of TFPCustomImage. And gradients are not supported at all, but may be provided after painting on an auxiliary bitmap.
130130
131131Here is the code which works for the `OnBeforeCustomDrawBackWall` event:
132132
133133``` pascal
134134procedure TForm1.Chart1BeforeCustomDrawBackWall(ASender: TChart;
135135 ADrawer: IChartDrawer; const ARect: TRect; var ADoDefaultDrawing: Boolean);
136136var
137137 bmp: TBitmap;
138138 img: TLazIntfImage;
139139begin
140140 img := TLazIntfImage.Create(0, 0);
141141 try
142142 bmp := TBitmap.Create;
143143 try
144144 bmp.SetSize(ARect.Right - ARect.Left, ARect.Bottom - ARect.Top);
145145 bmp.Canvas.StretchDraw(Rect(0, 0, bmp.Width, bmp.Height), FBackImage.Graphic);
146146 img.LoadFromBitmap(bmp.Handle, bmp.MaskHandle);
147147 finally
148148 bmp.Free;
149149 end;
150150 ADrawer.PutImage(ARect.Left, ARect.Top, img);
151151 ADoDefaultDrawing := false;
152152 finally
153153 img.Free;
154154 end;
155155end;
156156```
157157
158158The image painting command of the drawer is `PutImage`. It gets the image as a `TLazIntfImage` which, unlike TBitmap, is a descendant of TFPCustomImage. The originally loaded picture is stretch-drawn onto an auxiliary bitmap because the drawer does not support this operation. This auxiliary bitmap, finally is passed to the `LazIntfImage` via its method `LoadFromBitmap`.
159159
160160The `OnBeforeCustomDrawBackground` event handler follows the same principle. But now the auxiliary bitmap is painted with the gradient requested.
161161
162162``` pascal
163163procedure TForm1.Chart1BeforeCustomDrawBackground(ASender: TChart;
164164 ADrawer: IChartDrawer; const ARect: TRect; var ADoDefaultDrawing: Boolean);
165165var
166166 bmp: TBitmap;
167167 img: TLazIntfImage;
168168begin
169169 img := TLazIntfImage.Create(0, 0);
170170 try
171171 bmp := TBitmap.Create;
172172 try
173173 bmp.SetSize(ARect.Right - ARect.Left, ARect.Bottom - ARect.Top);
174174 bmp.Canvas.GradientFill(Rect(0, 0, bmp.Width, bmp.Height), clSkyBlue, clWhite, gdVertical);
175175 img.LoadFromBitmap(bmp.Handle, bmp.MaskHandle);
176176 finally
177177 bmp.Free;
178178 end;
179179 ADrawer.PutImage(ARect.Left, ARect.Top, img);
180180 ADoDefaultDrawing := false;
181181 finally
182182 img.Free;
183183 end;
184184end;
185185```
186186
187187### Finishing up
188188
189189The red bars of the series look a bit "aggressive". How about a more decent sky-blue again? Select the bar series in the object inspector and set the `SeriesColor` to `clSkyBlue`.
190190
191191And it would be better to see more of the Lazarus cheetah which is largely covered by the bars. The bar series has a property `Transparency`. The default value, `0`, means opaque, the maximum allowable value, `255` means fully transparent. A low value, such as `64`, adds some transparency to show more details of the cheetah, but does not push the bars too much in the background.
192192
193193And this brings us to the chart which is displayed at the top of this page, and to the end of this tutorial.
194194
195195## Summary
196196
197197These are the basic steps for **user-painting of chart backgrounds**:
198198
199199- Use the **`OnBeforeDrawBackWall` event** to provide your own painting procedure for the **area enclosed by the axis rectangle**.
200200- Similarly, the **`OnBeforeDrawBackground` event** for painting of the entire **chart background**.
201201- In these drawing procedures set **`ADoDefaultDrawing`** to **`false`** in order to call your own painting procedures.
202202
203203## Source code
204204
205205The source code of this tutorial project can be found in the folder *tutorials/background* of trunk TAChart installations.
206206
207207### Project file
208208
209209``` pascal
210210program backimage;
211211
212212{$mode objfpc}{$H+}
213213
214214uses
215215 {$IFDEF UNIX}{$IFDEF UseCThreads}
216216 cthreads,
217217 {$ENDIF}{$ENDIF}
218218 Interfaces, // this includes the LCL widgetset
219219 Forms, main, tachartlazaruspkg
220220 { you can add units after this };
221221
222222{$R *.res}
223223
224224begin
225225 RequireDerivedFormResource := True;
226226 Application.Initialize;
227227 Application.CreateForm(TForm1, Form1);
228228 Application.Run;
229229end.
230230```
231231
232232### Unit `main.pas`
233233
234234``` pascal
235235unit main;
236236{$mode objfpc}{$H+}
237237
238238interface
239239
240240uses
241241 Classes, SysUtils, FileUtil, TAGraph, TASeries, TASources, Forms, Controls,
242242 Graphics, Dialogs;
243243
244244type
245245
246246 { TForm1 }
247247
248248 TForm1 = class(TForm)
249249 Chart1: TChart;
250250 Chart1BarSeries1: TBarSeries;
251251 ListChartSource1: TListChartSource;
252252 procedure Chart1BeforeDrawBackground(ASender: TChart; ACanvas: TCanvas;
253253 const ARect: TRect; var ADoDefaultDrawing: Boolean);
254254 procedure Chart1BeforeDrawBackWall(ASender: TChart; ACanvas: TCanvas;
255255 const ARect: TRect; var ADoDefaultDrawing: Boolean);
256256 procedure FormCreate(Sender: TObject);
257257 procedure FormDestroy(Sender: TObject);
258258 private
259259 { private declarations }
260260 FBackImage: TPicture;
261261 public
262262 { public declarations }
263263 end;
264264
265265var
266266 Form1: TForm1;
267267
268268implementation
269269
270270{$R *.lfm}
271271
272272//{$R splash_logo.res}
273273
274274{ TForm1 }
275275
276276procedure TForm1.FormCreate(Sender: TObject);
277277begin
278278 FBackImage := TPicture.Create;
279279
280280 FBackImage.LoadFromFile('splash_logo.png');
281281 // this assumes that the logo is in the same folder as the binary
282282
283283 // or, using resources:
284284 //FBackImage.LoadFromResourceName(HInstance, 'splash_logo');
285285 // Don't forget ths {$R directive above...
286286end;
287287
288288procedure TForm1.Chart1BeforeDrawBackWall(ASender: TChart; ACanvas: TCanvas;
289289 const ARect: TRect; var ADoDefaultDrawing: Boolean);
290290begin
291291 ACanvas.StretchDraw(ARect, FBackImage.Graphic);
292292 ADoDefaultDrawing := false;
293293end;
294294
295295procedure TForm1.Chart1BeforeDrawBackground(ASender: TChart; ACanvas: TCanvas;
296296 const ARect: TRect; var ADoDefaultDrawing: Boolean);
297297begin
298298 ACanvas.GradientFill(ARect, clSkyBlue, clWhite, gdVertical);
299299 ADoDefaultDrawing := false;
300300end;
301301
302302procedure TForm1.FormDestroy(Sender: TObject);
303303begin
304304 FBackImage.Free;
305305end;
306306
307307end.
308308```
309309
310310### Form file `main.lfm`
311311
312-``` pascal
312+``` lfm
313313object Form1: TForm1
314314 Left = 340
315315 Height = 300
316316 Top = 154
317317 Width = 400
318318 Caption = 'Form1'
319319 ClientHeight = 300
320320 ClientWidth = 400
321321 OnCreate = FormCreate
322322 OnDestroy = FormDestroy
323323 LCLVersion = '1.3'
324324 object Chart1: TChart
325325 Left = 0
326326 Height = 300
327327 Top = 0
328328 Width = 400
329329 AxisList = <
330330 item
331331 Grid.Color = clSkyBlue
332332 Grid.Visible = False
333333 Marks.Format = '%.0n'
334334 Marks.Style = smsCustom
335335 Minors = <>
336336 Title.LabelFont.Height = -16
337337 Title.LabelFont.Orientation = 900
338338 Title.LabelFont.Style = [fsBold]
339339 Title.Visible = True
340340 Title.Caption = 'Downloads per year'
341341 end
342342 item
343343 Grid.Visible = False
344344 Alignment = calBottom
345345 Minors = <>
346346 Title.LabelFont.Height = -16
347347 Title.LabelFont.Style = [fsBold]
348348 Title.Visible = True
349349 Title.Caption = 'Year'
350350 end>
351351 Foot.Brush.Color = clBtnFace
352352 Foot.Font.Color = clBlue
353353 Title.Brush.Color = clBtnFace
354354 Title.Brush.Style = bsClear
355355 Title.Font.Color = clNavy
356356 Title.Font.Height = -17
357357 Title.Font.Style = [fsBold]
358358 Title.Text.Strings = (
359359 'Lazarus Downloads'
360360 )
361361 Title.Visible = True
362362 OnBeforeDrawBackground = Chart1BeforeDrawBackground
363363 OnBeforeDrawBackWall = Chart1BeforeDrawBackWall
364364 Align = alClient
365365 ParentColor = False
366366 object Chart1BarSeries1: TBarSeries
367367 Shadow.Color = clNavy
368368 Shadow.OffsetX = 4
369369 BarBrush.Color = clSkyBlue
370370 Source = ListChartSource1
371371 end
372372 end
373373 object ListChartSource1: TListChartSource
374374 DataPoints.Strings = (
375375 '2005|53299|?|'
376376 '2006|119613|?|'
377377 '2007|158060|?|'
378378 '2008|218915|?|'
379379 '2009|190567|?|'
380380 '2010|230108|?|'
381381 '2011|267858|?|'
382382 '2012|298335|?|'
383383 '2013|280586|?|'
384384 )
385385 left = 120
386386 top = 56
387387 end
388388end
389389```
390390
391391---
392392*From the Free Pascal wiki, page "TAChart Tutorial: Background design", as it stood on 2020-02-28 after a change by Trev, 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).*

Back to history