Performance Issue with Grid Lines and PenStyle psDot

TeeChart VCL for Borland/CodeGear/Embarcadero RAD Studio, Delphi and C++ Builder.
Post Reply
ags
Newbie
Newbie
Posts: 4
Joined: Wed May 18, 2022 12:00 am

Performance Issue with Grid Lines and PenStyle psDot

Post by ags » Thu Dec 19, 2024 9:43 am

Hello Steema Support,

I am experiencing significant performance degradation when enabling minor grid lines in TChart. Here are the details:
- TChart Version: 2023.38.230607
- Delphi Version: Delphi 11
- Operating System: Windows 11 Enterprise

Description:
When I enable minor grid lines in a TChart, the chart's rendering performance drops significantly. This is an issue when we do realtime redraws on mousemove events (see example) and in general when redrawing multiple/big charts.

Steps to Reproduce:
1. Create a new VCL Forms Application in Delphi.
2. Add the code provided below
3. run the app in 64-bit, maximized (in full screen) (performance is the same in 32-bit, but that is not our usecase)
4. Move the mouse into the chart, and the last point in the fastlineseries will follow the movement of the mouse.
5. Use mouse button click to add points to the chart. The display of minor grid lines will be switched on/off every time you click to add a point.
6. Observe the performance degradation when the chart is rendered with minor grid lines visible and you move the mouse.

Sample Code:

Code: Select all

uses
  VCLTee.Chart, VCLTee.TeEngine, VCLTee.Series;

var
  Chart1: TChart;
  FastLineSeries: TFastLineSeries;

procedure TForm1.AddFastLineSeriesToPlot;
begin
  FastLineSeries := TFastLineSeries.Create(nil);
  FastLineSeries.XValues.Order := loNone;
  FastLineSeries.YValues.Order := loNone;
  FastLineSeries.LinePen.OwnerCriticalSection := nil; // single threaded
  FastLineSeries.LinePen.Style := psSolid;
  FastLineSeries.LinePen.Width := 5;
  FastLineSeries.Color := clRed;
  FastLineSeries.AutoRepaint := False;
  Chart1.AddSeries(FastLineSeries);
end;

procedure TForm1.ShowHideMinorGrid;
var
  MinorGridVisible: Boolean;
begin
  if FastLineSeries.XValues.Count > 0 then begin
    MinorGridVisible := Odd(FastLineSeries.XValues.Count);
    Chart1.BottomAxis.MinorGrid.Visible := MinorGridVisible;
    Chart1.LeftAxis.MinorGrid.Visible := MinorGridVisible;
  end;
end;

procedure TForm1.AddPoint(MouseX, MouseY: Integer);
var
  X: Double;
  Y: Double;
begin
  X := Chart1.BottomAxis.CalcPosPoint(MouseX);
  Y := Chart1.LeftAxis.CalcPosPoint(MouseY);
  FastLineSeries.AddXY(X, Y);
  FastLineSeries.Repaint;
end;

procedure TForm1.MoveLastPoint(MouseX, MouseY : Integer);
var
  Index: Integer;
begin
  if FastLineSeries.XValues.Count > 0 then begin
    Index := FastLineSeries.XValues.Count - 1;
    FastLineSeries.XValues[Index] := Chart1.BottomAxis.CalcPosPoint(MouseX);
    FastLineSeries.YValues[Index] := Chart1.LeftAxis.CalcPosPoint(MouseY);
    FastLineSeries.Repaint;
  end;
end;

procedure TForm1.Chart1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  AddPoint(X, Y);
  ShowHideMinorGrid;
end;

