Recent

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

barsoom

  • New member
  • *
  • Posts: 7
[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: 6310
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: 7
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: 7
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: 6310
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: 7
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: 7
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: 417
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: 6310
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: 7
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: 6310
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: 7
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.