Recent

Author Topic: ChartPane caption  (Read 828 times)

kapibara

  • Hero Member
  • *****
  • Posts: 589
ChartPane caption
« on: December 05, 2022, 05:47:08 am »
Hi wp, at last I'm trying to make captions for chart panes.

Your advice in https://forum.lazarus.freepascal.org/index.php/topic,58368.msg434832.html#msg434832 was helpful.

The code below works to change IChartDrawer font settings, maybe there are simpler way than to create and free customfont? And most of all, how to change background color of the text ?

Code: Pascal  [Select][+][-]
  1. procedure TfrmCaption.Chart1AfterDraw(ASender: TChart; ADrawer: IChartDrawer);
  2. var
  3.   fnt: TFPCustomFont;
  4. begin
  5.   fnt:= TFPCustomFont.Create;
  6.   fnt.Bold:=True;
  7.   fnt.FPColor:=TColorToFPColor(clGreen);
  8.  
  9.   ADrawer.SetFont(fnt);
  10.   ADrawer.TextOut.Text('Panel #1').Pos(100, 200).Done;
  11.  
  12.   fnt.Free;
  13. end;
Lazarus trunk / fpc 3.2.2 / Kubuntu 22.04 - 64 bit

wp

  • Hero Member
  • *****
  • Posts: 10478
Re: ChartPane caption
« Reply #1 on: December 05, 2022, 02:37:40 pm »
Code: Pascal  [Select][+][-]
  1. uses
  2.   fpcanvas, lclintf;
  3.  
  4. procedure TForm1.Chart1AfterDraw(ASender: TChart; ADrawer: IChartDrawer);
  5. const
  6.   MARGIN = 4;
  7. var
  8.   fnt: TFPCustomFont;
  9.   txt: String;
  10.   R: TRect;
  11. begin
  12.   txt := 'Panel #1';
  13.  
  14.   fnt:= TFPCustomFont.Create;
  15.   fnt.Bold:=True;
  16.   fnt.FPColor:=TColorToFPColor(clYellow);
  17.  
  18.   ADrawer.SetFont(fnt);
  19.   R.TopLeft := Point(0, 0);
  20.   R.BottomRight := ADrawer.TextExtent(txt);
  21.   OffsetRect(R, 100, 200);
  22.   InflateRect(R, MARGIN, MARGIN);
  23.  
  24.   ADrawer.SetBrushColor(clBlue);
  25.   ADrawer.FillRect(R.Left, R.Top, R.Right, R.Bottom);
  26.  
  27.   InflateRect(R, -MARGIN, -MARGIN);
  28.   ADrawer.TextOut.Text('Panel #1').Pos(R.TopLeft).Done;
  29.  
  30.   fnt.Free;
  31. end;

kapibara

  • Hero Member
  • *****
  • Posts: 589
Re: ChartPane caption
« Reply #2 on: December 06, 2022, 08:25:33 pm »
Great. Now there needs to be some space within the pane, to make room for a caption.

I'm thinking that the height of the graph should be decresed, keeping the size of the pane intact. Then we have a space between the top of the pane and the top of the graph.

But the caption height is in pixels and the pane size is calculated by good old ConfigureAxisTransformation upon pane creation. Also, when panes are resized, the routine DragToolMoveDivider recalculates top and bottom so the captions also need to be repositioned. I'm aware of the GraphToAxis and AxisToGraph methods but not sure how to go about this task in the best way?

Below is code from the project attached to our last ChartPane thread, https://forum.lazarus.freepascal.org/index.php/topic,58368.msg445244.html#msg445244

