www.pudn.com > JfreeChartPanel.zip > FastChartPanel.java, change:2006-08-15,size:48441b


//Put this into your application package

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.List;

import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.plot.FastCombinedDomainXYPlot;
import org.jfree.chart.plot.FastXYPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.FastXYSeries;
import org.jfree.ui.RectangleEdge;
import org.jfree.util.ShapeUtilities;

/**
 *
 * <p>Title: FastChartPanel.</p>
 *
 * <p>Description: This class extends ChartPanel, ostensibly to add the ability
 * for the 'Scroll-Line'. Other small changes to ChartPanel that we needed are
 * included here as well. Minimal changes to the original ChartPanel becuase
 * other people use this copy of JFreeChart.</p>
 *
 * Note to others: MUCH of the stuff here is for application specific work. You
 * certainly don't need most of the methods within.
 *
 */
public class FastChartPanel extends ChartPanel implements ActionListener {


    /**
     * The Shape to draw at the final point.
     */
    public static final Shape POINT =
            new java.awt.geom.Rectangle2D.Double( -4.0, -4.0, 8.0, 8.0);


    /**
     * Number of days before Jan 1st 1970 that corresponds to MJD 0.0.
     */
    public static final double MJD_OFFSET = 587.0;

    /**
     * An amount to increment by.
     */
    public static final double SECS = 1000.0;

    /**
     * An amount to increment by.
     */

    public static final double MINS = 60000.0;

    /**
     * An amount to increment by.
     */
    public static final double LOW_GEAR = 1;

    /**
     * An amount to increment by.
     */
    public static final double MED_GEAR = 5;

    /**
     * An amount to increment by.
     */
    public static final double HIGH_GEAR = 25;

    /**
     * Number of milliseconds in a day.
     */
    public static final long MSECS_PER_DAY = 86400000;

    /**
     * Denoting the Scroll-line moving right.
     */
    public static final int SCROLLING_RIGHT = 1;

    /**
     * Denoting the Scroll-line moving left.
     */
    public static final int SCROLLING_LEFT = -1;

    /**
     * Denoting the Scroll-line not moving.
     */
    public static final int SCROLLING_NOT = 10;

    /**
     * The last position (actual time) held by the Scroll-line.
     */
    private double lastPosition;
    /**
     * The last index selected in teh seriesBox.
     */
    private int lastIndex;
    /**
     * Amount to increment by.
     */
    private double amount = 1000.0;

    /**
     * The Scroll-line.
     */
    private Line2D scrollLine = null;

    /**
     * Flag stating whether scrolling or not.
     */
    private boolean scrollMode = false;

    /**
     * IsDragging flag.
     */
    private boolean isDragging;

    /**
     * X versus Y flag.
     */
    private boolean XvsY = false;
    /**
     * Flag used to denote arrow enabled status.
     */
    private boolean someArrowsDisabled = false;

    /**
     * List of XYSeriesInfos displayed on an XvsY plot.
     */
    private XYSeriesInfo[] scrollXYSeriesList;

    /**
     * Array of indexes to XYSeriesInfos.
     */
    private int[] indexes;

    /**
     * An array of shapes to be used to store the translated Shapes.
     */
    private Shape[] points;

    /**
     * Point used to obtaing the scaled Data Area.
     */
    private Point2D scrollPoint = null;

    /**
     * The plotpanel that owns this ChartPanel.
     */
    private PlotPanel panel = null;


    /**
     * Constructor.
     * @param chart JFreeChart The chart this panel holds.
     * @param plotPanel PlotPanel The plotpanel that owns this ChartPanel.
     */
    public FastChartPanel(JFreeChart chart, PlotPanel plotPanel) {
        this(chart, false, plotPanel);
    }

    /**
     *  Constructor.
     * @param chart JFreeChart The chart this panel holds.
     * @param buffering boolean Wether to use buffering or not.
     * @param plotPanel PlotPanel The plotpanel that owns this ChartPanel.
     */
    public FastChartPanel(JFreeChart chart, boolean buffering,
                          PlotPanel plotPanel) {
        super(chart, buffering, false);
        panel = plotPanel;
    }

    /**
     * Sets the ScrollMode flag.
     * @param flag boolean the flag state.
     */
    public void setScrollMode(final boolean flag) {
        if (!XvsY) {
            scrollMode = flag;
        }
    }

    public void setAmount(final double x) {
        amount = x;
    }


    /**
     * Receives the new location of the Scroll-line, and updates the LRVPanel
     * and the clock with the new information.
     * @param x double the pixel location of the Scroll-line.
     * @param dataArea Rectangle2D the rectangle that 'x' is located within.
     */
    public void passOffX(double x, final Rectangle2D dataArea) {
        double time = this.getChart().getXYPlot().getDomainAxis().
                      java2DToValue(x, dataArea, RectangleEdge.BOTTOM);
        if (lastPosition == 0) {
            lastPosition = time;
        }
        if (time < lastPosition) {
            updateLRVS(time, SCROLLING_LEFT);
        } else if (time > lastPosition) {
            updateLRVS(time, SCROLLING_RIGHT);
        } else {
            updateLRVS(time, SCROLLING_NOT);
        }
        lastPosition = time;
        double mjd = time / MSECS_PER_DAY + MJD_OFFSET;
        Timetag tag = new Timetag(mjd);
        clockString(tag.getDateTime());

    }

