// Matrix.java (C) 2001 by Paul Falstad, www.falstad.com import java.io.InputStream; import java.awt.*; import java.awt.image.ImageProducer; import java.applet.Applet; import java.applet.AudioClip; import java.util.Vector; import java.util.Hashtable; import java.util.Enumeration; import java.io.File; import java.net.URL; import java.util.Random; import java.awt.image.MemoryImageSource; import java.lang.Math; import java.text.NumberFormat; import java.awt.event.*; class MatrixCanvas extends Canvas { Matrix pg; MatrixCanvas(Matrix p) { pg = p; } public Dimension getPreferredSize() { return new Dimension(300,400); } public void update(Graphics g) { pg.updateMatrix(g); } public void paint(Graphics g) { pg.updateMatrix(g); } }; class MatrixLayout implements LayoutManager { public MatrixLayout() {} public void addLayoutComponent(String name, Component c) {} public void removeLayoutComponent(Component c) {} public Dimension preferredLayoutSize(Container target) { return new Dimension(500, 500); } public Dimension minimumLayoutSize(Container target) { return new Dimension(100,100); } public void layoutContainer(Container target) { int cw = target.size().width; int cx = target.size().height; int ew = cw-cx; int ct = target.getComponentCount(); target.getComponent(ct-1).move(0, 0); target.getComponent(ct-1).resize(cw, target.size().height); int i; int h = 0; for (i = 0; i < ct-1; i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = m.getPreferredSize(); m.move(cx+(ew-d.width)/2, h); m.resize(d.width, d.height); h += d.height; } } } }; public class Matrix extends Applet implements ComponentListener, ActionListener, AdjustmentListener, MouseMotionListener, MouseListener { Thread engine = null; Dimension winSize; Image dbimage; Random random; public String getAppletInfo() { return "Matrix by Paul Falstad"; } Button identityButton; Button transposeButton; Button invertButton; Button rotateCWButton; Button rotateCCWButton; Button reflectXButton; Button reflectYButton; double vecs[][]; double origmatrix[][]; double eigens[][]; double pointX, pointY; boolean showPoint = false; boolean dragging = false; int selection = -1; Color darkCyan, darkRed, purple; int getrand(int x) { int q = random.nextInt(); if (q < 0) q = -q; return q % x; } MatrixCanvas cv; public void init() { darkCyan = new Color(0, 128, 128); darkRed = new Color(128, 0, 0); purple = new Color(192, 60, 206); setLayout(new MatrixLayout()); cv = new MatrixCanvas(this); cv.addComponentListener(this); cv.addMouseMotionListener(this); cv.addMouseListener(this); add(identityButton = new Button("Identity")); identityButton.addActionListener(this); add(transposeButton = new Button("Transpose")); transposeButton.addActionListener(this); add(invertButton = new Button("Invert")); invertButton.addActionListener(this); add(rotateCWButton = new Button("Rotate CW")); rotateCWButton.addActionListener(this); add(rotateCCWButton = new Button("Rotate CCW")); rotateCCWButton.addActionListener(this); add(reflectXButton = new Button("Reflect X")); reflectXButton.addActionListener(this); add(reflectYButton = new Button("Reflect Y")); reflectYButton.addActionListener(this); add(cv); setBackground(Color.black); setForeground(Color.lightGray); random = new Random(); vecs = new double[2][2]; vecs[0][0] = 1; vecs[0][1] = 0; vecs[1][0] = 0; vecs[1][1] = 1; eigens = new double[2][2]; reinit(); repaint(); } void reinit() { Dimension d = winSize = cv.getSize(); if (winSize.width == 0) return; dbimage = createImage(d.width, d.height); } public void paint(Graphics g) { cv.repaint(); } void findVecCoords(double x, double y, int result[]) { int cy = winSize.height/4; int cx = cy; result[0] = (int) (cx*(x+2)); result[1] = (int) (cy*(2-y)); } void findVecCoords(int num, int result[]) { findVecCoords(vecs[num][0], vecs[num][1], result); } void findXformVecCoords(double x, double y, int result[]) { findVecCoords(vecs[0][0]*x+vecs[1][0]*y, vecs[0][1]*x+vecs[1][1]*y, result); } void drawArrow(Graphics g, int x1, int y1, int x2, int y2) { g.drawLine(x1, y1, x2, y2); double l = java.lang.Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); if (l > 10) { double hatx = (x2-x1)/l; double haty = (y2-y1)/l; int as = 10; g.drawLine(x2, y2, (int) (haty*as-hatx*as+x2), (int) (-hatx*as-haty*as+y2)); g.drawLine(x2, y2, (int) (-haty*as-hatx*as+x2), (int) (hatx*as-haty*as+y2)); } } void drawBar(Graphics g, int offset, double val) { int x = (int) (winSize.width * val/6); int cx = winSize.width/2; int h = 5; int y = winSize.height + h*offset; int y2 = y+h-1; if (val < 0) g.fillRect(cx+x, y, -x, h); else g.fillRect(cx, y, x, h); } boolean isZero(double a) { return (a >= -.00001 && a <= .00001); } void eigenfind(double ev, int num) { double a, b; b = vecs[0][0] - ev; a = -vecs[1][0]; if (isZero(a) && isZero(b)) { b = -vecs[0][1]; a = vecs[1][1]-ev; } double l = java.lang.Math.sqrt(a*a+b*b); if ((a < 0 && b < 0) || a < -b || b < -a) l = -l; if (isZero(l)) eigens[num][0] = eigens[num][1] = 0; else { eigens[num][0] = a/l; eigens[num][1] = b/l; } } String eigenstring(int n, NumberFormat nf, double ev, boolean er, double tr, double ed) { if (er) return "lambda" + n + " = " + nf.format(ev); if (n == 1) return "lambda1 = " + nf.format(tr*.5) + "+" + nf.format(ed*.5) + "i"; return "lambda2 = " + nf.format(tr*.5) + "-" + nf.format(ed*.5) + "i"; } public void updateMatrix(Graphics realg) { int i, j; for (i = 0; i != 2; i++) for (j = 0; j != 2; j++) if (isZero(vecs[i][j])) vecs[i][j] = 0; double alen = java.lang.Math.sqrt( vecs[0][0] * vecs[0][0] + vecs[0][1] * vecs[0][1]); double blen = java.lang.Math.sqrt( vecs[1][0] * vecs[1][0] + vecs[1][1] * vecs[1][1]); double piadj = 180/3.14159265; double det = vecs[0][0] * vecs[1][1] - vecs[0][1] * vecs[1][0]; if (isZero(det)) invertButton.disable(); else invertButton.enable(); double trace = vecs[0][0] + vecs[1][1]; double eigendisc = trace*trace - 4*det; double eigen1, eigen2; boolean eigenreal = true; if (eigendisc < 0) { eigenreal = false; eigendisc = -eigendisc; } eigendisc = java.lang.Math.sqrt(eigendisc); eigen1 = .5*(trace + eigendisc); eigen2 = .5*(trace - eigendisc); int eigenveccount = (eigenreal) ? 2 : 0; if (eigenreal && eigen1 == eigen2) eigenveccount = (vecs[0][1] == 0 && vecs[1][0] == 0) ? 0 : 1; if (eigenveccount > 0) eigenfind(eigen1, 0); if (eigenveccount > 1) eigenfind(eigen2, 1); Graphics g = dbimage.getGraphics(); if (winSize == null || winSize.width == 0) return; g.setColor(getBackground()); g.fillRect(0, 0, winSize.width, winSize.height); g.setColor(Color.gray); for (i = -2; i <= 2; i++) { int x = winSize.height*(i+2)/4; g.drawLine(x, 0, x, winSize.height); g.drawLine(0, x, winSize.height, x); } int cy = winSize.height/2; int cx = cy; int vc[] = new int[2]; int vc2[] = new int[2]; int dotSize = 5; if (showPoint) { g.setColor(Color.gray); findXformVecCoords(pointX, pointY, vc); findVecCoords(pointX, pointY, vc2); drawArrow(g, vc2[0], vc2[1], vc[0], vc[1]); } g.setColor(Color.green); findXformVecCoords(1, 1, vc); findXformVecCoords(-1, 1, vc2); g.drawLine(vc[0], vc[1], vc2[0], vc2[1]); findXformVecCoords(-1, -1, vc); g.drawLine(vc[0], vc[1], vc2[0], vc2[1]); findXformVecCoords(1, -1, vc2); g.drawLine(vc[0], vc[1], vc2[0], vc2[1]); findXformVecCoords(1, 1, vc); g.drawLine(vc[0], vc[1], vc2[0], vc2[1]); findXformVecCoords(-.5, .5, vc); findXformVecCoords(.5, .5, vc2); g.drawLine(vc[0], vc[1], vc2[0], vc2[1]); findXformVecCoords(-.5, -.5, vc); g.drawLine(vc[0], vc[1], vc2[0], vc2[1]); findXformVecCoords(.5, -.5, vc2); g.drawLine(vc[0], vc[1], vc2[0], vc2[1]); findXformVecCoords(-.5, -.6, vc); findXformVecCoords(.5, -.6, vc2); g.drawLine(vc[0], vc[1], vc2[0], vc2[1]); if (showPoint) { g.setColor(purple); findXformVecCoords(pointX, pointY, vc); g.fillOval(vc[0]-dotSize/2, vc[1]-dotSize/2, dotSize, dotSize); } if (eigenveccount > 0) { g.setColor(Color.yellow); findVecCoords(eigens[0][0], eigens[0][1], vc); drawArrow(g, cx, cy, vc[0], vc[1]); } if (eigenveccount > 1) { g.setColor(Color.orange); findVecCoords(eigens[1][0], eigens[1][1], vc); drawArrow(g, cx, cy, vc[0], vc[1]); } if (!isZero(det)) { g.setColor(darkRed); findVecCoords(vecs[1][1]/det, -vecs[0][1]/det, vc); g.fillOval(vc[0]-dotSize/2, vc[1]-dotSize/2, dotSize, dotSize); g.setColor(darkCyan); findVecCoords(-vecs[1][0]/det, vecs[0][0]/det, vc); g.fillOval(vc[0]-dotSize/2, vc[1]-dotSize/2, dotSize, dotSize); } findVecCoords(0, vc); g.setColor(Color.red); drawArrow(g, cx, cy, vc[0], vc[1]); findVecCoords(1, vc); g.setColor(Color.cyan); drawArrow(g, cx, cy, vc[0], vc[1]); int yl = g.getFontMetrics().getHeight(); int y = winSize.height/2; NumberFormat nf = NumberFormat.getInstance(); nf.setMaximumFractionDigits(3); String as = nf.format(vecs[0][0]); String bs = nf.format(vecs[1][0]); String cs = nf.format(vecs[0][1]); String ds = nf.format(vecs[1][1]); FontMetrics fm = g.getFontMetrics(); int c1w = fm.stringWidth(as); int w = fm.stringWidth(cs); if (w > c1w) c1w = w; int c2w = fm.stringWidth(bs); w = fm.stringWidth(ds); if (w > c2w) c2w = w; int spacing = 10; int totw = c1w+spacing+c2w; int x = winSize.height + (winSize.width - winSize.height - totw)/2; g.setColor(Color.red); g.drawString(as, x+(c1w-fm.stringWidth(as))/2, y); g.setColor(Color.cyan); g.drawString(bs, x+c1w+spacing+(c2w-fm.stringWidth(bs))/2, y); y += yl; g.setColor(Color.red); g.drawString(cs, x+(c1w-fm.stringWidth(cs))/2, y); g.setColor(Color.cyan); g.drawString(ds, x+c1w+spacing+(c2w-fm.stringWidth(ds))/2, y); g.setColor(Color.white); int y1 = y-yl-fm.getMaxAscent(); int y2 = y+fm.getMaxDescent(); g.drawLine(x-5, y1, x-5, y2); g.drawLine(x-5, y1, x, y1); g.drawLine(x-5, y2, x, y2); g.drawLine(x+totw+5, y1, x+totw+5, y2); g.drawLine(x+totw, y1, x+totw+5, y1); g.drawLine(x+totw, y2, x+totw+5, y2); displayString(g, "det M = " + nf.format(det), y += yl); displayString(g, "tr M = " + nf.format(trace), y += yl); g.setColor(Color.yellow); displayString(g, eigenstring(1, nf, eigen1, eigenreal, trace, eigendisc), y += yl); g.setColor(Color.orange); displayString(g, eigenstring(2, nf, eigen2, eigenreal, trace, eigendisc), y += yl); realg.drawImage(dbimage, 0, 0, this); } void displayString(Graphics g, String s, int y) { int lx = winSize.height; int lw = winSize.width - lx; FontMetrics fm = g.getFontMetrics(); g.drawString(s, lx+(lw-fm.stringWidth(s))/2, y); } void edit(MouseEvent e) { if (!dragging) return; int x = e.getX(); int y = e.getY(); double cy = winSize.height/4; double cx = cy; double xf = x/cx-2; double yf = 2-y/cy; if (xf < -2) xf = -2; if (yf < -2) yf = -2; if (xf > 2) xf = 2; if (yf > 2) yf = 2; if (selection != -1) { vecs[selection][0] = xf; vecs[selection][1] = yf; } else { double oldang = java.lang.Math.atan2(pointY, pointX); double newang = java.lang.Math.atan2(yf, xf); double oldr = java.lang.Math.sqrt(pointX*pointX + pointY*pointY); double newr = java.lang.Math.sqrt(xf*xf+yf*yf); double rmult = newr/oldr; double costhr = java.lang.Math.cos(newang-oldang)*rmult; double sinthr = java.lang.Math.sin(newang-oldang)*rmult; vecs[0][0] = origmatrix[0][0] * costhr + origmatrix[1][0] * sinthr; vecs[0][1] = origmatrix[0][1] * costhr + origmatrix[1][1] * sinthr; vecs[1][0] = origmatrix[0][0] *-sinthr + origmatrix[1][0] * costhr; vecs[1][1] = origmatrix[0][1] *-sinthr + origmatrix[1][1] * costhr; } cv.repaint(); } public void componentHidden(ComponentEvent e){} public void componentMoved(ComponentEvent e){} public void componentShown(ComponentEvent e) { cv.repaint(); } public void componentResized(ComponentEvent e) { reinit(); cv.repaint(100); } public void actionPerformed(ActionEvent e) { if (e.getSource() == identityButton) { vecs[0][0] = 1; vecs[0][1] = 0; vecs[1][0] = 0; vecs[1][1] = 1; cv.repaint(); } if (e.getSource() == transposeButton) { double x = vecs[1][0]; vecs[1][0] = vecs[0][1]; vecs[0][1] = x; cv.repaint(); } if (e.getSource() == invertButton) { double det = vecs[0][0] * vecs[1][1] - vecs[0][1] * vecs[1][0]; if (det != 0) { double x; x = vecs[0][0]; vecs[0][0] = vecs[1][1]/det; vecs[1][1] = x/det; vecs[1][0] /= -det; vecs[0][1] /= -det; } cv.repaint(); } if (e.getSource() == rotateCCWButton) { double cos30 = .8660254; copyMatrix(); vecs[0][0] = origmatrix[0][0] * cos30 + origmatrix[1][0] * .5; vecs[0][1] = origmatrix[0][1] * cos30 + origmatrix[1][1] * .5; vecs[1][0] = origmatrix[0][0] *-.5 + origmatrix[1][0] * cos30; vecs[1][1] = origmatrix[0][1] *-.5 + origmatrix[1][1] * cos30; cv.repaint(); } if (e.getSource() == rotateCWButton) { double cos30 = .8660254; copyMatrix(); vecs[0][0] = origmatrix[0][0] * cos30 + origmatrix[1][0] * -.5; vecs[0][1] = origmatrix[0][1] * cos30 + origmatrix[1][1] * -.5; vecs[1][0] = origmatrix[0][0] * .5 + origmatrix[1][0] * cos30; vecs[1][1] = origmatrix[0][1] * .5 + origmatrix[1][1] * cos30; cv.repaint(); } if (e.getSource() == reflectXButton) { vecs[0][1] = -vecs[0][1]; vecs[1][1] = -vecs[1][1]; cv.repaint(); } if (e.getSource() == reflectYButton) { vecs[0][0] = -vecs[0][0]; vecs[1][0] = -vecs[1][0]; cv.repaint(); } } public void adjustmentValueChanged(AdjustmentEvent e) { } public void mouseDragged(MouseEvent e) { edit(e); } public void mouseMoved(MouseEvent e) { if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) { double cy = winSize.height/4; double cx = cy; double xf = e.getX()/cx-2; double yf = 2-e.getY()/cy; showPoint = true; if (xf < -2) showPoint = false; if (yf < -2) showPoint = false; if (xf > 2) showPoint = false; if (yf > 2) showPoint = false; pointX = xf; pointY = yf; cv.repaint(); return; } edit(e); } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { showPoint = false; cv.repaint(); } public void mousePressed(MouseEvent e) { if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) return; int x = e.getX(); int y = e.getY(); int vc[] = new int[2]; int i; selection = -1; showPoint = false; for (i = 0; i != 2; i++) { findVecCoords(i, vc); int space = 10; if (vc[0] >= x-space && vc[0] <= x+space && vc[1] >= y-space && vc[1] <= y+space) { selection = i; break; } } if (selection == -1) { double cy = winSize.height/4; double cx = cy; double xf = e.getX()/cx-2; double yf = 2-e.getY()/cy; if (xf < -2) return; if (yf < -2) return; if (xf > 2) return; if (yf > 2) return; if (isZero(xf) && isZero(yf)) return; dragging = true; pointX = xf; pointY = yf; copyMatrix(); return; } dragging = true; edit(e); } void copyMatrix() { origmatrix = new double[2][2]; int i, j; for (i = 0; i != 2; i++) for (j = 0; j != 2; j++) origmatrix[i][j] = vecs[i][j]; } public void mouseReleased(MouseEvent e) { if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) return; dragging = false; selection = -1; } }