Lazarus

Programming => General => Topic started by: mpknap on March 06, 2019, 06:27:24 am

Title: Multi Lineseries
Post by: mpknap on March 06, 2019, 06:27:24 am
How to create and display 100 LineSeries on Chart1 with Tchart component? (Later even 3000 line series I would need.)
  
Code: Pascal  [Select][+][-]
  1.  
  2. var
  3. Series_ar: array of TLineSeries;
  4. .....
  5.  for j :=1 to 100 do   begin
  6.      
  7.    Series_ar[i].AddXy( random(1000),i);
  8. ......
  9.  
  10.  
did not work.
Title: Re: Multi Lineseries
Post by: wp on March 06, 2019, 06:52:39 am
Did you create the series and add it to the chart?
Code: Pascal  [Select][+][-]
  1. uses
  2.   LCLIntf;
  3.  
  4. procedure TForm1.FormCreate(Sender: TObject);
  5. const
  6.   NUM_SERIES = 10;
  7.   NUM_POINTS = 10;
  8. var
  9.   i,j: Integer;
  10.   ser: TLineSeries;
  11. begin
  12.   for j := 1 to NUM_SERIES do begin
  13.     ser := TLineSeries.Create(Chart1);
  14.     ser.SeriesColor:= rgb(Random(256), Random(256), Random(256));
  15.     for i := 1 to NUM_POINTS do
  16.       ser.AddXY(Random, Random);
  17.     Chart1.AddSeries(ser);
  18.   end;
  19. end;  
Title: Re: Multi Lineseries
Post by: mpknap on March 06, 2019, 07:11:05 am
Thanks. I would like, however, Array 1..X]. To call for a specific series .. Is it possible?
Title: Re: Multi Lineseries
Post by: wp on March 06, 2019, 07:25:06 am
I would like, however, Array 1..X]. To call for a specific series ..
The chart has a built-in list of series. Begins with 0, though. (But I'd strongly advice against using an array starting at 1 anyway - it is guaranteed to make you trouble because all other arrays and lists used by the LCL begin with 0). To access the 7th series added (index 6) you simply call:
Code: Pascal  [Select][+][-]
  1. var
  2.   ser: TLineSeries;
  3. begin
  4.   ser := TLineSeries(Chart1.Series[6]);
  5.   ...
Of course you can use your own array in addition. Saves you the type-cast to TLineSeries:
Code: Pascal  [Select][+][-]
  1. var
  2.   Series_ar: array[1..NUM_SERIES] of TLineSeries;
  3. ...
  4.   for j := Low(Series_ar) to High(Series_ar) do begin
  5.     Series_ar[i] := TLineSeries.Create(Chart1);
  6.     Series_ar[i].SeriesColor:= rgb(Random(256), Random(256), Random(256));
  7.     for i := 1 to NUM_POINTS do
  8.       Series_ar[i].AddXY(Random, Random);
  9.     Chart1.AddSeries(Series_ar[i]);
  10.   end;
Title: Re: Multi Lineseries
Post by: mpknap on March 06, 2019, 04:44:30 pm
WP, Something is not quite right. An error pops up (attachment).
The code looks like this:
Code: Pascal  [Select][+][-]
  1. procedure TForm4.Button5Click(Sender: TObject);
  2. const
  3.   NUM_SERIES = 2000;
  4.   NUM_POINTS = 10;
  5. var
  6.   i, j: integer;
  7.   ser: TLineSeries;
  8.   Series_ar: array[1..2000] of TLineSeries;
  9.  
  10. begin
  11.  { for j := 1 to NUM_SERIES do begin
  12.     ser := TLineSeries.Create(Chart1);
  13.     ser.SeriesColor:= rgb(Random(256), Random(256), Random(256));
  14.     for i := 1 to NUM_POINTS do
  15.       ser.AddXY(Random, Random);
  16.     Chart1.AddSeries(ser);
  17.   end;
  18.  }
  19.   for j := Low(Series_ar) to 20 do
  20.   begin
  21.     Series_ar[j] := TLineSeries.Create(Chart2);
  22.     Series_ar[j].SeriesColor := rgb(Random(256), Random(256), Random(256));
  23.     for i := 1 to NUM_POINTS do
  24.       Series_ar[i].AddXY(Random, Random);
  25.     Chart2.AddSeries(Series_ar[i]);
  26.  
  27.   end;
  28. end;        
Title: Re: Multi Lineseries
Post by: wp on March 06, 2019, 04:52:52 pm
Carefully look at your code. Understand what each index, i and j, is good for. Why does the series have an index j in the outer loop and an index i in the inner loop? Is this correct?
Title: Re: Multi Lineseries
Post by: mpknap on March 06, 2019, 05:51:11 pm
Of course!!! It should be everywhere "j". Thanks again, now I will test a few days :)
Code: Pascal  [Select][+][-]
  1. for j := Low(Series_ar) to 20 do
  2.   begin
  3.     Series_ar[j] := TLineSeries.Create(Chart2);
  4.     Series_ar[j].SeriesColor := rgb(Random(256), Random(256), Random(256));
  5.     for i := 1 to NUM_POINTS do
  6.       Series_ar[j].AddXY(Random, Random);
  7.     Chart2.AddSeries(Series_ar[j]);
  8.  
  9.   end;
Title: Re: Multi Lineseries
Post by: wp on March 06, 2019, 05:55:00 pm
It should be everywhere "j".
Looked at my own code in reply #3, and found the "i" there as well. Excuse me for the typo.
Title: Re: Multi Lineseries
Post by: mpknap on March 07, 2019, 08:13:10 pm
One more problem and question (to WP).
Why I do not see String after adding in the

