TAChart Tutorial: Background design

From Free Pascal wiki
Jump to navigationJump to search

Introduction

tachart background finished.png

Standard 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 encloses by the axes, the so-called "back wall". In any case, the design of quite conventional compared to other charting packages and applications. Is is possible to give the chart background a more interesting design?

In 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 of the evolution of the Lazarus downloads during the previous years. To highlight the connection with Lazarus we plan to show the Lazarus splash screen as a background image of the chart.

Preparation

Data

Sourceforge 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:

Year 2005 2006 2007 2008 2009 2010 2011 2012 2013
Downloads 53299 119613 158060 218915 190567 230108 267858 298335 280586

These data will be basis of our chart.

Setting up the chart

Create 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.

We 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".

We 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.

tachart background no data.png tachart background datapoints editor.png

A 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.

There 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.

Finally, 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.

tachart background first version.png

The TChart painting events

So far, the project has been standard. Now let's intercept the drawing process...

TChart offers several events where our own code can be hooked in:

  • 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...
  • 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.
  • 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.
  • 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!
  • Then the chart paints the titles, the axes, the grid, the series, and the legend. Unfortunately, there are no events to intercept their painting. But we do have some control on the drawing process by means of the ZPosition properties of most chart elements. 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.
  • 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.

Showing the background image

The Lazarus logo makes a nice background for our chart of Lazarus downloads. 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.

At 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:

type
  TForm1 = class(TForm)
  ...
  private
    FBackImage: TPicture;
  ...

procedure TForm1.FormCreate(Sender: TObject);
begin
  FBackImage := TPicture.Create;
  FBackImage.LoadFromFile('splash_logo.png');
  // or: FBackImage.LoadFromFile('c:\lazarus\images\splash_logo.png');
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FBackImage.Free;
end;

Loading 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):

...
implementation

{$R *.lfm}
{$R splash_logo.res}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FBackImage := TPicture.Create;
  FBackImage.LoadFromResourceName(HInstance, 'splash_logo');
end;

Whichever 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.

procedure TForm1.Chart1BeforeDrawBackWall(ASender: TChart; ACanvas: TCanvas;
  const ARect: TRect; var ADoDefaultDrawing: Boolean);
begin
  ACanvas.StretchDraw(ARect, FBackImage.Graphic);
  ADoDefaultDrawing := false;
end;

Because the event handler is run-time code you have to compile the program to see the current state of the chart:

tachart background backwall.png

Showing a background gradient