Recent

Author Topic: Can I get the position and size of an axis title?  (Read 1411 times)

CM630

  • Hero Member
  • *****
  • Posts: 1674
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Can I get the position and size of an axis title?
« on: April 01, 2026, 09:29:34 am »
I am trying to get the positions (rather borders) of axis titles. The idea is to use multiple axises.
I have aded a ChartToolset with a ChartToolset1AxisClickTool activated on left mouse click.
When I click on an axis I get “0;4;4” in the label, which is not the result I am looking for.
Is there a way to find the position and size of the axis title?

Code: Pascal  [Select][+][-]
  1. procedure TForm1.ChartToolset1AxisClickTool1Click(ASender:TChartTool;Axis:TChartAxis;AHitInfo:TChartAxisHitTests);
  2. begin
  3.   Label1.Caption := IntToStr(axis.Margin) + '; ' + IntToStr(axis.Title.Margins.Left) +  '; ' + IntToStr(axis.Title.Margins.Right);
  4. end;

A snippet is attached.
« Last Edit: April 01, 2026, 10:14:56 am by CM630 »
Лазар 4,4 32 bit (sometimes 64 bit); FPC3,2,2

wp

  • Hero Member
  • *****
  • Posts: 13483
Re: Can I get the position and size of an axis title?
« Reply #1 on: April 01, 2026, 02:52:20 pm »
You can query the vertices of the polygon around the title which would be painted when its Frame.Style is not clear and its Frame.Visible is true. This is also the polygon which is checked by the AxisTitleClickTool to decide whether the click is inside the title.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.ChartToolset1AxisClickTool1Click(ASender:TChartTool;
  2.   Axis: TChartAxis; AHitInfo: TChartAxisHitTests);
  3. var
  4.   s, sp: String;
  5.   i: Integer;
  6. begin
  7. //  Label1.Caption := IntToStr(axis.Margin) + '; ' + IntToStr(axis.Title.Margins.Left) +  '; ' + IntToStr(axis.Title.Margins.Right);
  8.   s := '';
  9.   for i := 0 to High(Axis.TitlePolygon) do
  10.   begin
  11.     sp := Format('%d: (%d;%d)', [i, Axis.TitlePolygon[i].X, Axis.TitlePolygon[i].Y]);
  12.     if i > 0 then
  13.       s := s + '; ' + sp
  14.     else
  15.       s := sp;
  16.   end;
  17.   Label1.Caption := 'TitlePolygon = [' + s + ']';
  18. end;

CM630

  • Hero Member
  • *****
  • Posts: 1674
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Can I get the position and size of an axis title?
« Reply #2 on: April 03, 2026, 09:50:23 am »
Thanks!
Meanwhile I found out that there is a property *Scale.Editable, but it is “Not implemented”.

