001    /*
002     * $Id: McIDASVHistogramWrapper.java,v 1.24 2012/02/19 17:35:37 davep Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
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    
031    package edu.wisc.ssec.mcidasv.control;
032    
033    import java.awt.Color;
034    import java.awt.event.ActionEvent;
035    import java.awt.event.ActionListener;
036    import java.rmi.RemoteException;
037    import java.util.ArrayList;
038    import java.util.Hashtable;
039    import java.util.List;
040    
041    import javax.swing.JCheckBox;
042    import javax.swing.JComponent;
043    import javax.swing.JLabel;
044    import javax.swing.JMenu;
045    import javax.swing.JMenuItem;
046    import javax.swing.JTextField;
047    
048    import org.jfree.chart.ChartFactory;
049    import org.jfree.chart.ChartPanel;
050    import org.jfree.chart.axis.NumberAxis;
051    import org.jfree.chart.axis.ValueAxis;
052    import org.jfree.chart.event.AxisChangeEvent;
053    import org.jfree.chart.event.AxisChangeListener;
054    import org.jfree.chart.plot.PlotOrientation;
055    import org.jfree.chart.plot.XYPlot;
056    import org.jfree.chart.renderer.xy.StackedXYBarRenderer;
057    import org.jfree.chart.renderer.xy.XYBarRenderer;
058    import org.jfree.chart.renderer.xy.XYItemRenderer;
059    import org.jfree.data.Range;
060    import org.jfree.data.statistics.HistogramType;
061    import org.jfree.util.Log;
062    import org.slf4j.Logger;
063    import org.slf4j.LoggerFactory;
064    
065    import visad.FlatField;
066    import visad.Unit;
067    import visad.VisADException;
068    
069    import ucar.unidata.data.DataCancelException;
070    import ucar.unidata.data.DataChoice;
071    import ucar.unidata.idv.DisplayControl;
072    import ucar.unidata.idv.control.DisplayControlImpl;
073    import ucar.unidata.idv.control.chart.ChartWrapper;
074    import ucar.unidata.idv.control.chart.DataChoiceWrapper;
075    import ucar.unidata.idv.control.chart.HistogramWrapper;
076    import ucar.unidata.idv.control.chart.MyHistogramDataset;
077    import ucar.unidata.idv.control.multi.DisplayGroup;
078    import ucar.unidata.idv.ui.ImageSequenceGrabber;
079    import ucar.unidata.util.GuiUtils;
080    import ucar.unidata.util.LogUtil;
081    import ucar.unidata.util.Misc;
082    
083    /**
084     * Wraps a JFreeChart histograms to ease working with VisAD data.
085     */
086    public 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