I started doing this as a direct web application but then converted it to PHP5 OOP for my convinience, with the classes 'graph', and 'locus'. I also managed to make a 'tangent' class yesterday to draw a tangent at any point of a given graph.
The dimensions of the graph and properties of the functional relation can be highly customized... at least that was the main aim of it.
It hasn't been documented very properly, but I hope you can understand my comments.
The Script:
Code: Select all
<?php
/*
GD Math Function Graphs
Plot graphs and tangents of explicit and parametric relations
Made by Verminox
Classes: graph, locus, tangent
*/
//Constants
define('pi',M_PI);
define('e',M_E);
define('h',0.001);
//Commonly used functions that are not pre-built
function ln($x){
return log($x);
}
function sec($x){
return 1/cos($x);
}
function cosec($x){
return 1/sin($x);
}
function cot($x){
return 1/tan($x);
}
function asec($x){
return 1/acos($x);
}
function acosec($x){
return 1/sin($x);
}
function acot($x){
return 1/atan($x);
}
/* Main Graph Class */
class graph {
private $img; //The GD image resource
public $width, $height; //In Pixels
public $min_x, $max_x; //Limits of the X-axis in Units
public $min_y, $max_y; //Limits of the X-axis in Units
public $scale_x, $scale_y; //Measured in Pixels per Unit
public $grid = true; //Whether or not to mark a unit-wise grid
private $total_x, $total_y; //No. of units. Used internally only
private $legend_y = 25; //Offset from top of graph to mark legend
private $colors = array(); //Colors alloted to the image
public function __construct($min_x,$max_x,$min_y,$max_y,$scale_x,$scale_y){
$this->min_x = $min_x;
$this->max_x = $max_x;
$this->min_y = $min_y;
$this->max_y = $max_y;
$this->scale_x = $scale_x;
$this->scale_y = $scale_y;
//Validate
if ( $this->min_x >= $this->max_x || $this->min_y >= $this->max_y ){
die("Error! Incorrect range for either X axis or Y axis");
}
//Set Variables
$this->total_x = $this->max_x - $this->min_x;
$this->total_y = $this->max_y - $this->min_y;
$this->width = $this->total_x * $this->scale_x;
$this->height = $this->total_y * $this->scale_y;
//Create
$this->img = @imagecreatetruecolor($this->width,$this->height) or die('Cannot Initialize new GD image stream');
//Colors
$this->colors['black'] = imagecolorallocate($this->img, 0, 0, 0);
$this->colors['white'] = imagecolorallocate($this->img, 255, 255, 255);
$this->colors['red'] = imagecolorallocate($this->img, 255, 0, 0);
$this->colors['blue'] = imagecolorallocate($this->img, 0, 0, 255);
$this->colors['orange'] = imagecolorallocate($this->img, 210, 90, 0);
$this->colors['green'] = imagecolorallocate($this->img, 0, 128, 0);
$this->colors['purple'] = imagecolorallocate($this->img, 128, 0, 128);
$this->colors['grey'] = imagecolorallocate($this->img, 204, 204, 204);
//Main Fill
imagefill($this->img,0,0,$this->colors['white']);
//Grid and Marking: X-axis
for ( $unit_x = floor($this->min_x) + 1; $unit_x < $this->max_x; $unit_x++ ){
$pixel_x = $this->unit2pixel_x($unit_x);
if($this->grid) imageline($this->img, $pixel_x, 0, $pixel_x, $this->height, $this->colors['grey']);
imagestring($this->img, 1, $pixel_x-5, 10, $unit_x, $this->colors['black']);
imagestring($this->img, 1, $pixel_x-5, $this->height-20, $unit_x, $this->colors['black']);
}
//Grid and Marking: Y-axis
for ( $unit_y = floor($this->min_y) + 1; $unit_y < $this->max_y; $unit_y++ ){
$pixel_y = $this->unit2pixel_y($unit_y);
if($this->grid) imageline($this->img, 0, $pixel_y, $this->width, $pixel_y, $this->colors['grey']);
imagestring($this->img, 1, 10, $pixel_y-5, $unit_y, $this->colors['black']);
imagestring($this->img, 1, $this->width-20, $pixel_y-5, $unit_y, $this->colors['black']);
}
//Border and axes go later because they have to overwrite any grid lines that coincide
//Draw Border
imagerectangle($this->img,0,0,$this->width-1,$this->height-1,$this->colors['black']);
//X-axis
imageline($this->img,0,$this->unit2pixel_y(0),$this->width,$this->unit2pixel_y(0),$this->colors['black']);
//Y-axis
imageline($this->img,$this->unit2pixel_x(0),0,$this->unit2pixel_x(0),$this->height,$this->colors['black']);
}
//Conversions
private function unit2pixel_x($unit){
$pixel = ($unit - $this->min_x) * $this->scale_x;
return $pixel;
}
private function unit2pixel_y($unit){
$pixel = ($this->max_y - $unit) * $this->scale_y;
return $pixel;
}
private function pixel2unit_x($pixel){
$unit = $this->min_x + ($pixel / $this->scale_x);
return $unit;
}
private function pixel2unit_y($pixel){
$unit = $this->max_y - ($pixel / $this->scale_y);
return $unit;
}
//Plotting a Locus
public function plot($locus){
//Draw a colored box with the equation(s) of the locus (unless $locus->legend is false)
if($locus->legend){
imagefilledrectangle($this->img,25,$this->legend_y,40,$this->legend_y+10,$this->colors[$locus->color]);
imagestring($this->img, 2, 45,$this->legend_y, $locus->express(), $this->colors[$locus->color]);
$this->legend_y += 15;
}
/*
This is the main loop.
We will assume a parameter $t to vary between the limits of the locus
If the locus is parametric, it will be in the form of:
x = g(t);
y = f(t);
If the locus is explicit, it will be in the form of:
x = g(t) = t;
y = f(t) = f(x);
So $x can also be a variable in the equation for $y if $x==$t
Now, for each value of $t, we will get the value for $x and $y in Units.
Then we will convert this value into the absolute pixel value based on the
dimensions of the graph which are done by the conversion functions defined above.
A single point corresponding to the co-ordinates will then be plotted.
*/
for($t = $locus->min; $t <= $locus->max; $t += $locus->increment){
$x = @$locus->g($t);
$y = @$locus->f($t);
if ( $x === NULL || $x === false || $y === NULL || $y === false ){ continue; }
$pixel_x = $this->unit2pixel_x($x);
$pixel_y = $this->unit2pixel_y($y);
if ( $pixel_y < 0 || $pixel_y > $this->height || $pixel_x < 0 || $pixel_x > $this->width ){
continue;
}
imagesetpixel($this->img,$pixel_x,$pixel_y,$this->colors[$locus->color]);
}
}
//Create the image as PNG
public function draw($filename = NULL){
if( $filename ) {
return imagepng($this->img,$filename);
} else {
header("Content-type: image/png");
header("Pragma: No-cache");
return imagepng($this->img);
}
}
//The image can be destroyed either manually...
public function destroy(){
imagedestroy($this->img);
}
//...or automatically at the end of script execution. So there will never be a memory overload.
public function __destruct(){
@imagedestroy($this->img);
}
}
/* Main Locus Class; Both Explicit and Parametric */
class locus {
public $x = 't'; //Equation for x = g(t)
public $y = '0'; //Equation for y = f(t). Note: y can also be f(x) if x==t, i.e Explicit relation
public $color = 'black'; //Key for graph::colors[]
public $min = -10; //Min value for t
public $max = 10; //Max value for t
public $increment = 0.01; //δt i.e small increment in t during the loop for graph::plot()
public $legend = true; //Whether or not to display a legend at the top left of the graph
//Regexp pattern used in self::interpret()
const VAR_PATTERN = '/([\s\(\)\[\]\,\^\*\/\+\-\d])(x|t)([\s\(\)\[\]\,\^\*\/\+\-\d])/';
const EXPLICIT = 't'; //Used in locus::construct() if explicit
//For example: $var = new locus(locus::EXPLICIT, 'sin(x)');
//Constructor. First param can be locus::EXPLICIT if $y is a function of x.
public function __construct($x='t',$y='0'){
$this->x = $x;
$this->y = $y;
}
//Change short hand terms in the equation to PHP-valid syntax
public static function interpret($equation){
$equation = self::pad($equation);
//Change 't' to '$t'. In case of explicit, change 'x' to '$t' as well since $x==$t.
$equation = preg_replace(self::VAR_PATTERN,"$1\$t$3",$equation);
//Shorthand exponential terms. Not for use with complex expressions. Stuff like e^x allowed.
//For complex terms, use pow() instead
$equation = preg_replace('/([^\s\(\,]+)\^([^\s\)\,]+)/',"pow($1,$2)",$equation);
//Change '2x' to '2*x' and '(x+1)(x+2)' into '(x+1)*(x+2)', etc.
$equation = preg_replace('/([\d\)])([\$\(a-z])/',"$1*$2",$equation);
return $equation;
}
//Add whitespace at the ends for matching with self::VAR_PATTERN
protected static function pad($str){
return ' ' . $str . ' ';
}
//For use in legends
public function express(){
return 'x = ' . $this->x . '; y = ' . $this->y . '; ';
}
//x = g(t)
public function g($t){
$exp = self::interpret(self::pad($this->x));
eval("\$value = $exp;");
return $value;
}
//y = f(t)
public function f($t){
$exp = self::interpret(self::pad($this->y));
eval("\$value = $exp;");
return $value;
}
//Self-explanatory
public function is_parametric(){
return (trim($this->x) !== 't');
}
//Alias of $graph->plot($this)
public function plot_on(&$graph){
$graph->plot($this);
}
}
/* Class for constructing a tangent to a given locus at a given point */
class tangent extends locus {
public function __construct(&$locus,$t){
//Finding the derivative
$dy = $locus->f($t+h) - $locus->f($t);
$dx = $locus->g($t+h) - $locus->g($t);
//m = dy/dx => Slope of tangent
$m = ( $dy ) / ( $dx );
//Point of contact of tangent
$x1 = $locus->g($t);
$y1 = $locus->f($t);
/*
Now, we have the slope of the tangent, and point of contact.
By the slope-point form, its equation is
(y - y1) = m(x - x1)
=> y = m(x - x1) + y1
Note that here $m, $x and $y are passed as values instead of variables. The single 'x' part gets interpretted later on while plotting the graph to varying values of $x = $t
*/
$this->y = "( $m * ( x - $x1) ) + $y1";
$this->legend = false; //Not really needed for a tangent, but can be manually changed
}
}
?>Implementation:
Code: Select all
<?php
//Create a graph with scale of 40 pixels per unit
$graph = new graph(-10,10,-10,10,40,40);
//Circle with radius = 5 Units
$circle = new locus('5 * cos(t)','5 * sin(t)');
$circle->min = 0;
$circle->max = 2*pi;
$circle->color = 'red';
$circle->increment = 0.005;
$graph->plot($circle);
//Tangent to the above circle at t = pi/4 [i.e about 45 degrees]
$tangent = new tangent($circle,M_PI/4);
$tangent->color = 'green';
$graph->plot($tangent);
//Sine wave
$trig = new locus(locus::EXPLICIT,'sin(x)');
$trig->color = 'blue';
$graph->plot($trig);
//Exponential curve
$exp = new locus(locus::EXPLICIT,'e^x');
$exp->color = 'purple';
$graph->plot($exp);
//Draw graph
$graph->draw();
//Free memory
$graph->destroy();
?>Code: Select all
<?php
//Create a graph with scale of 40 pixels per unit
$graph = new graph(-10,10,-10,10,40,40);
//Butterfly Curve
$butterfly = new locus(
'sin(t) * ( pow(e,cos(t)) - 2cos(4t) - pow(sin(t/12),5) )',
'cos(t) * ( pow(e,cos(t)) - 2cos(4t) - pow(sin(t/12),5) )' );
$butterfly->min = -100;
$butterfly->max = 100;
$butterfly->increment = 0.01;
$butterfly->color = 'red';
$graph->plot($butterfly);
$graph->draw();
$graph->destroy();
?>Note: I am posting links the the image files rather than the PHP script because I want to save bandwidth. Also, I cannot upload this anywhere because my host does not support PHP5.
Does anybody know any free host that supports PHP5 so that I can show a working version to those who do not have PHP installed locally?
Criticism is greatly appreciated