Tutorial 7 - Working with Functions
Contents

Function Types
     Function characteristics
     Adding a Function
     Defining a datasource
     Function Period
     Period Style
     Deriving Custom Functions

Function Types

Function characteristics
A TeeChart Pro Function is a Series, that can be of almost any Series Type, to which an algebraic function is applied and for which the datasource is another Chart Series.

All Functions derive from the Function Class in the Steema.TeeChart.Functions namespace and inherit Function's Period property. TeeChart Pro offers following list of predefined functions:



Several Function types support only one input Series. However it is possible to chain link Functions, thus, for example, taking the average of several Series in your Chart to create an Average Function Series, then identify the Trend of the average by using the Average Function as the input to the Trend Function.

Adding a Function
With the TeeChart Editor, on the First Chart page, select the Add button as if to add a new Series to the Chart. In the TeeChart Gallery choose the Functions tab to select the Function you require. Each Function is presented as a Line Series, you may change the Series Type associated with the Function later by choosing the Change button on the first Chart Page. Function definitions are easily changed afterwards on the Datasource page of the Function Series. Here, just as easily, you may change the definition of a normal Series that you have added to the Chart to that of a Function (Function is really a definition of datasource, not a definition of Series Type).

The image below shows the Datasource page when editing a Function. The Line Series (Title "line2") is defined. The left listbox at the bottom of the Datasource page shows other Series in the Chart available for input (here "line1").



