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