【问题标题】:TeeChart Get Series Y value or index by given X valueTeeChart 通过给定的 X 值获取 Y 系列值或索引
【发布时间】:2016-05-12 07:23:50
【问题描述】:

我有以下问题:我正在使用带有 TeeChart 的 Delphi XE3,我想通过给定的 X 值检索 Y 值或系列的值索引。我的系列是一个以 X 轴为日期的时间序列。我知道图表上的日期,我想显示最接近该日期的相应 Y 值。

有没有 TChart 或 TChartSeries 组件的任何方法或功能来实现这一点?或者我是否需要遍历该系列,直到达到所选日期?

不能使用 CursorPostion 方法,因为光标可以在任何地方。

提前感谢您的帮助。

【问题讨论】:

    标签: delphi teechart


    【解决方案1】:

    您可以使用TChartValueListLocate 方法来获取相应数据条目的索引

    帮助示例:

    tmp:=LineSeries1.XValues.Locate(EncodeDate(2007,1,1));
    if tmp<>-1 then ...
    

    编辑:此方法适用于完全巧合。

    如果您的 X 值已排序(默认模式),那么您可以在 XValues 中使用二进制搜索来快速找到最接近的值。
    例如,我们可以修改this code 以返回最接近的值索引而不是-1,或者对两个相邻值使用线性插值(如果适用)。

      //assumes A.Order = loAscending (default)
      function FindClosestIndex(const Value: Double; A: TChartValueList): Integer;
      var
        ahigh, j, alow: integer;
      begin
        // extra cases
        if A.Count = 0 then
          Exit(-1);
        if Value <= A.First then
          Exit(0);
        if Value >= A.Last then
          Exit(A.Count - 1);
    
        // binary search
        alow := 0;
        ahigh := A.Count - 1;
        while ahigh - alow > 1 do begin
          j := (ahigh + alow) div 2;
          if Value <= A[j] then
            ahigh := j
          else
            alow := j;
        end;
    
        // choose the closest from ahigh, alow
        Result := ahigh - Ord(A[ahigh] - Value >= Value - A[alow])
      end;
    

    【讨论】:

    • 首先感谢您的快速回复。不幸的是,如果系列准确包含给定日期,则 Locate 方法仅提供有效索引。就我而言,不太可能将日期传递给该系列确切包含的方法。日期就在附近。
    • 我不明白为什么这个答案的赞太少了。太棒了!
    【解决方案2】:

    解决方案是一种插值算法,如features demo 中的All Features\Welcome!\Chart styles\Standard\Line(Strip)\Interpolating Lines 示例所示。以下是示例的完整代码:

    unit Line_Interpolate;
    {$I TeeDefs.inc}
    
    interface
    
    uses
      {$IFNDEF LINUX}
      Windows, Messages,
      {$ENDIF}
      SysUtils, Classes,
      {$IFDEF CLX}
      QGraphics, QControls, QForms, QDialogs, QExtCtrls, QStdCtrls, QComCtrls,
      {$ELSE}
      Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, ComCtrls,
      {$ENDIF}
      Base, TeEngine, Series, TeeProcs, Chart, TeeTools, TeeGDIPlus;
    
    type
      TLineInterpolateForm = class(TBaseForm)
        Series1: TLineSeries;
        CheckBox1: TCheckBox;
        Series2: TLineSeries;
        Series3: TLineSeries;
        ChartTool1: TCursorTool;
        ChartTool2: TGridBandTool;
        procedure FormCreate(Sender: TObject);
        procedure Chart1AfterDraw(Sender: TObject);
        procedure ChartTool1Change(Sender: TCursorTool; x, y: Integer;
          const XValue, YValue: Double; Series: TChartSeries;
          ValueIndex: Integer);
      private
        { Private declarations }
        xval: Double;
        function InterpolateLineSeries(Series: TChartSeries;XValue: Double): Double; overload;
        function InterpolateLineSeries(Series: TChartSeries; FirstIndex,
                                      LastIndex: Integer; XValue: Double): Double; overload;
      public
        { Public declarations }
      end;
    
    implementation
    
    {$IFNDEF CLX}
    {$R *.DFM}
    {$ELSE}
    {$R *.xfm}
    {$ENDIF}
    
    procedure TLineInterpolateForm.FormCreate(Sender: TObject);
    var i: Integer;
    begin
      inherited;
    
      for i:=0 to Chart1.SeriesCount-1 do
        Chart1[i].FillSampleValues;
    end;
    
    function TLineInterpolateForm.InterpolateLineSeries(Series: TChartSeries;
      XValue: Double): Double;
    begin
      result:=InterpolateLineSeries(Series,Series.FirstDisplayedIndex,Series.LastValueIndex,XValue);
    end;
    
    function TLineInterpolateForm.InterpolateLineSeries(Series: TChartSeries;
      FirstIndex, LastIndex: Integer; XValue: Double): Double;
    var
      Index: Integer;
      dx,dy: Double;
    begin
      for Index:=FirstIndex to LastIndex do
        if Series.XValues.Value[Index]>XValue then break;
    
      //safeguard
      if (Index<1) then Index:=1
      else if (Index>=Series.Count) then Index:=Series.Count-1;
    
      // y=(y2-y1)/(x2-x1)*(x-x1)+y1
      dx:=Series.XValues.Value[Index] - Series.XValues.Value[Index-1];
      dy:=Series.YValues.Value[Index] - Series.YValues.Value[Index-1];
    
      if (dx<>0) then
        result:=dy*(XValue - Series.XValues.Value[Index-1])/dx + Series.YValues.Value[Index-1]
      else result:=0;
    end;
    
    procedure TLineInterpolateForm.Chart1AfterDraw(Sender: TObject);
    var xs, ys, i: Integer;
    begin
      if CheckBox1.Checked then
      begin
        xs := Chart1.Axes.Bottom.CalcXPosValue(xval);
    
        for i:=0 to Chart1.SeriesCount - 1 do
        begin
          ys := Chart1[i].GetVertAxis.CalcYPosValue(InterpolateLineSeries(Chart1[i],xval));
          Chart1.Canvas.Brush.Color := Chart1[i].Color;
          Chart1.Canvas.Ellipse(xs-4,ys-4,xs+4,ys+4);
        end;
      end;
    end;
    
    procedure TLineInterpolateForm.ChartTool1Change(Sender: TCursorTool; x,
      y: Integer; const XValue, YValue: Double; Series: TChartSeries;
      ValueIndex: Integer);
    var
      i: Integer;
    begin
      xval := XValue;
    
      With Chart1.Title.Text do
      begin
        Clear;
        for i:=0 to Chart1.SeriesCount - 1 do
            Add(Chart1[i].Name + ': Y('+FloatToStrF(XValue, ffNumber, 8, 2)+')= ' +
                FloatToStrF(InterpolateLineSeries(Chart1[i],XValue), ffNumber, 8, 2)+#13#10);
      end;
    end;
    
    initialization
      RegisterClass(TLineInterpolateForm);
    end.
    

    【讨论】:

    • 这正是我想要避免做的事情:“或者我是否需要遍历系列直到我到达选定的日期?”。在您的代码中,您遍历系列的所有点,直到找到大于给定xXValuefor Index:=FirstIndex to LastIndex do if Series.XValues.Value[Index]&gt;XValue then break;。如果你有大约 100k 数据点,那就是慢慢来。因此二分查找是最好的解决方案。
    • @FlorianSchunke 当然。这只是分数很少的系列的替代方案。
    猜你喜欢
    • 2018-11-02
    • 1970-01-01
    • 1970-01-01
    • 2020-02-09
    • 1970-01-01
    • 1970-01-01
    • 2011-09-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多