ucar.unidata.idv.control.chart.TimeSeriesChartWrapper.java Source code

Java tutorial

Introduction

Here is the source code for ucar.unidata.idv.control.chart.TimeSeriesChartWrapper.java

Source

/*
 * $Id: TimeSeriesChartWrapper.java,v 1.51 2007/04/16 21:32:12 jeffmc Exp $
 *
 * Copyright 1997-2019 Unidata Program Center/University Corporation for
 * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
 * support@unidata.ucar.edu.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package ucar.unidata.idv.control.chart;

import org.jfree.chart.*;
import org.jfree.chart.axis.*;
import org.jfree.chart.event.*;
import org.jfree.chart.labels.*;
import org.jfree.chart.plot.*;
import org.jfree.chart.renderer.*;
import org.jfree.chart.renderer.xy.*;
import org.jfree.chart.urls.*;
import org.jfree.data.*;
import org.jfree.data.time.*;
import org.jfree.data.xy.*;
import org.jfree.ui.*;

import org.python.core.*;
import org.python.util.*;

import ucar.unidata.data.DataChoice;
import ucar.unidata.data.sounding.TrackDataSource;
import ucar.unidata.geoloc.LatLonPointImpl;
import ucar.unidata.geoloc.ProjectionRect;

import ucar.unidata.geoloc.projection.*;

import ucar.unidata.idv.control.DisplayControlImpl;

import ucar.unidata.util.GuiUtils;
import ucar.unidata.util.LogUtil;
import ucar.unidata.util.MidiManager;
import ucar.unidata.util.MidiProperties;
import ucar.unidata.util.Misc;
import ucar.unidata.util.ObjectListener;

import ucar.visad.GeoUtils;
import ucar.visad.Util;
import ucar.visad.display.*;

import visad.*;

import visad.georef.*;

import visad.util.BaseRGBMap;

import visad.util.ColorPreview;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;

import java.beans.*;

import java.rmi.RemoteException;

import java.text.SimpleDateFormat;

import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.TimeZone;
import java.util.Vector;

import javax.sound.midi.*;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

/**
 * Provides a time series chart
 *
 *
 * @author IDV Development Team
 * @version $Revision: 1.51 $
 */
public class TimeSeriesChartWrapper extends PlotWrapper {

    /** Property change id */
    public static final String PROP_TIMERANGE = "prop.timerange";

    /** Holds the dots */
    private CompositeDisplayable dotsHolder;

    /** List of way points */
    private List wayPoints = new ArrayList();

    /** The special animation showing way point */
    private WayPoint timeWayPoint;

    /** List of range filters */
    private List rangeFilters = new ArrayList();

    /** List ot track segments_ */
    private List segments = new ArrayList();

    /** Keep around the data arrays */
    List datas = new ArrayList();

    /** Keep around the min/max values of each data array */
    List ranges = new ArrayList();

    /** The full set of times in the domain */
    double[] times;

    /** The lats of each time */
    double[] lats;

    /** The lons each time */
    double[] lons;

    /** The altitudes of each time */
    double[] alts;

    /** The plot */
    private MyTimeSeriesPlot plot;

    /** The dataset */
    private TimeSeriesCollection dataset;

    /** Last time user clicked on */
    private double lastTimeClick = 0.0;

    /** Which one are we dragging */
    private ChartAnnotation draggedAnnotation;

    /** Closest annotation */
    private ChartAnnotation closestAnnotation;

    /** Are we currently dragging. Note: we may not have an annotation that we are dragging */
    private boolean dragging = false;

    /** Show the dots in the 3d display */
    private boolean showDots = true;

    /**
     *   Last wall clock time we drove the animation time in the main display.
     *   We keep this around so we don't  set the time, get the time changed event and
     *   redisplay.
     */
    private long lastTimeWeDrove = 0;

    /** Sets show the dots  in the properties dialog */
    private JCheckBox showDotsCbx;

    /** keep from infinite looping */
    private boolean inSetTime = false;

    /**
     * Default ctor
     */
    public TimeSeriesChartWrapper() {
    }

    /**
     * Ctor
     *
     * @param name The name
     * @param dataChoices List of data choices
     */
    public TimeSeriesChartWrapper(String name, List dataChoices) {
        super(name, dataChoices);
    }

    /**
     * Create the chart
     */
    private void createChart() {
        if (chartPanel != null) {
            return;
        }
        dataset = new TimeSeriesCollection();
        ValueAxis timeAxis = doMakeDateAxis();
        timeAxis.setLowerMargin(0.02);
        timeAxis.setUpperMargin(0.02);
        NumberAxis valueAxis = new NumberAxis("Data");
        valueAxis.setAutoRangeIncludesZero(false);
        plot = new MyTimeSeriesPlot(this, dataset, timeAxis, valueAxis);
        plot.setRenderer(doMakeRenderer());
        chart = new JFreeChart(getName(), JFreeChart.DEFAULT_TITLE_FONT, plot, true);

        addAnnotations(segments);
        addAnnotations(wayPoints);
        for (int i = 0; i < wayPoints.size(); i++) {
            WayPoint waypoint = (WayPoint) wayPoints.get(i);
            waypoint.addPropertyChangeListener(this);
        }
        addAnnotations(rangeFilters);

        initXYPlot(plot);
        XYItemRenderer r = plot.getRenderer();
        if (r instanceof XYLineAndShapeRenderer) {
            XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;
            //            renderer.setDefaultShapesVisible(false);
            //            renderer.setDefaultShapesFilled(false);
            //      renderer.setDefaultShapesFilled(true);
        }

        DateAxis axis = (DateAxis) plot.getDomainAxis();
        //        axis.setDateFormatOverride(new SimpleDateFormat("HH:MM:ss"));
        chartPanel = doMakeChartPanel(chart);
    }

    /**
     * Get the plot we use
     *
     * @return The plot
     */
    public XYPlot getPlot() {
        return plot;
    }

    /**
     * Utility to make the renderer
     *
     * @return The renderer
     */
    private XYItemRenderer doMakeRenderer() {
        XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
        //        renderer.setDefaultLinesVisible(true);
        //        renderer.setDefaultShapesVisible(false);
        return renderer;

    }

