Trieda na generovanie koláčového grafu pomocou knižnice GD.

<?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&#248;rn Borud, Guardian Networks AS, <borud@guardian.no>
**          Original author 1998/02/03
**
**          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&#248;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) {
#if( !($ftrack=fopen("/var/tmp/debug","a+"))) echo "not opened";
#fwrite($ftrack,"Start init()\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->im, 0xF5, 0xF5, 0xF5);
    $this->black = ImageColorAllocate($this->im,   0,   0,   0);
 
    $n = count($this->data);
    $this->add[0]=0;
 
    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->im, 0x33 ,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;
    }
 
    $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;
#      $to = $from + $this->angles[$i];
 
      $col = $this->colors[$i];
 
      $this->draw_slice($this->center_x,
                        $this->center_y,
                        $from,
                        $to,
                        $this->colors[$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);
#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->im, 0, $this->top, $this->width,
              $this->top,
              $this->black);
 
    ImageLine($this->im, 0, $this->height - $this->bottom,
              $this->width, $this->height - $this->bottom,
              $this->black);
 
    ImageLine($this->im, $this->left, 0, $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);
    $ax2 = round($x + ($axy2[0] * ($this->diameter /2)));
    $ay2 = round($y + ($axy2[1] * ($this->diameter /2)));
    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)));
    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,"ax2 : ".$ax2."\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);
    } 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(0, 5,6,7,8,9);
    $this->fy = array(0, 7,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->im, 0xF5, 0xF5, 0xF5);
    $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->im, 0x33 ,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->im, 0, $this->top, $this->width,
              $this->top,
              $this->black);
 
    ImageLine($this->im, 0, $this->height - $this->bottom,
              $this->width, $this->height - $this->bottom,
              $this->black);
 
    ImageLine($this->im, $this->left, 0, $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 ,0),
              array(100, "Second value", 0, 190, 0),
              array(100, "Third value", 0, 0 ,190),
              array(100, "Fourth value", 0, 190 ,190),
              array(301.2437, "Fifth value", 204, 0 ,204),
              array(308, "Sixth value", 204,204,0)
              );
 
/* For the pie chart uncomment this part */
$pie = new piechart;
$pie->init(300, 300, $vals);
$pie->display();
 
/* For the legend uncomment this part */
/*
$pie = new pielegend;
$pie->init(200, 300, $vals);
$pie->set_legend_percent();
$pie->display();
*/
 
 
/* }}} */
 
 
?>