/* * JChart.java * * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard * * This file is part of BEAST. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership and licensing. * * BEAST is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * BEAST is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with BEAST; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ package dr.app.gui.chart; import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Vector; public class JChart extends JPanel { /** * */ private static final long serialVersionUID = -7064065852204509247L; protected Axis yAxis, xAxis; private Vector<Plot> plots = new Vector<Plot>(); private Paint plotBackgroundPaint = Color.white; private Stroke originStroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); private Paint originPaint = Color.lightGray; private Stroke frameStroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); private Paint framePaint = Color.black; private Stroke axisStroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); private Paint axisPaint = Color.black; private Font labelFont = new Font("Helvetica", Font.PLAIN, 12); private Paint labelPaint = Color.black; private Rectangle2D plotBounds = null; private Rectangle2D dragRectangle = null; private double majorTickSize = 4; private double minorTickSize = 2; private double xTickLabelOffset; private double yTickLabelOffset; private boolean showLegend = false; private int legendAlignment = SwingConstants.NORTH_EAST; private double xScale, yScale, xOffset, yOffset; public JChart(Axis xAxis, Axis yAxis) { this(null, xAxis, yAxis); } public JChart(Plot plot, Axis xAxis, Axis yAxis) { setOpaque(false); this.xAxis = xAxis; this.yAxis = yAxis; if (plot != null) addPlot(plot); addMouseListener(new MListener()); addMouseMotionListener(new MMListener()); } public void setXAxis(Axis xAxis) { this.xAxis = xAxis; for (Object plot : plots) { Plot p = (Plot) plot; p.setAxes(xAxis, yAxis); } recalibrate(); repaint(); } public Axis getXAxis() { return xAxis; } public void setYAxis(Axis yAxis) { this.yAxis = yAxis; for (Plot p : plots) { p.setAxes(xAxis, yAxis); } recalibrate(); repaint(); } public Axis getYAxis() { return yAxis; } public void setFontSize(int size) { labelFont = new Font("Helvetica", Font.PLAIN, size); } public void addPlot(Plot plot) { plot.setAxes(xAxis, yAxis); plots.add(plot); recalibrate(); repaint(); } public void removePlot(Plot plot) { plots.remove(plot); xAxis.setRange(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY); yAxis.setRange(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY); for (Plot p : plots) { p.setAxes(xAxis, yAxis); } recalibrate(); repaint(); } public void removeAllPlots() { plots.clear(); xAxis.setRange(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY); yAxis.setRange(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY); recalibrate(); repaint(); } public int getPlotCount() { return plots.size(); } public Plot getPlot(int index) { return plots.get(index); } public Rectangle2D getDragRectangle() { return dragRectangle; } public void setDragRectangle(Rectangle2D dragRectangle) { this.dragRectangle = dragRectangle; repaint(); } public void selectPoints(Rectangle2D dragRectangle, boolean addToSelection) { for (Plot plot : plots) { plot.selectPoints(dragRectangle, addToSelection); } repaint(); } public void clearSelection() { for (Plot plot : plots) { plot.clearSelection(); } repaint(); } private void resetPlots() { // xAxis.setRange(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY); // yAxis.setRange(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY); for (Plot p : plots) { p.resetAxes(); } } /** * Set origin style. Use a stroke of null to not draw origin */ public void setOriginStyle(Stroke originStroke, Paint originPaint) { this.originStroke = originStroke; this.originPaint = originPaint; } /** * Set axis style. */ public void setAxisStyle(Stroke axisStroke, Paint axisPaint) { this.axisStroke = axisStroke; this.axisPaint = axisPaint; } /** * Get axis stroke. */ public Stroke getAxisStroke() { return axisStroke; } /** * Get axis paint. */ public Paint getAxisPaint() { return axisPaint; } /** * Set frame style. Use a stroke of null to not draw frame */ public void setFrameStyle(Stroke frameStroke, Paint framePaint) { this.frameStroke = frameStroke; this.framePaint = framePaint; } /** * Set label style. */ public void setLabelStyle(Font labelFont, Paint labelPaint) { this.labelFont = labelFont; this.labelPaint = labelPaint; } /** * Get label font. */ public Font getLabelFont() { return labelFont; } /** * Get label paint. */ public Paint getLabelPaint() { return labelPaint; } /** * Set legend on/off. */ public void setShowLegend(boolean showLegend) { this.showLegend = showLegend; repaint(); } /** * Set legend alignment (use SwingConstants.NORTH_EAST etc.). */ public void setLegendAlignment(int alignment) { this.legendAlignment = alignment; repaint(); } public Dimension getMinimumSize() { return new Dimension(128, 128); } public Rectangle2D getPlotBounds() { return plotBounds; } public double getMajorTickSize() { return majorTickSize; } public double getMinorTickSize() { return minorTickSize; } public double getXTickLabelOffset() { return xTickLabelOffset; } public double getYTickLabelOffset() { return yTickLabelOffset; } private boolean calibrated = true; public void recalibrate() { calibrated = false; } protected void calibrate(Graphics2D g2, Dimension size) { resetPlots(); } protected boolean hasContents() { return plots.size() > 0; } public void paintComponent(Graphics g) { if (!hasContents()) return; Graphics2D g2 = (Graphics2D)g; Dimension size = getSize(); // g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); try { if (!calibrated) { calibrate(g2, size); calibrated = true; } g2.setFont(labelFont); double tickLabelHeight = g2.getFontMetrics().getHeight(); xTickLabelOffset = g2.getFontMetrics().getAscent(); yTickLabelOffset = g2.getFontMetrics().getAscent() / 2; double maxXTickLabelWidth = getMaxTickLabelWidth(g2, xAxis); double maxYTickLabelWidth = getMaxTickLabelWidth(g2, yAxis); double w = size.width - (majorTickSize * 1.25) - maxYTickLabelWidth - (maxXTickLabelWidth / 2); double h = size.height - yTickLabelOffset - (majorTickSize * 1.25) - tickLabelHeight; plotBounds = new Rectangle2D.Double((majorTickSize * 1.25) + maxYTickLabelWidth, yTickLabelOffset, w, h); xOffset = plotBounds.getX(); yOffset = plotBounds.getMaxY(); xScale = w / (xAxis.transform(xAxis.getMaxAxis()) - xAxis.transform(xAxis.getMinAxis())); yScale = -h / (yAxis.transform(yAxis.getMaxAxis()) - yAxis.transform(yAxis.getMinAxis())); g2.setPaint(plotBackgroundPaint); g2.fill(plotBounds); g2.setClip(plotBounds); Stroke oldStroke = g2.getStroke(); if (originStroke != null && originPaint != null) { double minX = xAxis.getMinAxis(); double maxX = xAxis.getMaxAxis(); double minY = yAxis.getMinAxis(); double maxY = yAxis.getMaxAxis(); g2.setPaint(originPaint); g2.setStroke(originStroke); if (minX<0.0 && maxX>0.0) { Line2D line = new Line2D.Double(transformX(0.0), transformY(minY), transformX(0.0), transformY(maxY)); g2.draw(line); } if (minY<0.0 && maxY>0.0) { Line2D line = new Line2D.Double(transformX(minX), transformY(0.0), transformX(maxX), transformY(0.0)); g2.draw(line); } } g2.setStroke(oldStroke); paintContents(g2); if (showLegend) { paintLegend(g2); } if (dragRectangle != null) { g2.setPaint(new Color(128, 128, 128, 128)); g2.fill(dragRectangle); } g2.setClip(null); paintFrame(g2); paintAxis(g2, xAxis, true); paintAxis(g2, yAxis, false); } catch (ChartRuntimeException cre) { // ignore (just won't paint chart) } } protected void paintContents(Graphics2D g2) { for (Plot plot : plots) { plot.paintPlot(g2, xScale, yScale, xOffset, yOffset); } } protected void paintFrame(Graphics2D g2) { g2.setPaint(framePaint); g2.setStroke(frameStroke); g2.draw(plotBounds); } /** * Transform a chart co-ordinates into a drawing co-ordinates */ protected double transformX(double value) { return ((xAxis.transform(value) - xAxis.transform(xAxis.getMinAxis())) * xScale) + xOffset; } /** * Transform a chart co-ordinates into a drawing co-ordinates */ protected double transformY(double value) { return ((yAxis.transform(value) - yAxis.transform(yAxis.getMinAxis())) * yScale) + yOffset; } /* * Transform a drawing co-ordinate into a chart co-ordinate * / private double untransformX(double value) { return xAxis.untransform( xAxis.transform(xAxis.getMinAxis()) + ((value - xOffset) / xScale)); }*/ /* * Transform a drawing co-ordinate into a chart co-ordinate * / private double untransformY(double value) { return yAxis.untransform( yAxis.transform(yAxis.getMinAxis()) + ((value - yOffset) / yScale)); }*/ protected void paintLegend(Graphics2D g2) { float width = 0; int itemCount = 0; for (int i = 0; i < getPlotCount(); i++) { String name = getPlot(i).getName(); if (name != null) { float w = (float)g2.getFontMetrics().stringWidth(name); if (width < w) { width = w; } itemCount++; } } if (width == 0) return; // no plots have names so just return width += 32; float height = (float)(g2.getFontMetrics().getAscent() + 8) * itemCount; float x, y; if (legendAlignment == SwingConstants.NORTH_WEST || legendAlignment == SwingConstants.WEST || legendAlignment == SwingConstants.SOUTH_WEST) { x = (float)(plotBounds.getX() + 8); } else if (legendAlignment == SwingConstants.NORTH_EAST || legendAlignment == SwingConstants.EAST || legendAlignment == SwingConstants.SOUTH_EAST) { x = (float)(plotBounds.getX() + plotBounds.getWidth() - width - 8); } else { // centered x = (float)(plotBounds.getX() + ((plotBounds.getWidth() - width) / 2)); } if (legendAlignment == SwingConstants.NORTH_WEST || legendAlignment == SwingConstants.NORTH || legendAlignment == SwingConstants.NORTH_EAST) { y = (float)(plotBounds.getY() + 8); } else if (legendAlignment == SwingConstants.SOUTH_EAST || legendAlignment == SwingConstants.SOUTH || legendAlignment == SwingConstants.SOUTH_WEST) { y = (float)(plotBounds.getY() + plotBounds.getHeight() - height - 8); } else { // centered y = (float)(plotBounds.getY() + ((plotBounds.getHeight() - height) / 2)); } Rectangle2D legendBounds = new Rectangle2D.Float(x, y, width, height); g2.setPaint(framePaint); g2.setStroke(frameStroke); g2.draw(legendBounds); x += 8; float iy = 8 + g2.getFontMetrics().getAscent(); y += g2.getFontMetrics().getAscent() + 4; for (int i = 0; i < getPlotCount(); i++) { Plot plot = getPlot(i); String name = plot.getName(); if (name != null) { g2.setPaint(plot.getLineColor()); g2.fill(new Rectangle2D.Float(x, y - 8, 8, 8)); g2.setPaint(framePaint); g2.drawString(name, x + 16, y); y += iy; } } } /** * Get the maximum width of the labels of an axis */ protected double getMaxTickLabelWidth(Graphics2D g2, Axis axis) { String label; double width; double maxWidth = 0; if (axis.getLabelFirst()) { // Draw first minor tick as a major one (with a label) label = axis.format(axis.getMinorTickValue(0, -1)); width = g2.getFontMetrics().stringWidth(label); if (maxWidth < width) maxWidth = width; } int n = axis.getMajorTickCount(); for (int i = 0; i < n; i++) { double value = axis.getMajorTickValue(i); label = axis.format(value); width = g2.getFontMetrics().stringWidth(label); if (maxWidth < width) maxWidth = width; } if (axis.getLabelLast()) { // Draw first minor tick as a major one (with a label) label = axis.format(axis.getMinorTickValue(0, n - 1)); width = g2.getFontMetrics().stringWidth(label); if (maxWidth < width) maxWidth = width; } return maxWidth; } protected void paintAxis(Graphics2D g2, Axis axis, boolean horizontalAxis) { int n1 = axis.getMajorTickCount(); int n2, i, j; n2 = axis.getMinorTickCount(-1); if (axis.getLabelFirst()) { // Draw first minor tick as a major one (with a label) paintMajorTick(g2, axis.getMinorTickValue(0, -1), horizontalAxis); for (j = 1; j < n2; j++) { paintMinorTick(g2, axis.getMinorTickValue(j, -1), horizontalAxis); } } else { for (j = 0; j < n2; j++) { paintMinorTick(g2, axis.getMinorTickValue(j, -1), horizontalAxis); } } for (i = 0; i < n1; i++) { paintMajorTick(g2, axis.getMajorTickValue(i), horizontalAxis); n2 = axis.getMinorTickCount(i); if (i == (n1-1) && axis.getLabelLast()) { // Draw last minor tick as a major one paintMajorTick(g2, axis.getMinorTickValue(0, i), horizontalAxis); for (j = 1; j < n2; j++) { paintMinorTick(g2, axis.getMinorTickValue(j, i), horizontalAxis); } } else { for (j = 0; j < n2; j++) { paintMinorTick(g2, axis.getMinorTickValue(j, i), horizontalAxis); } } } } protected void paintMajorTick(Graphics2D g2, double value, boolean horizontalAxis) { g2.setPaint(axisPaint); g2.setStroke(axisStroke); if (horizontalAxis) { String label = xAxis.format(value); double pos = transformX(value); Line2D line = new Line2D.Double(pos, plotBounds.getMaxY(), pos, plotBounds.getMaxY() + majorTickSize); g2.draw(line); g2.setPaint(labelPaint); double width = g2.getFontMetrics().stringWidth(label); g2.drawString(label, (float)(pos - (width / 2)), (float)(plotBounds.getMaxY() + (majorTickSize * 1.25) + xTickLabelOffset)); } else { String label = yAxis.format(value); double pos = transformY(value); Line2D line = new Line2D.Double(plotBounds.getMinX(), pos, plotBounds.getMinX() - majorTickSize, pos); g2.draw(line); g2.setPaint(labelPaint); double width = g2.getFontMetrics().stringWidth(label); g2.drawString(label, (float)(plotBounds.getMinX() - width - (majorTickSize * 1.25)), (float)(pos + yTickLabelOffset)); } } protected void paintMinorTick(Graphics2D g2, double value, boolean horizontalAxis) { g2.setPaint(axisPaint); g2.setStroke(axisStroke); if (horizontalAxis) { double pos = transformX(value); Line2D line = new Line2D.Double(pos, plotBounds.getMaxY(), pos, plotBounds.getMaxY() + minorTickSize); g2.draw(line); } else { double pos = transformY(value); Line2D line = new Line2D.Double(plotBounds.getMinX(), pos, plotBounds.getMinX() - minorTickSize, pos); g2.draw(line); } } // private Point2D currentPoint = null; public class MMListener extends MouseMotionAdapter { public void mouseMoved(MouseEvent me) { // currentPoint = me.getPoint(); } public void mouseDragged(MouseEvent me) { // selectionEnd = boundedPoint(me.getPoint(), getSize()); // currentPoint = realPoint(selectionEnd); // repaint(200); } } public class MListener extends MouseAdapter { Point2D start, finish; public void mouseExited(MouseEvent me) { // currentPoint = null; } public void mousePressed(MouseEvent me) { } public void mouseReleased(MouseEvent me) { } public void mouseClicked(MouseEvent me) { if (plotBounds != null && plotBounds.contains(me.getPoint())) { for (Plot plot : plots) { plot.pointClicked(me.getPoint(), me.isShiftDown()); } } } } }