Difference between revisions of "TAChart Tutorial: Function Series"

From Lazarus wiki
Jump to navigationJump to search
(Created page with "*** UNDER CONSTRUCTION *** == Introduction == In this tutorial, we want to learn how to draw mathematical functions by means of [[TAChart_documentation#FunctionSeries|<code>...")
 
Line 9: Line 9:
 
* '''Align''': <code>alClient</code>
 
* '''Align''': <code>alClient</code>
 
* '''BackColor''': <code>clWhite</code>
 
* '''BackColor''': <code>clWhite</code>
* '''Title''': ''Text'': 'Mathematical functions', ''Visible'': <code>true</code>, ''Font.Style'': <code>fsBold</code>
 
 
* '''BottomAxis''': ''Grid.Color'': <code>clSilver</code>, ''Title.Caption'': 'x', ''Title.Visible'': <code>true</code>, ''Title.LabelFont.Style'': <code>fsBold</code>
 
* '''BottomAxis''': ''Grid.Color'': <code>clSilver</code>, ''Title.Caption'': 'x', ''Title.Visible'': <code>true</code>, ''Title.LabelFont.Style'': <code>fsBold</code>
 
* '''LeftAxis''': ''Grid.Color'': <code>clSilver</code>, ''Title.Caption'': 'y', ''Title.Visible'': <code>true</code>, ''Title.LabelFont.Style'': <code>fsBold</code>
 
* '''LeftAxis''': ''Grid.Color'': <code>clSilver</code>, ''Title.Caption'': 'y', ''Title.Visible'': <code>true</code>, ''Title.LabelFont.Style'': <code>fsBold</code>
Line 17: Line 16:
 
[[File:FuncSeries1.png]]
 
[[File:FuncSeries1.png]]
  
=== Adding a TFuncSeries ===
+
== Adding a TFuncSeries ==
 
At first, let's draw a sine function, y = sin(x). We double-click on the chart, the series editor opens ("Edit series"). Click on "Add" and select "Function series" from the drop-down list. This chart will display a line going from the left-bottom to the right-top corner as a representative of the FuncSeries.  
 
At first, let's draw a sine function, y = sin(x). We double-click on the chart, the series editor opens ("Edit series"). Click on "Add" and select "Function series" from the drop-down list. This chart will display a line going from the left-bottom to the right-top corner as a representative of the FuncSeries.  
  
Since we will plot the sine function, we rename the series as "SinFuncSeries". And we could give it a red color. FuncSeries don't have a <code>SeriesColor</code> property, but we can use the property <code>Pen</code> for our purpose: Set <code>Pen.Color</code> to <code>clRed</code>.
+
We could give the series a red color. FuncSeries don't have a <code>SeriesColor</code> property, but we can use the property <code>Pen</code> for our purpose: Set <code>Pen.Color</code> to <code>clRed</code>.
  
 
Now we go to the page "Events" of the object inspector, and double-click on the event "OnCalculate". This is the place where we define the function. <code>OnCalculate</code> is called whenever the series needs the y value for a given x value:  
 
Now we go to the page "Events" of the object inspector, and double-click on the event "OnCalculate". This is the place where we define the function. <code>OnCalculate</code> is called whenever the series needs the y value for a given x value:  
  
 
<source>
 
<source>
procedure TForm1.SinFuncSeriesCalculate(const AX: Double; out AY: Double);
+
procedure TForm1.Chart1FuncSeries1Calculate(const AX: Double; out AY: Double);
 
begin
 
begin
 
   AY := sin(AX);
 
   AY := sin(AX);
Line 31: Line 30:
 
</source>
 
</source>
  
 +
[[File:FuncSeries2.png]]
 +
 +
When we compile we'll see the sine funtion. But it is not the full wave, it runs only between -1 and +1 because we did not set up the axes - this is the default for an empty x axis extent.
 +
 +
== Setting the extent ==
 +
 +
=== x extent ===
 +
