![]() |
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:
Axis' Value to Screen co-ordinate Methods
Series' Value to Screen co-ordinate Methods
XScreenToValue and YScreenToValue
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
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:
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.
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) );
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.
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 }
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 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.
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;
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.
You should call Chart1.Invalidate or Series1.Repaint to force a Chart component to draw again.
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.
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.
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.