    /**
     * Return the human readable name of this chart
     *
     * @return Chart type name
     */
    public String getTypeName() {
        return "Time Series";
    }

    /**
     *
     * Create the chart if needed
     *
     * @return The gui contents
     */
    protected JComponent doMakeContents() {
        createChart();
        return chartPanel;
    }

    /**
     * Get the list of displayables we use
     *
     * @return List of displayables
     */
    public List getDisplayables() {
        List l = super.getDisplayables();
        if (dotsHolder != null) {
            l.add(dotsHolder);
        }
        return l;
    }

    /**
     * Create the charts
     *
     * @throws RemoteException On badness
     * @throws VisADException On badness
     */
    public void loadData() throws VisADException, RemoteException {
        long t1 = System.currentTimeMillis();
        loadDatax();
        long t2 = System.currentTimeMillis();
        //        System.err.println ("t:" + (t2-t1));
    }

    /**
     * Create the charts
     *
     * @throws RemoteException On badness
     * @throws VisADException On badness
     */
    public void loadDatax() throws VisADException, RemoteException {

        createChart();
        times = null;
        List unitList = new ArrayList();
        List dataChoiceWrappers = getDataChoiceWrappers();
        datas.clear();
        ranges.clear();
        try {
            plot.setIgnoreDataSetChanges(true);
            plot.clearRangeAxes();
            plot.setRangeAxis(0, new NumberAxis(""), false);
            for (int dataSetIdx = 0; dataSetIdx < plot.getDatasetCount(); dataSetIdx++) {
                TimeSeriesCollection dataset = (TimeSeriesCollection) plot.getDataset(dataSetIdx);
                dataset.removeAllSeries();
            }

            //            plot.clearDatasets();
            //            dataset.setDomainIsPointsInTime(true);
            Hashtable props = new Hashtable();
            props.put(TrackDataSource.PROP_TRACKTYPE, TrackDataSource.ID_TIMETRACE);

            AxisLocation lastSide = AxisLocation.BOTTOM_OR_RIGHT;
            for (int paramIdx = 0; paramIdx < dataChoiceWrappers.size(); paramIdx++) {
                DataChoiceWrapper wrapper = (DataChoiceWrapper) dataChoiceWrappers.get(paramIdx);
                DataChoice dataChoice = wrapper.getDataChoice();
                FlatField data = getFlatField((FieldImpl) dataChoice.getData(null, props));
                Set domainSet = data.getDomainSet();
                double[][] domain = domainSet.getDoubles(false);
                double[][] samples = data.getValues(false);
                double[] var = samples[0];

                Unit unit = ucar.visad.Util.getDefaultRangeUnits(data)[0];
                Unit displayUnit = null;
                if (unit != null) {
                    displayUnit = getDisplayControl().getDisplayConventions().getDisplayUnit(dataChoice.getName(),
                            null);
                    if ((displayUnit != null) && !displayUnit.equals(unit)) {
                        var = displayUnit.toThis(var, unit);
                        unit = displayUnit;

                    }
                }

                unitList.add(unit);
                double[] timeValues = getTimeValues(samples, (FlatField) data);
                double[][] result = filterData(var, timeValues);
                var = result[0];
                timeValues = result[1];
                TimeSeries series = new TimeSeries(dataChoice.getName() + ((unit == null) ? "" : " [" + unit + "]"),
                        FixedMillisecond.class);

                //TODO: Find the lat/lon/alt index in the domain
                times = timeValues;
                lats = domain[0];
                lons = domain[1];
                alts = domain[2];
                datas.add(var);
                long t1 = System.currentTimeMillis();
                double min = 0;
                double max = 0;
                for (int i = 0; i < var.length; i++) {
                    Date dttm = new Date((long) (timeValues[i]));
                    //                    series.addOrUpdate(new FixedMillisecond(dttm), var[i]);
                    series.add(new FixedMillisecond(dttm), var[i]);
                    if ((i == 0) || (var[i] < min)) {
                        min = var[i];
                    }
                    if ((i == 0) || (var[i] > max)) {
                        max = var[i];
                    }
                }
                ranges.add(new ucar.unidata.util.Range(min, max));

                long t2 = System.currentTimeMillis();
                //                System.err.println ("\t time to add:" + (t2-t1));

                TimeSeriesCollection dataset = new TimeSeriesCollection();
                dataset.setDomainIsPointsInTime(true);
                dataset.addSeries(series);
                NumberAxis rangeAxis = new NumberAxis(wrapper.getLabel(unit));
                plot.setRangeAxis(paramIdx, rangeAxis, false);
                plot.setDataset(paramIdx, dataset);

                XYItemRenderer renderer = doMakeRenderer();
                plot.setRenderer(paramIdx, renderer);
                plot.mapDatasetToRangeAxis(paramIdx, paramIdx);
                Color c = wrapper.getColor(paramIdx);
                rangeAxis.setLabelPaint(c);
                renderer.setSeriesPaint(0, c);
                renderer.setSeriesStroke(0, wrapper.getLineState().getStroke());

                AxisLocation side;
                if (wrapper.getSide() == wrapper.SIDE_UNDEFINED) {
                    if (lastSide == AxisLocation.TOP_OR_LEFT) {
                        side = AxisLocation.BOTTOM_OR_RIGHT;
                    } else {
                        side = AxisLocation.TOP_OR_LEFT;
                    }
                } else if (wrapper.getSide() == wrapper.SIDE_LEFT) {
                    side = AxisLocation.TOP_OR_LEFT;
                } else {
                    side = AxisLocation.BOTTOM_OR_RIGHT;
                }
                lastSide = side;
                plot.setRangeAxisLocation(paramIdx, side);
            }
        } catch (Exception exc) {
            LogUtil.logException("Error creating data set", exc);
            return;
        }

        if (dataChoiceWrappers.size() == 0) {
            NumberAxis axis = new NumberAxis("");
            plot.setRangeAxis(0, axis, false);
            ValueAxis timeAxis = doMakeDateAxis();
            plot.setDomainAxis(0, timeAxis, false);
        }

        plot.setIgnoreDataSetChanges(false);

        try {
            setLocationPositions();
        } catch (Exception exc) {
            LogUtil.logException("Error creating wayPoints", exc);
        }
    }

