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