* @license BSD */ namespace Domframework; /** This class allow to generate an image which is a graphic. A graphic takes * an array of values and draw the lines/histo... like a spreadsheet * graph methods are : * ->height ($height) or ->width ($width) The heigh/width of the graph */ class Graph { /** The X axis object */ public $axisX = null; /** The main Y axis object */ public $axisY1 = null; /** The optional secondary Y axis object */ public $axisY2 = null; /** The legend object */ public $legend = null; /** The graph title object */ public $title = null; /** The height of the graph (150px by default) */ private $height = 200; /** The width of the graph (200px by default) */ private $width = 300; /** The background color of the graph (white by default) */ private $bgcolor = "whitesmoke"; /** The graph style by default. Each serie can define another value */ private $style = null; /** The data object */ public $data = null; /** The series object */ public $series = null; /** Constructor : create the objects */ public function __construct() { if (! function_exists("imagecreatetruecolor")) { throw new \Exception(dgettext( "domframework", "No GD support in PHP : can't create image" ), 500); } if (! function_exists("bccomp")) { throw new \Exception(dgettext( "domframework", "No BCMath support in PHP : can't create image" ), 500); } $this->title = new GraphTitle(); $this->legend = new GraphLegend(); $this->data = new GraphData(); $this->series = new GraphSeries(); $this->axisX = new GraphAxisX(); $this->axisY1 = new GraphAxisY1(); $this->axisY2 = new GraphAxisY2(); // Default values $defaultTitleFontFile = "/usr/share/fonts/truetype/liberation/" . "LiberationSans-Bold.ttf"; $defaultFontFile = "/usr/share/fonts/truetype/liberation/" . "LiberationSans-Regular.ttf"; $this->title->fontfile($defaultTitleFontFile); $this->legend->fontfile($defaultFontFile); $this->axisX->fontfile($defaultFontFile); $this->axisY1->fontfile($defaultFontFile); $this->axisY2->fontfile($defaultFontFile); $this->axisX->axisColor("grey"); $this->axisY1->axisColor("grey"); $this->axisY1->gridColor("grey"); $this->axisY2->axisColor("grey"); $this->style("line"); $this->style()->palette("basic"); } /** Set the title position of the graph if the parameter is provided. * Get the title position of the graph if the parameter is not provided * @param string|null $titlePosition The title position of the graph */ public function titlePosition($titlePosition = null) { if ($titlePosition === null) { return $this->titlePosition; } if ( ! is_string($titlePosition) || ($titlePosition !== "top" && $titlePosition !== "bottom") ) { throw new \Exception(dgettext( "domframework", "Invalid titlePosition provided to graph" ), 406); } $this->titlePosition = $titlePosition; return $this; } /** Set the height of the graph if the parameter is provided. * Get the height of the graph if the parameter is not provided * @param integer|null $height The height of the graph */ public function height($height = null) { if ($height === null) { return $this->height; } if (! is_integer($height) || $height < 0 || $height > 3000) { throw new \Exception(dgettext( "domframework", "Invalid height provided to graph" ), 406); } $this->height = $height; return $this; } /** Set the background-color of the graph if the parameter is provided. * Get the background-color of the graph if the parameter is not provided * @param string|null $bgcolor The background-color of the graph */ public function bgcolor($bgcolor = null) { if ($bgcolor === null) { return $this->bgcolor; } if ( ! is_string($bgcolor) || ! in_array($bgcolor, Color::colorList()) ) { throw new \Exception(dgettext( "domframework", "Invalid bgcolor provided to graph" ), 406); } $this->bgcolor = $bgcolor; return $this; } /** Set the width of the graph if the parameter is provided. * Get the width of the graph if the parameter is not provided * @param integer|null $width The width of the graph */ public function width($width = null) { if ($width === null) { return $this->width; } if (! is_integer($width) || $width < 0 || $width > 3000) { throw new \Exception(dgettext( "domframework", "Invalid width provided to graph" ), 406); } $this->width = $width; return $this; } /** Set the default style of the graph if the parameter is provided. * Get the default style of the graph if the parameter is not provided * @param string|null $style The style of the graph */ public function style($style = null) { if ($style === null) { return $this->style; } if ( ! is_string($style) || ! in_array($style, array("line", "points", "linePoints")) ) { throw new \Exception(dgettext( "domframework", "Invalid style provided to graph" ), 406); } $styleClass = __NAMESPACE__ . "\\GraphStyle" . ucfirst($style); if ($this->style === null || $this->style()->name() !== $style) { $this->style = new $styleClass(); $this->style()->palette("basic"); } return $this->style; } /** Draw the graph to the screen with the previous defined parameters */ private function drawReal() { // Read the data $series = $this->data->getSeries(); foreach ($this->series->getList() as $serie) { // Remove the previous defined series which doesn't exists in the data if (! array_key_exists($serie, $series)) { $this->series->remove($serie); } } // Look for the min/max of the axis and use the data maximum if the user // doesn't define it previously. The min/max are defined on ALL the series // can not be defined by the axis directely $minValueX = null; $maxValueX = null; $minValueY1 = null; $maxValueY1 = null; $minValueY2 = null; $maxValueY2 = null; foreach ($series as $serie => $data) { // Add the data to the series or create them if they doesn't exists $this->series->serie($serie)->data($data); // Look for min/max and set the data for X axis $this->axisX->data(array_keys($data)); if ($minValueX === null) { $minValueX = $this->series->serie($serie)->minKey(); } $minValueX = min( $minValueX, $this->series->serie($serie)->minKey() ); if ($maxValueX === null) { $maxValueX = $this->series->serie($serie)->maxKey(); } $maxValueX = max( $maxValueX, $this->series->serie($serie)->maxKey() ); if (! $this->series->serie($serie)->axisYsecondary()) { // Look for min/max for Y1 axis if ($minValueY1 === null) { $minValueY1 = $this->series->serie($serie)->minValue(); } $minValueY1 = min( $minValueY1, $this->series->serie($serie)->minValue() ); $maxValueY1 = max( $maxValueY1, $this->series->serie($serie)->maxValue() ); } else { // Look for min/max for Y2 axis if ($minValueY2 === null) { $minValueY2 = $this->series->serie($serie)->minValue(); } $minValueY2 = min( $minValueY2, $this->series->serie($serie)->minValue() ); $maxValueY2 = max( $maxValueY2, $this->series->serie($serie)->maxValue() ); } } // Force the graph to display the 0 value //if ($minValueY1 > 0 && $maxValueY1 > 0) // $minValueY1 = 0; //if ($minValueY1 < 0 && $maxValueY1 < 0) // $minValueY1 = 0; //if ($minValueY2 > 0 && $maxValueY2 > 0) // $minValueY2 = 0; //if ($minValueY2 < 0 && $maxValueY2 < 0) // $minValueY2 = 0; // Look for numeric or labeled axis $numericalX = null; $numericalY1 = null; $numericalY2 = null; foreach ($series as $serie => $data) { $numericalKey = $this->series->serie($serie)->numericalKey(); if ($numericalX === null || $numericalX === true) { if ($numericalKey === false) { $numericalX = false; } else { $numericalX = true; } } $numericalValue = $this->series->serie($serie)->numericalValue(); if (! $this->series->serie($serie)->axisYsecondary()) { if ($numericalY1 === null || $numericalY1 === true) { if ($numericalValue === false) { $numericalY1 = false; } else { $numericalY1 = true; } } } else { if ($numericalY2 === null || $numericalY2 === true) { if ($numericalValue === false) { $numericalY2 = false; } else { $numericalY2 = true; } } } } $this->axisX->numerical($numericalX); $this->axisY1->numerical($numericalY1); $this->axisY2->numerical($numericalY2); if ($minValueX !== null && $this->axisX->min() === null) { $this->axisX->min($minValueX); } if ($maxValueX !== null && $this->axisX->max() === null) { $this->axisX->max($maxValueX); } if ($minValueY1 !== null && $this->axisY1->min() === null) { $this->axisY1->min($minValueY1); } if ($maxValueY1 !== null && $this->axisY1->max() === null) { $this->axisY1->max($maxValueY1); } if ($minValueY2 !== null && $this->axisY2->min() === null) { $this->axisY2->min($minValueY2); } if ($maxValueY2 !== null && $this->axisY2->max() === null) { $this->axisY2->max($maxValueY2); } // Manage the styles for each serie foreach ($this->series->getList() as $number => $serie) { if ($this->series->serie($serie)->style() === null) { $this->series->serie($serie)->style($this->style); } if ($this->series->serie($serie)->style()->palette() === null) { $this->series->serie($serie)->style()->palette( $this->style->palette() ); } $this->series->serie($serie)->style()->number($number); } // Create the image $gd = imagecreatetruecolor($this->width, $this->height); // Put the background color imagefilledrectangle( $gd, 0, 0, $this->width - 1, $this->height - 1, Color::allocateFromText($gd, $this->bgcolor) ); // The coordinates of the free space. Will be modified each time something // is drawing on the graph // xtop, ytop, xbottom, ybottom $free = array(0, 0, imagesx($gd), imagesy($gd)); // Add the title on top $free = $this->title->draw($gd, $free); // Add the legend on right $free = $this->legend->draw($gd, $free, $this->series); // If there is no title, add an offset of 10px to display correctely the // first label of the Y axis if ($free[1] === 0) { $free[1] = 10; } // Add the axis // Need two passes as the X axis can modify the Y axes $this->axisX->top($free[1]); $this->axisX->bottom($free[3] - 1); $this->axisY1->top($free[1]); $this->axisY2->top($free[1]); $this->axisY1->bottom($free[3] - 1); $this->axisY2->bottom($free[3] - 1); $Xleft = $this->axisY1->getWidth($gd); $Xright = $free[2] - $this->axisY2->getWidth($gd); $this->axisX->left($Xleft); $this->axisX->right($Xright); $Ybottom = $this->height - $this->axisX->getHeight($gd); $this->axisY1->right($Xright); $this->axisY1->bottom($Ybottom); //$this->axisY1->left (???); $this->axisY2->right($Xleft); $this->axisY2->bottom($Ybottom); $this->axisY2->left($Xright); // Draw the axis $this->axisX->draw($gd); $this->axisY1->draw($gd); $this->axisY2->draw($gd); // Add the graph part for each serie $lastFree = $free; foreach ($this->series->getList() as $number => $serie) { // As the series are superposed, do not update the $free each time if (! $this->series->serie($serie)->axisYsecondary()) { $lastFree = $this->series->serie($serie)->draw( $gd, $free, $this->axisX, $this->axisY1 ); } else { $lastFree = $this->series->serie($serie)->draw( $gd, $free, $this->axisX, $this->axisY2 ); } } $free = $lastFree; imagepng($gd); imagedestroy($gd); } /** Draw the graph to the screen with the previous defined parameters */ public function drawImage() { header('Content-Type: image/png'); $this->drawReal(); } /** Return the image coded in base64 * @return string The base64 string */ public function drawBase64() { ob_start(); $this->drawReal(); return base64_encode(ob_get_clean()); } }