Page 1 of 1

GD Math Function Graphs

Posted: Sun Aug 13, 2006 9:31 am
by Verminox
This is my first and only script using GD because I am usually a non-graphical person. Anyhow, what this script does is it will plot the graph of any mathematical function. The function can be explicit [ y = f(x) ] or parametric [ x = g(t); y = f(t) ].

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; //&#948;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();

?>
Click Here to see how the above graph looks

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();
?>
Click Here to see how the above graph looks

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 :)

Posted: Sun Aug 13, 2006 12:24 pm
by Ambush Commander
Ooh, that looks really really awesome! If you want, I could host a live copy of it on my webserver (you'll need to license it though, right now, we're operating under the assumption all rights reserved).

Suggestions:

* Antialiasing! Although I have no clue how you would go about implementing that
* Tangent line doesn't get labeled on the graph, you may want to automatically calculate the equation and then print it
* Make the grid configurable

Code issues:

* The aliases to the constants are understandable, but you probably want to use the real ones in your code. I think we can let the functions lie though
* I'd recommend namespacing your class names, in case you want this to piggyback off of other classes (although right now it is stand-alone). You should also capitalize them

That's amazing. Kudos!

Posted: Sun Aug 13, 2006 12:47 pm
by feyd
Ambush Commander wrote:* Antialiasing! Although I have no clue how you would go about implementing that
Subpixel calculation is the general way to "correctly" do them. Basically, you double, triple or quadruple the number of operations by doing partial pixels to know which pixels get how much color. It's not all that fun, but it looks really nice afterward. When doing subpixel I used to keep an internal temporary array of the pixel data so further calculations could more easily interact with previous pixel data. Granted, you can interrogate the internal image data.

Posted: Mon Aug 14, 2006 12:30 am
by Verminox
Ambush Commander wrote:Ooh, that looks really really awesome! If you want, I could host a live copy of it on my webserver (you'll need to license it though, right now, we're operating under the assumption all rights reserved).
Thanks, that would be great! I'll get in contact with you later.
* Antialiasing! Although I have no clue how you would go about implementing that
I tried using imageline() in the graph::plot() loop from the co-ords of the current pixel to those of the previously plotted pixel, but it just became slow and did not look too good. If you decrease the 'increment' property of the locus then it is possible to get a more clear graph but again, that will take more time to run.
* Tangent line doesn't get labeled on the graph, you may want to automatically calculate the equation and then print it
See the last line of tangent::_construct(). I've made the 'legend' property set to false. This is because the calculation of the derivative dy/dx and the x1 and y1 co-ordinates often goes into floats, which will make the equation very long and well, just not easily readable in general. That is why I made no legend for it. Perhaps I can make it print a legend such as "Tangent for <locus>" instead of the equation then.
* Make the grid configurable
Hmmm.... How exactly do you mean? Like make it mark at every 0.5 units, etc. ?
* The aliases to the constants are understandable, but you probably want to use the real ones in your code. I think we can let the functions lie though
If and when I use this as a web application to plot graphs from direct user input, you cannot be sure that the user knows the PHP constants, which is why I have used those functions, constants' aliases, as well as allowing 'x^y' and '2x' in the equation (although those preg_replace() often give unexpected results with complex input).
* I'd recommend namespacing your class names, in case you want this to piggyback off of other classes (although right now it is stand-alone). You should also capitalize them
Name-spacing? Not to familiar with PHP5 OOP to be honest, but if you mean like the way PEAR classes are handled, then yeah... I don't see the point of it though when you just have 3 classes. Its not a framework, so its not like you are going to use these classes with other PHP code (mostly)... It's probably gonna be just creating an image with these 3 classes. Capitalization - Alright.


Thanks for the critique though.


@feyd: Subpixel? *clueless*

Posted: Mon Aug 14, 2006 12:50 am
by feyd
basically what subpixel calculation is, is when you make calculations for a much larger image size but scale the results into one or more pixels at lower levels of fill than full brightness.

Posted: Mon Aug 14, 2006 5:36 am
by Verminox
Ahh alright. I'll see what I can do.

