diff --git a/src/org.eclipse.ice.viz.service/src/org/eclipse/ice/viz/service/csv/CSVPlot.java b/src/org.eclipse.ice.viz.service/src/org/eclipse/ice/viz/service/csv/CSVPlot.java index 33233d39c..369c092a1 100644 --- a/src/org.eclipse.ice.viz.service/src/org/eclipse/ice/viz/service/csv/CSVPlot.java +++ b/src/org.eclipse.ice.viz.service/src/org/eclipse/ice/viz/service/csv/CSVPlot.java @@ -16,7 +16,6 @@ import java.net.URI; import java.util.ArrayList; import java.util.HashMap; -import java.util.Hashtable; import java.util.Map; import org.eclipse.ice.client.widgets.viz.service.IPlot; @@ -25,6 +24,8 @@ import org.eclipse.ice.viz.plotviewer.CSVPlotEditor; import org.eclipse.ice.viz.plotviewer.PlotProvider; import org.eclipse.ice.viz.plotviewer.SeriesProvider; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.widgets.Composite; /** @@ -39,6 +40,9 @@ */ public class CSVPlot implements IPlot { + // FIXME We should be able to add and remove any number of series from the + // same drawn plot. Currently, we can only show one series at a time. + /** * The source of the data for this plot */ @@ -47,22 +51,26 @@ public class CSVPlot implements IPlot { /** * The plot properties */ - private Map properties; + private final Map properties; /** * The types that this plot can assume */ - private Map types; + private final Map types; /** - * The CSVDataProvider used to store the CSV data + * The CSVDataProvider used to store the loaded CSV data. */ - private CSVDataProvider provider; + private CSVDataProvider baseProvider; /** - * The CSVPlotEditor used to render the plot + * A map of drawn plots, keyed on the parent {@code Composite}s. Only one + * rendering should be created for a single parent. Subsequent calls to + * {@link #draw(String, String, Composite)} with a parent already in the + * map's key set should update the plot category and type for the associated + * drawn plot. */ - private CSVPlotEditor editor; + private final Map drawnPlots; /** * The Constructor @@ -75,11 +83,14 @@ public CSVPlot(URI source) { // Create the property map, empty by default properties = new HashMap(); // Create the plot type map and add empty arrays by default - types = new Hashtable(); + types = new HashMap(); String[] emptyStringArray = {}; types.put("line", emptyStringArray); types.put("scatter", emptyStringArray); + // Create the map of drawn plots. + drawnPlots = new HashMap(); + return; } @@ -106,24 +117,24 @@ public void run() { // Load the file CSVDataLoader dataLoader = new CSVDataLoader(); try { - provider = dataLoader.load(file); + baseProvider = dataLoader.load(file); } catch (Exception e) { throw new RuntimeException(e); } // Set the source so the title and everything gets // loaded right later - provider.setSource(source.toString()); + baseProvider.setSource(source.toString()); // Get the variables - ArrayList variables = provider.getFeatureList(); + ArrayList variables = baseProvider + .getFeatureList(); // Set the first feature as an independent variable - provider.setFeatureAsIndependentVariable(variables + baseProvider.setFeatureAsIndependentVariable(variables .get(0)); // Create lists to hold the plot types ArrayList plotTypes = new ArrayList( variables.size()); // Create the type list. Loop over every variable and // make it possible to plot it against the others. - String[] emptyStringArray = {}; for (int i = 0; i < variables.size(); i++) { for (int j = 0; j < variables.size(); j++) { if (i != j) { @@ -133,11 +144,14 @@ public void run() { } } } - // Put the types in the map - types.put("line", plotTypes.toArray(emptyStringArray)); - // Add the qualifier - types.put("scatter", - plotTypes.toArray(emptyStringArray)); + // Put the types in the map. Line, scatter, and bar + // plots can be created for any of the plot types, so we + // can re-use the same string array. + String[] plotTypesArray = plotTypes + .toArray(new String[] {}); + types.put("Line", plotTypesArray); + types.put("Scatter", plotTypesArray); + types.put("Bar", plotTypesArray); } } @@ -240,57 +254,147 @@ public Composite draw(String category, String plotType, Composite parent) Composite child = null; - // Make sure the plot type is valid - if (provider != null && category != null - && types.keySet().contains(category) && plotType != null - && plotType.contains(" vs. ")) { + // Determine whether the specified plotType is valid. Note that this + // also requires the category to be valid! + String[] types = this.types.get(category); + boolean typeValid = false; + if (types != null) { + for (String type : types) { + if (type != null && type.equals(plotType)) { + typeValid = true; + break; + } + } + } + + if (baseProvider != null && typeValid && parent != null + && !parent.isDisposed()) { + + // Get the drawn plot associated with the parent Composite, creating + // a new editor if necessary. + DrawnPlot drawnPlot = drawnPlots.get(parent); + if (drawnPlot == null) { + drawnPlot = new DrawnPlot(parent); + drawnPlots.put(parent, drawnPlot); + + // When the parent is disposed, remove the drawn plot and + // dispose of any of its resources. + parent.addDisposeListener(new DisposeListener() { + @Override + public void widgetDisposed(DisposeEvent e) { + drawnPlots.remove((Composite) e.widget).dispose(); + } + }); + } + + // Reset the plot time to the initial time. + Double plotTime = baseProvider.getTimes().get(0); + // FIXME Won't this affect all of the drawn plots? + baseProvider.setTime(plotTime); + + // Remove the previously plotted series, if one exists. + if (drawnPlot.seriesProvider != null) { + drawnPlot.plotProvider.removeSeries(plotTime, + drawnPlot.seriesProvider); + drawnPlot.seriesProvider = null; + } + // Get the axes to plot String[] axes = plotType.split(" "); String axis1 = axes[0]; String axis2 = axes[2]; - // Create the plot provider - PlotProvider plotProvider = new PlotProvider(); - // The new plot's title (the filename) - int lastSeparator = provider.getSourceInfo().lastIndexOf("/"); - String newPlotTitle = (lastSeparator > -1 ? provider - .getSourceInfo().substring(lastSeparator + 1) : provider - .getSourceInfo()); - // Set the title for the new plot provider - plotProvider.setPlotTitle(newPlotTitle); - // The plot's set time - Double plotTime = provider.getTimes().get(0); - // Set the time - not entirely why they get it and set it here. - // Whatevs. - provider.setTime(plotTime); + // Create a new series title for the new series String seriesTitle = axis1 + " vs. " + axis2 + " at " + plotTime; // Create a new series provider SeriesProvider seriesProvider = new SeriesProvider(); - seriesProvider.setDataProvider(provider); + seriesProvider.setDataProvider(drawnPlot.dataProvider); seriesProvider.setTimeForDataProvider(plotTime); seriesProvider.setSeriesTitle(seriesTitle); seriesProvider.setXDataFeature(axis1); seriesProvider.setYDataFeature(axis2); seriesProvider.setSeriesType(category); // Add this new series to the plot provider - plotProvider.addSeries(plotTime, seriesProvider); - // Create the plot editor - editor = new CSVPlotEditor(); - // Create the the plotting canvas - editor.createPartControl(parent); + drawnPlot.seriesProvider = seriesProvider; + drawnPlot.plotProvider.addSeries(plotTime, seriesProvider); + // Add the new plot to the editor (with time slider disabled) - editor.showPlotProvider(plotProvider, false); + drawnPlot.editor.showPlotProvider(drawnPlot.plotProvider, true); // We need to return the Composite used to render the CSV plot. - child = editor.getPlotCanvas(); + child = drawnPlot.editor.getPlotCanvas(); } else { // Complain that the plot is invalid throw new Exception("Invalid plot: category = " + category + ", type = " + plotType + ", provider = " - + provider.toString()); + + baseProvider.toString()); } return child; } + /** + * An instance of this nested class is composed of the drawn + * {@link CSVPlotEditor} and all providers necessary to populate it with CSV + * data. + * + * @author Jordan Deyton + * + */ + private class DrawnPlot { + /** + * The editor in which the CSV plot is rendered. + */ + public final CSVPlotEditor editor; + /** + * The data provider containing the loaded CSV data. + */ + public final CSVDataProvider dataProvider; + /** + * The provider responsible for maintaining the plot configuration. + */ + public final PlotProvider plotProvider; + + /** + * The current series rendered on the plot. + */ + public SeriesProvider seriesProvider; + + /** + * Creates a {@link CSVPlotEditor} and all providers necessary to + * populate it. The editor is created inside the specified parent + * {@code Composite}. + * + * @param parent + * The {@code Composite} in which to draw the CSV plot + * editor. + */ + public DrawnPlot(Composite parent) { + // Create the editor and all required providers. + editor = new CSVPlotEditor(); + dataProvider = baseProvider; + plotProvider = new PlotProvider(); + + // Set the plot title based on the file name. + int lastSeparator = dataProvider.getSourceInfo().lastIndexOf("/"); + String plotTitle = (lastSeparator > -1 ? dataProvider + .getSourceInfo().substring(lastSeparator + 1) + : dataProvider.getSourceInfo()); + // Set the title for the new plot provider + plotProvider.setPlotTitle(plotTitle); + + // Create the plot inside the parent Composite. + editor.createPartControl(parent); + + return; + } + + /** + * Disposes of the drawn plot and all related resources. + */ + public void dispose() { + // Nothing to do yet. + } + } + } diff --git a/src/org.eclipse.ice.viz/src/org/eclipse/ice/viz/plotviewer/PlotProvider.java b/src/org.eclipse.ice.viz/src/org/eclipse/ice/viz/plotviewer/PlotProvider.java index b79880f07..23fb064f8 100644 --- a/src/org.eclipse.ice.viz/src/org/eclipse/ice/viz/plotviewer/PlotProvider.java +++ b/src/org.eclipse.ice.viz/src/org/eclipse/ice/viz/plotviewer/PlotProvider.java @@ -13,6 +13,7 @@ package org.eclipse.ice.viz.plotviewer; import java.util.ArrayList; +import java.util.List; import java.util.TreeMap; /** @@ -82,7 +83,7 @@ public PlotProvider(String newPlotTitle) { * @param time * @param newSeries */ - public void addSeries(Double time, SeriesProvider newSeries) { + public void addSeries(double time, SeriesProvider newSeries) { // Checks if a series is added to a new time and initializes an // arraylist if (!seriesMap.containsKey(time)) { @@ -94,13 +95,30 @@ public void addSeries(Double time, SeriesProvider newSeries) { } } + /** + * Removes an existing SeriesProvider from the specified time. Does nothing + * if the arguments are invalid. + * + * @param time + * The time at which the series should be removed. + * @param oldSeries + * The series that should be removed. + */ + public void removeSeries(double time, SeriesProvider oldSeries) { + List seriesProviders = seriesMap.get(time); + if (seriesProviders != null) { + seriesProviders.remove(oldSeries); + } + return; + } + /** * Returns all the series at a specified time * * @param time * @return */ - public ArrayList getSeriesAtTime(Double time) { + public ArrayList getSeriesAtTime(double time) { if (seriesMap.containsKey(time)) { return seriesMap.get(time); } else { diff --git a/tests/org.eclipse.ice.viz.test/src/org/eclipse/ice/viz/plotviewer/test/PlotProviderTester.java b/tests/org.eclipse.ice.viz.test/src/org/eclipse/ice/viz/plotviewer/test/PlotProviderTester.java index 766b5637f..cd3ed65e6 100644 --- a/tests/org.eclipse.ice.viz.test/src/org/eclipse/ice/viz/plotviewer/test/PlotProviderTester.java +++ b/tests/org.eclipse.ice.viz.test/src/org/eclipse/ice/viz/plotviewer/test/PlotProviderTester.java @@ -15,6 +15,7 @@ import static org.junit.Assert.*; import java.util.ArrayList; +import java.util.List; import java.util.TreeMap; import org.eclipse.ice.viz.plotviewer.PlotProvider; @@ -90,6 +91,91 @@ public void checkAddSeries() { assertEquals(seriesMap, newSeriesMap); assertEquals(seriesMap.size(), newSeriesMap.size()); + return; + } + + /** + * Checks that SeriesProviders can be correctly removed from the + * PlotProvider. + */ + @Test + public void checkRemoveSeries() { + + // A PlotProvider whose remove functionality will be tested. No other + // tests should interfere, hence we do not use the class variable. + PlotProvider plotProvider = new PlotProvider(); + + // Series and times to add and later *remove*. Each series is added for + // each time, resulting in 4 total additions. + SeriesProvider series1 = new SeriesProvider(); + SeriesProvider series2 = new SeriesProvider(); + double time1 = 42.0; + double time2 = 343.1337; + + // Used as the return value from getSeriesAtTime(). This is necessary to + // verify that series were in fact removed from the PlotProvider. + List seriesProviders; + + // Add each series to each time. + plotProvider.addSeries(time1, series1); + plotProvider.addSeries(time2, series1); + plotProvider.addSeries(time1, series2); + plotProvider.addSeries(time2, series2); + + // Verify that all series are associated with each time. + seriesProviders = plotProvider.getSeriesAtTime(time1); + assertEquals(2, seriesProviders.size()); + assertTrue(seriesProviders.contains(series1)); + assertTrue(seriesProviders.contains(series2)); + seriesProviders = plotProvider.getSeriesAtTime(time2); + assertEquals(2, seriesProviders.size()); + assertTrue(seriesProviders.contains(series1)); + assertTrue(seriesProviders.contains(series2)); + + // Try some invalid remove commands. Nothing should change. + plotProvider.removeSeries(-1.0, series1); + plotProvider.removeSeries(time1, null); + + // Verify that nothing changed. + seriesProviders = plotProvider.getSeriesAtTime(time1); + assertEquals(2, seriesProviders.size()); + assertTrue(seriesProviders.contains(series1)); + assertTrue(seriesProviders.contains(series2)); + seriesProviders = plotProvider.getSeriesAtTime(time2); + assertEquals(2, seriesProviders.size()); + assertTrue(seriesProviders.contains(series1)); + assertTrue(seriesProviders.contains(series2)); + + // Now remove series 1 from time 1, and remove series 2 from time 2. + plotProvider.removeSeries(time1, series1); + plotProvider.removeSeries(time2, series2); + + // Series 2 should be the only series for time 1. Likewise, series 1 + // should be the only series for time 2. + seriesProviders = plotProvider.getSeriesAtTime(time1); + assertEquals(1, seriesProviders.size()); + assertFalse(seriesProviders.contains(series1)); + assertTrue(seriesProviders.contains(series2)); + seriesProviders = plotProvider.getSeriesAtTime(time2); + assertEquals(1, seriesProviders.size()); + assertTrue(seriesProviders.contains(series1)); + assertFalse(seriesProviders.contains(series2)); + + // Try the same remove commands. Nothing should change. + plotProvider.removeSeries(time1, series1); + plotProvider.removeSeries(time2, series2); + + // Verify that nothing changed. + seriesProviders = plotProvider.getSeriesAtTime(time1); + assertEquals(1, seriesProviders.size()); + assertFalse(seriesProviders.contains(series1)); + assertTrue(seriesProviders.contains(series2)); + seriesProviders = plotProvider.getSeriesAtTime(time2); + assertEquals(1, seriesProviders.size()); + assertTrue(seriesProviders.contains(series1)); + assertFalse(seriesProviders.contains(series2)); + + return; } /**