Recent

Author Topic: [SOLVED] Populating a Chart from StringGrid. AddXY problem  (Read 1314 times)

barsoom

  • New Member
  • *
  • Posts: 10
[SOLVED] Populating a Chart from StringGrid. AddXY problem
« on: October 14, 2019, 04:00:19 pm »
Hi forum,
I have an stringgrid with data, and i would like to plot them in a chart. I don´t know the number of rows and columns on design-time, so I am coding this to add series and to send the data to each serie:
Code: Pascal  [Select]
  1. procedure TForm1.PlotValuesExecute(Sender: TObject);
  2. var
  3.   Count1: Integer;
  4.   Count2: Long;
  5.   j: Long;
  6.   FLine : TLineSeries;
  7. begin
  8.   with StringGrid1 do
  9.   j := Rowcount;
  10.   begin
  11.     for Count1 := 1 to ColCount-1 do
  12.     begin
  13.       FLine := TLineSeries.Create(Chart1);
  14.       Chart1.AddSeries(FLine);
  15.       Chart1.Series.Items[Count1].Name:= StringGrid1.Cells[i,0];
  16.       for Count2 := 1 to RowCount-1 do
  17.         begin
  18.            Chart1.Series[Count1].AddXY(strtofloat(Cells[0,Count2]),strtofloat(Cells[1,Count2]), '' , clBlue );
  19.         end;
  20.     end;
  21.   end;
  22. end;
I know the code is still not good, but the problem is in AddXY... i saw many examples on the lazarus wiki and in this forum, but this is the error i have when i try to compile:
Quote
Error: identifier idents no member "AddXY"

May be you could help me with this issue. Thanks!
« Last Edit: October 16, 2019, 04:32:24 pm by barsoom »

wp

  • Hero Member
  • *****
  • Posts: 6334
Re: Populating a Chart from StringGrid. AddXY problem
« Reply #1 on: October 14, 2019, 04:57:45 pm »
This is because you call AddXY for Chart1.Series.Items[ i] -- but this is a TBasicChartSeries which has only the most fundamental properties and methods. The AddXY method is introduced in the class TChartSeries from which TLineSeries is derived. (See the overview of the TAChart series classes in the wiki documentation: https://wiki.lazarus.freepascal.org/TAChart_documentation#Series). Since you created Chart1.Series.Items[ i] as TLineSeries you must type-cast it to TLineSeries in order to have the TLineSeries properties available:

Code: Pascal  [Select]
  1.   (Chart1.Series[index] as TLineSeries).AddXY(some_x, some_y, some_label, some_color);

Or, since you stored the pointer to the created series in variable FLine which is declared as TLineSeries you can call AddXY immediately, without type-cast:

Code: Pascal  [Select]
  1.   FLine.AddXY(some_x, some_y, some_label, some_color);

A few notes on issues in your code:
(1) Assuming that the chart is empty when you call TForm1.PlotvaluesExecute the first created series gets the index 0 in the series list of the chart. But you call the AddXY for the series with Index Count1 which starts with the value 1 in the for loop. Therefore, your code will crash here because Chart1.Series[1] does not yet exist.

(2) You assign the caption of column i to the "Name" property of the series. This works, but probably is not what you want -- you probably want to define the text with which the series is identified in the legend. The corresponding property is called "Title".

(3) Since all data points get the same color, clBue, there is no need that you specifiy the color in the 4th parameter of the AddXY call. It is simpler to set the "SeriesColor" property globally for all data points of this series.

(4) After the "with StringGrid1 do" you begin a block within which you access StringGrid1 properties. But you insert another line between the "with" line and the following "begin" -- this has the effect that the "with" applies only to this line -- all what is inside the begin/end does not belong to the StringGrid any more!. Consequently, ColCount, RowCount and other identifiers are no longer understood. To avod this put the "j := RowCount" after the "begin" - but I don't see that it is used anyway, so better skip it altogether.

So, in total this is the code that I would use (not tested, some typos can be around here and there):

Code: Pascal  [Select]
  1. procedure TForm1.PlotValuesExecute(Sender: TObject);
  2. var
  3.   Count1: Integer;
  4.   Count2: Integer;  // Long <-- In Pascal there is no "Long" type, use "LongInt" instead; usually "Integer" is enough.
  5. //  j: Long;  <---- not needed.
  6.   FLine : TLineSeries;
  7. begin
  8.   with StringGrid1 do
  9.   begin
  10. //    j := RowCount;  // This is not used anywhere    
  11.     for Count1 := 1 to ColCount-1 do
  12.     begin
  13.       FLine := TLineSeries.Create(Chart1);
  14.       Chart1.AddSeries(FLine);
  15.       FLine.Title := StringGrid1.Cells[i,0];
  16.       FLine.SeriesColor := clBlue;
  17.       for Count2 := 1 to RowCount-1 do
  18.       begin
  19.         FLine.AddXY(strtofloat(Cells[0,Count2]), strtofloat(Cells[1,Count2]));
  20.       end;
  21.     end;
  22.   end;
  23. end;
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

barsoom

  • New Member
  • *
  • Posts: 10
Re: Populating a Chart from StringGrid. AddXY problem
« Reply #2 on: October 14, 2019, 06:14:15 pm »
Thanks @wp for your help. yes, my code was still so dirty.
I will try your solution!
Thanks you!

barsoom

  • New Member
  • *
  • Posts: 10
Re: Populating a Chart from StringGrid. AddXY problem
« Reply #3 on: October 16, 2019, 12:56:50 pm »
Thanks so much! It works good!
I also found other topic that shows exactly what i try to do:
https://forum.lazarus.freepascal.org/index.php/topic,25520.msg155099.html#msg155099

However, i have to problems.
1) I am not able to avoid null data... in my case, the first column of the stringgrid contains date/time values, and the others the Y values. Not all the Y cells have values, and when i plot the data by the next code, the chart show a continuous line, not disconnected on the dates where there is not Y values in the stringgrid.
Code: Pascal  [Select]
  1. procedure TForm1.PlotValuesExecute(Sender: TObject);
  2. var
  3.   colnumber: Integer;
  4.   rownumber: integer;
  5.   ls: Tlineseries;
  6.   x: TDateTime;
  7.   y: double;
  8.   i, n: integer;
  9.   clr: TColor;
  10. begin
  11.   Chart1.ClearSeries;
  12.   colnumber := StringGrid1.ColCount;
  13.   rownumber := StringGrid1.RowCount;
  14.   for i := 1 to colnumber-1 do
  15.     begin
  16.       ls := Tlineseries.Create(Chart1);
  17.       chart1.addSeries(ls);
  18.       ls.Legend.Format := StringGrid1.Cells[i, 0];
  19.       //ls.SeriesColor:=ChartStyles1.Styles.Style[i];
  20.       clr := RGBToColor(Random(256), Random(256), Random(256));
  21.       for n := 1 to Rownumber-1 do
  22.         begin
  23.           if TryStrToFloat(StringGrid1.Cells[i, n], y) then
  24.             begin
  25.               x := StrToDateTime(StringGrid1.Cells[0, n]);
  26.               ls.AddXY(x, y, '', clr);
  27.             end;
  28.         end;
  29.     end;
  30. end;  


