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
- Create a new VCL Project
- Drop a TChart component and a checkbox on the form
- Create the FormCreate for the form and the MouseMoveEvent for the chart
- Switch to the code view an insert the following code:
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.
- Press [F9]
I do not observe any difference, whether you use the position annotation code or the other one.
if
and related setting of the annotation position should not take any significant time (but why are you referring toFY
instead ofY
in the last one?). Same for the lastif
. But getting the values from the underlying data to the AnnoLst, might take time, but you did not show that, so impossible to evaluate. – Tom BrunbergAnnoLst
. – Tom BrunbergFY
was a mistake I changed it. I timed everything in the mouse move event and the Data -> AnnoLst part took a not measurable time. I only observed a differerence when I leave out this code above. Maybe you are right it not the code above do not cause the lag, but something in the backend of theTChart
component. I will put some example in the question that you can evaluate the behaviour. – FlorianSchunke