Assuming we start with a completely empty Chart here are the steps in code to build a simple Series-Function related Chart.
[C#] 
private void Form1_Load(object sender, System.EventArgs e)


            //Add a data Series
            Line line1 = new Line(tChart1.Chart);

            //Populate it with data (here random)
            line1.FillSampleValues(10);

            //Add a series to be used for an Average Function
            Line line2 = new Line(tChart1.Chart);

            //Define the Function Type for the new Series
            Steema.TeeChart.Functions.Average average1 = new Steema.TeeChart.Functions.Average();
            line2.Function = average1;

            //Define the Datasource for the new Function Series
            line2.DataSource = line1;

            //*Note - When populating your input Series manually you will need to  
            //use the Checkdatasource method  
            //- See the section entitled 'Defining a Datasource'
            //Change the Period of the Function so that it groups averages
            //every 2 Points
            line2.Function.Period = 2;
            line2.CheckDataSource();
        

[VB.Net]
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'Add a data Series
        Dim Line1 As New Steema.TeeChart.Styles.Line(TChart1.Chart)
        
        'Populate it with data (here random)
        Line1.FillSampleValues(10)

        'Add a series to be used for an Average Function
        Dim Line2 As New Steema.TeeChart.LineSeries(TChart1.Chart)

        'Define the Function Type for the new Series
        Dim Average1 As New Steema.TeeChart.Functions.Average()
        Line2.Function = Average1

        'Define the Datasource for the new Function Series
        Line2.DataSource = Line1

        '*Note - When populating your input Series manually you will need to  
        'use the Checkdatasource method  
        '- See the section entitled 'Defining a Datasource'
        'Change the Period of the Function so that it groups averages
        'every 2 Points
        Line2.Function.Period = 2
        Line2.CheckDataSource()
End Sub

We can add another Function to tell us something about the previous Function
[C#] 
private void button1_Click(object sender, System.EventArgs e)
        
            //Let's change to 2D for visibility
            tChart1.Aspect.View3D = false;
            //Add another Series to be used for a 2nd Function  
            Line line3 = new Line(tChart1.Chart);
            //Define the Function Type for the new Series  
            Steema.TeeChart.Functions.High high1 = new Steema.TeeChart.Functions.High();
            line3.Function = high1;
            //Define the Datasource for the new Function Series  
            //Use the existing Function (Series2) as input  
            line3.DataSource = tChart1.Series[1];
            //Leave the Period at default 0 (No Period set) to draw  
            //A line at Highest of all points of the Average Function  
        

[VB.Net]
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        'Let's change to 2D for visibility
        TChart1.Aspect.View3D = False
        'Add another Series to be used for a 2nd Function  
        Dim Line3 As New Steema.TeeChart.LineSeries(TChart1.Chart)
        'Define the Function Type for the new Series  
        Dim High1 As New Steema.TeeChart.Functions.High()
        Line3.Function = High1
        'Define the Datasource for the new Function Series  
        'Use the existing Function (Series2) as input  
        Line3.DataSource = TChart1.Series(1)
        'Leave the Period at default 0 (No Period set) to draw  
        'A line at Highest of all points of the Average Function  
End Sub

Defining a datasource
The examples in the previous section highlight the use of Datasource for populating a Function by code. Series use datasource for defining the input for a Function or to define a Series ADO.NET datasource (see the Tutorial about accessing databases).

Using the TeeChart Editor, after adding a Function, the Function Series' Datasource page will show a list of available series for inclusion in the function definition. Here you may change the Function Type you wish to apply to the Series and select Series from the Left listBox "Available" and add them to the right Listbox,"Selected".

Datasource by code uses the Series.Datasource property.

Example
Suppose we have 2 data Series in a Chart added in at design-time via the TeeChart Editor. We add a Function composed of the average of the 2 Series:
[C#] 
private void Form1_Load(object sender, System.EventArgs e)
        
            tChart1.Aspect.View3D = false;
            bar1.FillSampleValues(10);
            bar2.FillSampleValues(10);
        

        private void button1_Click(object sender, System.EventArgs e)
        
            Steema.TeeChart.Styles.Line line1 = new Steema.TeeChart.Styles.Line(tChart1.Chart);
            Steema.TeeChart.Functions.Average average1 = new Steema.TeeChart.Functions.Average();  
            line1.DataSource = new object[]  this.bar2,this.bar1;
            line1.Function = average1;
            line1.Marks.Visible = true;
        


[VB.Net]
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        TChart1.Aspect.View3D = False
        Bar1.FillSampleValues(10)
        Bar2.FillSampleValues(10)
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim DataSource As New ArrayList()

        DataSource.Add(Bar1)
        DataSource.Add(Bar2)

        Dim Line1 As New Steema.TeeChart.Styles.Line(TChart1.Chart)
        Dim Average1 As New Steema.TeeChart.Functions.Average()

        Line1.Function = Average1
        Line1.DataSource = DataSource
End Sub

We add points to the 2 Series:
[C#] 
private void button2_Click(object sender, System.EventArgs e)
        
            Random rnd = new Random();
            for(int i = 0; i < 10; ++i)
            
                bar1.Add(rnd.Next(500));
                bar2.Add(rnd.Next(500));
            
        

[VB.Net]
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim rnd As New Random()
        Dim i As Integer
        For i = 0 To 10
            Bar1.Add(rnd.Next(500))
            Bar2.Add(rnd.Next(500))
        Next
End Sub

Notice that the Function doesn't display. You need to add the Series.CheckDatasource method the the Button2_Click() event to read in values for the Function.
[C#] 
tChart1.Series[2].CheckDataSource();
[VB.Net]
TChart1.Series(2).CheckDataSource()

Function definitions may be changed at runtime to allocate a new Function to the Series simply by redefining the Series.DataSource property:
[C#] 
private void button3_Click(object sender, System.EventArgs e)
        
            Steema.TeeChart.Functions.Cumulative cumulative1 = new Steema.TeeChart.Functions.Cumulative();
            tChart1.Series[2].Function = cumulative1;
        

[VB.Net]
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
        Dim Cumulative1 As New Steema.TeeChart.Functions.Cumulative()
        TChart1.Series(2).Function = Cumulative1
End Sub


Function Period
Period is an important property for working with Functions as the Period defines the range of points across which a Function is cyclically applied.
Example

We have 6 data points (e.g. bars of a Bar Series) with values:

     3, 8, 6, 2, 9 and 12  

We define a Function Series with Period 0 (default) the average drawn is:

     6.667

With Period set to 2 we get 3 values of average as output from the function:

     5.5, 4 and 10.5

These values will plot centrally in their period range, i.e. The 1st  value between bars 1 and 2 of the input series, 2nd value between bars 3 and 4, etc..

You may define Period by selecting the relevant Series and Function in the Datasource page and clicking the "Options" tab or you may modify Period at runtime using FunctionType.

e.g. Where line1 is the function series:

[C#] 
line1.Function.Period = 2;
[VB.Net]
Line1.Function.Period = 2

Below are 2 Charts that highlight the effect of an applied Period



Period Style
Period can be defined to be a range. This is very useful when using DateTime series and we want to express the "Period" of the function as a TimeStep. The property "PeriodStyle" controls how is "Period" expressed.

For example you can now plot the "monthly average of sales" function just using a normal "Average" function on a date-time source series and setting the function period to "one month":

[C#] 
private void Form1_Load(object sender, System.EventArgs e)
            //Add in a BarSeries and Average Function at design-time.
            Random rnd = new Random();
            tChart1.Aspect.View3D = false;  
            
            TimeSpan month = new TimeSpan(30,0,0,0);
            DateTime today = DateTime.Today;

            bar1.Marks.Visible = false;
            bar1.XValues.DateTime = true;
            tChart1.Axes.Bottom.Labels.Angle = 90;

            for(int i = 0; i < 60; ++i)
                today = today.AddDays(5);
                bar1.Add(today, rnd.Next(100),"",Color.Red);
            

            average1.PeriodAlign = Steema.TeeChart.Functions.PeriodAligns.First;
            average1.PeriodStyle = Steema.TeeChart.Functions.PeriodStyles.Range;
            average1.Period = month.TotalDays;
            line1.DataSource = bar1;
            line1.CheckDataSource();
        

[VB.Net]
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'Add in a BarSeries and Average Function at design-time.
        TChart1.Aspect.View3D = False

        Dim Month As New TimeSpan(30, 0, 0, 0)
        Dim Today As DateTime = DateTime.Today
        Dim i As Integer

        Bar1.Marks.Visible = False
        Bar1.XValues.DateTime = True
        TChart1.Axes.Bottom.Labels.Angle = 90

        For i = 0 To 60
            Today = Today.AddDays(5)
            Bar1.Add(Today, Rnd() * 100, "", Color.Red)
        Next

        Average1.PeriodAlign = Steema.TeeChart.Functions.PeriodAligns.First
        Average1.PeriodStyle = Steema.TeeChart.Functions.PeriodStyles.Range
        Average1.Period = Month.TotalDays
        Line1.DataSource = Bar1
        Line1.CheckDataSource()
End Sub

This will result in several points, each one showing the "average" of each month of data in the BarSeries.

It's mandatory that points in the source Series should be sorted by date when calculating functions on datetime periods.

The range can also be used for non-datetime series:

[C#] 
for(int i = 0; i < 60; ++i)
     bar1.Add(Convert.ToDouble(i), rnd.Next(100),"",Color.Red);

average1.PeriodAlign = Steema.TeeChart.Functions.PeriodAligns.First;
average1.PeriodStyle = Steema.TeeChart.Functions.PeriodStyles.Range;
average1.Period = 6;

[VB.Net]
For i = 0 To 60
            Bar1.Add(i, Rnd() * 100, "", Color.Red)
Next
Average1.PeriodAlign = Steema.TeeChart.Functions.PeriodAligns.First
Average1.PeriodStyle = Steema.TeeChart.Functions.PeriodStyles.Range
Average1.Period = 6

This will calculate an average for each group of points inside every "6" interval.
(Points with X >=6, X<6 will be used to calculate the first average, points with X >=6, X<12 will be used to calculate the second average and so on... ).
Notice this is different than calculating an average for every 6 points.

Use the Period Alignment property to align the function Points within the Series range. The following will plot the Function point at the end of a monthly Period:

[C#] 
average1.PeriodAlign = Steema.TeeChart.Functions.PeriodAligns.First;
average1.PeriodStyle = Steema.TeeChart.Functions.PeriodStyles.Range;
average1.Period = month.TotalDays;

[VB.Net]
Average1.PeriodAlign = Steema.TeeChart.Functions.PeriodAligns.First
Average1.PeriodStyle = Steema.TeeChart.Functions.PeriodStyles.Range
Average1.Period = Month.TotalDays

Period = Month.TotalDays and PeriodAligns.First
As you can see from the picture below, the "average" is plotted at the end
of the month.



Period = Month.TotalDays and PeriodAligns.Last
In this case the "average" is plotted at the beginning of the month.



Deriving Custom Functions
Creating a new Function component is simply creating a new component derived from the Function class (it also can be derived from an existing function class). There are 2 important virtual methods in Function that can be overridden to create a new Function type.

1) Function.Calculate: public virtual double Calculate(Series Source,int First,int Last)

2) Function.CalculateMany:  public virtual double CalculateMany(ArrayList SourceSeries, int ValueIndex)

The Calculate method is used to calculate the function result if only one series serves as the datasource. CalculateMany is used to calculate the function result if multiple series can be used as a datasource.

Example : Creating new SquareSum Funtion.

Let's decide we need a SquareSum Function to return the "sum of squares".  

This function can have only one datasource or multiple datasources, so we'll override the Calculate and CalculateMany methods.

fnnnt [C#]
public class SquareSum : Steema.TeeChart.Functions.Function
        public SquareSum(): base()
        public SquareSum(Steema.TeeChart.Chart c): base(c)

        public override double Calculate(Series SourceSeries,int FirstIndex,int LastIndex)   
            ValueList v=ValueList(SourceSeries);
            if ( FirstIndex==-1 ) return v.Total;
            else
                double result=0;
                for (int t=FirstIndex; t<=LastIndex; t++)   
                    result+=Math.Sqrt(v[t]);
                return result;
            
        

        public override double CalculateMany(ArrayList SourceSeriesList,int ValueIndex)
            ValueList v;
            double result=0;

            for (int t=0; t<SourceSeriesList.Count; t++)
                v=ValueList((Series)SourceSeriesList[t]);
                if ( v.Count>ValueIndex )   
                    result+=Math.Sqrt(v[ValueIndex]);
            
            return result;
        


[VB.Net]
Public Class SquareSum
        Inherits Steema.TeeChart.Functions.Function

        Public Sub New()
            MyBase.New()
        End Sub

        Public Sub New(ByVal c As Steema.TeeChart.Chart)
            MyBase.New(c)
        End Sub

        Public Overrides Function Calculate(ByVal Source As Steema.TeeChart.Series, ByVal First As Integer, ByVal Last As Integer) As Double
            Dim v As Steema.TeeChart.ValueList
            Dim t As Integer
            v = ValueList(Source)

            If First = -1 Then
                Return v.Total
            Else
                Dim Result As Double = 0
                For t = First To t < Last
                    Result += Math.Sqrt(v(t))
                Next
                Return Result
            End If
        End Function

        Public Overrides Function CalculateMany(ByVal SourceSeries As System.Collections.ArrayList, ByVal ValueIndex As Integer) As Double
            Dim v As Steema.TeeChart.ValueList
            Dim Result As Double = 0
            Dim t As Integer

            For t = 0 To t < SourceSeries.Count
                v = ValueList(CType(SourceSeries(t), Steema.TeeChart.Series))
                If v.Count > ValueIndex Then
                    Result += Math.Sqrt(v(ValueIndex))
                End If
            Next

            Return Result
        End Function
End Class

The FirstIndex and EndIndex variables are used to "loop" all SourceSeries points to calculate the sum of squares.

The "ValueList" method is used to extract the mandatory Steema.TeeChart.ValueList to make the class work with Series types like HorizBarSeries where "XValues" holds the point values and not "YValues".

The "Calculate" method is used when the Series has only one Series as DataSource. When Series have more than one Series as datasources, the "CalculateMany" method is called.

"CalculateMany" will get called once for each point in the source Series, starting from zero and ending with the minimum point count of all datasources.  

It is very important to understand the difference between Calculate and CalculateMany. "Calculate" is called when there only one datasource and it's called only once. "CalculateMany" is called several times (one for each point) when there are more than one Series as datasources.