I added a Tmemo in the main form.
And why trying it, it occurred that when the scale is more than a line long, is switches from centered to aligned left... sort of (see the image attached.
Also, if there is an empty line in the caption, it is ignored:
Code: Pascal  [Select][+][-]
  1. BottomAxis.Title.Caption = 'Line 1 ' + LineEnding  + LineEnding + 'Line 4';
is displayed as:
Quote
Line 1
Line 4


Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs,StdCtrls,TAGraph,TATools, TAChartAxis, TAChartAxisUtils, LCLType, math;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Chart1:TChart;
  16.     ChartToolset1:TChartToolset;
  17.     ChartToolset1AxisClickTool1:TAxisClickTool;
  18.     Label1:TLabel;
  19.     Memo1:TMemo;
  20.     procedure ChartToolset1AxisClickTool1Click(ASender:TChartTool;Axis:TChartAxis;AHitInfo:TChartAxisHitTests);
  21.     procedure FormCreate(Sender:TObject);
  22.     procedure Memo1KeyDown(Sender:TObject;var Key:Word;Shift:TShiftState);
  23.   private
  24.      FClickedAxis : TChartAxis;
  25.   public
  26.  
  27.   end;
  28.  
  29. var
  30.   Form1: TForm1;
  31.  
  32. implementation
  33.  
  34. {$R *.lfm}
  35.  
  36. { TForm1 }
  37.  
  38. procedure TForm1.ChartToolset1AxisClickTool1Click(ASender:TChartTool;Axis:TChartAxis;AHitInfo:TChartAxisHitTests);
  39. var
  40.   s, sp: String;
  41.   i: Integer;
  42. begin
  43.   FClickedAxis := Axis;
  44.   Memo1.Top :=   Chart1.top +  Axis.TitlePolygon[0].Y;
  45.   Memo1.Height := Axis.TitlePolygon[1].Y - Axis.TitlePolygon[0].Y +1;
  46.   Memo1.left :=  Chart1.ClipRect.Left +   Chart1.Left;
  47.   Memo1.Width := Chart1.Width - Chart1.ClipRect.Left - Chart1.MarginsExternal.right;
  48.   Memo1.Font := Axis.Title.LabelFont;
  49.   memo1.Text :=  Axis.Title.Caption;
  50.   Memo1.Visible := true;
  51.  
  52.  
  53. end;
  54.  
  55. procedure TForm1.FormCreate(Sender:TObject);
  56. var
  57.   i: integer;
  58. begin
  59.   Chart1.Toolset := ChartToolset1;
  60.   chart1.AxisList.Add;
  61.   chart1.AxisList.Add;
  62.   chart1.AxisList.Add;
  63.   chart1.AxisList.Add;
  64.   for i:= 0 to   chart1.AxisList.Count -1 do
  65.   begin
  66.     chart1.AxisList[i].Title.Visible  := True;
  67.     chart1.AxisList[i].Title.Caption := 'Axis ' + IntToStr(i);
  68.     if i mod 2 = 0 then
  69.     begin
  70.       chart1.AxisList[i].Title.LabelFont.Orientation := 900;
  71.       chart1.AxisList[i].Alignment := calLeft;
  72.     end
  73.     else
  74.     begin
  75.       chart1.AxisList[i].Title.LabelFont.Orientation := 0;
  76.       chart1.AxisList[i].Alignment := calbottom;
  77.     end;
  78.   end;
  79.   chart1.AxisList[3].Title.Caption := chart1.AxisList[2].Title.Caption +  'abc' + LineEnding + '456';
  80.   Memo1.Alignment := taCenter;
  81.   memo1.WordWrap := False;
  82. end;
  83.  
  84. function GetMemoLineHeight(AMemo: TMemo): Integer;
  85. var
  86.   Bitmap: TBitmap;
  87. begin
  88.   Bitmap := TBitmap.Create;
  89.   try
  90.     Bitmap.Canvas.Font.Assign(AMemo.Font);
  91.     // Measure a standard character
  92.     Result := Bitmap.Canvas.TextHeight('Wg');
  93.   finally
  94.     Bitmap.Free;
  95.   end;
  96. end;
  97.  
  98. procedure TForm1.Memo1KeyDown(Sender:TObject;var Key:Word;Shift:TShiftState);
  99. var
  100.   FontHeight : integer = 14;
  101. begin
  102.   FontHeight := abs(GetFontData(Memo1.Font.Handle).Height);
  103.   if (Key = VK_ESCAPE) then
  104.   begin
  105.     Memo1.Visible := false;
  106.     Exit;
  107.   end;
  108.   if ((Key = VK_RETURN) and (ssCtrl in Shift)) then
  109.   begin
  110.     FClickedAxis.Title.Caption := Memo1.Text;
  111.      Memo1.Visible := false;
  112.     exit;
  113.   end;
  114.   if ((key = LCLtype.VK_RETURN) and (shift = [])) then
  115.     begin
  116.       Memo1.Height := min (FontHeight*3+8,Memo1.Height + FontHeight+4); //The first line starts 5 pixels below the top of the memo (in Windows)
  117.       Memo1.top := chart1.top+ FClickedAxis.TitlePolygon[0].Y - Memo1.Height + FontHeight+2;
  118.     end;
  119. end;
  120.  
  121. end.
  122.  
« Last Edit: April 03, 2026, 09:55:29 am by CM630 »
Лазар 4,4 32 bit (sometimes 64 bit); FPC3,2,2

wp

  • Hero Member
  • *****
  • Posts: 13483
Re: Can I get the position and size of an axis title?
« Reply #3 on: April 03, 2026, 10:25:05 am »
switches from centered to aligned left...
There's a property Alignment for the individual lines of the axis title (and Chart.Title and marks text, as well), you may want to switch it to taCenter. Another (newly introduced) property Anchor defines where the overall title is placed along the axis: ctaStart = where the axis begins, ctaEnd = where the axis ends, ctaCenter = center of the axis (default).

Also, if there is an empty line in the caption, it is ignored
This is because an empty line is considered to have zero extent (x:0; y:0). If you want to include the empty line insert a space character between the two LineEndings:
Code: Pascal  [Select][+][-]
  1. Chart1.BottomAxis.Title.Caption := 'Line 1' + LineEnding + ' ' + LineEnding + 'Line 2';
« Last Edit: April 03, 2026, 10:44:36 am by wp »

CM630

  • Hero Member
  • *****
  • Posts: 1674
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Can I get the position and size of an axis title?
« Reply #4 on: April 03, 2026, 10:50:26 am »
Indeed.
Code: Pascal  [Select][+][-]
  1.   for i:= 0 to   chart1.AxisList.Count -1 do
  2.   begin
  3.     chart1.AxisList[i].Title.Visible  := True;
  4.     chart1.AxisList[i].Title.Caption := 'Axis ' + IntToStr(i);
  5.     chart1.AxisList[i].Title.Alignment := taCenter;   //Here  
did the job.

And can I get some position data for the numeric scale (the scale itself) of the axis?
The scale position itself, or in the perfect case the position/size of the left/bottommost number and the right/topmost number?

I checked in TChartAxis = class(TChartBasicAxis) ... public... but nothings sounds similar.
« Last Edit: April 03, 2026, 11:04:15 am by CM630 »
Лазар 4,4 32 bit (sometimes 64 bit); FPC3,2,2

wp

  • Hero Member
  • *****
  • Posts: 13483
Re: Can I get the position and size of an axis title?
« Reply #5 on: April 03, 2026, 11:40:32 am »
What do you understand as "numeric scale"?

CM630

  • Hero Member
  • *****
  • Posts: 1674
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Can I get the position and size of an axis title?
« Reply #6 on: April 03, 2026, 01:34:31 pm »
I want to make the numeric scale editable (to change the extents of the scale) by the user.
So currently I overlap a text box (named txtExtents) over the first/last number on the scale when the user clicks there (with green rectangles on the image attached).

Currently (part of) the code is:

Code: Pascal  [Select][+][-]
  1.  procedure *.ctMainAxisClickLeftOnClick(ASender: TChartTool; Axis: TChartAxis; AHit: TChartAxisHitTests);
  2. ...
  3.  txtExtents.Alignment := taLeftJustify;
  4.       if (ahtAxisStart in aHit) and XScale.Visible then
  5.         begin
  6.           try
  7.             txtExtents.Font := Axis.Marks.LabelFont;
  8.           finally
  9.           end;
  10.           txtExtents.Text := FloatToStr(GetExtentsReal(Chart).coords[ExtXmin]);
  11.           AutoSizeEditWidth(txtExtents,True);
  12.           txtExtents.Left := Chart.Margins.Left + 75;
  13.           txtExtents.Top := Chart.ClipRect.Bottom + Axis.TickLength + 1;
  14.           txtExtents.Visible := True;
  15.           ExtentPosition := epXmin;
  16.         end;
  17. ...

but I am implementing multiple scales and this solution will not work, because txtExtents.Left and txtExtents.Top will not be set properly (most likely Top, Left shall be quite okay).
« Last Edit: April 03, 2026, 01:36:33 pm by CM630 »
Лазар 4,4 32 bit (sometimes 64 bit); FPC3,2,2

wp

  • Hero Member
  • *****
  • Posts: 13483
Re: Can I get the position and size of an axis title?
« Reply #7 on: April 05, 2026, 01:34:08 am »
Please test the following solution that I just committed to Lazarus trunk/main:

* TChartAxis has a (public) property Value[index] which lists the numeric and text value of the axis labels ("scale" in your words)
* TChartAxis.ValueCount is the length of this array.
* I added another element, FPolygon (TPointArray), to the Value record which contains the polygon surrounding each label. A polygon is used rather than a TRect because the label frame can have a rather complex shape. By default, it has the shape of a rectangle, though.
* Unfortunately the first and/or last axis labels are often outside the axis range so that you must search for the first/last VISIBLE label yourself.
* In order to draw a green border around the first and last labels on the axis, I tested the following (quick-and-dirty) code:

Code: Pascal  [Select][+][-]
  1. uses
  2.   Types, TAChartUtils, TADrawUtils, TACustomSource, TATransformations;
  3.  
  4. procedure HighlightFirstAndLastAxisLabel(Axis: TChartAxis);
  5. var
  6.   R: TRect;
  7.   ext: TDoubleRect;
  8.   v: Double;
  9.   i, idx: Integer;
  10.   t: TChartValueText;
  11.   transf: TChartAxisTransformations;
  12.   chart: TChart;
  13. begin
  14.   Axis.Drawer.SetPenParams(psSolid, clLime);
  15.   Axis.Drawer.SetBrushParams(bsClear, clNone);
  16.   chart := TChart(Axis.GetChart);
  17.   ext := chart.CurrentExtent;
  18.   transf := TransformByAxis(chart.AxisList, Axis.Index);
  19.  
  20.   // Find first label which is on the axis
  21.   idx := -1;
  22.   for i := 0 to Axis.ValueCount - 1 do
  23.   begin
  24.     t := Axis.Value[i];
  25.     v := transf.AxisToGraph(t.FValue);
  26.     if Axis.IsVertical then
  27.     begin
  28.       if (v >= ext.a.y) then
  29.       begin
  30.         idx := i;
  31.         break;
  32.       end;
  33.     end else
  34.     begin
  35.       if (v >= ext.a.x) then
  36.       begin
  37.         idx := i;
  38.         break;
  39.       end;
  40.     end;
  41.   end;
  42.   if idx = -1 then
  43.     exit;
  44.   R.TopLeft := Axis.Value[idx].FPolygon[0];
  45.   R.BottomRight := Axis.Value[idx].FPolygon[2];
  46.   Types.InflateRect(R, 4, 2);
  47.   Axis.Drawer.Rectangle(R);
  48.  
  49.   // Find last label which is on the axis;
  50.   idx := -1;
  51.   for i := Axis.ValueCount - 1 downto 0 do
  52.   begin
  53.     t := Axis.Value[i];
  54.     v := transf.AxisToGraph(t.FValue);
  55.     if Axis.IsVertical then
  56.     begin
  57.       if (v <= ext.b.y) then
  58.       begin
  59.         idx := i;
  60.         break;
  61.       end;
  62.     end else
  63.     begin
  64.       if (v <= ext.b.x) then
  65.       begin
  66.         idx := i;
  67.         break;
  68.       end;
  69.     end;
  70.   end;
  71.   if idx = -1 then
  72.     exit;
  73.   R.TopLeft := Axis.Value[idx].FPolygon[0];
  74.   R.BottomRight := Axis.Value[idx].FPolygon[2];
  75.   Types.InflateRect(R, 4, 2);
  76.   Axis.Drawer.Rectangle(R);
  77. end;  

wp

  • Hero Member
  • *****
  • Posts: 13483
Re: Can I get the position and size of an axis title?
« Reply #8 on: April 05, 2026, 12:56:41 pm »
Now also added methods GetIndexOfFirst(Last)VisibleMark to TChartAxis. This way my previous sample code simplifies to

Code: Pascal  [Select][+][-]
  1. procedure HighlightFirstAndLastAxisLabels(Axis: TChartAxis);
  2. var
  3.   R: TRect;
  4.   idx: Integer;
  5.   chart: TChart;
  6. begin
  7.   Axis.Drawer.SetPenParams(psSolid, clLime);
  8.   Axis.Drawer.SetBrushParams(bsClear, clNone);
  9.  
  10.   // Find first label which is on the axis
  11.   idx := Axis.GetIndexOfFirstVisibleMark;
  12.   if idx > -1 then
  13.   begin
  14.     R.TopLeft := Axis.Value[idx].FPolygon[0];
  15.     R.BottomRight := Axis.Value[idx].FPolygon[2];
  16.     Types.InflateRect(R, 4, 2);
  17.     Axis.Drawer.Rectangle(R);
  18.   end;
  19.  
  20.   // Find last label which is on the axis;
  21.   idx := Axis.GetIndexOfLastVisibleMark;
  22.   if idx > -1 then begin
  23.     R.TopLeft := Axis.Value[idx].FPolygon[0];
  24.     R.BottomRight := Axis.Value[idx].FPolygon[2];
  25.     Types.InflateRect(R, 4, 2);
  26.     Axis.Drawer.Rectangle(R);
  27.   end;
  28. end;

Quote
I want to make the numeric scale editable (to change the extents of the scale) by the user.
So currently I overlap a text box (named txtExtents) over the first/last number on the scale when the user clicks there (with green rectangles on the image attached).
I am not sure whether this will work. Because the first and last visible labels are not necessarily the values at which the axis starts and ends. You must provide extra code to make sure that these labels really are positioned at axis start and end.

If you are interesting in how to provide a GUI for run-time editing of chart properties you may want to take a look at the "charteditor" in the demos folder of the TAChart installation.
« Last Edit: April 05, 2026, 02:09:15 pm by wp »

CM630

  • Hero Member
  • *****
  • Posts: 1674
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Can I get the position and size of an axis title?
« Reply #9 on: April 06, 2026, 11:42:15 am »
Thanks!
1. When I try to use HighlightFirstAndLastAxisLabel* in Form.Create the app crashes
Adding if (axis = nil) then exit; does not prevent the crash.
2. I tried to move it to .OnShow but still the app crashes.
  Neither Chart1.Prepare;  nor   chart1.Invalidate; help.
  I do not intend to use it this way, but maybe this indicates some problem.

Pretty odd:
  3. The procedure from Reply #7 worked from the first try (named HighlightFirstAndLastAxisLabel_1 in the attached snippet).
  4. The procedure from Reply #8 did not work (named HighlightFirstAndLastAxisLabel_2 in the attached snippet) initially. After I removed a Chart that was not used from the form, it started working. I have done “clean up and build” before that with no result.
It seemed to find some coordinates, but nothing was drawn.

5. A snippet is attached. Stll no care is taken for inverted axises (the smaller one is on the right, the higher is on the left).

Quote
I want to make the numeric scale editable (to change the extents of the scale) by the user.
So currently I overlap a text box (named txtExtents) over the first/last number on the scale when the user clicks there (with green rectangles on the image attached).
I am not sure whether this will work. Because the first and last visible labels are not necessarily the values at which the axis starts and ends. You must provide extra code to make sure that these labels really are positioned at axis start and end.
Indeed, the user might click on a 5 and get text 8. I am not sure if I have taken to make sure that these labels really are positioned at axis start and end, I will find out.


If you are interesting in how to provide a GUI for run-time editing of chart properties you may want to take a look at the "charteditor" in the demos folder of the TAChart installation.
I will check that, but if there are questions or issues, I will use another tread.
« Last Edit: April 06, 2026, 03:10:33 pm by CM630 »
Лазар 4,4 32 bit (sometimes 64 bit); FPC3,2,2

wp

  • Hero Member
  • *****
  • Posts: 13483
Re: Can I get the position and size of an axis title?
« Reply #10 on: April 06, 2026, 05:26:11 pm »
1. When I try to use HighlightFirstAndLastAxisLabel* in Form.Create the app crashes
Adding if (axis = nil) then exit; does not prevent the crash.
2. I tried to move it to .OnShow but still the app crashes.
  Neither Chart1.Prepare;  nor   chart1.Invalidate; help.
  I do not intend to use it this way, but maybe this indicates some problem.
TAChart does all its sizing and layout adjustments during the Paint method. Therefore, when your calls are too early, before anything has been painted, bad things may happen... A good place to invoke TChartAxis.GetIndexOfFirst(Last)VisibleMarkIndex is the OnAfterPaint method of the chart; this happens when all painting is finished and everything is known.

5. A snippet is attached. Stll no care is taken for inverted axises (the smaller one is on the right, the higher is on the left).
I am attaching a test project for clicking on axis labels after having added a (read-only) property ClickedLabelIndex to the TAxisClickTool. Special code to care for inverted axes is not required.

CM630

  • Hero Member
  • *****
  • Posts: 1674
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Can I get the position and size of an axis title?
« Reply #11 on: April 07, 2026, 01:30:56 pm »
I updated to the latest TChart and your example worked.

What are the ways to have different values on the scales?
For example if I have voltage and current on one waveform, I would need a scale for -350 V to + 350 V and another one for current, say from -10 A to + 10 A.
One possible way is TchartAxisTransformations, but can I simply set the minimum and maximum values for the scale?

Also, in your example I tried

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender:TObject);
  2. begin
  3.   Chart1.AxisList.Add;
  4.   Chart1.AxisList[Chart1.AxisList.Count -1].Alignment := calTop;
  5.   Chart1.AxisList.Add;
  6.   Chart1.AxisList[Chart1.AxisList.Count -1].Alignment := calBottom;
  7.   Chart1.BottomAxis.Transformations := ChartAxisTransformations1;
  8.   TLinearAxisTransform(ChartAxisTransformations1.List[0]).Offset :=0;
  9.   TLinearAxisTransform(ChartAxisTransformations1.List[0]).Scale :=10;
  10. end;

