PX : code

Pie Chart & Legend classes by Jean-Marc Libs
Download this code


<?php /* -*- C -*- */
/*
**
** PHP Class for creating pie charts using the GD library functions.
**
** There is a bug in the GD library somewhere that seems to kick in
** when you try to return images that are larger than 4K.  We probably
** need a workaround for this...
**
** Pie charts look a bit shabby.  There seems to be one or more
** roundoff errors lurking about making life hard for us.  To fix this
** we should perhaps investigate how the Arc-drawing thingey works and
** try to find out how it gets the endpoints.  Also the flood-filler
** doesn't quite cope with filling the pieces very well.
**
** Authors: Bjørn Borud, Guardian Networks AS, <borud@guardian.no>
**          Original author 1998/02/03
**          Version found at http://px.sklar.com/code-pretty.html?code_id=14
**
**          Jean-Marc Libs <libs@mail.cybercable.tm.fr>
**          Version 2.0 2000-09-14 
**          Converted gif generation to png generation
**          Broke app into separate images for piechart and legend
**          (dropped heading generation, as this can be displayed much
**          simpler in plain html)
**          Cleaned up all the rounding stuff, and corrected bugs
**          which produced spilling for some values.
**          Added some debugging stuff, which is commented out with
**          #-type comments
**          Remark: It wouldn't be difficult to merge the "all-in-one image"
**          design of version 1.0 with the debugged algorithms of version 2.0
**          but neither I nor Bjørn currently have the free time for that
**          
*/

dl("gd.so"); // This is necessary if you have the GD library as a PHP module.
//           If your PHP has been compiled with GD support, remove the line.

/* {{{ piechart */

/*
**  This is a class for creating pie charts.  Generally you just have
**  to instantiate it, and then make a call to the "init" method to
**  set the size and transfer the data.
**
**  The data is an array of arrays that consist the following data:
**    o numeric value
**    o value legend
**    o red  \
**    o green > the RGB values for the color of the slice/legend
**    o blue /
**
*/

class piechart {

/* {{{ attributes */

  
var $im;
  var 
$width$height;

  var 
$data;
  var 
$colors;
  var 
$angles;

  var 
$left=10;
  var 
$right=10;
  var 
$top=10;
  var 
$bottom=10;
  var 
$head_top=10;
  var 
$head_space=5;

  var 
$legend_left=20;

  var 
$center_x;
  var 
$center_y;
  var 
$diameter;

  
/* sum of values */
  
var $sum;

  
/* font sizes */
  
var $fx$fy;

  var 
$legend_num "";

/* }}} */
/* {{{ constants */

  
var $PI 3.1415926535897931;

/* }}} */
/* {{{ get_xy_factors */

  /*
  ** Calculate the directional vector for the sides of the
  ** piece of pie.
  */

  
function get_xy_factors ($degrees) {
    
$x cos(deg2rad($degrees));
    
$y sin(deg2rad($degrees));
    return (array(
$x$y));
  }

/* }}} */
/* {{{ init */

