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