Code: Pascal  [Select][+][-]
  1. ...
  2. Series_ar[j].AddXY(Random, Random,' TIME ');
  3. ...


?


I changed to smsLabel, smsCustom ...

Maybe I misunderstood but I wanted the text "Time" to be in Bottom Chart Axis, under Grid ...
Title: Re: Multi Lineseries
Post by: wp on March 07, 2019, 11:33:09 pm
When you add a text to the "AddXY" call this text will be used as a series data point label. To show the labels you set the series' Marks.Style to smsLabel. If you want to use the labels as axis labels you must assign the internal ListSource of the series (to which the AddXY data go) to the Marks.Source of the BottomAxis (and set Marks.Style to smsLabel again).

Your note "I wanted the text "Time" to be in Bottom Chart Axis, under Grid" can also be understood as if you want to use the 'TIME" as axis title. In this case, AddXY is not the correct place. Go to the chart's BottomAxis.Title and assign the string to the subproperty "Caption". Don't forget to show the title by setting its "Visible" to true.
Title: Re: Multi Lineseries
Post by: mpknap on March 08, 2019, 05:54:32 am
I change, I think, I'm looking on the net. Somewhere, I made a mistake and I do not know where.
It's hard to explain but maybe it will be easier on the pictures. The graph is 24H time. As in the attachment in the picture. I want the time to be properly scaled after taking the zoom.

Maybe I'll add that this one variable (bottom) is unixtime. So maybe we'll change it to date and time ??
Title: Re: Multi Lineseries
Post by: wp on March 08, 2019, 11:13:57 am
Change the x values to be TDateTime and use a TDateTimeIntervalChartSource as Marks.Source of the BottomAxis (set Marks.Style to smsLabel). Increase the Params.MaxLength of the DateTimeIntervalChartSource to avoid overlapping labels. TDateTimeIntervalchartSource is able to create correct date/time labels at all zoom levels, although they are not always "nice", i.e. each new day is not always marked with a 0:00:00 label.
Title: Re: Multi Lineseries
Post by: mpknap on March 11, 2019, 05:23:07 pm
As always, many thanks.
One more question. I inserted TChartListbox. After starting (multiselect) all items are marked. How do you deselect all them, for example with a button?
Title: Re: Multi Lineseries
Post by: wp on March 11, 2019, 05:42:53 pm
You mean: All items are checked and you want to un-check an item? Just click on the checkbox - this will remove the checkmark and hide the associated series. Or by code: set the (public) property Checked[Index] to false; Index is the index of the item in the listbox. Don't forget to set the Chart property of the Listbox to the chart to be processed.
Title: Re: Multi Lineseries
Post by: mpknap on March 11, 2019, 05:51:33 pm
I mean uncheck everyone at once, by buttonClick.


But already done
Code: Pascal  [Select][+][-]
  1. procedure TForm4.Button6Click(Sender: TObject);
  2. var x : integer;
  3. begin
  4.   for x:=0  to   chartlistbox1.SeriesCount-1 do
  5.       chartlistbox1.Checked[x]:=false;
  6. end;
Title: Re: Multi Lineseries
Post by: mpknap on March 13, 2019, 06:02:30 am
The code below is part of a larger program. But it's a chart engine that you can run and check.

As at the beginning of the thread, I asked about the array of chart. This part has been resolved.
Now I have a problem with ChartToolset1DataPointClick. This is probably related to Array. I want to display the value of the clicked point.
Using the code from the tutorial, an error pops up.

