Variables and Graphical Plots in Java

This tutorial builds on the Numerical Expression Solver tutorial, adding textual variables such as “e” or “pi”, and custom ones. Then you will create an applet that takes advantage of this new functionality by creating a graphical representation of an arbitrary expression at each point in space. This applet is shown below.


Starting with the Expression.java file you created last time, add the following variable:


	/*
	* Used for getting variables
	*/
	Variable get;

This is an object of a type we haven’t defined yet, which will serve to return numerical values for variables.

Insert the following variable() function:


	/*
	* Evaluates variable terms.
	*/
	double variable(){
		double a;
		StringBuffer buf = new StringBuffer();
		while( s.length() > 0 && Character.isLetter( s.charAt( 0 ) ) ){
			buf.append( s.charAt( 0 ) );
			s = s.substring( 1 );
		}
		String v = buf.toString();
		if( v.compareTo( "e" ) == 0 ){
			a = Math.E;
		} else if( v.compareTo( "pi" ) == 0 ){
			a = Math.PI;
		} else {
			a = get.variable( v );
		}
		return a;
	}

For our purposes, a “variable” is defined as any contiguous sequence of alphabetic characters. This function firsts collects all the contiguous letters, and then checks to see if it recognizes them as one of its built-in constants, “e” or “pi”. If not, it makes a call to the get object, which hopefully will recognize the variable and return the appropriate value.

This function is not being called yet, so we need to modify the term() function as follows:


	double term(){
		double ans = 0;
		StringBuffer temp = new StringBuffer();

		if( Character.isLetter( s.charAt( 0 ) ) ){	// new lines
			return variable();			// ...
		}						// end new lines

		while( s.length() > 0 && Character.isDigit( s.charAt( 0 ) ) ){
			temp.append(Integer.parseInt( "" + s.charAt( 0 ) ));
			s = s.substring( 1 );
		}
	
		...
	
	}

These newly inserted lines check to see whether the term is a number or a variable. If it starts with a letter, we assume it is a variable and deal with it by calling variable(). Otherwise, we continue as we did before, assuming it is a number.

Now we need to modify the constructor Expression() by changing the first couple lines:


	public Expression( String s, Variable v ){
		get = v;
		// remove white space, assume only spaces or tabs
		StringBuffer b = new StringBuffer();
		
		...

	}

We changed the declaration to accept a Variable object as an additional argument, and then assigned it to our variable get. If you know that your expression will contain know variables, you may pass null as the second argument.

Finally, in order to run this from the command-line, you will have to change the main() function to pass null as a second argument to the Expression() constructor:


	public static void main( String[] args ){
		Expression e = new Expression( args[0], null );
		System.out.println( e + " = " + e.evaluate() );
	}

These are all the changes that we need to make to Expression.java. However, you can’t compile it until we create the interface declaration Variable.java, given below in its entirety:


public interface Variable{
	/*
	* Returns the value of given variable.
	*/
	public double variable( String s );
}

This interface allows any object to respond to the variable() function called by an Expression instance when it encounters an unknown variable.

You can now compile and run Expression.java as before. You can now enter “e” for the number 2.718… or “pi” for 3.141… These two “variables” function as numbers now.

Now let’s create the graphical plotter that uses this new functionality. Create the file ExpressionApplet.java as follows:


import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

public class ExpressionApplet extends java.applet.Applet implements Variable{
	Image img;				// image buffer used to draw plot
	Expression exp;			// the Expression we wish to draw
	TextField input;		// the user input field for the Expression
	double x, y;			// the currently evaluated variables
	double left = -Math.PI, right = Math.PI, top = Math.PI, bottom = - Math.PI;
							// our bounds
	
	/*
	* Set up the interface, create an initial Expression,
	* and draw it.
	*/
	public void init(){
		input = new TextField("x^2+y^2",20);
		Button b = new Button( "Draw" );
		final Variable v = this;
		input.addActionListener( new ActionListener(){
				public void actionPerformed( ActionEvent e ){
					exp = new Expression( input.getText(), v );
					img = null;
					repaint();
				}
			} );
		b.addActionListener( new ActionListener(){
				public void actionPerformed( ActionEvent e ){
					exp = new Expression( input.getText(), v );
					img = null;
					repaint();
				}
			} );
		add( input );
		add( b );
		exp = new Expression("x^2+y^2", this);
		repaint();
	}
}