    /**
     * Convenience method to create the string that is displayed in the 'clock'.
     * Milliseconds are not displayed.
     * @param tag short[] the Timetag to be displayed.
     */
    public static void clockString(final short[] tag) {
        if (tag != null) {
            MainWindow.setClock(tag[0] + ":" + tag[1] + ":" + tag[2] + ":"
                                + tag[3] + ":" + tag[4]);
        } else {
            MainWindow.setClock("");
        }
    }

    /**
     * Calls methods in PlotPanel that update the LRVPanel at the Scroll-line
     * position.
     * @param time double The actual time position of the Scroll-line in m-secs.
     * @param direction int the direction the Scroll-line was dragged.
     */
    private void updateLRVS(final double time, final int direction) {
        panel.findIndexes(time, direction);
        panel.updateScrollLRVS();
    }

    /**
     * Directs a click on scroll arrow to either movePoint or moveLine.
     * @param direction boolean the direction moved.
     */
    public void move(final boolean direction) {
        if (XvsY) {
            movePoints(direction);
        } else {
            moveLine(direction);
        }
    }

    /**
     * In response to clicks on the scrolling arrows, it moves the scroll-point
     * along the selected series.
     * @param direction boolean the direction moved.
     */
    private void movePoints(final boolean direction) {

        //erase the old point

        Graphics2D g2 = (Graphics2D) getGraphics();
        g2.setPaint(java.awt.Color.white);
        g2.setXORMode(java.awt.Color.black);
        g2.fill(points[lastIndex]);
        g2.draw(points[lastIndex]);

        //increment the index in the appropriate direction and calculate the new
        //point position. NOTE: True = RIGHT;
        if (direction) {
            if (someArrowsDisabled) {
                MainWindow.enableLeftArrows(true);
                someArrowsDisabled = false;
            }
            if ((indexes[lastIndex] + amount)
                < scrollXYSeriesList[lastIndex].getSeries().getItemCount()) {
                indexes[lastIndex] += amount;
            } else {
                indexes[lastIndex] = scrollXYSeriesList[lastIndex].getSeries().
                                     getItemCount() - 1;
                MainWindow.enableRightArrows(false);
                someArrowsDisabled = true;
            }
        } else {
            if (someArrowsDisabled) {
                MainWindow.enableRightArrows(true);
                someArrowsDisabled = false;
            }

            if ((indexes[lastIndex] - amount) > 0) {

                indexes[lastIndex] -= amount;
            } else {
                indexes[lastIndex] = 0;
                MainWindow.enableLeftArrows(false);
                someArrowsDisabled = true;
            }

        }

        Rectangle2D dataArea = getScreenDataArea();
        Rectangle2D scrollDataArea = getScreenDataArea((int) dataArea.
                getCenterX(), (int) dataArea.getMinY() + 5);
        List list = ((FastCombinedDomainXYPlot) getChart().getXYPlot()).
                    getSubplots();

        FastXYSeries series = scrollXYSeriesList[lastIndex].getSeries();
        double X1 = getChart().getXYPlot().getDomainAxis().
                    valueToJava2D(series.getXValue(indexes[lastIndex]),
                                  scrollDataArea, RectangleEdge.BOTTOM);
        double Y1 = ((FastXYPlot) list.get(0)).getRangeAxis().valueToJava2D(
                series.getYValue(indexes[lastIndex]), scrollDataArea,
                RectangleEdge.LEFT);
        points[lastIndex] = ShapeUtilities.createTranslatedShape(POINT, X1, Y1);
        updateClockXvsY(true);
        panel.updateXYScrollLRVS(scrollXYSeriesList, indexes);

        //now draw the new points;
        g2.fill(points[lastIndex]);
        g2.draw(points[lastIndex]);
        g2.setPaintMode();
    }

    /**
     * Updates the clock in the case of a point-scrolling XvsY plot.
     * @param flag boolean false if there are no points in the series.
     */
    private void updateClockXvsY(final boolean flag) {
        if (flag) {
            double pointTime = ((Double) scrollXYSeriesList[lastIndex].
                                getTimeList().
                                get(indexes[lastIndex])).doubleValue();
            pointTime = pointTime / MSECS_PER_DAY + MJD_OFFSET;
            Timetag tag = new Timetag(pointTime);
            clockString(tag.getDateTime());
        } else {
            MainWindow.setClock("NO POINTS IN SERIES");
            MainWindow.enableAllArrows(false);
        }
    }

