- <?php
- /*
- * Lokorin.com
- * Copyright 2004-2006
- * Licensed under the GNU LGPL. See COPYING for full terms.
- */
- /**
- * A library for creating and displaying graphs. Graphs takes arrays of values
- * and composes images representing the values.
- * @author Andreas Launila
- * @version $Revision: 1.8 $
- * @package com.lokorin.lokorin.lib
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
- */
-
- global $COLOR_BLACK, $COLOR_WHITE;
- /**
- * RGB array for the color black.
- * @var array
- * @access private
- */
- $COLOR_BLACK = array(0, 0, 0);
- /**
- * RGB array for the color white.
- * @var array
- * @access private
- */
- $COLOR_WHITE = array(255, 255, 255);
- /**
- * The length of the point of the arrow representing the axis.
- * @var integer
- * @access private
- */
- define('AXIS_POINT_LENGTH', 10);
- /**
- * The width of the arrow that represents the axis.
- * @var integer
- * @access private
- */
- define('AXIS_WIDTH', 4);
- /**
- * The number of pixels of length that the label marker of the axis should
- * take up.
- * @var integer
- * @access private
- */
- define('AXIS_MARKER_SIZE', 5);
- /**
- * The number of pixels of padding that should be between the axes and the
- * graph border.
- * @var integer
- * @access private
- */
- define('GRAPH_PADDING', 40);
-
- /**
- * Draws an arrow with specified paramaters on a specified image.
- * @param image $img The image on which the arrow should be drawn.
- * @param integer $x1 The x coordinate (pixels) for the starting point of the
- * arrow in the image.
- * @param integer $y1 The y coordinate (pixels) for the starting point of the
- * arrow in the image.
- * @param integer $x2 The x coordinate (pixels) for the end point of the arrow
- * in the image.
- * @param integer $y1 The y coordinate (pixels) for the end point of the arrow
- * in the image.
- * @param integer $length The length (in pixels) that the actual arrow point
- * should have.
- * @param integer $width The width (in pixels) that the arrow should have.
- * @param array $color The color that the arrow should have, an array with the
- * RGB values representing the color.
- * @access private
- */
- function drawArrow(&$img, $x1, $y1, $x2, $y2, $length, $width, $color) {
- $distance = sqrt(pow($x1 - $x2, 2) + pow($y1 - $y2, 2));
-
- $dx = $x2 + ($x1 - $x2) * $length / $distance;
- $dy = $y2 + ($y1 - $y2) * $length / $distance;
-
- $k = $width / $length;
-
- $x2o = $x2 - $dx;
- $y2o = $dy - $y2;
-
- $x3 = $y2o * $k + $dx;
- $y3 = $x2o * $k + $dy;
-
- $x4 = $dx - $y2o * $k;
- $y4 = $dy - $x2o * $k;
-
- imageline($img, $x1, $y1, $dx, $dy, $color);
- imageline($img, $x3, $y3, $x4, $y4, $color);
- imageline($img, $x3, $y3, $x2, $y2, $color);
- imageline($img, $x2, $y2, $x4, $y4, $color);
- }
-
- /**
- * Draws a specified string with a specified color centered on specified
- * coordinates on a specified image.
- * @param image $img The image that the string should be drawn on.
- * @param integer $font The font that the drawn string should have.
- * @param integer $x The x coordinate (pixels) that the drawn string should
- * be centered around in the image.
- * @param integer $y The y coordinate (pixels) that the drawn string should
- * be centered around in the image.
- * @param string $string The actual string that should be drawn.
- * @param array $color The color that the drawn string should have as an array
- * with the RGB values.
- * @access private
- */
- function drawStringCentered(&$img, $font, $x, $y, $string, $color) {
- $textWidth = imagefontwidth($font) * strlen($string);
- $xLoc = $x + $font - $textWidth / 2;
- imagestring($img, $font, $xLoc, $y, $string, $color);
- }
-
- /**
- * Draws a specified string with a specified color aligned right on specified
- * coordinates on a specified image.
- * @param image $img The image that the string should be drawn on.
- * @param integer $font The font that the drawn string should have.
- * @param integer $x The x coordinate (pixels) for the rightmost part of
- * string.
- * @param integer $y The y coordinate (pixels) that the drawn string should
- * be centered around in the image.
- * @param string $string The actual string that should be drawn.
- * @param array $color The color that the drawn string should have as an array
- * with the RGB values.
- * @access private
- */
- function drawStringRightAligned(&$img, $font, $x, $y, $string, $color) {
- $textWidth = imagefontwidth($font) * strlen($string);
- $xLoc = $x - $textWidth;
- imagestring($img, $font, $xLoc, $y, $string, $color);
- }
-
- /**
- * Describes a graph that takes values and then transforms them into a
- * graphical representation as a graph.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class Graph {
- /**
- * Holds all the colors that have been loaded into the graph. The name
- * that identifies the color as key, the actual color (RGB array) as value.
- * @var array
- */
- private $colors;
- /**
- * The number of units that each pixel should represent. The array has two
- * keys, 'x' and 'y'.
- * @var array
- */
- private $scale;
- /**
- * Contains arrays for each function which contain a key 'func' which
- * contains a GraphFunction instance, and a key 'color' which contains
- * the color identifier to use when drawin the function.
- * displayed in the graph.
- * @var array
- */
- private $functions;
- /**
- * The size, in pixels, of the graph. The array has two keys, 'x' and 'y'.
- * @var array
- */
- private $size;
-
- /**
- * Constructor for Graph.
- * @param integer $width The width, in pixels, that the graph should have.
- * @param integer $height The height, in pixels, that the graph should
- * have.
- */
- public function __construct($width = 600, $height = 300) {
- $this->size = array(
- 'x' => $width,
- 'y' => $height
- );
-
- //Initialise fields.
- global $COLOR_BLACK, $COLOR_WHITE;
- $this->colors = array(
- 'black' => $COLOR_BLACK,
- 'white' => $COLOR_WHITE
- );
- $this->scale = array(
- 'x' => 10,
- 'y' => 10
- );
- $this->functions = array();
- }
-
- /**
- * Adds a specified color into the graph. Colors have to be loaded before
- * they can be used.
- * @param string $name The name that should be connected to the color, any
- * subsequent uses of this color will have to specify this name.
- * @param integer $red A byte [0-255] representing the amount of red in the
- * color where 0 is none.
- * @param integer $green A byte [0-255] representing the amount of green in
- * the color where 0 is none.
- * @param integer $blue A byte [0-255] representing the amount of blue in
- * the color where 0 is none.
- */
- public function addColor($name, $red, $green, $blue) {
- $this->colors[$name] = array($red, $green, $blue);
- }
-
- /**
- * Adds a function that should be displayed in the graph.
- * @param string $name The name of the function.
- * @param array $function A strict function where the key is the function's
- * parameter and the corresponding value is the function's value for
- * the parameter. Both the parameters and key have to be numerical.
- * @param string $color The identifier for the color that should be used
- * when drawing the function.
- */
- public function addFunction($name, $function, $color = 'black') {
- $this->functions[] = array(
- 'func' => new GraphFunction($name, $function),
- 'color' => $color
- );
- }
-
- /**
- * Removes all functions with a specified name.
- * @param string $name The name of the function(s) that should be removed,
- */
- public function removeFunction($name) {
- foreach($this->functions as $key => $func) {
- if($func->getName() == $name) {
- //Remove the function.
- unset($this->functions[$key]);
- //Continue searching, in case there are more functions with the
- //same name
- }
- }
- }
-
- /**
- * Constructs and retrieves an image resource representing the graph.
- * @param string $backgroundColor The identifier for the color that should
- * be used for the background. The default is white.
- * @param string $axisColor The identifier for the color that should be
- * used for the axes. The default is white.
- * @param array $paramMarkers The parameter values that should be marked
- * and labeled on the axes.
- * @param array $valueMarkers The function values that should be marked
- * and labeled on the axes.
- * @param string $axisColor The identifier for the color that should be
- * used for the axis. The default is black.
- * @param callback $paramNameCallback An optional callback which is used
- * when labeling the parameters.
- * @param callback $valueNameCallback An optional callback which is used
- * when labeling the values.
- * @param array $viewport Specifies the viewport for graph. I.e. the
- * minimum and maximum values and paramters that should be allowed in
- * the graph. There are are four keys: 'valMin', 'valMax', 'paramMin',
- * 'paramMax'. I.e. min value, max value, min param and max param.
- * @param string $gridColor The identifier for the color that should
- * be used for the grids. The default is black.
- * @param boolean $xGrid True if the x axel should show a grid, false
- * otherwise.
- * @param boolean $yGrid True if the y axel should show a grid, false
- * otherwise.
- * @return image An image resource with the graph on it.
- */
- public function getGraph($backgroundColor = 'white', $axisColor = 'black',
- $paramMarkers = array(), $valueMarkers = array(),
- $paramNameCallback = '', $valueNameCallback = '',
- $viewport = array(), $gridColor = 'black', $xGrid = false,
- $yGrid = false) {
- //Create the image
- $img = imagecreate($this->size['x'], $this->size['y']);
-
- //Load all colors
- $colors = array();
- foreach($this->colors as $name => $color) {
- list($r, $g, $b) = $color;
- $colors[$name] = imagecolorallocate($img, $r, $g, $b);
- }
-
- //Set the viewport
- if(!isset($viewport['valMin'])) {
- $viewport['valMin'] = $this->getLowestValue();
- }
- if(!isset($viewport['valMax'])) {
- $viewport['valMax'] = $this->getHighestValue();
- }
- if(!isset($viewport['paramMin'])) {
- $viewport['paramMin'] = $this->getLowestParameter();
- }
- if(!isset($viewport['paramMax'])) {
- $viewport['paramMax'] = $this->getHighestParameter();
- }
-
- //Calculate the scale (units per pixel), add the axel padding
- $paddingX = $paddingY = GRAPH_PADDING;
- $dimX = $this->size['x'] - $paddingX;
- $dimY = $this->size['y'] - $paddingY;
- $scaleX = ($viewport['paramMax'] - $viewport['paramMin']) / $dimX;
- $scaleY = ($viewport['valMax'] - $viewport['valMin']) / $dimY;
-
- //Create the axes.
- if(!isset($colors[$axisColor])) {
- $axisColor = 'black';
- }
- //Convert the param markers to labels.
- $paramMin = $this->getLowestParameter();
- $paramLabels = array();
- foreach($paramMarkers as $marker) {
- if($paramNameCallback != '') {
- $label = $paramNameCallback($marker);
- } else {
- $label = $marker;
- }
- $paramLabels[($marker - $paramMin) / $scaleX] = $label;
- }
- //Construct the x axis.
- $xAxis = new Axis($paramLabels);
- $xAxis->setGrid($xGrid);
-
- //Convert the value markers to labels.
- $valueMin = min($valueMarkers);
- $valueLabels = array();
- foreach($valueMarkers as $marker) {
- if($valueNameCallback != '') {
- $label = $valueNameCallback($marker);
- } else {
- $label = $marker;
- }
- $valueLabels[($marker - $valueMin) / $scaleY] = $label;
- }
- //Construct the y axis.
- $yAxis = new Axis($valueLabels);
- $yAxis->setGrid($yGrid);
-
- //Set the background
- if(!isset($colors[$backgroundColor])) {
- $backgroundColor = 'white';
- }
- imagefilledrectangle($img, 0, 0, $this->size['x'], $this->size['y'],
- $colors[$backgroundColor]);
-
- //Draw everything
- $origo = $this->size['y'] - $paddingY;
-
- $xAxis->drawAxis($img, $colors[$axisColor], $colors[$gridColor],
- $paddingX, $origo, $this->size['x'], $origo);
- $yAxis->drawAxis($img, $colors[$axisColor], $colors[$gridColor],
- $paddingX, $origo, $paddingX, 0);
-
- foreach($this->functions as $function) {
- $function['func']->drawFunction($img, $colors[$function['color']], $paddingX,
- $paddingY, $this->getHighestParameter(),
- min($valueMarkers), max($valueMarkers),
- $scaleX, $scaleY);
- }
-
- imageinterlace($img, 1);
- return($img);
- }
-
- /**
- * Gets the lowest value defined in the graph.
- * @return numeric A value defined in the graph.
- */
- private function getLowestValue() {
- $minValues = array();
- foreach($this->functions as $function) {
- $minValues[] = $function['func']->getLowestValue();
- }
- return min($minValues);
- }
-
- /**
- * Gets the highest value defined in the graph.
- * @return numeric A value defined in the graph.
- */
- private function getHighestValue() {
- $maxValues = array();
- foreach($this->functions as $function) {
- $maxValues[] = $function['func']->getHighestValue();
- }
- return max($maxValues);
-
- }
-
- /**
- * Gets the lowest parameter which has a connected value in the graph.
- * @return numeric A parameter from one of the functions.
- */
- private function getLowestParameter() {
- $minParams = array();
- foreach($this->functions as $function) {
- $minParams[] = $function['func']->getLowestParameter();
- }
- return min($minParams);
- }
-
- /**
- * Gets the highest parameter which has a connected value in the graph.
- * @return numeric A parameter from one of the functions.
- */
- private function getHighestParameter() {
- $maxParams = array();
- foreach($this->functions as $function) {
- $maxParams[] = $function['func']->getHighestParameter();
- }
- return max($maxParams);
- }
- }
-
- /**
- * Desribes an graph axis. The axis is graded and can extend those marks to a
- * grid if desired. The grades' labels can be altered with callbacks.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class Axis {
- /**
- * True if the axis should display a grid, false otherwise. The default is
- * false.
- * @var boolean
- */
- private $displayGrid;
- /**
- * The labels that should be printed along the axis. The key is the
- * distance in pixels along the axis that the corresponding value should
- * be centered around.
- * @var array
- */
- private $labels;
-
- /**
- * Constructor for axis. Sets the labels that should be used for the axis.
- * @param array $labels The key should be the distance in pixels along
- * the axis that the corresponding value should be centered around.
- * @param array $color An optional RGB color array representing the color
- * that the axis and its labels should have.
- */
- public function __construct($labels) {
- $this->labels = $labels;
-
- $this->displayGrid = false;
- }
-
- /**
- * Sets if grids should be used or not. One can also specify what color the
- * grid should be.
- * @param boolean $useGrids True if grids should eminate from the axis
- * wherever an label is displayed.
- * @param array $gridColor An optional RGB color array representing the
- * color that the grid should have. The default is black.
- */
- public function setGrid($useGrids) {
- $this->displayGrid = $useGrids;
- }
-
- /**
- * Draws the axis at a specified position in a specified image.
- * @param color $color The color that should be used to draw the axis.
- * @param color $gridColor the Color that should be used to draw the axis'
- * grids.
- * @param integer $startX The x coordinate where the axis should start.
- * @param integer $startY The y coordinate where the axis should start.
- * @param integer $endX The x coordinate where the axis should end.
- * @param integer $endY The y coordinate where the axis should end.
- */
- public function drawAxis(&$img, $color, $gridColor, $startX, $startY, $endX, $endY) {
- drawArrow($img, $startX, $startY, $endX, $endY,
- AXIS_POINT_LENGTH, AXIS_WIDTH, $color);
-
- //Draw the markers and grid
- if($this->displayGrid === true) {
- $markerLength = 1000;
- } else {
- $markerLength = AXIS_MARKER_SIZE;
- }
-
- //Normalize the axis.
- $axisLength = sqrt(pow($endX - $startX, 2) + pow($endY - $startY, 2));
- $vNormalized = array(($endX - $startX)/$axisLength,
- ($endY - $startY)/$axisLength);
- //Create the normal of the axis for the markers and grid.
- if($endX == $startX) {
- //Avoid division by 0.
- $angle = pi()/2;
- } else {
- $angle = atan(($endY - $startY) / ($endX - $startX));
- }
- if($angle > pi()/4) {
- //Rotate pi/2 radians.
- $vNormal = array(-$vNormalized[1], $vNormalized[0]);
- } else {
- //Rotate -pi/2 radians.
- $vNormal = array($vNormalized[1], -$vNormalized[0]);
- }
-
- $origo = array($startX, $startY);
- //Draw the markers, grid and labels.
- foreach($this->labels as $distance => $label) {
- $markerStart = $this->addVectors(
- $origo, $this->scaleVector($vNormalized, $distance));
-
- if(($markerStart[0] < $origo[0]) || ($markerStart[1] > $origo[1]) ||
- ($markerStart[0] > $endX) || ($markerStart[1] < $endY)) {
- continue;
- }
-
- $markerEnd = $this->addVectors($markerStart,
- $this->scaleVector($vNormal, $markerLength));
-
- if($angle > pi()/4) {
- drawStringRightAligned($img, 2, $markerStart[0], $markerStart[1]-7,
- $label, $color);
- } else {
- drawStringCentered($img, 2, $markerStart[0], $markerStart[1],
- $label, $color);
- }
-
- imageline($img, $markerStart[0], $markerStart[1], $markerEnd[0],
- $markerEnd[1], $gridColor);
- }
- }
-
- /**
- * Scales a specified vector with a specified scalar.
- * @param array $vector The vector to scale.
- * @param integer $scalar The scalar to scale with.
- * @return array A scaled vector.
- */
- private function scaleVector($vector, $scalar) {
- $result = array();
- foreach($vector as $key => $value) {
- $result[$key] = $value * $scalar;
- }
- return $result;
- }
-
- /**
- * Adds two of vectors.
- * @param array $vector1 The first vector to add.
- * @param array $vector2 The second vector to add.
- * @return array A vector sum.
- */
- private function addVectors($vector1, $vector2) {
- $result = $vector1;
- foreach($vector2 as $key => $value) {
- if(!isset($result[$key])) {
- $result[$key] = $value;
- } else {
- $result[$key] += $value;
- }
- }
- return $result;
- }
- }
-
- /**
- * Describes a function that can be displayed in a graph.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class GraphFunction {
- /**
- * The name that should represent the function. The name can be used to
- * identify the function.
- * @var string
- */
- private $name;
- /**
- * The values represent a strict function where the key is the function's
- * parameter and the corresponding value is the function's value for the
- * parameter. Both the parameters and key have to be numerical.
- * @var array
- */
- private $values;
-
- /**
- * Constructor for GraphFunction.
- * @param string $name The name of the function, this acts as an identifier
- * for the function and should hence be unique.
- * @param array $values The values that represent a strict function where
- * the key is the function's parameter and the corresponding value is
- * the function's value for the parameter. Both the parameters and key
- * have to be numerical.
- */
- public function __construct($name, $values) {
- $this->name = $name;
- $this->values = array();
- //Make sure that the function's key are integers.
- foreach($values as $key => $value) {
- $this->values[round($key)] = $value;
- }
- }
-
- /**
- * Gets the name of the function.
- * @return string A function name.
- */
- public function getName() {
- return $this->name;
- }
-
- /**
- * Gets the lowest value that is taken by the function.
- * @return integer A numerical value.
- */
- public function getLowestValue() {
- return min($this->values);
- }
-
- /**
- * Gets the highest value that is taken by the function.
- * @return integer A numerical value.
- */
- public function getHighestValue() {
- return max($this->values);
- }
-
- /**
- * Gets the lowest parameter that has a value in the function.
- * @return integer A numerical value.
- */
- public function getLowestParameter() {
- return min(array_keys($this->values));
- }
-
- /**
- * Gets the highest parameter that has a value in the function.
- * @return integer A numerical value.
- */
- public function getHighestParameter() {
- return max(array_keys($this->values));
- }
-
- /**
- * Draws the function at a specified position in a specified image.
- * @param image $image The image that the function should be drawn on.
- * @param color $color The color that should be used when drawing the
- * function, it has to have been allocated to the image.
- * @param integer $startX The x coordinate (pixels) where the function
- * should start.
- * @param integer $startY The y coordinate (pixels) where the function
- * should start.
- * @param integer $highestParam The highest parameter that should be drawn.
- * Any higher parameters should be clipped.
- * @param integer $lowestValue The lowest value that the function is
- * allowed to take on. Any values lower than this should be clipped.
- * @param integer $highestValue The highest value that the function is
- * allowed to take on. Any values higher than this should be clipped.
- * @param integer $scaleX The number of units that each pixel should
- * represent in x axis.
- * @param integer $scaleY The number of units that each pixel should
- * represent in y axis.
- */
- public function drawFunction(&$img, $color, $startX, $startY, $highestParam,
- $lowestValue, $highestValue, $scaleX, $scaleY) {
- $paramBase = min(array_keys($this->values));
- $height = imagesy($img) - $startY;
-
- $firstX = false;
- foreach($this->values as $x => $y) {
- //Fit them in the viewport
- if($x > $highestParam) {
- $x = $highestParam;
- }
- if($y < $lowestValue) {
- $y = $lowestValue;
- }
- if($firstX === false) {
- $firstX = $x;
- }
-
- $x -= $paramBase;
- //Invert the y coordinate.
- $y = ($height * $scaleY + $lowestValue) - $y;
-
- if(!isset($lastX) || !isset($lastY)) {
- $lastX = $x;
- $lastY = $y;
- continue;
- }
-
- imageline($img,
- $lastX / $scaleX + $startX,
- $lastY / $scaleY,
- $x / $scaleX + $startX,
- $y / $scaleY,
- $color
- );
- $lastX = $x;
- $lastY = $y;
- }
-
- //Draw the name of the function if there is one.
- if($this->name != '') {
- $middleX = round(($lastX + $firstX) / 2);
- if(!isset($this->values[$middleX])) {
- //Use the first parameter instead.
- $keys = array_keys($this->values);
- $middleX = $keys[current($this->values)];
- }
- $middleY = $height * $scaleY + $lowestValue - $this->values[$middleX];
- $middleX -= $paramBase;
- drawStringCentered($img, 2,
- $middleX / $scaleX + $startX,
- $middleY / $scaleY,
- $this->name, $color);
- }
- }
- }
-
- ?>