As you can see, we have a good number of instance variables. The init() function takes care of setting things up for us, including creating an initial Expression, and calling repaint(), which draws the results of evaluating the Expression.

If you looked carefully at the above code, you would have noticed that the string passed to the Expression to evaluate includes “x” and “y”, which it can’t evaluate. This is where the Variable interface comes into play. Expression recognizes that “x” and “y” are variables, but doesn’t know what they are, so it calls its get.variable() function to evaluate them. As you might have guessed since the declaration of ExpressionApplet says that it implements the Variable interface, and in fact the applet is passed to the Expression constructor, we will implement that function here:


	/*
	* Looks for variables we define, and returns them
	* appropriately. Otherwise, returns NaN.
	*/
	public double variable( String s ){
		if( s.compareTo( "x" ) == 0 || s.compareTo( "X" ) == 0 )
			return x;
		if( s.compareTo( "y" ) == 0 || s.compareTo( "Y" ) == 0 )
			return y;
		return Math.log(-1);
	}

This function is what makes the graphical element possible. In the paint() function which follows, we will go through every pixel, setting the instance variables x and y appropriately, and then evaluating the Expression. If it contains “x” or “y”, our variable() function will be called.

Now add the paint() function, which does the actual evaluation and drawing of the Expression:


	/*
	* Evaluate and draw the Expression at every pixel.
	*/
	public void paint( Graphics g ){
		double min = 0, max = 0;
		if( img != null ){
			g.drawImage( img, 0, 0, this );
			return;
		}
		for( int y = 0; y < getSize().height; y++ ){
			for( int x = 0; x < getSize().width; x++ ){
				mapToVirtual( x, y );
				double a = exp.evaluate();
				if( a > max )
					max = a;
				else if( a < min )
					min = a;
			}
		}
		int pix[] = new int[ getSize().width * getSize().height ];
		int index = 0;
		for( int y = 0; y < getSize().height; y++ ){
			for( int x = 0; x < getSize().width; x++ ){
				mapToVirtual( x, y );
				pix[ index++ ] = Color.getHSBColor( (float)(exp.evaluate()/(max-min) - min/(max-min)), 1, 1 ).getRGB();
			}
		}
		img  = createImage( new MemoryImageSource( getSize().width, getSize().height, pix, 0, getSize().width ) );
		g.drawImage( img, 0, 0, this );
	}

The first thing to notice is that we check to see if the img variable is already defined before we do anything else. If it is defined, then paint() is being called simply because it was obscured, and now needs to be refreshed. If we didn't test for this case, we would have to re-evaluate the expression at every pixel every time something happened to require the applet to refresh. The reason this works is that when the user enters a new expression, we set the img variable to null.

Next we run through every pixel in our applet, calling mapToVirtual(), which sets the instance variables x and y to the appropriate values to be returned to the Expression, since most of the time the pixel index won't correspond to the coordinate we want it to. We then evaluate the expression and find the minimum and maximum values.

Once we have done this, we create an array of integers to hold our pixel color information, and reiterate through the pixels, this time setting them to colors. We have also scaled them to values between 0 and 1 because that is what the function Color.getHSBColor() needs. Once we have filled the pixel array, we create a MemoryImageSource with it, and draw the image. The instance variable img is now non-null, so if the applet needs to refresh, it won't have to go through all that calculation again.

Now we just need the function mapToVirtual():


	/*
	* Map a pixel coordinate to a virtual coordinate.
	*/
	public void mapToVirtual( int x, int y ){
		this.x = ( (right-left)/getSize().width )*x + left;
		this.y = ( (bottom-top)/getSize().height )*y + top;
	}

This function simply maps pixels linearly from their actual coordinates to assumed coordinates, according to the right, left, top, bottom variables declared at the beginning of the class. It sets the instance variables x, y to the virtual coordinates, as required for the variable() function.

You can now compile ExpressionApplet.java and include it in an HTML page as I have done here. If you wish, you may add text fields to set the virtual boundaries, so that you can move around and zoom in and out, instead of being always confined to one view, as here.

Here is the finished file Expression.java.
Here is the finished file Variable.java.
Here is the finished file ExpressionApplet.java.

Leave a Reply

Your email address will not be published. Required fields are marked *