Java tutorial
/* * This file is part of McIDAS-V * * Copyright 2007-2019 * Space Science and Engineering Center (SSEC) * University of Wisconsin - Madison * 1225 W. Dayton Street, Madison, WI 53706, USA * http://www.ssec.wisc.edu/mcidas * * All Rights Reserved * * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and * some McIDAS-V source code is based on IDV and VisAD source code. * * McIDAS-V is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * McIDAS-V is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser Public License for more details. * * You should have received a copy of the GNU Lesser Public License * along with this program. If not, see http://www.gnu.org/licenses. */ package ucar.unidata.idv.control.chart; import static ucar.unidata.idv.IdvPreferenceManager.DATE_FORMATS; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import javax.swing.ImageIcon; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.JTextField; import org.jfree.chart.axis.LogarithmicAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.event.ChartChangeEvent; import org.jfree.chart.plot.Plot; import ucar.unidata.idv.control.DisplayControlImpl; import ucar.unidata.ui.GraphPaperLayout; import ucar.unidata.ui.ImageUtils; import ucar.unidata.util.FileManager; import ucar.unidata.util.GuiUtils; import ucar.unidata.util.LogUtil; import ucar.unidata.util.Misc; import ucar.unidata.util.Range; import ucar.unidata.xml.XmlObjectStore; import visad.Real; /** * A time series chart * * @author MetApps Development Team * @version $Revision: 1.16 $ */ public abstract class ChartManager implements ImageObserver { /** chart layout type */ public static final int LAYOUT_HSTACK = 0; /** chart layout type */ public static final int LAYOUT_VSTACK = 1; /** chart layout type */ public static final int LAYOUT_2COLGRID = 2; /** chart layout type */ public static final int LAYOUT_3COLGRID = 3; /** chart layout type */ public static final int LAYOUT_GRAPH = 4; /** chart layout type */ public static final int LAYOUT_TAB = 6; /** chart layout type */ public static final int LAYOUT_CARD = 7; /** width of axis_ */ public static final int AXIS_WIDTH = 30; /** for synching_ */ protected Object MUTEX = new Object(); /** chart layout */ private int layout = LAYOUT_GRAPH; /** for chart layout */ private int gridLayoutDimension = Integer.MAX_VALUE; /** the charts */ protected List<ChartHolder> chartHolders = new ArrayList<ChartHolder>(); /** my control */ protected DisplayControlImpl control; /** gui_ */ protected JComponent contents; /** for gui */ private JTabbedPane tab = null; /** for gui */ private CardLayout cardLayout; /** for gui */ private JComponent cardContainer; /** currently setting data in chart_ */ protected boolean settingData = false; /** ok to draw charts */ protected boolean okToDraw = true; /** List of all fixed ranges. Keep this around for the line state dialogs to show a menu of ranges */ private List currentRanges = new ArrayList(); /** The line states_ */ private List currentLineStates = new ArrayList(); /** default chart */ private String defaultChartName; /** side legend thumbnail */ private JLabel chartThumb; /** flag for showing the thumbnail */ private boolean showThumb = false; /** will we be updating the thumbnail image */ private boolean thumbUpdatePending = false; /** * ctor */ public ChartManager() { } /** * ctor * * @param control the control */ public ChartManager(DisplayControlImpl control) { this.control = control; } /** * Default constructor. * * @param control the control * @param chartName name of default chart */ public ChartManager(DisplayControlImpl control, String chartName) { this(control); this.defaultChartName = chartName; } /** * add line state * * @param lineState line state */ protected void addLineState(LineState lineState) { currentLineStates.add(lineState); } /** * Save the image */ public void saveImage() { JComboBox publishCbx = control.getIdv().getPublishManager().getSelector("nc.export"); String filename = FileManager.getWriteFile(FileManager.FILTER_IMAGE, FileManager.SUFFIX_JPG, ((publishCbx != null) ? GuiUtils.top(publishCbx) : null)); if (filename == null) { return; } try { ImageUtils.writeImageToFile(getContents(), filename); control.getIdv().getPublishManager().publishContent(filename, null, publishCbx); } catch (Exception exc) { LogUtil.logException("Capturing image", exc); } } /** * clear all saved line states */ protected void clearLineStates() { currentLineStates = new ArrayList(); } /** * The chart name has changed * * @param oldName old name * @param newName new name */ protected void chartNameChanged(String oldName, String newName) { if (currentLineStates == null) { return; } for (int i = 0; i < currentLineStates.size(); i++) { LineState lineState = (LineState) currentLineStates.get(i); if (Misc.equals(lineState.getChartName(), oldName)) { lineState.setChartName(newName); } } } /** * The chart name has changed * * @param newName new name */ public void setChartName(String newName) { if (currentLineStates == null) { return; } for (int i = 0; i < currentLineStates.size(); i++) { LineState lineState = (LineState) currentLineStates.get(i); lineState.setChartName(newName); } } /** * init all charts */ protected void initCharts() { currentRanges = new ArrayList(); for (int plotIdx = 0; plotIdx < chartHolders.size(); plotIdx++) { initChart((ChartHolder) chartHolders.get(plotIdx)); } } /** * get the ranges * * @return ranges */ public List getCurrentRanges() { return currentRanges; } /** * add a range * * @param min min * @param max max * @param name name */ protected void addRange(double min, double max, String name) { if (!(min == min)) { return; } if (!(max == max)) { return; } min = Misc.parseNumber(control.getDisplayConventions().format(min)); max = Misc.parseNumber(control.getDisplayConventions().format(max)); currentRanges.add(new Range(min, max, name)); } /** * for synching the charts * * @return mutex */ protected Object getMutex() { return MUTEX; } /** * init chart * * @param chartHolder The chart */ protected void initChart(ChartHolder chartHolder) { synchronized (getMutex()) { chartHolder.initChart(); initPlot(chartHolder.getPlot()); } } /** * init plot * * @param plot plot */ protected void initPlot(Plot plot) { } /** * factory method to make the plot * * * @return the new plot */ public abstract Plot doMakePlot(); /** * Get the plots * * @return plots_ */ protected List getPlots() { List plots = new ArrayList(); for (ChartHolder chartHolder : chartHolders) { plots.add(chartHolder.getPlot()); } return plots; } /** * update thumbnail legend image * */ public void updateThumb() { updateThumb(false); } /** * update the thumbnail * * @param force if true then always update the thumb */ private void updateThumb(boolean force) { if (force) { updateThumbInner(); return; } if (thumbUpdatePending) { return; } thumbUpdatePending = true; //In 500 ms update the thumbnail image Misc.runInABit(500, this, "updateThumbInner", null); } /** * actually update the thumbnail image */ public void updateThumbInner() { try { if (!showThumb) { return; } // if (settingData) return; if ((getContents().getWidth() == 0) || (getContents().getHeight() == 0)) { // Misc.runInABit(1000, this, "updateThumb",null); return; } List images = new ArrayList(); Image thumb = ImageUtils.getImage(getContents()); if (thumb == null) { return; } if ((thumb.getWidth(null) <= 0) || (thumb.getHeight(null) <= 0)) { return; } double ratio = thumb.getWidth(null) / (double) thumb.getHeight(null); // int width = Math.max(label.getWidth(),200); int width = 200; int height = (int) (width / ratio); thumb = ImageUtils.toBufferedImage(thumb.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING), BufferedImage.TYPE_INT_RGB); boolean chartsShowingSomething = false; for (ChartHolder chartHolder : chartHolders) { if (chartHolder.getBeingShown() && chartHolder.hasParameters()) { chartsShowingSomething = true; break; } } if (chartsShowingSomething) { getThumb().setIcon(new ImageIcon(thumb)); } else { getThumb().setIcon(GuiUtils.getImageIcon("/auxdata/ui/icons/OnePixel.gif")); } } catch (Exception exc) { // LogUtil.logException("Showing thumbnail", exc); } thumbUpdatePending = false; } /** * Class FixedWidthLogarithmicAxis fixed width log axis * * * @author IDV Development Team * @version $Revision: 1.16 $ */ public static class FixedWidthLogarithmicAxis extends LogarithmicAxis { /** * ctor */ public FixedWidthLogarithmicAxis() { this(null); } /** * ctor * * @param theLabel label */ public FixedWidthLogarithmicAxis(String theLabel) { super(theLabel); setFixedDimension(AXIS_WIDTH); } /** * utility * * @param ticks param * @param g2 param * @param drawArea param * @param vertical param * * @return width */ protected double findMaximumTickLabelWidth(List ticks, Graphics2D g2, Rectangle2D drawArea, boolean vertical) { return AXIS_WIDTH; } } /** * Class FixedWidthNumberAxis fixed width axis * * * @author IDV Development Team * @version $Revision: 1.16 $ */ public static class FixedWidthNumberAxis extends NumberAxis { /** * ctor */ public FixedWidthNumberAxis() { this(null); } /** * ctor * * @param theLabel label */ public FixedWidthNumberAxis(String theLabel) { super(theLabel); setFixedDimension(AXIS_WIDTH); } /** * get width * * @param ticks param * @param g2 param * @param drawArea param * @param vertical param * * @return width */ protected double findMaximumTickLabelWidth(List ticks, Graphics2D g2, Rectangle2D drawArea, boolean vertical) { return AXIS_WIDTH; } } /** * Tell the chart holds that something changed */ public void signalChartChanged() { for (ChartHolder chartHolder : chartHolders) { if (chartHolder.getChartPanel() != null) { chartHolder.getChartPanel().chartChanged(new ChartChangeEvent(this)); } } } /** * refresh chart holders */ private void setRefresh() { for (ChartHolder chartHolder : chartHolders) { chartHolder.getChartPanel().setRefreshBuffer(true); } } /** * Call this after you are done loading data into the charts. It resets the flag * to ignore any intermediate chart repaints and then it signals the chart to repaint */ protected void doneLoadingData() { settingData = false; signalChartChanged(); updateThumb(false); } /** * Call this before you start loading data into the charts. It sets a flag * to ignore any intermediate chart repaints */ protected void startLoadingData() { settingData = true; } /** * get menu items for chart * * @param chartHolder chart_ * @param items items */ protected void getPopupMenuItems(ChartHolder chartHolder, List items) { } /** * show dialog for chart * * @param chartHolder chart */ protected void showPropertiesDialog(final ChartHolder chartHolder) { List comps = new ArrayList(); getPropertiesComponents(chartHolder, comps); GuiUtils.tmpInsets = GuiUtils.INSETS_5; JComponent contents = GuiUtils.doLayout(comps, 2, GuiUtils.WT_NY, GuiUtils.WT_N); final JDialog propertiesDialog = GuiUtils.createDialog(chartHolder.getName() + " Properties", true); ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent ae) { String cmd = ae.getActionCommand(); if (cmd.equals(GuiUtils.CMD_OK) || cmd.equals(GuiUtils.CMD_APPLY)) { if (!applyProperties(chartHolder)) { return; } updateThumb(true); } if (cmd.equals(GuiUtils.CMD_OK) || cmd.equals(GuiUtils.CMD_CANCEL)) { propertiesDialog.dispose(); } } }; JPanel buttons = GuiUtils.makeButtons(listener, new String[] { GuiUtils.CMD_APPLY, GuiUtils.CMD_OK, GuiUtils.CMD_CANCEL }); contents = GuiUtils.centerBottom(contents, buttons); contents = GuiUtils.inset(contents, 5); propertiesDialog.getContentPane().add(GuiUtils.top(contents)); propertiesDialog.pack(); propertiesDialog.setLocation(200, 200); propertiesDialog.setVisible(true); } /** * get properties widgets * * @param chartHolder the chart * @param comps comps */ protected void getPropertiesComponents(ChartHolder chartHolder, List comps) { chartHolder.nameFld = new JTextField(chartHolder.getName()); comps.add(GuiUtils.rLabel("Name: ")); comps.add(chartHolder.nameFld); XmlObjectStore store = getControl().getIdv().getStore(); chartHolder.backgroundSwatch = GuiUtils.makeColorSwatchWidget(store, chartHolder.getBackgroundColor(), "")[0]; chartHolder.dataAreaSwatch = GuiUtils.makeColorSwatchWidget(store, chartHolder.getDataAreaColor(), "")[0]; comps.add(GuiUtils.rLabel("Color: ")); List colorComps = Misc.newList(new JLabel("Background:"), chartHolder.backgroundSwatch, GuiUtils.filler(10, 5), new JLabel("Chart:"), chartHolder.dataAreaSwatch); comps.add(GuiUtils.left(GuiUtils.hbox(colorComps, 5))); comps.add(GuiUtils.rLabel("Domain Lines: ")); comps.add(chartHolder.getDomainLineState().getPropertyContents(store)); comps.add(GuiUtils.rLabel("Range Lines: ")); comps.add(chartHolder.getRangeLineState().getPropertyContents(store)); chartHolder.getPropertiesComponents(comps); comps.add(GuiUtils.rLabel("Date Format: ")); final String df = getControl().getIdv().getPreferenceManager().getDefaultDateFormat(); final List<String> l = new LinkedList<String>(DATE_FORMATS); if (!l.contains(df)) { l.add(df); } chartHolder.dateFormatBox = GuiUtils.getEditableBox(new LinkedList<String>(DATE_FORMATS), chartHolder.getDateFormat() == null ? df : chartHolder.getDateFormat()); comps.add(GuiUtils.leftRight(chartHolder.dateFormatBox, GuiUtils.filler())); } /** * apply properties to chart * * @param chartHolder the chart * * @return OK */ protected boolean applyProperties(ChartHolder chartHolder) { if (!chartHolder.getDomainLineState().applyProperties()) { return false; } if (!chartHolder.getRangeLineState().applyProperties()) { return false; } if (!chartHolder.applyProperties()) { return false; } String newName = chartHolder.nameFld.getText(); if (!Misc.equals(newName, chartHolder.getName())) { chartNameChanged(chartHolder.getName(), newName); chartHolder.setName(newName); } chartHolder.applyPlotProperties(); return true; } /** * Get the GraphPaperLayout locations * * @return locations for layout */ private List getLocations() { List locations = new ArrayList(); for (ChartHolder chartHolder : chartHolders) { if (!chartHolder.getBeingShown()) { continue; } locations.add(new GraphPaperLayout.Location(chartHolder.getChartPanel(), chartHolder, chartHolder.getName(), chartHolder.getLayoutRect())); } return locations; } /** * apply graph paper layout locations * * @param locations locations */ private void applyLocations(List locations) { for (int i = 0; i < locations.size(); i++) { GraphPaperLayout.Location loc = (GraphPaperLayout.Location) locations.get(i); ChartHolder chartHolder = (ChartHolder) loc.getObject(); chartHolder.setLayoutRect(loc.getRect()); } } /** * set layout * * @param i layout type */ public void setLayoutType(Integer i) { layout = i.intValue(); updateContents(true); } /** * change layout */ public void editLayout() { List locations = getLocations(); GraphPaperLayout.showDialog(locations, "Edit Chart Layout"); applyLocations(locations); updateContents(true); } /** * add menu items * * @param items items */ public void addViewMenuItems(List items) { JMenu layoutMenu = new JMenu("Layout"); items.add(layoutMenu); JMenuItem mi; int[] types = { LAYOUT_GRAPH, LAYOUT_VSTACK, LAYOUT_HSTACK, LAYOUT_2COLGRID, LAYOUT_3COLGRID, LAYOUT_TAB }; String[] names = { "Grid", "Vertical", "Horizontal", "2 Columns", "3 Columns", "Tabs" }; layoutMenu.add(mi = GuiUtils.makeMenuItem("Change Grid Layout", this, "editLayout")); layoutMenu.addSeparator(); mi.setEnabled(layout == LAYOUT_GRAPH); for (int i = 0; i < types.length; i++) { layoutMenu.add(mi = GuiUtils.makeMenuItem(names[i], this, "setLayoutType", new Integer(types[i]))); if (types[i] == layout) { mi.setEnabled(false); } } items.add(GuiUtils.MENU_SEPARATOR); for (ChartHolder chartHolder : chartHolders) { if (!chartHolder.getBeingShown()) { continue; } List chartItems = new ArrayList(); chartHolder.getMenuItems(chartItems); items.add(GuiUtils.makeMenu("Chart: " + chartHolder.getName(), chartItems)); } } /** * ok to draw chart * * @return ok to draw */ public boolean getOkToDraw() { return okToDraw; } /** * Are we currently setting data * * @return is setting data */ public boolean getSettingData() { return settingData; } /** * Stub for handling time change events. */ public void timeChanged() { timeChanged(null); } /** * Stub for handling time change events. Signals a redraw. * @param value the time value (may be null); */ public void timeChanged(Real value) { signalChartChanged(); updateThumb(true); // getContents().repaint(); } /** * Handle the image update * * @param img img * @param flags flags * @param x x * @param y y * @param width width * @param height height * * @return keep going */ public boolean imageUpdate(Image img, int flags, int x, int y, int width, int height) { boolean all = (flags & ImageObserver.ALLBITS) != 0; if (all) { getContents().repaint(); return false; } return true; } /** * do we have anything to show * * @return anything to show */ public boolean hasStuff() { for (ChartHolder chartHolder : chartHolders) { if (chartHolder.hasParameters()) { return true; } } return false; } /** * get gui * * @return gui */ public JComponent getContents() { if (contents == null) { doMakeContents(); MUTEX = contents.getTreeLock(); } return contents; } /** * get list of chart names * * @return names */ public List getPlotNames() { List names = new ArrayList(); for (ChartHolder chartHolder : chartHolders) { names.add(chartHolder.getName()); } return names; } /** * Get the chart holder that is displaying the given line state. * * @param lineState The line state * * @return The chart holder */ protected ChartHolder getChartHolder(LineState lineState) { String chartName = lineState.getChartName(); ChartHolder chartHolder = getChartHolder(chartName); if (chartName == null) { lineState.setChartName(chartHolder.getName()); } return chartHolder; } /** * find chart with name * * @param name name * * @return the chart or null if none found */ protected ChartHolder findChartHolder(String name) { for (ChartHolder tmp : chartHolders) { //If no name then use first chart if ((name == null) || Misc.equals(name, tmp.getName())) { return tmp; } } return null; } /** * find chart with name * * @param name name * * @return the chart or create one if needed */ protected ChartHolder getChartHolder(String name) { ChartHolder chartHolder = findChartHolder(name); if (chartHolder == null) { chartHolder = new ChartHolder(this, name, getPreferredChartSize()); addChart(chartHolder); } return chartHolder; } /** * add chart * * @param chartHolder chart */ protected void addChart(ChartHolder chartHolder) { initChart(chartHolder); chartHolders.add(chartHolder); } /** * Get the preferred chart size * * @return The preferred chart size */ protected Dimension getPreferredChartSize() { return new Dimension(300, 200); } /** * make gui */ protected void doMakeContents() { contents = new JPanel(new BorderLayout()); //Only create a new chart if we don't have any ChartHolder chartHolder = findChartHolder(null); if (chartHolder == null) { makeInitialChart(); } initCharts(); updateContents(true); } /** * make the chart */ protected void makeInitialChart() { getChartHolder(getDefaultChartName()); } /** * get name to use for default chart * * @return default chart name */ protected String getDefaultChartName() { if (defaultChartName != null) { return defaultChartName; } return "Chart"; } /** * update gui */ protected void updateContents() { updateContents(false); } /** * update gui * * @param force always do it */ protected void updateContents(boolean force) { if (contents == null) { return; } List comps = new ArrayList(); int tabIndex = 0; boolean needToUpdate = false; int goodCharts = 0; for (ChartHolder chartHolder : chartHolders) { if (chartHolder.hasParameters()) { goodCharts++; } } for (int plotIdx = 0; plotIdx < chartHolders.size(); plotIdx++) { ChartHolder chartHolder = (ChartHolder) chartHolders.get(plotIdx); if (chartHolder.hasParameters() || ((plotIdx == 0) && (goodCharts == 0))) { if (!chartHolder.getBeingShown()) { needToUpdate = true; } } else if (chartHolder.getBeingShown()) { needToUpdate = true; } } if (!force && !needToUpdate) { return; } contents.removeAll(); if (layout == LAYOUT_TAB) { if (tab == null) { tab = new JTabbedPane(); } else { tab.removeAll(); } } else { tab = null; } for (ChartHolder chartHolder : chartHolders) { chartHolder.resetChartPanel(); } for (int plotIdx = 0; plotIdx < chartHolders.size(); plotIdx++) { ChartHolder chartHolder = (ChartHolder) chartHolders.get(plotIdx); if ((plotIdx == 0) && (goodCharts == 0)) { //if we don't have any charts then always show the first one } else { if (!chartHolder.hasParameters()) { chartHolder.setBeingShown(false); continue; } } chartHolder.setBeingShown(true); if (layout == LAYOUT_TAB) { tab.addTab(chartHolder.getName(), chartHolder.getChartPanel()); } else { comps.add(chartHolder.getChartPanel()); } } JComponent inner = null; if (layout == LAYOUT_VSTACK) { inner = GuiUtils.doLayout(comps, 1, GuiUtils.WT_Y, GuiUtils.WT_Y); } else if (layout == LAYOUT_HSTACK) { inner = GuiUtils.doLayout(comps, comps.size(), GuiUtils.WT_Y, GuiUtils.WT_Y); } else if (layout == LAYOUT_2COLGRID) { inner = GuiUtils.doLayout(comps, 2, GuiUtils.WT_Y, GuiUtils.WT_Y); } else if (layout == LAYOUT_3COLGRID) { inner = GuiUtils.doLayout(comps, 3, GuiUtils.WT_Y, GuiUtils.WT_Y); } else if (layout == LAYOUT_GRAPH) { inner = GraphPaperLayout.layout(getLocations()); } else if (layout == LAYOUT_TAB) { inner = tab; } else { //Default to vstack inner = GuiUtils.doLayout(comps, 1, GuiUtils.WT_Y, GuiUtils.WT_Y); } contents.add(BorderLayout.CENTER, inner); contents.invalidate(); contents.validate(); contents.repaint(); } /** * get thumb label * * @return thumb */ public JLabel getThumb() { if (chartThumb == null) { chartThumb = new JLabel(""); chartThumb.setVisible(showThumb); } return chartThumb; } /** * Set the Layout property. * * @param value The new value for Layout */ public void setLayout(int value) { layout = value; } /** * Get the Layout property. * * @return The Layout */ public int getLayout() { return layout; } /** * Set the GridLayoutDimension property. * * @param value The new value for GridLayoutDimension */ public void setGridLayoutDimension(int value) { gridLayoutDimension = value; } /** * Get the GridLayoutDimension property. * * @return The GridLayoutDimension */ public int getGridLayoutDimension() { return gridLayoutDimension; } /** * Set the ChartHolders property. * * @param value The new value for ChartHolders */ public void setChartHolders(List value) { chartHolders = value; } /** * Get the ChartHolders property. * * @return The ChartHolders */ public List getChartHolders() { return chartHolders; } /** * Set the Control property. * * @param value The new value for Control */ public void setControl(DisplayControlImpl value) { control = value; } /** * Get the Control property. * * @return The Control */ public DisplayControlImpl getControl() { return control; } /** * Set the ShowThumb property. * * @param value The new value for ShowThumb */ public void setShowThumb(boolean value) { if (showThumb == value) { return; } showThumb = value; //If we have a thumb nail then update it if (chartThumb != null) { getThumb().setVisible(value); updateThumb(true); } } /** * Get the ShowThumb property. * * @return The ShowThumb */ public boolean getShowThumb() { return showThumb; } }