453 lines
15 KiB
PHP
453 lines
15 KiB
PHP
<?php
|
|
|
|
/** DomFramework
|
|
* @package domframework
|
|
* @author Dominique Fournier <dominique@fournier38.fr>
|
|
* @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());
|
|
}
|
|
}
|