Another small question. Is it possible to do the text of LeftAxis.Marks, was displayed only on the existing Series?
LeftAxis is "user_number". At the beginning, I create 15000 Series because there are so many users, but I want to describe only those displayed on Chart1.
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, TAGraph, TAIntervalSources, TASources, Forms,
  9.   Controls, Graphics, Dialogs, StdCtrls, ComCtrls, TATools,
  10.   dateutils, tatypes, TASeries, Types, TACustomSeries;
  11.  
  12. type
  13.  
  14.   { TForm1 }
  15.  
  16.   TForm1 = class(TForm)
  17.     Chart1: TChart;
  18.     ChartToolset1: TChartToolset;
  19.     ChartToolset1DataPointClickTool1: TDataPointClickTool;
  20.     DateTimeIntervalChartSource1: TDateTimeIntervalChartSource;
  21.     Label1: TLabel;
  22.     Label2: TLabel;
  23.     ListChartSource1: TListChartSource;
  24.     StatusBar1: TStatusBar;
  25.     procedure ChartToolset1DataPointClickTool1AfterMouseDown(ATool: TChartTool;
  26.       APoint: TPoint);
  27.     procedure FormCreate(Sender: TObject);
  28.   private
  29.  
  30.   public
  31.  
  32.   end;
  33.  
  34. type
  35.   hit = record
  36.     time: int64;
  37.     lat, long, user_n, user_name: string;
  38.   end;
  39.  
  40. var
  41.   Form1: TForm1;
  42.   Series_ar: array[1..15000] of TLineSeries;
  43.  
  44.  
  45. implementation
  46.  
  47. {$R *.lfm}
  48.  
  49. { TForm1 }
  50.  
  51. procedure TForm1.FormCreate(Sender: TObject);
  52. var
  53.   Detection: array[1..15000] of hit;
  54.   f: textfile;
  55.   i, j: integer;
  56.   cc: int64;
  57.   Read_Line: string;
  58.   dt: tdatetime;
  59. begin
  60.   AssignFile(f, '01.01.2019.txt');
  61.   reset(f);
  62.   i := 1;
  63.   while not EOF(f) do
  64.   begin
  65.     readln(f, cc);
  66.     Detection[i].time := cc;
  67.  
  68.     readln(f, Read_Line);
  69.     Detection[i].lat := Read_Line;
  70.  
  71.     readln(f, Read_Line);
  72.     Detection[i].long := Read_Line;
  73.  
  74.     readln(f, Read_Line);
  75.     Detection[i].user_n := Read_Line;
  76.  
  77.     readln(f, Read_Line);
  78.  
  79.     readln(f, Read_Line);
  80.     Detection[i].user_name := Read_Line;
  81.  
  82.     readln(f, Read_Line);
  83.     Inc(i);
  84.   end;
  85.   closefile(f);
  86.   for j := Low(Series_ar) to High(Series_ar) do
  87.   begin
  88.     Series_ar[j] := TLineSeries.Create(Chart1);
  89.     Series_ar[j].ShowPoints := True;
  90.     Series_ar[j].Pointer.Brush.Color := clRed;
  91.     Series_ar[j].Pointer.Pen.Color := clBlack;
  92.     Series_ar[j].Pointer.Style := psCircle;
  93.   end;
  94.   for j := 1 to High(Series_ar) do
  95.   begin
  96.     if Detection[j].user_n <> '' then
  97.     begin
  98.       DT := unixToDateTime((Detection[j].time) div 1000);
  99.  
  100.       Series_ar[StrToInt(Detection[j].user_n)].AddXy(dt,
  101.         StrToInt(Detection[j].user_n), 'a');
  102.       Series_ar[StrToInt(Detection[j].user_n)].Title :=
  103.         (Detection[j].user_name) + ' (' + (Detection[j].user_n) + ')';
  104.       Chart1.AddSeries(Series_ar[StrToInt(Detection[j].user_n)]);
  105.     end;
  106.  
  107.   end;
  108.  
  109. end;
  110.  
  111. procedure TForm1.ChartToolset1DataPointClickTool1AfterMouseDown(ATool: TChartTool;
  112.   APoint: TPoint);
  113. var
  114.   x, y: double;
  115. begin
  116.   with ATool as TDatapointClickTool do
  117.     if (Series is TLineSeries) then
  118.       with TLineSeries(Series) do
  119.       begin
  120.         x := GetXValue(PointIndex);
  121.         y := GetYValue(PointIndex);
  122.         Statusbar1.SimpleText := Format('%s: x = %f, y = %f', [Title, x, y]);
  123.       end
  124.     else
  125.       Statusbar1.SimpleText := '';
  126. end;
  127.  
  128. end.                  