    /**
     * Moves the Scroll-line in response to clicks on the 'arrow buttons'.
     * @param direction boolean The direction the line is moved. True = Right.
     */
    private void moveLine(final boolean direction) {

        //erase the old line
        Graphics2D g2 = (Graphics2D) getGraphics();
        g2.setPaint(java.awt.Color.white);
        g2.setXORMode(java.awt.Color.gray);
        g2.draw(scrollLine);

        //calculate new position by incrementing the time the Scroll-line
        //is indicating by AMOUNT.
        Rectangle2D dataArea = this.getScreenDataArea();
        scrollPoint = new Point2D.Double(dataArea.getCenterX(),
                                         dataArea.getMinY() + 5);
        Rectangle2D scaledDataArea = this.getScreenDataArea(
                (int) scrollPoint.getX(), (int) scrollPoint.getY());

        //Note: direction == true  means the RIGHT button has been pushed.
        // otherwise the LEFT has been pushed.
        double time = lastPosition;
        if (direction) {
            time += amount;
        } else {
            time -= amount;
        }

        time = getChart().getXYPlot().getDomainAxis().valueToJava2D(
                time, scaledDataArea, RectangleEdge.BOTTOM);
        if (time <= scaledDataArea.getMinX()) {
            scrollLine.setLine(scaledDataArea.getMinX() + 1,
                               dataArea.getMinY(),
                               scaledDataArea.getMinX() + 1,
                               dataArea.getMaxY());
        } else if (time >= scaledDataArea.getBounds().getMaxX()) {
            scrollLine.setLine(scaledDataArea.getMaxX() - 1,
                               dataArea.getMinY(),
                               scaledDataArea.getMaxX() - 1,
                               dataArea.getMaxY());
        } else {
            scrollLine.setLine(time, dataArea.getMinY(), time,
                               dataArea.getMaxY());
        }

        //Draw the new line.
        g2.draw(scrollLine);
        passOffX(scrollLine.getX1(), scaledDataArea);
        g2.setPaintMode();

    }

    /**
     * Called when Scroll-line mode is exited. Resets and removes the line
     * and the clock.
     */
    public void eraseOldLine() {
        if (XvsY) {
            if (points != null) {
                int index = MainWindow.getComboBoxIndex();
                MainWindow.getComboBox().removeActionListener(this);
                MainWindow.removeComboBoxArray();
                Graphics2D g2 = (Graphics2D) getGraphics();
                if ((points[index] != null) && (g2 != null)) {
                    g2.setPaint(java.awt.Color.white);
                    g2.setXORMode(java.awt.Color.black);
                    g2.fill(points[index]);
                    g2.draw(points[index]);
                    g2.setPaintMode();
                }

                points = null;
                scrollXYSeriesList = null;
                indexes = null;
                XvsY = false;
            }

        } else {

            if (scrollLine != null) {
                Graphics2D g2 = (Graphics2D) getGraphics();
                g2.setPaint(java.awt.Color.white);
                g2.setXORMode(java.awt.Color.gray);
                g2.draw(scrollLine);
                g2.setPaintMode();
                scrollLine = null;
            }
        }
        clockString(null);
    }

    /**
     * Returns the Scroll-line.
     * @return Shape the line returned.
     */
    public Shape getScrollLine() {
        return scrollLine;
    }

    /**
     * Returns the last Position of the Scroll-line.
     * @return double the last Position.
     */
    public double getLastPosition() {
        return lastPosition;
    }

    /**
     * Sets the Scroll-line with passed in one.
     * @param line Line2D the line passed in.
     */
    public void setScrollLine(final Line2D line) {
        scrollLine = line;
    }


//    public void findXYIndexes(double time, int direction) {
//        //if the scrollLine hasnt moved, do nothing. Included in case
//        //something SHOULD be done in the future.
//        if (direction == FastChartPanel.SCROLLING_NOT) {
//            return;
//        }
//
//        //loop through the series in the plot
//        for (int i = 0; i < scrollXYSeriesList.length; i++) {
//            LinkedList list = scrollXYSeriesList[i].getTimeList();
//            //if searching to the right find the first point that is
//            //greater than the time asked for, and report the value of the
//            //one before it.
//            if (direction == FastChartPanel.SCROLLING_RIGHT) {
//                if (indexes[i] != list.size() - 1) {
//                    int count = indexes[i];
//                    for (ListIterator iterator = list.listIterator(indexes[i]);
//                                                 iterator.hasNext(); ) {
//                        double listTime = ((Double) iterator.next()).
//                                          doubleValue();
//                        if (listTime > time) {
//                            indexes[i] = count - 1;
//                            break;
//                        }
//                        count++;
//                    }
//                    if (indexes[i] <= 0) {
//                        indexes[i] = 0;
//                    } else if (indexes[i]
//                               >= scrollXYSeriesList[i].getSeries().
//                               getItemCount()) {
//                        indexes[i] = scrollXYSeriesList[i].getSeries().
//                                     getItemCount()
//                                     - 1;
//                    }
//                }
//                //if searching to the left, find the first point that is
//                //equal to or less than the time asked for, and report
//                //the value of that.
//            } else if (direction == FastChartPanel.SCROLLING_LEFT) {
//                if (indexes[i] != 0) {
//                    int count = indexes[i];
//                    for (ListIterator iterator = list.listIterator(indexes[i]);
//                                                 iterator.hasPrevious(); ) {
//                        double listTime = ((Double) iterator.previous()).
//                                          doubleValue();
//                        if (listTime <= time) {
//                            indexes[i] = count;
//                            break;
//                        }
//                        count--;
//                    }
//                    if (indexes[i] <= 0) {
//                        indexes[i] = 0;
//                    } else if (indexes[i]
//                               >= scrollXYSeriesList[i].getSeries().
//                               getItemCount()) {
//                        indexes[i] = scrollXYSeriesList[i].getSeries().
//                                     getItemCount()
//                                     - 1;
//                    }
//                }
//            }
//        }
//
//        double mjd = time / MSECS_PER_DAY + MJD_OFFSET;
//        Timetag tag = new Timetag(mjd);
//        clockString(tag.getDateTime());
//
//    }

