Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / lazarus / 2014-09

Hint as text over TAChart

5 messages · 2 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Leonardo M. Ramé

Hi, I want to display the value of the point below the mouse cursor as text over the chart.

The way I'm trying to implement this is by first add a TChartToolSet and DataPointHintTool, with an onHintPosition event handler to determine the point value, for example:

procedure TForm1.ChartToolset1DataPointHintTool1HintPosition(
   ATool: TDataPointHintTool; var APoint: TPoint);
begin
   FmyHint := Format('O: %f, H: %f, L: %f, C: %f',
     [FOHLCArray[ATool.PointIndex].open,
     FOHLCArray[ATool.PointIndex].high,
     FOHLCArray[ATool.PointIndex].low,
     FOHLCArray[ATool.PointIndex].close]);
end;

This just set a value for a variable, in this case FMyHint. Then, to draw the value over the chart, I use the onAfterPaint event of TAChart:

procedure TForm1.Chart1AfterPaint(ASender: TChart);
begin
   Sender.Canvas.TextOut(10, 10, FmyHint);
end;

But this event is not called after DataPointHintToolPosition, but on other events, such as Form.onResize.

One question is, is this the correct way to display text over a TAChart?, I don't like to add a TLabel over the chart.

The second question, how can I force a call to TAChart.onAftertPaint?.

Regards,

Reply by email

Werner Pamler

Using the TDataPointHintTool is correct. It is just a little bit awkward to use... In particular you are not picking the correct event.

Here's a step-by-step instruction. Code shown will be based on the "financial" demo which I added to TAChart recently:

* Add a TChartToolset to the form
* Set the chart's "Toolset" property to the toolset added.
* Add a TDatapointHintTool.
* Set "DistanceMode" to cdmOnlyX - this means that only the x coordinate of the mouse is evaluated to find the data point under the mouse; otherwise you'd have to move the move correctly to x and y. "GrabRadius" is the tolerance for point detection; you may want to increase the value a bit.
* Add code to the "OnHint" event of the DatapointHintTool, this is the code which generates the hint text. Adapt your code to the following which works with the "financial" demo (note that the hint string may contain line breaks!):

    procedure TMainForm.ChartToolset1DataPointHintTool1Hint(ATool:
    TDataPointHintTool;
       const APoint: TPoint; var AHint: String);
    var
       ser: TOpenHighLowCloseSeries;
    begin
       ser := ATool.Series as TOpenHighLowCloseSeries;
       AHint := Format('Date: %s'#13'  Open: %.2f'#13'  High: %.2f'#13'
    Low: %.2f'#13'  Close: %.2f', [
         ser.ListSource[Atool.PointIndex]^.Text,                // In
    this example the date is not stored in "x", but in the "Text" of the
    data point
         ser.ListSource[ATool.PointIndex]^.YList[0],
         ser.ListSource[ATool.PointIndex]^.YList[2],
         ser.ListSource[ATool.PointIndex]^.Y,
         ser.ListSource[ATool.PointIndex]^.YList[1]
       ]);
    end;
  * When you compile the hints should work already. However, they are
    not properly positioned. I'd prefer to move them above a data point
    and center them horizontally to the data points. The OnHintPosition
    event is responsible to determine the location of the upper/left
    corner of the hint window.
    procedure
    TMainForm.ChartToolset1DataPointHintTool1HintPosition(ATool:
    TDataPointHintTool;
       var APoint: TPoint);
    var
       ser: TOpenHighLowCloseSeries;
       x, y: Integer;
       w, h: Integer;
       r: TRect;
       hintwnd : THintWindow;
       s: String;
    begin
       { Calculate screen coordinates of the "high" point }
       ser := ATool.Series as TOpenHighLowCloseSeries;
       x :=
    FinancialChart.XGraphToImage(ser.ListSource[ATool.PointIndex]^.X);
       y :=
    FinancialChart.YGraphToImage(ser.ListSource[ATool.PointIndex]^.YList[2]);
         // "High" value, i.e. max of data point
       { Calculate size of hint window }
       // Get hint text - just call the event handler of OnHint
       ChartToolset1DataPointHintTool1Hint(ATool, APoint, s);
       // Calculation - borrowed from TATools
       hintwnd := THintWindow.Create(nil);
       try
         r := hintwnd.CalcHintRect(FinancialChart.Width, s, nil);
         w := r.Right - r.Left;  // Hint width
         h := r.Bottom - r.Top;  // Hint height
       finally
         hintwnd.Free;
       end;
       // Center hint horizontally relative to data point
       APoint.x := x - w div 2;
       // Move hint 10 pixels above the "High" data point
       APoint.y := y - h - 10;
       // Hint coordinates are relative to screen
       APoint := FinancialChart.ClientToScreen(APoint);
    end;

Regarding your second question how to call "OnAfterPaint": Just call it like I call the OnHint event in above example.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lazarus.freepascal.org/pipermail/lazarus/attachments/20140920/2410bcdc/attachment.html>

Reply by email

