Page 1 of 1

Performance Issue with Grid Lines and PenStyle psDot

Posted: Thu Dec 19, 2024 9:43 am
by 16593767
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

Re: Performance Issue with Grid Lines and PenStyle psDot

Posted: Fri Dec 20, 2024 8:27 am
by yeray
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;

Re: Performance Issue with Grid Lines and PenStyle psDot

Posted: Wed Jan 22, 2025 12:44 pm
by 16593767
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

Re: Performance Issue with Grid Lines and PenStyle psDot

Posted: Thu Jan 23, 2025 2:02 pm
by yeray
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.

Re: Performance Issue with Grid Lines and PenStyle psDot

Posted: Thu Feb 27, 2025 12:56 pm
by 16593767
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.