Title: Re: Multi Lineseries
Post by: wp on March 13, 2019, 07:30:27 pm
Now I have a problem with ChartToolset1DataPointClick. This is probably related to Array. I want to display the value of the clicked point.
Using the code from the tutorial, an error pops up.
Which error? Which code? (Sorry, I wrote so many tutorials, I really don't know by heart which one covers the datapoint click tool). Does the problem occur also with the code that you show in the attachment?

The DataPointClickTool in the attachment works for me, but only in principle. Because using the DataPointCLICKTool implies that you really perform a CLICK, not a key press. When I set the Shift of the tool to ssLeft instead of ssShift then everything works fine. Of course you can combine both ([ssLeft, ssShift]) to trigger the tool by a SHIFT-Click event.

Is it possible to do the text of LeftAxis.Marks, was displayed only on the existing Series?
Regarding the 150000 series I would introduce a stringlist to which each user found during reading is added avoiding duplicates. After reading you can set the length of the series array exactly to the length of the stringlist in order to have a series for each user.

I would also use a dynamic array for the detections because you are wasting memory when the actual data file contains less then 150000 events or run into problems when it contains more events.

And in oder to facilitate reading your code for others I'd recommend that you stick to conventions, i.e. preceed type with a "T", i.e. "THit" instead of "hit".

These ideas are contained in this code segment:
Code: Pascal  [Select][+][-]
  1. type
  2.   TForm1 = class(TForm)
  3.   private
  4.     Series_ar: array of TLineSeries;  
  5.   ...
  6. type
  7.   THit = record
  8.     time: int64;
  9.     lat, long, user_n, user_name: string;
  10.   end;
  11. ...
  12. procedure TForm1.FormCreate(Sender: TObject);
  13. const
  14.   BLOCK_SIZE = 1000;
  15. var
  16.   Detection: array of THit;
  17.   f: textfile;
  18.   i, j: integer;
  19.   cc: int64;
  20.   Read_Line: string;
  21.   dt: tdatetime;
  22.   Users: TStringList;
  23. begin
  24.   Users := TStringList.Create;
  25.   Users.Duplicates := dupIgnore;
  26.   Users.Sorted := true;
  27.  
  28.   AssignFile(f, '01.01.2019.txt');
  29.   reset(f);
  30.   i := 0;
  31.   SetLength(Detection, 0);
  32.   while not EOF(f) do
  33.   begin
  34.     // To avoid excessive copying of data allocate memory for the Detection array only in blocks of 1000 elements.
  35.     if i mod BLOCK_SIZE = 0 then
  36.       SetLength(Detection, Length(Detection) + BLOCK_SIZE);
  37.  
  38.     readln(f, cc);
  39.     Detection[i].time := cc;
  40.  
  41.     readln(f, Read_Line);
  42.     Detection[i].lat := Read_Line;
  43.  
  44.     readln(f, Read_Line);
  45.     Detection[i].long := Read_Line;
  46.  
  47.     readln(f, Read_Line);
  48.     Detection[i].user_n := Read_Line;
  49.  
  50.     readln(f, Read_Line);
  51.  
  52.     readln(f, Read_Line);
  53.     Detection[i].user_name := Read_Line;
  54.  
  55.     Users.Add(Detection[i].user_n);
  56.  
  57.     readln(f, Read_Line);
  58.     Inc(i);
  59.   end;
  60.   closefile(f);
  61.   // Now we know the exact length of the Detection array and can adjust its length accordingly.
  62.   SetLength(Detection, i);
  63.  
  64.   // The stringlist "UserNames" contains the list of all user names. Each user
  65.   // name should have its own line series.
  66.   SetLength(Series_ar, Users.Count);
  67.   for j := 0 to Users.Count-1 do
  68.   begin
  69.     Series_ar[j] := TLineSeries.Create(Chart1);
  70.     Series_ar[j].ShowPoints := True;
  71.     Series_ar[j].Pointer.Brush.Color := clRed;
  72.     Series_ar[j].Pointer.Pen.Color := clBlack;
  73.     Series_ar[j].Pointer.Style := psCircle;
  74.     Series_ar[j].Title := '';
  75.     Chart1.AddSeries(Series_ar[j]);
  76.   end;
  77.  
  78.   // Populate the series with the detection times, on a per-user base.
  79.   for i := 0 to High(Detection) do begin
  80.     j := Users.IndexOf(Detection[i].User_n);
  81.     if j = -1 then
  82.       continue;
  83.     DT := UnixToDateTime(Detection[i].time div 1000);
  84.     Series_ar[j].AddXY(dt, StrToInt(Detection[i].user_n), 'a');
  85.     if Series_ar[j].Title = '' then
  86.       Series_ar[j].Title := Detection[i].user_name + ' (' + Detection[i].user_n + ')';
  87.   end;
  88.  
  89.   Users.Free;
  90. end;            
Title: Re: Multi Lineseries
Post by: mpknap on March 13, 2019, 08:20:24 pm
 
Quote
Which error? Which code? (Sorry, I wrote so many tutorials, I really don't know by heart which one covers the datapoint click tool). Does the problem occur also with the code that you show in the attachment?
 

Error after clicking on the series point, screenshot in attachment.

Quote
Which code?
Code: Pascal  [Select][+][-]
  1. procedure TForm1.ChartToolset1DataPointClickTool1AfterMouseDown(ATool: TChartTool;
  2.   APoint: TPoint);
  3. var
  4.   x, y: double;
  5. begin
  6.   with ATool as TDatapointClickTool do
  7.     if (Series is TLineSeries) then
  8.       with TLineSeries(Series) do
  9.       begin
  10.         x := GetXValue(PointIndex);
  11.         y := GetYValue(PointIndex);
  12.         Statusbar1.SimpleText := Format('%s: x = %f, y = %f', [Title, x, y]);
  13.       end
  14.     else
  15.       Statusbar1.SimpleText := '';
  16. end;

Other tips for which I thank you, I will check tomorrow
Title: Re: Multi Lineseries
Post by: wp on March 13, 2019, 10:59:40 pm
Error 219 is "invalid type-cast": the parameter ATool does not seem to be a TDatapointClickTool. Is this in the code that you posted? Maybe you incidentally assigned it to another chart tool.
Title: Re: Multi Lineseries
Post by: mpknap on March 14, 2019, 06:45:07 am
Hmmm, everything works. The problem was in a larger program. In the code added above on the forum, sShift is actually badly marked, and the error image(attachment) is from a large program.

Thanks for the great tips about Arrays. This will solve a lot of problems.

What else interests me? LeftAxis description.
Can it be only at user.n height? And it would be best if it was User_name string. Because when you zoom, you can see the values between them and they are not needed.
Title: Re: Multi Lineseries
Post by: wp on March 15, 2019, 12:38:43 pm
In the previous version the y values were equal to the user number ("user_n"). This means that space will be reserved on the y axis also for those users without Detection values.

You can fix this by using the stringlist index of the user for y instead; the stringlist was constructed to contain only those users with detections. The stringlist, however, is available only temporarily, but since each stringlist item corresponds to a lineseries in your case the stringlist index is equal to the series index in the Series_ar array.

Thus, you can use the event OnMarkToText of the chart's LeftAxis to convert the series index (being the axis label primarily) to a nice text given by the series' Title.

Since the y values run from bottom to top and the user list is sorted alphabetically you will find "A" at the bottom and "Z" at the top. To reverse the order you can simply set the "Inverted" property of the chart's LeftAxis to "true".

Code: Pascal  [Select][+][-]
  1. uses
  2.   lconvencoding;   // for conversion of names in data file to UTF8
  3.  
  4. procedure TForm1.FormCreate(Sender: TObject);
  5. const
  6.   BLOCK_SIZE = 1000;
  7. var
  8.   Detection: array of THit;
  9.   f: textfile;
  10.   i, j: integer;
  11.   cc: int64;
  12.   Read_Line: string;
  13.   dt: tdatetime;
  14.   Users: TStringList;
  15. begin
  16.   Users := TStringList.Create;
  17.   Users.Duplicates := dupIgnore;
  18.   Users.Sorted := true;
  19.  
  20.   AssignFile(f, '01.01.2019.txt');
  21.   reset(f);
  22.   i := 0;
  23.   SetLength(Detection, 0);
  24.   while not EOF(f) do
  25.   begin
  26.     if i mod BLOCK_SIZE = 0 then
  27.       SetLength(Detection, Length(Detection) + BLOCK_SIZE);
  28.  
  29.     readln(f, cc);
  30.     Detection[i].time := cc;
  31.  
  32.     readln(f, Read_Line);
  33.     Detection[i].lat := Read_Line;
  34.  
  35.     readln(f, Read_Line);
  36.     Detection[i].long := Read_Line;
  37.  
  38.     readln(f, Read_Line);
  39.     Detection[i].user_n := Read_Line;
  40.  
  41.     readln(f, Read_Line);
  42.  
  43.     readln(f, Read_Line);
  44.     Detection[i].user_name := CP1251ToUTF8(Read_Line);  
  45.     // The data file seems to be encoded in CP1251 and must be converted to UTF8
  46.     // at least here for the user name.
  47.  
  48.     Users.Add(Detection[i].user_name);  
  49.     // This is modified to use the user_name instead of user_n in the previous version.
  50.     // The advantage is that the user list is sorted alphabetically by name
  51.  
  52.     readln(f, Read_Line);
  53.     Inc(i);
  54.   end;
  55.   closefile(f);
  56.   SetLength(Detection, i);
  57.  
  58.   // The stringlist "UserNames" contains the list of all user names. Each user
  59.   // name should have its own line series.
  60.   Chart1.DisableRedrawing;
  61.   try
  62.     SetLength(Series_ar, Users.Count);
  63.     for j := 0 to Users.Count-1 do
  64.     begin
  65.       Series_ar[j] := TLineSeries.Create(Chart1);
  66.       Series_ar[j].ShowPoints := True;
  67.       Series_ar[j].Pointer.Brush.Color := clRed;
  68.       Series_ar[j].Pointer.Pen.Color := clBlack;
  69.       Series_ar[j].Pointer.Style := psCircle;
  70.       Series_ar[j].Title := '';
  71.       Chart1.AddSeries(Series_ar[j]);
  72.     end;
  73.  
  74.     // Populate the series with the detection times, on a per-user base.
  75.     for i := 0 to High(Detection) do begin
  76.       j := Users.IndexOf(Detection[i].User_name);
  77.       if j = -1 then
  78.         continue;
  79.  
  80.       DT := UnixToDateTime(Detection[i].time div 1000);
  81.       Series_ar[j].AddXY(dt, j);  // use the series index as y value
  82.       if Series_ar[j].Title = '' then
  83.         Series_ar[j].Title := Detection[i].user_name + ' (' + Detection[i].user_n + ')';
  84.     end;
  85.   finally
  86.     Chart1.EnableRedrawing;
  87.   end;
  88.  
  89.   Users.Free;
  90. end;
  91.  
  92. procedure TForm1.Chart1AxisList0MarkToText(var AText: String; AMark: Double);
  93. var
  94.   idx: Integer;
  95. begin
  96.   // AMark is the y value to which the axis wants to place a label. But the y values are equal to the
  97.   // series indexes in our case. Therefore, we can select the label text to be the series title which
  98.   // contains the user_name
  99.   idx := round(AMark);  
  100.   if (idx < 0) or (idx >= Length(Series_ar)) then
  101.     AText := ''
  102.   else
  103.     AText := Series_ar[idx].Title;
  104. end;
Title: Re: Multi Lineseries
Post by: mpknap on March 16, 2019, 07:02:04 am
Ok, I will test and check it soon

Question. The graph is drawn in memory and later displayed on the screen. Is it possible to be drawn in real time on the screen? I would even like to delay the loop to make it look like an animated presentation. ;)
Title: Re: Multi Lineseries
Post by: wp on March 16, 2019, 12:26:19 pm
Question. The graph is drawn in memory and later displayed on the screen. Is it possible to be drawn in real time on the screen? I would even like to delay the loop to make it look like an animated presentation. ;)
Code: Pascal  [Select][+][-]
  1.     // Populate the series with the detection times, on a per-user base.
  2.     for i := 0 to High(Detection) do begin
  3.       j := Users.IndexOf(Detection[i].User_name);
  4.       if j = -1 then
  5.         continue;
  6.  
  7.       DT := UnixToDateTime(Detection[i].time div 1000);
  8.       Series_ar[j].AddXY(dt, j);
  9.       if Series_ar[j].Title = '' then
  10.         Series_ar[j].Title := Detection[i].user_name + ' (' + Detection[i].user_n + ')';
  11.  
  12.       if j mod 20 = 0 then            // <---- ADDED
  13.         Application.ProcessMessages;  // <---- ADDED
  14.     end;  
