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: * */ public static final int X = 300; /** * The height, in pixels, of the trace plane display. Good defaults are: * */ 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(); } }