* @copyright Copyright (C) 2003, 2004 Jesper Veggerby Hansen * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @version CVS: $Id$ * @link http://pear.php.net/package/Image_Graph */ /** * Include file Image/Graph/Plot.php */ require_once 'Image/Graph/Plot.php'; /** * 2D Piechart. * * @category Images * @package Image_Graph * @subpackage Plot * @author Jesper Veggerby * @copyright Copyright (C) 2003, 2004 Jesper Veggerby Hansen * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @version Release: @package_version@ * @link http://pear.php.net/package/Image_Graph */ class Image_Graph_Plot_Pie extends Image_Graph_Plot { /** * The radius of the 'pie' spacing * @access private * @var int */ var $_radius = 3; /** * Explode pie slices. * @access private * @var mixed */ var $_explode = false; /** * The starting angle of the plot * @access private * @var int */ var $_startingAngle = 0; /** * The angle direction (1 = CCW, -1 = CW) * @access private * @var int */ var $_angleDirection = 1; /** * The diameter of the pie plot * @access private * @var int */ var $_diameter = false; /** * Group items below this limit together as "the rest" * @access private * @var double */ var $_restGroupLimit = false; /** * Rest group title * @access private * @var string */ var $_restGroupTitle = 'The rest'; /** * Perform the actual drawing on the legend. * * @param int $x0 The top-left x-coordinate * @param int $y0 The top-left y-coordinate * @param int $x1 The bottom-right x-coordinate * @param int $y1 The bottom-right y-coordinate * @access private */ function _drawLegendSample($x0, $y0, $x1, $y1) { $y = ($y0 + $y1) / 2; $this->_canvas->pieslice( array( 'x' => $x1, 'y' => $y, 'rx' => abs($x1 - $x0) / 2, 'ry' => abs($y1 - $y0) / 2, 'v1' => 45, 'v2' => 315 ) ); } /** * Calculate marker point data * * @param array $point The point to calculate data for * @param array $nextPoint The next point * @param array $prevPoint The previous point * @param array $totals The pre-calculated totals, if needed * @return array An array containing marker point data * @access private */ function _getMarkerData($point, $nextPoint, $prevPoint, &$totals) { $point = parent::_getMarkerData($point, $nextPoint, $prevPoint, $totals); $y = $totals['CURRENT_Y']; if ($this->_angleDirection < 0) { $y = $totals['ALL_SUM_Y'] - $y; } $point['ANGLE'] = 360 * (($y + ($point['Y'] / 2)) / $totals['ALL_SUM_Y']) + $this->_startingAngle; $totals['CURRENT_Y'] += $point['Y']; $point['ANG_X'] = cos(deg2rad($point['ANGLE'])); $point['ANG_Y'] = sin(deg2rad($point['ANGLE'])); $point['AX'] = -10 * $point['ANG_X']; $point['AY'] = -10 * $point['ANG_Y']; if ((isset($totals['ALL_SUM_Y'])) && ($totals['ALL_SUM_Y'] != 0)) { $point['PCT_MIN_Y'] = $point['PCT_MAX_Y'] = (100 * $point['Y'] / $totals['ALL_SUM_Y']); } $point['LENGTH'] = 10; //$radius; $x = $point['X']; $explodeRadius = 0; if ((is_array($this->_explode)) && (isset($this->_explode[$x]))) { $explodeRadius = $this->_explode[$x]; } elseif (is_numeric($this->_explode)) { $explodeRadius = $this->_explode; } $point['MARKER_X'] = $totals['CENTER_X'] + ($totals['RADIUS'] + $explodeRadius) * $point['ANG_X']; $point['MARKER_Y'] = $totals['CENTER_Y'] + ($totals['RADIUS'] + $explodeRadius) * $point['ANG_Y']; return $point; } /** * Draws markers on the canvas * * @access private */ function _drawMarker() { if ($this->_marker) { $totals = $this->_getTotals(); $totals['CENTER_X'] = (int) (($this->_left + $this->_right) / 2); $totals['CENTER_Y'] = (int) (($this->_top + $this->_bottom) / 2); $totals['CURRENT_Y'] = 0; $number = 0; $diameter = $this->_getDiameter(); $keys = array_keys($this->_dataset); foreach ($keys as $key) { $dataset =& $this->_dataset[$key]; if (count($this->_dataset) == 1) { $totals['RADIUS0'] = false; $totals['RADIUS'] = $diameter / 2; } else { $dr = $diameter / (2 * count($this->_dataset)); $totals['RADIUS0'] = $number * $dr + ($number > 0 ? $this->_radius : 0); $totals['RADIUS'] = ($number + 1) * $dr; } $totals['ALL_SUM_Y'] = 0; $totals['CURRENT_Y'] = 0; $dataset->_reset(); while ($point = $dataset->_next()) { $totals['ALL_SUM_Y'] += $point['Y']; } $dataset->_reset(); $currentY = 0; $the_rest = 0; while ($point = $dataset->_next()) { if (($this->_restGroupLimit !== false) && ($point['Y'] <= $this->_restGroupLimit)) { $the_rest += $point['Y']; } else { if ((!is_object($this->_dataSelector)) || ($this->_dataSelector->select($point)) ) { $point = $this->_getMarkerData( $point, false, false, $totals ); if (is_array($point)) { $this->_marker->_drawMarker( $point['MARKER_X'], $point['MARKER_Y'], $point ); } } } } if ($the_rest > 0) { $point = array('X' => $this->_restGroupTitle, 'Y' => $the_rest); $point = $this->_getMarkerData( $point, false, false, $totals ); if (is_array($point)) { $this->_marker->_drawMarker( $point['MARKER_X'], $point['MARKER_Y'], $point ); } } $number++; } unset($keys); } } /** * Explodes a piece of this pie chart * * @param int $explode Radius to explode with (or an array) * @param string $x The 'x' value to explode or omitted */ function explode($explode, $x = false) { if ($x === false) { $this->_explode = $explode; } else { $this->_explode[$x] = $explode; } } /** * Set the starting angle of the plot * * East is 0 degrees * South is 90 degrees * West is 180 degrees * North is 270 degrees * * It is also possible to specify the direction of the plot angles (i.e. clockwise 'cw' or * counterclockwise 'ccw') */ function setStartingAngle($angle = 0, $direction = 'ccw') { $this->_startingAngle = ($angle % 360); $this->_angleDirection = ($direction == 'ccw' ? 1 : -1); } /** * Set the diameter of the pie plot (i.e. the number of pixels the * pie plot should be across) * * Use 'max' for the maximum possible diameter * * Use negative values for the maximum possible - minus this value (fx -2 * to leave 1 pixel at each side) * * @param mixed @diameter The number of pixels */ function setDiameter($diameter) { $this->_diameter = $diameter; } /** * Set the limit for the y-value, where values below are grouped together * as "the rest" * * @param double $limit The limit * @param string $title The title to display in the legends (default 'The * rest') */ function setRestGroup($limit, $title = 'The rest') { $this->_restGroupLimit = $limit; $this->_restGroupTitle = $title; } /** * Get the diameter of the plot * @return int The number of pixels the diameter is * @access private */ function _getDiameter() { $diameter = 0; if ($this->_diameter === false) { $diameter = min($this->height(), $this->width()) * 0.75; } else { if ($this->_diameter === 'max') { $diameter = min($this->height(), $this->width()); } elseif ($this->_diameter < 0) { $diameter = min($this->height(), $this->width()) + $this->_diameter; } else { $diameter = $this->_diameter; } } return $diameter; } /** * Output the plot * * @return bool Was the output 'good' (true) or 'bad' (false). * @access private */ function _done() { if (parent::_done() === false) { return false; } $number = 0; $keys = array_keys($this->_dataset); foreach ($keys as $key) { $dataset =& $this->_dataset[$key]; $totalY = 0; $dataset->_reset(); while ($point = $dataset->_next()) { $totalY += $point['Y']; } $centerX = (int) (($this->_left + $this->_right) / 2); $centerY = (int) (($this->_top + $this->_bottom) / 2); $diameter = $this->_getDiameter(); if ($this->_angleDirection < 0) { $currentY = $totalY; } else { $currentY = 0; //rand(0, 100)*$totalY/100; } $dataset->_reset(); if (count($this->_dataset) == 1) { $radius0 = false; $radius1 = $diameter / 2; } else { $dr = $diameter / (2 * count($this->_dataset)); $radius0 = $number * $dr + ($number > 0 ? $this->_radius : 0); $radius1 = ($number + 1) * $dr; } $the_rest = 0; while ($point = $dataset->_next()) { if (($this->_restGroupLimit !== false) && ($point['Y'] <= $this->_restGroupLimit)) { $the_rest += $point['Y']; } else { $angle1 = 360 * ($currentY / $totalY) + $this->_startingAngle; $currentY += $this->_angleDirection * $point['Y']; $angle2 = 360 * ($currentY / $totalY) + $this->_startingAngle; $x = $point['X']; $id = $point['ID']; $dX = 0; $dY = 0; $explodeRadius = 0; if ((is_array($this->_explode)) && (isset($this->_explode[$x]))) { $explodeRadius = $this->_explode[$x]; } elseif (is_numeric($this->_explode)) { $explodeRadius = $this->_explode; } if ($explodeRadius > 0) { $dX = $explodeRadius * cos(deg2rad(($angle1 + $angle2) / 2)); $dY = $explodeRadius * sin(deg2rad(($angle1 + $angle2) / 2)); } $ID = $point['ID']; $this->_getFillStyle($ID); $this->_getLineStyle($ID); $this->_canvas->pieslice( $this->_mergeData( $point, array( 'x' => $centerX + $dX, 'y' => $centerY + $dY, 'rx' => $radius1, 'ry' => $radius1, 'v1' => $angle1, 'v2' => $angle2, 'srx' => $radius0, 'sry' => $radius0 ) ) ); } } if ($the_rest > 0) { $angle1 = 360 * ($currentY / $totalY) + $this->_startingAngle; $currentY += $this->_angleDirection * $the_rest; $angle2 = 360 * ($currentY / $totalY) + $this->_startingAngle; $x = 'rest'; $id = 'rest'; $dX = 0; $dY = 0; $explodeRadius = 0; if ((is_array($this->_explode)) && (isset($this->_explode[$x]))) { $explodeRadius = $this->_explode[$x]; } elseif (is_numeric($this->_explode)) { $explodeRadius = $this->_explode; } if ($explodeRadius > 0) { $dX = $explodeRadius * cos(deg2rad(($angle1 + $angle2) / 2)); $dY = $explodeRadius * sin(deg2rad(($angle1 + $angle2) / 2)); } $ID = $id; $this->_getFillStyle($ID); $this->_getLineStyle($ID); $this->_canvas->pieslice( $this->_mergeData( $point, array( 'x' => $centerX + $dX, 'y' => $centerY + $dY, 'rx' => $radius1, 'ry' => $radius1, 'v1' => $angle1, 'v2' => $angle2, 'srx' => $radius0, 'sry' => $radius0 ) ) ); } $number++; } unset($keys); $this->_drawMarker(); return true; } /** * Draw a sample for use with legend * * @param array $param The parameters for the legend * @access private */ function _legendSample(&$param) { if (is_array($this->_dataset)) { $this->_canvas->startGroup(get_class($this) . '_' . $this->_title); $this->_clip(true); $totals = $this->_getTotals(); $totals['CENTER_X'] = (int) (($this->_left + $this->_right) / 2); $totals['CENTER_Y'] = (int) (($this->_top + $this->_bottom) / 2); $totals['RADIUS'] = min($this->height(), $this->width()) * 0.75 * 0.5; $totals['CURRENT_Y'] = 0; if (is_a($this->_fillStyle, "Image_Graph_Fill")) { $this->_fillStyle->_reset(); } $count = 0; $keys = array_keys($this->_dataset); foreach ($keys as $key) { $dataset =& $this->_dataset[$key]; $count++; $dataset->_reset(); $the_rest = 0; while ($point = $dataset->_next()) { $caption = $point['X']; if (($this->_restGroupLimit !== false) && ($point['Y'] <= $this->_restGroupLimit)) { $the_rest += $point['Y']; } else { $this->_canvas->setFont($param['font']); $width = 20 + $param['width'] + $this->_canvas->textWidth($caption); $param['maxwidth'] = max($param['maxwidth'], $width); $x2 = $param['x'] + $width; $y2 = $param['y'] + $param['height']+5; if ((($param['align'] & IMAGE_GRAPH_ALIGN_VERTICAL) != 0) && ($y2 > $param['bottom'])) { $param['y'] = $param['top']; $param['x'] = $x2; $y2 = $param['y'] + $param['height']; } elseif ((($param['align'] & IMAGE_GRAPH_ALIGN_VERTICAL) == 0) && ($x2 > $param['right'])) { $param['x'] = $param['left']; $param['y'] = $y2; $x2 = $param['x'] + 20 + $param['width'] + $this->_canvas->textWidth($caption); } $x = $x0 = $param['x']; $y = $param['y']; $y0 = $param['y'] - $param['height']/2; $x1 = $param['x'] + $param['width']; $y1 = $param['y'] + $param['height']/2; if (!isset($param['simulate'])) { $this->_getFillStyle($point['ID']); $this->_getLineStyle($point['ID']); $this->_drawLegendSample($x0, $y0, $x1, $y1); if (($this->_marker) && ($dataset) && ($param['show_marker'])) { $prevPoint = $dataset->_nearby(-2); $nextPoint = $dataset->_nearby(); $p = $this->_getMarkerData($point, $nextPoint, $prevPoint, $totals); if (is_array($point)) { $p['MARKER_X'] = $x+$param['width']/2; $p['MARKER_Y'] = $y; unset ($p['AVERAGE_Y']); $this->_marker->_drawMarker($p['MARKER_X'], $p['MARKER_Y'], $p); } } $this->write($x + $param['width'] +10, $y, $caption, IMAGE_GRAPH_ALIGN_CENTER_Y | IMAGE_GRAPH_ALIGN_LEFT, $param['font']); } if (($param['align'] & IMAGE_GRAPH_ALIGN_VERTICAL) != 0) { $param['y'] = $y2; } else { $param['x'] = $x2; } } } if ($the_rest > 0) { $this->_canvas->setFont($param['font']); $width = 20 + $param['width'] + $this->_canvas->textWidth($this->_restGroupTitle); $param['maxwidth'] = max($param['maxwidth'], $width); $x2 = $param['x'] + $width; $y2 = $param['y'] + $param['height']+5; if ((($param['align'] & IMAGE_GRAPH_ALIGN_VERTICAL) != 0) && ($y2 > $param['bottom'])) { $param['y'] = $param['top']; $param['x'] = $x2; $y2 = $param['y'] + $param['height']; } elseif ((($param['align'] & IMAGE_GRAPH_ALIGN_VERTICAL) == 0) && ($x2 > $param['right'])) { $param['x'] = $param['left']; $param['y'] = $y2; $x2 = $param['x'] + 20 + $param['width'] + $this->_canvas->textWidth($this->_restGroupTitle); } $x = $x0 = $param['x']; $y = $param['y']; $y0 = $param['y'] - $param['height']/2; $x1 = $param['x'] + $param['width']; $y1 = $param['y'] + $param['height']/2; if (!isset($param['simulate'])) { $this->_getFillStyle('rest'); $this->_getLineStyle('rest'); $this->_drawLegendSample($x0, $y0, $x1, $y1); $this->write($x + $param['width'] + 10, $y, $this->_restGroupTitle, IMAGE_GRAPH_ALIGN_CENTER_Y | IMAGE_GRAPH_ALIGN_LEFT, $param['font']); } if (($param['align'] & IMAGE_GRAPH_ALIGN_VERTICAL) != 0) { $param['y'] = $y2; } else { $param['x'] = $x2; } } } unset($keys); $this->_clip(false); $this->_canvas->endGroup(); } } } ?>