package polly.rx.graphs; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Stroke; import java.util.ArrayList; import java.util.Collection; import java.util.TreeSet; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; import polly.rx.graphs.Point.PointType; public class Graph { private final Color GRID_COLOR = new Color(230, 230, 230); // TEST: Graph public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final ImageGraph graph = new ImageGraph(600, 400); String[] label = new String[24]; for (int i = 0; i < label.length; ++i) { label[i] = "" + (24 - i); //$NON-NLS-1$ } final PointSet set = new PointSet(); set.setName("Rot"); //$NON-NLS-1$ final PointSet set2 = new PointSet(Color.BLUE); set2.setName("Blau"); //$NON-NLS-1$ set.setColor(Color.RED); graph.addPointSet(set); graph.addPointSet(set2); graph.setxLabels(label); graph.setDrawGridVertical(true); for (int i = 0; i < 24; ++i) { set.add(new Point(i+0.5, i*i*50, PointType.X)); set2.add(new Point(i, i*1000, PointType.DOT)); } JPanel p = new JPanel() { private static final long serialVersionUID = 1L; @Override public void paintComponent(Graphics g) { graph.updateImage(); graph.drawImageTo((Graphics2D) g); } }; PointSet test = new PointSet(Color.GREEN); test.setName("Green"); //$NON-NLS-1$ test.add(1, 2000, PointType.DOT); test.add(1, 2500, PointType.DOT); test.add(1, 3000, PointType.DOT); test.add(1, 6000, PointType.DOT); graph.addPointSet(test); graph.setRightScale(new YScale("Just a test", 0, 200, 20)); //$NON-NLS-1$ PointSet rightSet = new PointSet(Color.BLACK); for (int i = 0; i < 24; ++i) { rightSet.add(i, Math.random() * 200, PointType.BOX); } rightSet.setConnect(true); rightSet.setConnectColor(Color.LIGHT_GRAY); graph.addHighlightArea(new HighlightArea("Test", 2, 5, new Color(0, 0, 0, 20))); //$NON-NLS-1$ frame.setLayout(new BorderLayout()); frame.add(p, BorderLayout.CENTER); frame.setVisible(true); frame.pack(); frame.setSize(new Dimension(700, 450)); } }); } private String[] xLabels; private final int width; private final int height; private boolean drawGridVertical; private final Collection<PointSet> data; private final Collection<HighlightArea> highlights; private final Collection<NamedPoint> rawPoints; private YScale left; private YScale right; public Graph(int width, int height) { this.width = width; this.height = height; this.data = new ArrayList<PointSet>(); this.highlights = new ArrayList<HighlightArea>(); this.rawPoints = new TreeSet<NamedPoint>(); } public void addHighlightArea(HighlightArea ha) { this.highlights.add(ha); } public YScale getLeftScale() { return this.left; } public void setLeftScale(YScale left) { this.left = left; } public YScale getRightScale() { return this.right; } public void setRightScale(YScale right) { this.right = right; } public int getWidth() { return this.width; } public int getHeight() { return this.height; } public String[] getxLabels() { return this.xLabels; } public void setxLabels(String[] xLabels) { this.xLabels = xLabels; } public boolean isDrawGridVertical() { return this.drawGridVertical; } public void setDrawGridVertical(boolean drawGridVertical) { this.drawGridVertical = drawGridVertical; } public void addPointSet(PointSet pointSet) { this.data.add(pointSet); } public Collection<PointSet> getData() { return this.data; } public Collection<NamedPoint> getRawPointsFromLastDraw() { return this.rawPoints; } private void drawScale(YScale scale, Graphics2D g, int actualHeight, int actualWidth, int xOffset, boolean left, int yLabelWidth) { final int steps = (int) Math.round( (scale.getMax() - scale.getMin()) / (double) scale.getStep()); final double yScale = (double)actualHeight / (double)steps; final FontMetrics m = g.getFontMetrics(); final int newX; if (left) { newX = xOffset + -(int)yLabelWidth + 5; } else { newX = xOffset + 5; } g.setColor(Color.BLACK); for(int y = 0; y <= steps; ++y) { int newY = -(int) Math.round(y * yScale); if (scale.isDrawGrid() && y > 0) { g.setColor(GRID_COLOR); g.drawLine(0, newY, actualWidth, newY); g.setColor(Color.BLACK); } if (y != steps) { // ommit last tick g.drawLine(-2 + xOffset, newY, 2 + xOffset, newY); } g.drawString("" + (y * scale.getStep() + scale.getMin()), newX, //$NON-NLS-1$ newY + m.getAscent() / 2); } g.drawLine(xOffset, 0, xOffset, -actualHeight); final Font f = g.getFont(); final Font fCopy = f.deriveFont(Font.BOLD); g.setFont(fCopy); g.drawString(scale.getName(), newX, -actualHeight - m.getAscent() / 2); g.setFont(f); } public void draw(Graphics2D g) { final FontMetrics m = g.getFontMetrics(); final double xLabelWidth = this.getLongestStringWidth(this.xLabels, m) + 10.0; final double leftYLabelWidth = Math.max( m.stringWidth("" + this.left.getMax()), //$NON-NLS-1$ m.stringWidth(this.left.getName())) + 10.0; double rightYLabelWidth; if (this.right == null) { rightYLabelWidth = xLabelWidth; } else { rightYLabelWidth = Math.max( Math.max( (int)xLabelWidth, m.stringWidth("" + this.right.getMax())), //$NON-NLS-1$ m.stringWidth(this.right.getName())) + 10.0; } final int zeroX = (int) Math.round(leftYLabelWidth); final int zeroY = (int) Math.round(this.height - xLabelWidth); final int actualWidth = this.width - zeroX - (int) Math.round(rightYLabelWidth); final int actualHeight = this.height - (int)xLabelWidth - 20; g.setBackground(new Color(244, 244, 244)); g.clearRect(0, 0, this.width, this.height); g.setColor(Color.BLACK); g.translate(zeroX, zeroY); // x axis g.drawLine(0, 0, actualWidth, 0); final double xScale = (double) actualWidth / (this.xLabels.length - 1); for(int x = 0; x < xLabels.length; ++x) { int newX = (int) Math.round(x * xScale); if (this.drawGridVertical && x > 0) { g.setColor(GRID_COLOR); g.drawLine(newX, 0, newX, -actualHeight); g.setColor(Color.BLACK); } g.drawLine(newX, -2, newX, 2); g.rotate(Math.PI / 4.0, newX, 10); g.drawString(xLabels[x], newX, 10); g.rotate(-Math.PI / 4.0, newX, 10); } // left y axis if (this.left != null) { this.drawScale(this.left, g, actualHeight, actualWidth, 0, true, (int)leftYLabelWidth); } if (this.right != null) { this.drawScale(this.right, g, actualHeight, actualWidth, actualWidth, false, (int)rightYLabelWidth); } else { final int x = actualWidth;// - (int) rightYLabelWidth; g.setColor(Color.BLACK); g.drawLine(x, 0, x, -actualHeight); } this.drawPoints(g, this.data, actualHeight, zeroX, zeroY, xScale); g.setColor(Color.BLACK); g.drawLine(0, -actualHeight, actualWidth, -actualHeight); this.drawLegend(g, actualWidth); this.drawHighlightAreas(g, actualHeight, xScale); } private void drawPoints(Graphics2D g, Collection<PointSet> pointSets, int actualHeight, int zeroX, int zeroY, double xScale) { final Stroke reset = g.getStroke(); this.rawPoints.clear(); for (final PointSet points : pointSets) { YScale yScale = points.getScale(); double scale = (double)actualHeight / (yScale.getMax() - yScale.getMin()); boolean first = true; int lastX = 0; int lastY = 0; for (final Point p : points) { final int x = (int) Math.round(p.getX() * xScale); final int y = -(int) Math.round((p.getY() - yScale.getMin()) * scale); if (y > 0) { continue; } if (points.isConnect() && !first) { g.setColor(points.getConnectColor()); g.setStroke(new BasicStroke(points.getStrength())); g.drawLine(lastX, lastY, x, y); g.setStroke(reset); } first = false; g.setColor(points.getColor()); drawPoint(g, x, y, p.getType()); // retranslate if (p instanceof NamedPoint) { this.rawPoints.add(new NamedPoint(((NamedPoint) p).getName(), x + zeroX, y + zeroY, PointType.NONE)); } else { this.rawPoints.add(new NamedPoint(points.getName() + ": " + p.getY(), //$NON-NLS-1$ x + zeroX, y + zeroY, PointType.NONE)); } lastX = x; lastY = y; } } } private void drawHighlightAreas(Graphics2D g, double actualHeight, double xScale) { final FontMetrics m = g.getFontMetrics(); for (final HighlightArea ha : this.highlights) { g.setColor(ha.getColor()); final int nameWidth = Math.round(m.stringWidth(ha.getName())); final int xmin = (int) Math.round(ha.getxMin() * xScale); final int xmax = (int) Math.round(ha.getxMax() * xScale); g.fillRect(xmin, -(int)actualHeight, xmax - xmin, (int) actualHeight); if (nameWidth > 0.5 * (xmax - xmin)) { continue; } g.setColor(Color.BLACK); int stringX = xmin + (xmax - xmin) / 2 - nameWidth / 2; g.drawString(ha.getName(), stringX, -(int) actualHeight / 2); } } private void drawLegend(Graphics2D g, int actualWidth) { final FontMetrics m = g.getFontMetrics(); double width = 0.0; for (final PointSet points : this.data) { width = Math.max(width, m.stringWidth(points.getName())); } final int legendX = (int) Math.round(actualWidth - (width + 30.0)); int legendY = -20; for (final PointSet points : this.data) { if (!points.getName().equals("")) { //$NON-NLS-1$ g.setColor(points.getColor()); g.fillRect(legendX, legendY, 10, 10); g.drawString(points.getName(), legendX + 15, legendY + 10); legendY -= 20; } } g.setColor(Color.BLACK); } private void drawPoint(Graphics2D g, int x, int y, PointType type) { switch (type) { case X: this.drawCross(g, x, y); break; case DOT: g.drawOval(x - 2, y - 2, 4, 4); break; case BOX: g.drawRect(x - 2, y - 2, 4, 4); break; default: break; } } private void drawCross(Graphics2D g, int x, int y) { g.rotate(Math.PI / 4.0, x, y); g.drawLine(x - 3, y, x + 3, y); g.drawLine(x, y - 3, x, y + 3); g.rotate(-Math.PI / 4.0, x, y); } private double getLongestStringWidth(String[] strings, FontMetrics m) { double stringWidth = 0.0; for (final String s : strings) { stringWidth = Math.max(stringWidth, m.stringWidth(s)); } return stringWidth; } }