// Mandelbrot set incorporating Mouse listener.
// Added display of appropriate Julia set on right-click
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import java.awt.image.*;
import java.text.*;
class Complex {
// implement complex arithmetic only in so far
// as is needed to compute Z = Z*Z + C
private double r;
private double i;
public Complex(double a, double b) {r = a; i = b;}
public void set(double a, double b) {r = a; i = b;}
public double getMagnitudeSquared() {return r*r + i*i; }
Complex setNextValue(Complex C) {
double rp = r, ip = i;
r = r*r - i*i + C.r;
i = 2*rp*ip + C.i;
return this;
}
public String toString() {return "(" + r + ", " + i + ")";}
}
class Boundary {
// package xLeft, xRight, yCenter
double xLeft;
double xRight;
double yCenter;
double xJulia, yJulia;
boolean julia;
Boundary(double l, double r, double c, boolean j, double xj, double yj) {
xLeft = l; xRight = r; yCenter = c;
julia = j; xJulia = xj; yJulia = yj;
}
boolean equals(Boundary x) {
return xLeft == x.xLeft
&& xRight == x.xRight
&& yCenter == x.yCenter
&& julia == x.julia
&& (julia
? xJulia == x.xJulia && yJulia == x.yJulia
: true);
}
public String toString() {
return xLeft + " " + xRight + " " + yCenter
+ " " + julia + " " + xJulia + " " + yJulia; }
}
class PointArray {
// holds the calculated points
static void setUp(int sizeIn) {
size = sizeIn;
// set up sizeXsize static array of points
arrayPoints = new int[size][size];
}
static void computePoints(boolean pushSwitch) {
if (pushSwitch || stk.empty()) {
Boundary b = new Boundary(xLeft, xRight, yCenter,
julia, xJulia, yJulia);
// Java5: if (stk.empty() || !(b.equals(stk.peek())))
if (stk.empty() || !(b.equals((Boundary)stk.peek())))
stk.push(b);
}
double a, b;
double lowb = yCenter - (xRight - xLeft)/2;
double delta = (xRight - xLeft) / size;
int k;
Complex z = new Complex(0,0);
maxVal = 0; minVal = loopCount;
a = xLeft;
if (!julia) {
for (int i=0; i<size; i++) {
b = lowb;
for (int j=0; j<size; j++) {
z.set(0,0);
C.set(a,b);
for (k=0; k<loopCount; k++)
if (z.setNextValue(C).getMagnitudeSquared() >= 4) break;
arrayPoints[i][j] = k;
if (k < loopCount) {
if (k > maxVal) maxVal = k;
if (k > 0)
if (k < minVal) minVal = k;
}
b = b + delta;
}
a = a + delta;
}
}
else {
C.set(xJulia,yJulia);
for (int i=0; i<size; i++) {
b = lowb;
for (int j=0; j<size; j++) {
z.set(a,b);
for (k=0; k<loopCount; k++)
if (z.setNextValue(C).getMagnitudeSquared() >= 4) break;
arrayPoints[i][j] = k;
if (k < loopCount) {
if (k > maxVal) maxVal = k;
if (k > 0)
if (k < minVal) minVal = k;
}
b = b + delta;
}
a = a + delta;
}
}
// set up image from computed points
if (colorArray == null) {
colorArray = new int[12];
// Colors, outside to close in
colorArray[0] = 0xFFF8F8F8; //gray
colorArray[1] = 0xFFFF0000; //red
colorArray[2] = 0xFFFFCC00; //orange
colorArray[3] = 0xFFFFFF00; //yellow
colorArray[4] = 0xFFCCFF00; //yellow-green
colorArray[5] = 0xFF00FF00; //green
colorArray[6] = 0xFF00FFCC; //blue-green
colorArray[6] = 0xFF00FFFF; //cyan
colorArray[7] = 0xFF00CCFF; //light blue
colorArray[8] = 0xFF0000FF; //blue
colorArray[9] = 0xFFCC00FF; //blue-violet
colorArray[10] = 0xFFFF00FF; //magenta
colorArray[11] = 0xFFFF00CC; //red-violet
}
int testVal;
int partition = (maxVal-minVal)/12+1;
image = new BufferedImage(size,size,BufferedImage.TYPE_INT_ARGB);
for (int i=0; i<PointArray.size; i++)
for (int j=0; j<PointArray.size; j++) {
testVal = arrayPoints[i][j];
if (testVal == loopCount) image.setRGB(i,size-j-1,0xFF000000);
else if (testVal == 0) image.setRGB(i,size-j-1,0xFFFFFFFF);
else
image.setRGB(i,size-j-1,colorArray[(int)((testVal-minVal)/partition)]);
}
}
static int[][] arrayPoints;
static int[] colorArray;
static int maxVal;
static int minVal;
static int size;
static BufferedImage image;
static int loopCount = 512;
static double xLeft, xRight, yCenter;
static double xJulia, yJulia;
static boolean julia = false;
static Complex C = new Complex(0.0,0.0);
// Java5: static Stack<Boundary> stk = new Stack<Boundary>();
static Stack stk = new Stack();
}
class ButtonHandler extends JPanel implements ActionListener {
// produce and respond to buttons
ButtonHandler(TextHandler textP, MyCanvas graphicsP) { // constructor sets up vertical panel of buttons
super();
textPanel = textP; graphicsPanel = graphicsP;
setLayout(new GridLayout(0,1)); // unlimited rows, 1 column
// add buttons
add(runButton);
add(homeButton);
add(backButton);
add(zoom2Button);
add(out2Button);
add(zoom10Button);
add(out10Button);
add(blkX2Button);
add(blkO2Button);
loopText.setText("512");
add(loopText);
// register button listeners
runButton.addActionListener(this);
homeButton.addActionListener(this);
zoom2Button.addActionListener(this);
zoom10Button.addActionListener(this);
out2Button.addActionListener(this);
out10Button.addActionListener(this);
backButton.addActionListener(this);
blkX2Button.addActionListener(this);
blkO2Button.addActionListener(this);
}
double getDouble(String text, double old) {
double val;
try {
val = Double.parseDouble(text);
}
catch (NumberFormatException nf) {
val = old;
}
return val;
}
int getLoopCount() {
int val;
try {
val = Integer.parseInt(loopText.getText());
}
catch (NumberFormatException nf) {
val = 512;
loopText.setText("512");
}
return val;
}
// buttons
private JButton runButton = new JButton("Run");
private JButton homeButton = new JButton("Home");
private JButton backButton = new JButton("Back");
private JButton zoom2Button = new JButton("X2");
private JButton zoom10Button = new JButton("X10");
private JButton out2Button = new JButton("/2");
private JButton out10Button = new JButton("/10");
private JButton blkX2Button = new JButton("BlkX2");
private JButton blkO2Button = new JButton("Blk/2");
JTextField loopText = new JTextField(7);
// the Listener
// comes here when one of the above buttons is clicked
public void actionPerformed(ActionEvent e) {
double xLeft, xRight, yCenter;
boolean pushSwitch = true;
xLeft = PointArray.xLeft;
xRight = PointArray.xRight;
yCenter = PointArray.yCenter;
String action = e.getActionCommand();
if (action.equals("Run")) {
// get contents of responses
xLeft = getDouble(textPanel.xLeftText.getText(), xLeft);
xRight = getDouble(textPanel.xRightText.getText(), xRight);
yCenter = getDouble(textPanel.yCenterText.getText(), yCenter);
}
else if (action.equals("Home")) {
xLeft = textPanel.ORIGINAL_XLEFT;
xRight = textPanel.ORIGINAL_XRIGHT;
yCenter = textPanel.ORIGINAL_YCENTER;
PointArray.loopCount = 512;
PointArray.julia = false;
loopText.setText(Integer.toString(PointArray.loopCount));
PointArray.stk.clear();
}
else if (action.equals("X2")) {
double quarter = (xRight - xLeft) / 4;
xLeft = xLeft + quarter;
xRight = xRight - quarter;
}
else if (action.equals("/2")) {
double adjust = (xRight - xLeft) / 2;
xLeft = xLeft - adjust;
xRight = xRight + adjust;
}
else if (action.equals("X10")) {
double adjust = 9 * (xRight - xLeft) / 20;
xLeft = xLeft + adjust;
xRight = xRight - adjust;
}
else if (action.equals("/10")) {
double adjust = 9 * (xRight - xLeft) / 2;
xLeft = xLeft - adjust;
xRight = xRight + adjust;
}
else if (action.equals("Back")) {
if (!(PointArray.stk.empty())) {
// Java5: Boundary b = PointArray.stk.pop();
Boundary bc = new Boundary(xLeft,xRight,yCenter,
PointArray.julia,
PointArray.xJulia,
PointArray.yJulia);
Boundary b = (Boundary)PointArray.stk.pop();
if (b.equals(bc) && !(PointArray.stk.empty()))
// current state was TOS get another if available
b = (Boundary)PointArray.stk.pop();
xLeft = b.xLeft;
xRight = b.xRight;
yCenter = b.yCenter;
PointArray.julia = b.julia;
PointArray.xJulia = b.xJulia;
PointArray.yJulia = b.yJulia;
}
}
else if (action.equals("BlkX2")) {
PointArray.loopCount *= 2;
loopText.setText(Integer.toString(PointArray.loopCount));
pushSwitch = false; // just changing loopCount, don't push
}
else if (action.equals("Blk/2")) {
PointArray.loopCount /= 2;
loopText.setText(Integer.toString(PointArray.loopCount));
pushSwitch = false; // just changing loopCount, don't push
}
else return; // do nothing (should never get here)
// set wait cursor for graphicsPanel and buttonPanel
graphicsPanel.setCursor(graphicsPanel.waitCursor);
setCursor(graphicsPanel.waitCursor);
PointArray.xLeft = xLeft;
PointArray.xRight = xRight;
PointArray.yCenter = yCenter;
textPanel.updatePanel();
PointArray.computePoints(pushSwitch);
graphicsPanel.repaint();
}
// other panel locations (set in constructor)
TextHandler textPanel;
MyCanvas graphicsPanel;
}
class TextHandler extends JPanel {
// Set Up label and text fields,
TextHandler() {
super();
// set up text fields in a grid
setLayout(new GridLayout(2,4));
add(xLeftText);
add(xRightText);
add(yCenterText);
sizeText.setEditable(false);
add(sizeText);
add(xLeftTag); add(xRightTag);
add(yCenterTag); add(sizeTag);
PointArray.xLeft = oldXLeft = ORIGINAL_XLEFT;
PointArray.xRight = oldXRight = ORIGINAL_XRIGHT;
PointArray.yCenter = oldYCenter = ORIGINAL_YCENTER;
style = new DecimalFormat("0.0#E0"); // for width display
updatePanel();
}
void updatePanel() {
double xLeft = PointArray.xLeft;
double xRight = PointArray.xRight;
double yCenter = PointArray.yCenter;
xLeftText.setText(Double.toString(xLeft));
xRightText.setText(Double.toString(xRight));
yCenterText.setText(Double.toString(yCenter));
sizeText.setText(style.format(xRight-xLeft));
if (buttonPanel != null)
PointArray.loopCount = buttonPanel.getLoopCount();
}
void setButtonHandler(ButtonHandler b) {buttonPanel = b;}
private JLabel xLeftTag = new JLabel("X left");
JTextField xLeftText = new JTextField(FIELD_WIDTH);
private JLabel xRightTag = new JLabel("X right");
JTextField xRightText = new JTextField(FIELD_WIDTH);
private JLabel yCenterTag = new JLabel("Y center");
JTextField yCenterText = new JTextField(FIELD_WIDTH);
private JLabel sizeTag = new JLabel("Width");
JTextField sizeText = new JTextField(FIELD_WIDTH);
static final double ORIGINAL_XLEFT = -2.0;
static final double ORIGINAL_XRIGHT = 0.5;
static final double ORIGINAL_YCENTER = 0.0;
private static final int FIELD_WIDTH = 25;
double oldXLeft, oldXRight, oldYCenter;
DecimalFormat style;
private ButtonHandler buttonPanel;
}
class MyCanvas extends JPanel {
public MyCanvas() {
super();
}
public void setButton(ButtonHandler bp) {
buttonPanel = bp;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(PointArray.image,null,0,0);
setCursor(defaultCursor);
buttonPanel.setCursor(defaultCursor);
if (PointArray.julia)
g2.drawString(PointArray.C.toString(),
10,getHeight()-10);
}
static Color colorArray[];
static final Cursor defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR);
static final Cursor waitCursor = new Cursor(Cursor.WAIT_CURSOR);
ButtonHandler buttonPanel;
}
class MouseHandler extends MouseAdapter
implements MouseMotionListener {
public MouseHandler(TextHandler textP, MyCanvas panel, ButtonHandler bp) {
super();
p = panel;
textPanel = textP;
buttonPanel = bp;
newJulia = false;
xOld = -1;
}
public void mouseMoved(MouseEvent e) {
// do nothing
}
public void mouseDragged(MouseEvent e) {
if (newJulia) return;
x = e.getX(); y = e.getY();
if (xOld > 0) drawIt(xStart, yStart, xOld, yOld);
drawIt(xStart, yStart, x, y);
xOld = x; yOld = y;
}
public void mouseReleased(MouseEvent e) {
double xLeft,xRight,yCenter,size;
xLeft = PointArray.xLeft;
xRight = PointArray.xRight;
yCenter = PointArray.yCenter;
size = PointArray.size;
xOld = -1;
double highb = yCenter +
(xRight - xLeft) / 2;
double delta = (xRight - xLeft) / size;
xLeft = xLeft + rx*delta;
xRight = xLeft + rw*delta;
yCenter = highb - (ry + rh/2) * delta;
p.setCursor(p.waitCursor); buttonPanel.setCursor(p.waitCursor);
PointArray.xLeft = xLeft;
PointArray.xRight = xRight;
PointArray.yCenter = yCenter;
if (newJulia) {
PointArray.xJulia = xLeft;
PointArray.yJulia = yCenter;
PointArray.julia = true;
PointArray.xLeft = -1.8;
PointArray.xRight = 1.8;
PointArray.yCenter = 0.0;
newJulia = false;
}
textPanel.updatePanel();
PointArray.computePoints(true);
p.repaint();
}
public void mousePressed(MouseEvent e) {
xStart = e.getX(); yStart = e.getY();
if ((e.getModifiersEx() & (InputEvent.BUTTON3_DOWN_MASK
| InputEvent.META_DOWN_MASK))
!= 0) { // right click to initiate Julia set
newJulia = true;
rx = xStart;
ry = yStart;
rh = 0;
rw = 1;
}
}
public void drawIt(int x1, int y1, int x2, int y2) {
g = p.getGraphics();
y2 = y1 + x2 - x1; // force a square
rx = x1; ry = y1;
rw = x2 - x1; rh = y2 - y1;
if (rw < 0) { rw = -rw; rx = rx - rw; }
if (rh < 0) { rh = -rh; ry = ry - rh; }
g.setXORMode(Color.white);
g.drawRect(rx, ry, rw, rh);
}
int xStart, yStart, xOld, yOld, x, y, rx, ry, rw, rh;
Graphics g;
MyCanvas p;
boolean newJulia;
TextHandler textPanel;
ButtonHandler buttonPanel;
}
public class MandelbrotApp extends JApplet {
// class constants
// initial size of some stuff
/* Documentation only, window sizes set by HTML
private static final int WINDOW_WIDTH = 581;
private static final int WINDOW_HEIGHT = 540;
*/
private static final int GRAPH_SIZE = 500;
private static final int WINDOW_LEFT = 10;
private static final int WINDOW_TOP = 20;
// instance variables
// private JFrame window = new JFrame("The Mandelbrot Set");
private MyCanvas gPanel;
// constructor
public void init() {
/* public MandelbrotGUI {
window.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
window.setLocation(WINDOW_LEFT, WINDOW_TOP);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
*/
TextHandler textPanel = new TextHandler();
PointArray.setUp(GRAPH_SIZE);
PointArray.computePoints(true);
gPanel = new MyCanvas();
gPanel.setPreferredSize(new Dimension(GRAPH_SIZE,GRAPH_SIZE));
ButtonHandler buttonPanel = new ButtonHandler(textPanel, gPanel);
textPanel.setButtonHandler(buttonPanel);
MouseHandler mouse = new MouseHandler(textPanel,gPanel,buttonPanel);
gPanel.setButton(buttonPanel);
gPanel.addMouseListener(mouse);
gPanel.addMouseMotionListener(mouse);
// Now set up the window
Container c = getContentPane();
c.add(gPanel,BorderLayout.CENTER);
c.add(textPanel,BorderLayout.SOUTH);
c.add(buttonPanel,BorderLayout.EAST);
/*
window.setVisible(true); // display window
window.toFront();
*/
// System.out.println(gPanel.getSize());
}
}