Custom drawing on the Chart

If you require to add textboxes or other shapes relative to Chart axis the Shape Series may be the best choice. If the shape Series doesn’t match your specific requirement you may draw your own lines and/or shapes on the Chart.

TeeChart offers access to the Chart area by axis or by screen pixel. The TeeChart demos, UDraw.pas and Uhighlo.pas, are useful references for custom drawing on a Chart.

Topics in this section include:

Calculating Co-ordinates

Axis' Value to Screen co-ordinate Methods

CalcPosValue

CalcPosPoint

CalcSizeValue

CalcYPos and CalcXPos

Series' Value to Screen co-ordinate Methods

CalcPosValue

CalcXPos and CalcYPos

XScreenToValue and YScreenToValue

Chart Canvas

Writing to the Canvas

Internal bitmap

Repainting

When to draw ?

Chart Regions

Drawing

Calculating Co-ordinates

This chapter explains how to convert from Point co-ordinates to pixels and vice-versa. Also how to determine the exact co-ordinates for each graphics element in Chart components.

Point Values are expressed in user custom scales.
Chart Axis are responsible for converting point values into suitable X and Y screen pixel co-ordinates for displaying them.

Both Axis and Series components have several functions to convert from screen co-ordinates (in pixels) to Axis or points values (in user-defined units).

The difference between using Axis or Series conversion functions is that Axis will only interpret co-ordinates for the topmost position in 3D mode, while Series will adjust co-ordinates to their Z order position.

Note: Using conversion functions is only valid after a Chart component has been drawn, either to screen or to a private hidden Canvas.

Axis' Value to Screen co-ordinate Methods

CalcPosValue

You can calculate the screen position for a given value using any Axis:

       Var MyPos : Integer ;
      MyPos := Chart1.LeftAxis.CalcPosValue( 100.0 );

MyPos holds now the pixel co-ordinate for “100.0” on Chart1 Left Axis ( Vertical ), being “100.0” a floating value in Axis scales.

You can use this co-ordinate to custom draw or to calculate mouse clicks. See chapters below.

If you want to convert from pixel co-ordinates to Axis values, you can use the opposite function:

CalcPosPoint

Var MyValue : Double ;

      MyValue := Chart1.LeftAxis.CalcPosPoint( 100 );

MyValue holds now the Axis value co-ordinate for “100” on Chart1. Left Axis ( Vertical ), being “100” a screen pixel co-ordinate.

Note: Pixel co-ordinates start from 0, being 0,0 the Chart origin point. This is valid for screen, but when drawing to metafiles, drawing to custom canvases or printing, Chart origin point can optionally be different than 0,0.

The Chart1.ChartBounds property returns the origin and ending co-ordinates for the Chart bounding rectangle.

CalcSizeValue

Axis have another function to calculate how much screen space represents a given Axis range:

Var Space : Integer ;

    Space := Chart1.LeftAxis.CalcSizeValue( 1000 );

Remember Axis can be DateTime and you can for example convert a Date Range period in pixels:

Var Space : Integer ;

    Space := Chart1.BottomAxis.CalcSizeValue( 
    EncodeDate( 2000,12,31) - EncodeDate( 2000,1,1) );

CalcYPos and CalcXPos

You could use CalcYPos and CalcXPos. When drawing using the XPos and YPos co-ordinates remember that the co-ordinate 0,0 is Top,Left of the Chart rectangle, ChartRect. ChartRect is the area enclosed by the 4 axis of the Chart.

The following example draws a line from an arbitary point from the Y-axis across the Chart. Note the use of canvas properties.

procedure TForm1.Button1Click(Sender: TObject);
var
    MyHalfwayPoint, YPosition: Integer;

Begin
  With Series1.YValues do
   MyHalfwayPoint:=round(((MaxValue-MinValue)* 0.5) + MinValue);

{ then calculate the Screen Pixel co-ordinate of the above value }

        YPosition:=DBChart1.LeftAxis.CalcYPosValue(MyHalfwayPoint);
   With DBChart1.Canvas do
      begin
        { change pen and draw the line avoiding the 
        3D areas and axis of the Chart - Height3D,Width3D}
          Pen.Width:=3;
          Pen.Style:=psSolid;
          Pen.Color:=clBlack;
         with DBChart1 do
         begin
          MoveTo(ChartRect.Left,YPosition);
          LineTo(ChartRect.Left+Width3D,YPosition-Height3D);
          LineTo(ChartRect.Right+Width3D,YPosition-Height3D);
         end;
       end;
end;

Series' Value to Screen co-ordinate Methods

Series have similar methods for converting co-ordinates to point values and vice-versa. The main difference is that by using the Series method you don’t need to know the exact Axis component for calculations.

This is a big advantage when having Series associated to Right or Top Axis, or multiple Series associated to each Axis.

CalcPosValue

This code calculates where in the screen the Series1 point with value 1000 is located:

Var MyPos : Integer ;

    MyPos := Series1.CalcPosValue( 1000 );

or...

MyYPos := Series1.CalcPosValue( Series1.YValue[ 0 ] ) ; { <-- first point }

CalcXPos and CalcYPos

You can calculate both X and Y co-ordinates for a specific point or for a specific point value:

MyXPos := Series1.CalcXPos( EncodeDate( 2000, 12, 31) ) ;

or...

MyXPos := Series1.CalcXPos( Series1.XValues.Last ); { <-- last point }

XScreenToValue and YScreenToValue

To convert from screen pixels to point values, use:

Var MyValue : Double ;

    MyValue := Series1.YScreenToValue( Y ) ;

(and XScreenToValue for horizontal co-ordinates).

