This repository has been archived by the owner on Mar 27, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 55
/
CSVPlot.java
511 lines (445 loc) · 14.8 KB
/
CSVPlot.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
/*******************************************************************************
* Copyright (c) 2014- UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Initial API and implementation and/or initial documentation -
* Jay Jay Billings
*******************************************************************************/
package org.eclipse.ice.viz.service.csv;
import java.io.File;
import java.lang.Thread.UncaughtExceptionHandler;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.ice.client.common.ActionTree;
import org.eclipse.ice.client.widgets.viz.service.IPlot;
import org.eclipse.ice.viz.plotviewer.CSVDataLoader;
import org.eclipse.ice.viz.plotviewer.CSVDataProvider;
import org.eclipse.ice.viz.plotviewer.CSVPlotEditor;
import org.eclipse.ice.viz.plotviewer.PlotProvider;
import org.eclipse.ice.viz.plotviewer.SeriesProvider;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MenuListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
/**
* This class implements the IPlot interface to provide access to a basic CSV
* plot using the existing CSV infrastructure in ICE.
*
* In addition to the IPlot operations it provides the load() operation that
* should be called after construction.
*
* @author Jay Jay Billings, Anna Wojtowicz
*
*/
public class CSVPlot implements IPlot {
/**
* The source of the data for this plot
*/
private URI source;
/**
* The plot properties
*/
private final Map<String, String> properties;
/**
* The types that this plot can assume
*/
private final Map<String, String[]> types;
/**
* The CSVDataProvider used to store the loaded CSV data.
*/
private CSVDataProvider baseProvider;
/**
* 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 final Map<Composite, DrawnPlot> drawnPlots;
/**
* The Constructor
*
* @param source
* The URI of the CSV file.
*/
public CSVPlot(URI source) {
this.source = source;
// Create the property map, empty by default
properties = new HashMap<String, String>();
// Create the plot type map and add empty arrays by default
types = new HashMap<String, String[]>();
// Create the map of drawn plots.
drawnPlots = new HashMap<Composite, DrawnPlot>();
return;
}
/**
* This operation loads the data that will be plotted. It uses a separate
* thread to avoid hanging the UI in the event that the file is large. It
* does not attempt to load the file if the source is null.
*
* @throws Exception
*/
public void load() {
if (source != null) {
// Create the loading thread
Thread loadingThread = new Thread(new Runnable() {
@Override
public void run() {
// Create a file handle from the source
File file = new File(source);
// Get a CSV loader and try to load the file
if (file.getName().endsWith("csv")) {
// Load the file
CSVDataLoader dataLoader = new CSVDataLoader();
try {
baseProvider = dataLoader.load(file);
} catch (Exception e) {
throw new RuntimeException(e);
}
// Set the source so the title and everything gets
// loaded right later
baseProvider.setSource(source.toString());
// Get the variables
ArrayList<String> variables = baseProvider
.getFeatureList();
// Set the first feature as an independent variable
baseProvider.setFeatureAsIndependentVariable(variables
.get(0));
// Create lists to hold the plot types
ArrayList<String> plotTypes = new ArrayList<String>(
variables.size());
// Create the type list. Loop over every variable and
// make it possible to plot it against the others.
for (int i = 0; i < variables.size(); i++) {
for (int j = 0; j < variables.size(); j++) {
if (i != j) {
String type = variables.get(i) + " vs. "
+ variables.get(j);
plotTypes.add(type);
}
}
}
// 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);
}
}
});
// Get a handle on the parent thread's exception handler
final UncaughtExceptionHandler parentHandler = Thread
.currentThread().getUncaughtExceptionHandler();
// Override the loadingThread's exception handler
loadingThread
.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// Pass the exception to the parent thread's handler
parentHandler.uncaughtException(t, e);
}
});
// Start the thread
loadingThread.start();
}
return;
}
/**
* @see org.eclipse.ice.client.widgets.viz.service.IPlot#getPlotTypes()
*/
@Override
public Map<String, String[]> getPlotTypes() throws Exception {
return new HashMap<String, String[]>(types);
}
/**
* @see org.eclipse.ice.client.widgets.viz.service.IPlot#getNumberOfAxes()
*/
@Override
public int getNumberOfAxes() {
// The CSV plots are always 2D
return 2;
}
/**
* @see org.eclipse.ice.client.widgets.viz.service.IPlot#getProperties()
*/
@Override
public Map<String, String> getProperties() {
return properties;
}
/**
* @see org.eclipse.ice.client.widgets.viz.service.IPlot#setProperties(java.util.Map)
*/
@Override
public void setProperties(Map<String, String> props) throws Exception {
// Do nothing
}
/**
* @see org.eclipse.ice.client.widgets.viz.service.IPlot#getDataSource()
*/
@Override
public URI getDataSource() {
return source;
}
/**
* @see org.eclipse.ice.client.widgets.viz.service.IPlot#getSourceHost()
*/
@Override
public String getSourceHost() {
return source.getHost();
}
/**
* @see org.eclipse.ice.client.widgets.viz.service.IPlot#isSourceRemote()
*/
@Override
public boolean isSourceRemote() {
boolean retVal = false;
// If the source is null, then it is a local file. Otherwise check it
// explicitly.
if (source.getHost() != null) {
retVal = !"localhost".equals(source.getHost());
}
return retVal;
}
/**
* @see org.eclipse.ice.client.widgets.viz.service.IPlot#draw(java.lang.String,
* java.lang.String, org.eclipse.swt.widgets.Composite)
*/
@Override
public Composite draw(String category, String plotType, Composite parent)
throws Exception {
Composite child = null;
// 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);
// FIXME This only affects one series...
// Remove the previously plotted series, if any exist.
if (drawnPlot.seriesProvider != null) {
drawnPlot.plotProvider.removeSeries(plotTime,
drawnPlot.seriesProvider);
drawnPlot.seriesProvider = null;
}
// Add the specified series to the drawn plot.
addSeries(category, plotType, drawnPlot);
// We need to return the Composite used to render the CSV plot.
child = drawnPlot.editor.getPlotCanvas();
} else {
// Complain that the plot is invalid
throw new Exception("Invalid plot: category = " + category
+ ", type = " + plotType + ", provider = "
+ baseProvider.toString());
}
return child;
}
/**
* Removes the specified series from the drawn plot.
*
* @param series
* The series to remove.
* @param drawnPlot
* The drawn plot that will lose its series.
*/
private void removeSeries(SeriesProvider series, DrawnPlot drawnPlot) {
Double plotTime = baseProvider.getTimes().get(0);
drawnPlot.plotProvider.removeSeries(plotTime, series);
}
/**
* Adds a new series to the drawn plot.
*
* @param category
* The category of the series to add.
* @param type
* The type of the series to add.
* @param drawnPlot
* The drawn plot that will get a new series.
*/
private void addSeries(String category, String type, DrawnPlot drawnPlot) {
// As this is a private method, the parameters are expected to be valid.
// 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);
// Get the axes to plot
String[] axes = type.split(" ");
String axis1 = axes[0];
String axis2 = axes[2];
// 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(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
drawnPlot.seriesProvider = seriesProvider;
drawnPlot.plotProvider.addSeries(plotTime, seriesProvider);
// Add the new plot to the editor.
drawnPlot.editor.showPlotProvider(drawnPlot.plotProvider, true);
return;
}
/**
* 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;
/**
* A tree of JFace {@code Action}s for adding new series to the drawn
* plot.
*/
private final ActionTree addSeriesTree;
/**
* A tree of JFace {@code Action}s for removing plotted series from the
* drawn plot.
*/
private final ActionTree removeSeriesTree;
/**
* 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) throws Exception {
// 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);
// Get the child Composite used to render the
Composite canvas = editor.getPlotCanvas();
// Get the context Menu for the parent Composite, or create a new
// one if the parent lacks a context Menu.
Menu menu = parent.getMenu();
if (menu == null) {
MenuManager menuManager = new MenuManager();
menu = menuManager.createContextMenu(canvas);
}
// Create the ActionTrees for adding and removing series on the fly.
addSeriesTree = new ActionTree("Add Series");
removeSeriesTree = new ActionTree("Remove Series");
// Fill out the add series tree. This tree will never need to be
// updated.
for (Entry<String, String[]> e : getPlotTypes().entrySet()) {
final String category = e.getKey();
String[] types = e.getValue();
if (category != null && types != null) {
// Create the tree for the category and all its types.
ActionTree catTree = new ActionTree(category);
addSeriesTree.add(catTree);
// Create Actions for all the types. Each Action should call
// addSeries(...) with the category and type.
for (final String type : types) {
if (type != null) {
catTree.add(new ActionTree(new Action(type) {
@Override
public void run() {
addSeries(category, type, DrawnPlot.this);
}
}));
}
}
}
}
// TODO Set up the remove series tree...
// When the Menu is about to be shown, add the add/remove series
// actions to it.
menu.addMenuListener(new MenuListener() {
@Override
public void menuHidden(MenuEvent e) {
// Nothing to do.
}
@Override
public void menuShown(MenuEvent e) {
Menu menu = (Menu) e.widget;
addSeriesTree.getContributionItem().fill(menu, -1);
}
});
// Set the context Menu for the main plot canvas. The slider can
// have its own menu set later.
editor.getPlotCanvas().setMenu(menu);
return;
}
/**
* Disposes of the drawn plot and all related resources.
*/
public void dispose() {
// Nothing to do yet.
}
}
}