Tutorial 13 - Custom drawing on the Chart Panel

TeeChart offers extensive custom drawing facilities via the Canvas object. With Canvas you may add shapes, lines and text anywhere on the Chart Panel and define their colours, pen and brush styles.

Contents

TeeChart Canvas
     Drawing order
     Ensuring that custom drawn items are saved to the Canvas
     Drawing Lines
     Canvas Pen and Brush
     Adding 2D Shapes
     Adding 3D Shapes
     Adding text
     Applied example

TeeChart Canvas

Drawing order
When using TeeChart's Canvas methods remember that drawing order is important. Drawing a Line on the Chart then adding Series data points will cause the Line to be overdrawn.  
There are four principle Chart draw events, which are, in order:
  • BeforeDraw event

  • BeforeDrawAxes event

  • BeforeDrawSeries event

  • AfterDraw event


  • Example:
    [C#] 
    private bool afterDraw;
    private bool beforeDraw;
    private bool beforeDrawAxis;
    private bool beforeDrawSeries;

    private void Form1_Load(object sender, System.EventArgs e) {  
         SetFlags(ref beforeDraw);
         Bar bar1 = new Bar(tChart1.Chart);
         bar1.FillSampleValues(20);
         radioButton1.Checked = true;
    }
    private void SetFlags(ref bool Flag) {
         beforeDraw = false;
         afterDraw = false;
         beforeDrawAxis = false;
         beforeDrawSeries = false;
         Flag = true;
    }
    private void DrawShape(Steema.TeeChart.Drawing.Graphics3D gg) {
         gg.Brush.Color = Color.Yellow;
         gg.Pen.Visible = true;
         gg.Pen.Style = System.Drawing.Drawing2D.DashStyle.Dash;
         gg.Brush.Visible = true;
         gg.Ellipse(1,1,gg.Chart.Width - 1,gg.Chart.Height - 1);
    }
    private void tChart1_AfterDraw(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
         if(afterDraw) {
         DrawShape(g);
        }
    }
    private void tChart1_BeforeDraw(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
         if(beforeDraw) {
         DrawShape(g);
        }
    }
    private void tChart1_BeforeDrawAxes(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
         if(beforeDrawAxis) {
         DrawShape(g);
        }
    }
    private void tChart1_BeforeDrawSeries(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
         if(beforeDrawSeries) {
         DrawShape(g);
        }
    }
    private void radioButton4_Click(object sender, System.EventArgs e) {
         SetFlags(ref afterDraw);
         tChart1.Refresh();
    }
    private void radioButton3_Click(object sender, System.EventArgs e) {
         SetFlags(ref beforeDrawSeries);
         tChart1.Refresh();
    }
    private void radioButton2_Click(object sender, System.EventArgs e) {
         SetFlags(ref beforeDrawAxis);
         tChart1.Refresh();
    }
    private void radioButton1_Click(object sender, System.EventArgs e) {
         SetFlags(ref beforeDraw);
         tChart1.Refresh();
    }

    [VB.Net]    
    Private BeforeDraw As Boolean
    Private BeforeDrawAxis As Boolean
    Private BeforeDrawSeries As Boolean
    Private AfterDraw As Boolean

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        SetFlags(BeforeDraw)
        Dim Bar1 As New Steema.TeeChart.Styles.Bar(TChart1.Chart)
        Bar1.FillSampleValues(20)
        RadioButton1.Checked = True
    End Sub
    Private Sub SetFlags(ByRef Flag As Boolean)
        BeforeDraw = False
        BeforeDrawAxis = False
        BeforeDrawSeries = False
        AfterDraw = False
        Flag = True
    End Sub
    Private Sub DrawShape(ByVal gg As Steema.TeeChart.Drawing.Graphics3D)
        gg.Brush.Color = Color.Yellow
        gg.Pen.Visible = True
        gg.Pen.Style = Drawing.Drawing2D.DashStyle.Dash
        gg.Brush.Visible = True
        gg.Ellipse(1, 1, gg.Chart.Width - 1, gg.Chart.Height - 1)
    End Sub
    Private Sub TChart1_AfterDraw(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.AfterDraw
        If AfterDraw = True Then
         DrawShape(g)
        End If
    End Sub
    Private Sub TChart1_BeforeDraw(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.BeforeDraw
        If BeforeDraw = True Then
         DrawShape(g)
        End If
    End Sub
    Private Sub TChart1_BeforeDrawAxes(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.BeforeDrawAxes
        If BeforeDrawAxis = True Then
         DrawShape(g)
        End If
    End Sub
    Private Sub TChart1_BeforeDrawSeries(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.BeforeDrawSeries
        If BeforeDrawSeries = True Then
         DrawShape(g)
        End If
    End Sub
    Private Sub RadioButton4_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles RadioButton4.Click
        SetFlags(AfterDraw)
        TChart1.Refresh()
    End Sub
    Private Sub RadioButton3_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles RadioButton3.Click
        SetFlags(BeforeDrawSeries)
        TChart1.Refresh()
    End Sub
    Private Sub RadioButton2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles RadioButton2.Click
        SetFlags(BeforeDrawAxis)
        TChart1.Refresh()
    End Sub
    Private Sub RadioButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles RadioButton1.Click
        SetFlags(BeforeDraw)
        TChart1.Refresh()
    End Sub

    Ensuring that custom drawn items are saved to the Canvas
    If you do not place calls to Canvas draw code in one of the Chart events, custom drawing will not be saved to the Canvas permanently thus causing any addition to be lost when an application is minimised or another Window placed over it. Your code does not need to reside directly in the Chart events; user drawn items can be saved for the life of the Chart window if you place code in BeforeDrawSeries/AfterDraw and check for flags set by your runtime Draw methods thus running your draw code when activity is flagged as true, as in the example above.

    Drawing Lines
    Let's add a Canvas Line:
    Example (drawing a line diagonally from top left to bottom right)

    [C#] 
    private void Form1_Load(object sender, System.EventArgs e) {
            line1.FillSampleValues(20);
            line1.VertAxis = VerticalAxis.Both;
            line1.HorizAxis = HorizontalAxis.Both;
            tChart1.Aspect.View3D = false;
    }

    private void tChart1_AfterDraw(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
            Point s = new Point(tChart1.Axes.Left.Position, tChart1.Axes.Top.Position);
            Point e = new Point(tChart1.Axes.Right.Position, tChart1.Axes.Bottom.Position);
            g.MoveTo(s);
            g.LineTo(e,0);
    }

    [VB.Net]
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            Line1.FillSampleValues(20)
            Line1.VertAxis = Steema.TeeChart.VerticalAxis.Both
            Line1.HorizAxis = Steema.TeeChart.HorizontalAxis.Both
            TChart1.Aspect.View3D = False
    End Sub

    Private Sub TChart1_AfterDraw(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.AfterDraw
            Dim S As New Point(TChart1.Axes.Left.Position, TChart1.Axes.Top.Position)
            Dim E As New Point(TChart1.Axes.Right.Position, TChart1.Axes.Bottom.Position)
            g.MoveTo(S)
            g.LineTo(E, 0)
    End Sub


    On a 3D Chart the Axis positions are offset from the Chart area due to 3D orthogonal displacement. We can move the Line accordingly:  
    Example (drawing a Line diagonally from top left to bottom right in the Chart Area of a 3D Chart)

    [C#] 
    private void Form1_Load(object sender, System.EventArgs e) {
            line1.FillSampleValues(20);
            line1.VertAxis = VerticalAxis.Both;
            line1.HorizAxis = HorizontalAxis.Both;
            tChart1.Aspect.Chart3DPercent = 50;
    }

    private void tChart1_AfterDraw(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
            Steema.TeeChart.Drawing.Point3D s = new Steema.TeeChart.Drawing.Point3D();
            s.X = tChart1.Axes.Left.Position;
            s.Y = tChart1.Axes.Top.Position;
            s.Z = 0;

            Steema.TeeChart.Drawing.Point3D e = new Steema.TeeChart.Drawing.Point3D();
            e.X = tChart1.Axes.Right.Position;
            e.Y = tChart1.Axes.Bottom.Position;
            e.Z = tChart1.Aspect.Width3D;
        
            g.MoveTo(s);
            g.LineTo(e);
    }

    [VB.Net]
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            Line1.FillSampleValues(20)
            Line1.VertAxis = Steema.TeeChart.VerticalAxis.Both
            Line1.HorizAxis = Steema.TeeChart.HorizontalAxis.Both
            TChart1.Aspect.Chart3DPercent = 50
    End Sub

    Private Sub TChart1_AfterDraw(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.AfterDraw
            Dim S As New Steema.TeeChart.Drawing.Point3D()
            S.X = TChart1.Axes.Left.Position
            S.Y = TChart1.Axes.Top.Position
            S.Z = 0

            Dim E As New Steema.TeeChart.Drawing.Point3D()
            E.X = TChart1.Axes.Right.Position
            E.Y = TChart1.Axes.Bottom.Position
            E.Z = TChart1.Aspect.Width3D

            g.MoveTo(S)
            g.LineTo(E)
    End Sub

    Canvas Pen and Brush
    The Line above is drawn using the Pen and Brush defined for the last object drawn before the Line is drawn. That may or may not be the Pen you want. Change the Pen accordingly:
    Example (define Pen before drawing the Line)

    [C#] 
    private void tChart1_AfterDraw(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
            Point p5 = new Point(line1.CalcXPos(5), line1.CalcYPos(5));
            Point p15 = new Point(line1.CalcXPos(15), line1.CalcYPos(15));
            g.Pen.DashCap = System.Drawing.Drawing2D.DashCap.Triangle;
            g.Pen.EndCap = System.Drawing.Drawing2D.LineCap.DiamondAnchor;
            g.Pen.Style = System.Drawing.Drawing2D.DashStyle.DashDotDot;
            g.Pen.Transparency = 70;
            g.Pen.Width = 3;
            g.Pen.Color = Color.BlueViolet;
            g.MoveTo(p5);
            g.LineTo(p15, 0);
    }

    [VB.Net]
    Private Sub TChart1_AfterDraw(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.AfterDraw
            Dim P5 As New Point(Line1.CalcXPos(5), Line1.CalcYPos(5))
            Dim P15 As New Point(Line1.CalcXPos(15), Line1.CalcYPos(15))
            g.Pen.DashCap = System.Drawing.Drawing2D.DashCap.Triangle
            g.Pen.EndCap = System.Drawing.Drawing2D.LineCap.DiamondAnchor
            g.Pen.Style = System.Drawing.Drawing2D.DashStyle.DashDotDot
            g.Pen.Transparency = 70
            g.Pen.Width = 3
            g.Pen.Color = Color.BlueViolet
            g.MoveTo(P5)
            g.LineTo(P15, 0)
    End Sub

    Adding 2D Shapes
    Add Canvas Shapes in a similar manner to Canvas Lines. The following example adds a Rectangle in the centre of the Chart Area:
    2D Charts 
    2D Charts only support 2D shapes.
    Example

    [C#] 
    private void tChart1_AfterDraw(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
            Size s = new Size(100,100);
            Point l = new Point(g.ChartXCenter - (s.Width / 2), g.ChartYCenter - (s.Height / 2));
            Rectangle r = new Rectangle(l,s);
            g.Pen.Color = Color.Aquamarine;
            g.Brush.Color = Color.Blue;
            g.Rectangle(r);
    }

    [VB.Net]
    Private Sub TChart1_AfterDraw(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.AfterDraw
            Dim S As New Size(100, 100)
            Dim L As New Point(g.ChartXCenter - (S.Width / 2), g.ChartYCenter - (S.Height / 2))
            Dim R As New Rectangle(L, S)
            g.Pen.Color = Color.Aquamarine
            g.Brush.Color = Color.Blue
            g.Rectangle(R)
    End Sub

    3D Charts 
    On a 3D Chart you can move the Rectangle in a Z plane too. See the RectangleWithZ method. This example places the Rectangle on the Left Wall but displaces it halfway towards the rear of the Chart (towards the Back Wall).

    [C#] 
    private void Form1_Load(object sender, System.EventArgs e) {
            point3DSeries1.LinePen.Visible = false;
            point3DSeries1.FillSampleValues(20);
            point3DSeries1.VertAxis = VerticalAxis.Both;
            point3DSeries1.HorizAxis = HorizontalAxis.Both;
            tChart1.Aspect.Chart3DPercent = 50;
            tChart1.Axes.Depth.Visible = true;
    }

    private void tChart1_AfterDraw(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
            Size s = new Size(100,100);
            Point l = new Point(tChart1.Axes.Left.Position, g.ChartYCenter - (s.Height / 2));
            Rectangle r = new Rectangle(l,s);
            g.Pen.Color = Color.Aquamarine;
            g.Brush.Color = Color.Blue;
            g.Rectangle(r, tChart1.Aspect.Width3D/2);
    }

    [VB.Net]
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            Point3DSeries1.LinePen.Visible = False
            Point3DSeries1.FillSampleValues(20)
            Point3DSeries1.VertAxis = Steema.TeeChart.VerticalAxis.Both
            Point3DSeries1.HorizAxis = Steema.TeeChart.HorizontalAxis.Both
            TChart1.Aspect.Chart3DPercent = 50
            TChart1.Axes.Depth.Visible = True
    End Sub

    Private Sub TChart1_AfterDraw(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.AfterDraw
            Dim S As New Size(100, 100)
            Dim L As New Point(TChart1.Axes.Left.Position, g.ChartYCenter - (S.Height / 2))
            Dim R As New Rectangle(L, S)
            g.Pen.Color = Color.Aquamarine
            g.Brush.Color = Color.Blue
            g.Rectangle(R, TChart1.Aspect.Width3D / 2)
    End Sub

    Adding 3D Shapes
    You may add 3D shapes to 3D Charts. This example draws a Cube in the middle of the Chart rectangle:

    [C#] 
    private void Form1_Load(object sender, System.EventArgs e) {
            point3DSeries1.LinePen.Visible = false;
            point3DSeries1.FillSampleValues(20);
            tChart1.Aspect.Chart3DPercent = 50;
            tChart1.Legend.Visible = false;
            tChart1.Axes.Depth.Visible = true;
    }

    private void tChart1_AfterDraw(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
            Size s = new Size(50,50);
            Point p = new Point(g.ChartXCenter - (s.Width/2), g.ChartYCenter - (s.Height/2));
            Rectangle r = new Rectangle(p,s);
            g.Cube(r, 0, 20, true);
    }

    [VB.Net]
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            Point3DSeries1.LinePen.Visible = False
            Point3DSeries1.FillSampleValues(20)
            TChart1.Aspect.Chart3DPercent = 50
            TChart1.Legend.Visible = False
            TChart1.Axes.Depth.Visible = True
    End Sub

    Private Sub TChart1_AfterDraw(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.AfterDraw
            Dim S As New Size(50, 50)
            Dim P As New Point(g.ChartXCenter - (S.Width / 2), g.ChartYCenter - (S.Height / 2))
            Dim R As New Rectangle(P, S)
            g.Cube(R, 0, 20, True)
    End Sub

    Adding text
    2D Text location 
    Add Text to the last Rectangle:
    Example

    [C#] 
    private void tChart1_AfterDraw(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
            string text = "My Text";
            Size s = new Size(150, 50);
            Point p = new Point(g.ChartXCenter - (s.Width/2), g.ChartYCenter - (s.Height/2));
            Rectangle r = new Rectangle(p,s);
            g.Pen.Color = Color.Blue;
            g.Rectangle(r);

            g.TextOut(Convert.ToInt32(g.ChartXCenter - (g.TextWidth(text)/2)), Convert.ToInt32(g.ChartYCenter - (g.TextHeight(text)/2)), text);
    }

    [VB.Net]
    Private Sub TChart1_AfterDraw(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.AfterDraw
            Dim Text As String = "My Text"
            Dim S As New Size(50, 50)
            Dim P As New Point(g.ChartXCenter - (S.Width / 2), g.ChartYCenter - (S.Height / 2))
            Dim R As New Rectangle(P, S)
            g.Pen.Color = Color.Blue
            g.Rectangle(R)

            g.TextOut(Convert.ToInt32(g.ChartXCenter - (g.TextWidth(Text) / 2)), Convert.ToInt32(g.ChartYCenter - (g.TextHeight(Text) / 2)), Text)
    End Sub

    3D Text location 
    You can place Text in a differing 3D plane by using the TextOut overload with a z coordinate.
    Example

    [C#] 
    private void Form1_Load(object sender, System.EventArgs e) {
            point3DSeries1.FillSampleValues(20);
            point3DSeries1.LinePen.Visible = false;
            tChart1.Aspect.Chart3DPercent = 50;
    }

    private void tChart1_AfterDraw(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
            string text = "My Text";
            g.TextOut(g.ChartXCenter, g.ChartYCenter, tChart1.Aspect.Width3D / 2, text);
    }

    [VB.Net]
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            Point3DSeries1.FillSampleValues(20)
            Point3DSeries1.LinePen.Visible = False
            TChart1.Aspect.Chart3DPercent = 50
    End Sub

    Private Sub TChart1_AfterDraw(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.AfterDraw
            Dim Text As String = "My Text"
            g.TextOut(g.ChartXCenter, g.ChartYCenter, TChart1.Aspect.Width3D / 2, Text)
    End Sub

    Applied example
    This example takes the 3rd and 10th values of a Series, plots a Line between them and tells us the value of the first and Last point of the new Line and the difference between them:
    Example

    [C#] 
    private void Form1_Load(object sender, System.EventArgs e) {
            tChart1.Aspect.View3D = false;
            line1.FillSampleValues(20);
    }

    private void tChart1_AfterDraw(object sender, Steema.TeeChart.Drawing.Graphics3D g) {
            if(tChart1.Series.Count > 0){
                if(tChart1.Series[0].Count > 10) {
                    Series s = tChart1.Series[0];
                    int h = Convert.ToInt32(g.TextHeight("H"));
                    Point p1 = new Point(s.CalcXPos(3), s.CalcYPos(3));
                    Point p2 = new Point(s.CalcXPos(10), s.CalcYPos(10));
                    g.Pen.Color = Color.Blue;
                    g.Pen.Width = 2;
                    g.Pen.Style = System.Drawing.Drawing2D.DashStyle.Dash;
                    g.MoveTo(p1);
                    g.LineTo(p2, 0);
                    g.TextOut(p1.X, p1.Y - h, "Point value: " + s.YValues[3].ToString());
                    g.TextOut(p2.X, p2.Y, "Point value: " + s.YValues[10].ToString());
                    g.TextOut(p2.X, p2.Y + h, "Change is: " + Convert.ToString(s.YValues[3] - s.YValues[10]));
                }
            }
    }

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

    Private Sub TChart1_AfterDraw(ByVal sender As Object, ByVal g As Steema.TeeChart.Drawing.Graphics3D) Handles TChart1.AfterDraw
            If TChart1.Series.Count > 0 Then
                If TChart1.Series(0).Count > 10 Then
                    Dim S As Steema.TeeChart.Series = TChart1.Series(0)
                    Dim H As Integer = Convert.ToInt32(g.TextHeight("H"))
                    Dim P1 As New Point(S.CalcXPos(3), S.CalcYPos(3))
                    Dim P2 As New Point(S.CalcXPos(10), S.CalcYPos(10))
                    g.Pen.Color = Color.Blue
                    g.Pen.Width = 2
                    g.Pen.Style = System.Drawing.Drawing2D.DashStyle.Dash
                    g.MoveTo(P1)
                    g.LineTo(P2, 0)
                    g.TextOut(P1.X, P1.Y - H, "Point value: " & S.YValues(3))
                    g.TextOut(P2.X, P2.Y, "Point value: " & S.YValues(10))
                    g.TextOut(P2.X, P2.Y + H, "Change is: " & (S.YValues(3) - S.YValues(10)))
                End If
            End If
    End Sub