    /**
     * Initialises the position and the attendant parts to the Scroll-line.
     */
    public void establishInitialPositions() {
        //Initialise the intial parts, and calculate a starting
        //Scroll-line position.


        if (!panel.getPageAttributes().getPlot(0).getPlotType().hasTimeAxis()) {
            //fill the combobox and enable it
            String[] comboBox = new String[panel.getPageAttributes().getPlot(0).
                                childCount()];
            for (int i = 0; i < comboBox.length; i++) {
                comboBox[i] = panel.getPageAttributes().getPlot(0).getChannel(i).
                              getName();
            }
            MainWindow.assignComboBoxArray(comboBox);
            MainWindow.getComboBox().addActionListener(this);
            lastIndex = MainWindow.getComboBoxIndex();

            //initialise, and find initial index
            //locations relevant to a time halfway between the global
            //first and last times over all the XYSeriesInfos.
            XvsY = true;
            scrollXYSeriesList = panel.initializeXvsYPoints();

            indexes = new int[scrollXYSeriesList.length];
            for (int i = 0; i < scrollXYSeriesList.length; i++) {
                if (scrollXYSeriesList[i].getSeries().getItemCount() > 0) {
                    indexes[i] = scrollXYSeriesList[i].getSeries().getItemCount()
                                 / 2;
                } else {
                    indexes[i] = Integer.MIN_VALUE;
                }
            }
            panel.updateXYScrollLRVS(scrollXYSeriesList, indexes);

            //now that a time has been found draw some shapes over the points
            //that best fit that time.
            points = new Shape[scrollXYSeriesList.length];
            Rectangle2D dataArea = getScreenDataArea();
            Rectangle2D scrollDataArea = getScreenDataArea((int) dataArea.
                    getCenterX(), (int) dataArea.getMinY() + 5);
            List list = ((FastCombinedDomainXYPlot) getChart().getXYPlot()).
                        getSubplots();
            for (int i = 0; i < indexes.length; i++) {
                if (indexes[i] != Integer.MIN_VALUE) {
                    FastXYSeries series = scrollXYSeriesList[i].getSeries();
                    double X1 = getChart().getXYPlot().getDomainAxis().
                                valueToJava2D(series.getXValue(indexes[i]),
                                              scrollDataArea,
                                              RectangleEdge.BOTTOM);
                    double Y1 = ((FastXYPlot) list.get(0)).getRangeAxis().
                                valueToJava2D(
                                        series.getYValue(indexes[i]),
                                        scrollDataArea,
                                        RectangleEdge.LEFT);
                    points[i] = ShapeUtilities.createTranslatedShape(POINT, X1,
                            Y1);
                } else {
                    points[i] = null;
                }
            }
            if (points[lastIndex] != null) {
                updateClockXvsY(true);
                Graphics2D g2 = (Graphics2D) getGraphics();
                g2.setPaint(java.awt.Color.white);
                g2.setXORMode(java.awt.Color.black);
                g2.fill(points[lastIndex]);
                g2.draw(points[lastIndex]);
                g2.setPaintMode();
            } else {
                updateClockXvsY(false);
                MainWindow.enableAllArrows(false);
            }
        } else {
            scrollLine = new Line2D.Double();
            Rectangle2D dataArea = getScreenDataArea();
            scrollPoint = new Point2D.Double(dataArea.getCenterX(),
                                             (dataArea.getMinY() + 5));
            scrollLine.setLine(dataArea.getCenterX(), dataArea.getMinY(),
                               dataArea.getCenterX(), dataArea.getMaxY());
            Rectangle2D scrollDataArea = getScreenDataArea((int) scrollPoint.
                    getX(), (int) scrollPoint.getY());
            double time = getChart().getXYPlot().getDomainAxis().java2DToValue(
                    scrollPoint.getX(),
                    scrollDataArea, RectangleEdge.BOTTOM);
            panel.initalizeScrollLine(time);
            lastPosition = time;
            passOffX(dataArea.getCenterX(), dataArea);

            //now that datastrutures are initialised, actually draw a line,
            //indicating we're ready to go.
            Graphics2D g2 = (Graphics2D) getGraphics();
            g2.setPaint(java.awt.Color.white);
            g2.setXORMode(java.awt.Color.gray);
            g2.draw(scrollLine);
            g2.setPaintMode();
        }
    }