  /*
  ** Initialize the object and draw the pie.  This would be the
  ** constructor in an ordinary OO scenario -- just that we haven't
  ** got constructors in PHP, now do we? ;-)
  */

  
function init ($w$h$d) {
#global $ftrack;
#if( !($ftrack=fopen("/var/tmp/debug","a+"))) echo "not opened";
#echo "toto";
#fwrite($ftrack,"Start init()\n");
#fwrite($ftrack,date("Y-m-d H:i:s\n"));
    
$this->im    ImageCreate($w$h);
    
$this->width  $w;
    
$this->height $h;

    
$this->data $d;

    
$this->da_width = ($this->width $this->left $this->right);
    
$this->da_height = ($this->height $this->top $this->bottom);

    
$this->center_x intval($this->left + ($this->da_width 2));
    
$this->center_y intval($this->top  + ($this->da_height 2));


    
/* decide the diameter of the pie */

    
if ($this->da_height $this->da_width) {
      
$this->diameter $this->da_width;
    } else {
      
$this->diameter $this->da_height;
    }

    
/* a less agressive kind of white */
//    $this->white = ImageColorAllocate($this->im, 255, 255, 255);
    
$this->white ImageColorAllocate($this->im0xF50xF50xF5);
    
$this->black ImageColorAllocate($this->im,   0,   0,   0);

    
$n count($this->data);
    
$this->add[0]=0;

#fwrite($ftrack,"Il y a $n valeurs\n");
    
for ($i 0$i $n$i++) {
      if((!
$this->data[$i][2])&&(!$this->data[$i][3])&&(!$this->data[$i][4])) { // -> Default color
        
$this->colors[$i] = ImageColorAllocate($this->im0x33 ,0x33 0x33);
      } else {
        
$this->colors[$i] = ImageColorAllocate($this->im$this->data[$i][2],   $this->data[$i][3], $this->data[$i][4]);
      }

      
$this->sum += $this->data[$i][0];
      
$this->add[$i+1] = $this->sum;
#fwrite($ftrack,"Sum: ".$this->sum."\n");
    
}

    
$from 0;    $to 0;

    for (
$i 0$i $n$i++) {

      
$from round(($this->add[$i]*360)/doubleval($this->sum));
      
$to round(($this->add[$i+1]*360)/doubleval($this->sum));

      
$this->angles[$i] = $to-$from;
#      $this->angles[$i] = round(($this->data[$i][0] * 360) / doubleval($this->sum));
#      $to = $from + $this->angles[$i];

      
$col $this->colors[$i];

#if ($i==($n-1)) {
      
$this->draw_slice($this->center_x,
                        
$this->center_y,
                        
$from,
                        
$to,
                        
$this->colors[$i]);
#fwrite($ftrack,'faulty : $this->draw_slice('.$this->center_x.','.$this->center_y.','.$from.','.$to.','.$this->colors[$i].")\n");
#}
#      $from += $this->angles[$i];
#fwrite($ftrack,"from: $from to: $to\n");
    
}
    
ImageArc($this->im,$this->center_x,$this->center_y,$this->diameter,$this->diameter,0,360,$this->black);
#fwrite($ftrack,"Fin de l'init\n");
#fclose($ftrack);
  
}

/* }}} */
/* {{{ draw_point */

  /*
  ** This function is just here for debugging purposes.  It is
  ** sometimes very useful to be able to draw an X to check
  ** coordinates.
  */
  
function draw_point($x$y) {
    
ImageLine($this->im$x-4$y-4$x+4$y+4$this->black);
    
ImageLine($this->im$x-4$y+4$x+4$y-4$this->black);
  }

/* }}} */
/* {{{ draw_margins */

  /*
  ** Also a debugging function to show where the margins are at
  */
  
function draw_margins () {

    
ImageLine($this->im0$this->top$this->width,
              
$this->top,
              
$this->black);

    
ImageLine($this->im0$this->height $this->bottom,
              
$this->width$this->height $this->bottom,
              
$this->black);

    
ImageLine($this->im$this->left0$this->left,
              
$this->height$this->black);

    
ImageLine($this->im$this->width $this->right,
              
0$this->width $this->right,
              
$this->height$this->black);
  }

/* }}} */
/* {{{ draw_slice */

