Question
How to plot a changing function in a fixed area using ocaml + gtk,
just like in the lissajous example in the examples folder of Lablgtk 1.2.5 ?
How to keep track of the past dots? What if the area moves away at constant speed?
Answer
First of all, let’s read and comment the lissajous example by Jacques Garrigue.
(* $Id: lissajous.ml,v 1.9 2000/06/06 03:51:04 garrigue Exp $ *)
(* Lissajous $B?^7A (B *)
(*z notation: comments starting with ’(*z’ were added by Zack *) *)
open GMain
let main () =
(*z create the main lissajous window *)
let window = GWindow.window border_width: 10 () in
(*z connect a debugging print to the ’delete’ event, the trailing "true" value
tells gtk not to process other handlers for this event after processing this
one. In this specific case, this makes lissajous not killable using the ’close
windows’ button of your window manager, try changing it to "false" ... *)
window#event#connect#delete
callback:(fun _ -> prerr_endline "Delete event occured"; true);
(*z on ’destroy’ event quit the gtk main loop *)
window#connect#destroy callback:Main.quit;
(*z vertical box which contains: *)
let vbx = GPack.vbox packing:window#add () in
(*z 1: a button ... *)
let quit = GButton.button label:"Quit" packing:vbx#add () in
(*z which destroy the window when clicking on it *)
quit#connect#clicked callback:window#destroy;
(*z 2: a drawing area *)
let area = GMisc.drawing_area width:200 height:200 packing:vbx#add () in
(*z "realize" the window related to the drawing area so that gtk effectively
create it, required before instantiate a ’drawable’ object *)
let drawing = area#misc#realize (); new GDraw.drawable (area#misc#window) in
let m_pi = acos (-1.) in
let c = ref 0. in
(*z callback invoked each time a slice of the window needs to be redrawn *)
(*z please note that the argument is ignored, this means that the window is
always fully redrawn *)
let expose_event _ =
(*z set foregrount and background color, draw the rectangle *)
drawing#set_foreground `WHITE;
drawing#rectangle filled:true x:0 y:0 width:200 height:200 ();
drawing#set_foreground `BLACK;
(*z compute a lissajous line (I guess, I’m not a lissajous expert :-) *)
let n = 200 in
let r = 100. in
let a = 3 in let b = 5 in
for i=0 to n do
let theta0 = 2.*.m_pi*.(float (i-1))/. (float n) in
let x0 = 100 + (truncate (r*.sin ((float a)*.theta0))) in
let y0 = 100 - (truncate (r*.cos ((float b)*.(theta0+. !c)))) in
let theta1 = 2.*.m_pi*.(float i)/.(float n) in
let x1 = 100 + (truncate (r*.sin((float a)*.theta1))) in
let y1 = 100 - (truncate (r*.cos((float b)*.(theta1+. !c)))) in
(*z draw the current line *)
drawing#line x:x0 y:y0 x:x1 y:y1
done;
false (*z proceed processing other handlers *)
in
(*z connect "expose" events to expose_event callback *)
area#event#connect#expose callback:expose_event;
(*z on timeout modify "c" and redraw *)
let timeout _ = c := !c +. 0.01*.m_pi;
expose_event ();
true in
(*z register a timeout which calls "timeout" callback twice a second *)
Timeout.add ms:500 callback:timeout;
window#show (); (*z show the main window *)
Main.main ()
let _ = Printexc.print main()
|
Now, let’s examine what to do next.
Basically we should draw what we need on a GDraw.drawable object and
then handle at least the "expose_event" signal which is invoked each
time the widget is shown again after having been hidden behind something
else. A tricky point is that in order to instantiate a GDraw.drawable
object we need a "realized" window, to obtain it we simply have to
invoke the "realize" method on the drawing_area which create the window
for the widget. The expose_event handler receives an area which is the
area that needs to be redrawn (on the lissajous example this area is
simply ignored and each time an expose_event is received the whole
window is redrawn).
To keep track of past points somewhere, make showable only the last N1 points (say),
put the last N1 points in a scrolled window that we can always see only
the latest N2 (smaller than N1) points of, one have some kind of structure
which is able to contains N1 points, this structure should have an operation
(which can be a method for an OO structure or a function for a more
functional approach) "add", this method should take care of adding a
point to the set of current point and remove an old point if the
structure is full.
The life cycle of an incoming point would be:
- being added to the set of "current" points
- being accessed each time we need to redraw the graph window
- being removed to the set of "current" points after N1 points have
been added to the same set
To keep track of points, two solutions stand out:
- "à la C" solution: use an Array as a circular buffer
- "à la OCaml" solution: use the Queue module of the standard ocaml
distribution and take care of keeping track of the queue size
To have the plot shift left at constant rate, we can:
- have a scrolled window on which to plot latest say 5 points
- have an array "points" which keeps the latest 5 points,
e.g.: points = [| 1; 12; 3; 32; 56 |]
- each time we have to replot we iter on the array and plot the points
we find in
- each time we have a new point we add it to the array removing the
first inserted, e.g. let’s suppose that we received a new point 75, our array
will become points = [| 12; 3; 32; 56; 75 |]
The invariant is: "the array points alway contains the latest 5 points",
using this invariant for plotting we are sure to plot always the latest
points, considering that we remove old points from the array we obtain
the "shift left" effect.
Obviously we additionally have to keep the scrollbar to the right and
to manually redraw (just calling the redraw callback as the lissajous
example does) each time we add a point to the array.
Here I enclose an example
- with a function more suitable for left-shift plotting than lissajous figures
- where the GUI part is coded using glade + lablgladecc which are the
combination I suggest to use because it’s IMO clearer and simpler
(as an example the ’realize’ trick is no longer
needed because all gtk’s widget are pre-realized when you create the
main object).
In order to compile it, you will need:
- Ocaml 3.x.x
- Gtk1.2
- Ocamlfind (AKA findlib)
- LablGtk configured with the "USE_GLADE=1" option and installed both with "make install"
and then with ocamlfind (look for the proper META file on the Internet)
Download the example named "plot_a_moving_function"
here.
For using the same in a threaded version, make sure that your ocaml is compiled with the
-pthreads option and get the example named
"plot_a_moving_function_w_threads" here.
© Stefano ’’Zack’’ Zacchiroli GNU FDL + GPL 2003