Code: Pascal  [Select][+][-]
  1. procedure TChartBox.ConfigureAxisTransformation(var AChartPane: TChartPane);
  2. var
  3.   paneIndex: Integer;
  4.   neighborPane: TChartPane;
  5.   dividerPane: TChartPane;
  6. begin
  7.   if PaneCount = 1 then
  8.   begin
  9.     // A pane has already been added, i.e. Count is 1 for the first pane.
  10.     // The pane with index 0 does not contain a divider line.
  11.     AChartPane.AutoScaleAxisTransform.MinValue := HALF_GAP;
  12.     AChartPane.AutoScaleAxisTransform.MaxValue := DEFAULT_PANE_SIZE - HALF_GAP - CAPTION_SIZE;
  13.     exit;
  14.   end;
  15.  
  16.   paneIndex := Panes.IndexOf(AChartPane);
  17.   // The pane has been added at the top of the pane stack. (Top of Chart)
  18.   if paneIndex = PaneCount-1 then
  19.   begin
  20.     neighborPane := GetChartPane(paneIndex-1);
  21.     AChartPane.AutoScaleAxisTransform.MinValue :=
  22.       neighborPane.AutoScaleAxisTransform.MaxValue + 2*HALF_GAP;
  23.  
  24.     AChartPane.AutoScaleAxisTransform.MaxValue :=
  25.       AChartPane.AutoScaleAxisTransform.MinValue + DEFAULT_PANE_SIZE
  26.       - 2*HALF_GAP - CAPTION_SIZE;
  27.  
  28.     // The divider line will be added in the current pane at the bottom.
  29.     dividerPane := AChartPane;
  30.   end else
  31.   // The pane has been added at the bottom of the pane stack. (Bottom of Chart)
  32.   if paneIndex = 0 then
  33.   begin
  34.     neighborPane := GetChartPane(paneIndex+1);
  35.     AChartPane.AutoScaleAxisTransform.MaxValue :=
  36.       neighborPane.AutoScaleAxisTransform.MinValue - 2*HALF_GAP - CAPTION_SIZE;
  37.  
  38.     AChartPane.AutoScaleAxisTransform.MinValue :=
  39.       AChartPane.AutoScaleAxisTransform.MaxValue - DEFAULT_PANE_SIZE + 2*HALF_GAP;
  40.     // The divider line will be added at the bottom of the neighbor pane.
  41.     dividerPane := neighborPane;
  42.   end else
  43.     exit;
  44.  
  45.   // Set up the divider line...
  46.   dividerPane.Divider := TConstantline.Create(FChart);
  47.   dividerPane.Divider.Legend.Visible := false;
  48.   dividerPane.Divider.Position := dividerPane.AutoScaleAxisTransform.MinValue - HALF_GAP;
  49.   FChart.AddSeries(dividerPane.Divider);
  50. end;
  51.  
  52.  
Code: Pascal  [Select][+][-]
  1. procedure TChartBox.DragToolMoveDivider(ASender: TDataPointDragTool;
  2.   var AGraphPoint: TDoublePoint);
  3. var
  4.   ser: TConstantLine;
  5.   paneIndex: Integer;
  6.   lowerPos, upperPos: Double;
  7.   pane_above_divider, pane_below_divider: TChartPane;
  8. begin
  9.   if not (TDataPointTool(ASender).Series is TConstantLine) then exit;
  10.  
  11.   ser := TConstantLine(TDataPointDragTool(ASender).Series);
  12.  
  13.   // Get the PaneIndex of the pane with the divider clicked.
  14.   paneIndex := GetPaneIndexOfDivider(ser);
  15.  
  16.   // Calculates the lowest allowed upper edge of the pane below the divider
  17.   pane_below_divider := GetChartPane(paneIndex-1);
  18.   lowerPos := pane_below_divider.AutoScaleAxisTransform.MinValue - HALF_GAP + MIN_PANE_SIZE;
  19.  
  20.   // Calculates the highest allowed lower edge of the pane above the divider
  21.   pane_above_divider := GetChartPane(paneIndex);
  22.   upperPos := pane_above_divider.AutoScaleAxisTransform.MaxValue + HALF_GAP - MIN_PANE_SIZE;
  23.    
  24.   // Ensures that the dragged data value is in the allowed range between
  25.   // lowerPos and upperPos.
  26.   AGraphPoint.Y := EnsureRange(AGraphPoint.Y, lowerPos, upperPos);
  27.    
  28.   // Adjusts axes of both panes using the values collected
  29.   // from calculations above
  30.   pane_above_divider.AutoScaleAxisTransform.MinValue := AGraphPoint.Y + HALF_GAP;
  31.   pane_below_divider.AutoScaleAxisTransform.MaxValue := AGraphPoint.Y - HALF_GAP;
  32. end;
  33.  
