CoNNeTTivo
Ritorna al Sommario

Arte e Grafica

Arte In Movimento

Auto-aiuto informatico

CDA Anomalia

Comunicoscopio

CoNNeTTivo

Gr(A)fismo

Ocamlearn

Radar di Pace
Ocamlearn
Q&A: Plotting a moving function with Ocaml and Gtk
lunedì 10 marzo 2003





NELLA STESSA RUBRICA :
>7 marzo 2003
>5 marzo 2003
PAROLE CHIAVE ASSOCIATE :





ARTICOLI CON LO STESSO TEMA :


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:

  1. being added to the set of "current" points
  2. being accessed each time we need to redraw the graph window
  3. 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:

  1. "à la C" solution: use an Array as a circular buffer
  2. "à 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:

  1. have a scrolled window on which to plot latest say 5 points
  2. have an array "points" which keeps the latest 5 points, e.g.: points = [| 1; 12; 3; 32; 56 |]
  3. each time we have to replot we iter on the array and plot the points we find in
  4. 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

CoNNeTTivo   



© 2003 realizzato e concepito da ernestor + arlok + oSi + Z.
Questo sito usa PHP e mySQL ed è realizzato con software in licenza GNU/GPL.
Tutti i testi contenuti in questo sito sono COPYRIGHT dei loro autori.

fil XML