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