    /**
     * Utility to create the DataAxis with the correct time zone
     *
     * @return data axis with timezone
     */
    private DateAxis doMakeDateAxis() {
        TimeZone timeZone = displayControl.getControlContext().getIdv().getPreferenceManager().getDefaultTimeZone();
        return new DateAxis("Time (" + timeZone.getID() + ")", timeZone);
    }

    /**
     * to string
     *
     * @return string
     */
    public String toString() {
        return "Time Series: " + getName();
    }

    /**
     * Set the given annotation as selected. The list is one of rangeFilters,
     * wayPoints or segments.
     *
     * @param annotation The annotation
     * @param list List its in
     * @param dontClear Dont clear the others in the list
     */
    public void setSelectedAnnotation(ChartAnnotation annotation, List list, boolean dontClear) {

        boolean anyWereWayPoints = false;
        if (!dontClear) {
            for (int i = 0; i < list.size(); i++) {
                ChartAnnotation ann = (ChartAnnotation) list.get(i);
                if (ann.getSelected()) {
                    anyWereWayPoints |= (ann instanceof WayPoint);
                }
                ann.setSelected(false);
            }
        }
        if (annotation != null) {
            anyWereWayPoints |= (annotation instanceof WayPoint);
            annotation.setSelected(true);
        }

        if (anyWereWayPoints) {
            try {
                setLocationPositions();
            } catch (Exception exc) {
                LogUtil.logException("Setting locations", exc);
            }
        }
    }

    /**
     * Remove all of the annotations in the list. The list is one of rangeFilters,
     * wayPoints or segments.
     *
     * @param annotations  The list of annotations
     */
    public void removeAnnotations(List annotations) {
        List tmp = new ArrayList(annotations);

        boolean didWaypoint = false;
        boolean didSegment = false;
        for (int i = 0; i < tmp.size(); i++) {
            ChartAnnotation annotation = (ChartAnnotation) tmp.get(i);
            if (annotation instanceof WayPoint) {
                didWaypoint = true;
                if (((WayPoint) annotation).getMinutesSpan() > 0) {
                    didSegment = true;
                }
            }
            if (annotation instanceof TrackSegment) {
                didSegment = true;
            }
            removeAnnotation(annotation);
        }

        if (didSegment) {
            firePropertyChange(PROP_TIMERANGE, null, segments);
        }

        try {
            if (didWaypoint) {
                setLocationPositions();
            }
        } catch (Exception exc) {
            LogUtil.logException("Error creating wayPoints", exc);
        }
    }

    /**
     * Add the annotations in the list. The list is one of rangeFilters,
     * wayPoints or segments.
     *
     * @param l List of annotations
     */
    private void addAnnotations(List l) {
        for (int i = 0; i < l.size(); i++) {
            plot.addAnnotation((ChartAnnotation) l.get(i));
        }
    }

    /**
     * Find the list that the given annotation belongs in. The list is one of rangeFilters,
     * wayPoints or segments.
     *
     * @param annotation The annotation
     *
     * @return Its list
     */
    private List getList(ChartAnnotation annotation) {
        if (annotation instanceof WayPoint) {
            return wayPoints;
        }
        if (annotation instanceof TrackSegment) {
            return segments;
        }
        return rangeFilters;
    }

    /**
     * Remove the annotation
     *
     * @param annotation The annotation
     */
    public void removeAnnotation(ChartAnnotation annotation) {
        annotation.doRemove();
        getList(annotation).remove(annotation);
        if (timeWayPoint == annotation) {
            setTimeWayPoint(null);
        }
        plot.removeAnnotation(annotation);
        if (annotation instanceof RangeFilter) {
            RangeFilter attached = ((RangeFilter) annotation).getAttached();
            if (attached != null) {
                removeAnnotation(attached);
            }
        }

        if (annotation instanceof WayPoint) {
            List tmp = new ArrayList(segments);
            for (int i = 0; i < tmp.size(); i++) {
                TrackSegment segment = (TrackSegment) tmp.get(i);
                if ((segment.getWayPoint1() == annotation) || (segment.getWayPoint2() == annotation)) {
                    removeAnnotation(segment);
                }
            }
        }
    }

    /**
     * Show the properties
     *
     * @param annotation The annotation
     */
    public void editAnnotation(ChartAnnotation annotation) {
        if (!annotation.showProperties(chartPanel, chartPanel.lastEventX, chartPanel.lastEventX)) {
            return;
        }

        signalChartChanged();

        if (annotation instanceof WayPoint) {
            try {
                setLocationPositions();
            } catch (Exception exc) {
                LogUtil.logException("Setting location positions", exc);
            }
        }
    }

    /**
     * Remove the selected annotations in the list.
     *
     * @param annotations  The list of annotations
     */
    public void removeSelectedAnnotations(List annotations) {
        removeAnnotations(getSelected(annotations));
    }

    /**
     * Callback method for receiving notification of a mouse click on a chart.
     *
     * @param event  information about the event.
     *
     * @return Did we handle this event
     */
    public boolean chartPanelMousePressed(MouseEvent event) {
        if (SwingUtilities.isRightMouseButton(event)) {
            closestAnnotation = findClosestAnnotation(getAllAnnotations(), event.getX(), event.getY(), false,
                    false);
        }
        return EVENT_PASSON;
    }

    /**
     * Callback method for receiving notification of a mouse click on a chart.
     *
     * @param event  information about the event.
     *
     * @return Did we handle this event
     */
    public boolean chartPanelMouseClicked(MouseEvent event) {
        if (SwingUtilities.isRightMouseButton(event)) {
            closestAnnotation = findClosestAnnotation(getAllAnnotations(), event.getX(), event.getY(), false,
                    false);
            return EVENT_PASSON;
        }

        if (event.getClickCount() <= 1) {
            ChartAnnotation annotation = findClosestAnnotation(getAllAnnotations(), event.getX(), event.getY(),
                    true, event.isShiftDown());
            if (annotation == null) {
                return false;
            }
            signalChartChanged();
            return EVENT_DONTPASSON;
        }

        try {
            if (isOnBottomDomainAxis(event)) {
                WayPoint waypoint = new WayPoint(getDomainValue(event.getX()), this);
                waypoint.addPropertyChangeListener(this);
                if (!waypoint.showProperties(chartPanel, event.getX(), event.getY())) {
                    return EVENT_DONTPASSON;
                }
                wayPoints.add(waypoint);
                setSelectedAnnotation(waypoint, wayPoints, event.isShiftDown());
                setLocationPositions();
                plot.addAnnotation(waypoint);
            } else if (isOnLeftRangeAxis(event)) {
                //                System.err.println("new x/y:" + event.getX() +"/" +event.getY());
                RangeFilter rangeFilter = new RangeFilter(getRangeValue(event.getY()), this);
                if (!rangeFilter.showProperties(chartPanel, event.getX(), event.getY())) {
                    return EVENT_DONTPASSON;
                }
                rangeFilters.add(rangeFilter);

                setSelectedAnnotation(rangeFilter, rangeFilters, event.isShiftDown());
                plot.addAnnotation(rangeFilter);

                if (true || event.isShiftDown()) {
                    RangeFilter attached = rangeFilter.doMakeAttached(event);
                    rangeFilters.add(attached);
                    plot.addAnnotation(attached);
                }
                rangeFiltersChanged();
            } else {
                return EVENT_PASSON;
            }
        } catch (Exception exc) {
            LogUtil.logException("Error creating wayPoints", exc);
        }
        return EVENT_DONTPASSON;
    }

    /**
     * Get the domain value of the x position
     *
     * @param x  The x position
     *
     * @return Domain value
     */
    public double getDomainValue(int x) {
        return plot.getDomainAxis().java2DToValue(x, getChartPanel().getScreenDataArea(), plot.getDomainAxisEdge());
    }

    /**
     * Get the range value of y
     *
     * @param y  The y position
     *
     * @return Range value
     */
    public double getRangeValue(int y) {
        return plot.getRangeAxis().java2DToValue(y, getChartPanel().getScreenDataArea(), plot.getRangeAxisEdge());
    }

    /**
     * Set the dots in the main display
     *
     * @throws RemoteException On badness
     * @throws VisADException On badness
     */
    private void setLocationPositions() throws VisADException, RemoteException {
        if (!showDots || (wayPoints.size() == 0)) {
            return;
        }
        if (dotsHolder == null) {
            dotsHolder = new CompositeDisplayable();
            getDisplayControl().addDisplayable(dotsHolder);
            if (!showDots) {
                dotsHolder.setVisible(false);
            }
        }
        dotsHolder.setDisplayInactive();
        while (dotsHolder.displayableCount() < wayPoints.size()) {
            PickableLineDrawing lineDrawing = new PickableLineDrawing("Line");
            lineDrawing.setColor(Color.blue);
            lineDrawing.setPointSize(5);
            dotsHolder.addDisplayable(lineDrawing);
        }
        while (dotsHolder.displayableCount() > wayPoints.size()) {
            dotsHolder.removeDisplayable(dotsHolder.lastDisplayable());
        }

        List locs = new ArrayList();
        for (int i = 0; i < wayPoints.size(); i++) {
            WayPoint waypoint = (WayPoint) wayPoints.get(i);
            PickableLineDrawing lineDrawing = (PickableLineDrawing) dotsHolder.getDisplayable(i);
            lineDrawing.setColor(waypoint.getColorToUse());
            int idx = findTimeIndex(waypoint.getDomainValue());
            if (idx >= 0) {
                EarthLocationTuple[] latLonsAlts = { new EarthLocationTuple(lats[idx], lons[idx], alts[idx]) };
                lineDrawing.setData(Util.indexedField(latLonsAlts, false));
            }
        }

        if (wayPoints.size() == 0) {
            dotsHolder.setVisible(false);
        } else {
            dotsHolder.setVisible(true);
        }

        dotsHolder.setDisplayActive();

    }

    /**
     * Create a list of time ranges from the segments
     *
     * @return List of ranges
     */
    public List getTimeRanges() {
        if (times == null) {
            return null;
        }
        List result = new ArrayList();
        for (int i = 0; i < segments.size(); i++) {
            TrackSegment segment = (TrackSegment) segments.get(i);
            result.add(new ucar.unidata.util.Range(segment.getLeft().getDomainValue(),
                    segment.getRight().getDomainValue()));
        }

        for (int i = 0; i < wayPoints.size(); i++) {
            WayPoint wayPoint = (WayPoint) wayPoints.get(i);
            double span = wayPoint.getMinutesSpan();
            if (span <= 0.0) {
                continue;
            }
            result.add(new ucar.unidata.util.Range(wayPoint.getDomainValue() - span * 30000.0,
                    wayPoint.getDomainValue() + span * 30000.0));
        }

        //        System.err.println("ranges:" + results);
        if (result.size() == 0) {
            return null;
        }
        return result;
    }

    /**
     * Animation in main display changed. Some charts show this
     *
     * @param time  the animation time
     */
    public void setTimeFromAnimation(Real time) {
        if (getShowTime()) {
            if (System.currentTimeMillis() - lastTimeWeDrove <= 1000) {
                return;
            }
        }
        setTime(time.getValue() * 1000, false);
    }

    /**
     * Set the time we're at
     *
     * @param value time
     * @param andDriveAnimation Set the time in the animation widget
     */
    protected void setTime(double value, boolean andDriveAnimation) {
        if (inSetTime) {
            return;
        }
        inSetTime = true;
        if (timeWayPoint == null) {
            setTimeWayPoint(new WayPoint(value, this));
            timeWayPoint.setIsForAnimation(true);
            timeWayPoint.setName("Time");
            wayPoints.add(timeWayPoint);
            plot.addAnnotation(timeWayPoint);
        }
        timeWayPoint.setDomainValue(value);
        ValueAxis axis = (ValueAxis) plot.getDomainAxis();
        axis.centerRange(value);
        firePropertyChange(PROP_TIMERANGE, null, segments);

        if (andDriveAnimation && getDriveTime() && (animationWidget != null)) {
            lastTimeWeDrove = System.currentTimeMillis();
            animationWidget.setTimeFromUser(new Real(RealType.Time, value / 1000));
        }
        inSetTime = false;
    }

    /**
     * Handle the event
     *
     * @param event the event
     */
    public void propertyChange(PropertyChangeEvent event) {
        if (event.getPropertyName().equals(WayPoint.PROP_WAYPOINTVALUE)) {
            WayPoint source = (WayPoint) event.getSource();
            if (source.canPlaySound()) {
                playSound(source);
            }
            if (getDriveTime() && (animationWidget != null)) {
                lastTimeWeDrove = System.currentTimeMillis();
                animationWidget.setTimeFromUser(new Real(RealType.Time, source.getDomainValue() / 1000));
            }
        } else if (event.getPropertyName().equals(PROP_SELECTEDTIME)) {
            Double dttm = (Double) event.getNewValue();
            if (dttm != null) {
                setTime(dttm.doubleValue(), true);
            }
        }
    }

    /**
     * Zoom the domain axis
     *
     * @param segment  The track segment
     */
    public void zoomTo(TrackSegment segment) {
        ValueAxis axis = (ValueAxis) plot.getDomainAxis();
        org.jfree.data.Range range = new org.jfree.data.Range(segment.getLeft().getDomainValue(),
                segment.getRight().getDomainValue());
        axis.setRange(range);
    }

    /**
     * Zoom the range axis
     *
     * @param rangeFilter  The range filter
     */
    public void zoomTo(RangeFilter rangeFilter) {
        RangeFilter other = rangeFilter.getAttached();
        double min = Math.min(other.getRangeValue(), rangeFilter.getRangeValue());
        double max = Math.max(other.getRangeValue(), rangeFilter.getRangeValue());
        ValueAxis axis = (ValueAxis) plot.getRangeAxis();
        org.jfree.data.Range range = new org.jfree.data.Range(min, max);
        axis.setRange(range);
    }

    /**
     * Center the domain axis about
     *
     * @param wayPoint  The way point
     */
    public void centerOn(WayPoint wayPoint) {
        ValueAxis axis = (ValueAxis) plot.getDomainAxis();
        axis.centerRange(wayPoint.getDomainValue());
    }

    /**
     * Center the range axis
     *
     * @param rangeFilter  The range filter
     */
    public void centerOn(RangeFilter rangeFilter) {
        ValueAxis axis = (ValueAxis) plot.getRangeAxis();
        axis.centerRange(rangeFilter.getRangeValue());

    }

    /**
     * Find the index in the times list of the given time
     *
     * @param value The time value
     *
     * @return The closest index
     */
    private int findTimeIndex(double value) {
        int idx = -1;
        if (times == null) {
            return -1;
        }
        double minDistance = 0;
        double f = 0.0;
        for (int i = 0; i < times.length; i++) {
            double distance = Math.abs(value - times[i]);
            if ((i == 0) || (distance < minDistance)) {
                f = times[i];
                minDistance = distance;
                idx = i;
            }
        }
        //      System.err.println(f+" distance:" + minDistance +" value:" + value +" idx:" + idx + " size:" + times.length);
        return idx;
    }

    /**
     * Handle event in chart
     *
     * @param event The event
     *
     * @return Did we handle this event
     */
    public boolean chartPanelMouseReleased(MouseEvent event) {

        if (SwingUtilities.isRightMouseButton(event)) {
            return EVENT_PASSON;
        }
        closestAnnotation = null;

        dragging = false;
        if (draggedAnnotation != null) {
            if ((draggedAnnotation instanceof WayPoint) || (draggedAnnotation instanceof TrackSegment)) {
                firePropertyChange(PROP_TIMERANGE, null, segments);
            }
            if (getDriveTime() && (animationWidget != null)) {
                if (draggedAnnotation instanceof WayPoint) {
                    lastTimeWeDrove = System.currentTimeMillis();
                    double value = ((WayPoint) draggedAnnotation).getDomainValue();
                    animationWidget.setTimeFromUser(new Real(RealType.Time, value / 1000));
                }
            }
        }
        draggedAnnotation = null;
        if (isOnAxis(event) && SwingUtilities.isRightMouseButton(event)) {
            return EVENT_DONTPASSON;
        }

        return EVENT_PASSON;
    }

    /**
     * Is mouse on an axis_
     *
     * @param mouseEvent the event
     *
     * @return On axis
     */
    private boolean isOnAxis(MouseEvent mouseEvent) {
        return isOnBottomDomainAxis(mouseEvent) || isOnTopDomainAxis(mouseEvent) || isOnLeftRangeAxis(mouseEvent);
    }

    /**
     * Is mouse on bottom axis
     *
     * @param mouseEvent the event
     *
     * @return on bottom axis
     */
    private boolean isOnBottomDomainAxis(MouseEvent mouseEvent) {
        Rectangle2D dataArea = chartPanel.getScreenDataArea();
        if (mouseEvent.getX() < dataArea.getX()) {
            return false;
        }
        if (mouseEvent.getX() > dataArea.getX() + dataArea.getWidth()) {
            return false;
        }

        double bottom = dataArea.getY() + dataArea.getHeight();
        return mouseEvent.getY() >= bottom - 20;
    }

    /**
     * Is mouse on top axis
     *
     * @param mouseEvent the event
     *
     * @return On top axis
     */
    private boolean isOnTopDomainAxis(MouseEvent mouseEvent) {
        Rectangle2D dataArea = chartPanel.getScreenDataArea();
        if (mouseEvent.getX() < dataArea.getX()) {
            return false;
        }
        if (mouseEvent.getX() > dataArea.getX() + dataArea.getWidth()) {
            return false;
        }
        double top = dataArea.getY();
        return mouseEvent.getY() <= top + 20;
    }

    /**
     * On left axis
     *
     * @param mouseEvent the event
     *
     * @return on left axis
     */
    private boolean isOnLeftRangeAxis(MouseEvent mouseEvent) {
        Rectangle2D dataArea = chartPanel.getScreenDataArea();
        if (mouseEvent.getY() < dataArea.getY()) {
            return false;
        }
        if (mouseEvent.getY() > dataArea.getY() + dataArea.getHeight()) {
            return false;
        }
        //        System.err.println("mouse:" + mouseEvent.getX() +" da:" + dataArea);

        double left = dataArea.getX();
        if (mouseEvent.getX() < left) {
            return true;
        }
        if (mouseEvent.getX() > left + 20) {
            return false;
        }
        return true;
    }

    /**
     * Add to the properties components
     *
     * @param comps  List of components
     * @param tabIdx Which tab in the properties dialog
     */
    protected void getPropertiesComponents(List comps, int tabIdx) {
        super.getPropertiesComponents(comps, tabIdx);
        if (tabIdx != 1) {
            return;
        }
        showDotsCbx = new JCheckBox("", showDots);
        comps.add(GuiUtils.rLabel("Show Dots: "));
        comps.add(GuiUtils.left(showDotsCbx));
    }

    /**
     * Apply properties
     *
     *
     * @return Was successful
     */
    protected boolean applyProperties() {
        if (!super.applyProperties()) {
            return false;
        }
        setShowDots(showDotsCbx.isSelected());

        return true;
    }

    /**
     * The annotaiton has changed. If its a WayPoint then fire the timerange property
     * change.
     *
     * @param chartAnnotation The annotation that changed
     */
    public void annotationChanged(ChartAnnotation chartAnnotation) {
        super.annotationChanged(chartAnnotation);
        if (chartAnnotation instanceof WayPoint) {
            firePropertyChange(PROP_TIMERANGE, null, segments);
        }
    }

    /**
     * Add a segment between the 2 waypoints
     *
     * @param wps 2 waypoints
     */
    public void addSegment(WayPoint[] wps) {
        TrackSegment segment = new TrackSegment(wps[0], wps[1], this);
        if (!segment.showProperties(chartPanel, chartPanel.lastEventX, chartPanel.lastEventX)) {
            return;
        }

        segments.add(segment);
        plot.addAnnotation(segment);
    }

    /**
     * Is it ok to draw the annotation
     *
     * @param annotation The annotation
     *
     * @return ok to draw
     */
    public boolean okToDraw(ChartAnnotation annotation) {
        return times != null;
    }

    /**
     * Get list of selected annotations
     *
     * @param annotations  The list of annotations
     *
     * @return selected annotations in list
     */
    public List getSelected(List annotations) {
        List selected = new ArrayList();
        for (int i = 0; i < annotations.size(); i++) {
            ChartAnnotation annotation = (ChartAnnotation) annotations.get(i);
            if (annotation.getSelected()) {
                selected.add(annotation);
            }
        }
        return selected;
    }

    /**
     * Get the popup menu items
     *
     * @param items list to add to
     *
     * @return the list with items from this
     */
    protected List getPopupMenuItems(List items) {

        List selectedWaypoints = getSelected(wayPoints);

        if (closestAnnotation != null) {
            items.add(GuiUtils.makeMenuItem(
                    "Edit " + closestAnnotation.getTypeName() + ": " + closestAnnotation.getName(), this,
                    "editAnnotation", closestAnnotation));
            items.add(GuiUtils.makeMenuItem(
                    "Remove " + closestAnnotation.getTypeName() + ": " + closestAnnotation.getName(), this,
                    "removeAnnotations", Misc.newList(closestAnnotation)));
            if (closestAnnotation instanceof TrackSegment) {
                items.add(GuiUtils.makeMenuItem("Zoom Domain Axis To", this, "zoomTo", closestAnnotation));

            }

            if (closestAnnotation instanceof WayPoint) {
                items.add(GuiUtils.makeMenuItem("Center Domain Axis To", this, "centerOn", closestAnnotation));

            }
            if (closestAnnotation instanceof RangeFilter) {
                if (((RangeFilter) closestAnnotation).getAttached() != null) {
                    items.add(GuiUtils.makeMenuItem("Zoom Range Axis To", this, "zoomTo", closestAnnotation));
                }
                items.add(GuiUtils.makeMenuItem("Center Range Axis To", this, "centerOn", closestAnnotation));

            }

            items.add(GuiUtils.MENU_SEPARATOR);
        }

        if (selectedWaypoints.size() == 2) {
            items.add(GuiUtils.makeMenuItem("Add Segment", this, "addSegment",
                    new WayPoint[] { (WayPoint) selectedWaypoints.get(0), (WayPoint) selectedWaypoints.get(1) }));
            items.add(GuiUtils.MENU_SEPARATOR);
        }

        JMenu markers = null;
        if (wayPoints.size() != 0) {
            if (markers == null) {
                markers = new JMenu("Markers");
                items.add(markers);
            }
            JMenu menu = new JMenu("Way Points");
            markers.add(menu);
            menu.add(GuiUtils.makeMenuItem("Remove All Way Points", this, "removeAnnotations", wayPoints));
            if (selectedWaypoints.size() > 0) {
                menu.add(GuiUtils.makeMenuItem("Remove Selected Way Points", this, "removeSelectedAnnotations",
                        wayPoints));
            }

            JMenu editMenu = new JMenu("Edit Way Points");
            menu.add(editMenu);
            for (int i = 0; i < wayPoints.size(); i++) {
                WayPoint waypoint = (WayPoint) wayPoints.get(i);
                editMenu.add(GuiUtils.makeMenuItem("#" + (i + 1) + " " + waypoint.getName() + " ", this,
                        "editAnnotation", waypoint));
            }

        }

        if (segments.size() != 0) {
            if (markers == null) {
                markers = new JMenu("Markers");
                items.add(markers);
            }
            JMenu menu = new JMenu("Segments");
            markers.add(menu);
            menu.add(GuiUtils.makeMenuItem("Remove All Segments", this, "removeAnnotations", segments));
            List selected = getSelected(segments);
            if (selected.size() > 0) {
                menu.add(GuiUtils.makeMenuItem("Remove Selected Segments", this, "removeSelectedAnnotations",
                        segments));
            }

            JMenu editMenu = new JMenu("Edit Segments");
            menu.add(editMenu);
            for (int i = 0; i < segments.size(); i++) {
                TrackSegment segment = (TrackSegment) segments.get(i);
                editMenu.add(GuiUtils.makeMenuItem("#" + (i + 1) + " " + segment.getName() + " ", this,
                        "editAnnotation", segment));
            }

        }

        if (rangeFilters.size() != 0) {
            if (markers == null) {
                markers = new JMenu("Markers");
                items.add(markers);
            }
            JMenu menu = new JMenu("Range Filters");
            markers.add(menu);
            menu.add(GuiUtils.makeMenuItem("Remove All Range Filters", this, "removeAnnotations", rangeFilters));

            List selected = getSelected(rangeFilters);
            if (selected.size() > 0) {
                menu.add(GuiUtils.makeMenuItem("Remove Selected Range Filters", this, "removeSelectedAnnotations",
                        rangeFilters));
            }

            JMenu editMenu = new JMenu("Edit Range Filters");
            menu.add(editMenu);
            for (int i = 0; i < rangeFilters.size(); i++) {
                ChartAnnotation annotation = (ChartAnnotation) rangeFilters.get(i);
                editMenu.add(GuiUtils.makeMenuItem("#" + (i + 1) + " " + annotation.getName() + " ", this,
                        "editAnnotation", annotation));
            }
        }

        return super.getPopupMenuItems(items);
    }

    /**
     * Find the closest annotation
     *
     * @param list List of annotations
     * @param x  The x position
     * @param y  The y position
     * @param andSetSelected If true set the closest as selected
     * @param addToSelected If true add to selected. Else remove selection set (if andSetSelected
     * is true.
     *
     * @return Closest annotation or null
     */
    public ChartAnnotation findClosestAnnotation(List list, int x, int y, boolean andSetSelected,
            boolean addToSelected) {
        double minDistance = 20;
        ChartAnnotation closest = null;
        for (int i = 0; i < list.size(); i++) {
            ChartAnnotation annotation = (ChartAnnotation) list.get(i);
            double distance = annotation.distance(x, y);
            if (distance < minDistance) {
                minDistance = distance;
                closest = annotation;
            }
        }
        if (andSetSelected) {
            setSelectedAnnotation(closest, list, addToSelected);
        }
        return closest;
    }

    /**
     * We can set the time in the main display
     *
     * @return can set the time in the main display
     */
    protected boolean canDoDriveTime() {
        return true;
    }

    /**
     * Can this chart use time selects
     * This is used to determine whether the checkbox should be shown
     * in the menus
     *
     * @return     Can this chart use time select
     */
    protected boolean canDoTimeSelect() {
        return true;
    }

    /**
     * The timeseries can have jython applied to it
     *
     * @return Can this chart have jython applied to it
     */
    protected boolean canDoJython() {
        return true;
    }

    /**
     * Add the state of this chart to the interpreter
     *
     * @param interpreter The interpreter to initialize
     */
    protected void initializeJython(PythonInterpreter interpreter) {
        super.initializeJython(interpreter);
        interpreter.set("waypoints", wayPoints);
        interpreter.set("segments", segments);
        interpreter.set("rangefilters", rangeFilters);
        interpreter.set("rangefilters", rangeFilters);
    }

    /**
     * Can we do the data area colors in the properties dialog
     *
     * @return can do colors
     */
    protected boolean canDoColors() {
        return true;
    }

    /**
     * Can we add time subsetting to this chart
     *
     * @return false
     */
    protected boolean canDoTimeFilters() {
        return false;
    }

    /**
     * Show colors in fields properties
     *
     * @return can do colors
     */
    public boolean canDoWrapperColor() {
        return true;
    }

    /**
     *  SHow side menu in fields properties
     *
     * @return can do sides
     */
    public boolean canDoWrapperSide() {
        return true;
    }

    //TODO: Fix the range filters to work with multiple range axis

    /**
     * Are the values ok to render
     *
     *
     * @param dataset Which data set
     * @param domainValue The domain value
     * @param rangeValue The range value
     *
     * @return show values
     */
    public boolean valuesOk(int dataset, double domainValue, double rangeValue) {
        return rangeValueOk(dataset, rangeValue);
    }

    /**
     * Get the List of range values for the given dataset
     *
     * @param dataset The data set
     *
     * @return List of range values
     */
    public List getRangeValues(int dataset) {
        List l = new ArrayList();
        for (int i = 0; i < rangeFilters.size(); i++) {
            RangeFilter rangeFilter = (RangeFilter) rangeFilters.get(i);
            l.add(rangeFilter);
        }
        return l;
    }

    /**
     * Does the value apss the range filters
     *
     *
     * @param dataset  Which dataset
     * @param value The range value
     *
     * @return show values
     */
    public boolean rangeValueOk(int dataset, double value) {
        for (int i = 0; i < rangeFilters.size(); i++) {
            if (!((RangeFilter) rangeFilters.get(i)).valueOk(value)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Get all of the annotations from the different lists
     *
     * @return all annotations
     */
    private List getAllAnnotations() {
        List all = new ArrayList(wayPoints);
        all.addAll(segments);
        all.addAll(rangeFilters);
        return all;
    }

    /**
     * Handle event in chart
     *
     * @param event The event
     *
     * @return Did we handle this event
     */
    public boolean chartPanelMouseDragged(MouseEvent event) {

        if (SwingUtilities.isRightMouseButton(event)) {
            return EVENT_DONTPASSON;
        }

        closestAnnotation = null;
        //Ignore shift down drag.
        if (event.isShiftDown()) {
            return EVENT_PASSON;
        }

        if (dragging && (draggedAnnotation == null)) {
            return EVENT_PASSON;
        }
        dragging = true;
        if (draggedAnnotation == null) {
            draggedAnnotation = findClosestAnnotation(getAllAnnotations(), event.getX(), event.getY(), true,
                    event.isShiftDown());
        }
        if (draggedAnnotation == null) {
            return EVENT_PASSON;
        }

        draggedAnnotation.setPosition(event);
        if (draggedAnnotation instanceof RangeFilter) {
            rangeFiltersChanged();
        }
        if ((draggedAnnotation instanceof WayPoint) || (draggedAnnotation instanceof TrackSegment)) {
            Rectangle2D r = getChartPanel().getScreenDataArea();
            if (event.getX() < r.getX()) {
                panPlot(false, 0.02);
            } else if (event.getX() > r.getX() + r.getWidth()) {
                panPlot(true, 0.02);
            }

        }

        signalChartChanged();
        try {
            setLocationPositions();
        } catch (Exception exc) {
            LogUtil.logException("Setting locations", exc);
        }
        return EVENT_DONTPASSON;

    }

    /**
     * Play the sound for the waypoint if it is enabled
     *
     * @param wp The waypoint to play a sound with
     */
    private void playSound(WayPoint wp) {
        if (!wp.canPlaySound()) {
            return;
        }
        MidiManager midiManager = wp.getMidiManager();
        MidiProperties mp = wp.getMidiProperties();
        int idx = findTimeIndex(wp.getDomainValue());
        double[] darray = (double[]) datas.get(0);
        double d = darray[idx];
        if (!rangeValueOk(0, d)) {
            return;
        }
        ucar.unidata.util.Range range = (ucar.unidata.util.Range) ranges.get(0);
        int note = (int) (mp.getLowNote() + (range.getPercent(d) * (mp.getHighNote() - mp.getLowNote())));
        //      System.err.println ("note:" + note);
        midiManager.play(note, 500);
    }

    /**
     * Get the tool tip text
     *
     * @param event The event
     *
     * @return tool tip
     */
    public String chartPanelGetToolTipText(MouseEvent event) {
        ChartAnnotation annotation = findClosestAnnotation(getAllAnnotations(), event.getX(), event.getY(), false,
                false);
        if (annotation == null) {
            return null;
        }
        return annotation.getToolTipText();
    }

    /**
     * Range filters changed
     */
    public void rangeFiltersChanged() {
        if ((rangeFilters.size() == 0) || (dataChoiceWrappers.size() == 0)) {
            return;
        }
        DataChoice dataChoice = ((DataChoiceWrapper) dataChoiceWrappers.get(0)).getDataChoice();
        double min = Double.NEGATIVE_INFINITY;
        double max = Double.POSITIVE_INFINITY;
        int greaterThanCnt = 0;
        int lessThanCnt = 0;

        for (int i = 0; i < rangeFilters.size(); i++) {
            RangeFilter rangeFilter = (RangeFilter) rangeFilters.get(i);
            if (rangeFilter.getType() == RangeFilter.TYPE_LESSTHAN) {
                if (lessThanCnt == 0) {
                    max = rangeFilter.getRangeValue();
                } else {
                    max = Math.max(max, rangeFilter.getRangeValue());
                }
                lessThanCnt++;
            } else {
                if (greaterThanCnt == 0) {
                    min = rangeFilter.getRangeValue();
                } else {
                    min = Math.min(min, rangeFilter.getRangeValue());
                }
                greaterThanCnt++;
            }
        }
        getDisplayControl().doShare(DisplayControlImpl.SHARE_SELECTRANGE,
                new Object[] { dataChoice, new ucar.unidata.util.Range(min, max) });
    }

    /**
     * Clear out list of annotations
     *
     * @param l list
     */
    private void clearAnnotations(List l) {
        for (int i = 0; i < l.size(); i++) {
            ChartAnnotation anno = (ChartAnnotation) l.get(i);
            anno.doRemove();
        }
        l.clear();
    }

    /**
     * remove me
     */
    public void doRemove() {
        super.doRemove();

        clearAnnotations(segments);
        clearAnnotations(wayPoints);
        clearAnnotations(rangeFilters);

        firePropertyChange(PROP_TIMERANGE, null, segments);

        if ((rangeFilters.size() == 0) || (dataChoiceWrappers.size() == 0)) {
            return;
        }
        DataChoice dataChoice = ((DataChoiceWrapper) dataChoiceWrappers.get(0)).getDataChoice();

        getDisplayControl().doShare(DisplayControlImpl.SHARE_SELECTRANGE, new Object[] { dataChoice,
                new ucar.unidata.util.Range(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY) });
    }

    /**
     * Do final initialization
     */
    public void initDone() {
        super.initDone();
        Misc.runInABit(2000, this, "rangeFiltersChanged", null);
    }

    /**
     *  Set the WayPoints property.
     *
     *  @param value The new value for WayPoints
     */
    public void setWayPoints(List value) {
        wayPoints = value;
    }

    /**
     *  Get the WayPoints property.
     *
     *  @return The WayPoints
     */
    public List getWayPoints() {
        return wayPoints;
    }

    /**
     * Set the Segments property.
     *
     * @param value The new value for Segments
     */
    public void setSegments(List value) {
        segments = value;
    }

    /**
     * Get the Segments property.
     *
     * @return The Segments
     */
    public List getSegments() {
        return segments;
    }

    /**
     * Set the RangeFilters property.
     *
     * @param value The new value for RangeFilters
     */
    public void setRangeFilters(List value) {
        rangeFilters = value;
    }

    /**
     * Get the RangeFilters property.
     *
     * @return The RangeFilters
     */
    public List getRangeFilters() {
        return rangeFilters;
    }

    /**
     * Set the TimeWayPoint property.
     *
     * @param value The new value for TimeWayPoint
     */
    public void setTimeWayPoint(WayPoint value) {
        if (timeWayPoint != null) {
            timeWayPoint.removePropertyChangeListener(this);
        }
        timeWayPoint = value;
        if (timeWayPoint != null) {
            timeWayPoint.addPropertyChangeListener(this);
        }
    }

    /**
     * Get the TimeWayPoint property.
     *
     * @return The TimeWayPoint
     */
    public WayPoint getTimeWayPoint() {
        return timeWayPoint;
    }

    /**
     * Set the ShowDots property.
     *
     * @param value The new value for ShowDots
     */
    public void setShowDots(boolean value) {
        if (value == showDots) {
            return;
        }
        showDots = value;
        if (dotsHolder != null) {
            try {
                dotsHolder.setVisible(value);
                setLocationPositions();
            } catch (Exception exc) {
                LogUtil.logException("Setting locations", exc);
            }
        }
    }

    /**
     * Get the ShowDots property.
     *
     * @return The ShowDots
     */
    public boolean getShowDots() {
        return showDots;
    }

}