2) the second problem is how to assign a different color to each line serie. The code i used here is not working, and all of the series are showed in black, in spite of the use of "clr" parameter in the AddXY function.


Thanks!
« Last Edit: October 16, 2019, 01:04:20 pm by barsoom »

wp

  • Hero Member
  • *****
  • Posts: 6334
Re: Populating a Chart from StringGrid. AddXY problem
« Reply #4 on: October 16, 2019, 02:54:06 pm »
1) I am not able to avoid null data... in my case, the first column of the stringgrid contains date/time values, and the others the Y values. Not all the Y cells have values, and when i plot the data by the next code, the chart show a continuous line, not disconnected on the dates where there is not Y values in the stringgrid.
This is because you nowhere use a NaN for y. Try this instead
Code: Pascal  [Select]
  1. procedure TForm1.PlotValuesExecute(Sender: TObject);
  2. var
  3.   colnumber: Integer;
  4.   rownumber: integer;
  5.   ls: Tlineseries;
  6.   x: TDateTime;
  7.   y: double;
  8.   i, n: integer;
  9.   clr: TColor;
  10. begin
  11.   Chart1.ClearSeries;
  12.   colnumber := StringGrid1.ColCount;
  13.   rownumber := StringGrid1.RowCount;
  14.   for i := 1 to colnumber-1 do
  15.     begin
  16.       ls := Tlineseries.Create(Chart1);
  17.       chart1.addSeries(ls);
  18.       // ls.Legend.Format := StringGrid1.Cells[i, 0];  <--- what is this supposed to do?
  19.       ls.SeriesColor:= RGBToColor(Random(256), Random(256), Random(256));
  20.       for n := 1 to Rownumber-1 do
  21.         begin
  22.           if TryStrToFloat(StringGrid1.Cells[0, n], x) then
  23.           begin
  24.             if not TryStrToFloat(StringGrid1.Cells[i, n], y) then
  25.               y := NaN;
  26.             ls.AddXY(x, y) ;
  27.           end;
  28.         end;
  29.     end;
  30. end;  
(I am not sure if the begin/end pairs are matching in this modified code, I am not used to your indentation style).