Lazarus trunk / fpc 3.2.2 / Kubuntu 22.04 - 64 bit

wp

  • Hero Member
  • *****
  • Posts: 10478
Re: ChartPane caption
« Reply #3 on: December 07, 2022, 08:03:16 pm »
I played a bit with this requirement, but found only solutions in which the pane caption overlaps with the series - see attachment.

I don't think that this request is possible in a clean way with the current TAChart so that the multidrawer concept is supported. The problem is that all the sizing and positioning happens in the chart's Draw method, but the definition of the AutoScaleAxisTransform's MinValue and MaxValue is outside. And there is no event fired from Draw so that the space requirement of an external component could be calculated in the same way as the chart title and footer. There is an OnPaint event before the entire drawing work, but this is not called when the output is supposed to go to a different drawer.

kapibara

  • Hero Member
  • *****
  • Posts: 589
Re: ChartPane caption
« Reply #4 on: December 08, 2022, 05:58:07 am »
Would it help if the caption's height was always static (like 20 pixels) and didn't need to be calculated?

It would be good to find some kind of workaround, even an ugly one.

As always, thank you for your time.
« Last Edit: December 08, 2022, 07:31:55 am by kapibara »
Lazarus trunk / fpc 3.2.2 / Kubuntu 22.04 - 64 bit

kapibara

  • Hero Member
  • *****
  • Posts: 589
Re: ChartPane caption
« Reply #5 on: December 09, 2022, 12:13:34 pm »
The TChartExtentLink, used like in the original panes demo could be a good alternative. If to put controls with labels above the top of those charts, then there would be pretty good "captions". Or to increase MarginsExternal.Top and draw the caption there directly with the IChartDrawer.

But how come it's not possible to drop or drag a control in between the charts? They seem anchored to each other, so I could not get a TSplitter between them.
« Last Edit: December 09, 2022, 12:31:58 pm by kapibara »
Lazarus trunk / fpc 3.2.2 / Kubuntu 22.04 - 64 bit

wp

  • Hero Member
  • *****
  • Posts: 10478
Re: ChartPane caption
« Reply #6 on: December 09, 2022, 01:19:33 pm »
So, you moved to individual charts now? Here the "pane" caption is no problem at all because every chart has a Title.

I am attaching a preliminary demo project in which charts can be added to a panel to get a paned view of several charts, similar to your previous code. The individual panes consist of the chart and a splitter, and adjacent panes are linked by anchoring the chart of one pane to the splitter of the next pane.

Please note that for deletion of a chart from the pane stack I had to modify the sources of unit TAChartExtentLink.pas. The changes are committed to Laz/main. If you don't use main you should add the following public procedure to TChartExtentLink in the mentioned unit and recompile the TAChart package (probably not absolutely necessary)

Code: Pascal  [Select][+][-]
  1. procedure TChartExtentLink.RemoveChart(AChart: TChart);
  2. var
  3.   i: TCollectionItem;
  4. begin
  5.   if AChart = nil then
  6.     exit;
  7.  
  8.   for i in LinkedCharts do
  9.     if TLinkedChart(i).Chart = AChart then
  10.     begin
  11.       LinkedCharts.Delete(i.Index);
  12.       exit;
  13.     end;
  14. end;
« Last Edit: December 09, 2022, 07:49:26 pm by wp »

kapibara

  • Hero Member
  • *****
  • Posts: 589
