Forum > TAChart
[Solved] Chart DateTime Axis with Hours only has too many values
Wilko500:
I have been experimenting with TChart for a little while and have managed to figure out most of what I need but how to reduce the number of marks on the bottom axis has me stumped.
The chart is created with all defaults plus the following code. The function GetChartDate ensures that the time portion of a date runs from 00:00:00 to 23:59:59 ie from beginning to end of a single day
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---Chart2.LeftAxis.Range.Min:=0;Chart2.LeftAxis.Range.Max:=4200;Chart2.LeftAxis.Range.UseMax:=True;Chart2.LeftAxis.Range.UseMin:=True;Chart2.LeftAxis.Grid.Color:=clTeal;Chart2.LeftAxis.Grid.Style:=psSolid;Chart2.LeftAxis.Marks.LabelFont.Name:='Tahoma';Chart2.LeftAxis.Marks.LabelFont.Size:=8;Chart2.LeftAxis.Title.LabelFont.Name:='Tahoma';Chart2.LeftAxis.Title.LabelFont.Size:=8;Chart2.LeftAxis.Title.Distance:=8;Chart2.LeftAxis.Title.Caption:='Watt';Chart2.LeftAxis.Title.Visible:=True;Chart2.LeftAxis.ZPosition:=0; Chart2.BottomAxis.Range.Min:=GetChartDate(Now, '00:00:00');Chart2.BottomAxis.Range.Max:=GetChartDate(Now, '23:59:59');Chart2.BottomAxis.Range.UseMin:=True;Chart2.BottomAxis.Range.UseMax:=True;Chart2.BottomAxis.Grid.Color:=clTeal;Chart2.BottomAxis.Grid.Style:=psSolid;Chart2.BottomAxis.Marks.LabelFont.Name:='Tahoma';Chart2.BottomAxis.Marks.LabelFont.Size:=8;Chart2.BottomAxis.Title.LabelFont.Name:='Tahoma';Chart2.BottomAxis.Title.LabelFont.Size:=8;Chart2.BottomAxis.Title.Caption:='Time on ' + FormatDateTime('dd mmm yyyy', Now);Chart2.BottomAxis.Title.Visible:=True;Chart2.BottomAxis.ZPosition:=1;Chart2.BottomAxis.Marks.Source:=DateTimeIntervalChartSource1;Chart2.BottomAxis.Marks.Style:=smslabel; Chart2.Margins.Left:=0;Chart2.Margins.Right:=0;Chart2.Margins.Top:=0;Chart2.Margins.Bottom:=1; Chart2.BackColor:=ClBlack;Power.SeriesColor:=clYellow;Power.ZPosition:=2; DateTimeIntervalChartSource1.DateTimeFormat:='h'; //'hh:nn' + LineEnding + 'dd-mm'; I would like the hours axis to be 0 4 8 12 etc instead of 0 2 4 6. I have experimented with changing interval values and options but can't get anything to change to bottom axis. I feel that I'm missing something obvious?
wp:
Yes, it's difficult to find the correct settings if you have special requirements in mind. In such a case, and in particular, if you do not want to zoom into the data, it is easier to remove the DateTimeChartSource and replace it by a TListChartSource populated such that it only contains the desired data points and labels:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TForm1.PopulateTimeLabels(ARange: TChartRange);const EPS = 0.001 / (24 * 60 * 60); // 1 ms tolerancevar dt: TDateTime;begin ListChartSource1.BeginUpdate; try ListChartSource1.Clear; // Iterate over the range dt := ARange.Min; while dt <= ARange.Max do begin if SameValue(dt, ARange.Max, EPS) then ListChartSource1.Add(dt, 0, '24') // Without this the last time label would be '0' which looks strange to me under these circumstances else ListChartSource1.Add(dt, 0, FormatDateTime('h', dt)); dt := dt + 4/24; // Advance dt in 4-hour steps end; finally ListChartSource1.EndUpdate; end;end;
Call this after setting the axis range:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} --- [...] Chart2.BottomAxis.Range.Min:=GetChartDate(Now, '00:00:00'); Chart2.BottomAxis.Range.Max:=GetChartDate(Now, '23:59:59'); Chart2.BottomAxis.Range.UseMin:=True; Chart2.BottomAxis.Range.UseMax:=True; PopulateTimeLabels(Chart2.BottomAxis.Range); Chart2.BottomAxis.Grid.Color:=clTeal; Chart2.BottomAxis.Grid.Style:=psSolid; Chart2.BottomAxis.Marks.LabelFont.Name:='Tahoma'; Chart2.BottomAxis.Marks.LabelFont.Size:=8; Chart2.BottomAxis.Title.LabelFont.Name:='Tahoma'; Chart2.BottomAxis.Title.LabelFont.Size:=8; Chart2.BottomAxis.Title.Caption:='Time on ' + FormatDateTime('dd mmm yyyy', Now); Chart2.BottomAxis.Title.Visible:=True; Chart2.BottomAxis.ZPosition:=1; Chart2.BottomAxis.Marks.Source:=ListChartSource1; Chart2.BottomAxis.Marks.Style:=smslabel; [...]
Wilko500:
Thank you. As it happens I do not want to zoom onto the data so that is a promising start. However my data is collected and displayed on the chart in real time. Does that mean this approach will not work?
To clarify, the blank chart is created on program start, cleared and reset at midnight. My program monitors the inverter 24/7, and will provide either zero when it is offline or the power value. The chart is updated by a timer every 1 minute.
I’m away from just now so I can’t try out your code yet but I will do asap.
However the untidy “0” at the end of the axis was another question waiting on the wings and I agree it looks untidy. Secondly I also have other charts in my project that are using historical data sets and those charts have lots of issues with d, m, y labels which are somewhat unpredictable and seem to vary with the number of data points and data range. The historical data varies from 1 day to inverter lifetime (12 years) and for charts up to data range of 1 year I can “manage” the labels by using a calculated interval min/max based on the number of points and the width of the chart. Always though, last label is missing. I think your approach will be very helpful for those
Wilko500:
Ok WP, your code snippet tested and I'm pleased to say that it works. However for the benefit of any others reading this thread I did have trouble with TChartRange and procedure SameValue. After a bit of searching I found the necessary units, TATypes for TChartRange and Math for SameValue.
A further small tweak needs for my code to get the 24 entry on the bottom axis. I had set the range max time element as GetChartDate(Now, '23:59:59') and then the 24 label was missing, blank instead. On reflection this was actually correct because my range max did not include 24, it stopped one second short. I changed it to GetChartDate(IncDay(Now), '00:00:00') and label 24 appeared.
I am now running a test on my live system that will take at least 24 hours. I have a suspicion that when the chart is cleared and reset at midnight the 24 label will vanish. I will post again when I get test results.
Thank you for your help
wp:
--- Quote from: Wilko500 on August 19, 2025, 10:18:37 pm ---After a bit of searching I found the necessary units, TATypes for TChartRange and Math for SameValue.
--- End quote ---
This is what I am doing when I run into this: your code contains a line "Chart2.BottomAxis.Range.Min:=GetChartDate(Now, '00:00:00')" - this is where the Range is accessed in a way accepted by the compiler. I hold the CTRL key down and click on the word "Range" --> the IDE opens unit TAChartAxis in which the property TChartAxis.Range is declared (line "property Range: TChartRange read FRange write SetRange"). It tells me that the property Range is of type TChartRange. Now I do the same with" TChartRange": CTRL+click on it, and the IDE will open the unit in which TChartRange is declared. In this case, this is unit TATypes - and this is what is missing from your uses clause.
As for the function SameValue, you can do the same when you know a unit in which it is called (for example in unit TAMath of TAChart). If you don't know such a unit this method does not work. There is some unit-finding feature in the Cody package which many users love. But I never used it... My standard way then is to go to the browser and type the search word, together with "Lazarus" or "Delphi", in the seach bar. Usually the missing unit can be found in one of the first hits.
Navigation
[0] Message Index
[#] Next page