Leonardo M. Ramé
to Werner Pamler
Werner Pamler wrote 95 lines
Using the TDataPointHintTool is correct. It is just a little bit awkward to use... In particular you are not picking the correct event. Here's a step-by-step instruction. Code shown will be based on the "financial" demo which I added to TAChart recently: * Add a TChartToolset to the form * Set the chart's "Toolset" property to the toolset added. * Add a TDatapointHintTool. * Set "DistanceMode" to cdmOnlyX - this means that only the x coordinate of the mouse is evaluated to find the data point under the mouse; otherwise you'd have to move the move correctly to x and y. "GrabRadius" is the tolerance for point detection; you may want to increase the value a bit. * Add code to the "OnHint" event of the DatapointHintTool, this is the code which generates the hint text. Adapt your code to the following which works with the "financial" demo (note that the hint string may contain line breaks!): procedure TMainForm.ChartToolset1DataPointHintTool1Hint(ATool: TDataPointHintTool; const APoint: TPoint; var AHint: String); var ser: TOpenHighLowCloseSeries; begin ser := ATool.Series as TOpenHighLowCloseSeries; AHint := Format('Date: %s'#13' Open: %.2f'#13' High: %.2f'#13' Low: %.2f'#13' Close: %.2f', [ ser.ListSource[Atool.PointIndex]^.Text, // In this example the date is not stored in "x", but in the "Text" of the data point ser.ListSource[ATool.PointIndex]^.YList[0], ser.ListSource[ATool.PointIndex]^.YList[2], ser.ListSource[ATool.PointIndex]^.Y, ser.ListSource[ATool.PointIndex]^.YList[1] ]); end; * When you compile the hints should work already. However, they are not properly positioned. I'd prefer to move them above a data point and center them horizontally to the data points. The OnHintPosition event is responsible to determine the location of the upper/left corner of the hint window. procedure TMainForm.ChartToolset1DataPointHintTool1HintPosition(ATool: TDataPointHintTool; var APoint: TPoint); var ser: TOpenHighLowCloseSeries; x, y: Integer; w, h: Integer; r: TRect; hintwnd : THintWindow; s: String; begin { Calculate screen coordinates of the "high" point } ser := ATool.Series as TOpenHighLowCloseSeries; x := FinancialChart.XGraphToImage(ser.ListSource[ATool.PointIndex]^.X); y := FinancialChart.YGraphToImage(ser.ListSource[ATool.PointIndex]^.YList[2]); // "High" value, i.e. max of data point { Calculate size of hint window } // Get hint text - just call the event handler of OnHint ChartToolset1DataPointHintTool1Hint(ATool, APoint, s); // Calculation - borrowed from TATools hintwnd := THintWindow.Create(nil); try r := hintwnd.CalcHintRect(FinancialChart.Width, s, nil); w := r.Right - r.Left; // Hint width h := r.Bottom - r.Top; // Hint height finally hintwnd.Free; end; // Center hint horizontally relative to data point APoint.x := x - w div 2; // Move hint 10 pixels above the "High" data point APoint.y := y - h - 10; // Hint coordinates are relative to screen APoint := FinancialChart.ClientToScreen(APoint); end; Regarding your second question how to call "OnAfterPaint": Just call it like I call the OnHint event in above example.

Great, calling onAfterPaint did the trick. Btw, thanks for the example.

Reply by email

Werner Pamler

The most general way: Use the CurrentExtent of the chart, it is in "graph coorinates", though, and you have to convert it to pixels yourself:

uses
   TAChartUtils;
procedure TForm1.Button1Click(Sender: TObject);
var
   ext: TDoubleRect;
begin
   ext := Chart1.CurrentExtent;
   Label1.Caption := Format('Left = %d, Top = %d, Right = %d, Bottom =
%d', [
     Chart1.XGraphToImage(ext.a.x),   // "a" is the lower left corner
     Chart1.YGraphToImage(ext.b.y),   // "b" is the upper right corner
     Chart1.XGraphToImage(ext.b.x),
     Chart1.YGraphtoImage(ext.a.y)
   ]);
end;

Or you could catch one of the events OnBeforeDrawBackwall or OnAfterDrawBackwall which are fired during the painting process and contain the rectangle enclosed by the axis lines as a parameter; simply store that Rect for later usage.

Reply by email

Leonardo M. Ramé
to Werner Pamler
Werner Pamler wrote 25 lines
The most general way: Use the CurrentExtent of the chart, it is in "graph coorinates", though, and you have to convert it to pixels yourself: uses TAChartUtils; procedure TForm1.Button1Click(Sender: TObject); var ext: TDoubleRect; begin ext := Chart1.CurrentExtent; Label1.Caption := Format('Left = %d, Top = %d, Right = %d, Bottom = %d', [ Chart1.XGraphToImage(ext.a.x), // "a" is the lower left corner Chart1.YGraphToImage(ext.b.y), // "b" is the upper right corner Chart1.XGraphToImage(ext.b.x), Chart1.YGraphtoImage(ext.a.y) ]); end; Or you could catch one of the events OnBeforeDrawBackwall or OnAfterDrawBackwall which are fired during the painting process and contain the rectangle enclosed by the axis lines as a parameter; simply store that Rect for later usage.

Great!, I used the onAfterDrawBackwll's rect.

Reply by email