Java tutorial
/* * RDV * Real-time Data Viewer * http://rdv.googlecode.com/ * * Copyright (c) 2005-2007 University at Buffalo * Copyright (c) 2005-2007 NEES Cyberinfrastructure Center * Copyright (c) 2008 Palta Software * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * $URL$ * $Revision$ * $Date$ * $Author$ */ package org.rdv.viz.chart; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Image; import java.awt.Paint; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Iterator; import java.util.List; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.event.AxisChangeEvent; import org.jfree.chart.event.AxisChangeListener; import org.jfree.chart.labels.StandardXYToolTipGenerator; import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.StandardXYItemRenderer; import org.jfree.chart.title.LegendTitle; import org.jfree.data.time.FixedMillisecond; import org.jfree.data.time.TimeSeries; import org.jfree.data.time.TimeSeriesCollection; import org.jfree.data.xy.XYDataset; import org.rdv.data.Channel; import org.rdv.data.DataFileReader; import org.rdv.data.NumericDataSample; import org.rdv.datapanel.AbstractDataPanel; import org.rdv.ui.UIUtilities; import com.rbnb.sapi.ChannelMap; /** * A data panel to plot data time series and xy charts. * * @author Jason P. Hanley */ public abstract class ChartViz extends AbstractDataPanel { /** * The logger for this class. */ static Log log = LogFactory.getLog(ChartViz.class.getName()); /** the data panel property to control the legend visibility */ private static final String DATA_PANEL_PROPERTY_SHOW_LOGEND = "showLegend"; /** * The chart. */ JFreeChart chart; /** * The xy plot for this chart. */ XYPlot xyPlot; /** * The domain (horizontal) axis that contains a value. This will be a number * axis for an xy plot or a date axis for a timeseries plot. */ ValueAxis domainAxis; /** * The range (vertical) axis that contains a number. */ NumberAxis rangeAxis; /** * The component that renderers the chart. */ ChartPanel chartPanel; /** * The data set for the chart. */ XYDataset dataCollection; /** * The legend for the series in the chart. */ LegendTitle seriesLegend; /** * The container for the chart component. */ JPanel chartPanelPanel; /** the menu item to control legend visibility */ private JCheckBoxMenuItem showLegendMenuItem; /** * A bit to indicate if we are plotting time series charts of x vs. y charts. */ final boolean xyMode; /** * The timestamp for the last piece if data displayed. */ double lastTimeDisplayed; /** * The number of local data series. */ int localSeries; /** * A channel map used to cache the values of an xy data set when only one * channel has been added. */ ChannelMap cachedChannelMap; /** * Plot colors for each series. */ HashMap<String, Color> colors; /** * Colors used for the series. */ final static Color[] seriesColors = { Color.decode("#FF0000"), Color.decode("#0000FF"), Color.decode("#009900"), Color.decode("#FF9900"), Color.decode("#9900FF"), Color.decode("#FF0099"), Color.decode("#0099FF"), Color.decode("#990000"), Color.decode("#000099"), Color.black }; /** a flag to control the legend visibility, defaults to true */ private boolean showLegend; /** * Constructs a chart data panel in time series mode. */ public ChartViz() { this(false); } /** * Constructs a chart data panel. * * @param xyMode if true in x vs. y mode, otherwise in time series mode */ public ChartViz(boolean xyMode) { super(); this.xyMode = xyMode; lastTimeDisplayed = -1; colors = new HashMap<String, Color>(); // show the legend by default showLegend = true; initChart(); setDataComponent(chartPanelPanel); } /** * Create the chart and setup it's UI. */ private void initChart() { XYToolTipGenerator toolTipGenerator; if (xyMode) { dataCollection = new XYTimeSeriesCollection(); NumberAxis domainAxis = new NumberAxis(); domainAxis.setAutoRangeIncludesZero(false); domainAxis.addChangeListener(new AxisChangeListener() { public void axisChanged(AxisChangeEvent ace) { boundsChanged(); } }); this.domainAxis = domainAxis; toolTipGenerator = new StandardXYToolTipGenerator("{0}: {1} , {2}", new DecimalFormat(), new DecimalFormat()); } else { dataCollection = new TimeSeriesCollection(); domainAxis = new FixedAutoAdjustRangeDateAxis(); domainAxis.setLabel("Time"); domainAxis.setAutoRange(false); toolTipGenerator = new StandardXYToolTipGenerator("{0}: {1} , {2}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"), new DecimalFormat()); } rangeAxis = new NumberAxis(); rangeAxis.setAutoRangeIncludesZero(false); rangeAxis.addChangeListener(new AxisChangeListener() { public void axisChanged(AxisChangeEvent ace) { boundsChanged(); } }); FastXYItemRenderer renderer = new FastXYItemRenderer(StandardXYItemRenderer.LINES, toolTipGenerator); renderer.setBaseCreateEntities(false); renderer.setBaseStroke(new BasicStroke(0.5f)); if (xyMode) { renderer.setCursorVisible(true); } xyPlot = new XYPlot(dataCollection, domainAxis, rangeAxis, renderer); chart = new JFreeChart(xyPlot); chart.setAntiAlias(false); seriesLegend = chart.getLegend(); chart.removeLegend(); chartPanel = new ChartPanel(chart, true); chartPanel.setInitialDelay(0); // get the chart panel standard popup menu JPopupMenu popupMenu = chartPanel.getPopupMenu(); // create a popup menu item to copy an image to the clipboard final JMenuItem copyChartMenuItem = new JMenuItem("Copy"); copyChartMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { copyChart(); } }); popupMenu.insert(copyChartMenuItem, 2); popupMenu.insert(new JPopupMenu.Separator(), 3); popupMenu.add(new JPopupMenu.Separator()); showLegendMenuItem = new JCheckBoxMenuItem("Show Legend", true); showLegendMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { setShowLegend(showLegendMenuItem.isSelected()); } }); popupMenu.add(showLegendMenuItem); if (xyMode) { popupMenu.add(new JPopupMenu.Separator()); JMenuItem addLocalSeriesMenuItem = new JMenuItem("Add local series..."); addLocalSeriesMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { addLocalSeries(); } }); popupMenu.add(addLocalSeriesMenuItem); } chartPanelPanel = new JPanel(); chartPanelPanel.setLayout(new BorderLayout()); chartPanelPanel.add(chartPanel, BorderLayout.CENTER); } /** * Takes the chart and puts it on the clipboard as an image. */ private void copyChart() { // get the system clipboard Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); // create an image of the chart with the preferred dimensions Dimension preferredDimension = chartPanel.getPreferredSize(); Image image = chart.createBufferedImage((int) preferredDimension.getWidth(), (int) preferredDimension.getHeight()); // wrap image in the transferable and put on the clipboard ImageSelection contents = new ImageSelection(image); clipboard.setContents(contents, null); } /** * Add data from a local file as a series to this chart. This will ask the * user for the file name, and which channels to use. */ private void addLocalSeries() { File file = UIUtilities.openFile(); if (file == null || !file.isFile() || !file.exists()) { return; } DataFileReader reader; try { reader = new DataFileReader(file); } catch (IOException e) { JOptionPane.showMessageDialog(getDataComponent(), e.getMessage(), "Problem reading data file", JOptionPane.ERROR_MESSAGE); return; } List<Channel> channels = reader.getChannels(); if (channels.size() < 2) { JOptionPane.showMessageDialog(getDataComponent(), "There must be at least 2 channels in the data file", "Problem with data file", JOptionPane.ERROR_MESSAGE); return; } Channel xChannel; Channel yChannel; if (channels.size() == 2) { xChannel = channels.get(0); yChannel = channels.get(1); } else { xChannel = (Channel) JOptionPane.showInputDialog(getDataComponent(), "Select the x channel:", "Add local channel", JOptionPane.PLAIN_MESSAGE, null, channels.toArray(), null); if (xChannel == null) { return; } yChannel = (Channel) JOptionPane.showInputDialog(getDataComponent(), "Select the y channel:", "Add local channel", JOptionPane.PLAIN_MESSAGE, null, channels.toArray(), null); if (yChannel == null) { return; } } String xChannelName = xChannel.getName(); if (xChannel.getUnit() != null) { xChannelName += " (" + xChannel.getUnit() + ")"; } int xChannelIndex = channels.indexOf(xChannel); String yChannelName = yChannel.getName(); if (yChannel.getUnit() != null) { yChannelName += " (" + yChannel.getUnit() + ")"; } int yChannelIndex = channels.indexOf(yChannel); String seriesName = xChannelName + " vs. " + yChannelName; XYTimeSeries data = new XYTimeSeries(seriesName, FixedMillisecond.class); try { NumericDataSample sample; while ((sample = reader.readSample()) != null) { double timestamp = sample.getTimestamp(); Number[] values = sample.getValues(); FixedMillisecond time = new FixedMillisecond((long) (timestamp * 1000)); XYTimeSeriesDataItem dataItem = new XYTimeSeriesDataItem(time); if (values[xChannelIndex] != null && values[yChannelIndex] != null) { dataItem.setX(values[xChannelIndex]); dataItem.setY(values[yChannelIndex]); } data.add(dataItem, false); } } catch (Exception e) { e.printStackTrace(); return; } Color color = getLeastUsedColor(); colors.put(seriesName, color); ((XYTimeSeriesCollection) dataCollection).addSeries(data); localSeries++; setSeriesColors(); updateTitle(); updateLegend(); } /** * Remove the local series from the chart. * * @param seriesName the name of the local series. */ private void removeLocalSeries(String seriesName) { XYTimeSeries series = ((XYTimeSeriesCollection) dataCollection).getSeries(seriesName); if (series == null) { return; } localSeries--; ((XYTimeSeriesCollection) dataCollection).removeSeries(series); colors.remove(seriesName); setSeriesColors(); updateTitle(); updateLegend(); } /** * Called when the bounds of an axis are changed. This updates the data panel * properties for these values. */ private void boundsChanged() { if (xyMode) { if (domainAxis.isAutoRange()) { properties.remove("domainLowerBound"); properties.remove("domainUpperBound"); } else { properties.setProperty("domainLowerBound", Double.toString(domainAxis.getLowerBound())); properties.setProperty("domainUpperBound", Double.toString(domainAxis.getUpperBound())); } } if (rangeAxis.isAutoRange()) { properties.remove("rangeLowerBound"); properties.remove("rangeUpperBound"); } else { properties.setProperty("rangeLowerBound", Double.toString(rangeAxis.getLowerBound())); properties.setProperty("rangeUpperBound", Double.toString(rangeAxis.getUpperBound())); } } /** * Indicates that this data panel can support multiple channels. This always * returns true. * * @return always true */ public boolean supportsMultipleChannels() { return true; } /** * Called when a channel has been added. * * @param channelName the new channel */ protected void channelAdded(String channelName) { String channelDisplay = getChannelDisplay(channelName); String seriesName = null; Color color = null; if (xyMode) { if (channels.size() % 2 == 0) { String firstChannelName = (String) channels.get(channels.size() - 2); String firstChannelDisplay = getChannelDisplay(firstChannelName); seriesName = firstChannelDisplay + " vs. " + channelDisplay; color = getLeastUsedColor(); XYTimeSeries data = new XYTimeSeries(seriesName, FixedMillisecond.class); data.setMaximumItemAge((long) (timeScale * 1000), (long) (time * 1000)); int position = dataCollection.getSeriesCount() - localSeries; ((XYTimeSeriesCollection) dataCollection).addSeries(position, data); } } else { seriesName = channelDisplay; color = getLeastUsedColor(); FastTimeSeries data = new FastTimeSeries(seriesName, FixedMillisecond.class); data.setMaximumItemAge((long) (timeScale * 1000), (long) (time * 1000)); ((TimeSeriesCollection) dataCollection).addSeries(data); } if (seriesName != null) { // find the least used color and set it colors.put(seriesName, color); setSeriesColors(); } updateTitle(); updateLegend(); } /** * Remove the channel from the data panel. * * @param channelName the channel to remove * @return true if the channel was removed, false otherwise */ public boolean removeChannel(String channelName) { if (xyMode) { if (!channels.contains(channelName)) { return false; } int channelIndex = channels.indexOf(channelName); String firstChannel, secondChannel; if (channelIndex % 2 == 0) { firstChannel = channelName; if (channelIndex + 1 < channels.size()) { secondChannel = (String) channels.get(channelIndex + 1); } else { secondChannel = null; } } else { firstChannel = (String) channels.get(channelIndex - 1); secondChannel = channelName; } rbnbController.unsubscribe(firstChannel, this); channels.remove(firstChannel); if (secondChannel != null) { rbnbController.unsubscribe(secondChannel, this); channels.remove(secondChannel); String firstChannelDisplay = getChannelDisplay(firstChannel); String secondChannelDisplay = getChannelDisplay(secondChannel); String seriesName = firstChannelDisplay + " vs. " + secondChannelDisplay; XYTimeSeriesCollection dataCollection = (XYTimeSeriesCollection) this.dataCollection; XYTimeSeries data = dataCollection.getSeries(seriesName); dataCollection.removeSeries(data); colors.remove(seriesName); } channelRemoved(channelName); return true; } else { return super.removeChannel(channelName); } } /** * Called when a channel has been removed. * * @param the name of the channel that was removed */ protected void channelRemoved(String channelName) { if (!xyMode) { String channelDisplay = getChannelDisplay(channelName); TimeSeriesCollection dataCollection = (TimeSeriesCollection) this.dataCollection; TimeSeries data = dataCollection.getSeries(channelDisplay); dataCollection.removeSeries(data); colors.remove(channelDisplay); } setSeriesColors(); updateTitle(); updateLegend(); } /** * Return a color that is least used. * * @return the color */ private Color getLeastUsedColor() { int usage = -1; Color color = null; for (int i = 0; i < seriesColors.length; i++) { int seriesUsingColor = getSeriesUsingColor(seriesColors[i]); if (usage == -1 || seriesUsingColor < usage) { usage = seriesUsingColor; color = seriesColors[i]; } } return color; } /** * Count the number of series using the specified color for their series * plot. * * @param color the color to find * @return the number of series using this color */ private int getSeriesUsingColor(Color color) { if (color == null) { return 0; } int count = 0; for (int i = 0; i < dataCollection.getSeriesCount(); i++) { Paint p = xyPlot.getRenderer().getSeriesPaint(i); if (p.equals(color)) { count++; } } return count; } /** * Set the color for all the series. */ private void setSeriesColors() { for (int i = 0; i < dataCollection.getSeriesCount(); i++) { String series = (String) dataCollection.getSeriesKey(i); xyPlot.getRenderer().setSeriesPaint(i, colors.get(series)); } } /** * Shows or hides the legend. * * @param showLegend if true, the legend will show, otherwise it will not */ private void setShowLegend(boolean showLegend) { if (this.showLegend == showLegend) { return; } this.showLegend = showLegend; showLegendMenuItem.setSelected(showLegend); if (showLegend) { properties.remove(DATA_PANEL_PROPERTY_SHOW_LOGEND); } else { properties.setProperty(DATA_PANEL_PROPERTY_SHOW_LOGEND, "false"); } updateLegend(); } /** * Update the legend and axis labels based on the series being viewed. */ private void updateLegend() { int series = dataCollection.getSeriesCount(); int chans = channels.size(); if (xyMode) { if (series == 0 && chans == 1) { String channelDisplay = getChannelDisplay((String) channels.get(0)); domainAxis.setLabel(channelDisplay); rangeAxis.setLabel(null); } else if (series == 1 && chans == 0) { XYTimeSeries xySeries = ((XYTimeSeriesCollection) dataCollection).getSeries(0); String seriesName = (String) xySeries.getKey(); String[] channelNames = seriesName.split(" vs. "); if (channelNames.length == 2) { domainAxis.setLabel(channelNames[0]); rangeAxis.setLabel(channelNames[1]); } } else if (series == 1 && chans == 2) { String channelDisplay1 = getChannelDisplay((String) channels.get(0)); domainAxis.setLabel(channelDisplay1); String channelDisplay2 = getChannelDisplay((String) channels.get(1)); rangeAxis.setLabel(channelDisplay2); } else { domainAxis.setLabel(null); rangeAxis.setLabel(null); } } else { if (series == 1) { String channelDisplay = getChannelDisplay((String) channels.get(0)); rangeAxis.setLabel(channelDisplay); } else { rangeAxis.setLabel(null); } } // show the legend if it is enabled and there are at least 2 series if (showLegend && series >= 2) { if (chart.getLegend() == null) { chart.addLegend(seriesLegend); } } else { if (chart.getLegend() != null) { seriesLegend = chart.getLegend(); } chart.removeLegend(); } } /** * Get the title of this data panel. This overides the super class * implementation to deal with x vs. y plots. * * @return the title of the data panel */ @Override protected String getTitle() { if (xyMode) { int remoteSeries = dataCollection.getSeriesCount() - localSeries; String title = new String(); Iterator<String> i = channels.iterator(); while (i.hasNext()) { String firstChannel = i.next(); title += firstChannel; if (i.hasNext()) { String secondChannel = i.next(); title += " vs. " + secondChannel; if (i.hasNext() || localSeries > 0) { title += ", "; } } } for (int j = remoteSeries; j < remoteSeries + localSeries; j++) { String seriesName = (String) dataCollection.getSeriesKey(j); title += seriesName; if (j < remoteSeries + localSeries - 1) { title += ", "; } } return title; } else { return super.getTitle(); } } /** * Get the component to display the channels in the header of the data panel. * This overides the super class implementation to deal with x vs. y plots. * * @return the component displaying the channels for the data panel */ @Override protected JComponent getChannelComponent() { if (xyMode) { int remoteSeries = dataCollection.getSeriesCount() - localSeries; if (channels.size() == 0 && localSeries == 0) { return null; } JPanel titleBar = new JPanel(); titleBar.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); titleBar.setOpaque(false); if (isShowChannelsInTitle()) { Iterator<String> i = channels.iterator(); while (i.hasNext()) { String firstChannel = i.next(); String series = firstChannel; if (i.hasNext()) { series += " vs. " + i.next(); } titleBar.add(new ChannelTitle(series, firstChannel)); } for (int j = remoteSeries; j < remoteSeries + localSeries; j++) { String seriesName = (String) dataCollection.getSeriesKey(j); titleBar.add(new LocalChannelTitle(seriesName)); } } return titleBar; } else { return super.getChannelComponent(); } } /** * A channel title component for local channels. */ class LocalChannelTitle extends ChannelTitle { /** serialization version identifier */ private static final long serialVersionUID = -6278564478512657058L; /** * Create a local channel title. * * @param seriesName the name of the series */ public LocalChannelTitle(String seriesName) { super(seriesName, seriesName); } /** * Return an actionlistener to remove this series. */ protected ActionListener getActionListener(final String seriesName, final String channelName) { return new ActionListener() { public void actionPerformed(ActionEvent ae) { removeLocalSeries(seriesName); } }; } } /** * Get the string for this channel to display in the UI. This will show the * channel units if there are any. * * @param channelName the name of the channel * @return the string to display the channel in the UI */ private String getChannelDisplay(String channelName) { String seriesName = channelName; Channel channel = rbnbController.getChannel(channelName); if (channel != null) { String unit = channel.getUnit(); if (unit != null) { seriesName += " (" + unit + ")"; } } return seriesName; } /** * Called when the time scale changes. This updates the maximum age of the * dataset. * * @param newTimeScale the new time scale */ public void timeScaleChanged(double newTimeScale) { super.timeScaleChanged(newTimeScale); SwingUtilities.invokeLater(new Runnable() { public void run() { int series = dataCollection.getSeriesCount(); if (xyMode) { series -= localSeries; } for (int i = 0; i < series; i++) { if (xyMode) { XYTimeSeriesCollection xyTimeSeriesCollection = (XYTimeSeriesCollection) dataCollection; XYTimeSeries data = xyTimeSeriesCollection.getSeries(i); data.setMaximumItemAge((long) (timeScale * 1000), (long) (time * 1000)); } else { TimeSeriesCollection timeSeriesCollection = (TimeSeriesCollection) dataCollection; FastTimeSeries data = (FastTimeSeries) timeSeriesCollection.getSeries(i); data.setMaximumItemAge((long) (timeScale * 1000), (long) (time * 1000)); } } if (!xyMode) { domainAxis.setRange((time - timeScale) * 1000, time * 1000); ((FixedAutoAdjustRangeDateAxis) domainAxis).setAutoAdjustRange((time - timeScale) * 1000, time * 1000); } } }); } /** * Posts new data to the data panel. * * @param channelMap the channel map with the new data */ public void postData(final ChannelMap channelMap) { cachedChannelMap = this.channelMap; SwingUtilities.invokeLater(new Runnable() { public void run() { ChartViz.this.channelMap = channelMap; } }); } /** * Posts the data in the channel map when in time series mode. * * @param channelMap the channel map with the new data */ private void postDataTimeSeries(ChannelMap channelMap) { //loop over all channels and see if there is data for them for (String channelName : channels) { int channelIndex = channelMap.GetIndex(channelName); //if there is data for channel, post it if (channelIndex != -1) { postDataTimeSeries(channelMap, channelName, channelIndex); } } } /** * Posts the data in the channel map to the specified channel when in time * series mode. * * @param channelMap the channel map containing the new data * @param channelName the name of the channel to post data to * @param channelIndex the index of the channel in the channel map */ private void postDataTimeSeries(ChannelMap channelMap, String channelName, int channelIndex) { TimeSeriesCollection dataCollection = (TimeSeriesCollection) this.dataCollection; FastTimeSeries timeSeriesData = (FastTimeSeries) dataCollection.getSeries(getChannelDisplay(channelName)); if (timeSeriesData == null) { log.error("We don't have a data collection to post this data."); return; } try { double[] times = channelMap.GetTimes(channelIndex); int typeID = channelMap.GetType(channelIndex); FixedMillisecond time; chart.setNotify(false); timeSeriesData.startAdd(times.length); switch (typeID) { case ChannelMap.TYPE_FLOAT64: double[] doubleData = channelMap.GetDataAsFloat64(channelIndex); for (int i = 0; i < doubleData.length; i++) { time = new FixedMillisecond((long) (times[i] * 1000)); timeSeriesData.add(time, doubleData[i]); } break; case ChannelMap.TYPE_FLOAT32: float[] floatData = channelMap.GetDataAsFloat32(channelIndex); for (int i = 0; i < floatData.length; i++) { time = new FixedMillisecond((long) (times[i] * 1000)); timeSeriesData.add(time, floatData[i]); } break; case ChannelMap.TYPE_INT64: long[] longData = channelMap.GetDataAsInt64(channelIndex); for (int i = 0; i < longData.length; i++) { time = new FixedMillisecond((long) (times[i] * 1000)); timeSeriesData.add(time, longData[i]); } break; case ChannelMap.TYPE_INT32: int[] intData = channelMap.GetDataAsInt32(channelIndex); for (int i = 0; i < intData.length; i++) { time = new FixedMillisecond((long) (times[i] * 1000)); timeSeriesData.add(time, intData[i]); } break; case ChannelMap.TYPE_INT16: short[] shortData = channelMap.GetDataAsInt16(channelIndex); for (int i = 0; i < shortData.length; i++) { time = new FixedMillisecond((long) (times[i] * 1000)); timeSeriesData.add(time, shortData[i]); } break; case ChannelMap.TYPE_INT8: byte[] byteData = channelMap.GetDataAsInt8(channelIndex); for (int i = 0; i < byteData.length; i++) { time = new FixedMillisecond((long) (times[i] * 1000)); timeSeriesData.add(time, byteData[i]); } break; case ChannelMap.TYPE_STRING: case ChannelMap.TYPE_UNKNOWN: case ChannelMap.TYPE_BYTEARRAY: log.error("Got byte array type for channel " + channelName + ". Don't know how to handle."); break; } timeSeriesData.fireSeriesChanged(); chart.setNotify(true); chart.fireChartChanged(); } catch (Exception e) { log.error("Problem plotting data for channel " + channelName + "."); e.printStackTrace(); } } /** * Posts a new time. This pulls data out of a posted channel map when in x vs. * y mode. * * @param time the new time */ public void postTime(double time) { if (time < this.time) { clearData(); } super.postTime(time); SwingUtilities.invokeLater(new Runnable() { public void run() { if (xyMode) { postDataXY(channelMap, cachedChannelMap); } else if (channelMap != null) { postDataTimeSeries(channelMap); channelMap = null; } setTimeAxis(); } }); } /** * Posts the data in the channel map when in x vs. y mode. * * @param channelMap the new channel map * @param cachedChannelMap the cached channel map */ private void postDataXY(ChannelMap channelMap, ChannelMap cachedChannelMap) { //loop over all channels and see if there is data for them int seriesCount = dataCollection.getSeriesCount() - localSeries; for (int i = 0; i < seriesCount; i++) { postDataXY(channelMap, cachedChannelMap, i); } lastTimeDisplayed = time; } /** * Posts the data in the channel map to the specified channel when in x vs. y * mode. * * @param channelMap the new channel map * @param cachedChannelMap the cached channel map * @param series the index of the series */ private void postDataXY(ChannelMap channelMap, ChannelMap cachedChannelMap, int series) { if (!xyMode) { log.error("Tried to post X vs. Y data when not in xy mode."); return; } if (channelMap == null) { //no data to display yet return; } Object[] channelsArray = channels.toArray(); String xChannelName = (String) channelsArray[series * 2]; String yChannelName = (String) channelsArray[series * 2 + 1]; //get the channel indexes for the x and y channels int xChannelIndex = channelMap.GetIndex(xChannelName); int yChannelIndex = channelMap.GetIndex(yChannelName); int firstXChannelIndex = -1; //return if this channel map doesn't have data for the y channel if (yChannelIndex == -1) { return; } else if (xChannelIndex == -1) { //see if we cached data for the x channel firstXChannelIndex = (cachedChannelMap == null) ? -1 : cachedChannelMap.GetIndex(xChannelName); if (firstXChannelIndex == -1) { cachedChannelMap = null; return; } } try { //TODO make sure data is at the same timestamp double[] times = channelMap.GetTimes(yChannelIndex); //FIXME go over all channel times int startIndex = -1; // determine what time we should load data from double dataStartTime; if (lastTimeDisplayed == time) { dataStartTime = time - timeScale; } else { dataStartTime = lastTimeDisplayed; } for (int i = 0; i < times.length; i++) { if (times[i] > dataStartTime && times[i] <= time) { startIndex = i; break; } } //see if there is no data in the time range we are loooking at if (startIndex == -1) { return; } int endIndex = startIndex; for (int i = times.length - 1; i > startIndex; i--) { if (times[i] <= time) { endIndex = i; break; } } XYTimeSeriesCollection dataCollection = (XYTimeSeriesCollection) this.dataCollection; XYTimeSeries xySeriesData = (XYTimeSeries) dataCollection.getSeries(series); //FIXME assume data of same type int typeID = channelMap.GetType(yChannelIndex); FixedMillisecond time; chart.setNotify(false); xySeriesData.startAdd(times.length); switch (typeID) { case ChannelMap.TYPE_FLOAT64: double[] xDoubleData = firstXChannelIndex == -1 ? channelMap.GetDataAsFloat64(xChannelIndex) : cachedChannelMap.GetDataAsFloat64(firstXChannelIndex); double[] yDoubleData = channelMap.GetDataAsFloat64(yChannelIndex); for (int i = startIndex; i <= endIndex; i++) { time = new FixedMillisecond((long) (times[i] * 1000)); xySeriesData.add(time, xDoubleData[i], yDoubleData[i], false); } break; case ChannelMap.TYPE_FLOAT32: float[] xFloatData = firstXChannelIndex == -1 ? channelMap.GetDataAsFloat32(xChannelIndex) : cachedChannelMap.GetDataAsFloat32(firstXChannelIndex); float[] yFloatData = channelMap.GetDataAsFloat32(yChannelIndex); for (int i = startIndex; i <= endIndex; i++) { time = new FixedMillisecond((long) (times[i] * 1000)); xySeriesData.add(time, xFloatData[i], yFloatData[i], false); } break; case ChannelMap.TYPE_INT64: long[] xLongData = firstXChannelIndex == -1 ? channelMap.GetDataAsInt64(xChannelIndex) : cachedChannelMap.GetDataAsInt64(firstXChannelIndex); long[] yLongData = channelMap.GetDataAsInt64(yChannelIndex); for (int i = startIndex; i <= endIndex; i++) { time = new FixedMillisecond((long) (times[i] * 1000)); xySeriesData.add(time, xLongData[i], yLongData[i], false); } break; case ChannelMap.TYPE_INT32: int[] xIntData = firstXChannelIndex == -1 ? channelMap.GetDataAsInt32(xChannelIndex) : cachedChannelMap.GetDataAsInt32(firstXChannelIndex); int[] yIntData = channelMap.GetDataAsInt32(yChannelIndex); for (int i = startIndex; i <= endIndex; i++) { time = new FixedMillisecond((long) (times[i] * 1000)); xySeriesData.add(time, xIntData[i], yIntData[i], false); } break; case ChannelMap.TYPE_INT16: short[] xShortData = firstXChannelIndex == -1 ? channelMap.GetDataAsInt16(xChannelIndex) : cachedChannelMap.GetDataAsInt16(firstXChannelIndex); short[] yShortData = channelMap.GetDataAsInt16(yChannelIndex); for (int i = startIndex; i <= endIndex; i++) { time = new FixedMillisecond((long) (times[i] * 1000)); xySeriesData.add(time, xShortData[i], yShortData[i], false); } break; case ChannelMap.TYPE_INT8: byte[] xByteData = firstXChannelIndex == -1 ? channelMap.GetDataAsInt8(xChannelIndex) : cachedChannelMap.GetDataAsInt8(firstXChannelIndex); byte[] yByteData = channelMap.GetDataAsInt8(yChannelIndex); for (int i = startIndex; i <= endIndex; i++) { time = new FixedMillisecond((long) (times[i] * 1000)); xySeriesData.add(time, xByteData[i], yByteData[i], false); } break; case ChannelMap.TYPE_BYTEARRAY: case ChannelMap.TYPE_STRING: case ChannelMap.TYPE_UNKNOWN: log.error("Don't know how to handle data type for " + xChannelName + " and " + yChannelName + "."); break; } xySeriesData.fireSeriesChanged(); chart.setNotify(true); chart.fireChartChanged(); //make sure cached channel map can be freeded cachedChannelMap = null; } catch (Exception e) { log.error("Problem plotting data for channels " + xChannelName + " and " + yChannelName + "."); e.printStackTrace(); } } /** * Sets the time axis to display within the current time and time scale. This * assumes it is called in the event dispatch thread. */ private void setTimeAxis() { if (chart == null) { log.warn("Chart object is null. This shouldn't happen."); return; } int series = dataCollection.getSeriesCount(); if (xyMode) { series -= localSeries; } for (int i = 0; i < series; i++) { if (xyMode) { XYTimeSeriesCollection xyTimeSeriesDataCollection = (XYTimeSeriesCollection) dataCollection; XYTimeSeries data = xyTimeSeriesDataCollection.getSeries(i); data.removeAgedItems((long) (time * 1000)); } else { TimeSeriesCollection timeSeriesDataCollection = (TimeSeriesCollection) dataCollection; TimeSeries data = timeSeriesDataCollection.getSeries(i); data.removeAgedItems((long) (time * 1000), true); } } if (!xyMode) { domainAxis.setRange((time - timeScale) * 1000, time * 1000); ((FixedAutoAdjustRangeDateAxis) domainAxis).setAutoAdjustRange((time - timeScale) * 1000, time * 1000); } } /** * Removes all data from all the series. */ void clearData() { if (chart == null) { return; } SwingUtilities.invokeLater(new Runnable() { public void run() { lastTimeDisplayed = -1; int series = dataCollection.getSeriesCount(); if (xyMode) { series -= localSeries; } for (int i = 0; i < series; i++) { if (xyMode) { XYTimeSeriesCollection xyTimeSeriesDataCollection = (XYTimeSeriesCollection) dataCollection; XYTimeSeries data = xyTimeSeriesDataCollection.getSeries(i); data.clear(); } else { TimeSeriesCollection timeSeriesDataCollection = (TimeSeriesCollection) dataCollection; TimeSeries data = timeSeriesDataCollection.getSeries(i); data.clear(); } } } }); log.info("Cleared data display."); } /** * Sets properties for the data panel. * * @param key the key for the property * @param value the value for the property */ public void setProperty(String key, String value) { super.setProperty(key, value); if (key != null && value != null) { if (key.equals("domainLowerBound")) { domainAxis.setLowerBound(Double.parseDouble(value)); } else if (key.equals("domainUpperBound")) { domainAxis.setUpperBound(Double.parseDouble(value)); } else if (key.equals("rangeLowerBound")) { rangeAxis.setLowerBound(Double.parseDouble(value)); } else if (key.equals("rangeUpperBound")) { rangeAxis.setUpperBound(Double.parseDouble(value)); } else if (key.equals(DATA_PANEL_PROPERTY_SHOW_LOGEND) && !Boolean.parseBoolean(value)) { setShowLegend(false); } } } /** * Get the name of this data panel. */ public String toString() { return "JFreeChart Data Panel"; } }