Huuuh, this is tricky, and I cannot give a general solution at the moment because the bar series comes in serveral flavors, stacked bars, or side-by-side bars. My solution is valid only for the simple case of a single bar series.
When a ChartTool detects a mouseclick it iterates through all series, and there, through all datapoints to find the one which is closest to the clicked point. For this purpose, the series provides a function GetNearestPoint which returns true if such a point is found and passes additional information in a TNearestPointResult parameter. This function is implemented in a very basic chart type and checks if the distance of a data point from the click point is smaller than a given tolerance. It works for most series types, but in case of the bar series it should check whether the clicked point simply is inside the drawn rectangle of the bar.
Fortunately, the function GetNearestPoint is virtual and can be overridden by a descendant class of the TBarSeries. This is the code which works for me:
function TBarSeries.GetNearestPoint(const AParams: TNearestPointParams;
out AResults: TNearestPointResults): Boolean;
var
i, lb, ub: Integer;
pt, pt1, pt2: TPoint;
sp: TDoublePoint;
barwidth: Integer;
begin
AResults.FDist := Sqr(AParams.FRadius) + 1;
AResults.FIndex := -1;
// Iterate through all points of the series
for i := 0 to Count - 1 do begin
sp := Source[i]^.Point;
if IsNan(sp) then continue;
// Calculate bar width
barwidth := GetBarWidth(i) div 2;
// Calculate image coordinates of the data point (this is the center of
// bar end)
pt := ParentChart.GraphToImage(AxisToGraph(sp));
pt1 := pt;
pt1.X := pt1.X - barwidth div 2;
// --> pt1 is the top-left corner point of the bar, in image coordinates
// Calculate the bottom of the bar - we assume that the bar starts at the
// "ZeroLevel" - this is not valid for stacked bars!
sp.Y := 0;
pt2 := ParentChart.GraphToImage(AxisToGraph(sp));
pt2.X := pt2.X + barwidth div 2;
// --> pt2 is the bottom-right corner point of the bar
// Check if the clicked point (AParams.FPoint) is inside the rectangle
// spanned by the corner point pt1 and pt2
if PtInRect(Rect(pt1.X, pt1.Y, pt2.X, pt2.Y), AParams.FPoint) then
begin
AResults.FDist := 0;
AResults.FIndex := i;
AResults.FImg := pt;
AResults.FValue := sp;
break;
end;
end;
Result := AResults.FIndex >= 0;
end;
It just does what I described above in words. You may notice that I call the derived series TBarSeries like its ancestor. This is a trick found sometimes in the Delphi community: because the new series only changes a virtual method the compiler can be cheated to replace the original TBarSeries by the newTBarSeries at runtime if the unit is in the uses list after TChartSeries (where the original TBarSeries is implemented). It you need the new BarSeries only once then simply implement it in the unit with the chart, like in the attached demo. This way you still can work with the bar series at design time in the object inspector (of course, if you create the series at runtime then it can be named arbitrarily).
In case of stacked bar series, the GetNearestPoint method should check the other series to calculate the y value where the previous series (onto which the current series is stacked) ends.
In case of side-by-side bar series the method must calculate the horizontal corner points of the bars being surrounded by the other adjacent bars.
The final code should also take care of the case of rotated bars.
Looking into the code of TBarSeries, this does not appear to be too difficult, but at the moment I don't have the time to figure it out.