  /*
  ** This function draws a piece of pie centered at x,y starting at
  ** "from" degrees and ending at "to" degrees using the specified color.
  */
  
function draw_slice ($x$y$from$to$color) {

    
# Awful Kludge!!!
    
if ($to 360) {
      
$to 360;
    }
    
/* to start on top */
    
$from=$from+270;
    
$to=$to+270;

/* Arc */
    
ImageArc($this->im$x$y,
             
$this->diameter$this->diameter$from$to$color);

    
/* First line */
    
$axy2 $this->get_xy_factors($from);
#global $ftrack;
    
$ax2 round($x + ($axy2[0] * ($this->diameter /2)));
    
$ay2 round($y + ($axy2[1] * ($this->diameter /2)));
#fwrite($ftrack,"aX: ".$ax2." | aY: ".$ay2."\n");
    
ImageLine($this->im$x$y$ax2$ay2$color);

    
/* Second line */
    
$bxy2 $this->get_xy_factors($to);
    
$bx2 round($x + ($bxy2[0] * ($this->diameter /2)));
    
$by2 round($y + ($bxy2[1] * ($this->diameter /2)));
#fwrite($ftrack,"bX: ".$bx2." | bY: ".$by2."\n");
    
ImageLine($this->im$x$y$bx2$by2$color);

    
/* Decide if it's worth filling. a(ax2,ay2) and b(bx2,by2) should be at least 2 pixels apart. */
#fwrite($ftrack,"bx2 : ".$bx2."  ");
#fwrite($ftrack,"ax2 : ".$ax2."\n");
#fwrite($ftrack,"pow(bx2-ax2,2) : ".pow($bx2-$ax2,2)."\n");
#fwrite($ftrack,"pow(by2-ay2,2) : ".pow($by2-$ay2,2)."\n");
    /* decide where to start filling, then fill, or draw a middle line */
    
$xy2 $this->get_xy_factors((($to $from) / 2) + $from);
    if (((
pow(($bx2-$ax2),2)+pow(($by2-$ay2),2))>=2)||(($to-$from)>90)) {
      
/* fill */
      
$x2 round($x + ($xy2[0] * ($this->diameter /2.2)));
      
$y2 round($y + ($xy2[1] * ($this->diameter /2.2)));
      
ImageFillToBorder($this->im$x2$y2$color$color);
#$this->draw_point($x2, $y2);
    
} else {
      
/* middle line */
      
ImageLine($this->im,$x,$y,$x+($xy2[0] * ($this->diameter /2)),$y+($xy2[1] * ($this->diameter /2)),$color);
    }

  }

/* }}} */
/* {{{ display */

  /*
  ** Make sure the legends are drawn, then output the image to the
  ** client
  */
  
function display() {

    
Header("Content-type: image/png");
    
Header("Pragma: no-cache");
    
Imagepng($this->im);
  }

/* }}} */
}