I expected that the BottomAxis (AxisList[1]) would be scaled (from -60 to 120), but actually it remains -6 to 12, and the other two horizontal axises are scaled 10 times.
Is this the expected behaviour?

Edit: I added a snippet. Things look ever more mysterious.
« Last Edit: April 07, 2026, 01:36:58 pm by CM630 »
Лазар 4,4 32 bit (sometimes 64 bit); FPC3,2,2

wp

  • Hero Member
  • *****
  • Posts: 13483
Re: Can I get the position and size of an axis title?
« Reply #12 on: April 08, 2026, 01:00:16 am »
For having two (or more) independent vertical axes (or horizontal - just the same logic...), there is a tutorial: https://wiki.lazarus.freepascal.org/TAChart_Tutorial:_Dual_y_axis,_Legend. In essence:
- Add a TChartAxistransformations component per axis, link it to the Transformations property of the axis.
- Add an AutoScaleAxistransform to the component (rather than a LinearAxisTransform).
- Every series must set the AxisIndexX and AxisIndexY to the index of it x and y axis.

For specific axis range:
- Use the Range property of the axis (axis.Range.Min, .Max, .UseMax, .UseMin) to define the value at which the axis begins (Min) and ends (Max). But note, when data in the series assigned to this axis will run out of this range, the range will automatically expand.

For user-entry of axis range:
- My recommendation: Do not use your label-click idea because the first visible label is not necessarily at the axis start. Suppose you want the axis to run between 190 and 500. Then TAChart will very probably put the first label to the value 200. I'd add two edits (or SpinEdits) to the form close to the axis start/end to define the axis start/end and let TAChart decide which labels will be displayed.

Inverted axis:
- I don't know whether this is a bug or a feature, but when you have two vertical axes and set the left axis' Inverted to true, both axes will flip their direction. It's better to add an additional LinearAxisTransform ("Linear" is correct now!) and switch its Scale property to -1 in order to invert the axis direction - the other axis then is not affected. But note that the LinearAxisTransform must be listed in the ChartAxisTransformations before the AutoScaleTransform, otherwise the flipping process will not work. The transformations are executed in the listed order: at first you must flip the axis coordinates (TLinearAxisTransform with Scale=-1) and then you must map the axis coordinates to the interal graph coordinates (TAutoScaleTransform).

See attached project.

 

TinyPortal © 2005-2018