TAChart: Changing standard time/date format for time axis lazarus / 55-TAChart ---------------------------------------------------------------------- kupferstecher · 2016-08-02 13:41 In TAChart it is possible to show TTimeDate-Values on the X-Axis when using TDateTimeIntervalChartSource. The formatting of the labels is adjusted automatically according to the range, which works quite nice. The only drawback is, that it is not possible to distinguish the hh:nn and nn:ss, as both are seperated by a ':'. I would like to have a '.' here instead. Examples, what a label actually shows: mm.yyyy -> 08.2016 dd.mm -> 02.08 dd hh:nn -> 02 15:22 hh:nn -> 15:22 nn:ss -> 22:50 ssµµµms -> 50300ms µµµms -> 300ms What I would like to have (most critical for me is the nn.ss one): mm.yyyy -> 08.2016 dd.mm. -> 02.08. dd. hh:nn -> 02. 15:22 hh:nn -> 15:22 nn.ss -> 22.50 ss.µµµ -> 50.300 .µµµ -> .300 Is this somehow possible to define? If I change the write in the initialisation DefaultFormatSettings.LongTimeFormat:= 'hh:nn.ss'; then nothing happens. I'm aware of that the formatting is related to the system settings, but it just doesn't look good in the way it is now. I guess the character defined in DefaultFormatSettings.TimeSeparator is used and there is no distinguishing between hh/nn and nn/ss. Setting DateTimeFormat of TDateTimeIntervalChartSource is not a solution, as the dynamic formatting is disabled by that. Thanks for your help! ---------------------------------------------------------------------- wp · 2016-08-02 19:16 Just uploaded a new version of TDateTimeIntervalChartSource which supports individual formatting of the date/time steps selected depending on axis range. The new property is called DateTimeStepFormat and has subproperties for year, month, etc steps. It is maybe a bit difficult to find the correct property if you want to modify a particular format string. Have a look at the axis demo, page "Date and time"; here you can activate some alternate format strings; a label displays the date/time step identifier currently selected. Maybe it gets clearer then which format you have to adapt. The format strings follow the syntax needed for the FormatDateTime function (http://www.freepascal.org/docs- html/rtl/sysutils/formatchars.html). Note that milliseconds are not specified by the symbol "µ", but by "z". The symbols "/" and ":" are replaced by the date and time separators of the DefaultFormatSettings, respectively. Beyond that, DefaultFormatSettings are ignored here. You can enclose any other text by quoting it. So, if you want a slash in a date, but your DateSeparator is "." then the format string must be dd"/"mm"/"yyyy - without the quotes the / would be replaced by a . Another Example (not used in the demo): s","zzz" sec" writes the seconds, a comma as decimal separator, then three digits for milliseconds, and the unit string "sec". You must use Lazarus trunk to get this new feature. Because it is a new feature it will not be backported to the upcoming Lazarus 1.6.2. So, if you don't want to use trunk you'll have to wait for Laz 1.8 or 2.0... ---------------------------------------------------------------------- kupferstecher · 2016-08-03 19:15 Thanks alot for the detailed reply! I'll have a look. ---------------------------------------------------------------------- kupferstecher · 2016-08-03 20:34 I did a first test and it works really nice! From your long description I thought it is more complicated, but the properties are quite intuitive! The only thing to be careful is with the quotation marks ". I just used them for all formattings to be sure it doesn't change behaviour on other systems. Here are my settings, leading to the desired formatting I mentioned in the first post: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   DateTimeIntervalChartSource.DateTimeStepFormat.MillisecondFormat:= 's"."zzz';  DateTimeIntervalChartSource.DateTimeStepFormat.SecondFormat:= 'nn"."ss';  DateTimeIntervalChartSource.DateTimeStepFormat.MinuteFormat:= 'hh":"nn';  DateTimeIntervalChartSource.DateTimeStepFormat.HourFormat:= 'dd"." hh":"00';  DateTimeIntervalChartSource.DateTimeStepFormat.DayFormat:= 'dd"."mm"."';  DateTimeIntervalChartSource.DateTimeStepFormat.WeekFormat:= 'dd"."mm"."';  DateTimeIntervalChartSource.DateTimeStepFormat.MonthFormat:= 'mm"."yyyy"';  DateTimeIntervalChartSource.DateTimeStepFormat.YearFormat:= 'yyyy'; There seems one mistake in the default settings, the default for SecondFormat is hh:ss instead of nn:ss resulting in the (wrong) outline of hours:seconds instead of minutes:seconds. One further nice to have in the formatting would be an optional clearance for Axis.Marks.OverlapPolicy:= opHideNeighbour (in the Axis settings, nothing to do with the TDateTimeIntervalChartSource). Now two labels can get randomly close as long as there is no overlapping. Or is there already a property to realise that? Thanks again, the chart component is really great! ---------------------------------------------------------------------- wp · 2016-08-03 21:05 Fixed the typo, sorry. Overlapping labels is a new story. You'll find the corresponding properties in the Params of the DateTimeIntervalChartSource (OverlapPolicy gives you less control). There are several conflicting options, and it is a bit difficult to describe. The "MinLength" is the smallest pixel distance between label positions. If two successive labels are closer then the algorithm selects the next wider interval. Similarly with "MaxLength". So, in order to get less crowded labels you should increase the MinLength. I cannot remember about the priorities, maybe you seek here in the forum the post where I asked a similar question regarding the axis Intervals to Alexander Klenin ("Ask") who wrote this feature. What I am missing is an option to set labels at "nice" positions, i.e. I would like to force month labels to be at the first day of a month, not somewhere in between. But I don't have a good idea so far. ---------------------------------------------------------------------- kupferstecher · 2016-08-04 20:30 Hello WP, the "MinLength" and "MaxLength" - as far as I know - are adjusting the intervals of the grid. But I only want to have less labels in case of overlapping or when they are too near, the grid should stay the same. Below some offtopics, but as the initial question is already solved, I guess thats allright~~ I think for numeric values (i.e. of the decimal system) the distance between each grid bar should always be the same for visual purpose. I.e. it just looks better. For time values this could work the same, but date values with the 31, 30 and 28 days per month cannot suit that, different distances between the grid is a must otherwise one label would be March 1st, the next March 7th 18:00, the next March 15th, 12:00 etc. I guess thats also the issue for the NiceSteps- algorithm, that it rather arranges the grid in same distances than picking out the first of each month. On the other hand for numeric values in my opinion the easiest way to define a nice looking axis is to give the left bound, the right bound and the spacing (here: Intervals.Count). With these three values the user can fully control each label position (make his own "NiceSteps") and NiceSteps would not be needed anymore. The distances in this case then should be actually all the same. For panning, the labels and grid should float simultaneously, so the label value would always be the same. When zooming, the labels and grid should also stay at the same value (not position). When zooming-in therefore the number of labels will be decreased to a certain point. When they get too rare they could be doubled, means the grid intervals get half distance. When zooming out the same vise versa. So for example a spacing of 5 is defined, when zooming in there will be first just 4 grid bars but when reaching a zoom level with just 3 bars, then the grid distance is cut in half resulting in 6 grid bars. In this way the labels would always be on a 2^n-fraction of the "home"-labels. It means, the grid policies in zoomed mode would be different to a non-zoomed one. For decimal data this would be very nice, I think. Even for a logarithmic scale this could be adaptable, I guess. If the "distance" (actually difference) of the logarithmic value intervals is always the same, then it would be possible to distribute the grid in a predictabel (logarithmic) way. Now this was just some imagination of mine, I couldn't contribute here, as the TAChart component is a black box to me. So if you are not interested in my thoughts to that topics then just don't mind :-) Regards ---------------------------------------------------------------------- wp · 2016-08-04 22:54 I know that the current axis labelling method is not perfect. For date/time, the issue is that the DateTimeIntervalChartSource is an "Interval"ChartSource, i.e. intrinsically tries to get constant intervals. This makes it impossible to get nice dates at the month and year levels. The other source of headache is the log scale which is unpractical for a scientific or technical user. As a workaround I proposed in many related posts here to pack all labels into a TListChartSource which you must create and populate on your own - a little piece of work, but you have full control over where labels are put. A general solution for zooming and panning, however, will soon get very complex (you can find some posts here). Your idea of freezing the axis limits and using a given count of labels is already possible today. Use Axis.Range to define the limits of the axis, specify the number of labels in Axis.Intervals.Count and disable the other labelling criteria, i.e. set Axis.Intervals.Options = [aipUseCount]. In case of the date/time chartsource you must use its Params instead of the Axis.Intervals. This does not work with zooming because unlike what you propose zooming can make the axis start and end at any value which makes the power of 2 approach unfunctional. If you want to avoid overlapping labels but keep the day-to-day grid, for example, you can consider adding a second x axis with labels turned off and grid turned on and with a ListChartSource containing only day values. But anyway - all solutions which you design for your current project are standalone solutions and nothing but workarounds for a deficiency of TAChart. Maybe I should take the time to re-think a better way. ---------------------------------------------------------------------- kupferstecher · 2016-08-05 08:35 Quote from: wp on August 05, 2016, 12:54:40 amAs a workaround I proposed in many related posts here to pack all labels into a TListChartSource which you must create and populate on your own - a little piece of work, but you have full control over where labels are put.For a non-zoomable Chart this is perfectly fine. Just when zooming is performed, then the overall impression suffers. but as you said, the implementation gets very complex... Besides, the existing functionality actually is very powerful. Quote from: wp on August 05, 2016, 12:54:40 amYour idea of freezing the axis limits and using a given count of labels is already possible today. I actually use these settings already. For the standard view its great, just when panning and zooming, the label values are getting really odd (of course...). Quote from: wp on August 05, 2016, 12:54:40 amThis does not work with zooming because unlike what you propose zooming can make the axis start and end at any value which makes the power of 2 approach unfunctional.My idea is always to use the power of 2 values, no matter at which value the axis starts and ends, it means the labels don't stick to the axis borders. In this way when zooming in, one could actually see zooming into the grid. Now the grid sometimes is jumping around. Example: Bounds 0 to 10 with spacing 5 results in the grid/labels: 0; 2; 4; 6; 8; 10 Zooming-in to bounds 3,36 to 5,14 would result in grid/labels: 3.5; 3.75; 4; 4.25; 4.5; 4.75; 5 I guess I'll try to understand how the ChartSources work, perhaps (just perhaps) I can make an example implementation then. ---------------------------------------------------------------------- wp · 2016-08-05 10:58 Quote from: kupferstecher on August 05, 2016, 10:35:12 amExample: Bounds 0 to 10 with spacing 5 results in the grid/labels: 0; 2; 4; 6; 8; 10 Zooming-in to bounds 3,36 to 5,14 would result in grid/labels: 3.5; 3.75; 4; 4.25; 4.5; 4.75; 5 What is the algorithm how to calculate the new spacing here? In my understanding there is a problem with the power-of-2 approach: It leads to "ugly" labels, such as the 0.25 interval from above example, probably also 0.125 and 0,0625 etc if zooming more. The aipNiceSteps Option tries to stick to 1/2/5 labels. Yes, an example of your idea would be very welcome. Just add a TListChartSource to the form, link it to the Marks.Source of an axis and set the Marks.Style to smsLabel. Write a method "PopulateLabels" (or similar) in which you clear the ChartSource (ListChartSource1.Clear)query the extent of the axis (var ext: TDoubleRect; ext := Chart1.LogicalExtent)and add the axis values which should have a label (apply it to both x and y to make the chartsource usable also in case of rotated axes): ListChartSource.Add(newpos, newpos, text_at_label). Calculate the newpos values according to your algorithm knowing the extent limits (ext.a.x and ext.b.x for the bottom axis)Call this whenever the chart's extent changes (OnExtentChanged event). ---------------------------------------------------------------------- kupferstecher · 2016-08-07 10:59 Quote from: wp on August 05, 2016, 12:58:11 pmIn my understanding there is a problem with the power-of-2 approach: It leads to "ugly" labels, such as the 0.25 interval from above example, probably also 0.125 and 0,0625 etc if zooming more. The aipNiceSteps Option tries to stick to 1/2/5 labels.Thats right, when zooming-in a lot then there will also be ugly labels. But the advantage is, that the grid really always is constant. The problem with aipNiceSteps is, that this is an automatic axis arrangement, i.e. you loose control over the positions. With my method if you want to place labels on the positions 0; Pi/2; Pi; 1.5Pi;2Pi then you simply can set XMin:= 0, XMax:= 2Pi and Intervals.Count:= 4. The labels will be ugly in this case, but the grid is as desired (fully under owns control). I know the aipNiceSteps can be adjusted to ones needs and for the software developer thats fine, but if you provide a chart in your application where the user can display random data, then the settings should be as simple as possible. Nevertheless I see the problem of the 2^n approach and tried also a decimal one with (10^n [+5] [+2]). 5 and 2 are optional and used according to the zoom factor, to keep the number of grid/labels in a better range. In the attached demo you can select either of them for the X-axis. For comparison purpose the Y-axis is configured with aipUseNiceSteps. In my approach the grid always depends on the unzoomed settings, thus if the bounds and spacing are choosen bad (e.g. xmin=0, xmax=10, spacing=3) then all sub grids will be bad. Quote from: wp on August 05, 2016, 12:58:11 pmWhat is the algorithm how to calculate the new spacing here? Difficult to describe and probably not the optimal implementation, but it works~ Basis is always the unzoomed grid. First I calculate the (constant) grid distance, to do that the ratio between the unzoomed extent and the zoomed one is used. In the zoomed-in state (Ratio < 1) the ratio is multiplied by the factor 2 until the Ratio gets a value of > 0.7 . In the same number of multiplications the unzoomed grid distance is divided by 2 (halving the distance). The value 0.7 comes from the number of grid bars/labels: The quantity shouldn't get less than 0.7 times the number in the unzoomed state. Zooming-out works in the same way, but vise versa. With this grid distance I determine the first visible grid bar within the zoom range and then just allign one by one in the same distance. The decimal approach works quite similar. In my implementation I derive from a TIntervalChartSource. The problem is that I don't know how to get the bounds of the unzoomed state. Especially when using transformations this could become troubblesome. Any idea here? In the demo I took the Chart.Extent.Xmin and Xmax directly, so this is really just a demo. One possibility would be to define the values in the ChartSource-component itself. Disadvantage is, this would need double definition of the bounds, once in Chart, once in the ChartSource-component. Advantage is, automatic axis ranges would be possible. Regards~ ---------------------------------------------------------------------- kupferstecher · 2016-08-07 18:54 Here an update of the demo. The class TFixedIntervalChartSource now is derived from TCustomChartSource, there are three properties to define the nonzoomed extent now: GridCount, DefaultExtentMin and DefaultExtentMax. The actual extent of the chart still has to be defined within the extent properties. Another property IntervalMode can have the value imDual for the 2^n approach and imDecimal for improved outline. With these properties there is no more internal dependency to the demo-chart, so it could be easily used for projects. It seems also to work with linear transformed axis (others not tested). Usage: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   XIntervalSource:= TFixedIntervalChartSource.Create(Self);  XIntervalSource.IntervalMode:= imDecimal;  XIntervalSource.GridCount:= 5;  XIntervalSource.DefaultExtentMin:= 0;  XIntervalSource.DefaultExtentMax:= 10;  Chart1.BottomAxis.Marks.Source:= XIntervalSource; ---------------------------------------------------------------------- wp · 2016-08-07 22:17 Very interesting, really! Initially, I was confused by the naming (TFixedIntervalChartSource), and by your description of a constant count of labels, because after zooming the count of labels changes. But after some playing I was beginning to see the point: If you zoom around a labeled point, then the label is still existing afterwards. In the builtin algorithm, however, this label usually disappears after zooming. This is a very interesting feature. To avoid the confusion with the name I'd propose a more general name, such as TAxisChartSource. Of course, it would be great if your algorithm would work also with nonlinear transformations as well. An interesting case would be a log scale. Suppose an axis between 1 and 1000; on log scale the axis limits are 0 and 3. I'd expect to see primarily labels at the integer parts of the graph units (0, 1, 2, 3), i.e. powers of 10 of the axis units (1, 10, 100, 1000). Now, if zooming happens, additional values may be inserted in between, but in order to get "nice" labels these additional labels will be at nonequidistant points (e.g: 1, 3, 10, 30 which in graph units corresponds to 0, 0.477, 1, 1.477) For working with transformations, the TValueInRangeParams passed to the ValuesInRange method contains fields with function pointers for the current transformations and their inverse: FAxisToGraph converts from axis to graph units, and conversely FGraphToAxis converts from graph to axis units. And there is also a FGraphToImage function pointer which converts the graph units to drawing units (usually pixels) (and a final conversion FScale from these units to final device units, as needed e.g. for printer output with a much higher pixel density). The ValuesInRangeParams contain also a field FFormat, you should use this to calculate the label (instead of FloatToStr). Using dedicated DefaultExtentMin/Max values for the ChartSource seem to pose some limitation to me because it does not take care of the automatic axis range determination. I see that there is not direct way for the ChartSource to get the unzoomed axis limits. Therefore I modified TAChart such that two more fields (e.g., FFullExtentMin and FFullExtentMax) are available in the ValuesInRangeParams. It is not yet in the official sources because I don't know where this discussion will lead us. Please copy the attached files into your TAChart folder (make a backup copy first). Note that the modified files are from the trunk version of Lazarus, I don't know if 1.6 will compile with this modification. In the second attachment you find a modified version of your demo where the DefaultExtentMin/Max properties are removed. ---------------------------------------------------------------------- wp · 2016-08-08 08:32 Quote from: wp on August 08, 2016, 12:17:32 amThe ValuesInRangeParams contain also a field FFormat, you should use this to calculate the label (instead of FloatToStr). The required code is already written in your local AddValue function, just uncomment it. This would have the advantage of respecting the format specified by the axis' Marks.Format. You should also override the inherited method GetCount to return 0. This prevents a crash if the user calls one of the inherited Extent* methods (due to calling an abstract method). ---------------------------------------------------------------------- kupferstecher · 2016-08-08 20:33 For the naming thing we can change it, no problem, its always difficult to find a good name~ Quote from: wp on August 08, 2016, 12:17:32 amUsing dedicated DefaultExtentMin/Max values for the ChartSource seem to pose some limitation to me because it does not take care of the automatic axis range determination. [...] Therefore I modified TAChart such that two more fields (e.g., FFullExtentMin and FFullExtentMax)How are the values FFullExtentMin/Max determined? The problem is, if the autoscaling is used, then the label values are not predictable and will result in ugly values. E.g. the automatic range is from 0 to 3 and GridCount is set to 5 then the labels will be 0; 0.6; 1.2; 1.8 etc. and all sub labels would be similar bad. You can see that behavior when adding the class to the Y-axis in your modified demo. For me an automatic ranging should be treated in the same way as zooming. So in my opinion the dedicated parameters are even for autoscaling actually useful. Because the label values will always stay the same as defined, just the quantity and intervals may change. Quote from: wp on August 08, 2016, 12:17:32 amOf course, it would be great if your algorithm would work also with nonlinear transformations as well. An interesting case would be a log scale. Suppose an axis between 1 and 1000; on log scale the axis limits are 0 and 3. I'd expect to see primarily labels at the integer parts of the graph units (0, 1, 2, 3), i.e. powers of 10 of the axis units (1, 10, 100, 1000).I tested for the logarithmic scale without changing anything in the unit, it actually works, the grid results in decreased distance. But of course its not in the way you pointed out. I think therefore a seperate ChartSource-class is needed. Normally the grid for a logarithmic scale is something like 1;2;3;...;9;10;20;30;...;90;100;200; etc. But labels might only be at 1;10;100;1000. A problem is how to handle zooming. If grid distance should be increased then how could it be? 1;3;5;7;9;10;30;50...? Not so nice, between 9 and 10 it is a different quality then the others. 1;4;7;10;40;70...? This seems quite ok, but grid bars are much less. When zooming in (or out) things get interesting. Perhaps its possible to do it in a similar way like in the TDateTimeIntervalChartSource, where always the next smaller unit is resolved. It means for bounds between 9 and 12 the grid could be 9;9.1;9.2;9.3...9.9;10;11;12. And number of grid/labels again trying to determine according a (not strict) parameter GridCount. But if the range is from 7 to 12 then we suddenly need the subgrid for 7, for 8 and for 9. Seems really not so easy... Quote from: wp on August 08, 2016, 12:17:32 amThe ValuesInRangeParams contain also a field FFormat, you should use this to calculate the label (instead of FloatToStr). I just commented out, because it gave me a compile error. I leave it first until the parameters and algorithms are fixed. Same with GetCount. But thanks for mentioning! I probably wouldn't have recognised. ---------------------------------------------------------------------- wp · 2016-08-09 09:24 Quote from: kupferstecher on August 08, 2016, 10:33:09 pmHow are the values FFullExtentMin/Max determined? The problem is, if the autoscaling is used, then the label values are not predictable and will result in ugly values. Then this a severe weakness of the algorithm, it MUST be able to produce better results than the builtin algorithm automatically. BTW: How does your algorithm behave if max is positive and min is negative? Does it produce a label to be at 0? Here is my idea. It is based on the observation that your algorithm produces "nice" starting points. My algorithm calculates a "nice" starting point and interval: Calculate range R = max - min (e.g. max = 123, min = 101 --> R = 22)Calculate estimated Interval I' = R / GridCount ( --> if GrindCount = 5 --> I' = 22/5 = 5.5) -- note: GridCount may change according to the situation.Calculate mantissa M and exponent E of estimated interval (M = 5.5, E = 0)If M > 5 --> Label interval I = 5 x 10^EIF M > 2 --> Label interval I = 2 x10^EIf M > 1 --> Label interval I = 1 x 10^E  ( --> I = 5)Round axis min to be a multiple of I (round down): Min = floor(min / I) * I  (--> Min = 100)Add intervals until value is > max ( --> 100, 105, 110, 115, 120, 125)Another example: min = 100.1229, max = 100.1256  --> R = 0.0027 -->  I' = 0.00054 --> M = 5.4, E = -4 --> I = 0.0005 --> labels at 100.1225, 100.1230, 100.1235, 100.1240, 100.1245, 100.1250, 100.1255, 100.1260 And a final example enclosing the zero: min = -98, max = 23 --> R = 121 --> I' = 24.2 --> M = 2.42, E = 1 --> I = 20 --> labels at -100, -80, -60, -40, -20, 0, 20, 40 Note that the first and last label are always beyond the real axis range. They will not be painted anyway, but are required in order to fill the space to the first/last visible label with minor labels if needed. As for logarithmic scaling I tried to implement the following idea several times but never completed it: There are two calculation modes, based on direct and transformed (log) valuesDefine a transition point, i.e. say: range is 2 decadeIf range is > 2 decades ("transition point") work on log values. Integer parts of logs define the axis labels --> Label = 10^integer. If range is very large then it may be required to skip one or more labels (check for overlapping labels?)If range is < 2 decades use your/my algorithm with direct values ---------------------------------------------------------------------- wp · 2016-08-09 13:56 Quote from: wp on August 08, 2016, 12:17:32 amInitially, I was confused by the naming (TFixedIntervalChartSource), and by your description of a constant count of labels, because after zooming the count of labels changes. But after some playing I was beginning to see the point: If you zoom around a labeled point, then the label is still existing afterwards. In the builtin algorithm, however, this label usually disappears after zooming.Oh I'm so stupid - I did not reckognize that the Intervals.Options only uses aipUseCount in your example. In the default settings, the axis behaves normally, in my eyes. So, I fear I still do not understand the advantage of your labeling algorithm. You say above: "the grid really always is constant." - but what I see in your demo: it is not, it is changing like with the builtin-labeling routine. ---------------------------------------------------------------------- kupferstecher · 2016-08-09 14:07 I guess its better to not consider the logarithmic algorithm first, otherwise its too much at the same time... Quote from: wp on August 09, 2016, 11:24:30 amThen this a severe weakness of the algorithm, it MUST be able to produce better results than the builtin algorithm automatically.Well, the purpose was not to beat the built-in (automatic) algorithm:-) Perhaps I have to explain the purpose better. I designed the algorithm actually only for zooming purpose, as in the non-zoomed state the grid/labels should be fully (100%) under user control. Consider the case the user wants to plot something depending on an angle. Desired grid positions are 0;45;90;135;180. For angles these are 'natural' positions. Therefore he chooses the range 0 to 180 (degree) and GridCount to 4, thats it. The grid is forced to these values, as the intervals are fixed in length (thats why I chose the name FixedIntervalChartSource). And the intervals are not just constant but are actually user defined through the bounds and the GridCount. In my opinion a quite intuitive approach for the user. When zooming then some automatism is needed, and as a different GridCount is not critical there, the algorithm suits quite good, I think. The case of automatic axis scaling can be deemed as zooming. The GridCount couldn't be kept, but more important the grid values still fit to the data, as they are always interpolated from the 'home' window. Through this interpolation the grid always fits to the data, but an explicit definition of the 'home' window is needed. On the oposite with an fully automatic grid arrangement such label positions would be difficult to realise. Or even imagine positions of 0;22.5;45... In the automatic system, gridCount couldn't be applied to strict, otherwise the labels get ugly. So how to realise such a spacing? Quote from: wp on August 09, 2016, 11:24:30 amBTW: How does your algorithm behave if max is positive and min is negative? Does it produce a label to be at 0?I just had to check that in the demo. You can play around with the bounds, thats why I placed the EditBoxes in the demo. It actually does force a label to zero, which I consider as bug, as it reduces user controlability:-) Quote from: wp on August 09, 2016, 11:24:30 amHere is my idea.Your algorithm always spans the grid starting from 0 and guarantees a constant grid. I think this is very nice! The built-in algorithm could come up with something like 0;0.4;0.7;1, which is very nasty. I'd appreciate a demo implementation ;-) Perhaps I can also use your approach to optimise the speed in my algorithm, as now the interpolation is somehow iterative. Quote from: wp on August 09, 2016, 11:24:30 amNote that the first and last label are always beyond the real axis range. They will not be painted anyway, but are required in order to fill the space to the first/last visible label with minor labels if needed.Can you describe that more detailed? Whats the difference if its in the list and not? ---------------------------------------------------------------------- kupferstecher · 2016-08-09 14:15 You were faster then me... Hope its clearer now. Quote from: wp on August 09, 2016, 03:56:24 pmI did not reckognize that the Intervals.Options only uses aipUseCount in your example.When using the ChartSource then the Intervals.Options are useless anyways. But in unzoomed status it behaves exactly (!?) like [aipUseCount]. But zooming gets interesting then. ---------------------------------------------------------------------- wp · 2016-08-09 15:50 Quote from: kupferstecher on August 09, 2016, 04:07:57 pmThe grid is forced to these values, as the intervals are fixed in lengthThe first part of this sentence cleared my understanding, the second part is wrong. The advantage of your algorithm is not that the intervals are fixed in length, but that the labels are attached to the series. We were discussing zooming all the time, and this confused me because this is where different properties come into play. But if we look at panning then the difference is clear. In the attachment there is a modification of the last demo uploaded. Now with a sine function, and ranging between -180 and 180. The program starts with your algorithm - labels are fine. Then switch to standard labeling - the labels are fine as well because I selected Axis.Intervals.Options := [aipUseCount] (nothing else!), Axis.Intervals.Count = 4, and Chart.Margins.Left = 0 and .Right = 0 (this is because the built-in labeler puts the first/labels at the axis border if the aipUseCount option used, but the first/last datepoint is shifted in by Chart.Margins.Left/Right - probably a bug). Now drag the curve by moving the mouse with the right button down. As you see: the standard labels are attached to the chart, not to the data, and immediately get very ugly. Go back to your algorithm and drag: the labels move with the curve! This really is a great advantage and a strong argument for your ChartSource because it is a very natural behavior. As for your question regarding the first and last label being outside the chart area: Each axis has a property Minors which you can use to add another set of ticks and labels and grid lines. I normally use it to draw minor ticks without labels and without grid lines - looks really good. These additional ticks/labels are drawn between the main labels. Therefore if the first label is inside the chart area then there are no minor ticks before it, the same with the last label. I'll try to implement my algorithm of the previous post. Give me some time... ---------------------------------------------------------------------- wp · 2016-08-09 21:18 The attachment contains an extended version of your demo, now with a TAxisLabelChartSource as roughly described a few posts above. It has a property "Interval" which - if not zero - defines the fixed interval between the axis labels. This is only for very special cases because it does not change at all, even after zooming. Automatic scaling is activated if "Interval" is 0 and is determined by the StepCount and NiceSteps properties: StepCount is the same as your GridCount (I renamed it because I never knew whether it is the number of grid lines or spaces). NiceSteps is a string containing all allowed step values separated by a | character. The order of magnitude of these numbers is ignored. The algorithm seeks that step value and multipies it with the correct power of 10 such that the required StepCount is best approximated. The default is '1|2|5'; with the data in the demo where the axis ranges between -180 and 180 this means that the intervals can be 10, 20, 50, or 100 in principle; along with the given StepCount of 4 the algorithm selects 50. For a sine curve this is not very nice, it would be better to have multiples of 30, 60, or 90 --> Change NiceSteps to '3|6|9' (you can also use '30|60|90') --> voila! There are some other parameter combinations for the built-in labeler. It can be seen that its default settings (top-most radiobutton) can produce similar results, also after panning, but it is extremely difficult to control the label spacing. The versions with aipUseNiceSteps only show only the center label, probably the MaxLength is essentially needed here. BTW: the TAxisLabelChartSource the labels do not change when the window is increased because the chart size does not enter the calculation. This may be seen as an advantage (better controllability of label positions) or disadvantage (too sparse labels in case of large or even full-screen chart). ---------------------------------------------------------------------- kupferstecher · 2016-08-10 19:53 Quote from: wp on August 09, 2016, 11:18:12 pmBTW: the TAxisLabelChartSource the labels do not change when the window is increased because the chart size does not enter the calculation. This may be seen as an advantage (better controllability of label positions) or disadvantage (too sparse labels in case of large or even full-screen chart).I guess we have three levels of automation now: 1. The build-in algorithm is fully automatic, number of labels, intervals etc. all is controlled by the algorithm, depending on data and size of chart. A custom manipulation is difficult. 2. Your algorithm which uses the StepCount as basis and does everything else automatic. Its easy adjustable to the data and gives more freedom for manual adjustment of the sizings in a simple way (using the StepCount). 3. My algorithm with manual set-up in the unzoomed state and full grid automation for zooming and panning. Therefore with more control of the grid in unzoomed state, but also more parameters needed. I think in total having these three options is a very nice choice for the programmers! Do you see a chance (or advantage) to combine them in one class or would you rather keep it seperated? For the build-in one with options [aipUseCount,aipUseNiceSteps] I would actually expect a grid like calculated with your algorithm. For my algorithm in oposite I think its better to keep it separated as the parameters could get confusing. On the otherhand using only the option [aipUseCount], then I would expect a behaviour like in my algorithm. When I compared our two algorithms, I found that the use of StepCount is a bit different. In yours (we should really give them qualified names~) the StepCount is actually the minimum number of steps. When zooming, the actual step number may be the same or higher, but never less. In my implementation on the other hand for zooming its more like the avarage number, it could get lower and higher. I think both is reasonable, in my case the number is defined in the unzoomed state and when zooming, then in the first step the grid should get less. In your case the algorithm is quite nice using the minimum approach. For clearity it may be helpful to rename it to MinStepCount? About the naming things: As I'm a 'new member' I hesitate with giving it a name, also its not so clear for me how to merge it into the TAChart component. Is there something like a standard procedure for such extensions/modifications? How to get 'agreement' from the developers? Said that, here some proposals: In my opinion the key difference in your algorithm in comparison to the built-in one is that the StepCount has to be defined, this makes it an interval chart source. As this class name already exists and this one is explicitly designed for an axis it could be Named TIntervalAxisSource. For my algorithm it could be TManualIntervalAxisSource. But still FixedInterval is not that bad a name, I think, as it describes the function in the unzoomed state quite well. About the StepCount/GridCount I still favour GridCount. In the TIntervalChartSource the parameter is just 'Count', especially derived in the TDateTimeIntervalChartSource its not so clear 'count' of what. I see it similar for StepCount: What 'steps'? Although its more precise if its for the number of intervals or number of grid bars, but when someone gets in touch with the parameter the first time, its more important to know what he can even influence with this parameter. IntervalCount might be an alternative. Again about the parameters in my algorithm, in the current state, passing the bounds through AParams.FFullExtentMin doesn't work well, as the auto ranging will destroy the concept. If such parameters are used, then they should forward the explicit user settings in extent.XMin and Xmax (Because auto ranging is deemed as zooming). But this would cause trouble for Axis transformations. So I still think that dedicated parameters are more useful, even if its more difficult to understand the usage. Passing the extent on the other hand is also not very intuitive as the range also could be set in the axis parameters... By the way, in the last two demos the manual bounds setting through the editboxes didn't work any more due to the auto ranging issue. ---------------------------------------------------------------------- wp · 2016-08-10 21:53 My problem is that the three axis labelers are very similar, and it will be very confusing for the users to select the one they need. We definitely must find speaking names and write good documentation. I hesitate to integrate the new labelers into the TChartAxis code directly because this would blow up the Object Inspector with properties working only if the corresponding algorithm is active - I always hate that. And when working with the "hot" source there's always a risk of breaking something. Fortunately, TAChart is very modular and the labelers can be provided as separate components, maybe combined into a single component with an algorithm switch. I am thinking of adding another operation mode to "my" algorithms in which labels are not determined by interval count but by interval width (in pixels, like the MaxLength/MinLength). This would make it very similar to the builtin algorithm. Ideally I'd get a better understanding of how the builtin algorithm works, and maybe I can fix it to do what we need, and then I could drop my algorithm altogether. If my algorithm survives I'd try to squeeze in a logarithmic mode as well. Log axes really are a pain now. I am still not happy with the fact that your algorithm requires user input. If data range is between 1 and 9 then you say somewhere above that a DefaultExtentMin/Max of 0 and 10 produce good results with StepCount=5. So, there must be some argument how to modify the extent limits. And then it can be coded as well! As for the GridCount/StepCount/IntervalCount naming: GridCount is confusing me. What is this? There is only one grid, but several grid lines. And the number of grid lines is different from the number of intervals. IntervalCount probably is the best. ---------------------------------------------------------------------- kupferstecher · 2016-08-10 22:51 Quote from: wp on August 10, 2016, 11:53:33 pmMy problem is that the three axis labelers are very similar, and it will be very confusing for the users to select the one they need. We definitely must find speaking names and write good documentation.Thats rigth, would be a pitty if its just not recognised by the users. Quote from: wp on August 10, 2016, 11:53:33 pmI hesitate to integrate the new labelers into the TChartAxis code directly because this would blow up the Object Inspector with properties working only if the corresponding algorithm is active - I always hate that.I agree there should not be too much unused parameters. But your algorithm fits to the default parameters I'd say (Count, MinWidth...). Quote from: wp on August 10, 2016, 11:53:33 pmI am still not happy with the fact that your algorithm requires user input. If data range is between 1 and 9 then you say somewhere above that a DefaultExtentMin/Max of 0 and 10 produce good results with StepCount=5. So, there must be some argument how to modify the extent limits. And then it can be coded as well!Its the basic idea of the algorithm that it adapts to the the user chosen bounds. So they have to be defined. DefaultExtent 0 to 10 and IntervalCount=5 forces the grid to 0;2;4... If the range is between 1 and 9 then for the algorithm this is zooming/panning, so labels will be nice, for this specific example at the same positions starting with 2, but the number of labels are less (2;4..8 instead of 0;2..10). As input the algorithm only has DefaultExtent, IntervalCount and CurrentExtent (from AParams), nothing else. Try it with my first or second demo (only for X-axis) and change the bounds in the editfields in the running demo. You will see you have the full control over the label positions, which you don't have in any automatic setting (or if, then more complicated to realise). You want a grid over radian instead of degree then define DefaultExtentMin:=0, DefaultExtentMax:=6.282 IntervalCount:= 4 then labels will be at 0;Pi/2;Pi;3Pi/2;2Pi. When zooming they always stay but could get interpolated. If the actual grid is different, then it still works, but the labels may not on the edges anymore. Instead of DefaultExtentMin/Max it would also be possible to use two parameters IntervalWidth and AnchorPoint (Normally 0). ---------------------------------------------------------------------- kupferstecher · 2016-08-21 10:38 There was a floating point problem e.g. resulting in 0.049999999999 instead of 0.05 when zooming-in. I fixed that with rounding. Also some of the variables I renamed in the hope that their purpose is more clear now. Attached the latest version and demo. The extend has to be defined by the user using the parameters DefaultExtentMin and DefaultExtentMax. From the functional side I would say its ready now. So there is still the question if it is possible/wished for to merge it into TAChart. By now its not a visual component, but that somehow is related to the question TAChart or not. ---------------------------------------------------------------------- wp · 2016-08-21 14:56 Thank you for your effort. As for integration in TAChart, I'll try to combine your approach with mine to some universal axis labeling chartsource, or improve the built-in labeler if I can spot the point(s) where it goes crazy. In my eyes the built-in approach has all the ingredients needed, and it SHOULD work. One short-coming is that labels do not necessarily start at zero, i.e. label values should always be an integer multiple of the interval distance calculated. This would cure a large fraction of the issues. Your request of providing a fixed interval count could be added as other element of the Intervals.Options set. I'd prefer such a solution ("universal labeler", or improved built-in algorithm) over a standalone chartsource dedicated to a single, rarely used task jamming the component palette. Unfortunately I am busy with other activities at the moment and cannot concentrate on this tricky issue. So, it may take some time until TAChart will have an improved labeling chart source. You'll have your own component for the meantime. ---------------------------------------------------------------------- kupferstecher · 2017-05-02 20:44 Hello WP, today I updated to Lazarus 1.6.4 FPC 3.0.2 SVN-Revision 54278 (official release). The property DateTimeStepFormat (class TDateTimeIntervalChartSource) you added on August last year seems to be gone in the new version. Error message: QuoteError: identifier idents no member "DateTimeStepFormat". Is this by mistake? Or is there an other way to use it? Thank you! Regards ---------------------------------------------------------------------- wp · 2017-05-05 16:06 Please re-read reply #1 (last paragraph) ---------------------------------------------------------------------- kupferstecher · 2017-05-05 19:28 Ah, I see. Thats fine