Extending GtkDrawingArea to draw animated graphs

Grapher class example Drawing graphs with GtkDrawingArea is very easy. This is even easier using Graph_Core.

Some details about Graph_Core

Standard event registration is performed in Graph_Core constructor. You don't need to redo it on your own. Some useful functions are ready for use :

  • color setting : Graph_Core::set_color()
  • graphic context : Graph_Core::gc()
  • colormap : Graph_Core::colormap()

To create some drawings, you need a graphic context and colors setting. All other drawing primitives are available as GdkDrawable methods.

Class usage

Making some rotating graphs like vu-meters is very easy with the Grapher class.

  • note 1 - create a Grapher class,
  • note 2 - data acquisition done in a timeout handler,
  • note 3 - this is a rotating graph, so translate it by one pixel
  • note 4 - generate some pseudo-data
  • note 5 - plot this data (in percentile or absolute units).
  • note 6 - refresh graph every 10 ms.
<?php
$grapher
= new Grapher();  // note 1
$grapher->set_size_request(300, 200);

$window = new GtkWindow();
$window->connect_simple('destroy', array('gtk', 'main_quit'));
$window->set_title('Graph demo');
$window->set_position(Gtk::WIN_POS_CENTER);
$window->set_border_width(8);
$window->add($grapher);
$window->show_all();

Gtk::timeout_add(100, 'graph_timeout', $grapher); // note 2
Gtk::main();

/**
  * an arbitrary function generator.
  *
  */
function  graph_timeout($graph) {
  static 
$value  0;
  static 
$delta  1;
  static 
$count  0;

  if (!
$graph->realized())
    return 
true;

  if (
$count  1)
   
$graph->translate();    //  note  3
 
$count++;

  if (
$value>100  ||  $value  0)
   
$delta  *=  -1;

 
$value  +=  $delta;

 
$sin  intval(100  sin($count  3.14  180)); // note 4
 
$cos  intval(100  cos($count  3.14  180));

 
$graph->plot(0+$value, 'blue',   $mode_percent=true);  // note 5
 
$graph->plot($sin,     'red'  $mode_percent=false);
 
$graph->plot($cos,     'green'$mode_percent=false);

 
Gtk::timeout_add(10, 'graph_timeout'$graph);        // note 6
 
return  false;
}
?>

Class source

This class is provided in two parts :

  • Graph_Core contains basic functions that may be reusable for other projects,
  • Grapher class contains basic functions to generate a rotating graph.

Class Graph_core

<?php
class Graph_Core extends GtkDrawingArea {
 
# prefix class attributes with "_" to avoid collisions
  # with GtkDrawingArea attributes.
 
protected $_pixmap;
  protected
$_gc;
  protected
$_realized;

  function
__construct() {
   
parent::__construct();

   
$this->connect('expose_event',
      array(
$this, 'expose_event'));
   
$this->connect('configure_event',
      array(
$this, 'configure_event'));
   
$this->connect('realize',
      array(
$this, 'realize'));
   
$this->set_events(Gdk::EXPOSURE_MASK
     
| Gdk::LEAVE_NOTIFY_MASK);

   
$this->_pixmap = null;
   
$this->_gc = null;
   
$this->_realized = false;
  }

  function
realize() {
   
$this->_realized = true;
  }

  function
realized() {
    return
$this->_realized;
  }

  function
configure_event($widget, $event) {
   
$w = $this->width();
   
$h = $this->height();

   
# allocate a new pixmap of the requested size ($this->allocation)
   
$this->_pixmap = new GdkPixmap($this->window, $w, $h, -1);

   
# clear pixmap (white color)
   
$this->_pixmap->draw_rectangle($this->style->white_gc, true,
     
0, 0, $w, $h);

    return
true;
  }

  function
expose_event($widget, $event) {
   
$this->window->draw_drawable($this->style->fg_gc[$this->state],
     
$this->pixmap(),
     
$event->area->x, $event->area->y,
     
$event->area->x, $event->area->y,
     
$event->area->width, $event->area->height
   
);
    return
false;
  }

  function
set_color($color) {
   
$col = $this->colormap()->alloc_color($color);
    if (
$col === false)
      return
$this->foreground('black');
    return
$this->gc()->set_foreground($col);
  }

  function
colormap() {
    return
$this->gc()->get_colormap();
  }

  function
pixmap() {
    if (!
$this->realized()) {
     
trigger_error("Widget not realized ; can't get pixmap");
      return
null;
    }
    return
$this->_pixmap;
  }

  function
gc() {
    if (!
$this->realized()) {
     
trigger_error("Widget not realized ; can't get GC");
      return
null;
    }
    if (
$this->_gc == null)
     
$this->_gc = new GdkGc($this->pixmap());
    return
$this->_gc;
  }

  function
width() {
    return
$this->allocation->width;
  }

  function
height() {
    return
$this->allocation->height;
  }

  function
w() {
    return
$this->width();
  }

  function
h() {
    return
$this->height();
  }
}
// file continues with class grapher
?>

class Grapher

<?php
// continuation of previous code piece

class Grapher extends Graph_Core{

/**
  *  plot a dot to the pixmap
  */
 
function plot ($value, $color = 'black', $mode_percent = false,
   
$x=null, $plot_w=1) {

   
$this->set_color($color);

    if (
$x == null)
     
$x = $this->w()-1;

    if (
$mode_percent) {
     
# $value is in percent
     
$value = $value * $this->h() / 100;
     
$y = intval($this->h() - $value);
    } else {
     
# 0 is in middle of window
     
$y = $this->h() / 2 - $value;
    }

    if (
$plot_w == 1) {
     
$this->pixmap()->draw_point($this->gc(), $x, $y);
     
$this->queue_draw_area($x, $y, 1, 1);
    } else {
     
$this->pixmap()->draw_arc($this->gc(), true,
       
$x - $plot_w, $y - $plot_w, $plot_w*2, $w*2,
       
0, 64 * 360);
     
$this->queue_draw_area($x - $w, $y - $plot_w,
       
$plot_w*2, $plot_w*2);
    }
  }

 
/**
   * scroll pixmap
   */
 
function translate($dx = -1, $dy = 0) {
   
$w = $this->w();
   
$h = $this->h();

   
# get full pixmap content into $img
   
$img = $this->pixmap()->get_image(0, 0, $w, $h);

   
# clear full area
   
$this->pixmap()->draw_rectangle($this->style->white_gc, true,
     
0, 0, $w, $h);

   
# draw $img with delta (translation)
   
$this->pixmap()->draw_image($this->gc(), $img, 0, 0,
     
$dx, $dy, $w, $h);
   
$this->queue_draw_area(0,0, $w, $h);
    }
  }
?>

Links

Fichier attachéTaille
Grapher.php_.txt4.72 Ko