Update a plot in LibPlotters

Engineering & Scientific library: http://zinnamturm.eu/downloadsIN.htm#Lib
dmaksimiuk
Posts: 28
Joined: Sun Jun 14, 2015 3:56 pm
Location: Delft, The Netherlands

Update a plot in LibPlotters

Post by dmaksimiuk »

Hi All,
this is not per se a problem with the BBox GUI but it contains some elements of it.
I am trying to add a semi real-time monitor to visualise data coming from a dual channel ADC (STM42F407 SoC, https://www.olimex.com/Products/ARM/ST/ ... e-hardware).
In order to accomplish my goal, I am using the LibPlotters library. The simplified code is presented below.
Filling data vector with values of j simulates the reading process from an external device. So, first an empty plot is created. For j =1 I am updating the data vector and then
I am sending the update message to a plot. The same is for j=2. The problem is that I cannot see the updated plot for j=1, and only for j=2 a line is shown on the plot (at the end of my program).
Why sending the update message (for j=1) does not refresh the plot? Did I miss something?
I am using BBox v1.6-RC6.
Your suggestions are welcome.

Cheers,
Darek

Code: Select all

MODULE TestPlotLib;
IMPORT  Plotters := LibPlotters,  Ports, Log := StdLog, Fnt:= Fonts,
               Vec := LibVectors;



VAR	PLp: Plotters.Plotter;

PROCEDURE InitPlotter (VAR P: Plotters.Plotter);
      
	BEGIN
	   P := Plotters.dir.NewPlotter(" Data display");
	   P.SetMargin(5,5,5,5);
	   P.Scale(0, 513.0, -1.0 , 11.0);
	   P.Xaxis(0, 512.0,  0, 0, 0.0, 0, {});
	   P.Yaxis(-1.0, 10.0, 0, 0,  0.0, 0, TRUE, {});  
	   P.SetFont2 ('New Courier', 8 * Fnt.point, {Fnt.italic}, Fnt.bold);
           P.XyLabel (256.0, 10., Plotters.jC, 'Test data');
           P.AddToolBar();       
	END InitPlotter;


PROCEDURE Run*(); 
   VAR data : Vec.Vector;
           i, j, k: INTEGER;
    BEGIN
       InitPlotter(PLp);
       Plotters.OpenAux(PLp, 0,0, TRUE, 'Test plot update');
       data := Vec.New(512);
       PLp.DrawVector(data, 0.0 , 1, 0.0, 1.0,  0, Ports.blue); 
       FOR j := 1 TO 2 DO 
          Log.String("Iteration : "); Log.Int(j); Log.Ln();
          FOR i := 0 TO LEN(data) -1 DO 
             data[i] := j ;
         END; 
         PLp.Update();  
         Log.String("Pause start");Log.Ln();
         (*just delay *)
         FOR k := 0 TO 500000000 DO  END; 
         Log.String("Pause end"); Log.Ln();
      END; 
END Run;

BEGIN
END TestPlotLib.

 TestPlotLib.Run 

  DevDebug.UnloadThis  TestPlotLib
manumart1
Posts: 67
Joined: Tue Sep 17, 2013 6:25 am

Re: Update a plot in LibPlotters

Post by manumart1 »

I've tried to install subsystem Lib from http://www.zinnamturm.eu, but I've got an error compiling module LibPlot3D:

Code: Select all

MODULE  LibPlot3D;
 	IMPORT Api := WinApi ...
	...
	PROCEDURE (g : SurfGraphic) Restore2 (f : Views.Frame);
		VAR
			...
			dc, k, res:  INTEGER;
			pts:  ARRAY  10  OF  Api.POINT;
	BEGIN
		...
		res  :=  Api.Polygon (dc, pts[0], k);
		...
The error is in parameter pts[0] --> actual parameter corresponding to open array is not an array

Regards
dmaksimiuk
Posts: 28
Joined: Sun Jun 14, 2015 3:56 pm
Location: Delft, The Netherlands

Re: Update a plot in LibPlotters

Post by dmaksimiuk »

Hi,
Check out this topic: http://community.blackboxframework.org/ ... ?f=16&t=94
The solution to your problem is there.

Regards,
Darek
manumart1 wrote:I've tried to install subsystem Lib from http://www.zinnamturm.eu, but I've got an error compiling module LibPlot3D:

Code: Select all

MODULE  LibPlot3D;
 	IMPORT Api := WinApi ...
	...
	PROCEDURE (g : SurfGraphic) Restore2 (f : Views.Frame);
		VAR
			...
			dc, k, res:  INTEGER;
			pts:  ARRAY  10  OF  Api.POINT;
	BEGIN
		...
		res  :=  Api.Polygon (dc, pts[0], k);
		...
The error is in parameter pts[0] --> actual parameter corresponding to open array is not an array

Regards
manumart1
Posts: 67
Joined: Tue Sep 17, 2013 6:25 am

Re: Update a plot in LibPlotters

Post by manumart1 »

Thanks,
I changed pts[0] to pts.

Regarding your question, I've noticed:
  • The call to PLp.Update() inside the loop, doesn't seem to do the drawing of a line; instead the whole graphic seems to be updated completely only when the program finished.
  • If you want to draw 2 lines, I think you'll need 2 variables of type Vec.Vector.
Example to draw 3 lines:

Code: Select all

	PROCEDURE Run* ();
		VAR data: ARRAY 3 OF Vec.Vector;  (* 3 lines *)
			i, j, k: INTEGER;
	BEGIN
		InitPlotter(PLp);
		Plotters.OpenAux(PLp, 0, 0, TRUE, 'Test plot update');
		FOR j := 0 TO 2 DO
			Log.String("Iteration : "); Log.Int(j); Log.Ln();
			data[j] := Vec.New(512);
			FOR i := 0 TO LEN(data[j]) - 1 DO
				data[j][i] := j + 1
			END;
			PLp.DrawVector(data[j], 0.0, 1, 0.0, 1.0, 0, Ports.blue);
			PLp.Update();
			Log.String("Pause start"); Log.Ln();
			(*just delay *)
			WinApi.Sleep(1000);
			Log.String("Pause end"); Log.Ln()
		END
	END Run;
Regards,
Manuel
manumart1
Posts: 67
Joined: Tue Sep 17, 2013 6:25 am

Re: Update a plot in LibPlotters

Post by manumart1 »

I am not sure why I need Services.Action and procedure ProcessMessages, but this example manages to show 5 lines, one at a time:

Code: Select all

	TYPE
		(* Copied from ObxActions *)
		Action = POINTER TO RECORD (Services.Action)
			j: INTEGER
		END;

	VAR PLp: Plotters.Plotter;

	PROCEDURE ProcessMessages; (* Chris Burrows *)
		VAR msg: WinApi.MSG; res: INTEGER;
	BEGIN
		WHILE WinApi.PeekMessage(msg, 0, 0, 0, WinApi.PM_REMOVE) # 0 DO
			res := WinApi.TranslateMessage(msg);
			res := WinApi.DispatchMessageA(msg)
		END
	END ProcessMessages;

	PROCEDURE (a: Action) Do;
		VAR data: Vec.Vector;
			i: INTEGER;
	BEGIN
		ASSERT((1 <= a.j) & (a.j <= 5), 20);
		data := Vec.New(512);
		PLp.DrawVector(data, 0.0, 1, 0.0, 1.0, 0, Ports.blue);

		Log.String("Iteration : "); Log.Int(a.j); Log.Ln();
		FOR i := 0 TO LEN(data) - 1 DO
			data[i] := a.j
		END;
		PLp.Update();

		IF a.j < 5 THEN
			(* just delay *)
			Log.String("Pause start"); Log.Ln();
			WinApi.Sleep(2000);
			Log.String("Pause end"); Log.Ln();

			ProcessMessages();

			INC(a.j);
			Services.DoLater(a, Services.immediately)
		END
	END Do;


	PROCEDURE InitPlotter (VAR P: Plotters.Plotter);
	BEGIN
		P := Plotters.dir.NewPlotter(" Data display");
		P.SetMargin(5, 5, 5, 5);
		P.Scale(0, 513.0,  - 1.0, 11.0);
		P.Xaxis(0, 512.0, 0, 0, 0.0, 0, {});
		P.Yaxis( - 1.0, 10.0, 0, 0, 0.0, 0, TRUE, {});
		P.SetFont2('New Courier', 8 * Fnt.point, {Fnt.italic}, Fnt.bold);
		P.XyLabel(256.0, 10., Plotters.jC, 'Test data');
		P.AddToolBar()
	END InitPlotter;


	PROCEDURE Run* ();
		VAR a: Action;
	BEGIN
		InitPlotter(PLp);
		Plotters.OpenAux(PLp, 0, 0, TRUE, 'Test plot update');

		NEW(a);
		a.j := 1;
		Services.DoLater(a, Services.immediately)
	END Run;
dmaksimiuk
Posts: 28
Joined: Sun Jun 14, 2015 3:56 pm
Location: Delft, The Netherlands

Re: Update a plot in LibPlotters

Post by dmaksimiuk »

Hi Manuel,
thanks for you answer. I am not sure if the solution fits my requirements because I cannot create a new variable for every incoming data from the ADC converters (via a serial port @921.6kbits/s).
I am getting samples in blocks of 2048 bytes at rate 40 Hz, and in theory the stream of blocks is infinite. I am just curious if there is another mechanism that allows an update of the graph in the right way. The other solution is to integrate (via DLL) a third-party plot routines, and to be honest I would like to avoid it.

BTW, did somebody went this path and integrate such a package into the BBox environment? Any feedback is very welcome.

Cheers,
Darek
manumart1
Posts: 67
Joined: Tue Sep 17, 2013 6:25 am

Re: Update a plot in LibPlotters

Post by manumart1 »

I don't know much, but I've heard about subsystem Gr (Data Acquisition and Monitoring Toolbox). It seems to fit your needs.
You can download it from http://www.zinnamturm.eu/downloadsDH.htm#Gr

See also
http://www.pas.rochester.edu/~skulski/Downloads.html
http://www.pas.rochester.edu/~skulski/P ... _Class.pdf

Regards,
Manuel
User avatar
Robert
Posts: 177
Joined: Sat Sep 28, 2013 11:04 am
Location: Edinburgh, Scotland

Re: Update a plot in LibPlotters

Post by Robert »

You need to replace the line

Code: Select all

PLp.Update
with

Code: Select all

PLp.UpdateNow
I can't remember if the published version includes the UpdateNow procedure. If not, the missing code is

Code: Select all

TYPE
  UpdateModelNow  =  RECORD (Models.UpdateMsg)
                       graphicOnly  :  BOOLEAN
                     END;

  UpdateViewNow   =  RECORD (Views.Message)
                       graphicOnly  :  BOOLEAN
                     END;

PROCEDURE (v : StdView) HandleModelMsg (VAR msg : Models.Message);
  VAR
    updateNow  :  UpdateViewNow;
  BEGIN
    WITH  msg  : UpdateModelNow  DO
      updateNow.graphicOnly  :=  msg.graphicOnly; Views.Broadcast (v, updateNow)
    |  msg  :  Models.UpdateMsg  DO
      Views.Update (v, Views.keepFrames)
    ELSE
    END
  END HandleModelMsg;

PROCEDURE (v : StdView) HandleViewMsg (f : Views.Frame; VAR msg : Views.Message);
  BEGIN
    WITH  msg : UpdateViewNow  DO
      IF  msg.graphicOnly  THEN
        v.RestoreGraphic (f, f.l, f.t, f.r, f.b)
      ELSE
        f.DrawRect (f.l, f.t, f.r, f.b, Ports.fill, Ports.background);
        v.Restore (f, f.l, f.t, f.r, f.b)
      END
    ELSE
    END
  END  HandleViewMsg;

PROCEDURE (p : Plotter) UpdateNow*, NEW, EXTENSIBLE;
  VAR
    msg  :  UpdateModelNow;
  BEGIN
    msg.graphicOnly  :=  FALSE; Models.Broadcast (p, msg)
  END  UpdateNow;
Let me know if you get this to work.

Regards

Robert
User avatar
Robert
Posts: 177
Joined: Sat Sep 28, 2013 11:04 am
Location: Edinburgh, Scotland

Re: Update a plot in LibPlotters

Post by Robert »

If you want to draw 2 lines, I think you'll need 2 variables of type Vec.Vector.
Yes. For efficiency reasons the Plotter does not copy the Vector, which means you can plot the Vector, then change its values, then call "p.Update" and the
Plotter will be redrawn, with the new values, when the current Command finishes - or use UpdateNow if you do not want to wait until the end of the Command.

The reason the Actions technique works with Update is that the Command finishes after each line, then a new Command is started for the next line.

If you have lots of data (say 1000 lines worth) and want to show the last 5 at any time you need to create 5 Vectors, call p.DrawVector for each, then keep
updating the Vectors on a round-robbin basis calling p.UpdateNow every time the oldest Vector is refilled with new data.
manumart1
Posts: 67
Joined: Tue Sep 17, 2013 6:25 am

Re: Update a plot in LibPlotters

Post by manumart1 »

The module LibPlotters (subsystem Lib from http://www.zinnamturm.eu/downloadsIN.htm#Lib) does not have Plotter.UpdateNow.

I modified module LibPlotters as you showed, but got an error:

Code: Select all

PROCEDURE (v : StdView) HandleViewMsg (f : Views.Frame; VAR msg : Views.Message);
BEGIN
  WITH  msg : UpdateViewNow  DO
    IF  msg.graphicOnly  THEN
      v.RestoreGraphic (f, f.l, f.t, f.r, f.b)    <-- undefined record field
    ELSE
      f.DrawRect (f.l, f.t, f.r, f.b, Ports.fill, Ports.background);
      v.Restore (f, f.l, f.t, f.r, f.b)
    END
  ELSE
  END
END  HandleViewMsg;
So RestoreGraphic is missing. I changed it to Restore and compiled ok.

It now works ok, i.e. each line is drawn separately and immediately, instead of all at the same time when the program finished.

But... if the delay is too long, after 6 seconds the cursor becomes busy and the screen freezes. I had to add a call to procedure ProcessMessages.

Code: Select all

VAR PLp: LibPlotters.Plotter;

PROCEDURE ProcessMessages; (* Chris Burrows *)
	VAR msg: WinApi.MSG; res: INTEGER;
BEGIN
	WHILE WinApi.PeekMessage(msg, 0, 0, 0, WinApi.PM_REMOVE) # 0 DO
		res := WinApi.TranslateMessage(msg);
		res := WinApi.DispatchMessageA(msg)
	END
END ProcessMessages;

PROCEDURE Run3* ();
	VAR data: Vec.Vector;
		j, i: INTEGER;
BEGIN
	InitPlotter(PLp);
	Plotters.OpenAux(PLp, 0, 0, TRUE, 'Test plot update');
	FOR j := 1 TO 5 DO
		Log.String("Iteration : "); Log.Int(j); Log.Ln();
		data := Vec.New(512);
		FOR i := 0 TO LEN(data) - 1 DO data[i] := j END;
		PLp.DrawVector(data, 0.0, 1, 0.0, 1.0, 0, Ports.blue);
		PLp.UpdateNow();
		(*just delay *)
		Log.String("Pause start"); Log.Ln();
		WinApi.Sleep(2000);
		Log.String("Pause end"); Log.Ln()
		ProcessMessages()
	END
END Run3;
Regards,
Manuel
Post Reply