TAChart AutoscaleTransformation lazarus / 55-TAChart ---------------------------------------------------------------------- wp · 2012-03-11 08:30 My current project with a TChart and various transformations crashes before data are loaded. Unfortunately, I did not succeed so far to strip down that project to reproducibly demonstrate the issue. However, I found out that FDrawData of TAutoScaleTransformation is nil in TAutoScaleTransformation.Clearbounds when the crash occurs and managed to avoid it by exiting the procedure in this case: Code: [Select]procedure TAutoScaleAxisTransform.ClearBounds; begin   inherited ClearBounds;   if FDrawData = nil then          // new     exit;                          // new   with TAutoScaleTransformData(FDrawData) do begin     FMin := SafeInfinity;     FMax := NegInfinity;     FOffset := 0.0;     FScale := 1.0;   end; end;  Unless this fix has any side effects I would appreciate if it could be incorporated into the official version. ---------------------------------------------------------------------- Ask · 2012-03-11 12:52 There will certainly be no adverse effects from adding this change. However, I am afraid it is only a workaround for the real bug, which, if not fixed, may manifest elsewhere. So before I add the fix, can you please post a stack trace from (View/Debug Windows/Call Stack => Copy All) its occurrence? Maybe I shall be able to find the cause. ---------------------------------------------------------------------- wp · 2012-03-11 13:52 This is the stack trace: Code: [Select]#0 TAUTOSCALEAXISTRANSFORM__CLEARBOUNDS() at C:\lazarus\components\TAChart\tatransformations.pas:680 #1 TCHARTAXISTRANSFORMATIONS__CLEARBOUNDS() at C:\lazarus\components\TAChart\tatransformations.pas:487 #2 TCHART__GETFULLEXTENT(, {A = {X = 1.623570557340873e-306, Y = 1.2828779085855282e-301}, B = {X = 1.2828850249788619e-301, Y = 2.1341054387579704e-314}, COORDS = {1.623570557340873e-306, 1.2828779085855282e-301, 1.2828850249788619e-301, 2.1341054387579704e-314}}) at C:\lazarus\components\TAChart\tagraph.pas:958 #3 UPDATEAXISTICS(0x294438, 0x2941f8, true) at tpTools.pas:151 #4 TMAINFORM__ACLOGPRESSUREEXECUTE(0x0, ) at tpMain.pas:199 #5 TMAINFORM__READFROMINI() at tpMain.pas:720 #6 TMAINFORM__BEFORERUN() at tpMain.pas:243 #7 main at TrionPlotter.lpr:19 and this is the procedure "UpdateAxisTics", the last one in my code where the error occurs: Code: [Select]procedure UpdateAxisTics(Axis:TChartAxis; LogMarksSource:TCustomChartSource;   Logarithmic:boolean); var   ymn, ymx : double;   ex : TDoubleRect;   t: TLogarithmAxisTransform;   ch: TChart; begin   Assert(Axis <> nil);   t := FindLogAxisTransform(Axis);   if t = nil then     exit;   ch := TChart(TChartAxisList(Axis.Collection).Owner);   if ch.RenderingParams.FIsZoomed then     ex := ch.LogicalExtent   else     ex := ch.GetFullExtent;   ymn := ex.a.y;   ymx := ex.b.y;   with Axis do begin     with Range do       if (ymn = ymx) then begin         if Logarithmic then begin           Max := ymx * 10;           Min := ymx * 0.1;         end else begin           Max := ymx + 0.5;           Min := ymx - 0.5;         end;         UseMax := true;         UseMin := true;       end else begin         UseMin := false;         UseMax := false;       end;     if Logarithmic then begin       if (ymx - ymn >= 2) then begin  // extent is in transformed units!         Marks.Source := LogMarksSource;         if Minors.Count > 0 then           Minors[0].Intervals.Count := 9;       end else begin         Marks.Source := nil;         Intervals.Tolerance := 5;         Intervals.MinLength := 10;         Intervals.MaxLength := 200;         Intervals.Options := [aipUseNiceSteps, aipUseMinLength, aipUseMaxLength];         Intervals.NiceSteps := Format('%g|%g|%g', [log10(2), log10(5), log10(10)]);         if Minors.Count > 0 then           Minors[0].Intervals.Count := 4;       end;     end else begin       Marks.Source := nil;       Intervals.Options := [aipUseNiceSteps, aipUseMinLength, aipUseMaxLength];       Intervals.MaxLength := 50;       Intervals.MinLength := 5;       Intervals.NiceSteps := '0.2|0.5|1.0';       if Minors.Count > 0 then         Minors[0].Intervals.Count := 4;     end;   end;   t.Enabled := Logarithmic; end; Btw, is there a more elegant way to find the chart to with an axis belongs than Code: [Select]TChart(TChartAxisList(Axis.Collection).Owner) ---------------------------------------------------------------------- Ask · 2012-03-11 14:50 I see the problem. You are calling GetFullExtent before the chart is drawn for the first time (as I conclude from the fact that there is BeforeRun method in the stack). Transformations are initialized in the TChart.Draw method, so they fail if you do not call it. This is inconvenient, so in r35887 I have extracted TChart.Prepare procedure which you can call at the beginning of UpdateAxisTics. User code is not supposed to directly access RenderingParams fields, so you should use ch.LogicalExtent, which will also be updated after the call to Prepare. Please test. As a final unrelated note, set Intervals.Tolerance to some small value -- in my tests, it improves the positioning to the extent that you may not need a separate LogMarksSource. Quotemore elegant way to find the chart to with an axis belongsAxis.GetChart? ---------------------------------------------------------------------- wp · 2012-03-17 21:47 Sorry for the late response. Thanks for TChart.Prepare. It works fine as far as the crash is concerned. However, the y values in the chart's CurrentExtent are not up to date with the data (the extent of the series' source is ok). How can I enforce recalculation of the chart's extent? Code: [Select]... set Intervals.Tolerance to some small value -- in my tests, it improves the positioning to the extent that you may not need a separate LogMarksSource. I know but for logarithmic axes I want marks only at the powers of 10 if the extent exceeds a certain threshold (in "UpdateAxisTics" 2 decades), and I did not find a better way to achieve this. The Intervals properties, also Tolerance, do not suppress intermediate values. ---------------------------------------------------------------------- wp · 2012-03-18 15:38 I found the error: Since a LogarithmAxisTransform and an AutoscaleAxisTransform are involved in my code, I have to call Axis.GetTransform.GraphToAxis in order to get back the original values. The following code works fine now: Code: [Select]procedure UpdateAxisTics(Axis:TChartAxis; LogMarksSource:TCustomChartSource;   Logarithmic:boolean); var   ymn, ymx : double;   ex : TDoubleRect;   t: TLogarithmAxisTransform;   ch: TChart; begin   Assert(Axis <> nil);   ch := TChart(Axis.GetChart);   if ch = nil then     exit;   ch.Prepare;    t := FindLogAxisTransform(Axis);   if t = nil then     exit;   ex := ch.CurrentExtent;   ymn := Axis.GetTransform.GraphToAxis(ex.a.y);   ymx := Axis.GetTransform.GraphToAxis(ex.b.y);   with Axis do begin     with Range do       if (ymn = ymx) then begin         if Logarithmic then begin           Max := ymx * 10;           Min := ymx * 0.1;         end else begin           Max := ymx + 0.5;           Min := ymx - 0.5;         end;         UseMax := true;         UseMin := true;       end else begin         UseMin := false;         UseMax := false;       end;     if Logarithmic then begin       if (ymx/ymn >= 100) then begin          Marks.Source := LogMarksSource;         if Minors.Count > 0 then           Minors[0].Intervals.Count := 9;       end else begin         Marks.Source := nil;         Intervals.Tolerance := 3;         Intervals.MinLength := 10;         Intervals.MaxLength := 200;         Intervals.Options := [aipUseNiceSteps, aipUseMinLength, aipUseMaxLength];         Intervals.NiceSteps := Format('%g|%g|%g', [log10(2), log10(5), log10(10)]);         if Minors.Count > 0 then           Minors[0].Intervals.Count := 4;       end;     end else begin       Marks.Source := nil;       Intervals.Options := [aipUseNiceSteps, aipUseMinLength, aipUseMaxLength];       Intervals.MaxLength := 50;       Intervals.MinLength := 5;       Intervals.NiceSteps := '0.2|0.5|1.0';       if Minors.Count > 0 then         Minors[0].Intervals.Count := 4;     end;   end;   t.Enabled := Logarithmic; end;