Title: Re: Multi Lineseries
Post by: mpknap on March 25, 2019, 07:34:30 pm
Dear WP.
That's the whole procedure. It works well, it shows what I want.
There is only one problem.

Code: Pascal  [Select][+][-]
  1. procedure TForm4.Button3Click(Sender: TObject);
  2. const
  3.   BLOCK_SIZE = 1000;
  4. var
  5.   Deteccion: array{[1..15000]} of thit;
  6.   f: textfile;
  7.   d, i, j, y, z: integer;
  8.   ex, cc: int64;
  9.   plik, dir, xxx: string;
  10.   dt: tdatetime;
  11.   Users: TStringList;
  12. begin
  13.   Users := TStringList.Create;
  14.   Users.Duplicates := dupIgnore;
  15.   Users.Sorted := True;
  16.  
  17.  
  18.   dir := GetCurrentDir;
  19.   chdir(dir + '\DATA');
  20.    begin
  21.  
  22.     plik := label6.Caption;
  23.     AssignFile(f, plik);
  24.     reset(f);
  25.     i := 0;
  26.     SetLength(Deteccion, 0);
  27.  
  28.     while not EOF(f) do
  29.     begin
  30.       if i mod BLOCK_SIZE = 0 then
  31.         SetLength(Deteccion, Length(Deteccion) + BLOCK_SIZE);
  32.  
  33.       readln(f, cc);
  34.       Deteccion[i].time := cc;//div 1000;
  35.       readln(f, xxx);
  36.       Deteccion[i].lat := xxx;
  37.       readln(f, xxx);
  38.       Deteccion[i].long := xxx;
  39.  
  40.       readln(f, xxx);
  41.       Deteccion[i].user_n := xxx;
  42.  
  43.       readln(f, xxx);
  44.  
  45.       readln(f, xxx);
  46.       Deteccion[i].user_name := CP1250ToUTF8(xxx);
  47.       Users.Add(Deteccion[i].user_n);
  48.  
  49.       readln(f, xxx);
  50.       Inc(i);
  51.     end;
  52.     closefile(f);
  53.     SetLength(Deteccion, i);
  54.  
  55.     // The stringlist "UserNames" contains the list of all user names. Each user
  56.     // name should have its own line series.
  57.     SetLength(Series_ar, Users.Count);
  58.  
  59.     z := 0;
  60.       for j := 0 to Users.Count - 1 do
  61.  
  62.     begin
  63.       Series_ar[j] := TLineSeries.Create(Chart1);
  64.       Series_ar[j].ShowPoints := True;
  65.       Series_ar[j].Pointer.Brush.Color := rgb(Random(256), Random(256), Random(256));
  66.       ;
  67.       Series_ar[j].Pointer.Pen.Color := clBlack;
  68.       Series_ar[j].Pointer.Style := psCircle;
  69.       Series_ar[j].Title := '';
  70.       Chart1.AddSeries(Series_ar[j]);
  71.      end;
  72.       d := 0;
  73.     for j := 0 to High(Deteccion) do
  74.     begin
  75.       d := Users.IndexOf(Deteccion[j].User_n);
  76.       if d = -1 then
  77.         continue;
  78.         DT := unixToDateTime((Deteccion[j].time) div 1000);
  79.    
  80.       Series_ar[d].AddXy(Deteccion[j].time, StrToInt(Deteccion[j].user_n){dt}, 'a');
  81.         Series_ar[d].Title := (Deteccion[j].user_name);
  82.      
  83.     end;
  84.      
  85.   end;
  86.   chdir(dir);
  87.   Users.Free;
  88. end;          