Re: ChartPane caption
« Reply #7 on: December 09, 2022, 09:44:45 pm »
Demo was not attached?

Yes, captions are important so it's probably better to use individual charts and TChartExtentLink for this. Although when using a crosshair, it will not extend into upper and lower panes as it does with a single chart, but that can be overcome.

I did a "git pull" of the latest trunk and your changes of TAChartExtentLink is ready available.
« Last Edit: December 09, 2022, 09:59:18 pm by kapibara »
Lazarus trunk / fpc 3.2.2 / Kubuntu 22.04 - 64 bit

wp

  • Hero Member
  • *****
  • Posts: 10478
Re: ChartPane caption
« Reply #8 on: December 09, 2022, 10:15:36 pm »
Sorry. Here it is...

kapibara

  • Hero Member
  • *****
  • Posts: 589
Re: ChartPane caption
« Reply #9 on: December 10, 2022, 07:35:03 am »
Great, definitely seems the way to go. I will have to read your code in more detail.

A few thoughts. For my application, only the bottom-most chart would need a BottomAxis because other such axes would repeat the same data using extra screenspace. I added this code to have a visible axis only at the chart at bottom.

procedure AddPaneAtBottom
Code: Pascal  [Select][+][-]
  1.   if Count > 1 then
  2.     Items[Pred(i)].Chart.BottomAxis.Visible:=False;
  3.  
..and AddPaneAtTop
Code: Pascal  [Select][+][-]
  1.   if Count > 1 then
  2.     pane.Chart.BottomAxis.Visible:=False;
  3.  

For a caption, I don't know if it is best to use a control. It is the easiest way probably. Some elements of the caption need different color. Using the IChartDrawer instead of a control could save space between charts, because the caption could be drawn inside the top margin of the chart instead of a control that has to sit between charts. Not sure if it matters in practice.

Another thought is that splitters may not even be needed if resizing of charts is done like ctrl+leftclicking the caption and moving the mouse. That would also save a little space on screen.

If you zoom in on one chart, the other charts don't respond in a linked way yet, and UpdateListbox tries to access objects using ItemIndex = -1, but I'm sure you already know this.
« Last Edit: December 10, 2022, 08:43:37 am by kapibara »
Lazarus trunk / fpc 3.2.2 / Kubuntu 22.04 - 64 bit

wp

  • Hero Member
  • *****
  • Posts: 10478
Re: ChartPane caption
« Reply #10 on: December 10, 2022, 05:43:39 pm »
"TChartPanes-6 (separate charts)-2.zip" contains an extended version of the previous demo taking into account some of your suggestions.

Of course you can select any colors for the chart title, and you can align it to the left or right side. There is one disadvantage, though, that it seems quite difficult to make it run across the entire chart (normally the title size is given by the text dimensions).

I would not add any additional controls for the pane captions. Splitters are exactly what is needed here to resize the adjacent charts. Since TSplitter inherits from TControl it has a built-in caption which you can set as you like. The problem that the splitter does not draw the text can be solved easily because it has an OnPaint event so that you can override the default painting routine - and here you can draw the caption.

The principle is explained in "splitter_with_text.zip".

"TChartPanes-6 (separate charts)-3.zip", finally is the chart demo again, now with labelled splitters. Note that it uses the same anchoring as in the other demos, therefore there is no splitter for the topmost chart (the splitter is always at the top of the chart in theses demos). If you want to see a splitter here, too, remove all lines which set the splitter.Visible to false. And redefine the related anchors so that the splitter anchors to the top of the FParent rather than the chart.


kapibara

  • Hero Member
  • *****
  • Posts: 589
Re: ChartPane caption
« Reply #11 on: December 10, 2022, 09:51:26 pm »
Good idea to use the splitter as also caption, now it serves two purposes.

I'll work with the demos and adapt them to my needs. Will upload a demo of that when its done.
Lazarus trunk / fpc 3.2.2 / Kubuntu 22.04 - 64 bit

 

TinyPortal © 2005-2018