Java tutorial
/* * #%L * SLIM Curve plugin for combined spectral-lifetime image analysis. * %% * Copyright (C) 2010 - 2015 Board of Regents of the University of * Wisconsin-Madison. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ package loci.slim.ui; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics2D; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.util.prefs.Preferences; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SwingUtilities; import loci.curvefitter.ICurveFitData; import loci.slim.ICursorListener; import loci.slim.fitting.cursor.FittingCursor; import loci.slim.fitting.cursor.IFittingCursorListener; import org.jdesktop.jxlayer.JXLayer; import org.jdesktop.jxlayer.plaf.AbstractLayerUI; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.LogarithmicAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.plot.CombinedDomainXYPlot; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.XYSplineRenderer; import org.jfree.chart.ui.RectangleEdge; import org.jfree.data.xy.XYSeries; import org.jfree.data.xy.XYSeriesCollection; /** * This is the chart that shows the transient decay data, the fitted model, and * the residuals. It also has a user interface to set the start and stop of the * fit. * * @author Aivar Grislis */ public class DecayGraph implements IDecayGraph, IStartStopProportionListener { // Unicode special characters private static final Character CHI = '\u03c7', SQUARE = '\u00b2', TAU = '\u03c4', SUB_1 = '\u2081', SUB_2 = '\u2082', SUB_3 = '\u2083', SUB_R = '\u1d63'; static final String WIDTH_KEY = "width"; static final String HEIGHT_KEY = "height"; static final Dimension SIZE = new Dimension(500, 270); static final Dimension FRAME_SIZE = new Dimension(450, 450); static final Dimension MAX_SIZE = new Dimension(961, 724); // see getDataArea( // ) below static final Dimension MIN_SIZE = new Dimension(313, 266); static final String PHOTON_AXIS_LABEL = "Photons"; static final String TIME_AXIS_LABEL = "Time"; static final String UNITS_LABEL = "nanoseconds"; static final String RESIDUAL_AXIS_LABEL = "Residual"; static final String CHI_SQUARE = "" + CHI + SQUARE + SUB_R; static final String PHOTON_COUNT = "Photons"; static final String LOGARITHMIC = "Logarithmic"; static final int DECAY_WEIGHT = 4; static final int RESIDUAL_WEIGHT = 1; static final int HORZ_TWEAK = 1; static final Color PROMPT_COLOR = Color.GRAY.brighter(); static final Color DECAY_COLOR = Color.GRAY.darker(); static final Color FITTED_COLOR = Color.RED; static final Color BACK_COLOR = Color.WHITE; static final Color TRANS_START_COLOR = Color.BLUE.darker(); static final Color DATA_START_COLOR = Color.GREEN.darker(); static final Color TRANS_STOP_COLOR = Color.RED.darker(); static final Color BASE_COLOR = Color.GREEN.darker(); static final Color RESIDUAL_COLOR = Color.GRAY; private static final Object _synchObject = new Object(); private static DecayGraph _instance; private JFrame _frame; FittingCursor _fittingCursor; IFittingCursorListener _fittingCursorListener; private StartStopDraggingUI<JComponent> _startStopDraggingUI; private double _timeInc; private int _bins; private double _maxValue; private Double _transStart; private Double _dataStart; private Double _transStop; private boolean _logarithmic = true; XYPlot _decaySubPlot; XYSeriesCollection _decayDataset; XYSeriesCollection _residualDataset; JTextField _tau1TextField; JTextField _tau2TextField; JTextField _tau3TextField; JTextField _chiSqTextField; JTextField _photonTextField; JCheckBox _logCheckBox; ICursorListener _cursorListener; /** * Public constructor, may be used to create non-singletons. */ public DecayGraph() { _frame = null; } /** * Gets the singleton instance. * * @return singleton instance */ public static synchronized DecayGraph getInstance() { if (null == _instance) { _instance = new DecayGraph(); } return _instance; } /** * Initialize the graph and returns the containing JFrame. * * @return frame */ @Override public JFrame init(final JFrame frame, final int bins, final double timeInc, final ICursorListener cursorListener) { final boolean create = false; if (null == _frame || !_frame.isVisible() || _bins != bins || _timeInc != timeInc) { // save incoming parameters _bins = bins; _timeInc = timeInc; _maxValue = timeInc * bins; _cursorListener = cursorListener; if (null != _frame) { // delete existing frame _frame.setVisible(false); _frame.dispose(); } // create the combined chart final JFreeChart chart = createCombinedChart(bins, timeInc); final ChartPanel panel = new ChartPanel(chart, true, true, true, false, true); panel.setDomainZoomable(false); panel.setRangeZoomable(false); panel.setPreferredSize(SIZE); // add start/stop vertical bar handling _dataStart = _transStop = null; final JXLayer<JComponent> layer = new JXLayer<JComponent>(panel); _startStopDraggingUI = new StartStopDraggingUI<JComponent>(panel, _decaySubPlot, this, _maxValue); layer.setUI(_startStopDraggingUI); // create a frame for the chart _frame = new JFrame(); final Container container = _frame.getContentPane(); container.setLayout(new BorderLayout()); container.add(layer, BorderLayout.CENTER); final JPanel miscPane = new JPanel(); miscPane.setLayout(new FlowLayout()); final JLabel label1 = new JLabel(CHI_SQUARE); miscPane.add(label1); _chiSqTextField = new JTextField(7); _chiSqTextField.setEditable(false); miscPane.add(_chiSqTextField); final JLabel label2 = new JLabel(PHOTON_COUNT); miscPane.add(label2); _photonTextField = new JTextField(7); _photonTextField.setEditable(false); miscPane.add(_photonTextField); _logCheckBox = new JCheckBox(LOGARITHMIC); _logCheckBox.setSelected(_logarithmic); _logCheckBox.addItemListener(new ItemListener() { @Override public void itemStateChanged(final ItemEvent e) { _logarithmic = _logCheckBox.isSelected(); NumberAxis photonAxis; if (_logarithmic) { photonAxis = new LogarithmicAxis(PHOTON_AXIS_LABEL); } else { photonAxis = new NumberAxis(PHOTON_AXIS_LABEL); } _decaySubPlot.setRangeAxis(photonAxis); } }); miscPane.add(_logCheckBox); container.add(miscPane, BorderLayout.SOUTH); // IJ.log("size from prefs " + getSizeFromPreferences()); // _frame.setSize(getSizeFromPreferences()); // _frame.setMaximumSize(MAX_SIZE); // doesn't work; bug in Java _frame.pack(); _frame.setLocationRelativeTo(frame); _frame.setVisible(true); _frame.addComponentListener(new ComponentListener() { @Override public void componentHidden(final ComponentEvent e) { } @Override public void componentMoved(final ComponentEvent e) { } @Override public void componentResized(final ComponentEvent e) { // constrain maximum size boolean resize = false; final Dimension size = _frame.getSize(); // IJ.log("COMPONENT RESIZED incoming size " + size); if (size.width > MAX_SIZE.width) { size.width = MAX_SIZE.width; resize = true; } if (size.height > MAX_SIZE.height) { size.height = MAX_SIZE.height; resize = true; } if (size.width < MIN_SIZE.width) { size.width = MIN_SIZE.width; resize = true; } if (size.height < MIN_SIZE.height) { size.height = MIN_SIZE.height; resize = true; } if (resize) { _frame.setSize(size); } // IJ.log("save resized " + resize + " size " + size); saveSizeInPreferences(size); } @Override public void componentShown(final ComponentEvent e) { } }); _frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(final WindowEvent e) { _cursorListener.hideCursor(); } }); _frame.setSize(getSizeFromPreferences()); } return _frame; } /** * Sets the fitting cursor, which keeps track of prompt and transient start * and stop cursors. * */ @Override public void setFittingCursor(final FittingCursor fittingCursor) { if (null == _fittingCursor) { _fittingCursorListener = new FittingCursorListener(); } else if (_fittingCursor != fittingCursor) { _fittingCursor.removeListener(_fittingCursorListener); } _fittingCursor = fittingCursor; _fittingCursor.addListener(_fittingCursorListener); } /** * Set or change the title. * */ @Override public void setTitle(final String title) { _frame.setTitle(title); } /** * Changes (or initializes) all of the charted data. * */ @Override public void setData(final int promptIndex, final double[] prompt, final ICurveFitData data) { createDatasets(_bins, _timeInc, promptIndex, prompt, data); } /** * Sets the reduced chi square. * */ @Override public void setChiSquare(final double chiSquare) { final String text = "" + roundToDecimalPlaces(chiSquare, 6); _chiSqTextField.setText(text); } /** * Sets the displayed photon count. * */ @Override public void setPhotons(final int photons) { _photonTextField.setText("" + photons); } /* * Sets whether vertical axis should be logarithmic. * */ public void setLogarithmic(final boolean logarithmic) { _logarithmic = logarithmic; } /** * Changes or initializes the start and stop vertical bars. * */ @Override public void setStartStop(final double transStart, final double dataStart, final double transStop) { _startStopDraggingUI.setStartStopValues(transStart, dataStart, transStop); } /** * Sets stop and start time bins, based on proportions 0.0..1.0. This is * called from the UI layer that lets user drag the start and stop vertical * bars. Validates and passes changes on to external listener. * */ @Override public void setStartStopProportion(final double transStartProportion, final double dataStartProportion, final double transStopProportion) { // calculate new start and stop final double transStart = transStartProportion * _maxValue; final double dataStart = dataStartProportion * _maxValue; final double transStop = transStopProportion * _maxValue; // if changed, notify cursor listeners if (null == _transStart || transStart != _transStart || null == _dataStart || dataStart != _dataStart || null == _transStop || transStop != _transStop) { _transStart = transStart; _dataStart = dataStart; _transStop = transStop; if (null != _fittingCursor) { _fittingCursor.setTransientStartValue(transStart); _fittingCursor.setDataStartValue(dataStart); _fittingCursor.setTransientStopValue(transStop); } } } /** * Creates the chart * * @param bins number of bins * @param timeInc time increment per bin * @return the chart */ JFreeChart createCombinedChart(final int bins, final double timeInc) { // create empty chart data sets _decayDataset = new XYSeriesCollection(); _residualDataset = new XYSeriesCollection(); // make a common horizontal axis for both sub-plots final NumberAxis timeAxis = new NumberAxis(TIME_AXIS_LABEL); timeAxis.setLabel(UNITS_LABEL); timeAxis.setRange(0.0, (bins - 1) * timeInc); // make a vertically combined plot final CombinedDomainXYPlot parent = new CombinedDomainXYPlot(timeAxis); // create decay sub-plot NumberAxis photonAxis; if (_logarithmic) { photonAxis = new LogarithmicAxis(PHOTON_AXIS_LABEL); } else { photonAxis = new NumberAxis(PHOTON_AXIS_LABEL); } final XYSplineRenderer decayRenderer = new XYSplineRenderer(); decayRenderer.setSeriesShapesVisible(0, false); decayRenderer.setSeriesShapesVisible(1, false); decayRenderer.setSeriesLinesVisible(2, false); decayRenderer.setSeriesShape(2, new Ellipse2D.Float(-1.0f, -1.0f, 2.0f, 2.0f)); decayRenderer.setSeriesPaint(0, PROMPT_COLOR); decayRenderer.setSeriesPaint(1, FITTED_COLOR); decayRenderer.setSeriesPaint(2, DECAY_COLOR); _decaySubPlot = new XYPlot(_decayDataset, null, photonAxis, decayRenderer); _decaySubPlot.setDomainCrosshairVisible(true); _decaySubPlot.setRangeCrosshairVisible(true); // add decay sub-plot to parent parent.add(_decaySubPlot, DECAY_WEIGHT); // create residual sub-plot final NumberAxis residualAxis = new NumberAxis(RESIDUAL_AXIS_LABEL); final XYSplineRenderer residualRenderer = new XYSplineRenderer(); residualRenderer.setSeriesPaint(0, RESIDUAL_COLOR); residualRenderer.setSeriesLinesVisible(0, false); residualRenderer.setSeriesShape(0, new Ellipse2D.Float(-1.0f, -1.0f, 2.0f, 2.0f)); final XYPlot residualSubPlot = new XYPlot(_residualDataset, null, residualAxis, residualRenderer); residualSubPlot.setDomainCrosshairVisible(true); residualSubPlot.setRangeCrosshairVisible(true); residualSubPlot.setFixedLegendItems(null); // add residual sub-plot to parent parent.add(residualSubPlot, RESIDUAL_WEIGHT); // now make the top level JFreeChart final JFreeChart chart = new JFreeChart(null, JFreeChart.DEFAULT_TITLE_FONT, parent, true); chart.removeLegend(); return chart; } /** * Creates the data sets for the chart * * @param bins number of time bins * @param timeInc time increment per time bin * @param promptIndex starting bin of prompt * @param prompt prompt curve * @param data from the fit */ private void createDatasets(final int bins, final double timeInc, final int promptIndex, final double[] prompt, final ICurveFitData data) { final XYSeries series1 = new XYSeries("Prompt"); final XYSeries series2 = new XYSeries("Fitted"); final XYSeries series3 = new XYSeries("Data"); final XYSeries series4 = new XYSeries("Residuals"); double xCurrent; // show transient data; find the maximum transient data in this pass double yDataMax = -Double.MAX_VALUE; xCurrent = 0.0; for (int i = 0; i < bins; ++i) { // show transient data final double yData = data.getTransient()[i]; // keep track of maximum if (yData > yDataMax) { yDataMax = yData; } // logarithmic plots can't handle <= 0.0 series3.add(xCurrent, (yData > 0.0 ? yData : null)); xCurrent += timeInc; } // show prompt if any if (null != prompt) {// debugging double yPromptMax = -Double.MAX_VALUE; for (int i = 0; i < prompt.length; ++i) { // debugging // IJ.log(" " + prompt[i]); // find max if (prompt[i] > yPromptMax) { yPromptMax = prompt[i]; } } // add prompt data xCurrent = 0.0; for (int i = 0; i < bins; ++i) { Double value = null; if (null != prompt) { if (i >= promptIndex) { if (i - promptIndex < prompt.length) { // logarithmic plots can't handle <= 0 if (prompt[i - promptIndex] > 0.0) { value = prompt[i - promptIndex]; value = yDataMax * value / yPromptMax; } } } } series1.add(xCurrent, value); xCurrent += timeInc; } } final int transStart = data.getTransStartIndex(); final int dataStart = data.getDataStartIndex(); final int transEnd = data.getTransEndIndex(); // show fitted & residuals xCurrent = 0.0; for (int i = 0; i < bins; ++i) { // only within cursors if (transStart <= i && i <= transEnd) { // from transStart..transEnd show fitted final double yFitted = data.getYFitted()[i - transStart]; // don't allow fitted to grow the chart downward or upward if (1.0 <= yFitted && yFitted <= yDataMax) { series2.add(xCurrent, yFitted); } else { series2.add(xCurrent, null); } // from dataStart..transEnd show residuals if (dataStart <= i && 0.0 < yFitted) { final double yData = data.getTransient()[i]; series4.add(xCurrent, yData - yFitted); } else { series4.add(xCurrent, null); } } else { series2.add(xCurrent, null); series4.add(xCurrent, null); } xCurrent += timeInc; } synchronized (_synchObject) { _decayDataset.removeAllSeries(); _decayDataset.addSeries(series1); _decayDataset.addSeries(series2); _decayDataset.addSeries(series3); _residualDataset.removeAllSeries(); _residualDataset.addSeries(series4); } } /** * Restores size from Java Preferences. * * @return size */ private Dimension getSizeFromPreferences() { final Preferences prefs = Preferences.userNodeForPackage(this.getClass()); return new Dimension(prefs.getInt(WIDTH_KEY, FRAME_SIZE.width), prefs.getInt(HEIGHT_KEY, FRAME_SIZE.height)); } /** * Saves the size to Java Preferences. * */ private void saveSizeInPreferences(final Dimension size) { final Preferences prefs = Preferences.userNodeForPackage(this.getClass()); prefs.putInt(WIDTH_KEY, size.width); prefs.putInt(HEIGHT_KEY, size.height); } private double roundToDecimalPlaces(final double value, final int decimalPlaces) { final double decimalTerm = Math.pow(10.0, decimalPlaces); final int tmp = (int) Math.round(value * decimalTerm); final double rounded = tmp / decimalTerm; return rounded; } /** * Inner class, UI which allows us to paint on top of the components, using * JXLayer. * * @param <V> component */ static class StartStopDraggingUI<V extends JComponent> extends AbstractLayerUI<V> { private static final int CLOSE_ENOUGH = 4; // pizels private final ChartPanel _panel; private final XYPlot _plot; private final IStartStopProportionListener _listener; private final double _maxValue; private boolean _dragTransStartMarker = false; private boolean _dragDataStartMarker = false; private boolean _dragTransStopMarker = false; private volatile Double _transStartMarkerProportion; private volatile Double _dataStartMarkerProportion; private volatile Double _transStopMarkerProportion; private int _y0; private int _y1; private int _xTransStart; private int _xDataStart; private int _xTransStop; /** * Creates the UI. * * @param panel for the chart * @param plot within the chart * @param listener to be notified when user drags start/stop vertical bars * @param maxValue used to scale cursors */ StartStopDraggingUI(final ChartPanel panel, final XYPlot plot, final IStartStopProportionListener listener, final double maxValue) { _panel = panel; _plot = plot; _listener = listener; _maxValue = maxValue; } void setStartStopValues(final double transStartValue, final double dataStartValue, final double transStopValue) { _transStartMarkerProportion = transStartValue / _maxValue; _dataStartMarkerProportion = dataStartValue / _maxValue; _transStopMarkerProportion = transStopValue / _maxValue; } /** * Used to draw the start/stop vertical bars. Overrides 'paintLayer()', not * 'paint()'. * */ @Override protected void paintLayer(final Graphics2D g2D, final JXLayer<? extends V> layer) { // synchronized with chart data update synchronized (_synchObject) { // this paints chart layer as is super.paintLayer(g2D, layer); } if (null != _transStartMarkerProportion && null != _dataStartMarkerProportion && null != _transStopMarkerProportion) { // adjust to current size final Rectangle2D area = getDataArea(); final double x = area.getX(); _y0 = (int) area.getY(); _y1 = (int) (area.getY() + area.getHeight()); final double width = area.getWidth(); // IJ.log("x " + area.getX() + " y " + area.getY() + " width " + // area.getWidth() + " height " + area.getHeight()); _xTransStart = (int) Math.round(x + width * _transStartMarkerProportion) + HORZ_TWEAK; _xDataStart = (int) Math.round(x + width * _dataStartMarkerProportion) + HORZ_TWEAK; _xTransStop = (int) Math.round(x + width * _transStopMarkerProportion) + HORZ_TWEAK; // custom painting is here g2D.setStroke(new BasicStroke(2f)); g2D.setXORMode(XORvalue(TRANS_START_COLOR)); g2D.drawLine(_xTransStart, _y0, _xTransStart, _y1); g2D.setXORMode(XORvalue(DATA_START_COLOR)); g2D.drawLine(_xDataStart, _y0, _xDataStart, _y1); g2D.setXORMode(XORvalue(TRANS_STOP_COLOR)); g2D.drawLine(_xTransStop, _y0, _xTransStop, _y1); } } /** * Mouse listener, catches drag events * */ @Override protected void processMouseMotionEvent(final MouseEvent event, final JXLayer<? extends V> layer) { super.processMouseMotionEvent(event, layer); if (event.getID() == MouseEvent.MOUSE_DRAGGED) { if (_dragTransStartMarker || _dragDataStartMarker || _dragTransStopMarker) { final double newProportion = getDraggedProportion(event); if (_dragTransStartMarker) { if (newProportion <= _dataStartMarkerProportion) { _transStartMarkerProportion = newProportion; } else { _transStartMarkerProportion = _dataStartMarkerProportion; } } else if (_dragDataStartMarker) { if (newProportion >= _transStartMarkerProportion) { if (newProportion <= _transStopMarkerProportion) { _dataStartMarkerProportion = newProportion; } else { _dataStartMarkerProportion = _transStopMarkerProportion; } } else { _dataStartMarkerProportion = _transStartMarkerProportion; } } else { if (newProportion >= _dataStartMarkerProportion) { _transStopMarkerProportion = newProportion; } else { _transStopMarkerProportion = _dataStartMarkerProportion; } } // mark the ui as dirty and needing to be repainted setDirty(true); } } } private Color XORvalue(final Color color) { final int drawRGB = color.getRGB(); final int backRGB = BACK_COLOR.getRGB(); return new Color(drawRGB ^ backRGB); } /** * Gets the currently dragged horizontal value as a proportion, a value * between 0.0 and 1.0. * * @return proportion */ private double getDraggedProportion(final MouseEvent e) { final Rectangle2D dataArea = _panel.getChartRenderingInfo().getPlotInfo().getDataArea(); final Rectangle2D area = getDataArea(); double proportion = (e.getX() - area.getX()) / area.getWidth(); if (proportion < 0.0) { proportion = 0.0; } else if (proportion > 1.0) { proportion = 1.0; } return proportion; } /** * Mouse listener, catches mouse button events. * */ @Override protected void processMouseEvent(final MouseEvent e, final JXLayer<? extends V> l) { super.processMouseEvent(e, l); if (null != _transStartMarkerProportion && null != _transStopMarkerProportion) { if (e.getID() == MouseEvent.MOUSE_PRESSED) { final int x = e.getX(); final int y = e.getY(); if (y > _y0 - CLOSE_ENOUGH && y < _y1 + CLOSE_ENOUGH) { if (Math.abs(x - _xTransStart) < CLOSE_ENOUGH) { // check for superimposition if (_xTransStart == _xDataStart) { if (_xTransStart == _xTransStop) { // all three superimposed if (x < _xTransStart) { // start dragging trans start line _dragTransStartMarker = true; } else { // start dragging trans stop line _dragTransStopMarker = true; } } else { // trans and data start superimposed if (x < _xTransStart) { // start dragging trans start line _dragTransStartMarker = true; } else { // start dragging data start line _dragDataStartMarker = true; } } } else { // no superimposition; start dragging start line _dragTransStartMarker = true; } } else if (Math.abs(x - _xDataStart) < CLOSE_ENOUGH) { // check for superimposition if (_xDataStart == _xTransStop) { // data start and trans stop superimposed if (x < _xDataStart) { // start dragging data start line _dragDataStartMarker = true; } else { // start dragging trans stop line _dragTransStopMarker = true; } } else { // no superimposition; start dragging data start line _dragDataStartMarker = true; } } else if (Math.abs(x - _xTransStop) < CLOSE_ENOUGH) { // possible superimpositions already checked // start dragging trans stop line _dragTransStopMarker = true; } } } if (e.getID() == MouseEvent.MOUSE_RELEASED) { _dragTransStartMarker = _dragDataStartMarker = _dragTransStopMarker = false; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (null != _listener) { _listener.setStartStopProportion(_transStartMarkerProportion, _dataStartMarkerProportion, _transStopMarkerProportion); } } }); } } } /** * Gets the area of the chart panel. As you resize larger and larger the * maximum value returned for height is 724 and the maximum width 961. * * @return 2D rectangle area */ private Rectangle2D getDataArea() { final Rectangle2D dataArea = _panel.getChartRenderingInfo().getPlotInfo().getDataArea(); return dataArea; } /** * Converts screen x to chart x value. * * @return chart value */ private double screenToValue(final int x) { return _plot.getDomainAxis().java2DToValue(x, getDataArea(), RectangleEdge.TOP); } } private class FittingCursorListener implements IFittingCursorListener { @Override public void cursorChanged(final FittingCursor cursor) { final double transStart = cursor.getTransientStartValue(); final double dataStart = cursor.getDataStartValue(); final double transStop = cursor.getTransientStopValue(); setStartStop(transStart, dataStart, transStop); _frame.repaint(); } } } /** * Used only within DecayGraph, to get results from StartStopDraggingUI inner * class. * * @author Aivar Grislis */ interface IStartStopProportionListener { public void setStartStopProportion(double transStartProportion, double dataStartProportion, double transStopProportion); }