2) the second problem is how to assign a different color to each line serie. The code i used here is not working, and all of the series are showed in black, in spite of the use of "clr" parameter in the AddXY function.
Normally you define the color of a lineseries by setting the "SeriesColor" property or the "LinePen.Color". The individual datapoint colors set in the AddXY call are meant for the data point symbols usually (In Laz trunk you can also colorize the line segment before/after a data point). The needed change is already contained in above code.
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

barsoom

  • New Member
  • *
  • Posts: 10
Re: Populating a Chart from StringGrid. AddXY problem
« Reply #5 on: October 16, 2019, 04:23:27 pm »
Thanks so much @wp, i had to made some modifications, and now it works nicely! Thanks!

Here is the code, in case it could be useful for others:
Code: Pascal  [Select]
  1. procedure TForm1.PlotValuesExecute(Sender: TObject);
  2. var
  3.   colnumber: Integer;
  4.   rownumber: integer;
  5.   ls: Tlineseries;
  6.   x: TDateTime;
  7.   y: double;
  8.   i, n: integer;
  9. begin
  10.   Chart1.ClearSeries;
  11.   colnumber := StringGrid1.ColCount;
  12.   rownumber := StringGrid1.RowCount;
  13.   for i := 1 to colnumber-1 do
  14.     begin
  15.       ls := Tlineseries.Create(Chart1);
  16.       chart1.addSeries(ls);
  17.       ls.Title := StringGrid1.Cells[i, 0];
  18.       ls.SeriesColor:= RGBToColor(Random(256), Random(256), Random(256));
  19.       for n := 1 to Rownumber-1 do
  20.         begin
  21.           if TryStrToDateTime(StringGrid1.Cells[0, n], x) then
  22.           begin
  23.             if not TryStrToFloat(StringGrid1.Cells[i, n], y) then
  24.               y := NaN;
  25.           end;
  26.           ls.AddXY(x, y) ;
  27.         end;
  28.     end;
  29. end;
An important issue. It is necessary to add "Math" to Uses section, otherwhise "NaN" returns an error.

Quote
ls.Legend.Format := StringGrid1.Cells[i, 0];  <--- what is this supposed to do?
In the mean time i readed other post from you where you explained other way to do it: to assign the series name based on the first row of the column. Now i changed to:
Code: Pascal  [Select]
  1. ls.Title := StringGrid1.Cells[i, 0];

Thanks a lot!


barsoom

  • New Member
  • *
  • Posts: 10
Re: [SOLVED] Populating a Chart from StringGrid. AddXY problem
« Reply #6 on: November 04, 2019, 08:06:52 pm »
Although it is working, now i have a problem trying to set the pointer style to circles on runtime.
It should be as simple as to add this code
Code: Pascal  [Select]
  1. ls.Pointer.Style. := psCircle;
between lines 18 and 19 in the previous post, but it returns and error on compilation:
Quote
unit1.pas(164,28) Error: Identifier not found "psCircle"

What am i doing wrong?
Thanks

winni

  • Sr. Member
  • ****
  • Posts: 436
Re: [SOLVED] Populating a Chart from StringGrid. AddXY problem
« Reply #7 on: November 04, 2019, 10:56:35 pm »
Don't forget the types:

Code: Pascal  [Select]
  1.  uses  ......., tatypes;

Winni


wp

  • Hero Member
  • *****
  • Posts: 6334
Re: [SOLVED] Populating a Chart from StringGrid. AddXY problem
« Reply #8 on: November 04, 2019, 11:19:01 pm »
When you learn how to navigate in source code you can resolve such issues easily. Click on the "Style" of "ls.Pointer.Style" and hold the CTRL key down. This opens the unit in which the Pointer.Style is declared; in this case you'll jump to the line
Code: Pascal  [Select]
  1.   property Style: TSeriesPointerStyle read FStyle write SetStyle default psRectangle;
Now repeat this with the word "TSeriesPointerStyle" and the IDE will jump to
Code: Pascal  [Select]
  1.   TSeriesPointerStyle = (
  2.     psNone, psRectangle, psCircle, psCross, psDiagCross, psStar,
  3.     psLowBracket, psHighBracket, psLeftBracket, psRightBracket, psDiamond,
  4.     psTriangle, psLeftTriangle, psRightTriangle, psVertBar, psHorBar, psPoint,
  5.     psDownTriangle, psHexagon, psFullStar);
Here we are... In the tab of the active editor page you'll see "TATypes" -- this is the unit in which the SeriesPointerStyles are declared.
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

barsoom

  • New Member
  • *
  • Posts: 10