To get a wider axis range we have two options: we can set the extent of the series or the extent of the chart. There are subtle differences between both cases, in our simple chart, however, they won't show up, and therefore, we will discuss them at another place. So go to the Object Inspector, select the series' property <code>Extent</code>, set the axis start at XMin to, say, -10, and the axis end at XMax to +10. Activate these axis limits by setting UseXMax and UseXMin to true. Now when you recompile, you'll see the sine function between -10 and 10.
 +
 +
[[File:FuncSeries3.png]]
 +
 +
=== y extent ===
 +
Why don't we play with the function a bit to see what happens? Go to the <code>OnCalculate</code> event handler and multiply the sine function by 2:
 +
 +
<source>
 +
procedure TForm1.Chart1FuncSeries1Calculate(const AX: Double; out AY: Double);
 +
begin
 +
  AY := 2*sin(AX);
 +
end; 
 +
</source>
 +
 +
[[File:FuncSeries4.png]]
 +
 +
Oh - the function series does not automatically update the y extent! This is true for the release version of Lazarus v1.0. In the trunk version, however, the <code>FuncSeries</code> has a new property <code>ExtentAutoY</code> which, if set to <code>true</code>, enforces automatic calculation of the y extent. Please note that the property applies only if both <code>Extent.UseXMin</code> and <code>Extent.UseXMax</code> are <code>true</code>.
 +
 +
If you don't have the trunc version of Lazarus you have to set the extent manually: Select the series <code>Extent</code> again, set <code>YMin</code> to -5, <code>YMax</code> to 5, and <code>UseYMin</code> and <code>UseYMax</code> to <code>true</code>. This will show us the full sine curve with amplitude 2. In this example the y extent is wider than needed because it prepares our chart for one of the next exercises.
 +
 +
[[File:FuncSeries5.png]]
 +
 +
== Zooming ==
 +
 +
== Domain exclusions ==
 +
 +
=== Plotting y = tan(x) ===
 +
In this exercise we want to plot a different function, y = tan(x). We can easily adapt our project to this function by adapting the <code>OnCalculate</code> event handler:
 +
 +
<source>
 +
procedure TForm1.Chart1FuncSeries1Calculate(const AX: Double; out AY: Double);
 +
begin
 +
  AY := tan(AX);
 +
end;
 +
</source>
 +
 +
[[File:FuncSeries6.png]]
 +
 +
The chart looks fine at first sight, but when you remember some basics of the tan function from school you'll see that the (almost) vertical lines near +/-1.6, +/-4.7, +/-7.9. They occur because the function is not defined at these locations (to be exact at +/-(2*n+pi)/2, with n an integer), but the series somehow must be drawn from the positive excursion before, to the negative excursion after these discontinuities.
 +
 +
<code>TFuncSeries</code> provides so-called <code>DomainExclusions</code> to overcome this issue. These are points and regions at which the function is not calculated and not drawn. Presently, <code>DomainExclusions</code> do not appear in the Object Inspector, but must be assigned in code at runtime by calling their methods <code>AddPoint</code> or <code>AddRange</code>.
 +
 +
In case of the tan function, we add the following code to the form's OnCreate event handler in which we exclude above-mentioned points from the calculation:
 +
 +
<source>
 +
procedure TForm1.FormCreate(Sender: TObject);
 +
begin
 +
  with Chart1FuncSeries1.DomainExclusions do begin
 +
    AddPoint(pi/2);      AddPoint(-pi/2);
 +
    AddPoint(3*pi/2);    AddPoint(-3*pi/2);
 +
    AddPoint(5*pi/2);    AddPoint(-5*pi/2);
 +
  end;
 +
end;
 +
</source>
 +
 +
This chart, now, is perfect.
 +
 +
[[File:FuncSeries7.png]]
 +
 +
=== Plotting y = ln(x) ===
 +
