Reputation: 27286
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:
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..
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)
Upvotes: 0
Views: 553
Reputation: 5990
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;
Upvotes: 3