如何为任何类型的图表系列显示通用数据标签?

发布于 2025-01-12 00:38:25 字数 4062 浏览 5 评论 0原文

我正在使用随 Delphi 10.4 一起发布的 TeeChart (TChart) 来显示数据。不同类型的数据显示在不同类型的图表系列中。更具体地说,我支持 4 种不同类型的图表:

  1. 折线图
  2. 饼图
  3. 垂直条形图 水平
  4. 条形图

对于折线图和垂直条形图,我已经成功实现了一个标签,当用户将鼠标悬停在光标上时,该标签会显示在光标旁边图表的任何部分。此标签显示与这些图表的每个可能系列关联的详细值,例如,某些图表可能有 10 个不同的系列,在这种情况下,所有这些值都会显示在标签中。

屏幕截图 -折线图中的数据标签

现在我尝试为其他 2 种图表类型(饼图和水平条形图)添加相同的支持。然而,我的运气并不好。我们以水平条形图为例。当我应用下面相同的代码时,数据无法正确显示。更具体地说,当我将鼠标悬停在水平条上时,它似乎取决于我将鼠标悬停在水平条的上半部分还是下半部分。将鼠标悬停在上半部分会显示前一项的数据(如果它是图表中的第一项,则根本不会显示任何数据),而将鼠标悬停在条形图的下半部分会显示预期数据,因此它似乎是偏移的。

屏幕截图 -水平条形图中的数据标签

此外,我根本无法使其在饼图上工作。它显示了标签的一小部分,但标签从未填充,因此它从未成功识别 SeriesIndex

屏幕截图 -饼图中的数据标签

此代码的工作方式是,它期望在 TChart 控件上找到 Tag 的值。

  1. 处理 X 轴(线和垂直条Chart)
  2. 处理 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:

  1. Line Chart
  2. Pie Chart
  3. Vertical Bar Chart
  4. 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.

Screenshot - data label in line chart

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.

Screenshot - data label in horizontal bar chart

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.

Screenshot - data label in pie chart

The way this code works is that it expects to find a value for Tag on the TChart control..

  1. Handles the X axis (line and Vert Bar Chart)
  2. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

℉絮湮 2025-01-19 00:38:25

如何为任何类型的图表系列显示通用数据标签?

可以重写为:

如何在任何类型的图表系列中查找鼠标光标下的系列和点索引?

您可以使用 CalcClickedPart 轻松获取两者。然后,您的鼠标移动处理程序可以编写如下:

TDashChartBase.ChartMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var Part:TChartClickedPart;
begin
  lblChartData.Visible:=false;
  Chart.CalcClickedPart(Point(X,Y),Part);
  if (Part.Part=cpSeries) or (Part.Part=cpSeriesPointer) then
  begin
    //extract all needed data using Part.ASeries and Part.PointIndex
    lblChartData.Caption:=Part.ASeries.Name + ';' + Part.PointIndex.ToString;
    lblChartData.Left:= X + 15;
    lblChartData.Top:= Y;
    lblChartData.Visible:=true;
  end;
end;

How to show a universal data label for any type of chart series?

Could be rewritten as:

How to find series and point's index under mouse cursor for any type of chart series?

You can easily get both using CalcClickedPart. Your mouse move handler can then be written as follows:

TDashChartBase.ChartMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var Part:TChartClickedPart;
begin
  lblChartData.Visible:=false;
  Chart.CalcClickedPart(Point(X,Y),Part);
  if (Part.Part=cpSeries) or (Part.Part=cpSeriesPointer) then
  begin
    //extract all needed data using Part.ASeries and Part.PointIndex
    lblChartData.Caption:=Part.ASeries.Name + ';' + Part.PointIndex.ToString;
    lblChartData.Left:= X + 15;
    lblChartData.Top:= Y;
    lblChartData.Visible:=true;
  end;
end;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文