/* * Created on 18.2.2005 * */ package viewer; import java.awt.*; import java.awt.event.*; import java.applet.*; import java.util.ArrayList; import java.util.HashMap; import org.nfunk.jep.type.Complex; /** COMPLEX VIEWER APPLET - by Matti Paalanen (2008) * * The purpose of this Applet is to offer the user a way to visualize complex functions easily with two distinct coordinate areas. * Other area is used to draw points with freehand, or the user can drive animations which are then mapped with the defined function * and the outcome is shown on the righthand coordinate plane. * * Program uses parser logic by Leigh Brookshaw, published under GNU licence. http://www.sci.usq.edu.au/staff/leighb/graph/ * * ViewerApplet class is the main program Class. This represents the Applet itself and initialized all the GUI objects and DrawCanvases. * * There are few panels that represent the drawable and the mapping areas and both have own GUI components. * * Most of the listener logic is in this class. Main responsibilites include handling of the various button triggerings, slider and actualizing * the function mappings between the coordinate areas. * * */ public class ViewerApplet extends Applet implements AdjustmentListener, ActionListener { public DrawCanvas origCanvas; //The actual drawable surface public DrawCanvas mapCanvas; //The surface that shows the outcome of the function mapping private Panel origPanel; private Panel mapPanel; private SweepThread thread; // Thread for animations to use public int sweepMode; // which animation is driven private Scrollbar sweepSpeed; private Panel functionPanel; private TextField realPartFunction; private TextField imaginaryPartFunction; private TextField complexFunction; private boolean complexMode = false; private Button applyFunction; private Button detailMode; private Label realPart; private Label imgPart; private Label complexLabel; private Button clear; private Button xsweep; private Button ysweep; private Button xysweep; private Button circlesweep; private Button rectsweep; private Button fillMode; private Button grid; private Button radius; private TextField origXsmall; //limit values that scale the original area boundaries private TextField origXlarge; private TextField origYsmall; private TextField origYlarge; private Button scaleOrigXup; private Button scaleOrigXdown; private Button scaleOrigYup; private Button scaleOrigYdown; private Button rescaleOrig; private TextField mapXsmall; //limit values that scale the map area boundaries private TextField mapXlarge; private TextField mapYsmall; private TextField mapYlarge; private Button scaleMapXup; private Button scaleMapXdown; private Button scaleMapYup; private Button scaleMapYdown; private Button rescaleMap; private Button lineMode; private double currentX = 0; private double currentY = 0; private double targetX = 0; private double targetY = 0; ParseFunction realParser; ParseFunction imaginaryParser; org.nfunk.jep.JEP complexParser = new org.nfunk.jep.JEP(); HashMap pointCache = new HashMap(); public static int EDGE = 20; public static int ACCURACY = 10; public void init() { super.init(); addNotify(); setLayout(null); complexParser.addStandardFunctions(); complexParser.addStandardConstants(); complexParser.addComplex(); complexParser.addVariable("z", 0, 0); complexParser.addVariable("x", 0); complexParser.addVariable("y", 0); setSize(Integer.parseInt(getParameter("apWidth")),Integer.parseInt(getParameter("apHeight"))); setBackground(Color.GRAY); functionPanel = new Panel(); functionPanel.setLayout(null); realPartFunction = new TextField(); realPartFunction.setText(""); imaginaryPartFunction = new TextField(); imaginaryPartFunction.setText(""); complexFunction = new TextField(); complexFunction.setText("z^2"); applyFunction = new Button("Ok"); applyFunction.addActionListener(this); lineMode = new Button("Lines"); lineMode.addActionListener(this); detailMode = new Button("Detail"); detailMode.addActionListener(this); realPart = new Label("-> x: "); imgPart = new Label("-> y: "); complexLabel = new Label("f(z) ="); functionPanel.add(realPart); functionPanel.add(imgPart); functionPanel.add(complexLabel); functionPanel.add(realPartFunction); functionPanel.add(imaginaryPartFunction); functionPanel.add(applyFunction); functionPanel.add(complexFunction); functionPanel.add(lineMode); functionPanel.add(detailMode); origCanvas = new DrawCanvas(this,true); origPanel = new Panel(); origPanel.setLayout(null); origPanel.add(origCanvas); origXsmall = new TextField(); origXsmall.setText("-10"); origPanel.add(origXsmall); origXlarge = new TextField(); origXlarge.setText("10"); origPanel.add(origXlarge); origYsmall = new TextField(); origYsmall.setText("-10"); origPanel.add(origYsmall); origYlarge = new TextField(); origYlarge.setText("10"); scaleOrigXdown = new Button("+"); scaleOrigXup = new Button("-"); scaleOrigYdown = new Button("+"); scaleOrigYup = new Button("-"); scaleOrigXdown.addActionListener(this); scaleOrigXup.addActionListener(this); scaleOrigYdown.addActionListener(this); scaleOrigYup.addActionListener(this); origPanel.add(scaleOrigXdown); origPanel.add(scaleOrigXup); origPanel.add(scaleOrigYdown); origPanel.add(scaleOrigYup); origPanel.add(origYlarge); rescaleOrig = new Button("Rescale"); rescaleOrig.addActionListener(this); mapCanvas = new DrawCanvas(this, false); mapPanel = new Panel(); mapPanel.setLayout(null); mapPanel.add(mapCanvas); mapXsmall = new TextField(); mapXsmall.setText("-10"); mapPanel.add(mapXsmall); mapXlarge = new TextField(); mapXlarge.setText("10"); mapPanel.add(mapXlarge); mapYsmall = new TextField(); mapYsmall.setText("-10"); mapPanel.add(mapYsmall); mapYlarge = new TextField(); mapYlarge.setText("10"); scaleMapXdown = new Button("+"); scaleMapXup = new Button("-"); scaleMapYdown = new Button("+"); scaleMapYup = new Button("-"); mapPanel.add(scaleMapXdown); mapPanel.add(scaleMapXup); mapPanel.add(scaleMapYdown); mapPanel.add(scaleMapYup); scaleMapXdown.addActionListener(this); scaleMapXup.addActionListener(this); scaleMapYdown.addActionListener(this); scaleMapYup.addActionListener(this); mapPanel.add(mapYlarge); rescaleMap = new Button("Rescale"); rescaleMap.addActionListener(this); clear = new Button("Clear"); clear.addActionListener(this); origPanel.add(clear); xsweep = new Button("X-sweep"); xsweep.addActionListener(this); origPanel.add(xsweep); ysweep = new Button("Y-sweep"); ysweep.addActionListener(this); origPanel.add(ysweep); xysweep = new Button("XY-sweep"); xysweep.addActionListener(this); origPanel.add(xysweep); circlesweep = new Button("circle"); circlesweep.addActionListener(this); origPanel.add(circlesweep); rectsweep = new Button("rect"); rectsweep.addActionListener(this); origPanel.add(rectsweep); fillMode = new Button("fill"); fillMode.addActionListener(this); origPanel.add(fillMode); grid = new Button("grid"); grid.addActionListener(this); origPanel.add(grid); radius = new Button("radius"); radius.addActionListener(this); origPanel.add(radius); sweepSpeed = new Scrollbar(Scrollbar.HORIZONTAL,215,10,45,435); sweepSpeed.addAdjustmentListener(this); origPanel.add(sweepSpeed); origPanel.add(rescaleOrig); mapPanel.add(rescaleMap); setVisible(true); setCompBounds(); add(origPanel); add(mapPanel); add(functionPanel); origCanvas.repaint(); mapCanvas.repaint(); } private void setCompBounds() { origPanel.setBounds(4,10,getWidth()/2 - 30,getHeight()-60); origCanvas.setBounds(60,40,origPanel.getWidth()-120,origPanel.getWidth()-120); clear.setBounds(origPanel.getWidth()/2-100,origPanel.getHeight()-30, 50,20); xsweep.setBounds(5,origPanel.getHeight()-70, 50,20); ysweep.setBounds(5,origPanel.getHeight()-50, 50,20); xysweep.setBounds(5,origPanel.getHeight()-30, 50,20); circlesweep.setBounds(5,origPanel.getHeight()-90,50,20); rectsweep.setBounds(5,origPanel.getHeight()-110,50,20); fillMode.setBounds(5,origPanel.getHeight()-150,50,20); grid.setBounds(5,origPanel.getHeight()-170,50,20); radius.setBounds(5,origPanel.getHeight()-130,50,20); rescaleOrig.setBounds(origPanel.getWidth()/2+70,origPanel.getHeight()-30, 50,20); origXsmall.setBounds(5,origPanel.getHeight()/2,40,20); origXlarge.setBounds(origPanel.getWidth()-50,origPanel.getHeight()/2,40,20); origYsmall.setBounds(origPanel.getWidth()/2-20,origPanel.getHeight()-30,40,20); origYlarge.setBounds(origPanel.getWidth()/2-20,5,40,20); sweepSpeed.setBounds(origPanel.getWidth()/2-80,origPanel.getHeight()-55,160,20); scaleOrigXup.setBounds(5,origPanel.getHeight()/2-30,12,12); scaleOrigXdown.setBounds(5,origPanel.getHeight()/2-18,12,12); scaleOrigYdown.setBounds(origPanel.getWidth()/2-50,5,12,12); scaleOrigYup.setBounds(origPanel.getWidth()/2-38,5,12,12); mapPanel.setBounds(getWidth()/2,10,getWidth()/2 - 30,getHeight()-60); mapCanvas.setBounds(60,40,mapPanel.getWidth()-120,mapPanel.getWidth()-120); rescaleMap.setBounds(mapPanel.getWidth()/2+70,origPanel.getHeight()-30, 50,20); mapXsmall.setBounds(5,mapPanel.getHeight()/2,40,20); mapXlarge.setBounds(mapPanel.getWidth()-50,mapPanel.getHeight()/2,40,20); mapYsmall.setBounds(mapPanel.getWidth()/2-20,mapPanel.getHeight()-30,40,20); mapYlarge.setBounds(mapPanel.getWidth()/2-20,5,40,20); scaleMapXup.setBounds(5,mapPanel.getHeight()/2-30,12,12); scaleMapXdown.setBounds(5,mapPanel.getHeight()/2-18,12,12); scaleMapYdown.setBounds(mapPanel.getWidth()/2-50,5,12,12); scaleMapYup.setBounds(mapPanel.getWidth()/2-38,5,12,12); functionPanel.setBounds(5, getHeight()-50, getWidth()-10, 50); realPart.setBounds(5,5,30,20); realPartFunction.setBounds(40,5, getWidth()/3-50, 20); imgPart.setBounds(getWidth()/3,5,30,20); imaginaryPartFunction.setBounds(getWidth()/3+35,5, getWidth()/3-50, 20); complexLabel.setBounds(5,30,30,20); complexFunction.setBounds(40,30, getWidth()/3-50, 20); applyFunction.setBounds(getWidth()-280,5,30,20); lineMode.setBounds(getWidth()-240,5,40,20); detailMode.setBounds(getWidth()-190,5,40,20); origCanvas.repaint(); mapCanvas.repaint(); } public void adjustmentValueChanged(AdjustmentEvent e) { if(thread != null) thread.sleepTime = 445-sweepSpeed.getValue(); } public void triggerMapping() { if(sweepMode == 6) { mapCanvas.clearPoints(); colorFill(); mapColorFill(); } else if(sweepMode == 7) { fillGrid(); mapGrid(); } else if(sweepMode > 0) { drawSweepLine(thread.counter); } else { mapCanvas.mapPoints(origCanvas.objects); } } public void actionPerformed(ActionEvent e) { if(e.getSource() == clear) { sweepMode = -1; origCanvas.clearPoints(); origCanvas.repaint(); mapCanvas.clearPoints(); mapCanvas.repaint(); exitSweepMode(); } else if(e.getSource() == rescaleMap) { applyRescale(mapXlarge,mapXsmall); applyRescale(mapYlarge,mapYsmall); mapCanvas.repaint(); } else if(e.getSource() == rescaleOrig) { applyRescale(mapXlarge,mapXsmall); applyRescale(mapYlarge,mapYsmall); triggerMapping(); origCanvas.repaint(); } else if(e.getSource() == applyFunction) { origCanvas.clearMappings(); triggerMapping(); if(sweepMode == 6) { colorFill(); mapColorFill(); } } else if(e.getSource() == xsweep) { sweepMode = 1; handleSweep(); } else if(e.getSource() == ysweep) { sweepMode = 2; handleSweep(); } else if(e.getSource() == xysweep) { sweepMode = 3; handleSweep(); } else if(e.getSource() == circlesweep) { sweepMode = 4; handleSweep(); } else if(e.getSource() == rectsweep) { sweepMode = 5; handleSweep(); } else if(e.getSource() == fillMode) { exitSweepMode(); try { Thread.currentThread().sleep(465-sweepSpeed.getValue()); }catch(Exception ex) { } sweepMode = 6; colorFill(); mapColorFill(); } else if(e.getSource() == grid) { exitSweepMode(); try { Thread.currentThread().sleep(465-sweepSpeed.getValue()); }catch(Exception ex) { } sweepMode = 7; fillGrid(); mapGrid(); } else if(e.getSource() == radius) { sweepMode = 8; handleSweep(); }else if(e.getSource() == scaleOrigXdown) { handleScale(origXlarge,origXsmall,true); } else if(e.getSource() == scaleOrigXup) { handleScale(origXlarge,origXsmall,false); } else if(e.getSource() == scaleOrigYdown) { handleScale(origYlarge,origYsmall,true); } else if(e.getSource() == scaleOrigYup) { handleScale(origYlarge,origYsmall,false); } else if(e.getSource() == scaleMapXdown) { handleScale(mapXlarge,mapXsmall,true); } else if(e.getSource() == scaleMapXup) { handleScale(mapXlarge,mapXsmall,false); } else if(e.getSource() == scaleMapYdown) { handleScale(mapYlarge,mapYsmall,true); } else if(e.getSource() == scaleMapYup) { handleScale(mapYlarge,mapYsmall,false); } else if(e.getSource() == lineMode) { mapCanvas.connectLines = !mapCanvas.connectLines; mapCanvas.repaint(); } else if(e.getSource() == detailMode) { if(ACCURACY == 2) { ACCURACY = 10; EDGE = 20; } else { ACCURACY = 2; EDGE = 60; } triggerMapping(); } } private void applyRescale(TextField large, TextField small) { try { double curLarge = Double.parseDouble(large.getText()); double curSmall = Double.parseDouble(small.getText()); if(curLarge<=curSmall) { if(curSmall > 0) curLarge = curSmall*2; else if(curSmall < 0) curLarge = -1*curSmall; else curLarge = 100; } large.setText(""+curLarge); small.setText(""+curSmall); origCanvas.repaint(); mapCanvas.repaint(); triggerMapping(); } catch(Exception ex) { } } private void handleScale(TextField large, TextField small, boolean increase) { try { double curLarge = Double.parseDouble(large.getText()); double curSmall = Double.parseDouble(small.getText()); if(curLarge<=curSmall) { if(curSmall > 0) curLarge = curSmall*2; else if(curSmall < 0) curLarge = -1*curSmall; else curLarge = 100; } double curDif = curLarge-curSmall; double newLarge = curLarge+curDif/2; double newSmall = curSmall-curDif/2; if(increase) { newLarge = curLarge-curDif/4; newSmall = curSmall+curDif/4; } large.setText(""+newLarge); small.setText(""+newSmall); origCanvas.repaint(); mapCanvas.repaint(); if(large==origXlarge || large == origYlarge) triggerMapping(); } catch(Exception ex) { } } Color[][] gridTable = {{new Color(0,0,255), new Color(255,0,0)}, {new Color(150,0,150), new Color(255,0,255)}}; private ArrayList gridFill() { sweepPoints.clear(); double[] limits = getSourceLimits(origCanvas); double width = limits[1] - limits[0]; double height = limits[2] - limits[3]; double actualX; double actualY; int halfW = origCanvas.getWidth()/2; int halfH = origCanvas.getHeight()/2; for(int i=EDGE;i<=origCanvas.getWidth()-EDGE;i=i+ACCURACY) { actualX = limits[0] + ((1.0*i) / origCanvas.getWidth()) * width; for(int j=EDGE;j<=origCanvas.getHeight()-EDGE;j=j+ACCURACY) { actualY = limits[2] - ((1.0*j) / origCanvas.getHeight()) * height; Point added = new Point(actualX, actualY); if(ACCURACY == 10) added.color = gridTable[i/halfW][j/halfH]; if(origCanvas.getHeight()-j 0) { try { realParser = new ParseFunction(realPart); realParser.parse(); }catch(Exception ex) { ex.printStackTrace(); } } String imagPart = imaginaryPartFunction.getText(); if(imagPart != null && imagPart.length() > 0) { try { imaginaryParser = new ParseFunction(imagPart); imaginaryParser.parse(); }catch(Exception ex) { ex.printStackTrace(); } } String complexText = complexFunction.getText(); if(complexText != null && complexText.length() > 0) { try { complexParser.parseExpression(complexText); complexMode = true; }catch(Exception ex) { complexParser.parseExpression("z"); } } } public double[] getMappedCoords(double x, double y) { double[] coords = {x, y}; double[] cached = (double[])pointCache.get(new CoorKey(x,y)); if(cached != null) { return cached; } try { if(complexMode) { complexParser.addVariable("z", x, y); complexParser.addVariable("x", x); complexParser.addVariable("y", y); Complex value = complexParser.getComplexValue(); coords[0] = value.re(); coords[1] = value.im(); } else { coords[0]=realParser.getResult(x,y); coords[1]=imaginaryParser.getResult(x,y); } }catch(Exception ex) { } pointCache.put(new CoorKey(x,y), coords); return coords; } private void handleSweep() { if(!origCanvas.sweepMode) { origCanvas.sweepMode(true); origCanvas.clearPoints(); mapCanvas.clearPoints(); if(sweepMode == 3) thread = new SweepThread(this, origCanvas.getWidth()*2,445-sweepSpeed.getValue()); else if(sweepMode == 4 || sweepMode == 5) thread = new SweepThread(this, origCanvas.getWidth()/2,445-sweepSpeed.getValue()); else if(sweepMode == 8) thread = new SweepThread(this, 360, 445-sweepSpeed.getValue()); else thread = new SweepThread(this, origCanvas.getWidth(),445-sweepSpeed.getValue()); thread.start(); } else { exitSweepMode(); } } public synchronized void drawSweepLine(int counter) { origCanvas.drawSweepLine(counter,sweepMode); mapCanvas.objects.clear(); mapCanvas.mapPoints(mapSweepLine(counter)); } private ArrayList mapFill() { sweepPoints.clear(); double[] limits = getSourceLimits(origCanvas); double width = limits[1] - limits[0]; double height = limits[2] - limits[3]; for(int i=0;i= 5 || forced) { triggerMapping(); moveCounter = 0; } } else { double curXSmall = Double.parseDouble(mapXsmall.getText()); double curXLarge = Double.parseDouble(mapXlarge.getText()); double curYSmall = Double.parseDouble(mapYsmall.getText()); double curYLarge = Double.parseDouble(mapYlarge.getText()); currentX = targetX; currentY = targetY; mapXsmall.setText(""+(curXSmall+moveX)); mapXlarge.setText(""+(curXLarge+moveX)); mapYsmall.setText(""+(curYSmall+moveY)); mapYlarge.setText(""+(curYLarge+moveY)); canvas.repaint(); } } catch(Exception ex) { } } public void moveCoordinates(double xMove, double yMove, DrawCanvas moved, boolean forced) { double[] limits = getSourceLimits(moved); double width = limits[1] - limits[0]; double height = limits[2] - limits[3]; double actualXMove = (3.0*xMove)/getWidth() * width; double actualYMove = (-3.0*yMove)/getHeight() * height; moveCanvasView(actualXMove, actualYMove, moved, forced); } public void clearPointCache() { pointCache.clear(); } }