You can query the point under a given pair of XY screen co-ordinates using the Series.Clicked function. (See Mouse Clicks chapter).

Chart Canvas

Chart.Canvas is a standard Delphi Canvas. You may control the appearance of your Chart using Canvas properties. See TCanvas help in Delphi for a full list of properties.

Writing to the Canvas

The following example divides the area of the backdrop of the Chart rectangle into 5 equal segments and colours them according to the colour array.

procedure TDrawForm.LineSeries1BeforeDrawValues(Sender: TObject);
Const
   MyColors:array[1..5] of TColor=
    ( clNavy,
      clGreen,
      clYellow,
      clRed,
      $00000080 { very red }
      );
var t,partial: Integer;
    tmpRect:TRect;
    With Chart1 do
      Begin
        { we will divide the total Chart width by 5 }
        tmpRect:=ChartRect;
        tmpRect.Right:=tmpRect.Left;
        partial:=ChartWidth div 5;

        { change the brush style }
        Canvas.Brush.Style:=bsDiagCross;
        Canvas.Pen.Style:=psClear;

        { for each section,fill with a specific color}
        for t:=1 to 5 do
        Begin
          { adjust the rectangle dimension }
              tmpRect.Right:=tmpRect.Right+partial+1 ;

              { set the brush color }
              Canvas.Brush.Color:=MyColors[t];

              { paint !!! }
              With tmpRect do
                Canvas.Rectangle( Left+Width3D,Top-                Height3D,Right+Width3D,Bottom-Height3D );
                  { adjust rectangle }
                  tmpRect.Left:=tmpRect.Right;
            end;
       end;
end;

Internal bitmap

TChart components have an internal bitmap object, which is used when drawing as a hidden “buffer”. When drawing is finished, this “buffer” is copied to the screen video memory to display it.

TChart Canvas property returns the internal bitmap Canvas object.

Note: BufferedDisplay property controls if the internal bitmap is used. Using this bitmap no flicker occurs when redrawing real-time Charts. Also, some Charts with many points can take advantage of faster drawing speed on memory bitmaps instead of direct drawing to slower video card memory chips.

Drawing to a bitmap Canvas is exactly the same as drawing to another “real” Canvas in terms of source code transparency and results.

Note: When drawing to a metafile or printing, the Chart Canvas property refers to metafile or printer Canvas objects. No bitmap is used in these cases.

After a Chart has been drawn onto the internal bitmap, and its copied onto the screen canvas, Chart Canvas property refers to the original “real” Chart Canvas.

Repainting

You should call Chart1.Invalidate or Series1.Repaint to force a Chart component to draw again.

When to draw ?

The order in which you draw to the Canvas is important.

If you wish Custom drawn items to appear above Chart Series you should use the Chart OnAfterDraw event. The Chart1.OnAfterDraw event is fired each time the Chart component is redrawn, just before copying the internal bitmap to screen.

You can place Custom drawn items above the Chart grid and below the Chart Series by placing your code in the Chart OnBeforeDrawSeries event.

Key Chart Paint events:

OnBeforeDrawChart
OnBeforeDrawAxes
OnBeforeDrawSeries
OnAfterDraw

Chart Sub components also have events that may be used to Paint to the Canvas.

Eg.

OnAfterDrawValues
OnBeforeDrawValues

An example of use of OnBeforeDrawValues is the TeeChart multiple Pie example which sets the Chart rectangle plotting area before plotting the values of the Series.

Advanced: For more control you might want to create your own Chart class and override the several Draw... virtual methods.

Chart Regions

The biggest rectangle around a Chart is at ChartBounds property. ChartWidth, ChartHeight, ChartXCenter and ChartYCenter are pre-calculated co-ordinates based on ChartBounds property.

Axis are drawn inside this space. The ChartRect property returns the bounding rectangle for Axis ( same in 2D and 3D ).

Chart Legend has the RectLegend public property which defines the Legend rectangle bounds.

Chart Title and Foot have TitleRect public properties.

In 3D Charts, each Series is assigned a specific section among the “Z” ( depth ) axis.

The Chart Width3D and Height3D are the dimensions of the 3D depth, in pixels. SeriesWidth3D and SeriesHeight3D are the dimensions for each individual Series in a Chart.

Drawing

Lets start drawing with a basic example.

This code draws an horizontal line just at the middle of Chart1:

procedure TForm1.Chart1AfterDraw(Sender: TObject);
begin
  With Chart1 do
          begin
                Canvas.Pen.Color:=clYellow;
                Canvas.MoveTo( ChartBounds.Left, 
                ChartYCenter );
                Canvas.LineTo( ChartBounds.Right,                     ChartYCenter );
          end;
end;

Drawing inside Axis space:

procedure TForm1.Chart1AfterDraw(Sender: TObject);
begin
  With Chart1 do
      begin
        Canvas.Pen.Color:=clYellow;
        Canvas.MoveTo( ChartRect.Left, ChartYCenter );
       Canvas.LineTo( ChartRect.Right, ChartYCenter );
      end;
    end;

Drawing a line at each point in Series1 :

procedure TForm1.Series1AfterDrawValues(Sender: TObject);
var t, x, y : Integer ;
begin
       for t := 0 to Series1.Count - 1 do
       begin
             x:=Series1.CalcXPos( t );
            y:=Series1.CalcYPos( t );
           Chart1.Canvas.MoveTo( x-8, y-8 );
           Chart1.Canvas.LineTo( x+8, y+8 );          
     end;
end;

Note: Drawing Text

Always set Chart1.Canvas.Font.Height to a negative value

instead of using Font.Size if want same font sizes on screen

and on printer or metafiles.