I also display the results in ChartListBox.
When I click Button3 for the first time, the results are ok. But when you re-click the ChartListBox, the results are added to the previous data. The graph is ok. How do I reset ChartListBox before redrawing?


I tried, ChartListbox1.clear, ... free, ... DeleteSelected and nothing.
Title: Re: Multi Lineseries
Post by: wp on March 25, 2019, 10:44:27 pm
When I click Button3 for the first time, the results are ok. But when you re-click the ChartListBox, the results are added to the previous data. The graph is ok. How do I reset ChartListBox before redrawing?
When you click that button a second time you re-read the data file and create the series again. The chartlistbox is correct: your chart contains the same series twice. To prevent this, you must clear the chart in the beginning of this click handler, or log that you already have executed this procedure and exit it immediately in this case.
Code: Pascal  [Select][+][-]
  1.   Chart.ClearSeries;
Title: Re: Multi Lineseries
Post by: mpknap on March 26, 2019, 07:44:50 am
Work Perfect!!! :)
Title: Re: Multi Lineseries
Post by: mpknap on March 30, 2019, 08:33:11 am
There is also a problem with displaying the correct time of the clicked point on the chart.

As you know, the input time is UnixTime, converted to a normal date Year / Month / Day / Hour / Minute / Second.

The graph is ok, but after clicking point, label4  isdisplays no complete time. (hour and minute = 0)

I have as source in DATETIMEINTERVALCHARTSOURCE, Marks is smsLabel.

