TeeChart:在当前鼠标位置显示系列值的快速方法

TeeChart: Fast way to show series values at current mouse position

如果光标在图表上,我想在当前鼠标位置显示所有系列的值。完全如图所示:

为了完成此行为,我使用了 TAnnotationToolOnMouseMove 事件。此外,我使用 TCursorToolStyle := cssVerticalFollowMouse := True 在当前移动位置绘制一条垂直线。不幸的是,这个解决方案非常慢。如果系列计数大于 10,则用户已经可以观察到鼠标后的注释 运行 有大约 500 毫秒的滞后。在调查这个问题的过程中,我发现 MouseMoveEvent 的这一部分是瓶颈:

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;

如果我使用垂直线上方的代码和光标后约 500 毫秒的注释 运行,系列计数为 100。滞后增加系列计数越高。另一方面,如果我不使用注释代码,则垂直线 运行 仅延迟约 100 毫秒。

是否有任何其他工具可以使用 TChart 组件更快地完成此行为?或者我可以使用任何属性来加快速度?

在此先感谢您的支持!

编辑:重现此问题的示例代码

  1. 创建一个新的 VCL 项目
  2. 在表单上拖放一个 TChart 组件和一个复选框
  3. 为表单创建 FormCreate 并为图表创建 MouseMoveEvent
  4. 切换到代码视图并插入以下代码:

代码:

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.
  1. 按[F9]

无论您使用位置注释代码还是其他代码,我都没有发现任何区别。

TCursorTool 有一个 FullRepaint 属性(默认情况下 false)使用 XOR 绘制它,所以整个图表不需要每次都重新绘制更新其位置。这很快。

但是,TAnnotationTool 不包括这种可能性,因此,当您更新 FAnnot 文本或位置时,您将强制重新绘制图表,并且有很多点会使过程变慢。

您可以使用 TLabel 组件而不是使用 TAnnotationTool 来绘制文本。