Recent

Author Topic: Labels not always showing  (Read 3705 times)

kapibara

  • Hero Member
  • *****
  • Posts: 610
Labels not always showing
« on: February 07, 2017, 09:27:25 pm »
The X-axis values don't always show for some reason. Plus it shows only the year, not the month although 'mmm yyyy' is specified. What am I missing?

Here's how records are added:

(Sample attached. It plots data from an included csv file)

Code: Pascal  [Select][+][-]
  1.     //Always give the first point a year and month
  2.     if aRow = 0 then
  3.     //If current year larger than old year, set label
  4.     else if YearOf(dt) > OldYear then
  5.       lcsSeries.AddXYList(aRow, ylist, FormatDateTime('mmm yyyy', dt, aFormatSettings))
  6.     //All other should have no labels
  7.     else
  8.       lcsSeries.AddXYList(aRow, ylist, '');
  9.  
« Last Edit: February 07, 2017, 09:30:02 pm by kapibara »
Lazarus trunk / fpc 3.2.2 / Kubuntu 22.04 - 64 bit

wp

  • Hero Member
  • *****
  • Posts: 11916
Re: Labels not always showing
« Reply #1 on: February 07, 2017, 11:19:36 pm »
I see several issues:
  • You want special labels, and you add them to the data points skipping those which should remain unlabeled. This is not a good strategy for two reasons:

    (1) You must be sure that data points sit exactly at the x values which you want to label (suppose you want a label at "0", but none of your data points is there?)

    (2) TAChart follows its own strategy for selecting labels, it is very difficult to control this. In your case it always picks the labels which are empty. Therefore, you only see the first label.

    How to avoid this? Use a separate chartsource which just contains x values and the mark texts for the positions to be labeled.
  • After these changes I still did not see the intended labels. This is because you did not initialize the OldYear variable outside the loop. It must have had a very large random value because the condition "YearOf(dt) > OldYear" never came true. Instead of outside the loop I put initialization of OldYear into the "if row = 0" condition which makes sure that the very first label is shown but no other one of the same year.
  • Not a bug at the moment, but it will be one if you call PopulateSeries several times: You must clear the chartsources before adding new data, otherwise data will be added to the ones previously loaded.
  • My country settings define the comma as decimal separator. You do prepare special FormatSettings to take care of this (great!), but you must add these FormatSettings to StrToFloat as well to make sure that the decimal point in the csv strings is detected correctly
  • Finally I wondered why I never saw the month in your labels in a WriteLn of the labels although you had specified the format string to include month. This is because you initialized only a few fields of the special FormatSettings, the other fields, i.e. the month names, are undefined. Assign the DefaultFormatSettings to your FormatSettings before applying your own modifications.
Here is the code which works correctly for me. As I said, there's an additional ListChartSource (called YearLabels) on the form.
Code: Pascal  [Select][+][-]
  1. { TfrmMain }
  2.  
  3. procedure TfrmMain.FormCreate(Sender: TObject);
  4. begin
  5.   Chart.BottomAxis.Marks.Source:= YearLabels;
  6.   Chart.LeftAxis.Alignment:=calRight;
  7.   CSV:= TCSVDocument.Create;
  8.   if FileExists('msft.csv') then
  9.     CSV.LoadFromFile('msft.csv');
  10.  
  11.   PopulateSeries;
  12. end;
  13.  
  14. procedure TfrmMain.PopulateSeries;
  15. var
  16.   aRow: integer;
  17.   dt: TDateTime;
  18.   ylist: array [0..3] of double;
  19.   OldYear: Word;
  20. begin
  21.   YearLabels.Clear;
  22.   lcsSeries.Clear;
  23.  
  24.   for aRow:= 0 to CSV.RowCount -1 do
  25.   begin
  26.     dt:= StrToDateTime(CSV.Cells[csvDATE,aRow], aFormatSettings);
  27.     ylist[yOPEN]:= StrToFloat(CSV.Cells[csvOPEN,aRow], aFormatSettings);
  28.     ylist[yHIGH]:= StrToFloat(CSV.Cells[csvHIGH, aRow], aFormatSettings);
  29.     ylist[yLOW]:= StrToFloat(CSV.Cells[csvLOW, aRow], aFormatSettings);
  30.     ylist[yCLOSE]:= StrToFloat(CSV.Cells[csvCLOSE, aRow], aFormatSettings);
  31.  
  32.     lcsSeries.AddXYList(aRow, ylist);
  33.  
  34.     //Always give the first point a year and month
  35.     if aRow = 0 then begin
  36.       YearLabels.Add(aRow, aRow, FormatDateTime('mm/yyyy', dt, aFormatSettings));
  37.       OldYear := YearOf(dt);
  38.     end
  39.     //If current year larger than old year, set label
  40.     else if YearOf(dt) > OldYear then begin
  41.       YearLabels.Add(aRow, aRow, FormatDateTime('yyyy', dt, aFormatSettings));
  42.       OldYear := YearOf(dt);
  43.     end;
  44.     //All others should have no labels
  45.   end;
  46. end;
  47.  
  48. initialization
  49.   aFormatSettings := DefaultFormatSettings;
  50.   aFormatSettings.DecimalSeparator:='.';
  51.   aFormatSettings.DateSeparator:= '-';
  52.   aFormatSettings.TimeSeparator:= ':';
  53.   aFormatSettings.LongDateFormat:= 'yyyy-MMM-dd';
  54.   aFormatSettings.ShortDateFormat:= 'yyyy-MM-dd';
  55.   aFormatSettings.ShortTimeFormat:= 'hh:nn';
  56.   aFormatSettings.LongTimeFormat:= 'hh:nn:ss';


