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

标签 delphi annotations teechart delphi-xe3 onmousemove

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

Target Display

为了实现此行为,我使用了 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;

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

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

预先感谢您的支持!

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

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

代码:

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.
  • 按 [F9]
  • 无论您使用位置注释代码还是其他代码,我都没有观察到任何差异。

    最佳答案

    TCursorTool 有一个 FullRepaint 属性(默认为 false),可以使用 XOR 来绘制它,因此不需要绘制完整的图表。每次更新位置时都会重新绘制。而且这很快。

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

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

    关于delphi - TeeChart:在当前鼠标位置显示系列值的快速方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37671484/

    相关文章:

    delphi - 在运行时更改图表栏偏移量 (OffsetPercent)

    java注释不调用重写的方法

    java - Spring MVC : Advanced annotation based mapping

    c# - 如何将图表从 teechart 库添加到现有布局

    Delphi - 检查内存是否正在释放 "on time"

    java - Android apt 从 1.4 升级到 1.7 版本

    delphi - 如何在delphi中运行时将图表系列添加到dbchart

    Delphi + Binance Api + 限价单问题 无效签名

    delphi - 为什么 RAD Studio Seattle 10 安装 Windows 10 SDK? (它可以与较新的 SDK 一起使用吗?)

    delphi - 如何从字符串中删除控制字符?