Re: [SOLVED] Populating a Chart from StringGrid. AddXY problem
« Reply #9 on: November 05, 2019, 10:12:36 am »
Thanks @WP,
I didn´t say, but i already added "TAStyles" to the uses section, but it returned the same error. Why? Who knows? Anyway, I tried today in a different computer and it worked!
this was my problem, that i was sure it was not caused because the absence of "TAStyles" at the head of the file... because this was one of the first things i did...
i decided to uninstall lazarus and install it again in the computer where i had the problems to see what is going on.
Thanks so much anyway for the tips!!

wp

  • Hero Member
  • *****
  • Posts: 6334
Re: [SOLVED] Populating a Chart from StringGrid. AddXY problem
« Reply #10 on: November 05, 2019, 07:38:06 pm »
I don't understand. TAStyles? This was not mentioned anywhere in this thread... Do you confuse it with TATypes which winni and I were mentioning in the previous commits?
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

barsoom

  • New Member
  • *
  • Posts: 10
Re: [SOLVED] Populating a Chart from StringGrid. AddXY problem
« Reply #11 on: November 07, 2019, 04:46:37 pm »
You are right, sorry "TATypes". My bad.
Thanks a lot.

barsoom

  • New Member
  • *
  • Posts: 10
Re: [SOLVED] Populating a Chart from StringGrid. AddXY problem
« Reply #12 on: November 13, 2019, 05:28:19 pm »
I observed my program, that is really simple (to fill a stringgrid from a csv file and plot a chart based on the data), is quite slow on showing, zooming and panning the chart, but the file is quite small (1.5 Mb). May be it is better to fill a chartsource from the csv file, and to connect the chart with the chartsource. What do you think?
In that case, how could i populate a list chart course from the csv considering multiple Y data for the same X in this way:
Quote
Date&Time;Y1; Y2;Y3,(...)

Edited:
Moreover, to be able to apply other tools such as derivative plotting, or smooth, or... it is necessary to call a chartsource (listchartsource, calculatedchartsource,...). Then, i think it is better to store the data into a listchartsource better than to plot directly from the stringgrid. Am i right?

Thanks!
« Last Edit: November 13, 2019, 06:01:37 pm by barsoom »

wp

  • Hero Member
  • *****
  • Posts: 6334
Re: [SOLVED] Populating a Chart from StringGrid. AddXY problem
« Reply #13 on: November 13, 2019, 08:00:43 pm »
Yes, using an external ChartSource has a lot of advantages - you mentioned some of them. I recommend calling the series' Add* methods only for simple and "quick-and-dirty" programs.

You mention a csv file as your data source. What do you do with the data? Just plotting? Then a TListChartSource is fine. But sometimes the internal chart data structure is overkill - you probably do not need the individual data point colors, or the text label attached. Or you want to avoid double storage in some dedicated datastructure and in the chart source. I often use a TUserDefinedChartSource which allows me to store the data in any data structure that fits to your intentions, such as an array, a list or whatever (see tutorial https://wiki.lazarus.freepascal.org/TAChart_Tutorial:_Userdefined_ChartSource).

When you stick to a ListSource you should call the source method BeginUpdate before and EndUpdate after adding data points. This avoids a lot of internal notifications and paint operations. You'll notice a significant speed boost this way. Another options might be to disconnect the ChartSource from the series before (Series.Source := nil), and reconnecting it after adding the data (Series.Source := ListChartSource).

Of course speed depends also on how you read the csv file.

I cannot imagine a reason why zooming and panning become slow. You should try to assemble a demo to show me the issue.

In order to use several y values for the same x value you simply set the YCount of the chart source accordingly. Note however, that determining colors and legend texts of the "sub-series" is a bit more complicated. You must use ChartStyles and add a style for each "sub-series". Working with ChartStyles is explained in this tutorial: https://wiki.lazarus.freepascal.org/TAChart_Tutorial:_Stacked_BarSeries.
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

barsoom

  • New Member
  • *
  • Posts: 10
Re: [SOLVED] Populating a Chart from StringGrid. AddXY problem
« Reply #14 on: November 15, 2019, 01:25:03 pm »
Thanks.
Yes, i use a csv file. At present time i just only want to plot the data, and explore them (zoom, panning)... To do it with excell is a pain... So, in this way i hope to be able to see de data in detail, measure distances, and to export plots.

I attached a simple example of a program and a data file. There is only o button, so, no doubts about how it works. The program load the data into a tringgrid (tab3), and then plot the data in the chart (tab 2).
In this example there are not the charttools to zoom and panning using the mouse wheell and the control and alt button on keyboard. After to remove all the tools, the simple program is still so slow when zooming. this is why i asked about to load the data on a ListChartSource better than on a stringgrid... may be it increase the speed.

The example file is 1.4Mb, so, it is available here:
https://www.dropbox.com/s/81ysd1x5x1a21yi/example_data.csv?dl=0

Thanks!