Problems with extent Ymax < Ymin lazarus / 55-TAChart ---------------------------------------------------------------------- CM630 · 2012-10-25 07:25 Here are some cases: If Chart1.extent.Ymax= -5 and Chart1.extent.Ymin= 5 (UseYmax and UseYmin are true) and exception is generated, but I see no reason for that. Here is the other case- if logicalextent [2]:= +5 and logicalextent [4]:=-5 instead of displaying the curve upside down, it is not displayed at all. I have not tested what will happen with Xmax and Ymax. ---------------------------------------------------------------------- wp · 2012-10-25 07:58 The max must always be greater than the min. Use the property "Inverted" of the axis if you want to have min and max interchanged. @Ask: When working with the extent of a chart I sometimes get the same message reported here by Paskal. I am not quite sure, when it happens. But I am thinking of a situation that I change the extent when it is already set. Suppose the extent is set to go from 5 to 10. Now I want to change it to a range from -10 to -5. Since I can only set the limits one at a time I have to decide which one to change first -- in this example it would be the Minimum; if I'd start with altering the Max I'd get that error. If, on the other hand, the range would be from -10 to -5 at first and I want to change to 5 to 10 I have to set the maximum first to avoid the error. The situation gets even more complex if one of the limits is not "used". In essence, there is always a large number of cases to check when using the Extent feature. I think it would be better to have an additional fail-safe procedure like Delphi's SetMaxMin which find out the right oder by itself or temporarily ignors such errors. ---------------------------------------------------------------------- Ask · 2012-10-25 11:47 At run-time, you can call TChartExtent.FixTo procedure to simultaneously set and turn on all four limits. I can add more helpers based on well-founded suggestions The design-time error is another case -- it is ignorable (i.e. you can click "OK" without a risk for destabilizing IDE), and is intended to warn user about a possible mistake. It can certainly be annoying in the cases you outlined, although it is IMHO rather minor annoyance. I see several solutions: 1) Add Warnings property which will collect non-fatal errors in chart parameters (other examples are non-compatible axises in the same axis group, non-default pen parameters for invisible pens etc...) The problem with this solution is that IDE currently lacks a mechanism to display such warnings in a convenient fashion. 2) Add specialized property editor for TChartExtent. Incidentally, it is a first item on the current roadmap, although of course the position there does not indicate priority. The only obstacle here is a lack of time. ---------------------------------------------------------------------- CM630 · 2012-10-25 13:13 Why not simply remove this limitation: Quote from: wp on October 25, 2012, 09:58:34 amThe max must always be greater than the min.If the reason for having it is Delphi compatibility, there might be a settable workaround (i.e. Delphi mode or Lazarus mode). If the reason is that such behaviour is hard to implement, then it is another issue. Now we get exceptions if Ymax string convertion, maybe it can be done in a more mathematical way, but what do you think about its functionality? BTW, I found a similar function here. ---------------------------------------------------------------------- Ask · 2012-10-31 09:42 Quotewhat do you think about its functionality?I do not know what you question is exactly so I'll try to answer all possible questions -- hopefully yours is one of them. Q: Can a specific user (you) use NiceRound function in his own application? A: Of course, although I would remove PercentAddition argument, since one function should do one thing, in this case round floating point value. Q: Is NiceRound function a useful addition to a standard FPC library? A: No, because it is essentially equivalent to Format('%.2e', [aExtended]) Q: Is it useful for TAChart to automatically adjust extent boundaries using NiceRound function? A: No, it will cause total breakage in some cases. Q: Is it useful for TAChart to automatically adjust extent boundaries to shorten their decimal representation, while retaining the same chart image? A: I am not sure. Similar adjustment for axis marks is already present and is controlled by Marks.Tolerance property. However, extent boundaries are never printed by the chart, so what is the point of rounding them? ---------------------------------------------------------------------- CM630 · 2012-10-31 11:02 Quote from: Ask on October 31, 2012, 10:42:34 amQ: Is it useful for TAChart to automatically adjust extent boundaries using NiceRound function? A: No, it will cause total breakage in some cases. I rather mean a function with a similar effect, but explaining what it should do is harder, than making it do it. Quote from: Ask on October 31, 2012, 10:42:34 am...extent boundaries are never printed by the chart... That is exactly the problem that I am trying to solve, so some nicer values could be helpful, thought indeed the total number of marks per axis shall be considered. Even disregarding that Quote from: Ask on October 31, 2012, 10:42:34 am...However, extent boundaries are never printed by the chart, so what is the point of rounding them? You mentioned (or at least I understood so) that you were working on a functionality, that would allow users to change maximum/ minimum extents of the axis. So when the axis value is set automatically to a long trail float number, when you click to change it you would get a number, which is not very pleasant to deal with. If I misunderstood you about this functionality- than it is not worth discussing. As far as PercentAddition is concerned, you can see on the screenshot, that the curve is exactly fitting into the Y range. It is a question of taste, but according to mine, it would look nicer if there was some empty space under and over it. ---------------------------------------------------------------------- CM630 · 2012-11-09 07:02 Finally a new snapshot of Lazarus was uploaded, so I could try Chart1.LineSeries1.Marks.AutoMargins := false; LogicaExtents seem to work just fine- I say 5000, it understands 5000. But Extents work as before- I say 5000, it hears whatever it wants. So a problem that remains is that when I zoom only horizontally the Graph autoscales at the Y axis. Case1: If this is how things were meant to be (AutoMargins to be considered for LogicalExtents only)- I can force Y scale by using LogicalExtents after each zoom operation. Case 2: If this is a bug, I hope that it will be fixed. I would like to ask @Ask which is the case, so I would know if I shall proceed with implementing forced Y scale? ---------------------------------------------------------------------- Ask · 2012-11-09 11:59 QuoteBut Extents work as before- I say 5000, it hears whatever it wants.I can not reproduce. Try this: 1) Open "extents" demo 2) Set Chart1.Margins to all zeroes 3) Set MargingForMarks = false for both axises 3) Run => Note that the current extent exactly follows fixed extent. ---------------------------------------------------------------------- CM630 · 2012-11-09 14:26 Thanks! Now everything seems to work fine, probably next week I could try to spend some more time to see how exactly I have reached this odd result, in case there is something more to be fixed. ---------------------------------------------------------------------- CM630 · 2013-05-07 13:27 I will examine this further, probably tomorrow, but it just occured to me that usings Extent.FixTo prevents panning in  TChartToolset  from working, while ZOOMing continues to work fine. Is it supposed to work this way? ---------------------------------------------------------------------- wp · 2013-05-07 15:15 I don't see this here. Maybe you have set the LimitToExtent options of the panning tool? ---------------------------------------------------------------------- CM630 · 2013-05-08 06:04 Indeed, that is the case. I had LimitTo left and right extents. When I removed it, panning started working! Yet this did not solve my problem, since now I can pan outside the data area of the X scale. I got the impression, that Extent.FixTo is to be used for setting LogicalExtents, but it occurs that it sets simply Extents. I tried to replace chart.Extent.FixTo(Extents); with chart.LogicalExtent:= Extents; but unfortunately it does not work properly (i.e. I set Ymax (coords[4]):= 1000, it sets it to 863,9213749832943), although all my margins are set to 0. The only idea I have is to enable/disable LimitTo left and right if panning goes outside the values of the X scale or to force them back within the values area. This is quite a clumsy solution. Here is my procedure: Code: [Select]procedure SetLogicalExtent (var Chart:  TChart;const  Value: double;const Index: byte); var   Extents: tDoubleRect;   TempCoord:double;   PlusMinus:integer=+1; begin   Extents:=Chart.LogicalExtent;   if Index<3 then PlusMinus:=+1 else PlusMinus:=-1;   if ((Index<3) and (Value2) and (Value>Extents.coords [Index-2]))   then   begin //Max > Min      Extents.coords [Index]:= Value;      if (Index= 1) or (Index=3) then chart.BottomAxis.Inverted:=False;      if (Index= 2) or (Index=4) then chart.LeftAxis.Inverted:=False;   end   else   begin //Min < Max     TempCoord:=Extents.coords[Index+PlusMinus*2];     Extents.coords[Index+PlusMinus*2]:=Value;     Extents.coords[Index]:=TempCoord;     if (Index=1) or (Index=3) then chart.BottomAxis.Inverted:=True;     if (Index=2) or (Index=4) then chart.LeftAxis.Inverted:=True;   end;   chart.Extent.FixTo(Extents);  //Works fine this way   //chart.LogicalExtent:= Extents; //Does not work this way end; ---------------------------------------------------------------------- wp · 2013-05-08 15:54 I don't exactly understand what you want to achieve: Zoom and pan, but when zoomed you only want to pan vertically? The panning tools have a property Direction to restrict the panning. Did you try that? ---------------------------------------------------------------------- CM630 · 2013-05-13 06:49 When I pan/zoom horizonatlly, TAChart autoscales vertically. This causes several issue: 1. Sometimes I do not want the contents to be autoscaled vertically, but TAChart does not have an option to disable, so I have to set extents. 2. Sometimes I want to autoscale vertically, but  TAChart does it in an unpleasant way, so I have to use my procedures. I have uploaded a video in Youtube, for some reason it is extremely ugly, but I hope that you can get the idea. ---------------------------------------------------------------------- CM630 · 2015-02-23 14:45 Houston, we have a serious problem. Here is the source: Code: [Select]procedure TForm1.FormCreate(Sender: TObject); var   TempExtents: TDoubleRect; begin   Chart1.Margins.Bottom:=0;   Chart1.Margins.Left:=0;   Chart1.Margins.Right:=0;   Chart1.Margins.Top:=0;   Chart1LineSeries1.AddXY (2, 3);   Chart1LineSeries1.Pointer.Visible:=True;   Chart1LineSeries1.Marks.Visible:=True;   Chart1LineSeries1.ShowPoints:=True;   Chart1LineSeries1.Marks.Style:=smsLabel;  //This causes problems   Chart1LineSeries1.SetText(0,'aaaa');   TempExtents.coords[1]:=-10;   TempExtents.coords[2]:=-15;   TempExtents.coords[3]:=+20;   TempExtents.coords[4]:=+25;   Chart1.Extent.FixTo   (TempExtents); end;                                  If line Chart1LineSeries1.Marks.Style:=smsLabel; is commented everything is fine and FixTo works properly. But, when I enable it, FixTo does not work properly for the vertical scale. I set -15 to +25, but actual values are -15 to 31,... If I have a negative point, the -15 will become -16 or something like this. Is this a bug of if it is a feature, how am I to override it? ---------------------------------------------------------------------- wp · 2015-02-23 14:50 Please put your code into a compilable demo which I can test immediately - it makes my life easier... Put pas, lfm, lpi and lpr files of the demo into a single zip file which you can upload here, no other files please, in particular no exe and no ppu. Anyway, your code is pretty much self-contained and it was easy to create a project... I see the issue. Yes this is caused by the labels attached to the data points.The problem is that the labels are drawn ABOVE the data points. This chart does not take care of the positions where the data points are, it just makes additonal space for the labels. While this behavior makes sense in an auto-scaled chart it is unexpected for a chart with frozen extent. Therefore, I'll look into the sources of TAChart to find the issue. ---------------------------------------------------------------------- wp · 2015-02-23 15:13 Ah - nothing to do, it's already there: Series.Marks.AutoMargins = false ---------------------------------------------------------------------- CM630 · 2015-02-24 08:21 Thanks, it works! Now I am trying to handle with the problems caused by that:  Quote from: wp on October 25, 2012, 09:58:34 amThe max must always be greater than the min. Use the property "Inverted" of the axis if you want to have min and max interchanged. ---------------------------------------------------------------------- wp · 2015-02-24 09:46 No, as is says: the max must ALWAYS be greater than the min. "Inverted" is intended to invert the drawing direction of an axis, there's nothing else to be done for this purpose. Never exchange max and min to invert the axis. ---------------------------------------------------------------------- CM630 · 2015-02-26 07:09 I added some info in the wiki, hopefully it contains no mistakes. Quote from: wp on February 24, 2015, 10:46:26 am No, as is says: the max must ALWAYS be greater than the min... Yes, I understand how it works, I am just beating my head how to overcome the troubles that this behaviours creates  . EDIT: Which exactly extents does FixTo set? I thought it sets LogicalExtents, but odd results occur? ---------------------------------------------------------------------- wp · 2015-02-26 11:01 Quote from: CM630Which exactly extents does FixTo set? I thought it sets LogicalExtents, but odd results occur?The extent you apply it to: For the chart you can only call FixTo for the Chart.Extent which corresponds to the unzoomed window. When you zoom into the chart you modify the LogicalExtent, Or you can assign a DoubleRect to the LogicalExtent. And there is a CurrentExtent which is equal to the LogicalExtent plus the chart's margins translated to graph coordinates. A bit confusing... Please have a look at the attached demo which plays with various ways to modify the extents and shows their effect. ---------------------------------------------------------------------- CM630 · 2015-02-26 14:22 I kind of solved my problems by adding an additional storage for the extents. I use an additional function, which sets the extents, and if necessary swaps them and inverts the scales, but does not touch the additional storage. Still my control happens to crash, but I will dig more trough it. I will check you demo, but if I understand you right, I'd better use LogicalExtents[1..4]:=something instead of FixToo. But the odd thing is that I use FixTo, my minimum X scale is i.e. 1, but both LogicalExtemt.coords[1] and  CurrentExtemt.coords[1] return i.e. 3,xyz which in not even close neither to 1, nor to the other border I have no zoom, my chart margins are zeroes, I have no captions on the plot... I am not sure if I had a plot at all. The bad things is that code is too big, to reproduce it in a simple way. So as I mentioned I added an additional buffer for the coordinates and stopped reading logical exents, except when an OnChangeExtents event occurs. ---------------------------------------------------------------------- wp · 2015-02-26 15:33 I am pretty sure that you are doing something wrong. The entire extent thing I must confess is a bit tricky because all the calculations are done during the painting procedure. So, if you initiate an extent change at the wrong place I could be overridden by the original calculation - just a feeling. This could explain why your LogicalExtent returns non-sense results. Please try to create a simple application which shows the issue. ---------------------------------------------------------------------- CM630 · 2015-02-27 08:22 These two functions shall provide the solution. I have made some standalone tests and they seem to be okay, I will report after some more usage: Code: [Select]uses ...TAChartUtils... const    ExtXmin=1;    ExtYmin=2;    ExtXmax=3;    ExtYmax=4; ... function  GetExtentsReal (var aChart: TChart): TDoubleRect; var   OutputExtents: TDoubleRect;   SwapExtent: Double; begin    OutputExtents:= aChart.LogicalExtent;    if aChart.BottomAxis.Inverted then    begin       SwapExtent:= OutputExtents.coords[ExtXmin];       OutputExtents.coords[ExtXmin]:= OutputExtents.coords[ExtXmax];       OutputExtents.coords[ExtXmax]:= SwapExtent;    end;    if aChart.LeftAxis.Inverted then    begin       SwapExtent:= OutputExtents.coords[ExtYmin];       OutputExtents.coords[ExtYmin]:= OutputExtents.coords[ExtYmax];       OutputExtents.coords[ExtYmax]:= SwapExtent;    end;    Result:= OutputExtents; end; function  SetExtentsReal (var aChart: TChart; aPosition: Integer; Value: Double): Boolean; var   OutputExtents: TDoubleRect;   SwapExtent: Double; begin    Result:=False;    OutputExtents:= GetExtentsReal(aChart);    OutputExtents.coords[aPosition]:=Value;    if (OutputExtents.coords[ExtXmin]= OutputExtents.coords[ExtXmax]) or (OutputExtents.coords[ExtYmin]= OutputExtents.coords[ExtYmax])          then Exit;  //Function does nothing and returns False if equal extents exist.    if (aPosition=ExtXmin) or (aPosition=ExtXmax) then      if OutputExtents.coords[ExtXmin]> OutputExtents.coords[ExtXmax] then         begin           SwapExtent:= OutputExtents.coords[ExtXmin];           OutputExtents.coords[ExtXmin]:= OutputExtents.coords[ExtXmax];           OutputExtents.coords[ExtXmax]:= SwapExtent;           aChart.BottomAxis.Inverted:=True;         end      else         aChart.BottomAxis.Inverted:=False;    if (aPosition=ExtYmin) or (aPosition=ExtYmax) then      if OutputExtents.coords[ExtYmin]> OutputExtents.coords[ExtYmax] then         begin           SwapExtent:= OutputExtents.coords[ExtYmin];           OutputExtents.coords[ExtYmin]:= OutputExtents.coords[ExtYmax];           OutputExtents.coords[ExtYmax]:= SwapExtent;           aChart.LeftAxis.Inverted:=True;         end      else         aChart.LeftAxis.Inverted:=False;    aChart.LogicalExtent:= OutputExtents;    Result:=True; end;               What I noticed is that when a Chart is put on a form, logical and current extents return nonsense if invoked in the procedure FormCreate procedure. Should it be this way? I get no exception, so the Chart is created. Probably some default values for the logical and current extents  are not set? ---------------------------------------------------------------------- wp · 2015-02-27 10:19 QuoteWhat I noticed is that when a Chart is put on a form, logical and current extents return nonsense if invoked in the procedure FormCreate procedure.This reminds of what I forgot to write yesterday: As I saw recently it may help to call Chart.Prepare before manipulating extents. As I wrote above the extents of data ranges are calculated during painting; Prepare does this immediately.