I want to show the values of all series at the current mouse position if the cursor is on the chart. Exactly as it is displayed in this figure:
To accomplish this behavior I used an TAnnotationTool
and the OnMouseMove
event. Additionally I use a TCursorTool
with Style := cssVertical
and FollowMouse := True
to draw a vertical line at the current mouve position. Unfortunately this solution is very slow. If the series count is greater than 10 the user already could observe that the annotation run after the mouse with a lag of about 500ms. During my investigation of this issue, I found out that this part of the MouseMoveEvent is the bottleneck:
chtMain : TChart;
FValAnno : TAnnotationTool;
...
TfrmMain.chtMainMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer)
var
HasData : Boolean;
AnnoLst : TStrings;
begin
...
if HasData then
Self.FValAnno.Text := AnnoLst.Text
else
Self.FValAnno.Text := 'No data';
//
if (X < Self.chtMain.Width - Self.FValAnno.Width - 5) then
Self.FValAnno.Shape.Left := X + 10
else
Self.FValAnno.Shape.Left := X - Self.FValAnno.Width - 15;
//
if (Y < Self.chtMain.Height - Self.FValAnno.Height - 5) then
Self.FValAnno.Shape.Top := Y + 10
else
Self.FValAnno.Shape.Top := Y - Self.FValAnno.Height - 15;
//
if (FX >= Self.chtMain.BottomAxis.IStartPos) and
(FX <= Self.chtMain.BottomAxis.IEndPos) and
(FY >= Self.chtMain.LeftAxis.IStartPos) and
(FY <= Self.chtMain.LeftAxis.IEndPos) then
Self.FValAnno.Active := True
else
Self.FValAnno.Active := False;
...
end;
If I use the code above the vertical line and the annotation run after the cursor by about 500ms at a series count of 100. The lag increases the higher the series count is. On the other hand if I do not use the annotation code the vertical line run after only by a lag of about 100ms.
Is there any other tool to accomplish this benaviour much faster with the TChart components? Or are there any properties I can play with to make this faster?
Thanks in advance for your support!
EDIT: Example code to reproduce this issue
Code:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VclTee.TeeGDIPlus,
VCLTee.TeEngine, Vcl.ExtCtrls, VCLTee.TeeProcs, VCLTee.Chart, VCLTee.TeeTools,
Vcl.StdCtrls;
type
TForm1 = class(TForm)
chtMain: TChart;
chkAnno: TCheckBox;
procedure FormCreate(Sender: TObject);
procedure chtMainMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
private
FCursor : TCursorTool;
FAnno : TAnnotationTool;
public
{ Public-Deklarationen }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
VCLTee.Series,
System.DateUtils;
const
ARR_MAXS : array[0..3] of Double = (12.5, 25.8, 2.8, 56.7);
procedure TForm1.chtMainMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
function GetXValueIndex(const ASerie: TChartSeries; const AX: Double): Integer;
var
index: Integer;
begin
for index := 0 to ASerie.XValues.Count - 1 do
begin
if ASerie.XValue[index] >= AX then
Break;
end;
//
Result := index - 1;
end;
var
Idx, I : Integer;
CursorX,
CursorY,
Value : Double;
Serie : TChartSeries;
LegendTxt : string;
AnnoLst : TStrings;
HasData : Boolean;
ShownDate : TDateTime;
begin
//
if not Self.chkAnno.Checked then
begin
//
FAnno.Text := Format('Position:'#13#10' X: %d'#13#10' Y: %d', [X, Y]);
end
else
begin
//
if (Self.chtMain.SeriesCount < 1) then
begin
//
if Assigned(Self.FAnno) then
Self.FAnno.Active := False;
Exit;
end;
//
Self.chtMain.Series[0].GetCursorValues(CursorX, CursorY);
//
AnnoLst := TStringList.Create;
try
//
ShownDate := 0;
HasData := False;
for I := 0 to Self.chtMain.SeriesCount - 1 do
begin
//
Serie := Self.chtMain.Series[I];
//
Idx := GetXValueIndex(Serie, CursorX);
if Serie.XValue[Idx] > ShownDate then
begin
//
LegendTxt := DateTimeToStr(Serie.XValue[Idx]);
if (AnnoLst.Count > 0) and
(ShownDate > 0) then
AnnoLst[0] := LegendTxt
else if AnnoLst.Count > 0 then
AnnoLst.Insert(0, LegendTxt)
else
AnnoLst.Add(LegendTxt);
HasData := True;
ShownDate := Serie.XValue[Idx];
end;
//
LegendTxt := Format('Serie: %d', [I]);
if Length(LegendTxt) <= 25 then
LegendTxt := Format('%-25s', [LegendTxt])
else
LegendTxt := Format('%s...', [LegendTxt.Substring(0, 22)]);
//
Value := Serie.YValue[Idx] * Abs(ARR_MAXS[I]);
LegendTxt := Format('%s: %3.3f %s', [LegendTxt, Value, 'none']);
AnnoLst.Add(LegendTxt);
end;
FAnno.Text := AnnoLst.Text;
finally
FreeAndNil(AnnoLst);
end;
end;
if (X < Self.chtMain.Width - Self.FAnno.Width - 5) then
Self.FAnno.Shape.Left := X + 10
else
Self.FAnno.Shape.Left := X - Self.FAnno.Width - 15;
//
if (Y < Self.chtMain.Height - Self.FAnno.Height - 5) then
Self.FAnno.Shape.Top := Y + 10
else
Self.FAnno.Shape.Top := Y - Self.FAnno.Height - 15;
//
if (X >= Self.chtMain.BottomAxis.IStartPos) and
(X <= Self.chtMain.BottomAxis.IEndPos) and
(Y >= Self.chtMain.LeftAxis.IStartPos) and
(Y <= Self.chtMain.LeftAxis.IEndPos) then
Self.FAnno.Active := True
else
Self.FAnno.Active := False;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Idx, J : Integer;
Serie : TFastLineSeries;
Start : TDateTime;
Value : Double;
begin
//
Self.chtMain.View3D := False;
Self.chtMain.Align := alClient;
Self.chtMain.BackColor := clWhite;
Self.chtMain.Color := clWhite;
Self.chtMain.Gradient.Visible := False;
Self.chtMain.Legend.LegendStyle := lsSeries;
Self.chtMain.Zoom.Allow := False;
Self.chtMain.AllowPanning := pmNone;
Self.chtMain.BackWall.Color := clWhite;
Self.chtMain.BackWall.Gradient.Visible := False;
Self.chtMain.LeftAxis.Automatic := False;
Self.chtMain.LeftAxis.Minimum := 0;
Self.chtMain.LeftAxis.Maximum := 2;
Self.chtMain.LeftAxis.Increment := 0.1;
Self.chtMain.LeftAxis.Visible := True;
Self.chtMain.LeftAxis.AxisValuesFormat := '#,##0.## LV';
//
Self.chtMain.BottomAxis.DateTimeFormat := 'dd.mm.yyyy hh:mm:ss';
Self.chtMain.BottomAxis.Increment := 1 / 6;
Self.chtMain.BottomAxis.Automatic := True;
Self.chtMain.BottomAxis.LabelsSize := 32;
Self.chtMain.BottomAxis.LabelsMultiLine := True;
Self.chtMain.MarginBottom := 6;
Self.chtMain.BottomAxis.Title.Caption := 'Date';
Self.chtMain.BottomAxis.Visible := False;
FAnno := Self.chtMain.Tools.Add(TAnnotationTool) as TAnnotationTool;
FAnno.Active := False;
FAnno.Shape.CustomPosition := True;
FCursor := Self.chtMain.Tools.Add(TCursorTool) as TCursorTool;
FCursor.FollowMouse := True;
FCursor.Style := cssVertical;
Randomize;
Start := Now;
for Idx := 0 to 3 do
begin
//
Serie := Self.chtMain.AddSeries(TFastLineSeries) as TFastLineSeries;
Serie.FastPen := True;
Serie.ShowInLegend := False;
Serie.XValues.DateTime := True;
Serie.VertAxis := aLeftAxis;
Serie.ParentChart := Self.chtMain;
for J := 1 to 1000 do
begin
//
Value := Random * ARR_MAXS[Idx] * 1.8;
Serie.AddXY(IncSecond(Start, J), Value / ARR_MAXS[Idx]);
end;
end;
end;
end.
I do not observe any difference, whether you use the position annotation code or the other one.
The TCursorTool
has a FullRepaint
property (false
by default) to draw it using XOR so the full chart doesn't need to be repainted everytime it updates its position. And this is fast.
However, the TAnnotationTool
doesn't include this possibility so, when you update your FAnnot text or position, you are forcing a chart repaint, and having many points makes the process slower.
You could use a TLabel
component instead of using a TAnnotationTool
to draw the text.