In the last exercise, we add another function, y = ln(x). For this, double-click on the chart again, and in the series editor add another function series. Set its color to <code>clBlue</code>, and write the following <code>OnCalculate</code> event handler which tells the series to plot a log function:
 +
 +
<source>
 +
procedure TForm1.Chart1FuncSeries2Calculate(const AX: Double; out AY: Double);
 +
begin
 +
  AY := ln(AX);
 +
end;   
 +
</source>
  
[[File:FuncSeries2.png]]
+
[[File:FuncSeries8.png]]
 +
 
 +
But when we run the program it crashes because of a floating point exception! Where does that come from? Our x axis starts at -10, and the logarithmic function can be calculated only for positive x values. What can be done against that? The answer is domain exclusions, again. We just forbid calculation of the function for negative values and for x=0. For this purpose, modify the form's <code>OnCreate</code> event handler as follows:
 +
 
 +
<source>
 +
procedure TForm1.FormCreate(Sender: TObject);
 +
begin
 +
  with Chart1FuncSeries1.DomainExclusions do begin
 +
    AddPoint(pi/2);      AddPoint(-pi/2);
 +
    AddPoint(3*pi/2);    AddPoint(-3*pi/2);
 +
    AddPoint(5*pi/2);    AddPoint(-5*pi/2);
 +
  end;
 +
  with Chart1FuncSeries2.DomainExclusions do begin
 +
    AddRange(NegInfinity, 0);
 +
    AddPoint(0);
 +
  end;
 +
end; 
 +
</source>
 +
 
 +
Now the program runs fine.
  
When we compile we'll see the sine funtion. Since we did not yet set up the axes the function runs only between -1 and +1 which is the default value for an empty x axis extent.
+
[[File:FuncSeries9.png]]

Revision as of 01:01, 24 September 2012

      • UNDER CONSTRUCTION ***

Introduction

In this tutorial, we want to learn how to draw mathematical functions by means of TFuncSeries. This is a series type that - at first sight - looks like an ordinary line series. But in the background, it is completely different. It does not get its data from a ChartSource, but from a mathematical function. Whenever the series needs data it calls the event handler for OnCalculate where the user can pass the function values for an x value requested. This saves memory for storage of the function values. But most of all, it allows to calculate the function values, depending on zooming level and chart size, at sufficiently narrow intervals, such that the series curve is smooth even at high magnifications.

Preparation

Let us start a new project with a standard TChart component on it. Do any modifications to its properties that you want to. I'll be using here the following settings:

  • Align: alClient
  • BackColor: clWhite
  • BottomAxis: Grid.Color: clSilver, Title.Caption: 'x', Title.Visible: true, Title.LabelFont.Style: fsBold
  • LeftAxis: Grid.Color: clSilver, Title.Caption: 'y', Title.Visible: true, Title.LabelFont.Style: fsBold

The resulting form is displayed in the following image on the left:

FuncSeries1.png

Adding a TFuncSeries

At first, let's draw a sine function, y = sin(x). We double-click on the chart, the series editor opens ("Edit series"). Click on "Add" and select "Function series" from the drop-down list. This chart will display a line going from the left-bottom to the right-top corner as a representative of the FuncSeries.

We could give the series a red color. FuncSeries don't have a SeriesColor property, but we can use the property Pen for our purpose: Set Pen.Color to clRed.

Now we go to the page "Events" of the object inspector, and double-click on the event "OnCalculate". This is the place where we define the function. OnCalculate is called whenever the series needs the y value for a given x value:

procedure TForm1.Chart1FuncSeries1Calculate(const AX: Double; out AY: Double);
begin
  AY := sin(AX);
end;

FuncSeries2.png

When we compile we'll see the sine funtion. But it is not the full wave, it runs only between -1 and +1 because we did not set up the axes - this is the default for an empty x axis extent.

Setting the extent

x extent

