Reputation: 33
I have encountered the performance issue while refreshing the whole graph with a lot of points on it and would like to ask if there is a way to decrease the time taken to redraw the graph.
For instance, my graph contains a hundred of TAreaSeries each worth of 1000 points. These values are built once and do not change after. But there is one short TLineSeries with 2 points that 'jumps' across the graph changing its coordinates each 50 milliseconds. Despite only a small region of the plot is being modified (the pixels in area of TLineSeries), the whole graph (with thousands of points) is updated and redrawn which spends loads of resources in vain and causes huge delays. The next and the previous position of this TLineSeries with 2 points is known in my case, so I wondered if there is a way to force the TChart to repaint some predefined plot region only instead of redrawing the whole graph?
Upvotes: 2
Views: 2561
Reputation: 7452
In real-time applications we generally recommend following what's explained in the Real-time Charting article here.
As far as repainting only a region of the chart, you can not call InvalidateRacte and only recreate the chart section in the rectangle. InvalidateRacte could only be used to repaint a part of the buffer bitmap (Chart1.Canvas.Bitmap, when using Chart1.BufferedDisplay), the VCL framework doesn't support that, we wouldn't get any benefit from it either.
Here you can download a project which paints a line series in a chart over another chart without recreating it, which is the most time consuming task. There's a button that counts the number of times the buffer bitmap can be repainted. We get about 5000 per second. If the chart needed to be recreated every time, e.g.: painting axes, series, legend, etc.), we get 100 times per second at most. The functionality shown in the project is only available using the new TeeChart Pro beta. Project's code is this:
type
TSeriesAccess=class(TChartSeries);
TChartAccess=class(TCustomTeePanel);
procedure TForm1.ScrollBar1Change(Sender: TObject);
begin
// Example: Modify a Line value
Series1.YValues[10]:=350+ScrollBar1.Position;
// Paint Line to Chart2, just to check the change (this is optional)
Series1.Repaint;
// Redraw Chart1 fast, just the buffer bitmap, without repainting everything:
TChartAccess(Chart1).Paint;
// Draw the Line over Chart1, on top of it, instead of "inside" it
TSeriesAccess(Series1).FParent:=Chart1;
TSeriesAccess(Series1).DrawAllValues;
TSeriesAccess(Series1).FParent:=Chart2;
end;
// Count the number of times a Chart buffer can be redisplayed
// (without redrawing everything)
procedure TForm1.Button1Click(Sender: TObject);
var t1 : Cardinal;
tmpCount : Integer;
begin
tmpCount:=0;
t1:=GetTickCount;
// Loop for one second
while GetTickCount-t1 < 1000 do
begin
TChartAccess(Chart1).Paint;
Inc(tmpCount);
end;
Caption:='Redraws per second: '+IntToStr(tmpCount);
end;
The other option is this, using a ColorLine which does more or less the same as our project, painting over using Pen.Mode = pmXor.
Regarding your project, you missed setting AutoRepaint to true before refreshing the series. This code works:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
// The 'jumping' TLineSeries will move to the right 1 point every 1 second.
Form1.Chart1.Series[1].XValue[0]:= Form1.Chart1.Series[1].XValue[0] + 1;
Form1.Chart1.Series[1].XValue[1]:= Form1.Chart1.Series[1].XValue[1] + 1;
Form1.Chart1.AutoRepaint:=True;
Form1.Chart1.Series[1].Repaint;
Form1.Chart1.AutoRepaint:=False;
end;
Also, you could use a TColorLineTool instead of a series for the vertical line. You should remove the second line series, add a ColorLine tool at design-time and implement the code like this:
unit UnitTeeChartRefresh;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, TeeProcs, TeEngine, Chart, Series, StdCtrls,
TeeGDIPlus, TeeTools;
type
TForm1 = class(TForm)
Chart1: TChart;
Series1: TAreaSeries;
Button1: TButton;
Timer1: TTimer;
Label1: TLabel;
ChartTool1: TColorLineTool;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure Chart1AfterDraw(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
times_redrawn: cardinal = 0; // A counter indicating how many times our chart was redrawn.
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
i: integer;
begin
//Let's add one constant TAreaSeries and one 'jumping' across the graph TLineSeries
Randomize;
Form1.Chart1.Axes.Left.SetMinMax(0, 10);
Form1.Chart1.Axes.Bottom.SetMinMax(0, 20);
for i:= 1 to 20 do
begin
Form1.Chart1.Series[0].AddXY(i, Random(10))
end;
ChartTool1.Axis:=Chart1.Axes.Bottom;
ChartTool1.Value:=0;
// If I enable AutoRepaint I will be able to see all changes on the whole plot in real-time
// and have no problem.
// But I want to manually control the refreshing process and even refresh some regions of the
// graph separately.
Form1.Chart1.AutoRepaint:=False;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
// This button launches the animation of the 'jumping' TLineSeries.
Form1.Timer1.Enabled:= not Form1.Timer1.Enabled;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
// The 'jumping' TLineSeries will move to the right 1 point every 1 second.
Form1.Chart1.AutoRepaint:=True;
ChartTool1.Value:=ChartTool1.Value + 1;
Form1.Chart1.AutoRepaint:=False;
end;
procedure TForm1.Chart1AfterDraw(Sender: TObject);
begin
times_redrawn:= times_redrawn + 1;
Form1.Label1.Caption:= intToStr(times_redrawn);
end;
end.
Upvotes: 2