2

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?

Wrackage
  • 33
  • 5
  • I miss the old PutPixel function in Turbo Pascal 7. Those really were the good old days. :-( – Sam Jul 29 '13 at 14:57

1 Answers1

2

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.
Community
  • 1
  • 1
Narcís Calvet
  • 7,304
  • 5
  • 27
  • 47
  • Excuse me for newbie question, but I'm not a professional programmer and am not familiar with .NET syntax, so I got confused: are you suggesting to copy a rectangle from one canvas and paste it onto the Chart1 canvas? – Wrackage Jul 30 '13 at 07:45
  • @Wrackage you could start with the "Real-time Charting" article suggestions. If this doesn't help you can try the other solution. It basically consists in repainting the area of the given rectangle instead of the whole chart to update this specific section only for performance purposes. – Narcís Calvet Jul 30 '13 at 07:59
  • Thanks, I have considered all the recommendations from the "Real-time Charting" article, however they didn't change the overall picture of redrawing delays. I'm sorry for my importunity, but could you please elaborate on how the Draw(UserCanvas:TCanvas; Const UserRect:TRect) method should be used in order to partly refresh the graph? What should I pass as the parameters? There is too few info in the documentation, could you give an extended description of the method? – Wrackage Jul 30 '13 at 13:22
  • @Wrackage sorry, Draw is not doing the same as .NET's Invalidate. In that case you probably need to do something as what's suggested [here](http://stackoverflow.com/questions/4478649/draw-over-controls-in-delphi-form) as Delphi has no Invalidate override which can be called with a rectangle as an argument. – Narcís Calvet Jul 30 '13 at 14:00
  • Thanks, but the only thing from that link that might help in my case is the 'InvalidateRect(WindowHandle: HWND, RectanglePointer: PRect, EraseBackground: Boolean)' method. However it doesn't work: if I change the TLineSeries coordinates and use 'InvalidateRect(Form1.Handle, @refresh_rect, False)' instead of 'LineSeries1.Repaint' nothing happens, I don't see the LineSeries1 moving. Even if 'refresh_rect = Chart1.ChartRect'. By the way, does the 'Chart1.Repaint' and 'InvalidateRect(Form1.Handle, refresh_rect,False)' function have the same effect (assuming that refresh_rect = Chart1.ChartRect)? – Wrackage Jul 31 '13 at 09:22
  • @Wrackage Repaint and Invalidate are pretty similar, see the differences [here](http://stackoverflow.com/questions/1251009/whats-the-difference-between-refresh-update-repaint). However, InvalidateRect only invalidates an area of the control specified by the rectangle parameter. Please send us a simple project we can run "as-is" to reproduce your problem here and we will try to optimize it to work as you'd expect. You can send your files at www.steema.net/upload/ or at info at steema dot com mentioning this thread. Thanks in advance. – Narcís Calvet Jul 31 '13 at 09:29
  • I have sent you the simple project you were asking for. If you launch it you'll see that with Chart1.AutoRepaint:= False the graph won't get updated after a change even if I make a separate 'Chart1.Repaint' call etc. The changes and updates of the graph happen at a timer tick. Thanks for your attention and I'm looking forward for your reply. – Wrackage Jul 31 '13 at 19:00
  • @Wrackage I'm sorry but we haven't received your email. You can try sending it to steemasales at telefonica dot net instead. Thanks again. – Narcís Calvet Aug 01 '13 at 14:45
  • Message sent. Looking forward for suggestions and solutions. Thanks. – Wrackage Aug 01 '13 at 19:11
  • In case some other e-mail problems come up you could pick up the project here: https://skydrive.live.com/redir?resid=9966BBBE2447AA89!117&authkey=!AFDcI0jKxuizbS4 – Wrackage Aug 02 '13 at 06:29
  • @Wrackage thanks for the example. Just updated my reply accordingly. Does this fit your needs? – Narcís Calvet Aug 02 '13 at 09:50
  • Thank you for the answer. However it brought even more questions for me. :) Firstly, what is the point of `Chart1.Repaint` calls if I must enable the `Chart1.Autorepaint` property beforehand? I could have as well enabled Autorepaint right before changing series values. I thought `Chart1.Repaint` is a method for having the Chart to "manually" repaint itself with `Chart1.Autorepaint` turned off. – Wrackage Aug 05 '13 at 07:45
  • Secondly, my main question regarding the partial chart refreshing is still actual. This trick doesnt work for: `Form1.Chart1.AutoRepaint:=True; update_rect:= Form1.Chart1.ChartRect; InvalidateRect(Form1.Handle, @update_rect , False); Form1.Chart1.AutoRepaint:=False;` – Wrackage Aug 05 '13 at 07:46
  • @Wrackage The AutoRepaint property indicates if a Chart should re-display (redraw) its contents whenever a property (color, etc) changes. Setting AutoRepaint to False will not refresh the Chart display until we manually call the Chart1.Repaint method. This can increment a little bit the speed when modifying many properties at the same time, avoiding the slow call to Windows API "Invalidate" procedure. That's why we call Repaint just after setting AutoRepaint to true, to force chart refreshing. Regarding the InvalidateRect issue, I'll investigate that option further and will get back to you. – Narcís Calvet Aug 05 '13 at 08:39
  • Hi! So how is the investigation going? – Wrackage Aug 09 '13 at 08:35
  • @Wrackage I have just updated my answer with a solution suggestion. – Narcís Calvet Aug 12 '13 at 10:26