<*>= <imports> <class mandel> <class DataField> <class MCanvas>
This code is written to a file (or else not used).
We initially specify which packages we are going to use. Since this program is being developed with the JDK version 1.1.1, we must import the new event handling mechanism from the packages in the java.awt.event bundle.
<imports>= import java.awt.*; import java.applet.Applet; import java.awt.event.*;
Used above.
The program is splited in two pieces: one that performs the actual
calculations and consequently draws the Mandelbrot set on the screen, and one
that is responsible for the handling of the user interface. Class mandel
handles the user interface and class MCanvas performs the calculations.
In the new event handling mechanism there are different event classes for each
kind of event. The actual event handling is done in methods that certain
interfaces provide. (For more information consult the Java documentation.)
Class mandel must be able to handle button event (i.e., has the user
pressed a button?) and mouse events. Moreover, since computing the Mandelbrot
Set is an computationally intense operation, we must put the calculations in a
thread of its own. So, we must implement the interface Runnable. (We do
not extend class Thread, because we want our application to run as an
Internet applet.) Class mandel is defined in a relatively standard way.
<class mandel>=
public class mandel extends Applet implements Runnable,
ActionListener,
MouseListener,
MouseMotionListener
{
<variable declarations I>
<method init>
<method run>
<method main>
<method actionPerformed>
<method mousePressed>
<method mouseReleased>
<method mouseDragged>
<dummy method definitions>
}
Used above.
We want our applet to initially draw an image of the Mandelbrot Set with
real-``coordinates'' from -2.0 to 0.5 and imaginary-``coordinates'' from
-1.5 to 1.0.
The user interface is composed of a Canvas (our drawing board), two
buttons to activate the painting procedure, and four text-fields to display
the current ``coordinates''. Each element of the user interface, but the
buttons, is implemented as an extension of a language class.
<variable declarations I>=
final double minR=-2.0, maxR=0.5, minI=-1.5, maxI=1.0;
DataField xmin, xmax, ymin, ymax;
Button drawButton, resetButton;
MCanvas drawingBoard;
Thread MThread;
Used above.
Method init is responsible for setting up the user interface. The
first thing we have to do is to select a layout manager. Next, we put each
element on the screen. Finally, we start the thread.
<method init>=
public void init()
{
<select layout manager>
<put the canvas on the screen>
<put the text fields on the screen>
<put the buttons on the screen>
<start the thread>
}
Used above.
We opt to use the GridBagLayout manager, since this manager gives us
maximum flexibility. With each GridBagLayout manager it is always
associated a GridBagConstraints object. This object is used to define the
way things appear on the screen.
<select layout manager>=
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
setLayout(gridbag);
Used above.
We now start to put components on the screen. The first component is
our drawing board. It is an instance of class MCanvas, i.e., a Canvas
capable to draw the Mandelbrot set. This components is big one so it must
be on a line of its own. Moreover, it must occupy all the available space.
<put the canvas on the screen>=
drawingBoard = new MCanvas(minR, maxR, minI, maxI);
c.insets = new Insets(4,4,5,5);
c.gridheight = 500;
c.gridwidth = GridBagConstraints.REMAINDER;
c.weightx = 1.0;
c.weighty = 1.0;
c.fill = GridBagConstraints.BOTH;
c.anchor = GridBagConstraints.CENTER;
gridbag.setConstraints(drawingBoard, c);
drawingBoard.addMouseListener(this);
drawingBoard.addMouseMotionListener(this);
add(drawingBoard);
Used above.
Text fields are not really text fields: they are instances of class
DataField, which provides a ``compound'' object: a text field and label
above it. The text field is used to display the range within we draw the
Mandelbrot set. We need four DataFields, one for each maximum and
for each minimum. (Maximum and minimum real and maximum and minimum real.)
<put the text fields on the screen>=
xmin = new DataField("xmin", minR);
c.insets= new Insets(1,1,1,1);
c.gridx= GridBagConstraints.RELATIVE;
c.gridy= GridBagConstraints.RELATIVE;
c.gridheight = 1;
c.gridwidth = GridBagConstraints.RELATIVE;
c.weightx = 0.0;
c.weighty = 0.0;
gridbag.setConstraints(xmin, c);
add(xmin);
xmax = new DataField("xmax", maxR);
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(xmax, c);
add(xmax);
ymin = new DataField("ymin", minI);
c.gridwidth=GridBagConstraints.RELATIVE;
gridbag.setConstraints(ymin, c);
add(ymin);
ymax = new DataField("ymax", maxI);
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(ymax, c);
add(ymax);
Used above.
Now we turn our attension to the two buttons. The first one is used by the
user to ``say'' to the program that he/she wants it to draw the selected area.
The second, resetButton, is used to draw the image that is being
drawn when the applet starts. We use the Courier typeface at 16 pt(?),
for the label titles.
<put the buttons on the screen>=
setFont(new Font("Courier", Font.BOLD, 16));
drawButton = new Button("Draw M-Set");
c.gridheight = 2;
c.fill = GridBagConstraints.NONE;
c.gridwidth = GridBagConstraints.RELATIVE;
gridbag.setConstraints(drawButton, c);
drawButton.addActionListener(this);
add(drawButton);
resetButton = new Button("Draw Original M-Set");
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(resetButton, c);
resetButton.addActionListener(this);
add(resetButton);
Used above.
A thread is started by first creating an instance of class Thread, in
our case we use the constructor that accepts only a class/object as an
argument, and then by calling is start method. N.B.: This method has
nothing to do with the start method of the java.applet.Applet.
<start the thread>=
MThread = new Thread(this);
MThread.start();
Used above.
As we said above, we opt to implement multithreading by means of
implementing the interface Runnable. So we must implement the method
run. This method just calls the repaint method of the
drawingBoard, which in turn calls the paint method of this class.
<method run>=
public void run()
{
drawingBoard.repaint();
}
Used above.
By adding the method main it is possible to make our program to work
as a stand alone application. The way the method main is structired is
pretty standard: we create a Frame that will hold all GUI components.
<method main>=
public static void main(String[] args)
{
Frame f = new Frame("Mandelbrot Set Drawing Program");
mandel m = new mandel();
m.init();
f.add("Center", m);
f.pack();
f.setVisible(true);
}
Used above.
When the applet has drawn the Mandelbrot set or some detail of it, we can
specify which section of the image we want to further magnify. This is done
very simple: we put the mouse (actualy the cursor with the help of the pointing
device) on some point. Then we press the left button and keep it pressed while
we drag the mouse to some other point. While the mouse is being dragged the
applet records its movements and displays its position on the four text fields.
When we reach a point of interest (remember this is the second one), we release
the mouse and we have specified the area of interest. If for some reason we
have done some mistake, we reinitiate the procedure. One we have finished
with the ``specification'' procedure we can either draw the specified area by
pressing the drawButton, or re-draw the ``original'' area by pressing the
resetButton.
Depending on the button the user presses, certain actions must take place.
If the user has pressed the resetButton, we must reset the value the
text fields' display and we must reset the value of the variables that
control the actual drawing. This variables, quite naturally, are instance
variables of class MCanvas. In case the user has pressed the
drawButton nothing further happens apart from initiating the drawing
process, something that must happen for the previous case too. This process
is controled by a thread, so we msut be sure that the current thread is
``dead''. Once this is verified, we create a new thread which is then started.
<method actionPerformed>=
public void actionPerformed(ActionEvent e)
{
Object source = e.getSource();
if (source == resetButton)
{
drawingBoard.pmin = minR;
drawingBoard.pmax = maxR;
drawingBoard.qmin = minI;
drawingBoard.qmax = maxI;
xmax.setDataField(maxR);
ymax.setDataField(maxI);
xmin.setDataField(minR);
ymin.setDataField(minI);
if (!MThread.isAlive())
{
MThread = new Thread(this);
MThread.start();
}
}
else if (source == drawButton && !MThread.isAlive())
{
MThread = new Thread(this);
MThread.start();
}
}
Used above.
We turn now our attension to MouseEvent handling. When the user
presses the left mouse button, then he wishes to set the ``upper-corner''
of the new drawing area. The real work is done by object drawingBoard,
here we merely set the text fields. This applies to all MouseEvent
handling methods.
<method mousePressed>=
public void mousePressed(MouseEvent e)
{
xmin.setDataField(drawingBoard.pmin);
ymin.setDataField(drawingBoard.qmin);
}
Used above.
<method mouseReleased>=
public void mouseReleased(MouseEvent e)
{
xmax.setDataField(drawingBoard.pmax);
ymax.setDataField(drawingBoard.qmax);
xmin.setDataField(drawingBoard.pmin);
ymin.setDataField(drawingBoard.qmin);
}
Used above.
<method mouseDragged>=
public void mouseDragged(MouseEvent e)
{
xmax.setDataField(drawingBoard.pmax);
ymax.setDataField(drawingBoard.qmax);
xmin.setDataField(drawingBoard.pmin);
ymin.setDataField(drawingBoard.qmin);
}
Used above.
The following methods are defined in the interfaces the class implements, but since we are not going to make any use of them we define then to do nothing.
<dummy method definitions>=
public void mouseClicked(MouseEvent e)
{ }
public void mouseEntered(MouseEvent e)
{ }
public void mouseExited(MouseEvent e)
{ }
public void mouseMoved(MouseEvent e)
{ }
Used above.
Class DataField is somehow an auxiliary class, in the sence that
an instance of this class does not affect the real computation in any way.
The class defines a TextField with an associated Label.
<class DataField>=
class DataField extends Panel
{
<variable declarations II>
<DataField constructor>
<method setText>
}
Used above.
The class defines three variables: a double which holds the value of
the number displayed on the TextField and a Label which says what the
TextField displays.
<variable declarations II>=
double DataFieldValue;
private Label title;
TextField datafield;
Used above.
The contructor has two parameters: a String which is the label that
the Label will display, and double which will be the initial value
of variable DataFieldValue and, consequently, it will be displayed by
the TextField. In order to proprerly arrange the two Components we
use the GridLayout manager with 2 rows and 1 column.
<DataField constructor>=
DataField (String LabelTitle, double initValue)
{
setLayout(new GridLayout(2,1,2,2));
title = new Label(LabelTitle, Label.CENTER);
add(title);
datafield = new TextField(Double.toString(initValue), 25);
datafield.setEditable(false);
add(datafield);
DataFieldValue = initValue;
}
Used above.
Method setText is used when we want to change the displayed on the
TextField. As a ``side effect'' we change the value of variable
DataFieldValue.
<method setText>=
void setDataField (double d)
{
datafield.setText(Double.toString(d));
DataFieldValue = d;
}
Used above.
We turn now our attention to the definition of class MCanvas, which
is, sort to say, the kernel of this applet. However, I will not make any
attempt to describe the algorithm that draws the Mandelbrot set. However,
it is important to say that the user screen is 500 pixels wide and 500
pixels high. Most variables are related to the algorithm that performs
the calculations.
<class MCanvas>=
class MCanvas extends Canvas implements MouseListener, MouseMotionListener
{
final int XScreen = 500;
final int YScreen = 500;
final double overflow = 1.0e100;
private double DeltaP, DeltaQ;
private int iter;
private double Pmin, Pmax, Qmax, Qmin;
double pmin, pmax, qmin, qmax;
Dimension minSize;
<MCanvas constructor>
<set height and width of canvas>
<handle mouse events>
<draw Mandelbrot set>
}
Used above.
When we create an instance of class MCanvas we pass as parameters
the dimensions related to the "subset" of the M-set. The user can specify
a subset of the M-set by drawing a rectangle on the screen. Currently the
rectangle is invisible, but the as the user moves the mouse with the
left button pressed, he can observe the current position of the cursor
on the screen. The mouse events are handled in two levels: at the
top level we just print the positions and the low level we calculate
the positions. We have covered the way we print the cursor positions,
here, among othewrs, we will cover the way we make these calculations.
<MCanvas constructor>=
public MCanvas(double pmin, double pmax, double qmin, double qmax)
{
super();
this.pmin = Pmin = pmin;
this.pmax = Pmax = pmax;
this.qmin = Qmin = qmin;
this.qmax = Qmax = qmax;
minSize = new Dimension(XScreen, YScreen);
addMouseListener(this);
addMouseMotionListener(this);
}
Used above.
Setting the dimensions is standard Java.
<set height and width of canvas>=
public Dimension getPreferredSize()
{
return getMinimumSize();
}
public synchronized Dimension getMinimumSize()
{
return minSize;
}
Used above.
In order to determine the position of the cursor while it is moving on the
canvas we must use a trick: Initially we get the integer position by
using methods getY() and getX() of
the stantard java.awt.event.MouseEvent class. Then we must make sure
that the position lies in the canvas. Next we determine the real position
by using the relation Xr=Xmin + DeltaX*X, where Xmin is the minimum real
value of X, and DeltaX is equal to Xmax minus Xmin devided by the number of
pixels in the X-direction.
<handle mouse events>=
public void mouseClicked(MouseEvent event)
{ }
public void mousePressed(MouseEvent event)
{
int x=event.getX(), y=event.getY(), X, Y;
X = (x > (XScreen - 1)) ? (XScreen - 1) : ((x < 0) ? 0 : x);
Y = (y > (YScreen - 1)) ? (YScreen - 1) : ((y < 0) ? 0 : y);
pmin = Pmin + DeltaP*X;
qmax = Qmax - DeltaQ*(YScreen-1-Y);
}
public void mouseEntered(MouseEvent event)
{ }
public void mouseExited(MouseEvent event)
{ }
public void mouseReleased(MouseEvent event)
{
int x=event.getX(), y=event.getY(), X, Y;
double temp;
X = (x > (XScreen - 1)) ? (XScreen - 1) : ((x < 0) ? 0 : x);
Y = (y > (YScreen - 1)) ? (YScreen - 1) : ((y < 0) ? 0 : y);
pmax = Pmin + DeltaP*X;
qmin = Qmin + DeltaQ*(YScreen - 1 - Y);
if (qmin > qmax )
{
temp = qmin;
qmin = qmax;
qmax = temp;
}
if (pmin > pmax )
{
temp = pmin;
pmin = pmax;
pmax = temp;
}
}
public void mouseDragged(MouseEvent event)
{
int x=event.getX(), y=event.getY(), X, Y;
double temp;
X = (x > (XScreen - 1)) ? (XScreen - 1) : ((x < 0) ? 0 : x);
Y = (y > (YScreen - 1)) ? (YScreen - 1) : ((y < 0) ? 0 : y);
pmax = Pmin + DeltaP*X;
qmin = Qmin + DeltaQ*(YScreen - 1 - Y);
if (qmin > qmax )
{
temp = qmin;
qmin = qmax;
qmax = temp;
}
if (pmin > pmax )
{
temp = pmin;
pmin = pmax;
pmax = temp;
}
}
public void mouseMoved(MouseEvent event)
{ }
Used above.
As we said above previously we will not explain at all the way we draw the M-Set.
<draw Mandelbrot set>=
Color colour(int a)
{
Color paintingColor = Color.white;
switch (a % 12)
{
case 0: paintingColor = Color.blue;
break;
case 1: paintingColor = Color.cyan;
break;
case 2: paintingColor = Color.darkGray;
break;
case 3: paintingColor = Color.gray;
break;
case 4: paintingColor = Color.green;
break;
case 5: paintingColor = Color.lightGray;
break;
case 6: paintingColor = Color.magenta;
break;
case 7: paintingColor = Color.orange;
break;
case 8: paintingColor = Color.pink;
break;
case 9: paintingColor = Color.red;
break;
case 10: paintingColor = Color.white;
break;
case 11: paintingColor = Color.yellow;
}
return paintingColor;
}
double MSetDist(double cx, double cy)
{
final int MaxIterations = 100;
final double huge = 1000.0;
int i;
double[] xorbit = new double[MaxIterations];
double[] yorbit = new double[MaxIterations];
boolean flag;
double dist, temp;
double xder, yder, x2, y2;
double x, y;
/* initializations */
x = y = 0.0;
dist = 0.0;
xorbit[0] = yorbit[0] = 0.0;
x2 = y2 = 0.0;
iter = 1;
while ((iter < MaxIterations) && ((x2+y2) < huge))
{
temp=x2-y2+cx;
y=2.0*x*y+cy;
x=temp;
x2=x*x;
y2=y*y;
xorbit[iter]=x;
yorbit[iter]=y;
iter++;
}
if ((x2+y2) > huge)
{
xder=0.0;
yder=0.0;
i=0;
flag=false;
while ((i < iter) && (! flag))
{
temp=2.0*(xorbit[i]*xder-yorbit[i]*yder+1.0);
yder=2.0*(yorbit[i]*xder+xorbit[i]*yder);
xder=temp;
flag=Math.max(Math.abs(xder), Math.abs(yder)) > overflow;
i++;
}
if (! flag)
{
dist= Math.log(x2+y2)*Math.sqrt(x2+y2)/
Math.sqrt(xder*xder+yder*yder);
}
}
return dist;
}
public void paint(Graphics g)
{
double x, y;
int iy, iynew;
int irad;
double D, Dnew;
int np;
Pmin = pmin;
Pmax = pmax;
Qmin = qmin;
Qmax = qmax;
DeltaP=(pmax-pmin)/(XScreen - 1);
DeltaQ=(qmin-qmax)/(YScreen -1);
for (np=0; np < (XScreen - 1); np++)
{
iy=0;
x=pmin+DeltaP*np;
D=MSetDist(x,qmin);
while (iy < (YScreen - 1))
{
iynew=iy+(int)(Math.floor(Math.max(1.0, Math.min(20.0, D))));
y=iynew*DeltaQ+qmax;
Dnew=MSetDist(x,y);
if (D <= 0.0)
g.setColor(Color.black);
else
g.setColor(colour(iter));
g.drawLine(np, iy, np, iynew);
iy=iynew;
D=Dnew;
}
}
}
Used above.