001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2015
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.control;
030
031import java.awt.Color;
032import java.awt.event.ActionEvent;
033import java.awt.event.ActionListener;
034import java.rmi.RemoteException;
035import java.util.ArrayList;
036import java.util.Hashtable;
037import java.util.List;
038
039import javax.swing.JComponent;
040import javax.swing.JMenuItem;
041
042import org.jfree.chart.ChartFactory;
043import org.jfree.chart.ChartPanel;
044import org.jfree.chart.axis.NumberAxis;
045import org.jfree.chart.axis.ValueAxis;
046import org.jfree.chart.event.AxisChangeEvent;
047import org.jfree.chart.event.AxisChangeListener;
048import org.jfree.chart.plot.PlotOrientation;
049import org.jfree.chart.plot.XYPlot;
050import org.jfree.chart.renderer.xy.StackedXYBarRenderer;
051import org.jfree.chart.renderer.xy.XYBarRenderer;
052import org.jfree.chart.renderer.xy.XYItemRenderer;
053import org.jfree.data.Range;
054import org.jfree.data.statistics.HistogramType;
055
056import org.slf4j.Logger;
057import org.slf4j.LoggerFactory;
058
059import ucar.unidata.data.DataChoice;
060import ucar.unidata.idv.DisplayControl;
061import ucar.unidata.idv.control.DisplayControlImpl;
062import ucar.unidata.idv.control.chart.DataChoiceWrapper;
063import ucar.unidata.idv.control.chart.HistogramWrapper;
064import ucar.unidata.idv.control.chart.MyHistogramDataset;
065import ucar.unidata.idv.control.multi.DisplayGroup;
066import ucar.unidata.util.GuiUtils;
067import ucar.unidata.util.LogUtil;
068
069import visad.ErrorEstimate;
070import visad.FieldImpl;
071import visad.FlatField;
072import visad.Unit;
073import visad.VisADException;
074
075/**
076 * Wraps a JFreeChart histogram to ease working with VisAD data.
077 */
078public class McIDASVHistogramWrapper extends HistogramWrapper {
079
080    private static final Logger logger = LoggerFactory.getLogger(McIDASVHistogramWrapper.class);
081
082    private DisplayControl imageControl;
083
084    /** The plot */
085    private XYPlot plot;
086
087    private double low;
088
089    private double high;
090
091    /**
092     * Default ctor
093     */
094    public McIDASVHistogramWrapper() {}
095
096    /**
097     * Ctor
098     *
099     * @param name The name.
100     * @param dataChoices List of data choices.
101     * @param control {@literal "Parent"} control.
102     */
103    public McIDASVHistogramWrapper(String name, List dataChoices, DisplayControlImpl control) {
104        super(name, dataChoices);
105        imageControl = control;
106    }
107
108    /**
109     * Create the chart
110     */
111    private void createChart() {
112        if (chartPanel != null) {
113            return;
114        }
115
116        MyHistogramDataset dataset = new MyHistogramDataset();
117        chart = ChartFactory.createHistogram("Histogram", null, null,
118                                             dataset,
119                                             PlotOrientation.VERTICAL, false,
120                                             false, false);
121        chart.getXYPlot().setForegroundAlpha(0.75f);
122        plot = (XYPlot) chart.getPlot();
123        initXYPlot(plot);
124        chartPanel = doMakeChartPanel(chart);
125    }
126
127    public JComponent doMakeContents() {
128        return super.doMakeContents();
129    }
130
131    /**
132     * Create the charts
133     *
134     * @throws RemoteException On badness
135     * @throws VisADException On badness
136     */
137    public void loadData(FlatField data) throws VisADException, RemoteException {
138        createChart();
139        List dataChoiceWrappers = getDataChoiceWrappers();
140        try {
141            for (int dataSetIdx = 0; dataSetIdx < plot.getDatasetCount(); dataSetIdx++) {
142                MyHistogramDataset dataset = (MyHistogramDataset)plot.getDataset(dataSetIdx);
143                dataset.removeAllSeries();
144            }
145
146            Hashtable props = new Hashtable();
147            ErrorEstimate[] errOut = new ErrorEstimate[1];
148            for (int paramIdx = 0; paramIdx < dataChoiceWrappers.size(); paramIdx++) {
149                DataChoiceWrapper wrapper = (DataChoiceWrapper)dataChoiceWrappers.get(paramIdx);
150
151                DataChoice dataChoice = wrapper.getDataChoice();
152                props = dataChoice.getProperties();
153                Unit defaultUnit = ucar.visad.Util.getDefaultRangeUnits((FlatField) data)[0];
154                Unit unit = ((DisplayControlImpl)imageControl).getDisplayUnit();
155                double[][] samples = data.getValues(false);
156                double[] actualValues = filterData(samples[0], getTimeValues(samples, data))[0];
157                if ((defaultUnit != null) && !defaultUnit.equals(unit)) {
158                    actualValues = Unit.transformUnits(unit, errOut, defaultUnit, null, actualValues);
159                }
160                final NumberAxis domainAxis = new NumberAxis(wrapper.getLabel(unit));
161
162                domainAxis.setAutoRangeIncludesZero(false);
163
164                XYItemRenderer renderer;
165                if (getStacked()) {
166                    renderer = new StackedXYBarRenderer();
167                } else {
168                    renderer = new XYBarRenderer();
169                }
170                plot.setRenderer(paramIdx, renderer);
171                Color c = wrapper.getColor(paramIdx);
172                domainAxis.setLabelPaint(c);
173                renderer.setSeriesPaint(0, c);
174
175                MyHistogramDataset dataset = new MyHistogramDataset();
176                dataset.setType(HistogramType.FREQUENCY);
177                dataset.addSeries(dataChoice.getName() + " [" + unit + "]",
178                    actualValues, getBins());
179                samples = null;
180                actualValues = null;
181                plot.setDomainAxis(paramIdx, domainAxis, false);
182                plot.mapDatasetToDomainAxis(paramIdx, paramIdx);
183                plot.setDataset(paramIdx, dataset);
184
185                domainAxis.addChangeListener(new AxisChangeListener() {
186                    public void axisChanged(AxisChangeEvent ae) {
187                        if (!imageControl.isInitDone()) {
188                            return;
189                        }
190
191                        Range range = domainAxis.getRange();
192                        double newLow = Math.floor(range.getLowerBound()+0.5);
193                        double newHigh = Math.floor(range.getUpperBound()+0.5);
194                        double prevLow = getLow();
195                        double prevHigh = getHigh();
196                        try {
197                            ucar.unidata.util.Range newRange;
198                            if (prevLow > prevHigh) {
199                                newRange = new ucar.unidata.util.Range(newHigh, newLow);
200                            } else {
201                                newRange = new ucar.unidata.util.Range(newLow, newHigh);
202                            }
203                            ((DisplayControlImpl) imageControl).setRange(newRange);
204                        } catch (Exception e) {
205                            System.out.println("Can't set new range e=" + e);
206                        }
207                    }
208                });
209
210                Range range = domainAxis.getRange();
211                low = range.getLowerBound();
212                high = range.getUpperBound();
213            }
214
215        } catch (Exception exc) {
216            System.out.println("Exception exc=" + exc);
217            LogUtil.logException("Error creating data set", exc);
218            return;
219        }
220    }
221
222    /**
223     * Modify the low and high values of the domain axis.
224     *
225     * @param lowVal Low value.
226     * @param hiVal High value.
227     *
228     * @return {@code false} if {@link #plot} is {@code null}. {@code true}
229     * otherwise.
230     */
231    protected boolean modifyRange(double lowVal, double hiVal) {
232        return modifyRange(lowVal, hiVal, true);
233    }
234
235    /**
236     * Modify the low and high values of the domain axis.
237     *
238     * @param lowVal Low value.
239     * @param hiVal High value.
240     * @param notify Whether or not listeners should be notified.
241     *
242     * @return {@code false} if {@link #plot} is {@code null}. {@code true}
243     * otherwise.
244     */
245    protected boolean modifyRange(double lowVal, double hiVal, boolean notify) {
246        try {
247            if (plot == null) {
248                return false;
249            }
250            ValueAxis domainAxis = plot.getDomainAxis();
251            org.jfree.data.Range newRange = new org.jfree.data.Range(lowVal, hiVal);
252            domainAxis.setRange(newRange, domainAxis.isAutoRange(), notify);
253            return true;
254        } catch (Exception e) {
255            return true;
256        }
257    }
258
259    protected Range getRange() {
260        ValueAxis domainAxis = plot.getDomainAxis();
261        return domainAxis.getRange();
262    }
263
264    protected void doReset() {
265        resetPlot();
266    }
267
268    /**
269     * reset the histogram to its previous range
270     */
271    public void resetPlot() {
272        if (chart == null) {
273            return;
274        }
275        if (!(chart.getPlot() instanceof XYPlot)) {
276            return;
277        }
278        XYPlot plot = (XYPlot) chart.getPlot();
279        int    rcnt = plot.getRangeAxisCount();
280        for (int i = 0; i < rcnt; i++) {
281            ValueAxis axis = plot.getRangeAxis(i);
282            axis.setAutoRange(true);
283        }
284        int dcnt = plot.getDomainAxisCount();
285        for (int i = 0; i < dcnt; i++) {
286            ValueAxis axis = plot.getDomainAxis(i);
287            try {
288                axis.setRange(low, high);
289            } catch (Exception e) {
290                logger.warn("jfreechart does not like ranges to be high -> low", e);
291            }
292        }
293    }
294
295    public double getLow() {
296        return low;
297    }
298
299    public void setLow(double val) {
300        low = val;
301    }
302
303    public double getHigh() {
304        return high;
305    }
306
307    public void setHigh(double val) {
308        high = val;
309    }
310
311    /**
312     * SHow the popup menu
313     *
314     * @param where component to show near to
315     * @param x x
316     * @param y y
317     */
318    @Override public void showPopup(JComponent where, int x, int y) {
319        List items = new ArrayList();
320        items = getPopupMenuItems(items);
321        if (items.isEmpty()) {
322            return;
323        }
324        GuiUtils.makePopupMenu(items).show(where, x, y);
325    }
326
327    /**
328     * Add the default menu items
329     * 
330     * @param items List of menu items
331     * 
332     * @return The items list
333     */
334    @Override protected List getPopupMenuItems(List items) {
335        items = super.getPopupMenuItems(items);
336        for (Object o : items) {
337            if (o instanceof JMenuItem) {
338                JMenuItem menuItem = (JMenuItem)o;
339                if ("Properties...".equals(menuItem.getText())) {
340                    menuItem.setActionCommand(ChartPanel.PROPERTIES_COMMAND);
341                    menuItem.addActionListener(buildHistoPropsListener());
342                }
343            }
344        }
345        return items;
346    }
347
348    /**
349     * @return {@link ActionListener} that listens for
350     * {@link ChartPanel#PROPERTIES_COMMAND} events and shows the histogram
351     * properties.
352     */
353    private ActionListener buildHistoPropsListener() {
354        return new ActionListener() {
355            @Override public void actionPerformed(ActionEvent event) {
356                String command = event.getActionCommand();
357                if (ChartPanel.PROPERTIES_COMMAND.equals(command)) {
358                    McIDASVHistogramWrapper.this.showProperties();
359                    return;
360                }
361            }
362        };
363    }
364
365    /**
366     * Show the properties dialog
367     *
368     * @return Was it ok
369     */
370    @Override public boolean showProperties() {
371        boolean result;
372        if (!hasDisplayControl()) {
373            result = showProperties(null, 0, 0);
374        } else {
375            result = super.showProperties();
376        }
377        return result;
378    }
379
380    public boolean hasDisplayControl() {
381        return getDisplayControl() != null;
382    }
383
384    /**
385     * Remove me
386     *
387     * @return was removed
388     */
389    public boolean removeDisplayComponent() {
390        if (GuiUtils.askYesNo("Remove Display",
391                              "Are you sure you want to remove: "
392                              + toString())) {
393            DisplayGroup displayGroup = getDisplayGroup();
394            if (displayGroup != null) {
395                displayGroup.removeDisplayComponent(this);
396            }
397
398            if (hasDisplayControl()) {
399                getDisplayControl().removeDisplayComponent(this);
400            }
401            return true;
402        } else {
403            return false;
404        }
405    }
406
407    /**
408     * Apply the properties
409     *
410     * @return Success
411     */
412    @Override protected boolean doApplyProperties() {
413        applyProperties();
414        
415//        try {
416//            // need to deal with the data being an imageseq 
417//            loadData((FlatField)imageControl.getDataChoice().getData(null));
418//        } catch (RemoteException e) {
419//            logger.error("trying to reload data", e);
420//        } catch (DataCancelException e) {
421//            logger.error("trying to reload data", e);
422//        } catch (VisADException e) {
423//            logger.error("trying to reload data", e);
424//        }
425
426        return true;
427    }
428
429    /**
430     * Been removed, do any cleanup
431     */
432    public void doRemove() {
433        isRemoved = true;
434        List displayables = getDisplayables();
435        if (hasDisplayControl() && !displayables.isEmpty()) {
436            getDisplayControl().removeDisplayables(displayables);
437        }
438        firePropertyChange(PROP_REMOVED, null, this);
439    }
440}
441