Recent

Author Topic: Resizable panes  (Read 524 times)

kapibara

  • Hero Member
  • *****
  • Posts: 516
Resizable panes
« on: July 05, 2019, 07:02:44 am »
Hi wp, I went back to your demo about resizable panes with dragtool and constantlines.

https://forum.lazarus.freepascal.org/index.php/topic,32457.msg209347.html#msg209347

When I make everything from new project, my topmost pane can be resized upwards unlimited and I cant stop that behaviour. In your demo, resizing is limited. What am I missing? My project is attached.

The TDoublePointBoolArr(ex.a)[boolean] on codeline 24 is beyond my comprehension, how does it work here?

Resizing of panes:

Code: Pascal  [Select]
  1. procedure TForm1.ChartToolset1DataPointDragToolAfterMouseMove(
  2.   ATool: TChartTool; APoint: TPoint);
  3. const
  4.   MIN_SIZE = 1.5;  // Min size of a pane, avoid div by zero
  5. var
  6.   pos, lowerPos, upperPos: Double;
  7.   ser: TConstantLine;
  8.   ex: TDoubleRect;
  9.   ls: array[1..2] of TConstantLine;
  10. begin
  11.   UnUsed(APoint);
  12.  
  13.   ser := TConstantLine(TDataPointDragTool(ATool).Series);
  14.   if ser = nil then
  15.     exit;
  16.  
  17.   pos := ser.Position;
  18.   ex := ser.ParentChart.GetFullExtent;
  19.  
  20.   ls[1] := Chart1ConstantLine1;
  21.   ls[2] := Chart1ConstantLine2;
  22.  
  23.   if ser = ls[1] then begin
  24.     lowerPos := TDoublePointBoolArr(ex.a)[false] + MIN_SIZE + HALF_GAP;  //PageControl1.ActivepageIndex=0
  25.     upperPos := ls[2].Position - HALF_GAP - MIN_SIZE;
  26.     ser.Position := EnsureRange(pos, lowerPos, upperPos);
  27.  
  28.     ChartAxisTransformations1AutoscaleAxisTransform1.MaxValue := ser.Position - HALF_GAP;
  29.     ChartAxisTransformations2AutoscaleAxisTransform1.Minvalue := ser.Position + HALF_GAP;
  30.   end
  31.   else
  32.   if ser = ls[2] then
  33.   begin
  34.     lowerPos := ls[1].Position + MIN_SIZE + HALF_GAP;
  35.     upperPos := TDoublePointBoolArr(ex.b)[false] - MIN_SIZE - HALF_GAP;[
  36.     ser.Position := EnsureRange(pos, lowerPos, upperPos);
  37.  
  38.     ChartAxisTransformations2AutoscaleAxisTransform1.Maxvalue := ser.Position - HALF_GAP;
  39.     ChartAxisTransformations3AutoscaleAxisTransform1.MinValue := ser.Position + HALF_GAP;
  40.   end;
  41. end;
  42.  
« Last Edit: July 05, 2019, 07:08:44 am by kapibara »
Lazarus trunk / fpc 3.0.4 / Debian 10 - 64 bit

wp

  • Hero Member
  • *****
  • Posts: 6225
Re: Resizable panes
« Reply #1 on: July 05, 2019, 09:58:26 am »
The chart's FullExtent is a record of two TDoublePoints, a and b: a is the lower left corner, b the upper right corner of the chart's axis range. A TDoublePoint is a record of x and y double values.

Denoting, like in your code, the FullExtent by the variable ex, the x values of a "normal" chart range between ex.a.x and ex.b.x, and the y values range between ex.a.y and ex.b.y.

However, a series can be "rotated" such that its x values are plotted along the y axis and its y values are plotted along the x axis. If this series covers the entire chart, ex.a.x now corresponds to the minimum of the series y values (not x values!).

To avoid lengthy "if" instructions for checking the case of rotated series, Alexander Klenin introduced the concept of the type TDoublePointBoolArr - this is another way to represent two double values, now not as a record, but as an array indexed by a boolean variable. When that boolean variable is false, the TDoublePointBoolArr refers to the first, otherwise to the second double value. Since TDoublePointBoolArr and TDoublePoint have the same memory layout (two double values in each case) the DoublePointBoolArray can be used to access the elements of the TDoublePoint without using an explicit "if" instruction. So, in other words, the cast TDoublePointBoolArr(ex.a)[false] is nothing but ex.a.x, and TDoublePointBoolArr(ex.a)[true] is the same as ex.a.y. The expression
Code: Pascal  [Select]
  1.  lowerPos := TDoublePointBoolArr(ex.a)[false] + MIN_SIZE + HALF_GAP;
is nothing else but
Code: Pascal  [Select]
  1.  lowerPos := ex.a.x + MIN_SIZE + HALF_GAP;
I applied this concept in the original multi-pane demo because it displays horizontal and vertical pane orientations. On the first page of the PageIndex the dividing line determines the y coordinate, on the second page the x coordinate of the pane borders.

In your example, this is useless and you could use the TDoublePoint elements directly.

And it is wrong because - as I said - TDoublePointBoolArr(ex.a)[false] refers to the x value of the extent, but you want to limit the y value of the series. Therefore, you must use "true" as boolean parameter (or, more simply, ex.a.y directly).

This also solves the issue that the topmost pane can be move in an apparently unlimited way.
« Last Edit: July 05, 2019, 10:19:33 pm by wp »
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

kapibara

  • Hero Member
  • *****
  • Posts: 516
Re: Resizable panes
« Reply #2 on: July 06, 2019, 12:35:38 am »
Good explanation, that solved it.
Lazarus trunk / fpc 3.0.4 / Debian 10 - 64 bit

kapibara

  • Hero Member
  • *****
  • Posts: 516
Re: Resizable panes
« Reply #3 on: July 08, 2019, 05:09:37 am »
To create panes at runtime I came up with two new classes, TChartPanes and TChartPane. The attached demo creates series and puts them in panes. Divider can be dragged, but no resizing. The general idea is there but needs some more work for sure. Suggestions very welcome.

Also need help in the AfterMouseMove event again, to resize the panes. How to access the "old" and new pane's MaxValue and MinValue to calculate the Divider position and set the values for the new pane?

With the DataTool active with left mousebutton only (ssLeft), it seem the Chart.OnMouseDown event doesn't fire, it fires on DoubleClick instead. Could probably get around that by using [ssCtrl,ssLeft] instead. I'm on Linux btw.

Also, the initial positioning of the Divider is just a smidge off and I dont get it why:

Code: Pascal  [Select]
  1.     //Create and set Divider.Position
  2.     aChartPane.Divider:= TConstantLine.Create(FChart);
  3.     aChartPane.Divider.Position:= (Items[Count -1].AxisTransform.MaxValue + HALF_GAP);
  4.     aChartPane.Chart.AddSeries(aChartPane.Divider);
  5.  
« Last Edit: July 08, 2019, 06:43:41 am by kapibara »
Lazarus trunk / fpc 3.0.4 / Debian 10 - 64 bit

wp

  • Hero Member
  • *****
  • Posts: 6225
Re: Resizable panes
« Reply #4 on: July 08, 2019, 06:12:19 pm »
I am attaching a more complete version. Still not perfect though because it is still leaking memory (see below)

The reason why the dragged divider did not change the panes was that the event handler for OnAfterMouseMove was not assigned. I did it, but noticed that this is not the correct event for this purpose because resizing does stop in the near-edge area but resumes when the mouse is dragged out of the chart. It is better to use the OnDrag event of the DragTool. It is called immediately before the drag position is accepted; for this purpose it has a parameter AGraphPoint which you can overwrite to effectively stop dragging at some point. I also redid some of the logics in the event handler, it's still not 100% perfect, because the heights of the first and last panes are different from the inner panes.

As I already noted there is a memory leak, just check the "use Heaptrc unit" box in the project's debug options, and you'll get a bunch of memory leak messages when the program stops. They are related to the FSeries list of TChartPane - strange, I did not find a place where this was properly created, and also destruction was incomplete. But after fixing these the memory leaks are still there. I stopped here to leave some work for you  ;)

P.S.
You seem to be a user of Laz trunk. I always forget this, but I want others to know the following issue when they post code written by trunk: The structure of some of the xml files was changed, and older Laz versions cannot read these files correctly. To avoid this, you must check the option "Maximize compatibility of project files" in  "Projections options" > "Miscellaneous". If this is not done users of Laz 2.0.2 and older will not see any files contained in the project.
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

kapibara

  • Hero Member
  • *****
  • Posts: 516
Re: Resizable panes
« Reply #5 on: July 09, 2019, 05:02:05 am »
Thanks, I'll work on improvements and post something back later.
Lazarus trunk / fpc 3.0.4 / Debian 10 - 64 bit