To get a wider axis range we have two options: we can set the extent of the series or the extent of the chart. There are subtle differences between both cases, in our simple chart, however, they won't show up, and therefore, we will discuss them at another place. So go to the Object Inspector, select the series' property Extent, set the axis start at XMin to, say, -10, and the axis end at XMax to +10. Activate these axis limits by setting UseXMax and UseXMin to true. Now when you recompile, you'll see the sine function between -10 and 10.

FuncSeries3.png

y extent

Why don't we play with the function a bit to see what happens? Go to the OnCalculate event handler and multiply the sine function by 2:

procedure TForm1.Chart1FuncSeries1Calculate(const AX: Double; out AY: Double);
begin
  AY := 2*sin(AX);
end;

FuncSeries4.png

Oh - the function series does not automatically update the y extent! This is true for the release version of Lazarus v1.0. In the trunk version, however, the FuncSeries has a new property ExtentAutoY which, if set to true, enforces automatic calculation of the y extent. Please note that the property applies only if both Extent.UseXMin and Extent.UseXMax are true.

If you don't have the trunc version of Lazarus you have to set the extent manually: Select the series Extent again, set YMin to -5, YMax to 5, and UseYMin and UseYMax to true. This will show us the full sine curve with amplitude 2. In this example the y extent is wider than needed because it prepares our chart for one of the next exercises.

FuncSeries5.png

Zooming

Domain exclusions

Plotting y = tan(x)

In this exercise we want to plot a different function, y = tan(x). We can easily adapt our project to this function by adapting the OnCalculate event handler:

procedure TForm1.Chart1FuncSeries1Calculate(const AX: Double; out AY: Double);
begin
  AY := tan(AX);
end;

FuncSeries6.png

The chart looks fine at first sight, but when you remember some basics of the tan function from school you'll see that the (almost) vertical lines near +/-1.6, +/-4.7, +/-7.9. They occur because the function is not defined at these locations (to be exact at +/-(2*n+pi)/2, with n an integer), but the series somehow must be drawn from the positive excursion before, to the negative excursion after these discontinuities.

TFuncSeries provides so-called DomainExclusions to overcome this issue. These are points and regions at which the function is not calculated and not drawn. Presently, DomainExclusions do not appear in the Object Inspector, but must be assigned in code at runtime by calling their methods AddPoint or AddRange.

In case of the tan function, we add the following code to the form's OnCreate event handler in which we exclude above-mentioned points from the calculation:

procedure TForm1.FormCreate(Sender: TObject);
begin
  with Chart1FuncSeries1.DomainExclusions do begin
    AddPoint(pi/2);      AddPoint(-pi/2);
    AddPoint(3*pi/2);    AddPoint(-3*pi/2);
    AddPoint(5*pi/2);    AddPoint(-5*pi/2);
  end;
end;

This chart, now, is perfect.

FuncSeries7.png

Plotting y = ln(x)

In the last exercise, we add another function, y = ln(x). For this, double-click on the chart again, and in the series editor add another function series. Set its color to clBlue, and write the following OnCalculate event handler which tells the series to plot a log function:

procedure TForm1.Chart1FuncSeries2Calculate(const AX: Double; out AY: Double);
begin
  AY := ln(AX);
end;

FuncSeries8.png

But when we run the program it crashes because of a floating point exception! Where does that come from? Our x axis starts at -10, and the logarithmic function can be calculated only for positive x values. What can be done against that? The answer is domain exclusions, again. We just forbid calculation of the function for negative values and for x=0. For this purpose, modify the form's OnCreate event handler as follows:

procedure TForm1.FormCreate(Sender: TObject);
begin
  with Chart1FuncSeries1.DomainExclusions do begin
    AddPoint(pi/2);      AddPoint(-pi/2);
    AddPoint(3*pi/2);    AddPoint(-3*pi/2);
    AddPoint(5*pi/2);    AddPoint(-5*pi/2);
  end;
  with Chart1FuncSeries2.DomainExclusions do begin
    AddRange(NegInfinity, 0);
    AddPoint(0);
  end;
end;

Now the program runs fine.

FuncSeries9.png