Load & Draw procedure :
Code: Pascal  [Select][+][-]
  1. procedure TForm4.Button3Click(Sender: TObject);
  2. const
  3.   BLOCK_SIZE = 1000;
  4. var
  5.   Detection: array{[1..15000]} of thit;
  6.   f: textfile;
  7.   d, i, j, y, z: integer;
  8.   ex, cc: int64;
  9.   plik, dir, xxx: string;
  10.   dt: tdatetime;
  11.   Users: TStringList;
  12. begin
  13.   Users := TStringList.Create;
  14.   Users.Duplicates := dupIgnore;
  15.   Users.Sorted := True;
  16.  
  17.    Chart1.ClearSeries;
  18.   dir := GetCurrentDir;
  19.   chdir(dir + '\DATA');
  20.    if FileExists(label6.Caption  ) =true then
  21.   begin
  22.  
  23.     plik := label6.Caption;
  24.     AssignFile(f, plik);
  25.     reset(f);
  26.     i := 0;
  27.     SetLength(Detection, 0);
  28.  
  29.     while not EOF(f) do
  30.     begin
  31.       if i mod BLOCK_SIZE = 0 then
  32.         SetLength(Detection, Length(Detection) + BLOCK_SIZE);
  33.  
  34.       readln(f, cc);
  35.       Detection[i].time := cc;//div 1000;
  36.       readln(f, xxx);
  37.       Detection[i].lat := xxx;
  38.       readln(f, xxx);
  39.       Detection[i].long := xxx;
  40.  
  41.       readln(f, xxx);
  42.       Detection[i].user_n := xxx;
  43.  
  44.       readln(f, xxx);
  45.  
  46.       readln(f, xxx);
  47.       Detection[i].user_name := CP1250ToUTF8(xxx);
  48.       Users.Add(Detection[i].user_n);
  49.  
  50.       readln(f, xxx);
  51.       Inc(i);
  52.     end;
  53.     closefile(f);
  54.     SetLength(Detection, i);
  55.  
  56.    
  57.     SetLength(Series_ar, Users.Count);
  58.  
  59.     z := 0;
  60.     label4.Caption := i.ToString;
  61.      for j := 0 to Users.Count - 1 do
  62.  
  63.     begin
  64.       Series_ar[j] := TLineSeries.Create(Chart1);
  65.       Series_ar[j].ShowPoints := True;
  66.       Series_ar[j].Pointer.Brush.Color := rgb(Random(256), Random(256), Random(256));
  67.        Series_ar[j].Pointer.Pen.Color := clBlack;
  68.       Series_ar[j].Pointer.Style := psCircle;
  69.       Series_ar[j].Title := '';
  70.       Chart1.AddSeries(Series_ar[j]);
  71.      end;
  72.      d := 0;
  73.     for j := 0 to High(Detection) do
  74.     begin
  75.       d := Users.IndexOf(Detection[j].User_n);
  76.       if d = -1 then
  77.         continue;
  78.        progressbar1.Position := j;
  79.        DT := unixToDateTime((Detection[j].time  div 1000));
  80.  
  81.      //Series_ar[d].AddXy(Detection[j].time, StrToInt(Detection[j].user_n){dt}, 'a');
  82.        Series_ar[d].AddXy( dt, StrToInt(Detection[j].user_n));
  83.  
  84.        Series_ar[d].Title := (Detection[j].user_name);
  85.    
  86.     end;
  87.     end;
  88.   chdir(dir);
  89.   Users.Free;
  90.  
  91. end;
  92.                  

Click point procedure :

Code: Pascal  [Select][+][-]
  1. procedure TForm4.ChartToolset1DataPointClickTool1AfterKeyDown(ATool: TChartTool;
  2.   APoint: TPoint);
  3. var
  4.   z: int64;
  5.   at: tdatetime;
  6. begin
  7.  
  8.   with ATool as TDatapointClickTool do
  9.     if (Series is TLineSeries) then
  10.       with TLineSeries(Series) do
  11.       begin
  12.         z := round(GetXValue(PointIndex));
  13.          at := unixToDateTime(z div 1000 );
  14.  
  15.         Statusbar1.SimpleText := 'Mouse Click : Left-Info/Right-Drag/MouseWhell-Zoom';
  16.  
  17.         ... Label4.Caption := title + ' ' +   datetimetostr(at );  I try this
  18.        ... Label4.Caption := title + ' ' +   datetimetostr(z );     and this
  19.  
  20.       end
  21.     else
  22.       Statusbar1.SimpleText := '';
  23.  
  24. end;  
  25.  
Title: Re: Multi Lineseries
Post by: wp on March 30, 2019, 12:32:10 pm
When you added the data to the series you had converted UNIX time to DateTime - the values that you get from querying the series are DateTime values. So, DateTimeToStr should be correct. Maybe you modified the FormatSettings? If you use FormatDateTime you have full control over the formatting process:

Code: Pascal  [Select][+][-]
  1. Label4.Caption := title + ' ' +  FormatDateTime('ddddd hh:nn:ss', z);

NB: the 'dddddd' part refers to the ShortDateFormat of your DefaultFormatSettings. Of course you can replace it by any other format, such as 'yyyy-mm-dd' to display today's date as '2019-03-30'.
Title: Re: Multi Lineseries
Post by: mpknap on March 30, 2019, 01:26:42 pm
There are still no hours and minutes, in Label4.
 On the entire Series, the hour after clicking is 00:00:00.(see Atachment)  The date is correct.

Is not it about GetXValue?

Title: Re: Multi Lineseries
Post by: wp on March 30, 2019, 01:59:37 pm
You should extract the chart code in a demo for me to have a look at.
Title: Re: Multi Lineseries
Post by: mpknap on March 30, 2019, 03:34:40 pm

Attachment

Title: Re: Multi Lineseries
Post by: wp on March 30, 2019, 05:29:11 pm
Why do you declare the variable which pick up the x value from the series as Integer? It is a TDateTime, i.e. a floating point value. The integer part of a TDateTime is the days only, the hours/minutes are lost because they are the fractional part. This is how it works:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.ChartToolset1DataPointClickTool1AfterMouseDown(ATool: TChartTool;
  2.   APoint: TPoint);
  3. var
  4.   x: Double;
  5. begin
  6.   with ATool as TDatapointClickTool do
  7.     if (Series is TLineSeries) then
  8.       with TLineSeries(Series) do
  9.       begin
  10.         x := GetXValue(PointIndex);
  11.         Label3.Caption := title + ' ' +  FormatDateTime('ddddd hh:nn:ss', x);
  12.       end
  13.     else
  14.       Statusbar1.SimpleText := '';
  15. end;
