TeeChart 通过给定的 X 值获取系列 Y 值或索引
TeeChart Get Series Y value or index by given X value
我遇到以下问题:我正在将 Delphi XE3 与 TeeChart 一起使用,我想通过给定的 X 值检索 Y 值或系列的值索引。我的系列是一个时间序列,日期在 X 轴上。我知道图表上的日期,我想显示最接近该日期的相应 Y 值。
TChart 或TChartSeries 组件有什么方法或功能可以实现吗?或者我是否需要遍历该系列直到到达所选日期?
无法使用 CursorPostion 方法,因为光标可能在任何地方。
在此先感谢您的帮助。
您可以使用 TChartValueList
的 Locate
方法来获取适当数据条目的 index。
来自帮助的示例:
tmp:=LineSeries1.XValues.Locate(EncodeDate(2007,1,1));
if tmp<>-1 then ...
编辑:此方法适用于精确巧合。
如果您的 X 值已排序(默认模式),那么您可以在 XValues 中使用二进制搜索来快速找到最接近的值。
例如,我们可以将 this code 修改为 return 最接近的值索引而不是 -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;
解决方案是一种插值算法,如 All Features\Welcome!\Chart styles\Standard\Line(Strip)\Interpolating Lines 示例所示 features demo。这是示例的完整代码:
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.
我遇到以下问题:我正在将 Delphi XE3 与 TeeChart 一起使用,我想通过给定的 X 值检索 Y 值或系列的值索引。我的系列是一个时间序列,日期在 X 轴上。我知道图表上的日期,我想显示最接近该日期的相应 Y 值。
TChart 或TChartSeries 组件有什么方法或功能可以实现吗?或者我是否需要遍历该系列直到到达所选日期?
无法使用 CursorPostion 方法,因为光标可能在任何地方。
在此先感谢您的帮助。
您可以使用 TChartValueList
的 Locate
方法来获取适当数据条目的 index。
来自帮助的示例:
tmp:=LineSeries1.XValues.Locate(EncodeDate(2007,1,1));
if tmp<>-1 then ...
编辑:此方法适用于精确巧合。
如果您的 X 值已排序(默认模式),那么您可以在 XValues 中使用二进制搜索来快速找到最接近的值。
例如,我们可以将 this code 修改为 return 最接近的值索引而不是 -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;
解决方案是一种插值算法,如 All Features\Welcome!\Chart styles\Standard\Line(Strip)\Interpolating Lines 示例所示 features demo。这是示例的完整代码:
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.