git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@3361 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
2600 lines
81 KiB
PHP
2600 lines
81 KiB
PHP
<?php
|
|
/** DomFramework
|
|
@package domframework
|
|
@author Dominique Fournier <dominique@fournier38.fr> */
|
|
|
|
require_once ("domframework/color.php");
|
|
|
|
/** 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);
|
|
$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 = "graphStyle".$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());
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The series objects */
|
|
class graphSeries
|
|
/* {{{ */
|
|
{
|
|
/** The series stored */
|
|
private $series = array ();
|
|
|
|
/** Return the serie object choosed. If doesn't exists, it is created before
|
|
* be returned
|
|
* @param string $name The name of the serie to create
|
|
*/
|
|
public function serie ($name)
|
|
{
|
|
if (is_integer ($name))
|
|
$name = dgettext ("domframework", "Serie")." $name";
|
|
if (! is_string ($name))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Can't get a serie if the name is not a string"),
|
|
406);
|
|
if (! array_key_exists ($name, $this->series))
|
|
$this->series[$name] = new graphSerie ($name);
|
|
return $this->series[$name];
|
|
}
|
|
|
|
/** Get the list of the defined series
|
|
*/
|
|
public function getList ()
|
|
{
|
|
return array_keys ($this->series);
|
|
}
|
|
|
|
/** Remove an existing serie
|
|
* @param string $name The name of the serie to remove
|
|
*/
|
|
public function remove ($name)
|
|
{
|
|
if (! is_string ($name))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Can't remove a serie if the name is not a string"),
|
|
406);
|
|
if (array_key_exists ($name, $this->series))
|
|
unset ($this->series[$name]);
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The serie object */
|
|
class graphSerie
|
|
/* {{{ */
|
|
{
|
|
/** The name of the serie
|
|
*/
|
|
private $name;
|
|
|
|
/** The data values for the serie
|
|
*/
|
|
private $data;
|
|
|
|
/** The numericalKey is true if all the keys are numeric
|
|
*/
|
|
private $numericalKey =null;
|
|
|
|
/** The minimum key of the serie
|
|
*/
|
|
private $minKey = null;
|
|
|
|
/** The maximum key of the serie
|
|
*/
|
|
private $maxKey = null;
|
|
|
|
/** The numericalValue is true if all the values are numeric
|
|
*/
|
|
private $numericalValue =null;
|
|
|
|
/** The minimum value of the serie
|
|
*/
|
|
private $minValue = null;
|
|
|
|
/** The maximum value of the serie
|
|
*/
|
|
private $maxValue = null;
|
|
|
|
/** The style object for the serie
|
|
*/
|
|
private $style;
|
|
|
|
/** The axis used to draw the serie.
|
|
* If false for main Y axis
|
|
* If true for secondary Y axis
|
|
*/
|
|
private $axisYsecondary = false;
|
|
|
|
/** When creating the serie, save the name
|
|
* @param string $name The name of the serie
|
|
*/
|
|
public function __construct ($name)
|
|
{
|
|
if (! is_string ($name))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Can't create a serie if the name is not a string"),
|
|
406);
|
|
$this->name = $name;
|
|
}
|
|
|
|
/** Set the data for the serie
|
|
If the parameter is not provided, return the actual $data value
|
|
* @param array|null $data The data to store in the serie
|
|
*/
|
|
public function data ($data = null)
|
|
{
|
|
if ($data === null)
|
|
return $this->data;
|
|
if (! is_array ($data))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Can't create a serie data if the value is not an array"),
|
|
406);
|
|
$this->data = $data;
|
|
$this->minmax ();
|
|
return $this;
|
|
}
|
|
|
|
/** Get the minimum and maximum values and keys
|
|
*/
|
|
private function minmax ()
|
|
{
|
|
if ($this->data === null)
|
|
return null;
|
|
foreach ($this->data as $key=>$value)
|
|
{
|
|
if ($this->minValue === null && is_numeric ($value))
|
|
$this->minValue = $value;
|
|
if ($this->minKey === null && is_numeric ($key))
|
|
$this->minKey = $key;
|
|
if (is_numeric ($value))
|
|
{
|
|
$this->minValue = min ($this->minValue, $value);
|
|
$this->maxValue = max ($this->maxValue, $value);
|
|
}
|
|
if (is_numeric ($key))
|
|
{
|
|
$this->minKey = min ($this->minKey, $key);
|
|
$this->maxKey = max ($this->maxKey, $key);
|
|
}
|
|
if (! is_numeric ($key) && $this->numericalKey === null)
|
|
$this->numericalKey = false;
|
|
if (! is_numeric ($value) && $value !== null &&
|
|
$this->numericalValue === null)
|
|
$this->numericalValue = false;
|
|
}
|
|
if ($this->numericalKey === null)
|
|
$this->numericalKey = true;
|
|
if ($this->numericalValue === null)
|
|
$this->numericalValue = true;
|
|
}
|
|
|
|
/** Set/get the numeric value of the serie
|
|
* If the parameter is not provided, return the actual state
|
|
* @param boolean|null The state of the numeric value
|
|
*/
|
|
public function numericalValue ($numericalValue = null)
|
|
{
|
|
if ($numericalValue === null)
|
|
return $this->numericalValue;
|
|
if (! is_bool ($numericalValue))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid numericalValue provided to serie"), 406);
|
|
$this->numericalValue = $numericalValue;
|
|
return $this;
|
|
}
|
|
|
|
/** The minimum value of the serie
|
|
*/
|
|
public function minValue ()
|
|
{
|
|
if ($this->minValue === null && $this->data !== null)
|
|
$this->minmax ();
|
|
return $this->minValue;
|
|
}
|
|
|
|
/** The maximum value of the serie
|
|
*/
|
|
public function maxValue ()
|
|
{
|
|
if ($this->maxValue === null && $this->data !== null)
|
|
$this->minmax ();
|
|
return $this->maxValue;
|
|
}
|
|
|
|
/** Set/get the numeric key of the serie
|
|
* If the parameter is not provided, return the actual state
|
|
* @param boolean|null The state of the numeric key
|
|
*/
|
|
public function numericalKey ($numericalKey = null)
|
|
{
|
|
if ($numericalKey === null)
|
|
return $this->numericalKey;
|
|
if (! is_bool ($numericalKey))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid numericalKey provided to serie"), 406);
|
|
$this->numericalKey = $numericalKey;
|
|
return $this;
|
|
}
|
|
|
|
/** The minimum key of the serie
|
|
*/
|
|
public function minKey ()
|
|
{
|
|
if ($this->minKey === null && $this->data !== null)
|
|
$this->minmax ();
|
|
return $this->minKey;
|
|
}
|
|
|
|
/** The maximum key of the serie
|
|
*/
|
|
public function maxKey ()
|
|
{
|
|
if ($this->maxKey === null && $this->data !== null)
|
|
$this->minmax ();
|
|
return $this->maxKey;
|
|
}
|
|
|
|
/** The number of elements in the serie
|
|
*/
|
|
public function count ()
|
|
{
|
|
return count ($this->data);
|
|
}
|
|
|
|
/** Set/Get the graph style for the serie
|
|
* If the parameter is not provided, return the actual state
|
|
* @param string|object|null $style The graph style
|
|
*/
|
|
public function style ($style = null)
|
|
{
|
|
if ($style === null)
|
|
return $this->style;
|
|
if (is_object ($style))
|
|
{
|
|
$this->style = clone $style;
|
|
return $this->style;
|
|
}
|
|
if (! is_string ($style) ||
|
|
! in_array ($style, array ("line", "points", "linePoints")))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid style provided to serie"), 406);
|
|
$styleClass = "graphStyle".$style;
|
|
if ($this->style === null)
|
|
$this->style = new $styleClass ();
|
|
return $this->style;
|
|
}
|
|
|
|
/** The serie is based on the secondary Y axis
|
|
* Set the value if the parameter is provided, get the value if the parameter
|
|
* is not set
|
|
* @param boolean|null $axisYsecondary The Serie on secondary Y axis
|
|
*/
|
|
public function axisYsecondary ($axisYsecondary = null)
|
|
{
|
|
if ($axisYsecondary === null)
|
|
return $this->axisYsecondary;
|
|
if (! is_bool ($axisYsecondary))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid axisYsecondary provided to graph axisYsecondary"),
|
|
406);
|
|
$this->axisYsecondary = $axisYsecondary;
|
|
return $this;
|
|
}
|
|
|
|
/** Draw the serie with the defined style class
|
|
* @param resource $gd The resource to modify
|
|
* @param array $free The free space coordinates on the graphic
|
|
* @param object $axisX The axis X used on the graph
|
|
* @param object $axisY The axis Y used on the graph
|
|
*/
|
|
public function draw ($gd, $free, $axisX, $axisY)
|
|
{
|
|
$this->style->draw ($gd, $free, $this->data, $axisX, $axisY);
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** Read the data */
|
|
class graphData
|
|
/* {{{ */
|
|
{
|
|
/** Store the data when the user provided them. Store them in array form
|
|
*/
|
|
private $data;
|
|
|
|
/** The titles are on the first line
|
|
*/
|
|
private $titlesOnFirstLine = null;
|
|
|
|
/** The titles are on the first column
|
|
*/
|
|
private $titlesOnFirstColumn = null;
|
|
|
|
/** The data are stored horizontally
|
|
*/
|
|
private $horizontalData = null;
|
|
|
|
/** Get the data from an indexed array
|
|
* @param array $array The data array to graph
|
|
*/
|
|
public function arrayIndexed ($array)
|
|
{
|
|
$this->data = $array;
|
|
if (! is_array ($this->data))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid Array Parameter provided: not an array"),
|
|
406);
|
|
return $this;
|
|
}
|
|
|
|
/** Get the data from an associative array
|
|
* The associative array are provided by database results
|
|
* @param array $array The data array to graph
|
|
*/
|
|
public function arrayAssociative ($array)
|
|
{
|
|
if (! is_array ($array))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid Array Parameter provided: not an array"),
|
|
406);
|
|
$titles = array ();
|
|
$this->data = array ();
|
|
foreach ($array as $line=>$lineArr)
|
|
{
|
|
foreach ($lineArr as $key=>$cell)
|
|
{
|
|
$titles[$key] = "";
|
|
$this->data[$line][] = $cell;
|
|
}
|
|
|
|
}
|
|
array_unshift ($this->data, array_keys ($titles));
|
|
return $this;
|
|
}
|
|
|
|
/** Get the data from a CSV string
|
|
* @param string $csv The CSV string
|
|
*/
|
|
public function csv ($csv)
|
|
{
|
|
$csv = trim ($csv);
|
|
$lines = preg_split ('/( *\R)+/s', $csv);
|
|
$this->data = array_map('str_getcsv', $lines);
|
|
if (! is_array ($this->data))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid CSV provided: not converted to array"),
|
|
406);
|
|
return $this;
|
|
}
|
|
|
|
/** Get the data from a JSON string
|
|
* @param string $json The JSON string
|
|
*/
|
|
public function json ($json)
|
|
{
|
|
$this->data = json_decode ($json, true);
|
|
if (! is_array ($this->data))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid JSON provided: not converted to array"),
|
|
406);
|
|
return $this;
|
|
}
|
|
|
|
/** Titles on first line
|
|
* Set the value if the parameter is provided, get the value if the parameter
|
|
* is not set
|
|
* @param boolean|null $titlesOnFirstLine The titles on first line
|
|
*/
|
|
public function titlesOnFirstLine ($titlesOnFirstLine = null)
|
|
{
|
|
if ($titlesOnFirstLine === null)
|
|
return $this->titlesOnFirstLine;
|
|
if (! is_bool ($titlesOnFirstLine))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid titlesOnFirstLine provided to graph titlesOnFirstLine"),
|
|
406);
|
|
$this->titlesOnFirstLine = $titlesOnFirstLine;
|
|
return $this;
|
|
}
|
|
|
|
/** Titles on first column
|
|
* Set the value if the parameter is provided, get the value if the parameter
|
|
* is not set
|
|
* @param boolean|null $titlesOnFirstColumn The titles on first column
|
|
*/
|
|
public function titlesOnFirstColumn ($titlesOnFirstColumn = null)
|
|
{
|
|
if ($titlesOnFirstColumn === null)
|
|
return $this->titlesOnFirstColumn;
|
|
if (! is_bool ($titlesOnFirstColumn))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid titlesOnFirstColumn provided to graph titlesOnFirstColumn"),
|
|
406);
|
|
$this->titlesOnFirstColumn = $titlesOnFirstColumn;
|
|
return $this;
|
|
}
|
|
|
|
/** The data are stored horizontally in the array
|
|
* Set the value if the parameter is provided, get the value if the parameter
|
|
* is not set
|
|
* @param boolean|null $horizontalData The data are stored horizontally
|
|
*/
|
|
public function horizontalData ($horizontalData = null)
|
|
{
|
|
if ($horizontalData === null)
|
|
return $this->horizontalData;
|
|
if (! is_bool ($horizontalData))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid horizontalData provided to graph horizontalData"),
|
|
406);
|
|
$this->horizontalData = $horizontalData;
|
|
return $this;
|
|
}
|
|
|
|
/** Get the series in an array with the associated values
|
|
*/
|
|
public function getSeries ()
|
|
{
|
|
// 0. If there is no data to graph, nothing to do
|
|
if (count ($this->data) === 0)
|
|
return array ();
|
|
|
|
// 1. If $this->titlesOnFirstLine === null, look if the first line contains
|
|
// titles
|
|
if ($this->titlesOnFirstLine === null)
|
|
{
|
|
$this->titlesOnFirstLine = true;
|
|
if (count ($this->data) === 1)
|
|
$this->titlesOnFirstLine = false;
|
|
if (is_array ($this->data[0]))
|
|
{
|
|
foreach ($this->data[0] as $cell)
|
|
{
|
|
if (is_numeric ($cell))
|
|
{
|
|
$this->titlesOnFirstLine = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->titlesOnFirstLine = false;
|
|
}
|
|
}
|
|
|
|
// 2. If $this->titlesOnFirstColumn === null, look if the first column
|
|
// contains titles
|
|
if ($this->titlesOnFirstColumn === null)
|
|
{
|
|
if (count ($this->data[0]) === 1)
|
|
$this->titlesOnFirstColumn = false;
|
|
else
|
|
{
|
|
$this->titlesOnFirstColumn = true;
|
|
foreach ($this->data as $lineArr)
|
|
{
|
|
if (is_numeric ($lineArr[0]))
|
|
{
|
|
$this->titlesOnFirstColumn = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. If $this->horizontalData === null, look for orientation with the
|
|
// titles states
|
|
if ($this->horizontalData === null)
|
|
{
|
|
if ($this->titlesOnFirstColumn === true ||
|
|
! array_key_exists (1, $this->data))
|
|
$this->horizontalData = true;
|
|
else
|
|
$this->horizontalData = false;
|
|
}
|
|
|
|
// 4. Create the series
|
|
$colTitles = array ();
|
|
$lineTitles = array ();
|
|
$series = array ();
|
|
foreach ($this->data as $linePos => $lineArr)
|
|
{
|
|
if (! is_array ($lineArr))
|
|
$lineArr = array ($lineArr);
|
|
if ($linePos === 0)
|
|
{
|
|
if ($this->titlesOnFirstLine)
|
|
{
|
|
$colTitles = $lineArr;
|
|
if ($this->horizontalData === false)
|
|
{
|
|
// First line with titles and vertical data: get the series names
|
|
$series = array_flip ($lineArr);
|
|
foreach ($series as &$value)
|
|
$value = array ();
|
|
if ($this->titlesOnFirstColumn)
|
|
array_shift ($series);
|
|
}
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
foreach ($lineArr as $i=>$value)
|
|
$colTitles[$i] = $i;
|
|
}
|
|
}
|
|
elseif (count ($lineArr) !== count ($colTitles))
|
|
throw new \Exception (sprintf (dgettext ("domframework",
|
|
"Invalid data provided: line %d doesn't have the same number ".
|
|
"of elements as the first line (%d != %d elements)"),
|
|
$linePos+1, count ($lineArr), count ($colTitles)), 406);
|
|
foreach ($lineArr as $colPos => $cell)
|
|
{
|
|
$cell = trim ($cell);
|
|
if ($colPos === 0)
|
|
{
|
|
if ($this->titlesOnFirstColumn)
|
|
{
|
|
$lineTitles[$linePos] = $cell;
|
|
if ($this->horizontalData === true)
|
|
{
|
|
$series[$cell] = array ();
|
|
}
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
$lineTitles[$linePos] = $linePos;
|
|
}
|
|
}
|
|
if (! is_numeric ($cell))
|
|
$cell = null;
|
|
else
|
|
$cell = $cell+0.0;
|
|
if ($this->horizontalData === false)
|
|
$series[$colTitles[$colPos]][$lineTitles[$linePos]] = $cell;
|
|
else
|
|
$series[$lineTitles[$linePos]][$colTitles[$colPos]] = $cell;
|
|
}
|
|
}
|
|
return $series;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The graphTitle object */
|
|
class graphTitle
|
|
/* {{{ */
|
|
{
|
|
/** The title text
|
|
*/
|
|
private $text = null;
|
|
|
|
/** The TTF fontfile to use
|
|
*/
|
|
private $fontfile = null;
|
|
|
|
/** The font size to use
|
|
*/
|
|
private $fontsize = 14;
|
|
|
|
/** The title color
|
|
*/
|
|
private $color = "black";
|
|
|
|
/** The padding arround the title (in px)
|
|
*/
|
|
private $padding = 10;
|
|
|
|
/** Set the text of the title if the parameter is provided.
|
|
* Get the text of the title if the parameter is not provided
|
|
* @param string|null $text The text of the title
|
|
*/
|
|
public function text ($text = null)
|
|
{
|
|
if ($text === null)
|
|
return $this->text;
|
|
if (! is_string ($text) || strlen ($text) < 0 || strlen ($text) > 50)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid text provided to graph title"), 406);
|
|
$this->text = $text;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the fontfile of the title if the parameter is provided.
|
|
* Get the fontfile of the title if the parameter is not provided
|
|
* @param string|null $fontfile The fontfile of the title
|
|
*/
|
|
public function fontfile ($fontfile = null)
|
|
{
|
|
if ($fontfile === null)
|
|
return $this->fontfile;
|
|
if (! is_string ($fontfile) || strlen ($fontfile) < 0 ||
|
|
! file_exists ($fontfile) || ! is_readable ($fontfile))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid fontfile provided to graph title"), 406);
|
|
$this->fontfile = $fontfile;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the font size of the title if the parameter is provided.
|
|
* Get the font size of the title if the parameter is not provided
|
|
* @param integer|null $fontsize The font size of the title
|
|
*/
|
|
public function fontsize ($fontsize = null)
|
|
{
|
|
if ($fontsize === null)
|
|
return $this->fontsize;
|
|
if (! is_integer ($fontsize) || $fontsize < 2 || $fontsize > 100)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid fontsize provided to graph title"), 406);
|
|
$this->fontsize = $fontsize;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the color of the title if the parameter is provided.
|
|
* Get the color of the title if the parameter is not provided
|
|
* @param string|null $color The color of the title
|
|
*/
|
|
public function color ($color = null)
|
|
{
|
|
if ($color === null)
|
|
return $this->color;
|
|
if (! is_string ($color) ||
|
|
! in_array ($color, \color::colorList ()))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid color provided to graph title"), 406);
|
|
$this->color = $color;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the padding of the title if the parameter is provided.
|
|
* Get the padding of the title if the parameter is not provided
|
|
* @param integer|null $padding The padding of the title
|
|
*/
|
|
public function padding ($padding = null)
|
|
{
|
|
if ($padding === null)
|
|
return $this->padding;
|
|
if (! is_integer ($padding) || $padding < 0 || $padding > 200)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid padding provided to graph title"), 406);
|
|
$this->padding = $padding;
|
|
return $this;
|
|
}
|
|
|
|
/** Draw the title in the $gd resource provided
|
|
* @param resource $gd The resource to modify
|
|
* @param array $free The free space coordinates on the graphic
|
|
* @return array the new free coordinates array
|
|
*/
|
|
public function draw ($gd, $free)
|
|
{
|
|
if ($this->text === null)
|
|
return $free;
|
|
// Look for the bounding box around the text. The bounding is not write on
|
|
// the image and return the coordinates for the text box
|
|
$bbox = imagettfbbox ($this->fontsize, 0, $this->fontfile, $this->text);
|
|
// Calculate the position of the text to be centered on the graph
|
|
// The padding is only on vertical : the x is centered
|
|
$x = floor (($free[2] - $free[0] - abs ($bbox[4] - $bbox[0])) / 2);
|
|
$y = ceil ($free[1] + abs ($bbox[5] - $bbox[1])) + $this->padding;
|
|
$x += $free[0];
|
|
$y += $free[1];
|
|
imagettftext ($gd, $this->fontsize, 0, $x, $y,
|
|
\color::allocateFromText ($gd, $this->color),
|
|
$this->fontfile, $this->text);
|
|
return array (intval ($free[0]), intval ($free[1] + $y + $this->padding),
|
|
intval ($free[2]), intval ($free[3]));
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The graphLegend object */
|
|
class graphLegend
|
|
/* {{{ */
|
|
{
|
|
/** Show the legend (no legend by default)
|
|
*/
|
|
private $show = false;
|
|
|
|
/** The TTF fontfile to use
|
|
*/
|
|
private $fontfile = null;
|
|
|
|
/** The font size to use
|
|
*/
|
|
private $fontsize = 10;
|
|
|
|
/** The legend color for the font
|
|
*/
|
|
private $color = "black";
|
|
|
|
/** The legend background-color
|
|
*/
|
|
private $bgcolor = "white";
|
|
|
|
/** The legend border color
|
|
*/
|
|
private $borderColor = "black";
|
|
|
|
/** The padding arround the title (in px)
|
|
*/
|
|
private $padding = 10;
|
|
|
|
/** Set the legend display status if the parameter is provided.
|
|
* Get the legend display status if the parameter is not provided
|
|
* @param boolean|null $show The legend display status
|
|
*/
|
|
public function show ($show = null)
|
|
{
|
|
if ($show === null)
|
|
return $this->show;
|
|
if (! is_bool ($show))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid show value provided to graph legend"), 406);
|
|
$this->show = $show;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the fontfile of the legend if the parameter is provided.
|
|
* Get the fontfile of the legend if the parameter is not provided
|
|
* @param string|null $fontfile The fontfile of the legend
|
|
*/
|
|
public function fontfile ($fontfile = null)
|
|
{
|
|
if ($fontfile === null)
|
|
return $this->fontfile;
|
|
if (! is_string ($fontfile) || strlen ($fontfile) < 0 ||
|
|
! file_exists ($fontfile) || ! is_readable ($fontfile))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid fontfile provided to graph legend"), 406);
|
|
$this->fontfile = $fontfile;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the font size of the legend if the parameter is provided.
|
|
* Get the font size of the legend if the parameter is not provided
|
|
* @param integer|null $fontsize The font size of the legend
|
|
*/
|
|
public function fontsize ($fontsize = null)
|
|
{
|
|
if ($fontsize === null)
|
|
return $this->fontsize;
|
|
if (! is_integer ($fontsize) || $fontsize < 2 || $fontsize > 100)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid fontsize provided to graph legend"), 406);
|
|
$this->fontsize = $fontsize;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the color of the legend if the parameter is provided.
|
|
* Get the color of the legend if the parameter is not provided
|
|
* @param string|null $color The color of the legend
|
|
*/
|
|
public function color ($color = null)
|
|
{
|
|
if ($color === null)
|
|
return $this->color;
|
|
if (! is_string ($color) ||
|
|
! in_array ($color, \color::colorList ()))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid color provided to graph legend"), 406);
|
|
$this->color = $color;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the background-color of the legend if the parameter is provided.
|
|
* Get the background-color of the legend if the parameter is not provided
|
|
* @param string|null $bgcolor The background-color of the legend
|
|
*/
|
|
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 legend"), 406);
|
|
$this->bgcolor = $bgcolor;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the border color of the legend if the parameter is provided.
|
|
* Get the border color of the legend if the parameter is not provided
|
|
* @param string|null $borderColor The border color of the legend
|
|
*/
|
|
public function borderColor ($borderColor = null)
|
|
{
|
|
if ($borderColor === null)
|
|
return $this->borderColor;
|
|
if (! is_string ($borderColor) ||
|
|
! in_array ($borderColor, \color::colorList ()))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid borderColor provided to graph legend"), 406);
|
|
$this->borderColor = $borderColor;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the padding of the legend if the parameter is provided.
|
|
* Get the padding of the legend if the parameter is not provided
|
|
* @param integer|null $padding The padding of the legend
|
|
*/
|
|
public function padding ($padding = null)
|
|
{
|
|
if ($padding === null)
|
|
return $this->padding;
|
|
if (! is_integer ($padding) || $padding < 0 || $padding > 200)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid padding provided to graph legend"), 406);
|
|
$this->padding = $padding;
|
|
return $this;
|
|
}
|
|
|
|
/** Draw the legend in the $gd resource provided
|
|
* @param resource $gd The resource to modify
|
|
* @param array $free The free space coordinates on the graphic
|
|
* @param object $series The series to graph
|
|
* @return array the new free coordinates array
|
|
*/
|
|
public function draw ($gd, $free, $series)
|
|
{
|
|
if ($this->show === false)
|
|
return $free;
|
|
// Look for maxmimum width of the labels
|
|
$maxwidth = 0;
|
|
$height = 0;
|
|
foreach ($series->getList () as $number=>$serie)
|
|
{
|
|
$bbox = imagettfbbox ($this->fontsize, 0, $this->fontfile, $serie);
|
|
$width = abs ($bbox[4] - $bbox[0]);
|
|
// TODO : If the serie name is too long, split it !
|
|
// 20px for the sample + the space before the label
|
|
if ((20 + $maxwidth) > (($free[2] - $free[0]) / 2))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"The serie name in legend must not takes more than half of the graph"),
|
|
500);
|
|
if ($number > 0)
|
|
$height += $this->padding;
|
|
$height += abs ($bbox[5] - $bbox[1]);
|
|
$maxwidth = max ($maxwidth, $width);
|
|
}
|
|
|
|
$x1 = $free[2] - $maxwidth - 20 - $this->padding * 2;
|
|
$x2 = $free[2] - $this->padding;
|
|
$y1 = $free[1] + $this->padding;
|
|
$y2 = $y1 + $height + $this->padding;
|
|
|
|
// Size of the border : 1px
|
|
$border = 1;
|
|
// Draw the background rectangle
|
|
imagefilledrectangle ($gd, $x1, $y1, $x2, $y2,
|
|
\color::allocateFromText ($gd, $this->borderColor));
|
|
imagefilledrectangle ($gd, $x1+$border, $y1+$border,
|
|
$x2-$border, $y2-$border,
|
|
\color::allocateFromText ($gd, $this->bgcolor));
|
|
|
|
// Display the serie names
|
|
$y = $y1;
|
|
foreach ($series->getList () as $number=>$serie)
|
|
{
|
|
// Write the label
|
|
$bbox = imagettfbbox ($this->fontsize, 0, $this->fontfile, $serie);
|
|
$height = abs ($bbox[5] - $bbox[1]);
|
|
$y += $height;
|
|
imagettftext ($gd, $this->fontsize, 0,
|
|
$x1 + 30, intval ($y + $height / 2),
|
|
\color::allocateFromText ($gd, $this->color),
|
|
$this->fontfile, $serie);
|
|
// Draw the sample
|
|
$series->serie ($serie)->style ()->sample ($gd, $x1 + 15, $y);
|
|
$y += $this->padding;
|
|
}
|
|
$free = array ($free[0], $free[1], $x1 - 5, $free[3]);
|
|
return $free;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The general axis management */
|
|
class graphAxisGeneral
|
|
/* {{{ */
|
|
{
|
|
/** The min value of the axis. Do not use it if the axis is composed of labels
|
|
*/
|
|
protected $min = null;
|
|
|
|
/** The max value of the axis. Do not use it if the axis is composed of labels
|
|
*/
|
|
protected $max = null;
|
|
|
|
/** The label max to display
|
|
*/
|
|
protected $labelMax;
|
|
|
|
/** The label min to display
|
|
*/
|
|
protected $labelMin;
|
|
|
|
/** The data displayed as values on the axis
|
|
*/
|
|
protected $data;
|
|
|
|
/** Set if the axis is only numerical (true) or is composed of labels (false)
|
|
*/
|
|
protected $numerical;
|
|
|
|
/** The minimum bottom position in pixels. Used on vertical axis
|
|
*/
|
|
protected $bottom;
|
|
|
|
/** The maximum top position in pixels. Used on vertical axis
|
|
*/
|
|
protected $top;
|
|
|
|
/** The minimum left position in pixels. Used on horizontal axis
|
|
*/
|
|
protected $left;
|
|
|
|
/** The maximum right position in pixels. Used on horizontal axis
|
|
*/
|
|
protected $right;
|
|
|
|
/** The fontfile to write the labels
|
|
*/
|
|
protected $fontfile;
|
|
|
|
/** The fontsize to write the labels
|
|
*/
|
|
protected $fontsize = 8;
|
|
|
|
/** Axis color
|
|
*/
|
|
protected $axisColor;
|
|
|
|
/** Grid color on the axis
|
|
* Can be set to null or "transparent" to not display the grid
|
|
*/
|
|
protected $gridColor;
|
|
|
|
/** Set the min value of the axis if the parameter is provided.
|
|
* Get the min value of the axis if the parameter is not provided
|
|
* @param integer|null $min The min value of the axis
|
|
*/
|
|
public function min ($min = null)
|
|
{
|
|
if ($min === null)
|
|
return $this->min;
|
|
if (! is_numeric ($min))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid min provided to graph Axis")." ".get_class ($this), 406);
|
|
$this->min = $min;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the max value of the axis if the parameter is provided.
|
|
* Get the max value of the axis if the parameter is not provided
|
|
* @param integer|null $max The max value of the axis
|
|
*/
|
|
public function max ($max = null)
|
|
{
|
|
if ($max === null)
|
|
return $this->max;
|
|
if (! is_numeric ($max))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid max provided to graph Axis")." ".get_class ($this), 406);
|
|
$this->max = $max;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the data of the axis if the parameter is provided.
|
|
* Get the data of the axis if the parameter is not provided
|
|
* @param array|null $data The data of the axis
|
|
*/
|
|
public function data ($data = null)
|
|
{
|
|
if ($data === null)
|
|
return $this->data;
|
|
if (! is_array ($data))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid data provided to graph Axis")." ".get_class ($this), 406);
|
|
$this->data = $data;
|
|
if ($this->numerical === null)
|
|
{
|
|
// Look if the provided data are only numerical. Then define the numerical
|
|
// property
|
|
foreach ($data as $d)
|
|
{
|
|
if (! is_numeric ($d))
|
|
{
|
|
$this->numerical = false;
|
|
break;
|
|
}
|
|
}
|
|
if ($this->numerical === null)
|
|
$this->numerical = true;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/** Set if the axis is numerical or composed of labels if the parameter is
|
|
* provided.
|
|
* Get if the axis is numerical if the parameter is not provided
|
|
* @param boolean|null $numerical the axis is numerical
|
|
*/
|
|
public function numerical ($numerical = null)
|
|
{
|
|
if ($numerical === null)
|
|
return $this->numerical;
|
|
if (! is_bool ($numerical))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid numerical parameter provided to graph Axis")." ".
|
|
get_class ($this), 406);
|
|
$this->numerical = $numerical;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the bottom position of the axis if the parameter is provided.
|
|
* Get the bottom position of the axis if the parameter is not provided
|
|
* @param integer|null $bottom The bottom position of the axis
|
|
*/
|
|
public function bottom ($bottom = null)
|
|
{
|
|
if ($bottom === null)
|
|
return $this->bottom;
|
|
if (! is_integer ($bottom) || $bottom < 0 || $bottom > 5000)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid bottom provided to graph Axis")." ".get_class ($this), 406);
|
|
$this->bottom = $bottom;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the top position of the axis if the parameter is provided.
|
|
* Get the top position of the axis if the parameter is not provided
|
|
* @param integer|null $top The top position of the axis
|
|
*/
|
|
public function top ($top = null)
|
|
{
|
|
if ($top === null)
|
|
return $this->top;
|
|
if (! is_integer ($top) || $top < 0 || $top > 5000)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid top provided to graph Axis")." ".get_class ($this), 406);
|
|
$this->top = $top;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the left position of the axis if the parameter is provided.
|
|
* Get the left position of the axis if the parameter is not provided
|
|
* @param integer|null $left The left position of the axis
|
|
*/
|
|
public function left ($left = null)
|
|
{
|
|
if ($left === null)
|
|
return $this->left;
|
|
if (! is_integer ($left) || $left < 0 || $left > 5000)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid left provided to graph Axis")." ".get_class ($this), 406);
|
|
$this->left = $left;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the right position of the axis if the parameter is provided.
|
|
* Get the right position of the axis if the parameter is not provided
|
|
* @param integer|null $right The right position of the axis
|
|
*/
|
|
public function right ($right = null)
|
|
{
|
|
if ($right === null)
|
|
return $this->right;
|
|
if (! is_integer ($right) || $right < 0 || $right > 5000)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid right provided to graph Axis")." ".get_class ($this), 406);
|
|
$this->right = $right;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the fontfile of the labels if the parameter is provided.
|
|
* Get the fontfile of the labels if the parameter is not provided
|
|
* @param string|null $fontfile The fontfile of the title
|
|
*/
|
|
public function fontfile ($fontfile = null)
|
|
{
|
|
if ($fontfile === null)
|
|
return $this->fontfile;
|
|
if (! is_string ($fontfile) || strlen ($fontfile) < 0 ||
|
|
! file_exists ($fontfile) || ! is_readable ($fontfile))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid fontfile provided to graph title"), 406);
|
|
$this->fontfile = $fontfile;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the font size of the labels if the parameter is provided.
|
|
* Get the font size of the labels if the parameter is not provided
|
|
* @param integer|null $fontsize The font size of the title
|
|
*/
|
|
public function fontsize ($fontsize = null)
|
|
{
|
|
if ($fontsize === null)
|
|
return $this->fontsize;
|
|
if (! is_integer ($fontsize) || $fontsize < 2 || $fontsize > 100)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid fontsize provided to graph title"), 406);
|
|
$this->fontsize = $fontsize;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the axis color if the parameter is provided.
|
|
* Get the axis color if the parameter is not provided
|
|
* @param string|null $axiscolor The axis color
|
|
*/
|
|
public function axisColor ($axisColor = null)
|
|
{
|
|
if ($axisColor === null)
|
|
return $this->axisColor;
|
|
if (! is_string ($axisColor) ||
|
|
! in_array ($axisColor, \color::colorList ()))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid axisColor provided to graph axis"), 406);
|
|
$this->axisColor = $axisColor;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the grid color if the parameter is provided.
|
|
* Get the grid color if the parameter is not provided
|
|
* @param string|null $gridcolor The grid color
|
|
*/
|
|
public function gridColor ($gridColor = null)
|
|
{
|
|
if ($gridColor === null)
|
|
return $this->gridColor;
|
|
if (! is_string ($gridColor) ||
|
|
! in_array ($gridColor, \color::colorList ()))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid gridColor provided to graph grid"), 406);
|
|
$this->gridColor = $gridColor;
|
|
return $this;
|
|
}
|
|
|
|
/** Calculate the labels that will be displayed on the axis
|
|
* @param integer $nbMaxValues The maximum number of values to return
|
|
* @return array The array of labels to display
|
|
*/
|
|
protected function labels ($nbMaxValues)
|
|
{
|
|
// Activate the debug of this method
|
|
$deb = false;
|
|
if (! is_int ($nbMaxValues))
|
|
throw new \Exception (
|
|
"Invalid parameter nbMaxValues provided to graphAxisGeneral::labels",
|
|
500);
|
|
if ($deb) echo "=========== LABELS\n";
|
|
if ($this->min > 0)
|
|
$minLabel = $this->min * 0.90;
|
|
else
|
|
$minLabel = $this->min * 1.05;
|
|
if ($this->max > 0)
|
|
$maxLabel = $this->max * 1.05;
|
|
else
|
|
$maxLabel = $this->max * 0.90;
|
|
|
|
$minBase = intval (log10 ($minLabel)) - 1;
|
|
$maxBase = intval (log10 ($maxLabel)) - 1;
|
|
$base = max ($minBase, $maxBase);
|
|
if ($deb) echo "minBase=$minBase, maxBase=$maxBase ===> base = $base\n";
|
|
|
|
if ($base < 0 && $base > -1)
|
|
$base = $base - 1;
|
|
// The while loop reduce the base to not explode the number of labels
|
|
// If there is too much labels, remove one digit and retry
|
|
while (1)
|
|
{
|
|
$this->labelMin = null;
|
|
$this->labelMax = null;
|
|
$min = round ($minLabel, -1 * $base);
|
|
$max = round ($maxLabel, -1 * $base);
|
|
if ($deb) echo "min = $min, max = $max\n";
|
|
|
|
$scale = pow (10, $base);
|
|
if ($deb) echo "BASE=".($base).", SCALE = $scale\n";
|
|
if ($scale === 0 || $scale === 0.0)
|
|
die ("Scale equal 0 on line ".__LINE__."\n");
|
|
$labels = array ();
|
|
if ($this->min <= 0 && $this->max >= 0)
|
|
{
|
|
// Return the 0 label and the values arround it
|
|
$zeroAlreadyDraw = false;
|
|
for ($i = 0 ;
|
|
bccomp ($i, 1.2 * $max, abs ($base) + 1) < 1 ;
|
|
$i += $scale)
|
|
{
|
|
if ($i === 0)
|
|
$zeroAlreadyDraw = true;
|
|
if ($deb) echo "Values with Zero : add pos val $i\n";
|
|
$labels[] = $i;
|
|
}
|
|
for ($i = 0 ;
|
|
bccomp ($i, 1.4 * $min, abs ($base) + 1) >= 0 ;
|
|
$i -= $scale)
|
|
{
|
|
if ($zeroAlreadyDraw === true && $i === 0)
|
|
continue;
|
|
if ($deb) echo "Values with Zero : add neg val $i\n";
|
|
$labels[] = $i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No 0 Axis : From $this->min to $this->max
|
|
for ($i = $min ;
|
|
bccomp ($i, $max, abs ($base) + 1) < 1 ;
|
|
$i += $scale)
|
|
{
|
|
if ($deb) echo "Values no Zero : add val $i\n";
|
|
$labels[] = $i;
|
|
}
|
|
}
|
|
if (count ($labels) <= $nbMaxValues)
|
|
{
|
|
foreach ($labels as &$label)
|
|
{
|
|
if ($base < 0)
|
|
$label = sprintf ("%0.".intval (abs ($base))."f", $label);
|
|
else
|
|
$label = sprintf ("%".intval ($base)."d", $label);
|
|
if ($this->labelMin === null ||
|
|
bccomp ($this->labelMin, $label, abs ($base) + 1) >= 0)
|
|
$this->labelMin = $label;
|
|
if ($this->labelMax === null ||
|
|
bccomp ($this->labelMax, $label, abs ($base) + 1) <= 0)
|
|
$this->labelMax = $label;
|
|
}
|
|
|
|
if (bccomp ($this->labelMin, $this->min, abs ($base) + 1) > 0)
|
|
{
|
|
// Add a label in the minimum
|
|
$this->labelMin = $this->labelMin - $scale;
|
|
if ($deb) echo "Force Add Min $this->labelMin\n";
|
|
if ($base < 0)
|
|
$labels[] = sprintf ("%0.".intval (abs ($base))."f",
|
|
$this->labelMin);
|
|
else
|
|
$labels[] = sprintf ("%".intval ($base)."d", $this->labelMin);
|
|
}
|
|
if (bccomp ($this->labelMax, $this->max, abs ($base) + 1) < 0)
|
|
{
|
|
$this->labelMax = $this->labelMax + $scale;
|
|
if ($deb) echo "Force Add Max $this->labelMax\n";
|
|
if ($base < 0)
|
|
$labels[] = sprintf ("%0.".intval (abs ($base))."f",
|
|
$this->labelMax);
|
|
else
|
|
$labels[] = sprintf ("%".intval ($base)."d", $this->labelMax);
|
|
}
|
|
if ($this->labelMin > $this->min)
|
|
die ("labelMin > min ($this->labelMin > $this->min)\n");
|
|
if ($this->labelMax < $this->max)
|
|
die ("labelMax < max ($this->labelMax < $this->max)\n");
|
|
if (count ($labels) <= $nbMaxValues)
|
|
{
|
|
$labels = array_unique ($labels);
|
|
if ($deb) print_r ($labels);
|
|
return $labels;
|
|
}
|
|
}
|
|
if ($deb) echo "LOOP == ".count ($labels)." > $nbMaxValues ==========\n";
|
|
$base = $base + 0.1;
|
|
}
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The graph Axis Horizontal class */
|
|
class graphAxisHorizontal extends graphAxisGeneral
|
|
/* {{{ */
|
|
{
|
|
/** Calculate the position in pixels for a value
|
|
* If the value is out of range, return null to not draw the point
|
|
* @param string|float|integer $value The value to position
|
|
*/
|
|
public function position ($value)
|
|
{
|
|
if ($value === null)
|
|
return null;
|
|
if (! is_numeric ($value) && ! is_string ($value))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid value provided to graph Axis for position")." ".
|
|
get_class ($this), 406);
|
|
if ($this->numerical === null)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"No numerical type defined for Axis")." ".get_class ($this), 406);
|
|
if ($this->numerical)
|
|
{
|
|
// Numerical axis, use a standard scale
|
|
if ($value < $this->min || $value > $this->max)
|
|
return null;
|
|
if (($this->max - $this->min) == 0)
|
|
$dividor = 1;
|
|
else
|
|
$dividor = ($this->max - $this->min);
|
|
$scale = ($value - $this->min) / $dividor;
|
|
return intval ($this->left + $scale * ($this->right - $this->left));
|
|
}
|
|
else
|
|
{
|
|
// Label axis, count them
|
|
if (! is_array ($this->data))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"No data defined for Axis")." ".get_class ($this), 406);
|
|
$pos = array_search ($value, $this->data);
|
|
if ($pos === false)
|
|
return null;
|
|
$width = ($this->right - $this->left) / count ($this->data);
|
|
return intval ($this->left + $width * $pos + $width / 2);
|
|
}
|
|
}
|
|
|
|
/** Calculate the positionMin, used for labeled axies
|
|
* If the value is out of range, return null to not draw the point
|
|
* @param string|float|integer $value The value to position
|
|
*/
|
|
public function positionMin ($value)
|
|
{
|
|
if ($this->numerical)
|
|
return $posCenter;
|
|
if (! is_array ($this->data))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"No data defined for Axis")." ".get_class ($this), 406);
|
|
$pos = array_search ($value, $this->data);
|
|
if ($pos === false)
|
|
return null;
|
|
$width = ($this->right - $this->left) / count ($this->data);
|
|
return intval ($this->left + $width * $pos);
|
|
}
|
|
|
|
/** Calculate the positionMax, used for labeled axies
|
|
* If the value is out of range, return null to not draw the point
|
|
* @param string|float|integer $value The value to position
|
|
*/
|
|
public function positionMax ($value)
|
|
{
|
|
$posCenter = $this->position ($value);
|
|
if ($this->numerical)
|
|
return $posCenter;
|
|
if (! is_array ($this->data))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"No data defined for Axis")." ".get_class ($this), 406);
|
|
$pos = array_search ($value, $this->data);
|
|
if ($pos === false)
|
|
return null;
|
|
$width = ($this->right - $this->left) / count ($this->data);
|
|
return intval ($this->left + $width * $pos + $width);
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The X axis management */
|
|
class graphAxisX extends graphAxisHorizontal
|
|
/* {{{ */
|
|
{
|
|
/** The angle choosed to draw the graph
|
|
*/
|
|
private $angle;
|
|
|
|
/** The padding between the label and the axis
|
|
*/
|
|
private $padding = 7;
|
|
|
|
/** The heigth of the labels + padding (it is the base of the graph) in pixels
|
|
*/
|
|
private $height;
|
|
|
|
/** The number of chars to be displayed in one label
|
|
*/
|
|
protected $nbcharsLabel = 0;
|
|
|
|
/** Look for the height of the X axis based on the angle of the text when it
|
|
* will be drawn
|
|
*/
|
|
public function getHeight ($gd)
|
|
{
|
|
// Look for the angle of the value. Start Horizontally (angle=0), then
|
|
// try 45°, then finish at 90°. As all the values must in the same angle,
|
|
// test all the values. If one is bad, change the angle and retry all the
|
|
// values
|
|
$width = null;
|
|
if ($this->numerical)
|
|
{
|
|
if ($this->max === null)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"The max is not defined for graphAxisHorizontal"), 406);
|
|
$powMax = intval (log10 ($this->max));
|
|
$tenPercent = round ($this->max / pow (10, $powMax), 1) + 0.1;
|
|
$labelMax = $tenPercent * pow (10, $powMax);
|
|
$this->nbcharsLabel = strlen ($labelMax);
|
|
$bbox = imagettfbbox ($this->fontsize, $this->angle, $this->fontfile,
|
|
$labelMax);
|
|
$height = abs ($bbox[4] - $bbox[0]);
|
|
$this->height = $height + 2 * $this->padding;
|
|
return $this->height;
|
|
}
|
|
else
|
|
{
|
|
if ($this->data === null)
|
|
return 0;
|
|
foreach (array (0, 45, 90) as $this->angle)
|
|
{
|
|
$bboxMaxHeight = 0;
|
|
foreach ($this->data as $key=>$value)
|
|
{
|
|
if ($width === null)
|
|
$width = $this->positionMax ($value) - $this->positionMin ($value);
|
|
// Look for the bounding box around the text. The bounding is not
|
|
// write on the image and return the coordinates for the text box
|
|
$bbox = imagettfbbox ($this->fontsize, $this->angle, $this->fontfile,
|
|
$value);
|
|
if (abs ($bbox[4] - $bbox[0]) > $width)
|
|
continue 2;
|
|
$bboxMaxHeight = max ($bboxMaxHeight, abs ($bbox[5] - $bbox[1]));
|
|
}
|
|
// All the values are OK : we have found the angle : break the angle
|
|
// loop
|
|
break;
|
|
}
|
|
}
|
|
$this->height = $bboxMaxHeight + 2 * $this->padding;
|
|
return $bboxMaxHeight + 2 * $this->padding;
|
|
}
|
|
|
|
/** Draw the axis
|
|
*/
|
|
public function draw ($gd)
|
|
{
|
|
$axisColor = \color::allocateFromText ($gd, $this->axisColor);
|
|
if ($this->numerical)
|
|
{
|
|
if ($this->angle === null)
|
|
$this->getHeight ($gd);
|
|
if ($this->data === null)
|
|
return;
|
|
foreach ($this->data as $key=>$value)
|
|
{
|
|
$position = $this->position ($value);
|
|
|
|
// Draw the labels
|
|
$bbox = imagettfbbox ($this->fontsize, $this->angle, $this->fontfile,
|
|
$value);
|
|
$width = abs ($bbox[4] - $bbox[0]);
|
|
$x = $position - $width / 2;
|
|
$y = $this->bottom - $this->padding;
|
|
// The font color is forced to black
|
|
imagettftext ($gd, $this->fontsize, $this->angle, $x, $y,
|
|
\color::allocateFromText ($gd, "black"),
|
|
$this->fontfile, $value);
|
|
|
|
// Draw the scale
|
|
imageline ($gd,
|
|
$position, $this->bottom - $this->height + 1,
|
|
$position,
|
|
$this->bottom - $this->height + 1 + $this->padding,
|
|
$axisColor);
|
|
|
|
// Draw the grid
|
|
$this->drawGrid ($gd, $this->bottom - $this->height, $position);
|
|
}
|
|
|
|
// Draw the axis
|
|
$y = $this->bottom - $this->height + 1;
|
|
imageline ($gd, $this->left, $y, $this->right, $y, $axisColor);
|
|
}
|
|
else
|
|
{
|
|
if ($this->angle === null)
|
|
$this->getHeight ($gd);
|
|
if ($this->data === null)
|
|
return;
|
|
foreach ($this->data as $key=>$value)
|
|
{
|
|
$position = $this->position ($value);
|
|
|
|
// Draw the labels
|
|
$bbox = imagettfbbox ($this->fontsize, $this->angle, $this->fontfile,
|
|
$value);
|
|
$width = abs ($bbox[4] - $bbox[0]);
|
|
$x = $position - $width / 2;
|
|
$y = $this->bottom - $this->padding;
|
|
// The font color is forced to black
|
|
imagettftext ($gd, $this->fontsize, $this->angle, $x, $y,
|
|
\color::allocateFromText ($gd, "black"),
|
|
$this->fontfile, $value);
|
|
|
|
// Draw the separators
|
|
$y = $this->bottom - $this->height + 1;
|
|
$xmin = $this->positionMin ($value);
|
|
$xmax = $this->positionMax ($value);
|
|
imageline ($gd, $xmin, $y, $xmin, $y + $this->padding, $axisColor);
|
|
imageline ($gd, $xmax, $y, $xmax, $y + $this->padding, $axisColor);
|
|
|
|
// Draw the grid
|
|
$this->drawGrid ($gd, $this->bottom - $this->height, $position);
|
|
}
|
|
|
|
// Draw the axis
|
|
$y = $this->bottom - $this->height + 1;
|
|
imageline ($gd, $this->left, $y, $this->right, $y, $axisColor);
|
|
}
|
|
}
|
|
|
|
/** Draw the grid
|
|
* @param resource $gd The resource to modify
|
|
* @param integer $width The width of the labels on the axis
|
|
* @param integer|float $position The position to draw
|
|
*/
|
|
protected function drawGrid ($gd, $width, $position)
|
|
{
|
|
if ($this->gridColor === null || $this->gridColor === "transparent")
|
|
return;
|
|
$gridColor = \color::allocateFromText ($gd, $this->gridColor);
|
|
if ($position === null)
|
|
return;
|
|
$y = $this->bottom - $this->height;
|
|
imageline ($gd, $position, $y, $position, $this->top, $gridColor);
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** Manage the vertical axis */
|
|
class graphAxisVertical extends graphAxisGeneral
|
|
/* {{{ */
|
|
{
|
|
/** The angle choosed to draw the graph
|
|
*/
|
|
private $angle;
|
|
|
|
/** The padding between the label and the axis
|
|
*/
|
|
protected $padding = 7;
|
|
|
|
/** The width of the labels + padding (it is the base of the graph) in pixels
|
|
*/
|
|
protected $width;
|
|
|
|
/** The number of chars to be displayed in one label
|
|
*/
|
|
protected $nbcharsLabel = 0;
|
|
|
|
/** Look for the width of the Y axis
|
|
*/
|
|
public function getWidth ($gd)
|
|
{
|
|
if ($this->numerical)
|
|
{
|
|
if ($this->max === null)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"The max is not defined for graphAxisVertical"), 406);
|
|
// Look at the minimum distance between two labeled values
|
|
$bbox = imagettfbbox ($this->fontsize, 0, $this->fontfile, "NOT");
|
|
$height = abs ($bbox[5] - $bbox[1]) + $this->padding;
|
|
for ($nbMaxValues = 10 ; $nbMaxValues > 2 ; $nbMaxValues--)
|
|
{
|
|
if ($nbMaxValues * $height < abs ($this->top - $this->bottom))
|
|
break;
|
|
}
|
|
$labels = $this->labels ($nbMaxValues);
|
|
$labelBiggest = "";
|
|
$this->nbcharsLabel = 0;
|
|
foreach ($labels as $label)
|
|
{
|
|
if (strlen ($label) > $this->nbcharsLabel)
|
|
{
|
|
$this->nbcharsLabel = strlen ($label);
|
|
$labelBiggest = $label;
|
|
}
|
|
}
|
|
$bbox = imagettfbbox ($this->fontsize, $this->angle, $this->fontfile,
|
|
$labelBiggest);
|
|
$width = abs ($bbox[4] - $bbox[0]);
|
|
$this->width = $width + 2 * $this->padding;
|
|
return $this->width;
|
|
}
|
|
else
|
|
{
|
|
if ($this->data === null)
|
|
return $this->padding;
|
|
die ("TODO : getWidth in labeled line ".__LINE__."\n");
|
|
}
|
|
}
|
|
|
|
/** Calculate the position in pixels for a value
|
|
* If the value is out of range, return null to not draw the point
|
|
* @param string|float|integer $value The value to position
|
|
*/
|
|
public function position ($value)
|
|
{
|
|
if ($value === null)
|
|
return null;
|
|
if (! is_numeric ($value) && ! is_string ($value))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid value provided to graph Axis for position")." ".
|
|
get_class ($this), 406);
|
|
if ($this->numerical === null)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"No numerical type defined for Axis")." ".get_class ($this), 406);
|
|
if ($this->numerical)
|
|
{
|
|
// Numerical axis, use a standard scale
|
|
if ($value > $this->labelMax || $value < $this->labelMin)
|
|
return null;
|
|
$scale = ($value - $this->labelMin) / ($this->labelMax - $this->labelMin);
|
|
$pos = intval ($this->bottom + $scale * ($this->top - $this->bottom));
|
|
return $pos;
|
|
}
|
|
else
|
|
{
|
|
// Label axis, count them
|
|
if (! is_array ($this->data))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"No data defined for Axis")." ".get_class ($this), 406);
|
|
$pos = array_search ($value, $this->data);
|
|
if ($pos === false)
|
|
return null;
|
|
$width = ($this->top - $this->bottom) / count ($this->data);
|
|
$pos = intval ($this->bottom + $width * $pos + $width / 2);
|
|
return $pos;
|
|
}
|
|
}
|
|
|
|
/** Calculate the positionMin, used for labeled axies
|
|
* If the value is out of range, return null to not draw the point
|
|
* @param string|float|integer $value The value to position
|
|
*/
|
|
public function positionMin ($value)
|
|
{
|
|
if ($this->numerical)
|
|
return $posCenter;
|
|
if (! is_array ($this->data))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"No data defined for Axis")." ".get_class ($this), 406);
|
|
$pos = array_search ($value, $this->data);
|
|
if ($pos === false)
|
|
return null;
|
|
$width = ($this->top - $this->bottom) / count ($this->data);
|
|
return intval ($this->bottom + $width * $pos);
|
|
}
|
|
|
|
/** Calculate the positionMax, used for labeled axies
|
|
* If the value is out of range, return null to not draw the point
|
|
* @param string|float|integer $value The value to position
|
|
*/
|
|
public function positionMax ($value)
|
|
{
|
|
$posCenter = $this->position ($value);
|
|
if ($this->numerical)
|
|
return $posCenter;
|
|
if (! is_array ($this->data))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"No data defined for Axis")." ".get_class ($this), 406);
|
|
$pos = array_search ($value, $this->data);
|
|
if ($pos === false)
|
|
return null;
|
|
$width = ($this->top - $this->bottom) / count ($this->data);
|
|
return intval ($this->bottom + $width * $pos + $width);
|
|
}
|
|
|
|
/** Draw the axis labels and lines
|
|
*/
|
|
public function draw ($gd)
|
|
{
|
|
$axisColor = \color::allocateFromText ($gd, $this->axisColor);
|
|
if ($this->numerical)
|
|
{
|
|
// Look at the minimum distance between two labeled values
|
|
$bbox = imagettfbbox ($this->fontsize, 0, $this->fontfile, "NOT");
|
|
$height = abs ($bbox[5] - $bbox[1]) + $this->padding;
|
|
for ($nbMaxValues = 10 ; $nbMaxValues > 2 ; $nbMaxValues--)
|
|
{
|
|
if ($nbMaxValues * $height < abs ($this->top - $this->bottom))
|
|
break;
|
|
}
|
|
|
|
$width = $this->getWidth ($gd);
|
|
$labels = $this->labels ($nbMaxValues);
|
|
if (count ($labels) > $nbMaxValues)
|
|
die ("Too much labels to display (".
|
|
count ($labels)." > $nbMaxValues)\n");
|
|
foreach ($labels as $label)
|
|
{
|
|
$this->drawOne ($gd, $width, $label);
|
|
$this->drawGrid ($gd, $width, $label);
|
|
}
|
|
$this->drawAxis ($gd, $width);
|
|
}
|
|
else
|
|
{
|
|
// Labeled
|
|
// If there is no data, there is nothing to draw : return
|
|
if ($this->data === null)
|
|
return;
|
|
die ("graphAxisVertical::draw labeled TBD line ".__LINE__."\n");
|
|
}
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The Y1 axis management */
|
|
class graphAxisY1 extends graphAxisVertical
|
|
/* {{{ */
|
|
{
|
|
/** Draw one value on the axis
|
|
* @param resource $gd The resource to modify
|
|
* @param integer|float $value The value to draw
|
|
* @param integer $width The width of the labels on the axis
|
|
*/
|
|
protected function drawOne ($gd, $width, $val)
|
|
{
|
|
$axisColor = \color::allocateFromText ($gd, $this->axisColor);
|
|
if ($this->numerical)
|
|
{
|
|
// Do not allow the label to be longer than $this->max
|
|
if (strlen ($val) > $this->nbcharsLabel)
|
|
$itmp = rtrim (substr ($val, 0, $this->nbcharsLabel), ".");
|
|
else
|
|
$itmp = $val;
|
|
// Write the labels align to right
|
|
$bbox = imagettfbbox ($this->fontsize, 0, $this->fontfile, $itmp);
|
|
$labelwidth = abs ($bbox[4] - $bbox[0]) + $this->padding;
|
|
$labelheight = abs ($bbox[5] - $bbox[1]);
|
|
$y = $this->position ($val);
|
|
if ($y === null)
|
|
return;
|
|
// The color is forced to black
|
|
imagettftext ($gd, $this->fontsize, 0,
|
|
$width - $labelwidth,
|
|
$y + $labelheight / 2,
|
|
\color::allocateFromText ($gd, "black"),
|
|
$this->fontfile, $itmp);
|
|
|
|
// Display the separators
|
|
imageline ($gd, $width - $this->padding, $y,
|
|
$width, $y, $axisColor);
|
|
}
|
|
else
|
|
{
|
|
die ("graphAxisY1:: drawOne NOT numerical line ".__LINE__."\n");
|
|
}
|
|
}
|
|
|
|
/** Draw the axis
|
|
* @param resource $gd The resource to modify
|
|
* @param integer $width The width of the labels on the axis
|
|
*/
|
|
protected function drawAxis ($gd, $width)
|
|
{
|
|
$axisColor = \color::allocateFromText ($gd, $this->axisColor);
|
|
imageline ($gd, $width, $this->bottom, $width, $this->top, $axisColor);
|
|
}
|
|
|
|
/** Draw the grid
|
|
* @param resource $gd The resource to modify
|
|
* @param integer $width The width of the labels on the axis
|
|
* @param integer|float $val The value to draw
|
|
*/
|
|
protected function drawGrid ($gd, $width, $val)
|
|
{
|
|
if ($this->gridColor === null || $this->gridColor === "transparent")
|
|
return;
|
|
$gridColor = \color::allocateFromText ($gd, $this->gridColor);
|
|
if ($this->numerical)
|
|
{
|
|
$y = $this->position ($val);
|
|
if ($y === null)
|
|
return;
|
|
imageline ($gd, $width, $y, $this->right, $y, $gridColor);
|
|
}
|
|
else
|
|
{
|
|
die ("graphAxisY1:: drawGrid NOT numerical line ".__LINE__."\n");
|
|
}
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The Y2 axis management */
|
|
class graphAxisY2 extends graphAxisVertical
|
|
/* {{{ */
|
|
{
|
|
/** Draw one value on the axis
|
|
* @param resource $gd The resource to modify
|
|
* @param integer|float $value The value to draw
|
|
* @param integer $width The width of the labels on the axis
|
|
*/
|
|
protected function drawOne ($gd, $width, $val)
|
|
{
|
|
$axisColor = \color::allocateFromText ($gd, $this->axisColor);
|
|
if ($this->numerical)
|
|
{
|
|
// Do not allow the label to be longer than $this->max
|
|
if (strlen ($val) > $this->nbcharsLabel)
|
|
$itmp = rtrim (substr ($val, 0, $this->nbcharsLabel), ".");
|
|
else
|
|
$itmp = $val;
|
|
// Write the labels align to left
|
|
$bbox = imagettfbbox ($this->fontsize, 0, $this->fontfile, $itmp);
|
|
$labelwidth = abs ($bbox[4] - $bbox[0]) + $this->padding;
|
|
$labelheight = abs ($bbox[5] - $bbox[1]);
|
|
$y = $this->position ($val);
|
|
if ($y === null)
|
|
return;
|
|
// The color is forced to black
|
|
imagettftext ($gd, $this->fontsize, 0,
|
|
$this->left + $this->padding + 3,
|
|
$y + $labelheight / 2,
|
|
\color::allocateFromText ($gd, "black"),
|
|
$this->fontfile, $itmp);
|
|
|
|
// Display the separators
|
|
imageline ($gd, $this->left, $y,
|
|
$this->left + $this->padding, $y, $axisColor);
|
|
}
|
|
else
|
|
{
|
|
die ("graphAxisY2:: drawOne NOT numerical line ".__LINE__."\n");
|
|
}
|
|
}
|
|
|
|
/** Draw the axis
|
|
* @param resource $gd The resource to modify
|
|
* @param integer $width The width of the labels on the axis
|
|
*/
|
|
protected function drawAxis ($gd, $width)
|
|
{
|
|
$axisColor = \color::allocateFromText ($gd, $this->axisColor);
|
|
imageline ($gd, $this->left, $this->bottom,
|
|
$this->left, $this->top, $axisColor);
|
|
}
|
|
|
|
/** Draw the grid
|
|
* @param resource $gd The resource to modify
|
|
* @param integer $width The width of the labels on the axis
|
|
* @param integer|float $val The value to draw
|
|
*/
|
|
protected function drawGrid ($gd, $width, $val)
|
|
{
|
|
// The grid can't be graphed for Y2 axis : only the Y1 axis is allowed
|
|
return;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The graphStyleLine : draw a graph with lines */
|
|
class graphStyleLinePoints
|
|
/* {{{ */
|
|
{
|
|
/** The line color. To hide the lines, choose "transparent"
|
|
*/
|
|
protected $lineColor;
|
|
|
|
/** The point color background. To hide the points, choose "transparent"
|
|
*/
|
|
protected $pointBgcolor;
|
|
|
|
/** The point color border
|
|
*/
|
|
protected $pointColor;
|
|
|
|
/** The point shape (square, circle, triangle, lozenge)
|
|
*/
|
|
protected $pointShape;
|
|
|
|
/** The point width in pixel
|
|
*/
|
|
protected $pointWidth;
|
|
|
|
/** The number of the colors/shapes to use
|
|
*/
|
|
protected $number;
|
|
|
|
/** The allowed shapes */
|
|
protected $allowedShapes = array ("square", "circle", "triangle", "lozenge");
|
|
|
|
/** The palette to use */
|
|
protected $palette;
|
|
|
|
/** Return the name of the style */
|
|
public function name ()
|
|
{
|
|
return "lineAndPoints";
|
|
}
|
|
|
|
/** Set the line color if the parameter is provided.
|
|
* Get the line color if the parameter is not provided
|
|
* @param string|null $lineColor The line color
|
|
*/
|
|
public function lineColor ($lineColor = null)
|
|
{
|
|
if ($lineColor === null)
|
|
return $this->lineColor;
|
|
if (! is_string ($lineColor) ||
|
|
($lineColor !== "transparent" &&
|
|
! in_array ($lineColor, \color::colorList ())))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid lineColor provided to line style"), 406);
|
|
$this->lineColor = $lineColor;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the palette to use if the parameter is provided
|
|
* Get the palette if the parameter is not provided
|
|
* @param string|null $palette The palette to use
|
|
*/
|
|
public function palette ($palette = null)
|
|
{
|
|
if ($palette === null)
|
|
return $this->palette;
|
|
if (! is_string ($palette))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid palette provided to line style"), 406);
|
|
$this->palette = $palette;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the point background color if the parameter is provided.
|
|
* Get the point background color if the parameter is not provided
|
|
* @param string|null $pointBgcolor The point background color
|
|
*/
|
|
public function pointBgcolor ($pointBgcolor = null)
|
|
{
|
|
if ($pointBgcolor === null)
|
|
return $this->pointBgcolor;
|
|
if (! is_string ($pointBgcolor) ||
|
|
($pointBgcolor !== "transparent" &&
|
|
! in_array ($pointBgcolor, \color::colorList ())))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid pointBgcolor provided to line style"), 406);
|
|
$this->pointBgcolor = $pointBgcolor;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the point border color if the parameter is provided.
|
|
* Get the point border color if the parameter is not provided
|
|
* @param string|null $pointColor The point border color
|
|
*/
|
|
public function pointColor ($pointColor = null)
|
|
{
|
|
if ($pointColor === null)
|
|
return $this->pointColor;
|
|
if (! is_string ($pointColor) ||
|
|
($pointColor !== "transparent" &&
|
|
! in_array ($pointColor, \color::colorList ())))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid pointColor provided to line style"), 406);
|
|
$this->pointColor = $pointColor;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the point shape if the parameter is provided.
|
|
* Get the point shape if the parameter is not provided
|
|
* @param string|null $pointShape The point shape
|
|
*/
|
|
public function pointShape ($pointShape = null)
|
|
{
|
|
if ($pointShape === null)
|
|
return $this->pointShape;
|
|
if (! is_string ($pointShape) ||
|
|
! in_array ($pointShape, $this->allowedShapes))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid pointShape provided to line style"), 406);
|
|
$this->pointShape = $pointShape;
|
|
return $this;
|
|
}
|
|
|
|
/** Set the point width if the parameter is provided.
|
|
* Get the point width if the parameter is not provided
|
|
* @param string|null $pointWidth The point width
|
|
*/
|
|
public function pointWidth ($pointWidth = null)
|
|
{
|
|
if ($pointWidth === null)
|
|
return $this->pointWidth;
|
|
if (! is_integer ($pointWidth) || $pointWidth < 3 || $pointWidth > 20)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid pointWidth provided to line style"), 406);
|
|
$this->pointWidth = $pointWidth;
|
|
return $this;
|
|
}
|
|
|
|
/** Select the colors, shapes by the serie number.
|
|
* Do not change any property if the property is already defined
|
|
* If the parameter is not provided, return the value
|
|
* @param integer|null $number The serie number
|
|
*/
|
|
public function number ($number = null)
|
|
{
|
|
if ($number === null)
|
|
return $this->number;
|
|
if (! is_int ($number) || $number < 0)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Invalid number provided to line style number"), 406);
|
|
$this->number = $number;
|
|
if ($this->pointShape === null)
|
|
$this->pointShape = $this->allowedShapes[
|
|
($number%count($this->allowedShapes))];
|
|
$palette = graphPalette::getPalette ($this->palette);
|
|
if ($this->pointBgcolor === null)
|
|
$this->pointBgcolor = $palette[($number%count ($palette))]["bgcolor"];
|
|
if ($this->pointColor === null)
|
|
$this->pointColor = $palette[($number%count ($palette))]["color"];
|
|
if ($this->lineColor === null)
|
|
$this->lineColor = $palette[($number%count ($palette))]["color"];
|
|
}
|
|
|
|
/** Draw in the $gd resource, in the $free array, the data with the parameter
|
|
* of the style
|
|
* @param resource $gd The resource to modify
|
|
* @param array $free The free space coordinates on the graphic
|
|
* @param array $data The data to graph
|
|
* @param object $axisX The X axis used to graph
|
|
* @param object $axisY The Y axis used to graph
|
|
*/
|
|
public function draw ($gd, $free, $data, $axisX, $axisY)
|
|
{
|
|
if ($this->lineColor !== "transparent")
|
|
$lineColor = \color::allocateFromText ($gd, $this->lineColor);
|
|
$lastX = null;
|
|
$lastY = null;
|
|
foreach ($data as $key=>$value)
|
|
{
|
|
$posX = $axisX->position ($key);
|
|
$posY = $axisY->position ($value);
|
|
if ($posX === null || $posY === null)
|
|
{
|
|
// NULL position : skip the point
|
|
$lastX = null;
|
|
$lastY = null;
|
|
continue;
|
|
}
|
|
|
|
// Draw the lines between points (except if the last point was null, or
|
|
// if the line color is "transparent")
|
|
if ($lastX !== null && $lastY !== null &&
|
|
$this->lineColor !== "transparent")
|
|
{
|
|
imageline ($gd, $lastX, $lastY, $posX, $posY, $lineColor);
|
|
// Redraw the old point which was scratch by the new created line
|
|
$this->drawPoint ($gd, $lastX, $lastY);
|
|
}
|
|
if ($lastX === null)
|
|
$lastX = $posX;
|
|
if ($lastY === null)
|
|
$lastY = $posY;
|
|
|
|
// Draw the point. Will overwrite the lines
|
|
if ($this->pointWidth === null)
|
|
$this->pointWidth = 6;
|
|
$this->drawPoint ($gd, $posX, $posY);
|
|
$lastX = $posX;
|
|
$lastY = $posY;
|
|
}
|
|
}
|
|
|
|
/** Draw a point defined in the property of the class
|
|
* @param resource $gd The resource to modify
|
|
* @param integer $posX The X position to draw the point
|
|
* @param integer $posY The Y position to draw the point
|
|
*/
|
|
private function drawPoint ($gd, $posX, $posY)
|
|
{
|
|
if ($this->pointColor !== "transparent")
|
|
$pointColor = \color::allocateFromText ($gd, $this->pointColor);
|
|
if ($this->pointBgcolor !== "transparent")
|
|
$pointBgcolor = \color::allocateFromText ($gd, $this->pointBgcolor);
|
|
$half = intval ($this->pointWidth / 2);
|
|
switch ($this->pointShape)
|
|
{
|
|
case "circle":
|
|
if ($this->pointBgcolor !== "transparent")
|
|
imagefilledellipse ($gd, $posX, $posY, $this->pointWidth,
|
|
$this->pointWidth, $pointBgcolor + 2);
|
|
if ($this->pointColor !== "transparent")
|
|
imageellipse ($gd, $posX, $posY, $this->pointWidth + 2,
|
|
$this->pointWidth + 2, $pointColor);
|
|
break;
|
|
case "lozenge":
|
|
if ($this->pointBgcolor !== "transparent")
|
|
imagefilledpolygon ($gd, array ($posX - $half - 2, $posY,
|
|
$posX, $posY - $half - 2,
|
|
$posX + $half + 2, $posY,
|
|
$posX, $posY + $half + 2),
|
|
4, $pointBgcolor);
|
|
if ($this->pointColor !== "transparent")
|
|
imagepolygon ($gd, array ($posX - $half - 2, $posY,
|
|
$posX, $posY - $half - 2,
|
|
$posX + $half + 2, $posY,
|
|
$posX, $posY + $half + 2),
|
|
4, $pointColor);
|
|
break;
|
|
case "square":
|
|
if ($this->pointBgcolor !== "transparent")
|
|
imagefilledrectangle ($gd, $posX - $half - 1, $posY - $half - 1,
|
|
$posX + $half + 1, $posY + $half + 1,
|
|
$pointBgcolor);
|
|
if ($this->pointColor !== "transparent")
|
|
imagerectangle ($gd, $posX - $half - 1, $posY - $half - 1,
|
|
$posX + $half + 1, $posY + $half + 1,
|
|
$pointColor);
|
|
break;
|
|
case "triangle":
|
|
if ($this->pointBgcolor !== "transparent")
|
|
imagefilledpolygon ($gd, array ($posX - $half -2, $posY + $half + 2,
|
|
$posX, $posY - $half -2,
|
|
$posX + $half + 2, $posY + $half + 2),
|
|
3, $pointBgcolor);
|
|
if ($this->pointColor !== "transparent")
|
|
imagepolygon ($gd, array ($posX - $half - 2, $posY + $half + 2,
|
|
$posX, $posY - $half - 2,
|
|
$posX + $half + 2, $posY + $half + 2),
|
|
3, $pointColor);
|
|
break;
|
|
default:
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Unknown pointShape for serie"), 406);
|
|
}
|
|
}
|
|
|
|
/** Draw a sample of the style for the legend
|
|
* @param resource $gd The resource to modify
|
|
* @param integer $x The central position of the sample in x
|
|
* @param integer $y The central position of the sample in y
|
|
*/
|
|
public function sample ($gd, $x, $y)
|
|
{
|
|
if ($this->lineColor !== "transparent")
|
|
{
|
|
$lineColor = \color::allocateFromText ($gd, $this->lineColor);
|
|
imageline ($gd, $x-10, $y, $x+10, $y, $lineColor);
|
|
}
|
|
if ($this->pointWidth === null)
|
|
$this->pointWidth = 6;
|
|
$this->drawPoint ($gd, $x, $y);
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The graphStylePoints : draw a graph with points */
|
|
class graphStylePoints extends graphStyleLinePoints
|
|
/* {{{ */
|
|
{
|
|
/** The line color : transparent
|
|
*/
|
|
protected $lineColor = "transparent";
|
|
|
|
/** Return the name of the style */
|
|
public function name ()
|
|
{
|
|
return "points";
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The graphStyleLine : draw a graph with line */
|
|
class graphStyleLine extends graphStyleLinePoints
|
|
/* {{{ */
|
|
{
|
|
/** The point color background. To hide the points, choose "transparent"
|
|
*/
|
|
protected $pointBgcolor = "transparent";
|
|
|
|
/** The point color border
|
|
*/
|
|
protected $pointColor = "transparent";
|
|
|
|
/** Return the name of the style */
|
|
public function name ()
|
|
{
|
|
return "line";
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** The graphPalette class */
|
|
class graphPalette
|
|
/* {{{ */
|
|
{
|
|
/** Get the complete palette */
|
|
public static function getPalette ($name)
|
|
{
|
|
$palette = array (
|
|
"basic" => array (
|
|
array ("bgcolor"=>"indianred", "color"=>"firebrick"),
|
|
array ("bgcolor"=>"lightblue", "color"=>"blue"),
|
|
array ("bgcolor"=>"peru", "color"=>"maroon"),
|
|
array ("bgcolor"=>"mediumaquamarine", "color"=>"teal"),
|
|
array ("bgcolor"=>"orange", "color"=>"goldenrod"),
|
|
array ("bgcolor"=>"limegreen", "color"=>"green"),
|
|
array ("bgcolor"=>"darkgrey", "color"=>"grey"),
|
|
array ("bgcolor"=>"darkyellow1", "color"=>"darkyellow2"),
|
|
array ("bgcolor"=>"grey", "color"=>"black"),
|
|
),
|
|
);
|
|
if (! is_string ($name) || ! array_key_exists ($name, $palette))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Unknown palette name provided"), 406);
|
|
return $palette[$name];
|
|
}
|
|
}
|
|
/* }}} */
|