package mygraphs; import java.util.*; import java.lang.*; import java.awt.*; import javax.swing.*; public class PhaseGraph extends JFrame { float [] points = null; int numPoints = 0; float graphWidth, graphHeight; // The width and height in graph coordinates float graphOriginX, graphOriginY; // The position of the graph scale at the origin int graphLocX, graphLocY; // The screen coordinates location of the origin of the graph // Note that Y is upside down (numbers get higher going up) int graphBorderX, graphBorderY; // Width of the border around right and top edges of graph final static int LINEAR = 1, LOGARITHMIC = 2; int scaleType = LINEAR; // Either logarithmic or linear Vector paths = new Vector(); String xAxisLabel = "", yAxisLabel = "", title = ""; static final float sin_angle = (float)Math.sin(25 * (2 * 3.14159) / 360); static final float cos_angle = (float)Math.cos(25 * (2 * 3.14159) / 360); static final float arrow_len = 4; public PhaseGraph() { setSize(500, 500); setVisible(true); setDefaultCloseOperation(DISPOSE_ON_CLOSE); setTitle("Phase Portrait"); graphLocX = 40; graphLocY = 30; graphBorderX = graphLocX; graphBorderY = 20; } public void setPoints(float [] points, int num_points) { this.points = points; this.numPoints = num_points; repaint(); } public void setGraphAxes(float x, float y, float width, float height) { graphOriginX = x; graphOriginY = y; graphWidth = width; graphHeight = height; repaint(); } public void setScaleType(String str) { if(str.toUpperCase().equals("LOGARITHMIC")) scaleType = LOGARITHMIC; else scaleType = LINEAR; } public void setXAxisLabel(String label) { xAxisLabel = label; } public void setYAxisLabel(String label) { yAxisLabel = label; } public void setTitle(String label) { title = label; } /** Adds a path through the phase space to the plot. The PlotPath class holds an array of x,y points that are strung together into a trajectory through the space. */ public void addPath(PlotPath path) { paths.addElement(path); } public void paint(Graphics g) { int i, x1, x2, y1, y2, num_pts; float x_scale, y_scale; Dimension area = getSize(); int w = area.width - graphLocX - graphBorderX; // Put a little space at the outer edges int h = area.height - graphLocY - graphBorderY; g.drawString(title, w / 2 - title.length() * 12 / 2 + graphLocX, 12); if(scaleType == LINEAR) { x_scale = w / graphWidth; y_scale = h / graphHeight; for(int p = 0; p < paths.size(); p++) { PlotPath path = (PlotPath) paths.elementAt(p); x2 = Math.round((path.getX(0) - graphOriginX) * x_scale + graphLocX); y2 = Math.round((path.getY(0) - graphOriginY) * y_scale + graphLocY); y2 = area.height - y2; num_pts = path.getNumPoints(); for(i = 1; i < num_pts; i++) { x1 = x2; y1 = y2; x2 = Math.round((path.getX(i) - graphOriginX) * x_scale + graphLocX); y2 = Math.round((path.getY(i) - graphOriginY) * y_scale + graphLocY); y2 = area.height - y2; if(inGraph(x1,y1) && inGraph(x2, y2)) g.drawLine(x1, y1, x2, y2); } if(num_pts > 1) { drawArrowHead(g, x2, y2, path.getX(num_pts - 1) - path.getX(num_pts - 2), path.getY(num_pts - 1) - path.getY(num_pts - 2)); } } } else { float log_orig_x = (float)Math.log(graphOriginX); float log_orig_y = (float)Math.log(graphOriginY); x_scale = w / (float)(Math.log(graphOriginX + graphWidth) - log_orig_x); y_scale = h / (float)(Math.log(graphOriginY + graphHeight) - log_orig_y); for(int p = 0; p < paths.size(); p++) { PlotPath path = (PlotPath) paths.elementAt(p); x2 = Math.round((float)(Math.log(path.getX(0)) - log_orig_x) * x_scale) + graphLocX; y2 = Math.round((float)(Math.log(path.getY(0)) - log_orig_y) * y_scale) + graphLocY; y2 = area.height - y2; num_pts = path.getNumPoints(); for(i = 1; i < num_pts; i++) { x1 = x2; y1 = y2; x2 = Math.round((float)(Math.log(path.getX(i)) - log_orig_x) * x_scale) + graphLocX; y2 = Math.round((float)(Math.log(path.getY(i)) - log_orig_y) * y_scale) + graphLocY; y2 = area.height - y2; g.drawLine(x1, y1, x2, y2); } if(num_pts > 1) { drawArrowHead(g, x2, y2, path.getX(num_pts - 1) - path.getX(num_pts - 2), path.getY(num_pts - 1) - path.getY(num_pts - 2)); } } } drawScaleHorizontal(g, graphOriginX, graphOriginX + graphWidth); drawScaleVertical(g, graphOriginY, graphOriginY + graphHeight); } private void drawArrowHead(Graphics g, int x, int y, float vecx, float vecy) { // Draw an arrow head on the end. I'm drawing arrow head by taking a vector // going backwards from this vector (i.e. - starts at x2,y2) and then rotating // it 30 and -30 degrees, and drawing 4 pixel stubs in those directions. // This means multiplying by matrix [cos, -sin][sin, cos]. float arrowx = cos_angle * -vecx + -sin_angle * -vecy; float arrowy = sin_angle * -vecx + cos_angle * -vecy; float dist = (float)Math.sqrt(arrowx * arrowx + arrowy * arrowy); if(dist > 0) { arrowx = arrowx * arrow_len / dist; arrowy = arrowy * arrow_len / dist; g.drawLine(x, y, Math.round(x + arrowx), Math.round(y - arrowy)); arrowx = cos_angle * -vecx + sin_angle * -vecy; arrowy = -sin_angle * -vecx + cos_angle * -vecy; arrowx = arrowx * arrow_len / dist; arrowy = arrowy * arrow_len / dist; g.drawLine(x, y, Math.round(x + arrowx), Math.round(y - arrowy)); } } private void drawScaleHorizontal(Graphics g, float start, float end) { Dimension area = getSize(); float scale; g.drawLine(graphLocX, area.height - graphLocY, area.width - graphLocX, area.height - graphLocY); if(scaleType == LINEAR) scale = (area.width - graphBorderX - graphLocX) / (end - start); else scale = (area.width - graphBorderX - graphLocX) / (float)(Math.log(end - start) - Math.log(start)); float incr = (end - start) / 10f; int pt; for(int i = 0; i <= 10; i++) { float loc = i * incr; if(scaleType == LINEAR) pt = Math.round(loc * scale) + graphLocX; else pt = Math.round((float)(Math.log(loc) - Math.log(start)) * scale) + graphLocX; g.drawLine(pt, area.height - graphLocY + 5, pt, area.height - graphLocY); } g.drawString(xAxisLabel, graphLocX + (area.width - graphLocX) / 2, area.height - graphLocY + 20); } private void drawScaleVertical(Graphics g, float start, float end) { Dimension area = getSize(); float scale; g.drawLine(graphLocX, graphBorderY, graphLocX, area.height - graphLocY); if(scaleType == LINEAR) scale = (area.height - graphLocY - graphBorderY) / (end - start); else scale = (area.height - graphLocY - graphBorderY) / (float)(Math.log(end - start) - Math.log(start)); int pt; float incr = (end - start) / 10f; for(int i = 0; i <= 10; i++) { float loc = i * incr; if(scaleType == LINEAR) pt = Math.round(loc * scale) + graphLocY; else pt = Math.round((float)(Math.log(loc) - Math.log(start)) * scale) + graphLocY; pt = area.height - pt; g.drawLine(graphLocX - 5, pt, graphLocX, pt); } char [] chars = new char[1]; int start_y = (area.height - graphLocY) / 2 - yAxisLabel.length() * 14 / 2; for(int i = 0; i < yAxisLabel.length(); i++) { chars[0] = yAxisLabel.charAt(i); g.drawChars(chars, 0, 1, graphLocX - 15, start_y); start_y += 14; } } private boolean inGraph(int x, int y) { Dimension area = getSize(); if(x < graphLocX || x > area.width - graphLocX || y < graphLocY || y > area.height - graphLocY) return false; return true; } }