    /**
     * Handles a 'mouse pressed' event.
     * <P>
     * This event is the popup trigger on Unix/Linux.  For Windows, the popup
     * trigger is the 'mouse released' event.
     *
     * @param e  The mouse event.
     */
    public void mousePressed(MouseEvent e) {

//--------------------------Added to original ChartPanel mousePressed.----------
        if (scrollMode) {

            //check if the mouse was pressed within 5 pixels of the scrollLine
            //horizontally, and simply within the upper and lower bounds of the
            //dataArea vertically.

            if (e.getX() >= scrollLine.getX1() - 2
                && e.getX() <= scrollLine.getX1() + 2
                && e.getY() >= scrollLine.getY1()
                && e.getY() <= scrollLine.getY2()) {
                scrollPoint = new Point2D.Double(scrollLine.getX1(),
                                                 scrollLine.getY1() + 5);
                isDragging = true;
            }
        } else {
//----------------------------------------------------------------------
            if (this.zoomRectangle == null) {
                Rectangle2D screenDataArea = getScreenDataArea(e.getX(), e.getY());
                if (screenDataArea != null) {
                    this.zoomPoint = getPointInRectangle(
                            e.getX(), e.getY(), screenDataArea
                                     );
                } else {
                    this.zoomPoint = null;
                }
                if (e.isPopupTrigger()) {
                    if (this.popup != null) {
                        displayPopupMenu(e.getX(), e.getY());
                    }
                }
            }
        }
    }

    /**
     * Receives notification of mouse clicks on the panel. These are
     * translated and passed on to any registered chart mouse click listeners.
     *
     * @param event  Information about the mouse event.
     */
    public void mouseClicked(MouseEvent event) {

        Insets insets = getInsets();
        int x = (int) ((event.getX() - insets.left) / this.scaleX);
        int y = (int) ((event.getY() - insets.top) / this.scaleY);

        this.anchor = new java.awt.geom.Point2D.Double(x, y);

//----------------Only change in mouseClicked. Don't let it fire notifications
        //        this.chart.setNotify(true); // force a redraw
//---------------------------------------------------------------------------
        // new entity code...
        Object[] listeners = this.chartMouseListeners.getListeners(
                ChartMouseListener.class);
        if (listeners.length == 0) {
            return;
        }

        ChartEntity entity = null;
        if (this.info != null) {
            EntityCollection entities = this.info.getEntityCollection();
            if (entities != null) {
                entity = entities.getEntity(x, y);
            }
        }
        ChartMouseEvent chartEvent = new ChartMouseEvent(getChart(), event,
                entity);
        for (int i = listeners.length - 1; i >= 0; i -= 1) {
            ((ChartMouseListener) listeners[i]).chartMouseClicked(
                    chartEvent);
        }

    }


    /**
     * Handles a 'mouse dragged' event.
     *
     * @param e  the mouse event.
     */
    public void mouseDragged(MouseEvent e) {

//--------------Added from original ChartPanel mouseDragged.----------------
        //If the mouse is dragged and the isDragging flag is up
        // this means the user has 'picked-up' the Scroll-line.
        if (isDragging) {

            //erase the old line
            Graphics2D g2 = (Graphics2D) getGraphics();
            g2.setPaint(java.awt.Color.white);
            g2.setXORMode(java.awt.Color.gray);
            g2.draw(scrollLine);

            //calculate where the new line should be
            Rectangle2D dataArea = this.getScreenDataArea();
            Rectangle2D scaledDataArea = this.getScreenDataArea(
                    (int) scrollPoint.getX(), (int) scrollPoint.getY());
            if (e.getX() <= scaledDataArea.getMinX()) {
                scrollLine.setLine(scaledDataArea.getMinX() + 1,
                                   dataArea.getMinY(),
                                   scaledDataArea.getMinX() + 1,
                                   dataArea.getMaxY());
            } else if (e.getX() >= scaledDataArea.getMaxX()) {
                scrollLine.setLine(scaledDataArea.getMaxX() - 1,
                                   dataArea.getMinY(),
                                   scaledDataArea.getMaxX() - 1,
                                   dataArea.getMaxY());
            } else {
                scrollLine.setLine(e.getX(), dataArea.getMinY(), e.getX(),
                                   dataArea.getMaxY());
            }

            //and draw it again.
            g2.draw(scrollLine);
            passOffX(scrollLine.getX1(), scaledDataArea);
            g2.setPaintMode();

        } else {
//---------------------------------------------------------------------------

            // if the popup menu has already been triggered, then ignore dragging...
            if (this.popup != null && this.popup.isShowing()) {
                return;
            }
            // if no initial zoom point was set, ignore dragging...
            if (this.zoomPoint == null) {
                return;
            }

            Graphics2D g2 = (Graphics2D) getGraphics();

            // use XOR to erase the previous zoom rectangle (if any)...
            g2.setXORMode(java.awt.Color.gray);
            if (this.zoomRectangle != null) {
                if (this.fillZoomRectangle) {
                    g2.fill(this.zoomRectangle);
                } else {
                    g2.draw(this.zoomRectangle);
                }
            }

            boolean hZoom = false;
            boolean vZoom = false;
            if (this.orientation == PlotOrientation.HORIZONTAL) {
                hZoom = this.rangeZoomable;
                vZoom = this.domainZoomable;
            } else {
                hZoom = this.domainZoomable;
                vZoom = this.rangeZoomable;
            }
            Rectangle2D scaledDataArea = getScreenDataArea(
                    (int)this.zoomPoint.getX(), (int)this.zoomPoint.getY()
                                         );
            if (hZoom && vZoom) {
                // selected rectangle shouldn't extend outside the data area...
                double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
                double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
                this.zoomRectangle = new Rectangle2D.Double(
                        this.zoomPoint.getX(), this.zoomPoint.getY(),
                        xmax - this.zoomPoint.getX(),
                        ymax - this.zoomPoint.getY()
                                     );
            } else if (hZoom) {
                double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
                this.zoomRectangle = new Rectangle2D.Double(
                        this.zoomPoint.getX(), scaledDataArea.getMinY(),
                        xmax - this.zoomPoint.getX(), scaledDataArea.getHeight()
                                     );
            } else if (vZoom) {
                double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
                this.zoomRectangle = new Rectangle2D.Double(
                        scaledDataArea.getMinX(), this.zoomPoint.getY(),
                        scaledDataArea.getWidth(), ymax - this.zoomPoint.getY()
                                     );
            }

            if (this.zoomRectangle != null) {
                // use XOR to draw the new zoom rectangle...
                if (this.fillZoomRectangle) {
                    g2.fill(this.zoomRectangle);
                } else {
                    g2.draw(this.zoomRectangle);
                }
            }
            g2.dispose();

        }
    }

