src/Graph.php
<?php
namespace jlawrence\eos;
// fun class that requires the GD libraries to give visual output to the user
/**
* Equation Graph
*
* Fun class that requires the GD libraries to give visual output of an
* equation to the user. Extends the Parser class.
*
* @author Jon Lawrence <jlawrence11@gmail.com>
* @copyright Copyright ©2005-2013 Jon Lawrence
* @license http://opensource.org/licenses/LGPL-2.1 LGPL 2.1 License
* @package Math
* @subpackage EOS
* @version 3.x
*/
class Graph
{
private static $width = 640;
private static $height = 480;
/**
* @var resource
*/
private static $image;
public static $labelAxis = true;
public static $backgroundColor = array(255, 255, 255);
public static $gridColor = array(150, 150, 150);
public static $axisColor = array(0, 0, 0);
public static $lineColor = array(0, 0, 0);
/**
* Initializer
*
* Sets up the Graph class with an image width and height defaults to
* 640x480
*
* @param int $width Image width
* @param int $height Image height
*/
public static function init($width = 640, $height = 480)
{
// default width and height equal to that of a poor monitor (in early 2000s)
self::$width = $width;
self::$height = $height;
// initialize main class
Parser::init();
//can't really mess this up, return true
return true;
}
/**
* Create GD Graph Image
*
* Creates a GD image based on the equation given with the parameters that are set
*
* @param string $eq Equation to use. Needs variable in equation to create graph, all variables are interpreted as 'x'
* @param integer $xLow Lower x-bound for graph
* @param integer $xHigh Upper x-bound for graph
* @param float $xStep Stepping points while solving, the lower, the better precision. Slow if lower than .01
* @param bool $xyGrid Draw grid-lines?
* @param bool $yGuess Guess the upper/lower yBounds?
* @param int $yLow Lower y-bound
* @param int $yHigh Upper y-bound
* @return null
*/
public static function graph($eq, $xLow, $xHigh, $xStep = null, $xyGrid = false, $yGuess = true, $yLow = null, $yHigh = null)
{
//create our image and allocate the two colors
$img = ImageCreate(self::$width, self::$height);
//The following noinspection needed because the first color allocated is the background, but not used for anything else.
/** @noinspection PhpUnusedLocalVariableInspection */
$bgColor = ImageColorAllocate($img, self::$backgroundColor[0], self::$backgroundColor[1], self::$backgroundColor[2]);
$aColor = ImageColorAllocate($img, self::$axisColor[0], self::$axisColor[1], self::$axisColor[2]);
$lColor = ImageColorAllocate($img, self::$lineColor[0], self::$lineColor[1], self::$lineColor[2]);
$gColor = ImageColorAllocate($img, self::$gridColor[0], self::$gridColor[1], self::$gridColor[2]);
//$black = ImageColorAllocate($img, 0, 0, 0);
//$grey = ImageColorAllocate($img, 150, 150, 150);
//$darkGrey = ImageColorAllocate($img, 50, 50, 50);
if ($xLow > $xHigh)
list($xLow, $xHigh) = array($xHigh, $xLow); //swap function
//Smart xStep calc
if ($xStep == false) {
$xStep = ($xHigh - $xLow) / self::$width;
}
$xStep = abs($xStep);
$hand = null;
$xVars = array();
//If yGuess is true, make sure yLow and yHigh are not set
if ($yGuess) {
$yLow = null;
$yHigh = null;
}
//We want to limit the number of lines/ticks/etc so graph remains readable, set max now
$xMaxLines = 30;
$yMaxLines = 30;
//DEVELOPER, UNCOMMENT NEXT LINE IF WANTING TO PREVENT SLOW GRAPHS
//$xStep = ($xStep < .01) ? $xStep : 0.01;
$xScale = self::$width / ($xHigh - $xLow);
$counter = 0;
// @codeCoverageIgnoreStart
if (Math::$DEBUG) {
$hand = fopen("Graph.txt", "w");
fwrite($hand, "$eq\n");
}
// @codeCoverageIgnoreEnd
for ($i = $xLow; $i <= $xHigh; $i += $xStep) {
$tester = sprintf("%10.3f", $i);
if ($tester == "-0.000") $i = 0;
$y = Parser::solve($eq, $i);
//eval('$y='. str_replace('&x', $i, $eq).";"); /* used to debug my Parser class results */
// @codeCoverageIgnoreStart
if (Math::$DEBUG) {
$tmp1 = sprintf("y(%5.3f) = %10.3f\n", $i, $y);
fwrite($hand, $tmp1);
}
// @codeCoverageIgnoreEnd
// If developer asked us to find the upper and lower bounds for y...
if ($yGuess == true) {
$yLow = ($yLow === null || ($y < $yLow)) ? $y : $yLow;
$yHigh = ($yHigh === null || $y > $yHigh) ? $y : $yHigh;
}
$xVars[$counter] = $y;
$counter++;
}
//Now that we have all the variables stored...find the yScale
$yScale = self::$height / (($yHigh) - ($yLow));
// @codeCoverageIgnoreStart
//Calculate the stepping points for lines now
if ($yHigh - $yLow > $yMaxLines) {
$yJump = ceil(($yHigh - $yLow) / $yMaxLines);
} else {
$yJump = 1;
}
if ($xHigh - $xLow > $xMaxLines) {
$xJump = ceil(($xHigh - $xLow) / $xMaxLines);
} else {
$xJump = 1;
}
// @codeCoverageIgnoreEnd
// add 0.01 to each side so that if y is from 1 to 5, the lines at 1 and 5 are seen
$yLow -= 0.01;
$yHigh += 0.01;
// @codeCoverageIgnoreStart
if (Math::$DEBUG) {
fwrite($hand, $yLow . " -- " . $yHigh . "\n");
}
// @codeCoverageIgnoreEnd
// if developer wanted a grid on the graph, add it now
if ($xyGrid == true) {
// @codeCoverageIgnoreStart
if (Math::$DEBUG) {
fwrite($hand, "Drawing Grid\n");
}
// @codeCoverageIgnoreEnd
for ($i = ceil($yLow); $i <= floor($yHigh); $i += $yJump) {
$i0 = abs($yHigh - $i);
ImageLine($img, 0, $i0 * $yScale, self::$width, $i0 * $yScale, $gColor);
imagestring($img, 1, 2, $i0 * $yScale + 2, $i, $gColor);
}
for ($i = ceil($xLow); $i <= floor($xHigh); $i += $xJump) {
$i0 = abs($xLow - $i);
ImageLine($img, $i0 * $xScale, 0, $i0 * $xScale, self::$height, $gColor);
imagestring($img, 1, $i0 * $xScale + 2, 2, $i, $gColor);
}
}
//Now that we have the scales, let's see if we can draw an x/y-axis
if ($xLow <= 0 && $xHigh >= 0) {
//the y-axis is within our range - draw it.
$x0 = abs($xLow) * $xScale;
ImageLine($img, $x0, 0, $x0, self::$height, $aColor);
for ($i = ceil($yLow); $i <= floor($yHigh); $i += $yJump) {
$i0 = abs($yHigh - $i);
ImageLine($img, $x0 - 3, $i0 * $yScale, $x0 + 3, $i0 * $yScale, $aColor);
//If we want the axis labeled... (call in the allies?)
if (self::$labelAxis) {
imagestring($img, 1, $x0 + 2, $i0 * $yScale + 1, $i, $aColor);
}
}
}
if ($yLow <= 0 && $yHigh >= 0) {
//the x-axis is within our range - draw it.
$y0 = abs($yHigh) * $yScale;
ImageLine($img, 0, $y0, self::$width, $y0, $aColor);
//Create ticks for y
for ($i = ceil($xLow); $i <= floor($xHigh); $i += $xJump) {
$i0 = abs($xLow - $i);
ImageLine($img, $i0 * $xScale, $y0 - 3, $i0 * $xScale, $y0 + 3, $aColor);
//If we want the axis labeled....
if (self::$labelAxis) {
imagestring($img, 1, $i0 * $xScale + 2, $y0 + 1, $i, $aColor);
}
}
}
$counter = 1;
//now graph it all ;]
for ($i = $xLow + $xStep; $i <= $xHigh; $i += $xStep) {
$x1 = (abs($xLow - ($i - $xStep))) * $xScale;
$y1 = (($xVars[$counter - 1] < $yLow) || ($xVars[$counter - 1] > $yHigh)) ? -1 : (abs($yHigh - $xVars[$counter - 1])) * $yScale;
$x2 = (abs($xLow - $i)) * $xScale;
$y2 = (($xVars[$counter] < $yLow) || ($xVars[$counter] > $yHigh)) ? -1 : (abs($yHigh - $xVars[$counter])) * $yScale;
// if any of the y values were found to be off of the y-bounds, don't graph those connecting lines
if ($y1 != -1 && $y2 != -1)
ImageLine($img, $x1, $y1, $x2, $y2, $lColor);
$counter++;
}
// @codeCoverageIgnoreStart
if (Math::$DEBUG) {
fclose($hand);
}
// @codeCoverageIgnoreEnd
self::$image = $img;
}
/**
* Sends JPG to browser
*
* Sends a JPG image with proper header to output
*
* @codeCoverageIgnore
*/
public static function outJPG()
{
header("Content-type: image/jpeg");
ImageJpeg(self::$image);
}
/**
* Sends PNG to browser
*
* Sends a PNG image with proper header to output
*
* @codeCoverageIgnore
*/
public static function outPNG()
{
header("Content-type: image/png");
ImagePng(self::$image);
}
/**
* Output GD Image
*
* Will give the developer the GD resource for the graph that
* can be used to store the graph to the FS or other media
*
* @return Resource GD Image Resource
*/
public static function getImage()
{
return self::$image;
}
/**
* Output GD Image
*
* Alias for eqGraph::getImage()
*
* @return Resource GD Image resource
*/
public static function outGD()
{
return self::getImage();
}
}