Posted: Mon Aug 14, 2006 8:11 am
by onion2k
The easy way to do anti-aliasing is, rather than making an 800*800 image to start with, make a 3200*3200 image, and then use imagecopyresampled() to scale it down to 800*800.

But you'll need plenty of memory allocated to PHP..

Posted: Wed Aug 16, 2006 10:03 am
by Verminox
Ok so I tried my hand at anti-aliasing.

Here is the new version of the script (now Licensed too)

Code: Select all

<?php
/*
 * Title: GD Math Function Graphs
 * Desc: Plot graphs of explicit/parametric functions and tangents to them
 * Author: Verminox <verminox@gmail.com>
 * License: Creative Commons 2.5 - http://creativecommons.org/licenses/by/2.5/deed.en_GB
 * Version: 1.6
 * Compatibility: PHP 5 with GD 2.0.1 or better
 */

//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/asin($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 $anti_aliasing;		//Amount of clarity. Int between 1-10. More clarity, more memory required.
	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,$anti_aliasing = 1){
		$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;
		$this->anti_aliasing = (int)$anti_aliasing;
		
		//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->anti_aliasing;
		$this->height  = $this->total_y * $this->scale_y * $this->anti_aliasing;
	
		//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']);
		}
		
		//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 * $this->anti_aliasing;
		return $pixel;
	}
	
	private function unit2pixel_y($unit){
		$pixel = ($this->max_y - $unit) * $this->scale_y * $this->anti_aliasing;
		return $pixel;
	}
	
	private function pixel2unit_x($pixel){
		$unit = $this->min_x + ($pixel / ($this->scale_x * $this->anti_aliasing));
		return $unit;
	}
	
	private function pixel2unit_y($pixel){
		$unit = $this->max_y - ($pixel / ($this->scale_y * $this->anti_aliasing));
		return $unit;
	}
	
	//Plot the graph of 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, 3, 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( $this->anti_aliasing != 1 ){
			$img = imagecreatetruecolor( $this->width / $this->anti_aliasing , $this->height / $this->anti_aliasing ) and
			imagecopyresampled ( $img, $this->img, 0, 0, 0, 0, $this->width / $this->anti_aliasing, $this->height / $this->anti_aliasing, $this->width, $this->height );
		} else {
			$img = $this->img;
		}
	
		if( $filename ) {
			$draw = imagepng($img,$filename);
		} else {
			header("Content-type: image/png");
			header("Pragma: No-cache");
			$draw =  imagepng($img);
		}
		imagedestroy($img);
		return $draw;
	}
	
	//The image can be destroyed either manually...
	public function destroy(){
		return imagedestroy($this->img);
	}
	
	//...or automatically at the end of script execution. So there will never be a memory overload.
	public function __destruct(){
		return @imagedestroy($this->img);
	}
	
}

/* Main Locus Class; Both Explicit and Parametric */

class Locus {
	
	public $x = 't'; //Equation for x = g(t)
	public $y = 'x'; //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; //&#948;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
	}
	
}

?>




Basically, the new thing is the last param in Graph::__construct(). It is an integer by which the scale/dimensions of the graph will be multiplied while making the image, and then the image will resampled to the desired size when Graph::draw() is called.

So the following:

Code: Select all

$graph = new Graph(-10,10,-10,10,40,40,2);
Should be a 800X800 image (20 units * 40 px/unit on both axes). However, due to the last param being '2', the image will be made as 1600X1600 and scaled at 80px/unit while plotting the graph, then resized back to 800X800 when it is drawn.


My only problem now is getting the text font size to also multiply along with the rest of the graph. I don't know how to specify text size using imagestring(). So when the image is resized back to normal the text becomes really very small. Any help?


Edit: Oh and another thing.... it seems the whole idea was useless after all. The higher I specify the multiplier, the ligher the image becomes when it is resized. In fact, it is so faint that you can see almost nothing if the multiplier is > 4. Bummer :(

Posted: Wed Aug 16, 2006 5:37 pm
by Weirdan
The higher I specify the multiplier, the ligher the image becomes when it is resized. In fact, it is so faint that you can see almost nothing if the multiplier is > 4. Bummer
Perhaps you need to post-process the image with Unsharp Mask after resampling

Here's sample implementation: http://community.livejournal.com/ru_php ... #t10251502