Java tutorial
/*- * Copyright 2009 Diamond Light Source Ltd., Science and Technology * Facilities Council Daresbury Laboratory * * This file is part of GDA. * * GDA is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License version 3 as published by the Free * Software Foundation. * * GDA 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 General Public License for more * details. * * You should have received a copy of the GNU General Public License along * with GDA. If not, see <http://www.gnu.org/licenses/>. */ package gda.plots; import gda.observable.IObserver; import gda.scan.AxisSpec; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Stack; import java.util.Vector; import javax.imageio.ImageIO; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.JSeparator; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.filechooser.FileFilter; import org.eclipse.dawnsci.analysis.dataset.impl.DoubleDataset; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartMouseEvent; import org.jfree.chart.ChartMouseListener; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.LegendItem; import org.jfree.chart.LegendItemCollection; import org.jfree.chart.LegendItemSource; import org.jfree.chart.annotations.XYAnnotation; import org.jfree.chart.axis.LogarithmicAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.block.BlockBorder; import org.jfree.chart.entity.ChartEntity; import org.jfree.chart.entity.XYItemEntity; import org.jfree.chart.event.AxisChangeEvent; import org.jfree.chart.event.AxisChangeListener; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.XYItemRenderer; import org.jfree.chart.title.LegendTitle; import org.jfree.chart.title.TextTitle; import org.jfree.data.Range; import org.jfree.data.xy.XYDataset; import org.jfree.ui.RectangleEdge; import org.jfree.ui.RectangleInsets; import org.jfree.ui.VerticalAlignment; import org.jfree.util.ShapeUtilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides a simple data line oriented way to use the JFreeChart plotting classes. */ public class SimplePlot extends ChartPanel implements Printable, XYDataHandler { private static final Logger logger = LoggerFactory.getLogger(SimplePlot.class); /** * Inner class used to store axis limits on the zoomStack */ private class AxisLimits { private boolean xAuto; private double xMax; private double xMin; private boolean yAuto; private double yMax; private double yMin; private boolean yTwoAuto; private double yTwoMax; private double yTwoMin; private AxisLimits(boolean xAuto, boolean yAuto, double xMin, double xMax, double yMin, double yMax, boolean yTwoAuto, double yTwoMin, double yTwoMax) { this.xAuto = xAuto; this.yAuto = yAuto; this.xMin = xMin; this.xMax = xMax; this.yMin = yMin; this.yMax = yMax; this.yTwoAuto = yTwoAuto; this.yTwoMin = yTwoMin; this.yTwoMax = yTwoMax; } /** * @return value of yTwoAuto */ public boolean isYTwoAuto() { return yTwoAuto; } /** * @return yTwoMax */ public double getYTwoMax() { return yTwoMax; } /** * @return yTwoMin */ public double getYTwoMin() { return yTwoMin; } /** * @return Returns the xAuto. */ public boolean isXAuto() { return xAuto; } /** * @return Returns the yAuto. */ public boolean isYAuto() { return yAuto; } /** * @return Returns the xMax. */ public double getXMax() { return xMax; } /** * @return Returns the xMin. */ public double getXMin() { return xMin; } /** * @return Returns the yMax. */ public double getYMax() { return yMax; } /** * @return Returns the yMin. */ public double getYMin() { return yMin; } /** * Returns a suitable string representation * * @return the formatted string representation of the axis limits */ @Override public String toString() { return "AxisLimits: xMin = " + xMin + " xMax " + xMax + " yMin " + yMin + " yMax " + yMax; } } /** * Inner class to deal with mouse positions on the plot. Implements ChartMouseListener. Mouse motion events can be * used to provide continuous display of position - MousePositionTracker extends TextTitle so that it can be placed * on a JFreeChart for this purpose. Mouse click events can be used to return mouse positions to SimplePlot. */ private class MousePositionTracker extends TextTitle implements ChartMouseListener, Runnable { private boolean gettingCoordinates = false; private Thread thread; private boolean working = false; private double[] cursorCoordinates = new double[2]; private double[] entityCoordinates = new double[2]; private boolean clickWasInsideAxes = false; /** * This is the variable which stores the coordinates which are used by the right click menu */ public SimpleDataCoordinate coordinates; /** * @param string */ public MousePositionTracker(String string) { super(string); setPosition(RectangleEdge.BOTTOM); } /** * Implements the ChartMouseListener interface. Called when the mouse is moved. NB this is one step removed from * directly overriding the MouseMoved method. (Which is actually overriden in the main SimplePlot class - this * inconsistency ought to be sorted out). * * @param cme * a ChartMouseEvent representing the move. */ @Override public void chartMouseMoved(ChartMouseEvent cme) { // Set the text to the mouse position (after conversion by // SimplePlot) // If the mouse is outside the scaledDataArea then the text is // set to // " " (NB not "" which causes it to be removed from the chart // which is // then rescaled) if (working) { MouseEvent me = cme.getTrigger(); coordinates = convertMouseEvent(me); if (getScreenDataArea().contains(me.getX(), me.getY())) setText(coordinateFormatter.formatCoordinates(coordinates)); else setText(" "); } } /** * Switches off the displaying of mouse position */ public void switchOff() { if (working) { working = false; // Setting the text to "" makes the chart remove it completely setText(""); } } /** * Switches on the displaying of mouse position */ public void switchOn() { if (!working) { working = true; // Setting the text to " " puts it on the chart (though there is // nothing to seeForXAxis of course). setText(" "); } } /** * Implements the ChartMouseListener interface, called when a mouse button is clicked. * * @see org.jfree.chart.ChartMouseListener#chartMouseClicked(org.jfree.chart.ChartMouseEvent) */ @Override public void chartMouseClicked(ChartMouseEvent cme) { // If gettingCoordinates has been set by start method then gets // both the actual mouse coordinates and the coordinates of the // nearest plot entity. if (gettingCoordinates) { MouseEvent me = cme.getTrigger(); if (getScreenDataArea().contains(me.getX(), me.getY())) { clickWasInsideAxes = true; SimpleDataCoordinate sdc = convertMouseEvent(me); logger.debug("Mouse clicked at " + sdc.getX() + " " + sdc.getY()); logger.debug(" entity " + cme.getEntity()); cursorCoordinates = sdc.toArray(); XYItemEntity entityOne = (XYItemEntity) cme.getEntity(); if (entityOne != null) { entityCoordinates[0] = entityOne.getDataset().getXValue(entityOne.getSeriesIndex(), entityOne.getItem()); entityCoordinates[1] = entityOne.getDataset().getYValue(entityOne.getSeriesIndex(), entityOne.getItem()); } } else logger.debug("Mouse was clicked outside axes"); // Synchronize in order to set the flag which terminates // the run thread synchronized (this) { gettingCoordinates = false; notifyAll(); } } // If not getting coordinates then look for clicks on the legend // and popup the LinePropertiesEditor if appropriate. else { ChartEntity ce = cme.getEntity(); if (ce != null && ce instanceof SimpleLegendEntity) { if (linePropertiesEditor == null) { linePropertiesEditor = new LinePropertiesEditor(); } linePropertiesEditor.setCurrentLine(((SimpleLegendEntity) ce).getSeries()); // Setting visible true when it is already true causes // annoying flashing. if (!linePropertiesEditor.isVisible()) { linePropertiesEditor.setVisible(true); } linePropertiesEditor.toFront(); } } } /** * Returns the cursor coordinates of the last mouse click * * @return double[] x and y coordinates of mouse click */ private double[] getCursorCoordinates() { double[] values = null; if (clickWasInsideAxes) values = cursorCoordinates; return values; } /** * Returns the coordinates of the chart entity nearest the last mouse click. * * @return double[] x and y coordinates of entity */ private double[] getEntityCoordinates() { double[] values = null; if (clickWasInsideAxes) values = entityCoordinates; return values; } /** * Waits for the thread to terminate. */ private void join() { // This is the proper way to do it according // to example 94 in the Java Developers Almanac try { thread.join(); } catch (InterruptedException e) { logger.error("Unexpectedly interrupted in SimplePlot.MousePositionTracker.join()"); } } /** * Implements the Runnable interface. Just waits for the mouse to be clicked. * * @see java.lang.Runnable#run() */ @Override public void run() { synchronized (this) { do { try { wait(); } catch (InterruptedException ie) { logger.error("MousePositionTracker wait() unexpectedly interrupted"); } } while (gettingCoordinates); } } /** * Starts the collecting of coordinates. */ private void start() { gettingCoordinates = true; clickWasInsideAxes = false; thread = uk.ac.gda.util.ThreadManager.getThread(this, getClass().getName()); thread.start(); } } /** * Inner class to control dragging out of rectangles. */ private class RectangleDragger implements Runnable { private Rectangle2D dragArea = null; private boolean draggingRectangle; private Point2D dragPoint = null; private Rectangle2D dragRectangle = null; private Thread thread = null; private IObserver iObserver = null; /** * Constructor */ private RectangleDragger() { } private RectangleDragger(IObserver iObserver) { this.iObserver = iObserver; } /** * Returns the coordinates of the chart entities nearest to the corners of the dragged rectangle * * @return EntityOneX, EntityOneY, EntityTwoX, EntityTwoY */ private double[] getEntityValues() { double[] values = new double[4]; XYItemEntity entityOne = (XYItemEntity) getEntityForPoint((int) dragArea.getMinX(), (int) (dragArea.getMaxY())); XYItemEntity entityTwo = (XYItemEntity) getEntityForPoint((int) dragArea.getMaxX(), (int) (dragArea.getMinY())); values[0] = entityOne.getDataset().getXValue(entityOne.getSeriesIndex(), entityOne.getItem()); values[2] = entityOne.getDataset().getYValue(entityOne.getSeriesIndex(), entityOne.getItem()); values[1] = entityTwo.getDataset().getXValue(entityTwo.getSeriesIndex(), entityTwo.getItem()); values[3] = entityTwo.getDataset().getYValue(entityTwo.getSeriesIndex(), entityTwo.getItem()); return values; } /** * Returns the corners of the dragged rectangle * * @return array of minX, maxX, minY, maxY */ private double[] getValues() { // Convert the corners of the dragArea to user coordinates double[] values = new double[4]; ValueAxis va = getChart().getXYPlot().getDomainAxis(); values[0] = va.java2DToValue(dragArea.getMinX(), getScreenDataArea(), getChart().getXYPlot().getDomainAxisEdge()); values[1] = va.java2DToValue(dragArea.getMaxX(), getScreenDataArea(), getChart().getXYPlot().getDomainAxisEdge()); // The Y values need to be interchanged because screen // coordinates increase downwards va = getChart().getXYPlot().getRangeAxis(); values[3] = va.java2DToValue(dragArea.getMinY(), getScreenDataArea(), getChart().getXYPlot().getRangeAxisEdge()); values[2] = va.java2DToValue(dragArea.getMaxY(), getScreenDataArea(), getChart().getXYPlot().getRangeAxisEdge()); return values; } /** * Returns the actual dragged area (in Java coordinates) * * @return the actual dragged area */ public Rectangle2D getDragArea() { return dragArea; } /** * Wait for the thread to terminate. */ private void join() { // This is the proper way to do it according // to example 94 in the Java Developers Almanac try { thread.join(); } catch (InterruptedException e) { logger.error("Unexpectedly interrupted in SimplePlot.RectangleDragger.join()"); } } /** * SimplePlot calls this in place of the super class method when a rectangle is being dragged. See * SimplePlot.mouseDragged. * * @param e * the MouseEvent */ private void mouseDragged(MouseEvent e) { // This is a scaled down version of the ChartPanel method // workings pressed into service in a slightly different way Graphics2D g2 = (Graphics2D) getGraphics(); g2.setXORMode(Color.gray); // Undraw the previous rectangle if (dragRectangle != null) { g2.draw(dragRectangle); } // Calculate the new rectangle, it is constrained to be // within the scaled data area Rectangle2D scaledDataArea = getScreenDataArea(); double xmax = Math.min(e.getX(), scaledDataArea.getMaxX()); double ymax = Math.min(e.getY(), scaledDataArea.getMaxY()); dragRectangle = new Rectangle2D.Double(dragPoint.getX(), dragPoint.getY(), xmax - dragPoint.getX(), ymax - dragPoint.getY()); // Draw the new rectangle if (dragRectangle != null) { g2.draw(dragRectangle); } g2.dispose(); } /** * SimplePlot calls this in place of the super class method when a rectangle is being dragged. See * SimplePlot.mousePressed. * * @param e * the MouseEvent */ private void mousePressed(MouseEvent e) { // This is a scaled down version of the ChartPanel method // workings pressed into service in a slightly different way // This is the starting point of the rectangle if (dragRectangle == null) dragPoint = ShapeUtilities.getPointInRectangle(e.getX(), e.getY(), getScreenDataArea()); } /** * SimplePlot calls this in place of the super class method when a rectangle is being dragged. See * SimplePlot.mouseReleased. * * @param e * the MouseEvent */ private void mouseReleased(@SuppressWarnings("unused") MouseEvent e) { // This is a scaled down version of the ChartPanel method // workings pressed into service in a slightly different way // Calculate a Rectangle2D which represents the dragged area Rectangle2D scaledDataArea = getScreenDataArea(); double x = dragPoint.getX(); double y = dragPoint.getY(); double w = Math.min(dragRectangle.getWidth(), scaledDataArea.getMaxX() - dragPoint.getX()); double h = Math.min(dragRectangle.getHeight(), scaledDataArea.getMaxY() - dragPoint.getY()); dragArea = new Rectangle2D.Double(x, y, w, h); // Undraw the dragRectangle Graphics2D g2 = (Graphics2D) getGraphics(); g2.setXORMode(java.awt.Color.gray); g2.draw(dragRectangle); g2.dispose(); dragPoint = null; dragRectangle = null; if (iObserver == null) { // This ends the thread synchronized (this) { draggingRectangle = false; notifyAll(); } } else { iObserver.update(this, getValues()); } } /** * */ @Override public void run() { synchronized (this) { do { try { wait(); } catch (InterruptedException ie) { logger.error("RectangleDragger wait() unexpectedly interrupted"); } } while (draggingRectangle); } } private void start() { draggingRectangle = true; thread = uk.ac.gda.util.ThreadManager.getThread(this, getClass().getName()); thread.start(); } private synchronized void stop() { draggingRectangle = false; notifyAll(); } } /** * Provides LegendItems to a Legend. */ private class LegendItemsGetter implements LegendItemSource { private int datasetIndex; private boolean visible = true; private LegendItemsGetter(int datasetIndex) { this.datasetIndex = datasetIndex; } @Override public LegendItemCollection getLegendItems() { // This is a simplified version of what happens in XYPlot. LegendItemCollection result = new LegendItemCollection(); if (visible) { XYDataset dataset = ((XYPlot) getChart().getPlot()).getDataset(datasetIndex); if (dataset != null) { XYItemRenderer renderer = ((XYPlot) getChart().getPlot()).getRenderer(datasetIndex); for (int i = 0; i < dataset.getSeriesCount(); i++) { if (renderer.isSeriesVisible(i) && renderer.isSeriesVisibleInLegend(i)) { LegendItem item = renderer.getLegendItem(datasetIndex, i); if (item != null) { result.add(item); } } } } } return result; } /** * @param visible */ public void setVisible(boolean visible) { this.visible = visible; } } /** * */ public static int LINECHART = 0; /** * */ public static int BARCHART = 1; /** * */ public static int LEFTYAXIS = 0; /** * */ public static int RIGHTYAXIS = 1; private HashMap<Integer, SimpleXYSeries> seriesStore = new HashMap<Integer, SimpleXYSeries>(); protected SimpleXYSeriesCollection leftSeriesCollection = new SimpleXYSeriesCollection(this); protected SimpleXYSeriesCollection rightSeriesCollection = null; private LegendTitle leftSeriesLegend = null; private LegendItemsGetter leftSeriesLegendItemsGetter = null; private LegendTitle rightSeriesLegend = null; private LegendItemsGetter rightSeriesLegendItemsGetter = null; private boolean legendVisible = true; private MousePositionTracker mpt = null; private boolean pointerTracking = false; private RectangleDragger rd = null; private String title = "title"; private boolean titleVisible = true; private boolean yAxisTwoOn = false; private XYPlot xYPlot; private int secondaryAxisNumber = 1; private int secondaryDataSetIndex = 1; private CoordinateFormatter coordinateFormatter = null; private boolean zooming = false; private boolean turboMode = false; private JCheckBoxMenuItem zoomButton, turboModeButton, stripModeButton, xLimitsButton; private JCheckBoxMenuItem magnifyDataButton; private JMenuItem unZoomButton; private JMenuItem xLogLinButton; private JMenuItem yLogLinButton; private JMenuItem y2LogLinButton; private JMenuItem saveButton; private JMenuItem xFormatButton; private JMenuItem yFormatButton; private JMenuItem addLine = null; private JMenuItem removeFirst = null; private JMenuItem removeLast = null; private JMenuItem clearLines = null; private JCheckBoxMenuItem xAxisVerticalTicksButton; private Stack<AxisLimits> zoomStack = new Stack<AxisLimits>(); private boolean magnifyingImage = false; private LinePropertiesEditor linePropertiesEditor = null; private int magnifyXPoint; private int magnifyYPoint; private int magnifyWidth = 100; private int magnifyHeight = 50; private boolean magnifyingData = false; private DataMagnifierWindow dataMagnifierWindow; private Rectangle2D magnifyRectangle; private boolean magnifyRectangleIsNew; private Color dragColour = null; private Magnifier magnifier = null; private SimpleNumberAxis linearXAxis; private LogarithmicAxis logarithmicXAxis; private boolean xAxisLogarithmic = false; private SimpleNumberAxis dependentXAxis; private double emForXAxis; private double seeForXAxis; private boolean dependentXAxisOn = false; private AxisChangeListener xAxisChangeListener; private SimpleNumberAxis dependentYAxis; private double emForYAxis; private double seeForYAxis; private boolean dependentYAxisOn = false; private AxisChangeListener yAxisChangeListener; private SimpleNumberAxis linearYAxis; private LogarithmicAxis logarithmicYAxis; private boolean yAxisLogarithmic = false; private SimpleNumberAxis linearYAxisTwo; private LogarithmicAxis logarithmicYAxisTwo; private boolean yAxisTwoLogarithmic = false; private NumberFormat xAxisNumberFormat; private NumberFormat yAxisNumberFormat; private NumberFormat yAxisTwoNumberFormat; private boolean currentlyDoingAZoom; private boolean batching = false; private static int HMAX = 5; // History limit // this is a FIFO buffer for keeping a history of SimpleXYSeries private LinkedList<SimpleXYSeries> history = null; private JFileChooser fc; private final int type; private boolean showToolTip; /** * @return boolean */ public boolean isShowToolTip() { return showToolTip; } /** * Enable/Disable the tool tip for main plot * @param showToolTip */ public void setShowToolTip(boolean showToolTip) { this.showToolTip = showToolTip; XYItemRenderer xyrenderer = xYPlot.getRenderer(); if (showToolTip) xyrenderer.setToolTipGenerator(new SimpleXYToolTipGenerator()); else xyrenderer.setToolTipGenerator(null); xYPlot.setRenderer(xyrenderer); } /** * No argument constructor creates a LINECHART */ public SimplePlot() { this(LINECHART); } /** * Constructor which creates either a LINECHART or a BARCHART * * @param type * either SimplePlot.LINECHART or SimplePlot.BARCHART */ // Create simplePlot with autoRange true public SimplePlot(int type) { this(type, true, true); } private static UpdatePlotQueue updateQueue = new UpdatePlotQueue(); /** * @param type * @param autoRange * @param enableHistory */ public SimplePlot(int type, boolean autoRange, boolean enableHistory) { super(new JFreeChart(new XYPlot())); setChart(ChartFactory.createXYLineChart(title, "xaxis", "yaxis", leftSeriesCollection, PlotOrientation.VERTICAL, true, false, false)); this.type = type; if (enableHistory) { history = new LinkedList<SimpleXYSeries>(); addHistoryCommandsToPopupMenu(); } // For width and height greater than these values the graph is drawn by // using a transform on the Graphics2D. If this comes into operation // then the placing of components added with wrapAndDisplay goes wrong. // The easiest way to keep out of trouble is to make them large. This // also // stops distortion of fonts when expanding to full screen size. setMaximumDrawWidth(5000); setMaximumDrawHeight(2000); // Next line added to fix sun.dc.pr.PRException on Windows PC's getChart().setAntiAlias(false); xYPlot = getChart().getXYPlot(); if (type == BARCHART) { xYPlot.setRenderer(new SimpleXYBarRenderer()); } else { xYPlot.setRenderer(new SimpleXYItemRenderer()); xYPlot.getRenderer().setToolTipGenerator(new SimpleXYToolTipGenerator()); } // The x and y axes are created and set here so that SimpleNumberAxis // can // be used. This allows SimplePlot to keep the CoordinateFormatter up to // date about the accuracy required (seeForXAxis paintComponent()). NB // Any secondary y axes will be of type NumberAxis. createXAxes(autoRange); xYPlot.setDomainAxis(linearXAxis); createYAxes(autoRange); xYPlot.setRangeAxis(linearYAxis); // There are two legends. We use the exisiting default one for // lines displayed with respect to the left Y axis and create a // new one for those of the right Y axis (if there is one). // Normally getChart().getPlot() would provide the LegendItemSource // for the default legend but we extract it and set its Source to be a // LegendItemsGetter (defined here) and change its position to RIGHT // (default is TOP). // leftSeriesLegend = getChart().getLegend(); // leftSeriesLegend.setPosition(RectangleEdge.RIGHT); // This LegendItemsGetter will return only left series legend items. leftSeriesLegendItemsGetter = new LegendItemsGetter(0); // TEST CODE DO NOT REMOVE leftSeriesLegend = new SimpleLegendTitle(leftSeriesLegendItemsGetter); leftSeriesLegend.setMargin(new RectangleInsets(1.0, 1.0, 1.0, 1.0)); leftSeriesLegend.setBorder(new BlockBorder()); leftSeriesLegend.setBackgroundPaint(Color.white); leftSeriesLegend.setPosition(RectangleEdge.BOTTOM); leftSeriesLegend.setPosition(RectangleEdge.RIGHT); getChart().removeLegend(); getChart().addLegend(leftSeriesLegend); // END OF TEST CODE leftSeriesLegend.setSources(new LegendItemSource[] { leftSeriesLegendItemsGetter }); rightSeriesLegend = new SimpleLegendTitle(null); rightSeriesLegend.setPosition(RectangleEdge.RIGHT); rightSeriesLegend.setBorder(leftSeriesLegend.getBorder()); rightSeriesLegend.setBackgroundPaint(leftSeriesLegend.getBackgroundPaint()); // This LegendItemsGetter will return only right series legend items. rightSeriesLegendItemsGetter = new LegendItemsGetter(1); rightSeriesLegend.setSources(new LegendItemSource[] { rightSeriesLegendItemsGetter }); getChart().addLegend(rightSeriesLegend); // The mousePositionTracker is added as a subtitle mpt = new MousePositionTracker(""); addChartMouseListener(mpt); getChart().addSubtitle(mpt); // The coordinateFormatter is used in the MousePositionTracker when // displaying the mouse position. coordinateFormatter = new SimpleCoordinateFormatter(); dataMagnifierWindow = new DataMagnifierWindow(SwingUtilities.getRoot(this)); dataMagnifierWindow.setSimplePlot(this, leftSeriesCollection); // Make sure zooming starts in the correct state. setZooming(false); setTurboMode(false); setStripWidth(null); setTrackPointer(true); } /** * @return Plot is in TurboMode When in TurboMode then: 1. rendering is done by TurboXYItemRenderer 2. addPoints * does not directly fire a DataSetChange but rather an update queue is used 3. the ranges for the x and y * are controlled by SimplePlot - in calls getRangeBounds and getDomainBounds allow such features are * stripchart and fixed xmin/xmax //TODO */ public boolean isTurboMode() { return turboMode; } XYItemRenderer nonTurboRenderer; XYItemRenderer nonTurboSecondaryRenderer; TurboXYItemRenderer turboRenderer; TurboXYItemRenderer turboSecondaryRenderer; /** * @param turboMode * When in TurboMode then: 1. rendering is done by TurboXYItemRenderer 2. addPoints does not directly * fire a DataSetChange but rather an update queue is used 3. the ranges for the x and y are controlled * by SimplePlot - in calls getRangeBounds and getDomainBounds allow such features are stripchart and * fixed xmin/xmax //TODO * */ @Override public void setTurboMode(boolean turboMode) { if (type != LINECHART) { turboMode = false; return; } boolean switchToTurbo = !isTurboMode() & turboMode & type == LINECHART; this.turboMode = turboMode & type == LINECHART; if (type == LINECHART) { if (turboRenderer == null) { turboRenderer = new TurboXYItemRenderer(); turboRenderer.setToolTipGenerator(new SimpleXYToolTipGenerator()); } if (turboSecondaryRenderer == null) { turboSecondaryRenderer = new TurboXYItemRenderer(); } if (switchToTurbo) { nonTurboRenderer = xYPlot.getRenderer(); if (this.yAxisTwoOn) { nonTurboSecondaryRenderer = xYPlot.getRenderer(secondaryDataSetIndex); } } { XYItemRenderer renderer = turboMode ? turboRenderer : nonTurboRenderer; if (renderer != null) { if (renderer instanceof SimpleXYItemRenderer) { ((SimpleXYItemRenderer) renderer) .setXValueTransformer(leftSeriesCollection.getXValueTransformer()); } xYPlot.setRenderer(renderer); } } if (yAxisTwoOn) { XYItemRenderer renderer = turboMode ? turboSecondaryRenderer : nonTurboSecondaryRenderer; if (renderer != null) { if (renderer instanceof SimpleXYItemRenderer) { ((SimpleXYItemRenderer) renderer) .setXValueTransformer(rightSeriesCollection.getXValueTransformer()); } xYPlot.setRenderer(secondaryDataSetIndex, renderer); } } } if (turboMode) { setZooming(true); } else { setStripWidth(null); setDomainBounds(null); } turboModeButton.setSelected(turboMode); stripModeButton.setEnabled(turboMode); xLimitsButton.setEnabled(turboMode); } /** * @return Plot is in StripChart mode - the x range is set to display only the last set number of points */ public boolean isStripMode() { return stripWidth != null; } /** * Turns strip chart mode on and off. * @param stripWidth width in domain axis from last point value to min val */ public void setStripWidth(Double stripWidth) { this.stripWidth = type == LINECHART ? stripWidth : null; if (stripModeButton != null) stripModeButton.setSelected(isStripMode()); if (isStripMode()) { setDomainBounds(null); } } /** * Creates the linear and logarithmic x axes * * @param autoRange */ private void createXAxes(boolean autoRange) { xAxisNumberFormat = NumberFormat.getNumberInstance(); linearXAxis = new SimpleNumberAxis("xaxis"); initAxis(linearXAxis, autoRange, xAxisNumberFormat); logarithmicXAxis = new LogarithmicAxis("xaxis"); initLogAxis(logarithmicXAxis, autoRange, xAxisNumberFormat); } /** * Creates the linear and logarithmic y axes * * @param autoRange */ private void createYAxes(boolean autoRange) { yAxisNumberFormat = NumberFormat.getNumberInstance(); yAxisNumberFormat.setGroupingUsed(false); linearYAxis = new SimpleNumberAxis("yaxis"); initAxis(linearYAxis, autoRange, yAxisNumberFormat); logarithmicYAxis = new LogarithmicAxis("yaxis"); initLogAxis(logarithmicYAxis, autoRange, yAxisNumberFormat); } /** * Creates the linear and logarithmic second y axes * * @param autoRange */ private void createYAxesTwo(boolean autoRange) { yAxisTwoNumberFormat = NumberFormat.getNumberInstance(); yAxisTwoNumberFormat.setGroupingUsed(false); linearYAxisTwo = new SimpleNumberAxis("Second Y Axis"); initAxis(linearYAxis, autoRange, yAxisTwoNumberFormat); logarithmicYAxisTwo = new LogarithmicAxis("Second Y Axis"); initLogAxis(logarithmicYAxisTwo, autoRange, yAxisTwoNumberFormat); } /** * Allows any old JComponent to be displayed on a SimplePlot. There are currently layout problems if you use this * method. See PCS for details. * * @param jc * the JComponent * @param re * where to display, one of the RectangleEdge constants */ public void wrapAndDisplay(JComponent jc, RectangleEdge re) { add(jc); BlockWrapper bw = new BlockWrapper(jc); bw.setPosition(re); bw.setVerticalAlignment(VerticalAlignment.TOP); getChart().addSubtitle(bw); } /** * Creates the JPopupMenu, overrides (but uses) the super class method adding items for the Magnification and * Logarithmic axes. * * @param properties * boolean if true appears on menu * @param save * boolean if true appears on menu * @param print * boolean if true appears on menu * @param zoom * boolean if true appears on menu * @return the popup menu */ @Override protected JPopupMenu createPopupMenu(boolean properties, boolean save, boolean print, boolean zoom) { // Create the popup without the zooming parts JPopupMenu jpm = super.createPopupMenu(properties, false, print, false); // as the save function on the chartpanel doesn't remember its location, // we shall remove it and and create a new save option if (save) { jpm.add(new JSeparator()); // The save button saveButton = new JMenuItem("Save As"); saveButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { saveAs(); } }); jpm.add(saveButton); } jpm.add(new JSeparator()); // This button toggles the data-type magnification magnifyDataButton = new JCheckBoxMenuItem("Magnify(Data)"); magnifyDataButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setMagnifyingData(!isMagnifyingData()); } }); jpm.add(magnifyDataButton); jpm.add(new JSeparator()); // The zoomButton toggles the value of zooming. zoomButton = new JCheckBoxMenuItem("Zoom"); zoomButton.setHorizontalTextPosition(SwingConstants.LEFT); zoomButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setZooming(!isZooming()); } }); jpm.add(zoomButton); // The unZoomButton is not a toggle, it undoes the last zoom. unZoomButton = new JMenuItem("UnZoom"); unZoomButton.setEnabled(false); unZoomButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { unZoom(); } }); jpm.add(unZoomButton); if (type == LINECHART) { jpm.add(new JSeparator()); turboModeButton = new JCheckBoxMenuItem("Turbo Mode"); turboModeButton.setHorizontalTextPosition(SwingConstants.LEFT); turboModeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setTurboMode(!isTurboMode()); } }); turboModeButton.setSelected(isTurboMode()); jpm.add(turboModeButton); stripModeButton = new JCheckBoxMenuItem("StripChart Mode"); stripModeButton.setHorizontalTextPosition(SwingConstants.LEFT); stripModeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String widthStr = ""; { double width = lastDomainBoundsLeft != null ? lastDomainBoundsLeft.getLength() : Double.MAX_VALUE; widthStr = getXAxisNumberFormat().format(width); widthStr = JOptionPane.showInputDialog(null, "Enter the x strip width - clear for autorange", widthStr); if (widthStr == null) //cancel return; } Double newStripWidth = null; if (!widthStr.isEmpty()) { try { newStripWidth = Double.valueOf(widthStr); } catch (Exception ex) { logger.error(ex.getMessage(), ex); } } setStripWidth(newStripWidth); } }); stripModeButton.setSelected(isStripMode()); jpm.add(stripModeButton); xLimitsButton = new JCheckBoxMenuItem("Fix X Axis Limits"); xLimitsButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String minStr = ""; { double min = lastDomainBoundsLeft != null ? lastDomainBoundsLeft.getLowerBound() : Double.MAX_VALUE; minStr = getXAxisNumberFormat().format(min); minStr = JOptionPane.showInputDialog(null, "Enter the min x value - clear for autorange", minStr); if (minStr == null) //cancel return; } String maxStr = ""; if (!minStr.isEmpty()) { double max = lastDomainBoundsLeft != null ? lastDomainBoundsLeft.getUpperBound() : -Double.MAX_VALUE; maxStr = getXAxisNumberFormat().format(max); maxStr = JOptionPane.showInputDialog(null, "Enter the max x value - clear for autorange", maxStr); if (maxStr == null) //cancel return; } Range newBounds = null; if (!maxStr.isEmpty() && !minStr.isEmpty()) { try { newBounds = new Range(Double.valueOf(minStr), Double.valueOf(maxStr)); } catch (Exception ex) { logger.error(ex.getMessage(), ex); } } setDomainBounds(newBounds); } }); xLimitsButton.setSelected(false); jpm.add(xLimitsButton); } jpm.add(new JSeparator()); xLogLinButton = new JMenuItem("Logarithmic X axis"); xLogLinButton.setEnabled(true); xLogLinButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setXAxisLogarithmic(!isXAxisLogarithmic()); } }); jpm.add(xLogLinButton); yLogLinButton = new JMenuItem("Logarithmic Y axis"); yLogLinButton.setEnabled(true); yLogLinButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setYAxisLogarithmic(!isYAxisLogarithmic()); } }); jpm.add(yLogLinButton); y2LogLinButton = new JMenuItem("Logarithmic Y2 axis"); y2LogLinButton.setEnabled(false); y2LogLinButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setYAxisTwoLogarithmic(!isYAxisTwoLogarithmic()); } }); jpm.add(y2LogLinButton); jpm.add(new JSeparator()); // Adding a new button to allow the user to select the formatting they // want on the x and y axis xFormatButton = new JMenuItem("X Axis Format"); xFormatButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String Format = getXAxisNumberFormat().format(0.0); String input = JOptionPane.showInputDialog(null, "Enter the new formatting for the X axis, of the form 0.0000E00", Format); // try forcing this into some objects try { setScientificXAxis(new DecimalFormat(input)); } catch (Exception err) { logger.error("Could not use this format due to {}", e); } } }); jpm.add(xFormatButton); // Adding a new button to allow the user to select the formatting they // want on the x and y axis yFormatButton = new JMenuItem("Y Axis Format"); yFormatButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String Format = getYAxisNumberFormat().format(0.0); String input = JOptionPane.showInputDialog(null, "Enter the new formatting for the Y axis, of the form 0.0000E00", Format); // try forcing this into some objects try { setScientificYAxis(new DecimalFormat(input)); } catch (Exception err) { logger.error("Could not use this format due to {}", e); } } }); jpm.add(yFormatButton); // The zoomButton toggles the value of zooming. xAxisVerticalTicksButton = new JCheckBoxMenuItem("Vertical X Ticks"); xAxisVerticalTicksButton.setHorizontalTextPosition(SwingConstants.LEFT); xAxisVerticalTicksButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setVerticalXAxisTicks(xAxisVerticalTicksButton.isSelected()); } }); jpm.add(xAxisVerticalTicksButton); return jpm; } // this is called later in constructor private void addHistoryCommandsToPopupMenu() { JPopupMenu jpm = getPopupMenu(); jpm.add(new JSeparator()); // New button to allow user to add current plot to stored plots // This history mechanism assumes that there is only one line being plotted // and this line is stored in the last entry of seriesStore that was not in history addLine = new JMenuItem("Add current line to history"); addLine.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { SimpleXYSeries s = findLine(lastLineAdded - history.size()); if (s != null) { history.addFirst(s); if (history.size() > HMAX) { history.removeLast(); } } } }); jpm.add(addLine); // New button to allow user to remove first plot from stored plots removeFirst = new JMenuItem("Remove most recent line from history"); removeFirst.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { historyRemoveFirst(); } }); jpm.add(removeFirst); // New button to allow user to remove last plot from stored plots removeLast = new JMenuItem("Remove oldest line from history"); removeLast.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { historyRemoveLast(); } }); jpm.add(removeLast); // New button to allow user to add current plot to stored plots clearLines = new JMenuItem("Clear history"); clearLines.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { historyClear(); } }); jpm.add(clearLines); } /** * @param other */ public void copySettings(SimplePlot other) { setScientificXAxis(other.getXAxisNumberFormat()); setScientificYAxis(other.getYAxisNumberFormat()); setDomainBounds(other.leftDomainBounds); setStripWidth(other.stripWidth); } /** * Remove history lines from leftSeriesCollection and seriesStore */ private void historyCleanse() { for (SimpleXYSeries s : history) { leftSeriesCollection.removeSeries(s); seriesStore.remove(s.getLineNumber()); } lastLineAdded -= history.size(); } /** * Repopulate this object with lines from history */ private void historyRepopulate() { // now blast the List of lines to the parent SimplePlot int h = lastLineAdded; for (int l = 0; l < history.size(); l++) { SimpleXYSeries s = history.get(l); s.setLineNumber(h + l + 1); s.setName("History " + (l + 1)); initializeLine(s); } } /** * */ private void historyRemoveFirst() { historyCleanse(); if (history.size() > 0) history.removeFirst(); historyRepopulate(); validate(); } /** * */ private void historyRemoveLast() { historyCleanse(); if (history.size() > 0) history.removeLast(); historyRepopulate(); validate(); } /** * */ private void historyClear() { if (history == null) return; historyCleanse(); history.clear(); validate(); } /** * IDEWISOTT */ private void unZoom() { // Pop the set of limits at the top of the stack // and use them to reset axes.. setAxisLimits(zoomStack.pop()); // If the stack is now empty disable the unZoom button unZoomButton.setEnabled(!zoomStack.isEmpty()); } /** * @param minVal */ public void setXRangeMin(double minVal) { double maxval = linearXAxis.getRange().getUpperBound(); linearXAxis.setRange(new Range(minVal, maxval)); logarithmicXAxis.setRange(new Range(minVal, maxval)); } /** * @param maxVal */ public void setXRangeMax(double maxVal) { double minval = linearXAxis.getRange().getLowerBound(); linearXAxis.setRange(new Range(minval, maxVal)); logarithmicXAxis.setRange(new Range(minval, maxVal)); } /** * @param minVal */ public void setYRangeMin(double minVal) { double maxval = linearYAxis.getRange().getUpperBound(); linearYAxis.setRange(new Range(minVal, maxval)); logarithmicYAxis.setRange(new Range(minVal, maxval)); if (linearYAxisTwo != null) { linearYAxisTwo.setRange(new Range(minVal, maxval)); logarithmicYAxisTwo.setRange(new Range(minVal, maxval)); } } /** * @param maxVal */ public void setYRangeMax(double maxVal) { double minval = linearYAxis.getRange().getLowerBound(); linearYAxis.setRange(new Range(minval, maxVal)); logarithmicYAxis.setRange(new Range(minval, maxVal)); if (linearYAxisTwo != null) { linearYAxisTwo.setRange(new Range(minval, maxVal)); logarithmicYAxisTwo.setRange(new Range(minval, maxVal)); } } Collection<Integer> linesChanged = new Vector<Integer>(); /** * Adds a single data point to a line. The line must already have been initialized. * @param scanId * * @param which * the line * @param x * the new x value * @param y * the new y value */ public void addPointToLine(@SuppressWarnings("unused") String scanId, int which, double x, double y) { addPointToLine(which, x, y); } /** * @param which * @param x * @param y */ @Override public void addPointToLine(int which, double x, double y) { addPointToLine(which, x, y, !isTurboMode()); } /** * Adds a single data point to a line. The line must already have been initialized. * * @param which * the line * @param x * the new x value * @param y * the new y value * @param notifyDirectly * is the DataSet to fireDataSetChange directly or via a 0.5 period event */ public void addPointToLine(int which, double x, double y, boolean notifyDirectly) { /* * if (newPlot) { setXRangeMax(x + 0.1); setXRangeMin(x - 0.1); setYRangeMax(y + 0.1); setYRangeMin(y - 0.1); * newPlot = false; } else { // before anything see if this needs to be rescaled if * (linearXAxis.getRange().getLowerBound() > x) { setXRangeMin(x - linearXAxis.getRange().getLength()); } // * before anything see if this needs to be rescaled if (linearXAxis.getRange().getUpperBound() < x) { * setXRangeMax(x + linearXAxis.getRange().getLength()); } // before anything see if this needs to be rescaled * if (linearYAxis.getRange().getLowerBound() > y) { setYRangeMin(y - linearYAxis.getRange().getLength()); } // * before anything see if this needs to be rescaled if (linearYAxis.getRange().getUpperBound() < y) { * setYRangeMax(y + linearYAxis.getRange().getLength()); } } */ SimpleXYSeries s = findLine(which); if (s == null) { logger.warn("SimplePlot - unable to find line " + Integer.toString(which)); return; } s.add(x, y, notifyDirectly); if (!notifyDirectly) { updateQueue.update(this); } if (isTurboMode()) { synchronized (linesChanged) { if (!linesChanged.contains(which)) linesChanged.add(which); } } } /** * Adds a second y axis on the right of the chart. This will be completely independent of the first y axis and can * have data independently referred to it. * * @see #addDependentYAxis */ public void addYAxisTwo() { addYAxisTwo(false); } /** * @see #addYAxisTwo() * @param autoRange */ public void addYAxisTwo(boolean autoRange) { if (!dependentYAxisOn) { createYAxesTwo(autoRange); y2LogLinButton.setEnabled(true); xYPlot.setRangeAxis(secondaryAxisNumber, linearYAxisTwo); yAxisTwoLogarithmic = false; // Note that the newly created SimpleXYSeriesCollection and // SimpleXYItemRenderer must have their x value transformers set // to whatever is currently in the leftSeriesCollection. rightSeriesCollection = new SimpleXYSeriesCollection(this); rightSeriesCollection.setXValueTransformer(leftSeriesCollection.getXValueTransformer()); xYPlot.setDataset(secondaryDataSetIndex, rightSeriesCollection); xYPlot.mapDatasetToRangeAxis(secondaryDataSetIndex, secondaryAxisNumber); SimpleXYItemRenderer newRenderer = isTurboMode() ? new TurboXYItemRenderer() : new SimpleXYItemRenderer(); newRenderer.setXValueTransformer(leftSeriesCollection.getXValueTransformer()); xYPlot.setRenderer(secondaryDataSetIndex, newRenderer); // If there is to be a second YAxis then we move the Legend for // the // first. leftSeriesLegend.setPosition(RectangleEdge.LEFT); yAxisTwoOn = true; } } /** * Converts the (pixel) coordinates of a MouseEvent into their actual plot value equivalents * * @param me * the MouseEvent to be converted * @return SimpleDataCoordinate object containing the converted coordinates */ public SimpleDataCoordinate convertMouseEvent(MouseEvent me) { SimpleDataCoordinate sdc = new SimpleDataCoordinate( xYPlot.getDomainAxis().java2DToValue(me.getX(), getScreenDataArea(), xYPlot.getDomainAxisEdge()), xYPlot.getRangeAxis().java2DToValue(me.getY(), getScreenDataArea(), xYPlot.getRangeAxisEdge())); if (dependentXAxisOn) { sdc.setDependentXCalibrationValues(this.emForXAxis, this.seeForXAxis); } if (dependentYAxisOn) { sdc.setDependentYCalibrationValues(this.emForYAxis, this.seeForYAxis); } return sdc; } /** * Deletes a line * * @param which * the line to delete */ @Override public void deleteLine(int which) { SimpleXYSeries s; if ((s = findLine(which)) != null) { if (s.getAxis() == RIGHTYAXIS && rightSeriesCollection != null) rightSeriesCollection.removeSeries(s); else { if (s.isVisible()) { leftSeriesCollection.removeSeries(s); } } seriesStore.remove(which); repaint(); } } /** * Deletes all lines */ @Override public void deleteAllLines() { leftSeriesCollection.removeAllSeries(); if (rightSeriesCollection != null) { rightSeriesCollection.removeAllSeries(); } seriesStore.clear(); // all lines deleted, so set highest used line number to 0 // (meaning the next line number will be 1) highestUsedLineNumber = 0; } /** * Adds a second x axis which has values dependent on those of the first xaxis. This allows different units to be * shown for the same data. The x values of this axis are related to those of the first xaxis by: xtwo = em * xone + * see * * @param em * the gradient of the expression relating the two axes * @param see * the intercept of the expression relating the two axes */ public void addDependentXAxis(double em, double see) { if (!dependentXAxisOn && !isXAxisLogarithmic()) { xLogLinButton.setEnabled(false); dependentXAxis = new SimpleNumberAxis("dependent xaxis"); dependentXAxis.setAutoRange(false); this.emForXAxis = em; this.seeForXAxis = see; setDependentXAxisRange(); xYPlot.setDomainAxis(1, dependentXAxis); dependentXAxisOn = true; // FIXME: should prevent setting of logarithmic while this is on xAxisChangeListener = new AxisChangeListener() { @Override public void axisChanged(AxisChangeEvent event) { if (!currentlyDoingAZoom) { // The superclass zooming mechanism correctly deals // with the dependent axis when zooming and resetting // the range actually causes it to go wrong. setDependentXAxisRange(); } } }; linearXAxis.addChangeListener(xAxisChangeListener); } } private void setDependentXAxisRange() { Range r = linearXAxis.getRange(); Range newR = new Range(r.getLowerBound() * emForXAxis + seeForXAxis, r.getUpperBound() * emForXAxis + seeForXAxis); dependentXAxis.setRange(newR); } /** * Removes the dependent XAxis. */ public void removeDependentXAxis() { if (dependentXAxisOn) { xLogLinButton.setEnabled(true); xYPlot.setDomainAxis(1, null); dependentXAxisOn = false; dependentXAxis = null; linearXAxis.removeChangeListener(xAxisChangeListener); xAxisChangeListener = null; } } /** * Adds a second y axis which has values dependent on those of the first xaxis. This allows different units to be * shown for the same data. The y values of this axis are related to those of the first yaxis by: ytwo = em * yone + * see * * @see #addYAxisTwo() * @param em * the gradient of the expression relating the two axes * @param see * the intercept of the expression relating the two axes */ public void addDependentYAxis(double em, double see) { if (!dependentYAxisOn && !isYAxisLogarithmic() && !yAxisTwoOn) { yLogLinButton.setEnabled(false); dependentYAxis = new SimpleNumberAxis("dependent yaxis"); dependentYAxis.setAutoRange(false); this.emForYAxis = em; this.seeForYAxis = see; setDependentYAxisRange(); xYPlot.setRangeAxis(1, dependentYAxis); dependentYAxisOn = true; yAxisChangeListener = new AxisChangeListener() { @Override public void axisChanged(AxisChangeEvent event) { // The superclass zooming mechanism correctly deals // with the dependent axis when zooming and resetting // the range actually causes it to go wrong. if (!currentlyDoingAZoom) { setDependentYAxisRange(); } } }; linearYAxis.addChangeListener(yAxisChangeListener); } } private void setDependentYAxisRange() { Range r = linearYAxis.getRange(); Range newR = new Range(r.getLowerBound() * emForYAxis + seeForYAxis, r.getUpperBound() * emForYAxis + seeForYAxis); dependentYAxis.setRange(newR); } /** * Removes dependent YAxis. */ public void removeDependentYAxis() { if (dependentYAxisOn) { yLogLinButton.setEnabled(true); xYPlot.setRangeAxis(1, null); dependentYAxisOn = false; dependentYAxis = null; linearYAxis.removeChangeListener(yAxisChangeListener); yAxisChangeListener = null; } } /** * Allows a rectangle to be dragged out on the plot and returns maximum and minimum x and y coordinates. * * @return the coordinates in order minX, maxX, minY, maxY */ public double[] dragRectangle() { // Create a RectangleDragger, start it, wait // for its thread to end and then return its // values. rd = new RectangleDragger(); rd.start(); rd.join(); double[] values = rd.getValues(); rd = null; return values; } /** * Allows a rectangle to be dragged out on the plot and returns the coordinates of two ChartEntity objects, one at * bottom left, one at top right. * * @return x position of bottom left entity, x position of top right entity, y position of bottom right entity, y * position of top right entity. */ public double[] dragRectanglePoints() { // Create a RectangleDragger, start it, wait // for its thread to end and then return its // values. rd = new RectangleDragger(); rd.start(); rd.join(); double[] values = rd.getEntityValues(); rd = null; return values; } /** * Allows a rectangle to be dragged out on the plot and returns the actual Rectangle2D which is dragged * * @return the dragged rectangle */ public Rectangle2D dragRectangleRaw() { // Create a RectangleDragger, start it, wait // for its thread to end and then return its // values. rd = new RectangleDragger(); rd.start(); rd.join(); Rectangle2D r2D = rd.getDragArea(); rd = null; return r2D; } /** * @param dragObserver */ public void startObservingDragging(IObserver dragObserver) { rd = new RectangleDragger(dragObserver); rd.start(); } /** * */ public void stopObservingDragging() { rd.stop(); rd = null; } /** * Waits for the next mouse click and returns its coordinates. * * @return coordinates of cursor at mouse click */ public double[] getCursorCoordinates() { // MousePositionTracker is responsible for this mpt.start(); mpt.join(); return mpt.getCursorCoordinates(); } /** * Returns the last known position of the cursor * * @return coordinates. */ public double[] getCoordinates() { double result[] = { 0.0, 0.0 }; if (mpt.coordinates != null) { result[0] = mpt.coordinates.getX(); result[1] = mpt.coordinates.getY(); } return result; } /** * Waits for the next mouse click and returns the coordinates of the entity nearest to it. * * @return coordinates of entity. */ public double[] getEntityCoordinates() { // MousePositionTracker is responsible for this mpt.start(); mpt.join(); return mpt.getEntityCoordinates(); } /** * A tricky little service method which searches the static int fields of a class for one called fieldName and * returns its integer value. * * @param fieldName * the field name to get * @param theClass * find in this class * @return the integer value of the named field or -1 if not found */ public static int getIntFieldFromClass(String fieldName, Class<?> theClass) { Integer found; int field = -1; try { found = (Integer) theClass.getField(fieldName.toUpperCase()).get(null); field = found.intValue(); } catch (NoSuchFieldException e) { logger.error("Class " + theClass + " does not contain " + fieldName); } catch (IllegalAccessException e) { logger.error("Class " + theClass + " will not allow access"); } return field; } /** * Gets the number of lines * * @return the number of lines */ @Override public int getNextAvailableLine() { return highestUsedLineNumber + 1; } /** * Returns the maximum value of the Y axis * * @return Y axis maximum value */ public double getYAxisMax() { // FIXME: this does not seem to return quite the right value // where does the actual axis maximum (always greater than // this) come from? return xYPlot.getRangeAxis().getUpperBound(); } /** * Returns the minimum value of the Y axis * * @return Y axis minimum value */ public double getYAxisMin() { return xYPlot.getRangeAxis().getLowerBound(); } /** * Returns the maximum value of the X axis * * @return X axis maximum value */ public double getXAxisMax() { // FIXME: this does not seem to return quite the right value // where does the actual axis maximum (always greater than // this) come from? return xYPlot.getDomainAxis().getUpperBound(); } /** * Returns the minimum value of the X axis * * @return X axis minimum value */ public double getXAxisMin() { return xYPlot.getDomainAxis().getLowerBound(); } /** * Initialises a line using LEFTYAXIS for its y values. * * @param which * the line to initialise (numbered from 0) */ public void initializeLine(int which) { initializeLine(which, LEFTYAXIS); } /** * Initialises a line using specified axis for its y values * * @param which * the line to initialise (numbered from 0) * @param axis * the axis - LEFTAXIS */ public void initializeLine(int which, int axis) { initializeLine(which, axis, null, "", "", "", null); } /** * Initialises a line using specified axis for its y values and name * * @param which * the line to initialise (numbered from 0) * @param axis * the axis - LEFTAXIS * @param name */ @Override public void initializeLine(int which, int axis, String name, String xAxisHeader, String yAxisHeader, String dataFileName, AxisSpec yAxisSpec) { initializeLineImpl(which, axis, name, null); } /** * Initialises a line using specified SimpleXYSeries * * @param s */ public void initializeLine(SimpleXYSeries s) { initializeLineImpl(s.getLineNumber(), s.getAxis(), null, s); } private int lastLineAdded = 0; // last line added /** Highest line number used so far */ private int highestUsedLineNumber = 0; private void initializeLineImpl(int which, int axis, String name, SimpleXYSeries s) { if (lineExists(which)) { deleteLine(which); } if (yAxisTwoOn == false) axis = LEFTYAXIS; SimpleXYSeries newLine = s != null ? s : new SimpleXYSeries(name != null ? name : "line " + which, which, axis); newLine.setBatching(batching); if (newLine.isVisible()) { if (axis == LEFTYAXIS) { leftSeriesCollection.addSeries(newLine); } else { rightSeriesCollection.addSeries(newLine); } synchronized (linesChanged) { linesChanged.clear(); } lastLineAdded = which; } seriesStore.put(which, newLine); highestUsedLineNumber = Math.max(which, highestUsedLineNumber); } /** * Sets the data points for a line - the line must already have been initialized. * * @param which * the line * @param x * array of x values * @param y * array of y values */ public void setLinePoints(int which, double x[], double y[]) { SimpleXYSeries s; if ((s = findLine(which)) != null) { s.setPoints(x, y); repaint(); } } /** * Sets a line to be drawn as both markers at the individual points and lines joining them. * * @param which * the line */ public void lineAndPoints(int which) { setLineType(which, Type.LINEANDPOINTS); } /** * Checks whether a line exists * * @param lineNumber * the line to check * @return true if there is a line with the given number */ public boolean lineExists(int lineNumber) { return (findLine(lineNumber) != null); } /** * Sets a line to be drawn only as the lines joining individual points. * * @param which * the line */ public void linesOnly(int which) { setLineType(which, Type.LINEONLY); } /** * Part of the implementation of MouseMotionListener - overrides the super class (ChartPanel) implementation so that * the mouse can be used to select a rectangle as well as for zooming. * * @param e * the mouse event which caused the call */ @Override public void mouseDragged(MouseEvent e) { // If the rectangle dragger is not in operation then call the // super class method (to deal with any possible zooming) then // deal with magnifyingImage or magnifyingData. if (rd == null) { super.mouseDragged(e); if ((magnifyingImage || magnifyingData) && (e.getModifiers() & InputEvent.BUTTON3_MASK) == 0) { Graphics2D g2 = (Graphics2D) getGraphics(); g2.setXORMode(dragColour); if (magnifyRectangle != null) { if (magnifyRectangleIsNew) { magnifyRectangleIsNew = false; } else { g2.fill(magnifyRectangle); } } if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) { magnifyWidth = e.getX() - magnifyXPoint; magnifyHeight = e.getY() - magnifyYPoint; } recalculateMagnifyRectangle(e); if ((e.getModifiers() & InputEvent.BUTTON2_MASK) != 0) { magnifier.update(magnifyRectangle); } if (magnifyRectangle != null) { g2.fill(magnifyRectangle); } g2.dispose(); } } else { rd.mouseDragged(e); } } /** * Part of the implementation of MouseListener - overrides the super class (ChartPanel) implementation so that the * mouse can be used to select a rectangle as well as for zooming. * * @param e * the mouse event which caused the call */ @Override public void mousePressed(MouseEvent e) { // Unless a rectangle is being dragged we just // want to call the super class method (which // deals with zooming) if (rd == null) { super.mousePressed(e); if (magnifyingImage || magnifyingData) { if (e.getButton() == MouseEvent.BUTTON1) { magnifyRectangle = null; magnifyXPoint = e.getX(); magnifyYPoint = e.getY(); } if (!e.isPopupTrigger()) { Graphics2D g2 = (Graphics2D) getGraphics(); g2.setXORMode(dragColour); if (magnifyRectangle != null) { g2.fill(magnifyRectangle); } g2.dispose(); } } } else { rd.mousePressed(e); } } /** * Part of the implementation of MouseListener. Overrides the super class (ChartPanel) implementation so that the * mouse can be used to select a rectangle as well as for zooming. * * @see #mousePressed(java.awt.event.MouseEvent) * @see #mouseDragged(java.awt.event.MouseEvent) * @param e * the mouse event which caused the call */ @Override public void mouseReleased(MouseEvent e) { // Unless a rectangle is being dragged we just // want to call the super class method (which // deals with zooming) if (rd == null) { super.mouseReleased(e); if (magnifyingImage || magnifyingData) { // If the button released is BUTTON1 then the rectangle for // magnification will have been resized. if (e.getButton() == MouseEvent.BUTTON1) { magnifyWidth = e.getX() - magnifyXPoint; magnifyHeight = e.getY() - magnifyYPoint; magnifyRectangleIsNew = true; } // If the button released is BUTTON2 then the rectangle will // have been being dragged around. Need to redraw in XOR mode // one // last time to remove rectangle from plot. else if (e.getButton() == MouseEvent.BUTTON2) { Graphics2D g2 = (Graphics2D) getGraphics(); g2.setXORMode(dragColour); if (magnifyRectangle != null) { g2.fill(magnifyRectangle); } g2.dispose(); } recalculateMagnifyRectangle(e); magnifier.update(magnifyRectangle); } } else { rd.mouseReleased(e); } } /** * Part of the implementation of MouseListener - overrides the super class (ChartPanel) implementation but currently * does nothing. * * @param e * the mouse event which caused the call */ @Override public void mouseMoved(MouseEvent e) { super.mouseMoved(e); } /** * Recalculates the rectangle which should be magnified. * * @param e * the MouseEvent which triggered the recalculation */ private void recalculateMagnifyRectangle(MouseEvent e) { if (magnifyWidth == 0 || magnifyHeight == 0) return; Rectangle2D scaledDataArea = getScreenDataArea(); double widthToUse; double heightToUse; double xToUse; double yToUse; // If magnifyWidth is positive then e.getX() is // the end of the rectangle so the start is (e.getX() - magnifyWidth ). // If magnifyWidth is negative then e.getX() is the start of a // rectangle with the opposite sign width. Similarly for y. if (magnifyWidth > 0) { xToUse = e.getX() - magnifyWidth; widthToUse = magnifyWidth; } else { xToUse = e.getX(); widthToUse = -1.0 * magnifyWidth; } if (magnifyHeight > 0) { yToUse = e.getY() - magnifyHeight; heightToUse = magnifyHeight; } else { yToUse = e.getY(); heightToUse = -1.0 * magnifyHeight; } // xToUse and yToUse now specify the top left of the rectangle. In order // to keep the magnified rectangle inside the data area the start point // must be inside a rectangle which is the scaledDataArea reduced in // width // and height by the width and height of the magnifyRectangle. Point2D revisedStartPoint = ShapeUtilities.getPointInRectangle(xToUse, yToUse, new Rectangle2D.Double(scaledDataArea.getMinX(), scaledDataArea.getMinY(), scaledDataArea.getWidth() - magnifyWidth, scaledDataArea.getHeight() - magnifyHeight)); magnifyRectangle = new Rectangle2D.Double(revisedStartPoint.getX(), revisedStartPoint.getY(), widthToUse, heightToUse); } /** * Sets a line to be drawn as individual point markers only * * @param which * the line */ public void pointsOnly(int which) { setLineType(which, Type.POINTSONLY); } /** * Removes the second y axis */ public void removeYAxisTwo() { if (yAxisTwoOn) { xYPlot.setRangeAxis(secondaryAxisNumber, null); xYPlot.setRenderer(secondaryAxisNumber, null); xYPlot.setDataset(secondaryAxisNumber, null); rightSeriesCollection = null; leftSeriesLegend.setPosition(RectangleEdge.RIGHT); yAxisTwoOn = false; linearYAxisTwo = null; logarithmicYAxisTwo = null; yAxisTwoNumberFormat = null; // A new, linear Y2 axis will be created if switched on again // so this is the correct text for the menu item. y2LogLinButton.setText("Logarithmic Y2 axis"); y2LogLinButton.setEnabled(false); } } /** * Sets the visibility of the Legend * * @param newValue * true or false */ @Override public void setLegendVisible(boolean newValue) { legendVisible = newValue; leftSeriesLegendItemsGetter.setVisible(legendVisible); rightSeriesLegendItemsGetter.setVisible(legendVisible); repaint(); } /** * Sets the colour used to draw the line - NB this also sets the colour used to draw the point markers since this is * the behaviour most people will want - to use different colours each call of setLineColor must be followed by a * call of setLineMarkerColor. * * @param which * the line * @param color * a Color */ @Override public void setLineColor(int which, Color color) { SimpleXYSeries s; if ((s = findLine(which)) != null) { s.setPaint(color); s.setSymbolPaint(color); repaint(); } } /** * Gets the reference to a particular line * * @param which * The number of the line which is required * @return A SimpleXYSeries which is the line specified */ public SimpleXYSeries getLine(int which) { return findLine(which); } /** * Finds a line from left or right series collections * * @param which * the line number * @return the SimpleXYSeries which represents the line (or null) */ private SimpleXYSeries findLine(int which) { return seriesStore.get(which); } /** * Sets the colour used to draw the line. * * @param which * the line * @param colorName * the name of a colour, any name which appears as a static field in class Color or is a standard X11 * colour name will work */ public void setLineColor(int which, String colorName) { setLineColor(which, ColorFactory.createColor(colorName)); } /** * Sets the Marker for a line. * * @param which * @param marker */ @Override public void setLineMarker(int which, Marker marker) { SimpleXYSeries s; if ((s = findLine(which)) != null) { s.setMarker(marker); repaint(); } } /** * Sets the marker shape used for the points on a line. * * @param which * the line * @param name * the name of the marker to use */ public void setLineMarker(int which, String name) { setLineMarker(which, Marker.fromString(name)); } /** * Sets the colour used to draw the marker symbol. * * @param which * the line * @param color * a Color */ public void setLineMarkerColor(int which, Color color) { SimpleXYSeries s; if ((s = findLine(which)) != null) { s.setSymbolPaint(color); repaint(); } } /** * Sets the colour used to draw the marker symbol. * * @param which * the line * @param colorName * the name of a colour */ public void setLineMarkerColor(int which, String colorName) { Color newColor = ColorFactory.createColor(colorName); setLineMarkerColor(which, newColor); } /** * Sets the size of the marker for a line. * * @param which * the line * @param size * the marker size, any value, default is 6 */ public void setLineMarkerSize(int which, int size) { SimpleXYSeries s; if ((s = findLine(which)) != null) { s.setMarkerSize(size); repaint(); } } /** * Sets the name of a line - this is what will appear next to it in the legend. * * @param which * the line * @param name * the name */ public void setLineName(int which, String name) { SimpleXYSeries s; if ((s = findLine(which)) != null) { s.setName(name); repaint(); } } /** * Sets the pattern used to draw the line. * * @param which * the line * @param simpleStroke * any Pattern */ public void setLinePattern(int which, Pattern simpleStroke) { SimpleXYSeries s; if ((s = findLine(which)) != null) { s.setPattern(simpleStroke); repaint(); } } /** * Sets the pattern used to draw the line. * * @param which * the line * @param name * the name of the pattern to use. Allowed values are "solid", "dotted", "dashed", "dotdashed" */ public void setLinePattern(int which, String name) { setLinePattern(which, Pattern.fromString(name)); } /** * Set the line type (points, points and line, line). * * @param which * the line * @param type * the type */ public void setLineType(int which, Type type) { // This should be handled inside SimpleXYSeries SimpleXYSeries s; if ((s = findLine(which)) != null) { s.setType(type); repaint(); } } /** * Set the line type for all lines * @param type */ @Override public void setLineType(Type type) { for (SimpleXYSeries s : seriesStore.values()) { s.setType(type); } repaint(); } /** * Sets the line type. * * @param which * the line * @param name * the name of the type to use, allowed values are: "lineonly", "pointsonly", "lineandpoints" */ public void setLineType(int which, String name) { setLineType(which, Type.fromString(name)); } /** * Sets whether or not line is visible. * * @param which * the line * @param visibility * true to be visible */ @Override public void setLineVisibility(int which, boolean visibility) { SimpleXYSeries s; if ((s = findLine(which)) != null) { boolean isVisible = s.isVisible(); if (isVisible == visibility) return; if (s.getAxis() == RIGHTYAXIS && rightSeriesCollection != null) { if (isVisible) { rightSeriesCollection.removeSeries(s); } s.setVisible(visibility); if (visibility) { rightSeriesCollection.addSeries(s); } } else { if (isVisible) { leftSeriesCollection.removeSeries(s); } s.setVisible(visibility); if (visibility) { leftSeriesCollection.addSeries(s); } } repaint(); } } /** * Sets whether or not the line is part of the data, this should be set to false for lines which are only markers on * the plot. * * @param which * the line * @param includedInData */ public void setLineIncludedInData(int which, boolean includedInData) { SimpleXYSeries s; if ((s = findLine(which)) != null) { s.setIncludedInData(includedInData); repaint(); } } /** * Sets the thickness of a line. * * @param which * the line to change * @param width * the thickness of the line */ public void setLineWidth(int which, int width) { SimpleXYSeries s; if ((s = findLine(which)) != null) { s.setLineWidth(width); repaint(); } } /** * Sets the title of the plot. * * @param title * the title to use */ @Override public void setTitle(String title) { this.title = title; // See comments in setTitleVisible if (titleVisible) { getChart().setTitle(title); } } /** * Sets the visibility of the title of the plot. * * @param newValue */ public void setTitleVisible(boolean newValue) { titleVisible = newValue; // The only way to get the chart not to draw the title // is to set it to null. The title text and title // visibility are stored here in SimplePlot so we // can do this. It could be made more elegant by adding an // AbstractTitle.INVISIBLE title position. if (titleVisible) { getChart().setTitle(title); } else { getChart().setTitle((String) null); } } /** * Toggles display of pointer position. * * @param toggle * true to display, false to hide */ public void setTrackPointer(boolean toggle) { if (pointerTracking != toggle) { if (toggle) { mpt.switchOn(); } else { mpt.switchOff(); } pointerTracking = toggle; } } /** * Sets the x axis autoscaling. * * @param newValue * the new value (true or false) */ public void setXAxisAutoScaling(boolean newValue) { linearXAxis.setAutoRange(newValue); logarithmicXAxis.setAutoRange(newValue); } /** * Sets whether the x axis autoscaling always includes zero. * * @param newValue * the new value (true or false) */ public void setXAxisAutoScalingIncludesZero(boolean newValue) { linearXAxis.setAutoRangeIncludesZero(newValue); logarithmicXAxis.setAutoRangeIncludesZero(newValue); } /** * Sets the x axis label. * * @param label * the label */ @Override public void setXAxisLabel(String label) { linearXAxis.setLabel(label); logarithmicXAxis.setLabel(label); } /** * Sets the x axis limits. * * @param xmin * the minimum x value * @param xmax * the maximum x value */ public void setXAxisLimits(double xmin, double xmax) { linearXAxis.setRange(new Range(xmin, xmax)); logarithmicXAxis.setRange(new Range(xmin, xmax)); } /** * Sets the y axis autoscaling. * * @param newValue * the new value (true or false) */ public void setYAxisAutoScaling(boolean newValue) { linearYAxis.setAutoRange(newValue); logarithmicYAxis.setAutoRange(newValue); } /** * Sets whehter the y axis autoscaling always includes zero. * * @param newValue * the new value (true or false) */ public void setYAxisAutoScalingIncludesZero(boolean newValue) { linearYAxis.setAutoRangeIncludesZero(newValue); logarithmicYAxis.setAutoRangeIncludesZero(newValue); } /** * Sets the y axis label. * * @param label * the label */ @Override public void setYAxisLabel(String label) { linearYAxis.setLabel(label); logarithmicYAxis.setLabel(label); } /** * Sets the y axis limits. * * @param ymin * the minimum y value * @param ymax * the maximum y value */ public void setYAxisLimits(double ymin, double ymax) { linearYAxis.setRange(new Range(ymin, ymax)); logarithmicYAxis.setRange(new Range(ymin, ymax)); } /** * Sets the second y axis autoscaling. * * @param newValue * the new value (true or false) */ public void setYAxisTwoAutoScaling(boolean newValue) { linearYAxisTwo.setAutoRange(newValue); logarithmicYAxisTwo.setAutoRange(newValue); } /** * Sets whehter the second y axis autoscaling always includes zero. * * @param newValue * the new value (true or false) */ public void setYAxisTwoAutoScalingIncludesZero(boolean newValue) { linearYAxisTwo.setAutoRangeIncludesZero(newValue); logarithmicYAxisTwo.setAutoRangeIncludesZero(newValue); } /** * Sets the second y axis label. * * @param label * the label */ public void setYAxisTwoLabel(String label) { linearYAxisTwo.setLabel(label); logarithmicYAxisTwo.setLabel(label); } /** * Sets the second y axis limits. * * @param ymin * the minimum y value * @param ymax * the maximum y value */ public void setYAxisTwoLimits(double ymin, double ymax) { linearYAxisTwo.setRange(new Range(ymin, ymax)); logarithmicYAxisTwo.setRange(new Range(ymin, ymax)); } /** * Gets the current CoordinateFormatter. * * @return current value of coordinateFormatter */ public CoordinateFormatter getCoordinateFormatter() { return coordinateFormatter; } /** * Sets the current CoordinateFormatter. * * @param coordinateFormatter * the CoordinateFormatter to set as current. */ public void setCoordinateFormatter(CoordinateFormatter coordinateFormatter) { this.coordinateFormatter = coordinateFormatter; } /** * Returns the current axis limits. * * @return AxisLimits */ private AxisLimits getAxisLimits() { return new AxisLimits(isXAxisAutoRange(), isYAxisAutoRange(), getXAxisMin(), getXAxisMax(), getYAxisMin(), getYAxisMax(), (linearYAxisTwo != null ? linearYAxisTwo.isAutoRange() : false), (linearYAxisTwo != null ? linearYAxisTwo.getLowerBound() : 0.0), (linearYAxisTwo != null ? linearYAxisTwo.getUpperBound() : 0.0)); } /** * Overrides the super class method in order to deal with remembering the previous axis limits (which are pushed * onto a stack). * * @param selection * is the Rectangle2D selected for zooming but is only actually used in the super class */ @Override public void zoom(Rectangle2D selection) { // Push current AxisLimits onto stack zoomStack.push(getAxisLimits()); // This flag means that a zoom is currently being calculated, Compare // the // zooming flag which means that zooming is actually allowed. currentlyDoingAZoom = true; // Get the super class to actually zoom super.zoom(selection); currentlyDoingAZoom = false; // Enable unZoomButton unZoomButton.setEnabled(!zoomStack.isEmpty()); } /** * Sets the axis limits (both axes). * * @param newLimits * an AxisLimits object containing the information */ public void setAxisLimits(AxisLimits newLimits) { if (newLimits.isXAuto()) { linearXAxis.setAutoRange(true); logarithmicXAxis.setAutoRange(true); } else { linearXAxis.setLowerBound(newLimits.getXMin()); linearXAxis.setUpperBound(newLimits.getXMax()); logarithmicXAxis.setLowerBound(newLimits.getXMin()); logarithmicXAxis.setUpperBound(newLimits.getXMax()); } if (newLimits.isYAuto()) { linearYAxis.setAutoRange(true); logarithmicYAxis.setAutoRange(true); } else { linearYAxis.setLowerBound(newLimits.getYMin()); linearYAxis.setUpperBound(newLimits.getYMax()); logarithmicYAxis.setLowerBound(newLimits.getYMin()); logarithmicYAxis.setUpperBound(newLimits.getYMax()); } // If linearYAxisTwo exists then logarithmicYAxisTwo will too. if (linearYAxisTwo != null) { if (newLimits.isYTwoAuto()) { linearYAxisTwo.setAutoRange(true); logarithmicYAxisTwo.setAutoRange(true); } else { linearYAxisTwo.setLowerBound(newLimits.getYTwoMin()); linearYAxisTwo.setUpperBound(newLimits.getYTwoMax()); logarithmicYAxisTwo.setLowerBound(newLimits.getYTwoMin()); logarithmicYAxisTwo.setUpperBound(newLimits.getYTwoMax()); } } } /** * Determines if X (Domain) axis has autoRange set. * * @return autoRange true or false */ public boolean isXAxisAutoRange() { return xYPlot.getDomainAxis().isAutoRange(); } /** * Determines if Y (Range) axis has autoRange set. * * @return autoRange true or false */ public boolean isYAxisAutoRange() { return xYPlot.getRangeAxis().isAutoRange(); } /** * Function that is called when the new save as option on the right click menu is clicked. */ private void saveAs() { try { if (fc == null) fc = new JFileChooser(); fc.setFileFilter(new FileFilter() { @Override public boolean accept(File f) { if (f.isDirectory()) { return true; } String path = f.getPath(); if (path.endsWith(".png")) { return true; } return false; } @Override public String getDescription() { return "PNG files"; } }); int returnValue = fc.showSaveDialog(this); logger.debug("check the save dialogue"); if (returnValue == JFileChooser.APPROVE_OPTION) { BufferedImage im = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); super.paintComponent(im.createGraphics()); ImageIO.write(im, "png", fc.getSelectedFile()); } } catch (Exception e) { logger.error("Error in Save As", e); } } /** * Overrides super class method in order to paint image for magnfication. * * @param g * the Graphics into which this should paint itself */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); coordinateFormatter.setXDigits(linearXAxis.getDigits()); coordinateFormatter.setYDigits(linearYAxis.getDigits()); } private void initLogAxis(LogarithmicAxis axis, boolean autoRange, NumberFormat formatter) { initAxis(axis, autoRange, formatter); axis.setAllowNegativesFlag(true); } private void initAxis(NumberAxis axis, boolean autoRange, NumberFormat formatter) { axis.setAutoRange(autoRange); axis.setLowerMargin(0.); axis.setUpperMargin(0.); axis.setAutoRangeIncludesZero(false); axis.setNumberFormatOverride(formatter); } /** * @return Returns the zooming. */ private boolean isZooming() { return zooming; } /** * Sets the value of zooming. * * @param zooming * The new value. */ @Override public void setZooming(boolean zooming) { this.zooming = zooming; zoomButton.setSelected(zooming); // This actually enables or disables zooming in the super class setMouseZoomable(zooming, false); // Stop the other buttons being used while zooming is true magnifyDataButton.setEnabled(!zooming); magnifier = null; } /** * @return Returns the magnifyingData. */ private boolean isMagnifyingData() { return magnifyingData; } /** * Sets the magnfiyingData flag - this magnfying mode uses a window containing another plot which has its axis * limits set to display the magnfied data. * * @param magnifyingData * The new value. */ private void setMagnifyingData(boolean magnifyingData) { this.magnifyingData = magnifyingData; zoomButton.setEnabled(!magnifyingData); dataMagnifierWindow.setVisible(magnifyingData); if (magnifyingData) { dragColour = Color.red; magnifier = dataMagnifierWindow; magnifier.setSize(getSize()); magnifier.update(magnifyRectangle); } else magnifier = null; } /** * @return Returns the magnifyRectangle. */ public Rectangle2D getMagnifyRectangle() { return magnifyRectangle; } /** * @return Returns the title. */ public String getTitle() { return title; } /** * @return Returns the image. */ // public BufferedImage getImage() { // return image; // } /** * Gets the x value at which a line has its maximum y value. * * @param line * the line * @return the x value corresponding to the maximum y value of this line */ public double getLineXValueOfPeak(int line) { return leftSeriesCollection.getSeriesXValueOfPeak(line); } /** * Gets the x value at which a line has its maximum y value. * * @param text * the name of the line * @return the x value corresponding to the maximum y value of this line */ public double getLineXValueOfPeak(String text) { return leftSeriesCollection.getSeriesXValueOfPeak(text); } /** * Temporary main for speed testing purposes only * * @param args */ public static void main(String args[]) { JFrame jf = new JFrame(); final SimplePlot sp = new SimplePlot(); jf.getContentPane().add(sp); jf.pack(); jf.setVisible(true); sp.setXAxisAutoScaling(false); sp.setXAxisLimits(0.0, 300.0); sp.setYAxisLimits(0.0, 1.0); sp.initializeLine(0); final double x[] = new double[1000]; final double y[] = new double[1000]; final double[] numbers = new double[10]; final double[] timesOne = new double[10]; final double[] timesTwo = new double[10]; for (int i = 0; i < 1000; i++) x[i] = i; try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { long timeOne; long timeTwo; for (int j = 0; j < 10; j++) { sp.initializeLine(j); timeOne = System.currentTimeMillis(); for (int k = 0; k < 300; k++) { y[k] = Math.random(); sp.addPointToLine(j, x[k], y[k]); } timeTwo = System.currentTimeMillis(); numbers[j] = j; timesOne[j] = timeTwo - timeOne; System.out.println("LINE " + j + " " + timesOne[j]); } } }); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { sp.deleteAllLines(); } }); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { long timeOne; long timeTwo; // SimpleXYSeries[] lines = new SimpleXYSeries[10]; // sp.setBatchingRepaints(true); for (int j = 0; j < 10; j++) { sp.initializeLine(j); // lines[j] = sp.getActualLine(j); timeOne = System.currentTimeMillis(); for (int k = 0; k < 300; k++) { y[k] = Math.random(); } // lines[j].setPoints(x, y); sp.setLinePoints(j, x, y); timeTwo = System.currentTimeMillis(); timesTwo[j] = timeTwo - timeOne; System.out.println("LINE " + j + " " + (timeTwo - timeOne)); } } }); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { sp.deleteAllLines(); } }); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { sp.initializeLine(0); sp.setLinePoints(0, numbers, timesOne); sp.initializeLine(1); sp.setLinePoints(1, numbers, timesTwo); } }); } catch (InterruptedException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } /** * This overrides the method in ChartPanel which seems to behave slightly differently. The replacement may be * unecessary -investigate. */ @Override public void createChartPrintJob() { PrinterJob printerJob = PrinterJob.getPrinterJob(); PageFormat pageFormat = printerJob.defaultPage(); pageFormat.setOrientation(PageFormat.LANDSCAPE); printerJob.setPrintable(this, pageFormat); try { if (printerJob.printDialog()) { printerJob.print(); } } catch (PrinterException pe) { logger.error("Caught PrinterException: " + pe.getMessage()); } } /** * Adds an item to the popup menu. * * @param jmi * the new menu item */ public void addPopupMenuItem(JComponent jmi) { getPopupMenu().add(jmi); } /** * Set a SimpleValueTransformer for x coordinates - the SimpleValueTransformer will allow x values to be transformed * on output only (not affecting the actual data)- this is useful, for example, in allowing a choice of units for x * axis displays. * * @param xValueTransformer * the SimpleValueTransformer to use */ public void setXValueTransformer(SimpleValueTransformer xValueTransformer) { // The easiest way to implement this was to push the responsibility // down. // Both the Renderer (which actually draws the data) and the various // SeriesCollections (which keep the axes correct) need to know about // the Transformer. ((SimpleXYItemRenderer) xYPlot.getRenderer()).setXValueTransformer(xValueTransformer); leftSeriesCollection.setXValueTransformer(xValueTransformer); if (rightSeriesCollection != null) { rightSeriesCollection.setXValueTransformer(xValueTransformer); ((SimpleXYItemRenderer) xYPlot.getRenderer(secondaryDataSetIndex)) .setXValueTransformer(xValueTransformer); } } /** * Returns the x axis number format. * * @return xAxisNumberFormat */ @Override public NumberFormat getXAxisNumberFormat() { return xAxisNumberFormat; } /** * Function to set up the X axis with scientific notation. */ @Override public void setScientificXAxis() { setScientificXAxis(new DecimalFormat("0.000E00")); } /** * Function to set up the X axis with scientific notation. With the specified number Format. * * @param inFormat * the new formating for the X axis */ public void setScientificXAxis(NumberFormat inFormat) { xAxisNumberFormat = inFormat; linearXAxis.setNumberFormatOverride(xAxisNumberFormat); logarithmicXAxis.setNumberFormatOverride(xAxisNumberFormat); } /** * Function to set up the Y axis with scientific notation. */ @Override public void setScientificYAxis() { setScientificYAxis(new DecimalFormat("0.0000E00")); } /** * Function to set up the Y axis with scientific notation. With the inputed numberformat * * @param inFormat * the new formating for the Y axis */ public void setScientificYAxis(NumberFormat inFormat) { yAxisNumberFormat = inFormat; linearYAxis.setNumberFormatOverride(yAxisNumberFormat); logarithmicYAxis.setNumberFormatOverride(yAxisNumberFormat); } /** * Returns the y axis number format. * * @return yAxisNumberFormat */ @Override public NumberFormat getYAxisNumberFormat() { return yAxisNumberFormat; } /** * Returns the second y axis number format. * * @return yAxisTwoNumberFormat */ public NumberFormat getYAxisTwoNumberFormat() { return yAxisTwoNumberFormat; } /** * specifies whether the X axis ticks are vertical or horizontal. * * @param value */ @Override public void setVerticalXAxisTicks(boolean value) { linearXAxis.setVerticalTickLabels(value); logarithmicXAxis.setVerticalTickLabels(value); } /** * @return true if X axis is logarithmic */ public boolean isXAxisLogarithmic() { return xAxisLogarithmic; } /** * @return true if first (left) Y axis is logarithmic */ public boolean isYAxisLogarithmic() { return yAxisLogarithmic; } /** * Sets whether or not x axis is logarithmic. * * @param xAxisLogarithmic * true makes the axis logarithmic */ public void setXAxisLogarithmic(boolean xAxisLogarithmic) { this.xAxisLogarithmic = xAxisLogarithmic; if (xAxisLogarithmic) { xYPlot.setDomainAxis(logarithmicXAxis); xLogLinButton.setText("Linear X axis"); } else { xYPlot.setDomainAxis(linearXAxis); xLogLinButton.setText("Logarithmic X axis"); } } /** * Sets whether or not first (left) y axis is logarithmic. * * @param yAxisLogarithmic * true makes the axis logarithmic */ public void setYAxisLogarithmic(boolean yAxisLogarithmic) { this.yAxisLogarithmic = yAxisLogarithmic; if (yAxisLogarithmic) { xYPlot.setRangeAxis(logarithmicYAxis); yLogLinButton.setText("Linear Y axis"); } else { xYPlot.setRangeAxis(linearYAxis); yLogLinButton.setText("Logarithmic Y axis"); } } /** * @return true if second (right) Y axis is logarithmic */ public boolean isYAxisTwoLogarithmic() { return yAxisTwoLogarithmic; } /** * Sets whether or not second (right) y axis is logarithmic. * * @param yAxisTwoLogarithmic * true makes the axis logarithmic */ public void setYAxisTwoLogarithmic(boolean yAxisTwoLogarithmic) { this.yAxisTwoLogarithmic = yAxisTwoLogarithmic; if (yAxisTwoLogarithmic) { xYPlot.setRangeAxis(secondaryAxisNumber, logarithmicYAxisTwo); y2LogLinButton.setText("Linear Y2 axis"); } else { xYPlot.setRangeAxis(secondaryAxisNumber, linearYAxisTwo); y2LogLinButton.setText("Logarithmic Y2 axis"); } } /** * Allows anything implementing the XYAnnotation interface to be added to the plot. * * <p>See the {@code org.jfree.chart.annotations} package for wrappers which implement * the interface and wrap various types of object * * @param annotation * the annotation */ public void addAnnotation(XYAnnotation annotation) { getChart().getXYPlot().addAnnotation(annotation); if (annotation instanceof ChartMouseListener) addChartMouseListener((ChartMouseListener) annotation); } /** * Sets the label for the dependent x axis. * * @param string * the label */ public void setDependentXAxisLabel(String string) { dependentXAxis.setLabel(string); } /** * Sets the label for the dependent y axis. * * @param string * the label */ public void setDependentYAxisLabel(String string) { dependentYAxis.setLabel(string); } /** * Specifies weather the graph is in batching mode or not * * @return true if the graph is batching. */ public boolean isBatching() { return batching; } /** * Sets whether the graph is batching or not. When the graph is batching, any new data sent to the graph is not * displayed, therefore speeding up the process. Once batching is set to false, all the data put into the graph is * displayed in one redraw. * * @param batching * sets the batching flag to true or false */ public void setBatching(boolean batching) { this.batching = batching; if (leftSeriesCollection != null) { leftSeriesCollection.setBatching(batching); } if (rightSeriesCollection != null) { rightSeriesCollection.setBatching(batching); } if (!batching && history != null) { if (seriesStore.size() == 1) historyRepopulate(); } } Range lastRangeBoundsLeft = null; Range lastRangeBoundsRight = null; Range leftRangeBounds = null; Range rightRangeBounds = null; /** * @param collection * @param includeInterval * @return range for the range axis Y */ public Range getRangeBounds(SimpleXYSeriesCollection collection, @SuppressWarnings("unused") boolean includeInterval) { if (!isTurboMode()) return null; Range domainBounds = null; Range rangeBounds = null; if (collection == leftSeriesCollection) { domainBounds = leftDomainBounds; rangeBounds = leftRangeBounds; } else if (collection == rightSeriesCollection) { domainBounds = rightDomainBounds; rangeBounds = rightRangeBounds; } if (rangeBounds != null) return rangeBounds; Double min = Double.POSITIVE_INFINITY; Double max = Double.NEGATIVE_INFINITY; if (isStripMode()) { synchronized (linesChanged) { if (linesChanged.size() == 0) return null; Iterator<Integer> iter = linesChanged.iterator(); while (iter.hasNext()) { SimpleXYSeries sxys = collection.find(iter.next()); // is it in this collection? if (sxys != null && sxys.isVisible()) { Double sxys_min, sxys_max; Double sxys_Xmax = sxys.getMaxX(); if (Double.isInfinite(sxys_Xmax)) return null; /* * create range in data units over which we want to get the y min and max */ SimpleValueTransformer valTrans = collection.getXValueTransformer(); Double maxXTransformed = valTrans.transformValue(sxys_Xmax); double minX = valTrans.transformValueBack(maxXTransformed - stripWidth); Double[] extents = sxys.getBounds(new Range(minX, sxys_Xmax)); sxys_min = extents[2]; sxys_max = extents[3]; if (Double.isInfinite(sxys_min) || Double.isInfinite(sxys_max)) return null; min = Math.min(min, sxys_min); max = Math.max(max, sxys_max); } } } } else { for (Object obj : collection.getSeries()) { if (obj != null && obj instanceof SimpleXYSeries) { SimpleXYSeries sxys = (SimpleXYSeries) obj; if (sxys.isVisible()) { if (domainBounds == null) { double sxys_min, sxys_max; sxys_min = sxys.getMinY(); sxys_max = sxys.getMaxY(); min = Math.min(min, sxys_min); max = Math.max(max, sxys_max); } else { double sxys_min, sxys_max; Double[] extents = sxys.getBounds(domainBounds); sxys_min = extents[2]; sxys_max = extents[3]; if (Double.isInfinite(sxys_min) || Double.isInfinite(sxys_max)) return null; min = Math.min(min, sxys_min); max = Math.max(max, sxys_max); } } } } } if (Double.isInfinite(min) || Double.isInfinite(max)) return null; Range newRange = new Range(min, max); if (collection == leftSeriesCollection) lastRangeBoundsLeft = newRange; if (collection == rightSeriesCollection) lastRangeBoundsRight = newRange; return newRange; } /* * The following are in transformed X values */ private Range lastDomainBoundsLeft = null; @SuppressWarnings("unused") private Range lastDomainBoundsRight = null; private Range leftDomainBounds = null; private Range rightDomainBounds = null; private Double stripWidth = null; //width in domain axis from last point value to min val /** * @param collection * @param includeInterval * @return range for the domain axis X */ public Range getDomainBounds(SimpleXYSeriesCollection collection, @SuppressWarnings("unused") boolean includeInterval) { Double min = Double.POSITIVE_INFINITY; Double max = Double.NEGATIVE_INFINITY; if (!isTurboMode()) return null; if (collection == leftSeriesCollection && leftDomainBounds != null) return leftDomainBounds; if (collection == rightSeriesCollection && rightDomainBounds != null) return rightDomainBounds; if (isStripMode()) { /* * In stripMode get max of changed lines - min will be the max minus the stripWidth */ synchronized (linesChanged) { if (linesChanged.size() == 0) return null; Iterator<Integer> iter = linesChanged.iterator(); while (iter.hasNext()) { SimpleXYSeries sxys = collection.find(iter.next()); // is it in this collection? if (sxys != null && sxys.isVisible()) { double sxys_max; sxys_max = sxys.getMaxX(); max = Math.max(max, sxys_max); } } min = max; //we remove the stripWidth later as stripWidth is in final units after transformation } } else { /* * go through all visible lines are get min and max */ for (Object obj : collection.getSeries()) { if (obj != null && obj instanceof SimpleXYSeries) { SimpleXYSeries sxys = (SimpleXYSeries) obj; if (sxys.isVisible()) { double sxys_min, sxys_max; sxys_min = sxys.getMinX(); sxys_max = sxys.getMaxX(); min = Math.min(min, sxys_min); max = Math.max(max, sxys_max); } } } } if (Double.isInfinite(min) || Double.isInfinite(max)) return null; SimpleValueTransformer valTrans = collection.getXValueTransformer(); Double maxTransformed = valTrans.transformValue(max); Double minTransformed = stripWidth != null ? maxTransformed - stripWidth : valTrans.transformValue(min); Range newRange = new Range(minTransformed, maxTransformed); if (collection == leftSeriesCollection) lastDomainBoundsLeft = newRange; if (collection == rightSeriesCollection) lastDomainBoundsRight = newRange; return newRange; } /* * Depending on the mode of the plot we will either fire a DataSet change event or a chart.fireChange event which * simply causes a refresh. */ @Override public void onUpdate(boolean force) { if (force) { //need to force a reassessment of limits so act as if data has changed repaint(); SimpleXYSeries sxys = findLine(lastLineAdded); if (sxys != null) { sxys.fireSeriesChanged(); } return; } synchronized (linesChanged) { if (linesChanged.size() == 0) return; Iterator<Integer> iter = linesChanged.iterator(); while (iter.hasNext()) { SimpleXYSeries sxys = findLine(iter.next()); if (sxys != null) { sxys.fireSeriesChanged(); } } } } /** * gives opportunity to plot to archive date not visible to reduce memory usage * @param all * * @throws IOException */ @Override public void archive(boolean all, String archiveFolder) throws IOException { if (isTurboMode()) { Iterator<SimpleXYSeries> iter = seriesStore.values().iterator(); while (iter.hasNext()) { SimpleXYSeries sxys = iter.next(); if (all || !sxys.isVisible()) sxys.archive(); } } } /** */ @Override public void unArchive() { if (isTurboMode()) { for (Object obj : leftSeriesCollection.getSeries()) { ((SimpleXYSeries) obj).unArchive(); } if (rightSeriesCollection != null) { for (Object obj : rightSeriesCollection.getSeries()) { ((SimpleXYSeries) obj).unArchive(); } } } } /** * @param leftRangeBounds */ @Override public void setLeftRangeBounds(Range leftRangeBounds) { this.leftRangeBounds = leftRangeBounds; } /** * @param rightRangeBounds */ @Override public void setRightRangeBounds(Range rightRangeBounds) { this.rightRangeBounds = rightRangeBounds; } /** * @param domainBounds The same value is used for both left and rigth series */ @Override public void setDomainBounds(Range domainBounds) { this.leftDomainBounds = domainBounds; this.rightDomainBounds = domainBounds; xLimitsButton.setSelected(domainBounds != null); if (domainBounds != null) { if (!zoomStack.isEmpty()) { unZoom(); } setStripWidth(null); } onUpdate(true); } /** * */ @Override public void dispose() { } @Override protected void finalize() throws Throwable { logger.info("SimplePlot.finalize"); super.finalize(); } @Override public void copySettings(XYDataHandler other) { setScientificXAxis(other.getXAxisNumberFormat()); setScientificYAxis(other.getYAxisNumberFormat()); setDomainBounds(other.getLeftDomainBounds()); setStripWidth(other.getStripWidth()); } @Override public Range getLeftDomainBounds() { return leftDomainBounds; } @Override public Color getLineColor(int which) { return (Color) getLine(which).getPaint(); } @Override public Marker getLineMarker(int which) { return getLine(which).getMarker(); } @Override public Double getStripWidth() { return stripWidth; } @Override public void setsPointsForLine(int which, DoubleDataset xData, DoubleDataset yData) { //no used in Swing } }