import java.awt.*;
import java.awt.event.*;
import java.util.*;
/**
* A class which shows a piece of the complex plane for the graphical display
* and entry of traces.
*
* @author Jonathan A. Poritz
* @version 1.7, 17 Sep 1998
*/
public class TracePlanePanel extends Plane {
/**
* A reference to the containing inputpanel.
*
* @see FDBApplet#inputpanel
*/
public InputPanel inputpanel;
/**
* The location of a mousedown in this plane, to compute the extent of
* a drag that may occur; in abstract coordinates.
*/
protected double[] prev = new double[2];
/**
* The location of a mousedown in this plane, to compute the extent of
* a drag that may occur; in pixel coordinates.
*/
protected int[] previ = new int[2];
/**
* Tells whether the trace plane has been decomposed since last window
* change.
*/
public boolean decomposed;
/**
* Array of colors for the pixels of the trace plane when it has been
* decomposed.
*/
public Color domaincolors[][]; //colors of the decomposed domains
/**
* The abstract x-coordinate of the left side of the trace plane display.
*/
public static final double CX = -2.5D;
/**
* The abstract y-coordinate of the upper edge of the trace plane display.
*/
public static final double CY = 4.5D;
/**
* The width, in abstract units, of the trace plane display.
*/
public static final double SX = 5D;
/**
* The height, in abstract units, of the trace plane display.
*/
public static final double SY = 5D;
/**
* The width, in pixels, of the trace plane display. Good defaults are:
*
- 200
*
- for small screens,
*
- 300
*
- for large screens (then use 875x450 in HTML), and
*
- 500
*
- for screen-grabbing huge images (then use 1000x650 in HTML)
*
*/
public static final int X = 300;
/**
* The height, in pixels, of the trace plane display. Good defaults are:
* - 200
*
- for small screens,
*
- 300
*
- for large screens (then use 875x450 in HTML), and
*
- 500
*
- for screen-grabbing huge images (then use 1000x650 in HTML)
*
*/
public static final int Y = 300;
/**
* An image into which to store the decomposition of the current window on
* the trace plane.
*/
public Image backgroundimage;
/**
* The Graphics object for drawing the background image of the decomposed
* trace plane.
*/
public Graphics backgroundgraphics;
/**
* Constructor for TracePlanePanels.
*
* @param nptpnl the ambient inputpanel
*/
public TracePlanePanel(InputPanel nptpnl) {
// make the basic plane with axes and xticks
super(CX, CY, SX, SY, X, Y);
draw_axes = true;
show_xticks = true;
// have not decomposed the traceplane, but set aside an array of
// colors for when we will
decomposed = false;
domaincolors = new Color[X][Y];
// save a reference to the inputpanel and display the label
// indicating the window of abstract coordinates that are displayed
inputpanel = nptpnl;
inputpanel.traceplanelabel.setText(this.toString());
// deal with mouse presses
this.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
// get the pixel coordinates of the press
int x = e.getX();
int y = e.getY();
// convert to abstract coordinates
double xx = (pixelsToAbstract(x, y))[0];
double yy = (pixelsToAbstract(x, y))[1];
int modifiers = e.getModifiers();
if ((modifiers & InputEvent.BUTTON3_MASK) != 0) {
// right button zoom, so save mouseDown location
previ[0] = x;
previ[1] = y;
}
else if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
// center button translate, so save mouseDown location
prev[0] = xx;
prev[1] = yy;
previ[0] = x;
previ[1] = y;
}
else if ((yy != 0D) || (Math.abs(xx) >= 2D) || (inputpanel.outputpanel.fordordirichlet.getSelectedIndex() == 0) || (inputpanel.dist != 0D)) {
// left button trace selection, only make the change if allowed,
// i.e., no change of trace if it is elliptic or parabolic and the
// domain is Dirichlet and the axis distance is zero
inputpanel.trace.x = xx;
if (yy < 0D)
inputpanel.trace.y = 0D;
else
inputpanel.trace.y = yy;
inputpanel.update();
repaint();
}
}
});
// deal with mouse drags
this.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
// get the current pixel coordinates
int x = e.getX();
int y = e.getY();
// convert to abstract coordinates
double xx = (pixelsToAbstract(x, y))[0];
double yy = (pixelsToAbstract(x, y))[1];
int modifiers = e.getModifiers();
if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
// center button translate
corner[0] += prev[0] - xx;
corner[1] += prev[1] - yy;
inputpanel.traceplanelabel.setText(this.toString());
// invalidate decomposition
decomposed = false;
draw_axes = true;
repaint();
}
else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) {
// right button zoom; invalidate decomposition
decomposed = false;
draw_axes = true;
zoom(1D - ((double)(previ[1] - y)) / 50D);
}
else if ((yy != 0D) || (Math.abs(xx) >= 2D) || (inputpanel.outputpanel.fordordirichlet.getSelectedIndex() == 0) || (inputpanel.dist != 0D)) {
// left button trace selection, only make the change if allowed,
// i.e., no change of trace if it is elliptic or parabolic and the
// domain is Dirichlet and the axis distance is zero
inputpanel.trace.x = xx;
if (yy < 0D)
inputpanel.trace.y = 0D;
else
inputpanel.trace.y = yy;
inputpanel.update();
repaint();
}
previ[0] = x;
previ[1] = y;
}
});
}
/**
* Paint the TracePlanePanel, either painting the simple plane or the
* background image of the decomposition, then putting a point at the
* currently selected trace and writing all the tickmarks.
*
* @param g the Graphics object upon which to paint the TracePlanePanel
*/
public void paint(Graphics g) {
if (decomposed && (backgroundimage != null)) {
// have a decomposition and the corresponding image, so draw it
g.drawImage(backgroundimage, 0, 0, this);
}
else {
// have no background, so draw the basic plane
super.paint(g);
}
// draw a small disk where the current trace is and then make the ticks
g.setColor(Color.black);
int[] tp = abstractToPixels(inputpanel.trace.x, inputpanel.trace.y);
g.fillOval(tp[0] - 2, tp[1] - 2, 4, 4);
drawTickmarks(g);
}
/**
* Scales the size of the visible window onto the trace plane.
*
* @param factor the scaling factor, can be any positive number
*/
public void zoom(double factor) {
corner[0] = corner[0] + .5D * size[0] - .5D * size[0] * factor;
size[0] *= factor;
corner[1] = corner[1] - .5D * size[1] + .5D * size[1] * factor;
size[1] *= factor;
inputpanel.traceplanelabel.setText(this.toString());
repaint();
}
/**
* Computes a decomposition of the visible portion of the trace plane by
* the combinatorial type of the corresponding fundamental domain, building
* a color image from the decompostion which can then be used as the
* background of the trace plane display; then redraws the trace plane.
*
*/
public void decompose() {
// no axes with the decomposed background
draw_axes = false;
// increments in the x and y directions for one pixel
double pixelx = size[0]/((double) X);
double pixely = size[1]/((double) Y);
// a list of the combinatorial types found so far in the scan
Vector typesfound = new Vector();
// an array of the indices in the Vector typesfound for each pixel on the
// background image; some special values: -1 indicates type {1},
// -2 means impossible type
int[][] colorindex = new int[X][Y];
double ycoord = corner[1];
for (int j=0; j < Y; j++) {
// announce via progress label the fraction of the y-values done
inputpanel.decomposeprogress.setText("Fraction of visible trace-plane processed: "+Complex.DoubleToString(((double) (100*j))/((double) Y), 2)+"%");
if (ycoord <= DomainCombinatorics.CIRCLAPRES) {
// the y value is less than the computational precision, all the
// remaining pixels have traces of negative imaginary part, which
// we will count as type {1}; fill with -1's and break loop
do {
for (int i=0; i < X; i++)
colorindex[i][j] = -1;
} while (++j < Y);
break;
}
double xcoord = corner[0];
for (int i=0; i < X; i++) {
// make a domain combinatorics for the trace at the current
// pixel at the location of the scan
DomainCombinatorics dc = new DomainCombinatorics(new Complex(xcoord, ycoord), inputpanel.dist, inputpanel.outputpanel.fordordirichlet.getSelectedIndex());
// make the corresponding domain combinatorics label, then see if it
// is an unknown -- impossible type, will have a "?" in the label --
// type {1}, a previously found color or a new one -- which must then
// be inserted in typesfound
String dclabel = dc.makeLabel();
if (dclabel.indexOf("?") >= 0)
colorindex[i][j] = -2;
else if (dclabel.equals("{1}"))
colorindex[i][j] = -1;
else if (typesfound.contains(dclabel))
colorindex[i][j] = typesfound.indexOf(dclabel);
else {
colorindex[i][j] = typesfound.size();
typesfound.addElement(dclabel);
}
xcoord += pixelx;
}
ycoord -= pixely;
}
// number distinct types found
double siz = (double) typesfound.size();
// make the array of colors depending on the types found, white if type
// {1}, black if impossible type and a non-linear scaled version of the
// colorindex otherwise
for (int i=0; i < X; i++) {
for (int j=0; j < Y; j++) {
if (colorindex[i][j] == -1)
domaincolors[i][j] = Color.white;
else if (colorindex[i][j] == -2)
domaincolors[i][j] = Color.black;
else {
int k = (int) (((double) 0xFFFFFF)*(Math.sqrt((siz - 1 - ((double) colorindex[i][j]))/siz)));
domaincolors[i][j] = new Color(k);
}
}
}
// clear out the progress label
inputpanel.decomposeprogress.setText("");
// if we don't already have one, make an image to conatin the background
// of the trace plane from the computed colors
if (backgroundimage == null) {
backgroundimage = createImage(X, Y);
backgroundgraphics = backgroundimage.getGraphics();
}
// now fill in the image
for (int i=0; i < X; i++) {
Color prevcolor = domaincolors[i][0];
boolean lastblack = false;
for (int j=0; j < Y; j++) {
Color dmnclr = domaincolors[i][j];
if (prevcolor.equals(dmnclr) || lastblack) {
// use the computed color if it hasn't changed from the pixel
// immediately above or if that pixel was made black because of
// failing this test last iteration...
backgroundgraphics.setColor(dmnclr);
lastblack = false;
}
else {
// ...otherwise use black
backgroundgraphics.setColor(Color.black);
lastblack = true;
}
// preserve current color for comparison next cycle and draw rectangle
// into the background image of the correct color
prevcolor = dmnclr;
backgroundgraphics.drawRect(i, j, 1, 1);
}
}
// set successful decomposition and redraw
decomposed = true;
repaint();
}
}