如何为任何类型的图表系列显示通用数据标签?
我正在使用随 Delphi 10.4 一起发布的 TeeChart (TChart
) 来显示数据。不同类型的数据显示在不同类型的图表系列中。更具体地说,我支持 4 种不同类型的图表:
- 折线图
- 饼图
- 垂直条形图 水平
- 条形图
对于折线图和垂直条形图,我已经成功实现了一个标签,当用户将鼠标悬停在光标上时,该标签会显示在光标旁边图表的任何部分。此标签显示与这些图表的每个可能系列关联的详细值,例如,某些图表可能有 10 个不同的系列,在这种情况下,所有这些值都会显示在标签中。
现在我尝试为其他 2 种图表类型(饼图和水平条形图)添加相同的支持。然而,我的运气并不好。我们以水平条形图为例。当我应用下面相同的代码时,数据无法正确显示。更具体地说,当我将鼠标悬停在水平条上时,它似乎取决于我将鼠标悬停在水平条的上半部分还是下半部分。将鼠标悬停在上半部分会显示前一项的数据(如果它是图表中的第一项,则根本不会显示任何数据),而将鼠标悬停在条形图的下半部分会显示预期数据,因此它似乎是偏移的。
此外,我根本无法使其在饼图上工作。它显示了标签的一小部分,但标签从未填充,因此它从未成功识别 SeriesIndex
。
此代码的工作方式是,它期望在 TChart
控件上找到 Tag
的值。
- 处理 X 轴(线和垂直条Chart)
- 处理 Y 轴(水平条形图)
图表内部有一个名为 lblChartData
的标签(TChart
也是一个容器)。
然后图表有一个 OnMouseMove
事件处理程序:
procedure TDashChartBase.ChartMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
begin
ShowDataLabel(X, Y);
lblChartData.Left:= X + 15;
lblChartData.Top:= Y;
Chart.Repaint;
end;
然后是 ShowDataLabel
的代码:
procedure TDashChartBase.ShowDataLabel(const X, Y: Integer);
var
S: String;
SeriesIndex: Integer;
V: Double;
I: Integer;
CursorX: Double;
CursorY: Double;
Serie: TChartSeries;
function GetXValueIndex(const ASerie: TChartSeries; const AX: Double): Integer;
var
Idx: Integer;
begin
for Idx := 0 to ASerie.XValues.Count - 1 do begin
if ASerie.XValue[Idx] >= AX then
Break;
end;
Result := Idx - 1;
end;
function GetYValueIndex(const ASerie: TChartSeries; const AY: Double): Integer;
var
Idx: Integer;
begin
for Idx := 0 to ASerie.YValues.Count - 1 do begin
if ASerie.YValue[Idx] >= AY then
Break;
end;
Result := Idx - 1;
end;
begin
try
Chart.Cursor:= crCross;
if not (Chart.SeriesCount > 0) then begin
//Chart does not have any series to display...
lblChartData.Visible:= False;
end else begin
//Get cursor position of first series...
Chart.Series[0].GetCursorValues(CursorX, CursorY);
for I := 0 to Chart.SeriesCount-1 do begin
Serie:= Chart.Series[I];
//Identify index of item based on mouse position...
case Chart.Tag of
1: SeriesIndex:= GetXValueIndex(Serie, CursorX);
else SeriesIndex:= GetYValueIndex(Serie, CursorY);
end;
if (SeriesIndex < 0) or (SeriesIndex > Serie.Count-1) then begin
//Series index is out of range...
lblChartData.Visible:= False;
Break;
end else begin
if I = 0 then begin
//Add the value mark text as the first line in label...
S:= S + Serie.ValueMarkText[SeriesIndex] + sLineBreak;
if (Trim(S) = '') then begin
//No value exists for this plot point...
lblChartData.Visible:= False;
Break;
end;
end;
//Add the value associated with this series to the label...
case Chart.Tag of
1: V:= Double(Serie.ValuesList[1].Value[SeriesIndex]);
else V:= Double(Serie.ValuesList[0].Value[SeriesIndex]);
end;
S:= S + Serie.Title+': '+FormatFloat(Serie.ValueFormat, V) + sLineBreak;
end;
end;
lblChartData.Caption:= S;
end;
except
on E: Exception do begin
lblChartData.Visible:= False;
end;
end;
end;
如何使其适用于其他系列类型?
(注意:上面的代码片段中删除了一些不相关的代码)
I'm using the TeeChart (TChart
) which is distributed with Delphi 10.4 to display data. Different types of data are displayed in different types of chart series. More specifically, I support 4 different types of charts:
- Line Chart
- Pie Chart
- Vertical Bar Chart
- Horizontal Bar Chart
When it comes to the Line Chart and Vertical Bar Chart, I have successfully implemented a label which displays next to the cursor when the user hovers over any part of the chart. This label displays the detailed values associated with each possible series of those charts, and some charts might have 10 different series, for example, in which case all those values show in the label.
Now I am trying to add the same support for the other 2 chart types (Pie and Horizontal Bar). However, I'm not having much luck. Let's take the Horizontal Bar Chart for example. When I apply the same code below, the data does not display correctly. More specifically, when I hover over a horizontal bar, it seems to depend on whether I'm hovering over the upper or lower half of the bar. Hovering over the upper half shows data for the previous item (or nothing at all if it's the first item in the chart), and hovering over the lower half of a bar shows the expected data, so it seems to be offset.
Further, I cannot make it work at all on pie charts. It shows a small little bit of the label, but the label is never populated, so it never successfully identifies the SeriesIndex
.
The way this code works is that it expects to find a value for Tag
on the TChart
control..
- Handles the X axis (line and Vert Bar Chart)
- Handles the Y axis (Horz Bar Chart)
The charts have a label inside them named lblChartData
(the TChart
is also a container).
Then the charts have an OnMouseMove
event handler:
procedure TDashChartBase.ChartMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
begin
ShowDataLabel(X, Y);
lblChartData.Left:= X + 15;
lblChartData.Top:= Y;
Chart.Repaint;
end;
Then the code for ShowDataLabel
:
procedure TDashChartBase.ShowDataLabel(const X, Y: Integer);
var
S: String;
SeriesIndex: Integer;
V: Double;
I: Integer;
CursorX: Double;
CursorY: Double;
Serie: TChartSeries;
function GetXValueIndex(const ASerie: TChartSeries; const AX: Double): Integer;
var
Idx: Integer;
begin
for Idx := 0 to ASerie.XValues.Count - 1 do begin
if ASerie.XValue[Idx] >= AX then
Break;
end;
Result := Idx - 1;
end;
function GetYValueIndex(const ASerie: TChartSeries; const AY: Double): Integer;
var
Idx: Integer;
begin
for Idx := 0 to ASerie.YValues.Count - 1 do begin
if ASerie.YValue[Idx] >= AY then
Break;
end;
Result := Idx - 1;
end;
begin
try
Chart.Cursor:= crCross;
if not (Chart.SeriesCount > 0) then begin
//Chart does not have any series to display...
lblChartData.Visible:= False;
end else begin
//Get cursor position of first series...
Chart.Series[0].GetCursorValues(CursorX, CursorY);
for I := 0 to Chart.SeriesCount-1 do begin
Serie:= Chart.Series[I];
//Identify index of item based on mouse position...
case Chart.Tag of
1: SeriesIndex:= GetXValueIndex(Serie, CursorX);
else SeriesIndex:= GetYValueIndex(Serie, CursorY);
end;
if (SeriesIndex < 0) or (SeriesIndex > Serie.Count-1) then begin
//Series index is out of range...
lblChartData.Visible:= False;
Break;
end else begin
if I = 0 then begin
//Add the value mark text as the first line in label...
S:= S + Serie.ValueMarkText[SeriesIndex] + sLineBreak;
if (Trim(S) = '') then begin
//No value exists for this plot point...
lblChartData.Visible:= False;
Break;
end;
end;
//Add the value associated with this series to the label...
case Chart.Tag of
1: V:= Double(Serie.ValuesList[1].Value[SeriesIndex]);
else V:= Double(Serie.ValuesList[0].Value[SeriesIndex]);
end;
S:= S + Serie.Title+': '+FormatFloat(Serie.ValueFormat, V) + sLineBreak;
end;
end;
lblChartData.Caption:= S;
end;
except
on E: Exception do begin
lblChartData.Visible:= False;
end;
end;
end;
How can I make this work properly for the other series types?
(NOTE: Some irrelevant code was stripped out from above snippets)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
可以重写为:
您可以使用 CalcClickedPart 轻松获取两者。然后,您的鼠标移动处理程序可以编写如下:
Could be rewritten as:
You can easily get both using CalcClickedPart. Your mouse move handler can then be written as follows: