PhaseAndMeanPlotPane.java

/**
 * VStar: a statistical analysis tool for variable star data.
 * Copyright (C) 2009  AAVSO (http://www.aavso.org/)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */
package org.aavso.tools.vstar.ui.pane.plot;

import java.awt.Dimension;
import java.awt.geom.Point2D;

import org.aavso.tools.vstar.data.ValidObservation;
import org.aavso.tools.vstar.input.AbstractObservationRetriever;
import org.aavso.tools.vstar.ui.mediator.AnalysisType;
import org.aavso.tools.vstar.ui.mediator.Mediator;
import org.aavso.tools.vstar.ui.mediator.ViewModeType;
import org.aavso.tools.vstar.ui.mediator.message.NewStarMessage;
import org.aavso.tools.vstar.ui.mediator.message.ObservationSelectionMessage;
import org.aavso.tools.vstar.ui.mediator.message.PanRequestMessage;
import org.aavso.tools.vstar.ui.mediator.message.SeriesVisibilityChangeMessage;
import org.aavso.tools.vstar.ui.mediator.message.ZoomRequestMessage;
import org.aavso.tools.vstar.ui.model.plot.ObservationAndMeanPlotModel;
import org.aavso.tools.vstar.ui.model.plot.PhasedObservationAndMeanPlotModel;
import org.aavso.tools.vstar.util.locale.LocaleProps;
import org.aavso.tools.vstar.util.notification.Listener;
import org.aavso.tools.vstar.util.prefs.NumericPrecisionPrefs;
import org.aavso.tools.vstar.util.stats.BinningResult;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.general.Dataset;

/**
 * This class represents a chart pane containing a phase plot for a set of valid
 * observations along with mean-based data.
 */
@SuppressWarnings("serial")
public class PhaseAndMeanPlotPane extends ObservationAndMeanPlotPane {

    public static String PHASE = LocaleProps.get("PHASE");

    private double epoch;
    private double period;

    private String xyMsgFormat;

    private ObservationAndMeanPlotModel[] obsAndMeanModels;

    // Did the last selection correspond to a standard phase domain value?
    // The alternative is a previous cycle phase selection, or null, meaning
    // no selection.
    private Dataset previousCyclePhaseModel;
    private Dataset standardPhaseModel;
    private Boolean wasLastSelectionStdPhase;

    /**
     * Constructor.
     * 
     * @param title            The title for the chart.
     * @param subTitle         The sub-title for the chart.
     * @param bounds           The bounding box to which to set the chart's
     *                         preferred size.
     * @param epoch            The starting JD for the current phase plot.
     * @param period           The period for the current phase plot.
     * @param retriever        The observation retriever for observations in this
     *                         plot.
     * @param obsAndMeanModels The data models to plot.
     */
    public PhaseAndMeanPlotPane(String title, String subTitle, Dimension bounds, double epoch, double period,
            AbstractObservationRetriever retriever, PhasedObservationAndMeanPlotModel... obsAndMeanModels) {

        super(title, subTitle, PHASE, getBrightnessAxisLabel(retriever), obsAndMeanModels[0], bounds, retriever,
                AnalysisType.PHASE_PLOT);

        this.epoch = epoch;
        this.period = period;

        this.obsAndMeanModels = obsAndMeanModels;

        this.wasLastSelectionStdPhase = null;

        xyMsgFormat = "%s, %s";

        this.chart.getXYPlot().setDataset(1, obsAndMeanModels[1]);

        previousCyclePhaseModel = this.chart.getXYPlot().getDataset(0);
        standardPhaseModel = this.chart.getXYPlot().getDataset(1);

        // setSeriesVisibility();
    }

    /**
     * @return the epoch
     */
    public double getEpoch() {
        return epoch;
    }

    /**
     * @return the period
     */
    public double getPeriod() {
        return period;
    }

    /**
     * @return the obsAndMeanModels
     */
    public ObservationAndMeanPlotModel[] getObsModels() {
        return obsAndMeanModels;
    }

    /**
     * @return the wasLastSelectionStdPhase
     */
    public Boolean wasLastSelectionStdPhase() {
        return wasLastSelectionStdPhase;
    }

    /**
     * @param meanSourceSeriesNum the meanSourceSeriesNum to set
     */
    public void setMeanSourceSeriesNum(int meanSourceSeriesNum) {
        for (ObservationAndMeanPlotModel obsModel : obsAndMeanModels) {
            obsModel.setMeanSourceSeriesNum(meanSourceSeriesNum);
        }
    }

    /**
     * Attempt to create a new mean series with the specified number of time
     * elements per bin.
     * 
     * @param timeElementsInBin The number of days or phase steps to be created per
     *                          bin.
     * @return Whether or not the series was changed.
     */
    public boolean changeMeansSeries(double timeElementsInBin) {
        boolean changed = false;

        for (ObservationAndMeanPlotModel obsModel : obsAndMeanModels) {
            changed |= obsModel.changeMeansSeries(timeElementsInBin);
        }

        return changed;
    }

    // @Override
    // protected void setSeriesVisibility() {
    //
    // // super.setSeriesVisibility();
    //
    // if (obsAndMeanModels != null) {
    // for (ObservationAndMeanPlotModel obsModel : obsAndMeanModels) {
    // Map<SeriesType, Boolean> seriesVisibilityMap = obsModel
    // .getSeriesVisibilityMap();
    //
    // // taken from abstract plot pane... necessary?
    // for (SeriesType seriesType : seriesVisibilityMap.keySet()) {
    // int seriesNum = obsModel.getSrcTypeToSeriesNumMap().get(
    // seriesType);
    // renderer.setSeriesVisible(seriesNum, seriesVisibilityMap
    // .get(seriesType));
    // }
    //
    // boolean isModelFuncVisible = seriesVisibilityMap
    // .get(SeriesType.ModelFunction);
    //
    // if (isModelFuncVisible && obsModel.getModelFunction() != null) {
    // ContinuousModelPlotModel modelFuncModel = new ContinuousModelPlotModel(
    // obsModel.getModelFunction());
    //
    // JFreeChart modelFuncPlot = ChartFactory.createXYLineChart(
    // "", "", "", modelFuncModel,
    // PlotOrientation.VERTICAL, false, false, false);
    //
    // int modelFuncSeriesNum = obsModel
    // .getSrcTypeToSeriesNumMap().get(
    // SeriesType.ModelFunction);
    //
    // chart.getXYPlot().setDataset(modelFuncSeriesNum,
    // modelFuncModel);
    // chart.getXYPlot().setRenderer(modelFuncSeriesNum,
    // modelFuncPlot.getXYPlot().getRenderer());
    // Color color = SeriesType
    // .getColorFromSeries(SeriesType.ModelFunction);
    // chart.getXYPlot().getRenderer(modelFuncSeriesNum)
    // .setSeriesPaint(modelFuncSeriesNum, color);
    // chart.getXYPlot().getRenderer(modelFuncSeriesNum)
    // .setSeriesVisible(modelFuncSeriesNum, true);
    // }
    // }
    // }
    // }

    // From ChartMouseListener interface.
    // If the mouse is over a data point, set its tool-tip with phase and
    // magnitude.
    public void chartMouseMoved(ChartMouseEvent event) {
        ChartEntity entity = event.getEntity();
        if (entity instanceof XYItemEntity) {
            XYItemEntity item = (XYItemEntity) entity;
            ValidObservation ob = obsModel.getValidObservation(item.getSeriesIndex(), item.getItem());
            String xyMsg = String.format(xyMsgFormat, NumericPrecisionPrefs.formatTime(ob.getStandardPhase()),
                    NumericPrecisionPrefs.formatMag(ob.getMag()));
            item.setToolTipText(xyMsg);
        }
    }

    // Returns a series visibility change listener to update the chart legends
    // when the set of visible series changes.
    protected Listener<SeriesVisibilityChangeMessage> createSeriesVisibilityChangeListener() {
        return new Listener<SeriesVisibilityChangeMessage>() {
            @Override
            public void update(SeriesVisibilityChangeMessage info) {
                // Nothing to do apparently.
            }

            @Override
            public boolean canBeRemoved() {
                return true;
            }
        };
    }

    // Returns an observation selection listener.
    protected Listener<ObservationSelectionMessage> createObservationSelectionListener() {
        return new Listener<ObservationSelectionMessage>() {

            public void update(ObservationSelectionMessage message) {
                // Move the cross hairs if we have phase information since
                // this plot's domain is phase.
                if (message.getSource() != this && message.getObservation().getStandardPhase() != null) {
                    chart.getXYPlot().setDomainCrosshairValue(message.getObservation().getStandardPhase());
                    chart.getXYPlot().setRangeCrosshairValue(message.getObservation().getMag());

                    updateSelectionFromObservation(message.getObservation());
                }
            }

            public boolean canBeRemoved() {
                return true;
            }
        };
    }

    /**
     * @see org.aavso.tools.vstar.ui.pane.plot.AbstractObservationPlotPane#updateSelectionFromObservation(org.aavso.tools.vstar.data.ValidObservation)
     */
    @Override
    protected void updateSelectionFromObservation(ValidObservation ob) {
        super.updateSelectionFromObservation(ob);
        // We assume that the last selected dataset has been set before the
        // observation selection message was sent.
        wasLastSelectionStdPhase = getLastDatasetSelected() == standardPhaseModel;
    }

    // Returns a zoom request listener.
    protected Listener<ZoomRequestMessage> createZoomRequestListener() {
        return new Listener<ZoomRequestMessage>() {
            public void update(ZoomRequestMessage info) {
                if (Mediator.getInstance().getAnalysisType() == AnalysisType.PHASE_PLOT
                        && Mediator.getInstance().getViewMode() == ViewModeType.PLOT_OBS_MODE) {
                    doZoom(info.getZoomType());
                }
            }

            public boolean canBeRemoved() {
                return true;
            }
        };
    }

    // Returns a pan request listener.
    protected Listener<PanRequestMessage> createPanRequestListener() {
        return new Listener<PanRequestMessage>() {
            @Override
            public void update(PanRequestMessage msg) {
                final PlotRenderingInfo plotInfo = chartPanel.getChartRenderingInfo().getPlotInfo();

                final Point2D source = new Point2D.Double(0, 0);

                double percentage = 0.01;

                XYPlot plot = chart.getXYPlot();
                NewStarMessage newStarMsg = Mediator.getInstance().getLatestNewStarMessage();

                switch (msg.getPanType()) {
                case LEFT:
                    if (plot.getDomainAxis().getLowerBound() >= -1) {
                        plot.panDomainAxes(-percentage, plotInfo, source);
                    }
                    break;
                case RIGHT:
                    if (plot.getDomainAxis().getUpperBound() <= 1) {
                        plot.panDomainAxes(percentage, plotInfo, source);
                    }
                    break;
                case UP:
                    if (newStarMsg.getMinMag() <= plot.getRangeAxis().getLowerBound()) {
                        plot.panRangeAxes(percentage, plotInfo, source);
                    }
                    break;
                case DOWN:
                    if (newStarMsg.getMaxMag() >= plot.getRangeAxis().getUpperBound()) {
                        plot.panRangeAxes(-percentage, plotInfo, source);
                    }
                    break;
                }
            }

            @Override
            public boolean canBeRemoved() {
                return true;
            }
        };
    }

    // Returns a mean observation change (binning result) listener.
    protected Listener<BinningResult> createBinChangeListener() {
        return new Listener<BinningResult>() {
            public void update(BinningResult info) {
                // Do nothing. We may want to show ANOVA here, but I need to
                // understand whether this is useful for a phase plot by reading
                // more and talking with Grant et al.
            }

            public boolean canBeRemoved() {
                return true;
            }
        };
    }

    protected void updateAnovaSubtitle(BinningResult binningResult) {
        // Do nothing. See createBinChangeListener().
    }
}