Title: Re: Multi Lineseries
Post by: mpknap on March 30, 2019, 05:50:28 pm
That is the question why ... I changed it once and I forgot. :-[
 Thanks .
Title: Re: Multi Lineseries
Post by: mpknap on April 08, 2019, 08:22:16 am
Welcome back.
Until now, the graph showed the detection time and username.
I make a second chart, similar to the first one. It is supposed to show the detection time for a team in which there are many users. Works properly.
But I do not know how to do it by clicking on the point correctly showing the name of the team and the user of the particular detection.
Code: Pascal  [Select][+][-]
  1. procedure TForm4.Button3Click(Sender: TObject);
  2. const
  3.   BLOCK_SIZE = 1000;
  4. var
  5.   Detection: array{[1..15000]} of thit;
  6.   f: textfile;
  7.   d, i, j, y, z: integer;
  8.   ex, cc: int64;
  9.   plik, dir, xxx: string;
  10.   dt: tdatetime;
  11.   Users: TStringList;
  12. begin
  13.   Users := TStringList.Create;
  14.   Users.Duplicates := dupIgnore;
  15.   Users.Sorted := True;
  16.  
  17.   Chart1.ClearSeries;
  18.   dir := GetCurrentDir;
  19.   chdir(dir + '\DATA');
  20.   if FileExists(label6.Caption  ) =true then
  21.   begin
  22.  
  23.     plik := label6.Caption;
  24.     AssignFile(f, plik);
  25.     reset(f);
  26.     i := 0;
  27.     SetLength(Detection, 0);
  28.  
  29.     while not EOF(f) do
  30.     begin
  31.       if i mod BLOCK_SIZE = 0 then
  32.         SetLength(Detection, Length(Detection) + BLOCK_SIZE);
  33.  
  34.       readln(f,  Detection[i].time  );
  35.      
  36.       readln(f, Detection[i].lat );
  37.        
  38.       readln(f, Detection[i].long  );
  39.      
  40.       readln(f,  Detection[i].user_name  );
  41.      
  42.       readln(f, Detection[i].user_n  );
  43.  
  44.       readln(f,  Detection[i].team_name  );
  45.      
  46.  
  47.       readln(f, Detection[i].team_nr );
  48.  
  49.  
  50.       readln(f, xxx);
  51.      
  52.       Users.Add(Detection[i].team_nr);
  53.  
  54.       readln(f, xxx);
  55.       Inc(i);
  56.     end;
  57.     closefile(f);
  58.     SetLength(Detection, i);
  59.  
  60.    
  61.     SetLength(Series_ar, Users.Count);
  62.  
  63.     z := 0;
  64.     label4.Caption := i.ToString;
  65.      for j := 0 to Users.Count - 1 do
  66.  
  67.     begin
  68.       Series_ar[j] := TLineSeries.Create(Chart1);
  69.       Series_ar[j].ShowPoints := True;
  70.       Series_ar[j].Pointer.Brush.Color := rgb(Random(256), Random(256), Random(256));
  71.        Series_ar[j].Pointer.Pen.Color := clBlack;
  72.       Series_ar[j].Pointer.Style := psCircle;
  73.       Series_ar[j].Title := '';
  74.       Chart1.AddSeries(Series_ar[j]);
  75.     end;
  76.     d := 0;
  77.     for j := 0 to High(Detection) do
  78.     begin
  79.       d := Users.IndexOf(Detection[j].team_nr);
  80.       if d = -1 then
  81.         continue;
  82.         DT := unixToDateTime((Detection[j].time  div 1000));
  83.  
  84.      //Series_ar[d].AddXy(Detection[j].time, StrToInt(Detection[j].team_nr) );
  85.        Series_ar[d].AddXy( dt, StrToInt(Detection[j].team_nr));
  86.  
  87.        Series_ar[d].Title := (Detection[j].team_name+ Detection[j].User_name);{<---?????}
  88.    
  89.     end;
  90.     end;
  91.   chdir(dir);
  92.   Users.Free;
  93.  
  94. end;

I have also changed the format of the TXT file:
Time
Lat
Long
User Name
User Number
Team Name
Team Number
Free line - separator
Title: Re: Multi Lineseries
Post by: wp on April 08, 2019, 10:48:38 am
You could combine team and user name and add this string as a third parameter to the series' AddXY:
Code: Pascal  [Select][+][-]
  1. Series_ar[d].AddXX(
  2.   dt,                                              // x
  3.   StrToInt(Detection[j].team_nr),                  // y
  4.   Detection[j].team_name+ Detection[j].User_name   // text
  5. );
Then, in the click routine of the DataPointClickTool you extract that text and combine it with the date:
Code: Pascal  [Select][+][-]
  1.     procedure TForm1.ChartToolset1DataPointClickTool1AfterMouseDown(ATool: TChartTool;
  2.       APoint: TPoint);
  3.     var
  4.       x: Double;
  5.       s: String;
  6.     begin
  7.       with ATool as TDatapointClickTool do
  8.         if (Series is TLineSeries) then
  9.           with TLineSeries(Series) do
  10.           begin
  11.             x := GetXValue(PointIndex);
  12.             s := Source[PointIndex]^.Text;
  13.             Label3.Caption := s + ' ' +  DateTimeToStr(x);
  14.           end
  15.         else
  16.           Label3.Caption := '';
  17.     end;
Title: Re: Multi Lineseries
Post by: mpknap on April 08, 2019, 07:56:16 pm
Perfect!  ;)
Title: Re: Multi Lineseries
Post by: mpknap on May 05, 2019, 05:51:42 pm
A new problem WP.
How to count in each Series, how many detections are there in an identical timestamp (on x axis).
I want to show only those detections that occurred on several users at one time (+ -1 second).
I hope I wrote it clearly;)
TinyPortal © 2005-2018