procedure TForm1.Chart1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  MoveLastPoint(X, Y);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Chart1 := TChart.Create(Self);

  Chart1.Parent := Self;
  Chart1.Align := alClient;
  Chart1.View3D := False;
  Chart1.Legend.Visible := False;
  Chart1.ClipPoints := False;
  Chart1.Canvas.ReferenceCanvas.Pen.OwnerCriticalSection := nil; // single threaded
  Chart1.Axes.FastCalc := True;

  Chart1.BottomAxis.SetMinMax(0, 100);
  Chart1.LeftAxis.SetMinMax(0, 10);

  Chart1.OnMouseDown := Chart1MouseDown;
  Chart1.OnMouseMove := Chart1MouseMove;
  // major grid
  Chart1.BottomAxis.Grid.Visible := True;
  Chart1.BottomAxis.Grid.Style := psSolid;
  Chart1.BottomAxis.Grid.Width := 2;
  Chart1.BottomAxis.Grid.Color := clLtGray;
  Chart1.LeftAxis.Grid.Visible := True;
  Chart1.LeftAxis.Grid.Style := psSolid;
  Chart1.LeftAxis.Grid.Width := 2;
  Chart1.LeftAxis.Grid.Color := clLtGray;
  // minor grid
  Chart1.BottomAxis.MinorGrid.Visible := False;
  Chart1.BottomAxis.MinorGrid.Style := psDot;
  Chart1.BottomAxis.MinorGrid.Width := 1;
  Chart1.BottomAxis.MinorGrid.Color := clLtGray;
  Chart1.LeftAxis.MinorGrid.Visible := False;
  Chart1.LeftAxis.MinorGrid.Style := psDot;
  Chart1.LeftAxis.MinorGrid.Width := 1;
  Chart1.LeftAxis.MinorGrid.Color := clLtGray;

  AddFastLineSeriesToPlot;
  AddPoint(10, 10);
end;

I would appreciate any advice or solutions you can provide to improve the performance when minor grid lines are enabled.

Thank you,
Ags


Update:
After further investigation the cause of the performance issue is narrowed down to the PenStyle:

Code: Select all

  Chart1.LeftAxis.MinorGrid.Style := psDot;   // is slow
  Chart1.LeftAxis.MinorGrid.Style := psSolid; // is fast
However that means that the performance issue is also observed if psDot is used for the major grid lines:

Code: Select all

  Chart1.LeftAxis.Grid.Style := psDot; // is slow

Yeray
Site Admin
Site Admin
Posts: 9645
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Performance Issue with Grid Lines and PenStyle psDot

Post by Yeray » Fri Dec 20, 2024 8:27 am

Hello,

I'm afraid GDIPlus is slow at drawing dashed lines. However, moving to OpenGL may be a good option for you.
In that project, just add TeeGLCanvas to the uses clause and initialize the canvas at the end of your FormCreate:

Code: Select all

procedure TForm1.FormCreate(Sender: TObject);
begin
  //...
  Chart1.Canvas:=TGLCanvas.Create;
end;
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

ags
Newbie
Newbie
Posts: 4
Joined: Wed May 18, 2022 12:00 am

Re: Performance Issue with Grid Lines and PenStyle psDot

Post by ags » Wed Jan 22, 2025 12:44 pm

Thank you for the suggestion, the TGLCanvas does draw punctured gridlines faster.
Unfortunately the OpenGL canvas is not a seamless 1:1 replacement of the GDI+ version, and we have run into various small and big issues when testing with our app.

The minor issues are the placement of text and labels, eg the position of chart annotations is a bit different. If we should move to TGLCanvas, we would have to spend some time adjusting the position of the various elements to the changed layout.

The major issue that we have seen, is that something with the panning event handler seems to work in a different way and the code we have that draws an image with data that we decided were not suited for any of the existing series types, just fails in a very noticeable way.

As it stands right now the move to TGLCanvas appear to contain some known-unknowns and maybe some unknown-unknowns that we are not ready to take on. Even tough TGLCanvas solves this particular issue, we have decided to stay with the GDI+ and just avoid the problematic styles when we need real-time performance.

Best regards,
Ags

Yeray
Site Admin
Site Admin
Posts: 9645
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Performance Issue with Grid Lines and PenStyle psDot

Post by Yeray » Thu Jan 23, 2025 2:02 pm

Hello Ags,

Thanks for the explanation.
I hope we can invest more time on improving the TGLCanvas so it can be used as a 1:1 replacement to GDI/GDIPlus.
Could you please expand a little bit on the major issues? I'm not sure to understand what are you doing in the panning event.
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

ags
Newbie
Newbie
Posts: 4
Joined: Wed May 18, 2022 12:00 am

Re: Performance Issue with Grid Lines and PenStyle psDot

Post by ags » Thu Feb 27, 2025 12:56 pm

Hello Yeray

We are compiling a list of changes that we observe when using TGLCanvas instead of the GDI+ canvas, but to keep the thread on track we want to respond to the question of the panning event.

What we observe is that the TChartImageTool works in a different way when panning and TChart uses the TGLCanvas. The result is that the image is not updated properly in our app when using TGLCanvas.

Our use-case is not like the example we have prepared, but the code below illustrates the issue we are trying to understand. We always run 64-bit, the app requires a MainForm with a Chart1 and hooking the FormCreate event handler.

