001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2017
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.JComboBox;
040import javax.swing.JComponent;
041import javax.swing.JMenuItem;
042
043import org.jfree.chart.ChartFactory;
044import org.jfree.chart.ChartPanel;
045import org.jfree.chart.axis.NumberAxis;
046import org.jfree.chart.axis.ValueAxis;
047import org.jfree.chart.event.AxisChangeEvent;
048import org.jfree.chart.event.AxisChangeListener;
049import org.jfree.chart.plot.PlotOrientation;
050import org.jfree.chart.plot.XYPlot;
051import org.jfree.chart.renderer.xy.StackedXYBarRenderer;
052import org.jfree.chart.renderer.xy.XYBarRenderer;
053import org.jfree.chart.renderer.xy.XYItemRenderer;
054import org.jfree.data.Range;
055import org.jfree.data.statistics.HistogramType;
056
057import org.slf4j.Logger;
058import org.slf4j.LoggerFactory;
059
060import ucar.unidata.data.DataChoice;
061import ucar.unidata.data.grid.GridUtil;
062import ucar.unidata.idv.DisplayControl;
063import ucar.unidata.idv.control.DisplayControlImpl;
064import ucar.unidata.idv.control.chart.DataChoiceWrapper;
065import ucar.unidata.idv.control.chart.HistogramWrapper;
066import ucar.unidata.idv.control.chart.MyHistogramDataset;
067import ucar.unidata.idv.control.multi.DisplayGroup;
068import ucar.unidata.ui.ImageUtils;
069import ucar.unidata.util.FileManager;
070import ucar.unidata.util.GuiUtils;
071import ucar.unidata.util.LogUtil;
072
073import visad.ErrorEstimate;
074import visad.FieldImpl;
075import visad.FlatField;
076import visad.Unit;
077import visad.VisADException;
078
079/**
080 * Wraps a JFreeChart histogram to ease working with VisAD data.
081 *
082 * @deprecated Use {@link ucar.unidata.idv.control.McVHistogramWrapper}.
083 */
084public class McIDASVHistogramWrapper extends HistogramWrapper {
085
086    private static final Logger logger = LoggerFactory.getLogger(McIDASVHistogramWrapper.class);
087
088    private DisplayControl imageControl;
089
090    /** The plot */
091    private XYPlot plot;
092
093    private double low;
094
095    private double high;
096
097    /**
098     * Default ctor
099     */
100    public McIDASVHistogramWrapper() {}
101
102    /**
103     * Ctor
104     *
105     * @param name The name.
106     * @param dataChoices List of data choices.
107     * @param control {@literal "Parent"} control.
108     */
109    public McIDASVHistogramWrapper(String name, List dataChoices, DisplayControlImpl control) {
110        super(name, dataChoices);
111        imageControl = control;
112    }
113
114    /**
115     * Create the chart.
116     */
117    private void createChart() {
118        if (chartPanel != null) {
119            return;
120        }
121
122        MyHistogramDataset dataset = new MyHistogramDataset();
123        chart = ChartFactory.createHistogram("Histogram", null, null,
124                                             dataset,
125                                             PlotOrientation.VERTICAL, false,
126                                             false, false);
127        chart.getXYPlot().setForegroundAlpha(0.75f);
128        plot = (XYPlot) chart.getPlot();
129        initXYPlot(plot);
130        chartPanel = doMakeChartPanel(chart);
131    }
132
133    public JComponent doMakeContents() {
134        return super.doMakeContents();
135    }
136
137    /**
138     * Clear the histogram.
139     */
140    public void clearHistogram() {
141        if (chartPanel != null) {
142            XYPlot tempPlot = chartPanel.getChart().getXYPlot();
143            for (int i = 0; i < tempPlot.getDatasetCount(); i++) {
144                MyHistogramDataset dataset =
145                        (MyHistogramDataset)tempPlot.getDataset(i);
146                if (dataset != null) {
147                    dataset.removeAllSeries();
148                }
149            }
150        }
151    }
152
153    /**
154     * _more_
155     */
156    public void saveImage() {
157        JComboBox publishCbx =
158            imageControl.getViewManager().getPublishManager().getSelector(
159                "nc.export");
160        String filename = FileManager.getWriteFile(FileManager.FILTER_IMAGE,
161                              FileManager.SUFFIX_JPG, ((publishCbx != null)
162                ? GuiUtils.top(publishCbx)
163                : null));
164        if (filename == null) {
165            return;
166        }
167        try {
168            ImageUtils.writeImageToFile(getContents(), filename);
169            imageControl.getViewManager().getPublishManager().publishContent(
170                filename, null, publishCbx);
171        } catch (Exception exc) {
172            LogUtil.logException("Capturing image", exc);
173        }
174    }
175
176    /**
177     * Create the histogram.
178     *
179     * @param data Data to use in histogram.
180     *
181     * @throws IllegalArgumentException if {@code data} is all NaNs.
182     * @throws RemoteException On badness
183     * @throws VisADException On badness
184     */
185    public void loadData(FlatField data) throws IllegalArgumentException, RemoteException, VisADException {
186        if ((data != null) && !GridUtil.isAllMissing(data)) {
187            reallyLoadData(data);
188        } else {
189            throw new IllegalArgumentException("Nothing to show in histogram");
190        }
191     }
192
193    /**
194     * Assumes that {@code data} has been validated and is okay to actually try
195     * loading.
196     *
197     * @param data Data to use in histogram. Cannot be {@code null} or all NaNs.
198     *
199     * @throws VisADException
200     * @throws RemoteException
201     */
202    private void reallyLoadData(FlatField data) throws VisADException, RemoteException {
203        createChart();
204        List dataChoiceWrappers = getDataChoiceWrappers();
205
206        try {
207            clearHistogram();
208
209            Hashtable props = new Hashtable();
210            ErrorEstimate[] errOut = new ErrorEstimate[1];
211            for (int paramIdx = 0; paramIdx < dataChoiceWrappers.size(); paramIdx++) {
212                DataChoiceWrapper wrapper = (DataChoiceWrapper)dataChoiceWrappers.get(paramIdx);
213
214                DataChoice dataChoice = wrapper.getDataChoice();
215                props = dataChoice.getProperties();
216                Unit defaultUnit = ucar.visad.Util.getDefaultRangeUnits((FlatField) data)[0];
217                Unit unit = ((DisplayControlImpl)imageControl).getDisplayUnit();
218                double[][] samples = data.getValues(false);
219                double[] actualValues = filterData(samples[0], getTimeValues(samples, data))[0];
220                if ((defaultUnit != null) && !defaultUnit.equals(unit)) {
221                    actualValues = Unit.transformUnits(unit, errOut, defaultUnit, null, actualValues);
222                }
223                final NumberAxis domainAxis = new NumberAxis(wrapper.getLabel(unit));
224
225                domainAxis.setAutoRangeIncludesZero(false);
226
227                XYItemRenderer renderer;
228                if (getStacked()) {
229                    renderer = new StackedXYBarRenderer();
230                } else {
231                    renderer = new XYBarRenderer();
232                }
233                if ((plot == null) && (chartPanel != null)) {
234                    plot = chartPanel.getChart().getXYPlot();
235                }
236                plot.setRenderer(paramIdx, renderer);
237                Color c = wrapper.getColor(paramIdx);
238                domainAxis.setLabelPaint(c);
239                renderer.setSeriesPaint(0, c);
240
241                MyHistogramDataset dataset = new MyHistogramDataset();
242                dataset.setType(HistogramType.FREQUENCY);
243                dataset.addSeries(dataChoice.getName() + " [" + unit + ']',
244                    actualValues, getBins());
245                samples = null;
246                actualValues = null;
247                plot.setDomainAxis(paramIdx, domainAxis, false);
248                plot.mapDatasetToDomainAxis(paramIdx, paramIdx);
249                plot.setDataset(paramIdx, dataset);
250
251                domainAxis.addChangeListener(new AxisChangeListener() {
252                    public void axisChanged(AxisChangeEvent ae) {
253                        if (!imageControl.isInitDone()) {
254                            return;
255                        }
256
257                        Range range = domainAxis.getRange();
258                        double newLow = Math.floor(range.getLowerBound()+0.5);
259                        double newHigh = Math.floor(range.getUpperBound()+0.5);
260                        double prevLow = getLow();
261                        double prevHigh = getHigh();
262                        try {
263                            ucar.unidata.util.Range newRange;
264                            if (prevLow > prevHigh) {
265                                newRange = new ucar.unidata.util.Range(newHigh, newLow);
266                            } else {
267                                newRange = new ucar.unidata.util.Range(newLow, newHigh);
268                            }
269                            ((DisplayControlImpl) imageControl).setRange(newRange);
270                        } catch (Exception e) {
271                            logger.error("Cannot change range", e);
272                        }
273                    }
274                });
275
276                Range range = domainAxis.getRange();
277                low = range.getLowerBound();
278                high = range.getUpperBound();
279            }
280
281        } catch (Exception exc) {
282            System.out.println("Exception exc=" + exc);
283            LogUtil.logException("Error creating data set", exc);
284        }
285    }
286
287    /**
288     * Modify the low and high values of the domain axis.
289     *
290     * @param lowVal Low value.
291     * @param hiVal High value.
292     *
293     * @return {@code false} if {@link #plot} is {@code null}. {@code true}
294     * otherwise.
295     */
296    protected boolean modifyRange(double lowVal, double hiVal) {
297        return modifyRange(lowVal, hiVal, true);
298    }
299
300    /**
301     * Modify the low and high values of the domain axis.
302     *
303     * @param lowVal Low value.
304     * @param hiVal High value.
305     * @param notify Whether or not listeners should be notified.
306     *
307     * @return {@code false} if {@link #plot} is {@code null}. {@code true}
308     * otherwise.
309     */
310    protected boolean modifyRange(double lowVal, double hiVal, boolean notify) {
311        try {
312            if (plot == null) {
313                return false;
314            }
315            ValueAxis domainAxis = plot.getDomainAxis();
316            org.jfree.data.Range newRange = new org.jfree.data.Range(lowVal, hiVal);
317            domainAxis.setRange(newRange, domainAxis.isAutoRange(), notify);
318            return true;
319        } catch (Exception e) {
320            return true;
321        }
322    }
323
324    protected Range getRange() {
325        ValueAxis domainAxis = plot.getDomainAxis();
326        return domainAxis.getRange();
327    }
328
329    protected void doReset() {
330        resetPlot();
331    }
332
333    /**
334     * reset the histogram to its previous range
335     */
336    public void resetPlot() {
337        if (chart == null) {
338            return;
339        }
340        if (!(chart.getPlot() instanceof XYPlot)) {
341            return;
342        }
343        XYPlot plot = (XYPlot) chart.getPlot();
344        int    rcnt = plot.getRangeAxisCount();
345        for (int i = 0; i < rcnt; i++) {
346            ValueAxis axis = plot.getRangeAxis(i);
347            axis.setAutoRange(true);
348        }
349        int dcnt = plot.getDomainAxisCount();
350        for (int i = 0; i < dcnt; i++) {
351            ValueAxis axis = plot.getDomainAxis(i);
352            try {
353                axis.setRange(low, high);
354            } catch (Exception e) {
355                logger.warn("jfreechart does not like ranges to be high -> low", e);
356            }
357        }
358    }
359
360    public double getLow() {
361        return low;
362    }
363
364    public void setLow(double val) {
365        low = val;
366    }
367
368    public double getHigh() {
369        return high;
370    }
371
372    public void setHigh(double val) {
373        high = val;
374    }
375
376    /**
377     * SHow the popup menu
378     *
379     * @param where component to show near to
380     * @param x x
381     * @param y y
382     */
383    @Override public void showPopup(JComponent where, int x, int y) {
384        List items = new ArrayList();
385        items = getPopupMenuItems(items);
386        if (items.isEmpty()) {
387            return;
388        }
389        GuiUtils.makePopupMenu(items).show(where, x, y);
390    }
391
392    /**
393     * Add the default menu items
394     * 
395     * @param items List of menu items
396     * 
397     * @return The items list
398     */
399    @Override protected List getPopupMenuItems(List items) {
400        items = super.getPopupMenuItems(items);
401        for (Object o : items) {
402            if (o instanceof JMenuItem) {
403                JMenuItem menuItem = (JMenuItem)o;
404                if ("Properties...".equals(menuItem.getText())) {
405                    menuItem.setActionCommand(ChartPanel.PROPERTIES_COMMAND);
406                    menuItem.addActionListener(buildHistoPropsListener());
407                }
408            }
409        }
410        return items;
411    }
412
413    /**
414     * @return {@link ActionListener} that listens for
415     * {@link ChartPanel#PROPERTIES_COMMAND} events and shows the histogram
416     * properties.
417     */
418    private ActionListener buildHistoPropsListener() {
419        return new ActionListener() {
420            @Override public void actionPerformed(ActionEvent event) {
421                String command = event.getActionCommand();
422                if (ChartPanel.PROPERTIES_COMMAND.equals(command)) {
423                    McIDASVHistogramWrapper.this.showProperties();
424                    return;
425                }
426            }
427        };
428    }
429
430    /**
431     * Show the properties dialog
432     *
433     * @return Was it ok
434     */
435    @Override public boolean showProperties() {
436        boolean result;
437        if (!hasDisplayControl()) {
438            result = showProperties(null, 0, 0);
439        } else {
440            result = super.showProperties();
441        }
442        return result;
443    }
444
445    public boolean hasDisplayControl() {
446        return getDisplayControl() != null;
447    }
448
449    /**
450     * Remove me
451     *
452     * @return was removed
453     */
454    public boolean removeDisplayComponent() {
455        if (GuiUtils.askYesNo("Remove Display",
456                              "Are you sure you want to remove: "
457                              + toString())) {
458            DisplayGroup displayGroup = getDisplayGroup();
459            if (displayGroup != null) {
460                displayGroup.removeDisplayComponent(this);
461            }
462
463            if (hasDisplayControl()) {
464                getDisplayControl().removeDisplayComponent(this);
465            }
466            return true;
467        } else {
468            return false;
469        }
470    }
471
472    /**
473     * Apply the properties
474     *
475     * @return Success
476     */
477    @Override protected boolean doApplyProperties() {
478        applyProperties();
479        
480//        try {
481//            // need to deal with the data being an imageseq 
482//            loadData((FlatField)imageControl.getDataChoice().getData(null));
483//        } catch (RemoteException e) {
484//            logger.error("trying to reload data", e);
485//        } catch (DataCancelException e) {
486//            logger.error("trying to reload data", e);
487//        } catch (VisADException e) {
488//            logger.error("trying to reload data", e);
489//        }
490
491        return true;
492    }
493
494    /**
495     * Been removed, do any cleanup
496     */
497    public void doRemove() {
498        isRemoved = true;
499        List displayables = getDisplayables();
500        if (hasDisplayControl() && !displayables.isEmpty()) {
501            getDisplayControl().removeDisplayables(displayables);
502        }
503        firePropertyChange(PROP_REMOVED, null, this);
504    }
505}
506