    /**
     * Handles a 'mouse released' event.  On Windows, we need to check if this
     * is a popup trigger, but only if we haven't already been tracking a zoom
     * rectangle.
     *
     * @param e  information about the event.
     */
    public void mouseReleased(MouseEvent e) {
//--------------------Added to original ChartPanel mouseReleased------------
        if (isDragging) {
            scrollPoint = null;
            isDragging = false;
        }
//-------------------------------------------------------------------------
        if (this.zoomRectangle != null) {
            boolean hZoom = false;
            boolean vZoom = false;
            if (this.orientation == PlotOrientation.HORIZONTAL) {
                hZoom = this.rangeZoomable;
                vZoom = this.domainZoomable;
            } else {
                hZoom = this.domainZoomable;
                vZoom = this.rangeZoomable;
            }

            boolean zoomTrigger1 = hZoom && Math.abs(e.getX()
                    - this.zoomPoint.getX()) >= this.zoomTriggerDistance;
            boolean zoomTrigger2 = vZoom && Math.abs(e.getY()
                    - this.zoomPoint.getY()) >= this.zoomTriggerDistance;
            if (zoomTrigger1 || zoomTrigger2) {
                if ((hZoom && (e.getX() < this.zoomPoint.getX()))
                    || (vZoom && (e.getY() < this.zoomPoint.getY()))) {
                    restoreAutoBounds();
                } else {
                    double x, y, w, h;
                    Rectangle2D screenDataArea = getScreenDataArea(
                            (int)this.zoomPoint.getX(),
                            (int)this.zoomPoint.getY()
                                                 );
                    // for mouseReleased event, (horizontalZoom || verticalZoom)
                    // will be true, so we can just test for either being false;
                    // otherwise both are true
                    if (!vZoom) {
                        x = this.zoomPoint.getX();
                        y = screenDataArea.getMinY();
                        w = Math.min(
                                this.zoomRectangle.getWidth(),
                                screenDataArea.getMaxX()
                                - this.zoomPoint.getX()
                            );
                        h = screenDataArea.getHeight();
                    } else if (!hZoom) {
                        x = screenDataArea.getMinX();
                        y = this.zoomPoint.getY();
                        w = screenDataArea.getWidth();
                        h = Math.min(
                                this.zoomRectangle.getHeight(),
                                screenDataArea.getMaxY()
                                - this.zoomPoint.getY()
                            );
                    } else {
                        x = this.zoomPoint.getX();
                        y = this.zoomPoint.getY();
                        w = Math.min(
                                this.zoomRectangle.getWidth(),
                                screenDataArea.getMaxX()
                                - this.zoomPoint.getX()
                            );
                        h = Math.min(
                                this.zoomRectangle.getHeight(),
                                screenDataArea.getMaxY()
                                - this.zoomPoint.getY()
                            );
                    }
                    Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w,
                            h);
                    zoom(zoomArea);
                }
                this.zoomPoint = null;
                this.zoomRectangle = null;
            } else {
                Graphics2D g2 = (Graphics2D) getGraphics();
                g2.setXORMode(java.awt.Color.gray);
                if (this.fillZoomRectangle) {
                    g2.fill(this.zoomRectangle);
                } else {
                    g2.draw(this.zoomRectangle);
                }
                g2.dispose();
                this.zoomPoint = null;
                this.zoomRectangle = null;
            }

        } else if (e.isPopupTrigger()) {
            if (this.popup != null) {
                displayPopupMenu(e.getX(), e.getY());
            }
        }

    }

    /**
     * Listens to selections on the MainWindow seriesBox. On changes in
     * selection it moves the point-scrolling from one XYSeriesInfo to another.
     * Because we extend ChartPanel, the first line is a call to the ChartPanel
     * actionListener.
     * @param e ActionEvent the event recieved.
     */
    public void actionPerformed(ActionEvent e) {
        super.actionPerformed(e);
        if (e.getActionCommand().equals("comboBoxChanged")) {

            Graphics2D g2 = (Graphics2D) getGraphics();
            g2.setPaint(java.awt.Color.white);
            g2.setXORMode(java.awt.Color.black);
            if (points[lastIndex] != null) {

                g2.fill(points[lastIndex]);
                g2.draw(points[lastIndex]);
            }
            lastIndex = MainWindow.getComboBoxIndex();
            Rectangle2D dataArea = getScreenDataArea();
            Rectangle2D scrollDataArea = getScreenDataArea((int) dataArea.
                    getCenterX(), (int) dataArea.getMinY() + 5);
            List list = ((FastCombinedDomainXYPlot) getChart().getXYPlot()).
                        getSubplots();
            FastXYSeries series = scrollXYSeriesList[lastIndex].getSeries();
            if (series.getItemCount() != 0) {
                double X1 = getChart().getXYPlot().getDomainAxis().
                            valueToJava2D(series.getXValue(indexes[lastIndex]),
                                          scrollDataArea, RectangleEdge.BOTTOM);
                double Y1 = ((FastXYPlot) list.get(0)).getRangeAxis().
                            valueToJava2D(
                                    series.getYValue(indexes[lastIndex]),
                                    scrollDataArea,
                                    RectangleEdge.LEFT);

                points[lastIndex] = ShapeUtilities.createTranslatedShape(POINT,
                        X1,
                        Y1);
                updateClockXvsY(true);
                MainWindow.enableAllArrows(true);

                //now draw the new points;
                g2.fill(points[lastIndex]);
                g2.draw(points[lastIndex]);

            } else {
                points[lastIndex] = null;
                updateClockXvsY(false);
                MainWindow.enableAllArrows(false);
            }
            g2.setPaintMode();
        }
    }


    /**
     * Paints the component by drawing the chart to fill the entire component,
     * but allowing for the insets (which will be non-zero if a border has been
     * set for this component).  To increase performance (at the expense of
     * memory), an off-screen buffer image can be used.
     * In the off-screen buffer image is used, no resize, and the
     * <code>drawAppended</code> flag is set, then the <code>drawAppendedData</code>
     * method is called in {@link JFreeChart}
     * In the redrawing the chart each time and the  <code>drawAppended</code>
     * flag is set, then the <code>drawAppendedData</code> method is called in
     * {@link JFreeChart}
     *
     * @param g  the graphics device for drawing on.
     */
    /**
     * Note: The functionality of drawAppended has been added to this method.
     * Depending on the status of the drawAppended flag, one of two routes
     * ending at the plot's renderer is chosen. If the flag is false, then the
     * traditional route is followed which repaints the entire contents of the
     * plot. If the flag is true, only the last undrawn entries in the given
     * series are drawn, greatly improving speed/efficiency.
     *
     */
    public void paintComponent(Graphics g) {

        //these three lines replace super.paintComponent(g);
        g.setColor(getBackground());
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setColor(getForeground());

        if (this.chart == null) {
            return;
        }
        Graphics2D g2 = (Graphics2D) g.create();

        // first determine the size of the chart rendering area...
        Dimension size = getSize();
        Insets insets = getInsets();
        Rectangle2D available = new Rectangle2D.Double(
                insets.left, insets.top,
                size.getWidth() - insets.left - insets.right,
                size.getHeight() - insets.top - insets.bottom
                                );

        // work out if scaling is required...
        boolean scale = false;
        double drawWidth = available.getWidth();
        double drawHeight = available.getHeight();
        this.scaleX = 1.0;
        this.scaleY = 1.0;

        if (drawWidth < this.minimumDrawWidth) {
            this.scaleX = drawWidth / this.minimumDrawWidth;
            drawWidth = this.minimumDrawWidth;
            scale = true;
        } else if (drawWidth > this.maximumDrawWidth) {
            this.scaleX = drawWidth / this.maximumDrawWidth;
            drawWidth = this.maximumDrawWidth;
            scale = true;
        }

        if (drawHeight < this.minimumDrawHeight) {
            this.scaleY = drawHeight / this.minimumDrawHeight;
            drawHeight = this.minimumDrawHeight;
            scale = true;
        } else if (drawHeight > this.maximumDrawHeight) {
            this.scaleY = drawHeight / this.maximumDrawHeight;
            drawHeight = this.maximumDrawHeight;
            scale = true;
        }

        Rectangle2D chartArea = new Rectangle2D.Double(
                0.0, 0.0, drawWidth, drawHeight
                                );

        // are we using the chart buffer?
        if (this.useBuffer) {

            // do we need to resize the buffer?
            ////////////////// Added to support appended draw:Start ////////////////////////
            boolean resize = (this.chartBufferWidth != available.getWidth())
                             || (this.chartBufferHeight != available.getHeight());
            ////////////////// Added to support appended draw:End ////////////////////////
            if ((this.chartBuffer == null || resize)
                || (this.chartBufferWidth != available.getWidth())
                || (this.chartBufferHeight != available.getHeight())
                    ) {
                this.chartBufferWidth = (int) available.getWidth();
                this.chartBufferHeight = (int) available.getHeight();
                this.chartBuffer = createImage(
                        this.chartBufferWidth, this.chartBufferHeight
                                   );
                this.refreshBuffer = true;
            }

            // do we need to redraw the buffer?
            if (this.refreshBuffer) {

                Rectangle2D bufferArea = new Rectangle2D.Double(
                        0, 0, this.chartBufferWidth, this.chartBufferHeight
                                         );

                Graphics2D bufferG2
                        = (Graphics2D)this.chartBuffer.getGraphics();
                AffineTransform saved = bufferG2.getTransform();
                ////////////////// Added/Modified to support appended draw:Start ////////////////////////
                this.info.setInitTranslation(saved); //@todo Ask Jamie about this.
                if (scale) {

                    AffineTransform st = AffineTransform.getScaleInstance(
                            this.scaleX, this.scaleY
                                         );
                    bufferG2.transform(st);
                }
                // Start of the minimal draw route. Triggered by flag being
                // set to true;
                if (dataAppended && !resize) {
                    this.chart.drawAppendedData(bufferG2, chartArea,
                                                this.anchor, this.info);
                    dataAppended = false;
                } else {
                    // Start of the full draw route. Triggered by flag being set
                    // to false;
                    this.chart.draw(bufferG2, chartArea, this.anchor,
                                    this.info);
                }
                if (scale) {
                    bufferG2.setTransform(saved);
                }
                this.refreshBuffer = false;
            }

            //----------Added to support the Scroll-Line. NOT NEEDED for AppendedDraw------
            //On a repaint, redraw the Scroll-line in the position indicated
            //by the time it represents.
            //Get it into the chartBuffer before it gets 'zapped'

            //----------------------------------------------------------------------------
            // zap the buffer onto the panel...
            g2.drawImage(this.chartBuffer, insets.left, insets.right, this);

            if (scrollLine != null) {
                Rectangle2D dataArea = getScreenDataArea();
                Rectangle2D scaledDataArea = getScreenDataArea((int)
                        dataArea.
                        getCenterX(), (int) dataArea.getMinY() + 5);
                double time = lastPosition;
                time = getChart().getXYPlot().getDomainAxis().valueToJava2D(
                        time, scaledDataArea, RectangleEdge.BOTTOM);
                time = Math.rint(time);
                if (time >= scaledDataArea.getMaxX() - 1) {
                    time = Math.rint(scaledDataArea.getMaxX() - 2);
                    lastPosition = getChart().getXYPlot().getDomainAxis().
                                   java2DToValue(
                                           time, scaledDataArea,
                                           RectangleEdge.BOTTOM);
                } else if (time <= scaledDataArea.getMinX() + 2) {
                    time = Math.rint(scaledDataArea.getMinX() + 1);
                    lastPosition = getChart().getXYPlot().getDomainAxis().
                                   java2DToValue(
                                           time, scaledDataArea,
                                           RectangleEdge.BOTTOM);
                }
                scrollLine.setLine(time, dataArea.getMinY(), time,
                                   dataArea.getMaxY());

                g2.setPaint(java.awt.Color.white);
                g2.setXORMode(java.awt.Color.gray);
                g2.draw(scrollLine);
                g2.setPaintMode();
                passOffX(time, dataArea);
            } else if (points != null) {
                Rectangle2D dataArea = getScreenDataArea();
                Rectangle2D scrollDataArea = getScreenDataArea((int)
                        dataArea.
                        getCenterX(), (int) dataArea.getMinY() + 5);
                List list = ((FastCombinedDomainXYPlot) getChart().
                             getXYPlot()).
                            getSubplots();
                for (int i = 0; i < scrollXYSeriesList.length; i++) {
                    FastXYSeries series = scrollXYSeriesList[i].getSeries();
                    if (series.getItemCount() != 0) {
                        double X1 = getChart().getXYPlot().getDomainAxis().
                                    valueToJava2D(series.getXValue(indexes[i]),
                                                  scrollDataArea,
                                                  RectangleEdge.BOTTOM);
                        double Y1 = ((FastXYPlot) list.get(0)).getRangeAxis().
                                    valueToJava2D(
                                            series.getYValue(indexes[i]),
                                            scrollDataArea,
                                            RectangleEdge.LEFT);
                        points[i] = ShapeUtilities.createTranslatedShape(POINT,
                                X1, Y1);
                    } else {
                        points[i] = null;
                    }
                }
                if (points[lastIndex] != null) {
                    g2.setPaint(java.awt.Color.white);
                    g2.setXORMode(java.awt.Color.black);
                    g2.fill(points[lastIndex]);
                    g2.draw(points[lastIndex]);
                    g2.setPaintMode();
                } else {
                    updateClockXvsY(false);
                }
            }

            // or redrawing the chart every time...
        } else {

            AffineTransform saved = g2.getTransform();
            this.info.setInitTranslation(saved);
            g2.translate(insets.left, insets.top);
            if (scale) {
                AffineTransform st = AffineTransform.getScaleInstance(
                        this.scaleX, this.scaleY
                                     );
                g2.transform(st);
            }
            // Start of the minimal draw route. Triggered by flag being
            // set to true;
            // NOTE: not used by our application, so not sure if this works.
            if (dataAppended) {
                this.chart.drawAppendedData(g2, chartArea, this.anchor,
                                            this.info);
                dataAppended = false;
            }
            // Start of the full draw route. Triggered by flag being set
            // to false;
            else {
                this.chart.draw(g2, chartArea, this.anchor, this.info);
            }

            g2.setTransform(saved);

        }

        this.anchor = null;
        this.verticalTraceLine = null;
        this.horizontalTraceLine = null;
////////////////// Added/Modified to support appended draw:End ////////////////////////

    }

}