diff --git a/src/TimingGraph.java b/src/TimingGraph.java index af7c803..f28c5bb 100644 --- a/src/TimingGraph.java +++ b/src/TimingGraph.java @@ -4,6 +4,8 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; import java.awt.geom.*; import java.awt.font.*; import java.awt.image.BufferedImage; @@ -20,8 +22,9 @@ */ public class TimingGraph extends JPanel { - private int numRuns; + // lists containing timings of each function on different sized boards private List> timings; + // number of tiles for each board that was tested private List numTiles; /** @@ -33,7 +36,6 @@ public TimingGraph(List> timings, List numTiles) { this.numTiles = numTiles; setBackground(Color.WHITE); setOpaque(true); - numRuns = timings.get(0).size(); showAndTell(); } @@ -44,52 +46,95 @@ public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + // height and width of window int w = getWidth(); int h = getHeight(); - // Draw y-axis - g2.draw(new Line2D.Double(numRuns, numRuns, numRuns, h - numRuns)); - // Draw x-axis - g2.draw(new Line2D.Double(numRuns, h - numRuns, w - numRuns, h - numRuns)); - // Draw labels + // offset of the axes from the left and bottom edges of the window + int xOffset = 80; + int yOffset = 60; + // (0, 0) is in the top left corner + // Draw y-axis (extended a little past the end of the graph) + g2.draw(new Line2D.Double(xOffset, yOffset * 3/4, xOffset, h - yOffset)); + // Draw x-axis (extended a little past the end of the graph) + g2.draw(new Line2D.Double(xOffset, h - yOffset, w - (xOffset * 3/4), h - yOffset)); + // setup font Font font = g2.getFont(); FontRenderContext frc = g2.getFontRenderContext(); LineMetrics lm = font.getLineMetrics("0", frc); - float sh = lm.getAscent() + lm.getDescent(); // Draw label on the y-axis - String s = "time"; - float sy = numRuns + ((h - 2 * numRuns) - s.length() * sh) / 2 + lm.getAscent(); - for (int i = 0; i < s.length(); i++) { - String letter = String.valueOf(s.charAt(i)); - float sw = (float) font.getStringBounds(letter, frc).getWidth(); - float sx = (numRuns - sw) / 2; - g2.drawString(letter, sx, sy); - sy += sh; - } - // Draw label on the x-axis - s = "board size"; - sy = h - numRuns + (numRuns - sh) / 2 + lm.getAscent(); + // rotate the x and y axes 90 degrees counterclockwise to draw the label sideways + AffineTransform at = new AffineTransform(); + at.rotate(Math.PI / -2); + g2.setTransform(at); + String s = "time (s)"; + String exampleTime = "0.002"; float sw = (float) font.getStringBounds(s, frc).getWidth(); - float sx = (w - sw) / 2; + // due to the rotation, the negative x acts like the y pre-rotation + float sx = (h + sw) / -2; + // similarly, the positive y acts like the x (pre-rotation) + float sy = (xOffset - (float) font.getStringBounds(exampleTime, frc).getWidth() + lm.getAscent()) / 2; + g2.drawString(s, sx, sy); + // revert the rotation + at.rotate(Math.PI / 2); + g2.setTransform(at); + // Draw label on the x-axis + s = "board size (tiles)"; + sw = (float) font.getStringBounds(s, frc).getWidth(); + sx = (w - sw) / 2; + sy = h - ((yOffset - lm.getAscent()) / 2); + g2.drawString(s, sx, sy); + // Label the origin + s = "0"; + sw = (float) font.getStringBounds(s, frc).getWidth(); + sx = xOffset - sw; + sy = h - yOffset + lm.getAscent(); g2.drawString(s, sx, sy); - // Draw lines - double xInc = (double) (w - 2 * numRuns) / numTiles.get(numTiles.size() - 1); - double scale = (double) (h - 2 * numRuns) / getMaxTime(); - g2.setPaint(Color.GREEN.darker()); + // Draw the x-axis labels + float maxLabelWidth = (float) font.getStringBounds(String.format("%d", Constants.MAX_BOARD_SIZE_FOR_AUTOPLAY), frc).getWidth(); + int numIntervals = w / (int) (3 * maxLabelWidth); + int intervalWidth = (w - (2 * xOffset)) / numIntervals; + for (int i = 1; i != numIntervals + 1; ++i) { + s = String.format("%d", i * (Constants.MAX_BOARD_SIZE_FOR_AUTOPLAY / numIntervals)); + sw = (float) font.getStringBounds(s, frc).getWidth(); + sx = xOffset + (i * intervalWidth) - (sw / 2); + sy = h - yOffset + lm.getAscent(); + g2.drawString(s, sx, sy); + } + // Draw the y-axis labels + numIntervals = h / (int) (6 * lm.getAscent()); + int intervalHeight = (h - (2 * yOffset)) / numIntervals; + double maxRecordedTime = getMaxTime(); + for (int i = 1; i != numIntervals + 1; ++i) { + // Draw time with 3 digit precision + s = String.format("%.3f", i * (maxRecordedTime / numIntervals)); + sw = (float) font.getStringBounds(s, frc).getWidth(); + sx = xOffset - sw; + sy = h - yOffset - (i * intervalHeight); + g2.drawString(s, sx, sy); + } + // Draw the points and the lines connecting them + double xScale = (double) (w - (2 * xOffset)) / Constants.MAX_BOARD_SIZE_FOR_AUTOPLAY; + double yScale = (double) (h - (2 * yOffset)) / maxRecordedTime; g2.setPaint(Color.RED); - for (List run : timings) { - for (int i = 0; i != run.size() - 1; ++i) { - double x1 = numRuns + numTiles.get(i) * xInc; - double y1 = h - numRuns - scale * run.get(i); - double x2 = numRuns + numTiles.get(i + 1) * xInc; - double y2 = h - numRuns - scale * run.get(i + 1); - g2.draw(new Line2D.Double(x1, y1, x2, y2)); - } - // Mark data points with small circles - for (int i = 0; i < run.size(); i++) { - double x = numRuns + numTiles.get(i) * xInc; - double y = h - numRuns - scale * run.get(i); + s = "flood"; + for (List times : timings) { + // Set the previous to be the origin + double prevX = xOffset; + double prevY = h - yOffset; + for (int i = 0; i != times.size(); ++i) { + // Draw the current point and connect it to the previous point + double x = xOffset + (numTiles.get(i) * xScale); + double y = h - yOffset - (times.get(i) * yScale); g2.fill(new Ellipse2D.Double(x - 2, y - 2, 4, 4)); + g2.draw(new Line2D.Double(prevX, prevY, x, y)); + // Update the previous point + prevX = x; + prevY = y; } + // draw the legend + g2.drawString(s, (float) prevX + 5, (float) Math.min(prevY + lm.getAscent() / 2.5, h - yOffset)); + // update flood function name for legend + s = "flood1"; g2.setPaint(Color.LIGHT_GRAY); } } @@ -122,6 +167,14 @@ private void showAndTell() { BufferedImage image = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); Graphics g = image.createGraphics(); frame.paint(g); + // repaint the frame if the size of the window changes + frame.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + super.componentResized(e); + frame.repaint(); + } + }); g.dispose(); // Tell try {