To test, run the app and pan in the chart, using the default, mouse right button.
What you should see is that:
when using the default GDI+ canvas: The image is displaced on mouse move and updated on mouse up.
when using the TGLCanvas: The image is not updated, but seems to be switching between two images and two levels of zooming. The displayed content of TChartImageTool appear to change/alternate between two images on both left and right mouse button clicks.

Br ags

Code: Select all

unit MainForm_;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, VclTee.TeeGDIPlus, VCLTee.TeEngine,
  VCLTee.Series, VCLTee.TeeProcs, VCLTee.Chart, VCLTee.TeeTools, Math,
  VCLTee.TeeGLCanvas {Requires package TeeGL929}
  //
  ;

type
  TForm1 = class(TForm)
    Chart1: TChart;
    procedure FormCreate(Sender: TObject);
    procedure Chart1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure Chart1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure Chart1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure Chart1Scroll(Sender: TObject);
  private
    { Private declarations }
    FDragging: Boolean;
    FStartX, FStartY: Integer;
    ImageTool: TChartImageTool;
    PointSeries: TPointSeries;
    BufferImage: TPicture;
    XDisplacement: Integer;
    YDisplacement: Integer;
    procedure UpdateCenterCoordinates;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate;
begin
//  Chart1.Canvas := TGLCanvas.Create; // use or dont OpenGL canvas to see difference in behaviour when panning

  Chart1.OnMouseDown := Chart1MouseDown;
  Chart1.OnMouseMove := Chart1MouseMove;
  Chart1.OnMouseUp := Chart1MouseUp;
  Chart1.OnScroll := Chart1Scroll;


  Chart1.View3D := False;
  Chart1.BottomAxis.SetMinMax(-5, 5);
  Chart1.LeftAxis.SetMinMax(-5, 5);

  PointSeries := TPointSeries.Create(Self);
  PointSeries.Clear;
  PointSeries.AddXY(-5, -5);
  PointSeries.AddXY( 5,  5);
  PointSeries.Visible := True;
  Chart1.AddSeries(PointSeries);

  BufferImage := TPicture.Create();
  BufferImage.Bitmap.SetSize(100, 100); // Set the size of the image

  ImageTool := TChartImageTool.Create(Self);
  ImageTool.ParentChart := Chart1;
  ImageTool.Series := PointSeries;
  ImageTool.Picture.Assign(BufferImage);
  ImageTool.Visible := True;

  Chart1.Tools.Add(ImageTool);

  UpdateCenterCoordinates;
end;

procedure TForm1.Chart1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  FDragging := True;
  FStartX := X;
  FStartY := Y;
end;

procedure TForm1.Chart1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  if FDragging then
  begin
    XDisplacement := (X - FStartX);
    YDisplacement := (Y - FStartY);
  end;
end;

procedure TForm1.Chart1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  FDragging := False;
  UpdateCenterCoordinates;
end;

procedure TForm1.Chart1Scroll(Sender: TObject);
begin
    // Draw picture - displaced
    ImageTool.Picture.Bitmap.Canvas.Brush.Color := clSkyBlue;
    ImageTool.Picture.Bitmap.Canvas.FillRect(Rect(0, 0, ImageTool.Picture.Bitmap.Width, ImageTool.Picture.Bitmap.Height));
    ImageTool.Picture.Bitmap.Canvas.Draw(XDisplacement, YDisplacement, BufferImage.Graphic);

    PointSeries.Repaint;
    ImageTool.Repaint;
end;

procedure TForm1.UpdateCenterCoordinates;
var
  CenterX, CenterY: Double;
  ChartCenterX, ChartCenterY: Integer;
begin
  BufferImage.bitmap.SetSize(Max(100, Chart1.ChartRect.Width), Max(100, Chart1.ChartRect.Height));

  ChartCenterX := BufferImage.bitmap.Width div 2;
  ChartCenterY := BufferImage.bitmap.Height div 2;

  CenterX := Chart1.BottomAxis.CalcPosPoint(ChartCenterX);
  CenterY := Chart1.LeftAxis.CalcPosPoint(ChartCenterY);

  BufferImage.bitmap.Canvas.FillRect(Rect(0, 0, BufferImage.bitmap.Width, BufferImage.bitmap.Height));
  BufferImage.bitmap.Canvas.TextOut(ChartCenterX-40, ChartCenterY-5, Format('Center: (%.1f, %.1f)', [CenterX, CenterY]));

  ImageTool.Picture.Assign(BufferImage);
end;


end.

Post Reply