Maybe you are interested in a similar program that I once wrote for displaying historic financial share values (https://sourceforge.net/p/wp-laz/code/HEAD/tree/FinancialChart/trunk/). It downloads the share prices from free internet servers.
« Last Edit: February 07, 2017, 11:33:41 pm by wp »

kapibara

  • Hero Member
  • *****
  • Posts: 610
Re: Labels not always showing
« Reply #2 on: February 08, 2017, 11:36:16 am »
A separate chartsource for BottomAxis did the trick. I went ahead and added one more chartsource called DayLabels. It is switched to by code in the OnExtentChanged event if PointsInRange are less than 100. Zoom out and it switches back to using the YearLabels chartsource. (Ctrl + Mousewheel)

At some zoomlevels, the labels get crowded. I would like them to never be closer to eachother than 5 millimeter or so, but didn't manage yet. Also the Y scale starts at zero but there is no data until 15 so some of  the chart area is wasted. How to change that?

(Forgot to clear the DayLabels before uploading the sample  ::))
Lazarus trunk / fpc 3.2.2 / Kubuntu 22.04 - 64 bit

wp

  • Hero Member
  • *****
  • Posts: 11916
Re: Labels not always showing
« Reply #3 on: February 08, 2017, 12:53:04 pm »
The decision to use an external Chartsource for the axis labels means that you have full control over label positioning but it means also that you must decide on every details, there's no more automatism (except for Marks.OverlapPolicy = opNeighbar - but I don't like it because it results in irregular intervals). To avoid overlapping to the degree of several millimeters you must calculate the length of the labels and consider this when positioning labels. There were some discussions here with examples, please use the forum search, maybe seek for "DateTimeInterval" and my alias.

Alternatively you could make a rough classification depending on the zoomed extent:
- If the extent is shorter than 10 days put labels at every day
- shorter than 1 month: --> every week start day (Sunday or Monday, depending on country)
- shorter than 1 year: --> every 1st of a month
etc.

Or: Why don't you use a DateTimeIntervalChartSource for the zoomed case? In my eyes it works nicely for this purpose. Using its Params.MinLength/MaxLength you can control overlapping very efficiently without turning OverlapPolicy on. Its only disadvantage is that it does not necessarily put labels at the start of a date/time entity, i.e. it may decide to label the 2nd of a month while it could label the 1st as well.

Your DayLabels are very long (format 'dd/mm yyyy'). How about adding a linebreak? 'dd/mm' + LineEnding +'yyyy'.

The y axis normally starts automatically at the data minimum. Since it does not and since you don't override any of the related settings I had a look at the data, and in fact there are a few rows for which the open, high and low values are missing and have been replaced by a zero value (rows 230, 250, 255 and some more). For these days only the close tick is painted (zoom around 25-12 2008). You may want to set the open, high, low values equal to the close value in these cases instead. This should avoid the y axis to start at zero.

 

TinyPortal © 2005-2018