class 
pielegend {
/* {{{ attributes */

  
var $im;
  var 
$width$height;

  var 
$data;
  var 
$colors;
  var 
$angles;

  var 
$left=10;
  var 
$right=200;
  var 
$top=50;
  var 
$bottom=10;
  var 
$head_top=10;
  var 
$head_space=5;

  var 
$legend_left=20;

  var 
$center_x;
  var 
$center_y;
  var 
$diameter;

  
/* sum of values */
  
var $sum;

  
/* font sizes */
  
var $fx$fy;

  var 
$legend_num "";

/* }}} */
/* {{{ constants */

  
var $PI 3.1415926535897931;

/* }}} */
/* {{{ get_xy_factors */

  /*
  ** Calculate the directional vector for the sides of the
  ** piece of pie.
  */

  
function get_xy_factors ($degrees) {
    
$x cos(deg2rad($degrees));
    
$y sin(deg2rad($degrees));
    return (array(
$x$y));
  }

/* }}} */
/* {{{ init */

  /*
  ** Initialize the object and draw the pie.  This would be the
  ** constructor in an ordinary OO scenario -- just that we haven't
  ** got constructors in PHP, now do we? ;-)
  */

  
function init ($w$h$d) {
    
$this->im    ImageCreate($w$h);
    
$this->width  $w;
    
$this->height $h;

    
$this->data $d;

    
$this->da_width = ($this->width $this->left $this->right);
    
$this->da_height = ($this->height $this->top $this->bottom);

    
$this->center_x intval($this->left + ($this->da_width 2));
    
$this->center_y intval($this->top  + ($this->da_height 2));


    
/* font sizes */
    
$this->fx = array(05,6,7,8,9);
    
$this->fy = array(07,8,10,14,11);

    
/* decide the diameter of the pie */

    
if ($this->da_height $this->da_width) {
      
$this->diameter $this->da_width;
    } else {
      
$this->diameter $this->da_height;
    }

//    $this->white = ImageColorAllocate($this->im, 255, 255, 255);
    
$this->white ImageColorAllocate($this->im0xF50xF50xF5);
    
$this->black ImageColorAllocate($this->im,   0,   0,   0);

    
$n count($this->data);
    for (
$i 0$i $n$i++) {
    if((!
$this->data[$i][2])&&(!$this->data[$i][3])&&(!$this->data[$i][4])) { // Not a country which color was decided on
           
$this->colors[$i] = ImageColorAllocate($this->im0x33 ,0x33 0x33);
    } else {
           
$this->colors[$i] = ImageColorAllocate($this->im$this->data[$i][2],   $this->data[$i][3], $this->data[$i][4]);
    }
      
$this->sum += $this->data[$i][0];
    }

    
$from 0;    $to 0;

  }

/* }}} */
/* {{{ set_legend_percent */

/* utility function to set an attribute so we display percentages */
  
function set_legend_percent () {
    
$this->legend_num "p";
  }

/* }}} */
/* {{{ set_legend_value */

  /* utility function to set an attribute so we display values */
  
function set_legend_value () {
    
$this->legend_num "v";
  }

/* }}} */
/* {{{ draw_point */

  /*
  ** This function is just here for debugging purposes.  It is
  ** sometimes very useful to be able to draw an X to check
  ** coordinates.
  */
  
function draw_point($x$y) {
    
ImageLine($this->im$x-4$y-4$x+4$y+4$this->black);
    
ImageLine($this->im$x-4$y+4$x+4$y-4$this->black);
  }

/* }}} */
/* {{{ draw_margins */

  /*
  ** Also a debugging function to show where the margins are at
  */
  
function draw_margins () {

    
ImageLine($this->im0$this->top$this->width,
              
$this->top,
              
$this->black);

    
ImageLine($this->im0$this->height $this->bottom,
              
$this->width$this->height $this->bottom,
              
$this->black);

    
ImageLine($this->im$this->left0$this->left,
              
$this->height$this->black);

    
ImageLine($this->im$this->width $this->right,
              
0$this->width $this->right,
              
$this->height$this->black);
  }

/* }}} */
/* {{{ draw_legends */

  /*
  ** Draw legends at the right side of the pie chart.  This function
  ** accepts a fontsize and gathers all the other information from
  ** the multilevel data array
  */
  
function draw_legends ($fontsize) {
    
$n count($this->data);

    
$x1 $this->width $this->right $this->legend_left;
    
$x2 $x1 $this->fy[$fontsize];;

    for (
$i 0$i $n$i++) {

      
/* determine Y coordinates */
      
$y1 = ($i $this->fy[$fontsize] * 1.5) + $this->top;
      
$y2 $y1 $this->fy[$fontsize];

      
/* draw the legend color rectangle */
      
ImageFilledRectangle($this->im$x1$y1$x2$y2$this->colors[$i]);
      
ImageRectangle($this->im$x1$y1$x2$y2$this->black);

      
$legend $this->data[$i][1];

      
/* decide what to show after legend */
      
switch ($this->legend_num) {
      case 
"v":
        
$legend .= sprintf(" (%.2f)"$this->data[$i][0]);
        break;
      case 
"p":
        
$legend .= sprintf(" (%.2f%%)",
                           (
$this->data[$i][0]
                            * 
100 doubleval($this->sum)));
        break;
      }

      
ImageString($this->im$fontsize$x2 5$y1,
                  
$legend$this->black);
    }
  }

/* }}} */
/* {{{ display */

  /*
  ** Make sure the legends are drawn, then output the image to the
  ** client
  */
  
function display() {

    
$this->draw_legends(2);

    
Imagepng($this->im"/tmp/pie.png");

    
Header("Content-type: image/png");
    
Imagepng($this->im);
  }

/* }}} */
}

/* {{{ Test code */

$vals = array(
              array(
100"First value"190,0),
              array(
100"Second value"01900),
              array(
100"Third value"0,190),
              array(
100"Fourth value"0190 ,190),
              array(
301.2437"Fifth value"204,204),
              array(
308"Sixth value"204,204,0)
              );

/* For the pie chart uncomment this part */
$pie = new piechart;
$pie->init(300300$vals);
$pie->display();

/* For the legend uncomment this part */
/*
$pie = new pielegend;
$pie->init(200, 300, $vals);
$pie->set_legend_percent();
$pie->display();
*/


/* }}} */


?>


Comments or questions?
PX is running PHP 5.2.17
Thanks to Miranda Productions for hosting and bandwidth.
Use of any code from PX is at your own risk.