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