title: TeeChart: Fast way to show series values at current mouse position
body: <p>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:</p>
  
  <p><a href="https://i.stack.imgur.com/Yr7Xe.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/Yr7Xe.png" alt="Target Display"></a></p>
  
  <p>To accomplish this behavior I used an <code>TAnnotationTool</code> and the <code>OnMouseMove</code> event. Additionally I use a <code>TCursorTool</code> with <code>Style := cssVertical</code> and <code>FollowMouse := True</code> 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:</p>
  
  <pre><code>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 &lt; 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 &lt; 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 &gt;= Self.chtMain.BottomAxis.IStartPos) and
      (FX &lt;= Self.chtMain.BottomAxis.IEndPos) and
      (FY &gt;= Self.chtMain.LeftAxis.IStartPos) and
      (FY &lt;= Self.chtMain.LeftAxis.IEndPos) then
      Self.FValAnno.Active := True
    else
      Self.FValAnno.Active := False;
    ...
  end;
  </code></pre>
  
  <p>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. </p>
  
  <p>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?</p>
  
  <p>Thanks in advance for your support! </p>
  
  <p>EDIT: Example code to reproduce this issue</p>
  
  <ol>
  <li>Create a new VCL Project</li>
  <li>Drop a TChart component and a checkbox on the form</li>
  <li>Create the FormCreate for the form and the MouseMoveEvent for the chart</li>
  <li>Switch to the code view an insert the following code:</li>
  </ol>
  
  <p>Code:</p>
  
  <pre><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] &gt;= 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 &lt; 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] &gt; ShownDate then
          begin
            //
            LegendTxt := DateTimeToStr(Serie.XValue[Idx]);
            if (AnnoLst.Count &gt; 0) and
              (ShownDate &gt; 0) then
              AnnoLst[0] := LegendTxt
            else if AnnoLst.Count &gt; 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) &lt;= 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 &lt; 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 &lt; 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 &gt;= Self.chtMain.BottomAxis.IStartPos) and
      (X &lt;= Self.chtMain.BottomAxis.IEndPos) and
      (Y &gt;= Self.chtMain.LeftAxis.IStartPos) and
      (Y &lt;= 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.
  </code></pre>
  
  <ol start="5">
  <li>Press [F9]</li>
  </ol>
  
  <p>I do not observe any difference, whether you use the position annotation code or the other one.</p>