TeeChart按给定的X值

时间:2016-05-12 07:23:50

标签: delphi teechart

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

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

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

提前感谢您的帮助。

2 个答案:

答案 0 :(得分:2)

您可以使用Locate TChartValueList方法获取相应数据输入的索引

帮助示例:

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;

答案 1 :(得分:0)

解决方案是插值算法,如{{3>}中的所有要素\欢迎!\图表样式\标准\行(条带)\插值行示例所示。以下是该示例的完整代码:

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.