-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathPlot_Results.bsh
334 lines (300 loc) · 11.7 KB
/
Plot_Results.bsh
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
/* Plot_Results.bsh
* IJ BAR: https://github.com/tferr/Scripts#scripts
*
* Interactively creates a multi-series plot (XY with or without error bars,
* and vector fields) from ImageJ measurements or imported spreadsheet data.
*
* Requires BAR_-X.X.X.jar. To use it without BAR append the getResultsTable()
* method in bar.Utils to this file, replacing its calls accordingly.
*
* TF, v2015.08
*/
import bar.Utils;
import ij.IJ;
import ij.Prefs;
import ij.gui.DialogListener;
import ij.gui.ImageWindow;
import ij.gui.GenericDialog;
import ij.gui.NonBlockingGenericDialog;
import ij.gui.Plot;
import ij.gui.PlotDialog;
import ij.gui.PlotWindow;
import ij.measure.ResultsTable;
import ij.plugin.Colors;
import ij.plugin.frame.Recorder;
import ij.util.Tools;
import ij.WindowManager;
/**
* Adds a dataset to the script's plot according to the settings
* retrieved by the dialog listener. Plot is created if it does
* not exist, i.e., if the method has not been called before.
*/
void addDataset() {
// Do not proceed if dataset contains invalid data
if (!super.validData) {
IJ.showMessage("Invalid data",
"Chosen column(s) do not seem to contain numeric data.");
return;
}
// Create and display plot with ajusted limits if first dataset
boolean firstRun = false;
if (super.plot==null || super.pw==null) {
firstRun = true;
xLimits = Tools.getMinMax(x1);
yLimits = Tools.getMinMax(y1);
super.plot = new Plot(WindowManager.makeUniqueName("Plotted Results"),
"", "", Plot.DEFAULT_FLAGS);
super.plot.setLimits(xLimits[0], xLimits[1], yLimits[0], yLimits[1]);
super.pw = super.plot.show();
}
// Add the dataset (with vectors, all column choices must be valid)
super.plot.setColor(super.dColor);
if (super.vectorData) //
super.plot.drawVectors(super.x1, super.y1, super.x2, super.y2);
else {
super.plot.addPoints(super.x1, super.y1, Plot.toShape(super.dShape));
if (super.x2!=null)
super.plot.addHorizontalErrorBars(super.x2);
if (super.y2!=null)
super.plot.addErrorBars(super.y2);
}
// Update plot legend, dataset tally, and axis limits
if (!firstRun)
super.legend += "\n";
super.legend += super.datasetLabel;
super.plot.setColor(Color.BLACK);
super.plot.addLegend(super.legend);
super.plot.setLimitsToFit(true);
super.datasetCounter++;
}
/**
* Generates abscissae from row numbers of the specified ResultsTable.
* Useful for datasets in which only Y-values need to be plotted.
*/
double[] generateX(ResultsTable rt) {
int size = rt.getCounter();
double[] incX = new double[size];
for (int i=0; i<size; i++)
incX[i] = (double)i;
return incX;
}
/**
* Retrieves a list of column choices for the specified ResultsTable.
* The "Label" column (containing non-numeric data) is excluded).
*/
String[] getColumnChoices(ResultsTable rt) {
n = rt.getLastColumn();
cChoices = new String[n+2];
for (int i=0; i<=n; i++)
cChoices[i] = rt.getColumnHeading(i);
cChoices[n+1] = "*None*";
return cChoices;
}
/**
* Activates the Checkbox of a RadioButtonGroup() in a GenericDialog
* associated with the specified label. <code>panel</code> is the
* java.awt.Panel to which the RadioButtonGroup (java.awt.CheckboxGroup)
* was added as per ij.gui.GenericDialog.addRadioButtonGroup().
*/
void activateRadioCheckbox(Panel panel, String label) {
Component[] components = panel.getComponents();
for (int i = 0; i<components.length; i++) {
Component c = components[i];
if (c instanceof Checkbox) {
Checkbox cb = (Checkbox)c;
if (cb.getLabel().equals(label)) {
cb.setState(true);
return;
}
}
}
return;
}
/**
* Prompts the user for settings. Position of dialog is stored using
* the ImageJ preferences mechanism. All the values in the prompt are
* retrieved in real time by scripting the ij.gui.DialogListener
* interface (see anonymous inner class <code>dl</code> below).
*/
void promptForData() {
// Define column choices for drop-down menus
super.colChoices = getColumnChoices(rt);
// Build prompt if it is not being displayed
gdWindow = WindowManager.getWindow("Plot Builder");
if (!IJ.macroRunning() && gdWindow!=null) {
gdWindow.toFront();
return;
}
gd = new NonBlockingGenericDialog("Plot Builder");
gd.addChoice(" X-values", super.colChoices, "*None*");
gd.addChoice(" X-error bars", super.colChoices, "*None*");
gd.addChoice(" Y-values", super.colChoices, super.colChoices[0]);
gd.addChoice(" Y-error bars", super.colChoices, "*None*");
gd.addCheckbox("Vector field dataset", super.vectorData);
gd.addMessage("");
gd.addRadioButtonGroup("Style:", super.S_LABELS, 4, 2, super.dShape);
gd.addRadioButtonGroup("Color:", Colors.colors, 5, 2, super.dColor);
// Add the "Add Dataset" button to the dialog
super.datasetButton = new Button(" Add Dataset "
+ String.valueOf(super.datasetCounter+1) + " ");
super.datasetButton.addActionListener(gd);
buttonPanel = new Panel();
buttonPanel.add(super.datasetButton);
gd.addPanel(buttonPanel, GridBagConstraints.SOUTH, new Insets(10,0,0,0));
// Generate a 3-button prompt
gd.addHelp("");
gd.setHelpLabel("Options...");
gd.enableYesNoCancel("Close", "Reload");
gd.hideCancelButton();
gd.addDialogListener(dl);
// Display prompt at last screen position
LOC_KEY = "bar.PRloc";
loc = Prefs.getLocation(LOC_KEY);
if (loc!=null) {
gd.centerDialog(false);
gd.setLocation(loc);
}
gd.showDialog();
Prefs.saveLocation(LOC_KEY, gd.getLocation());
// Dialog closure: Update plot if user pressed "OK",
// plot dataset if a macro/script "OKayed" the prompt
if (gd.wasCanceled()) { // Dialog dismissed
return;
} if (gd.wasOKed()) { // "Close" button pressed
if (IJ.macroRunning())
addDataset();
else if (super.plot!=null)
super.plot.setLimitsToFit(true);
} else { // "Reload" button pressed
super.rt = Utils.getResultsTable();
if (rt!=null) {
// Re-displaying the prompt is problematic when recording macros
// due to duplicated keywords. So, we'll intercept the recording
if (Recorder.record)
Recorder.setCommand("Plot Results");
newPlot = (IJ.altKeyDown() || super.plot==null);
if (!newPlot)
newPlot = IJ.showMessageWithCancel("New Plot?",
"Results Table successfully reloaded. Plot it in new plot?"
+"\n(Hold \"Alt\" while reloading to skip this message).");
if (newPlot) {
super.datasetCounter = 0;
super.legend = "";
super.plot = null;
super.pw = null;
}
promptForData();
}
}
return;
}
/** Assesses if the specified ResultsTable heading contains numeric data */
boolean validColumn(String columnHeading) {
return !columnHeading.equals("Label") && !columnHeading.equals("*None*") &&
super.rt.getColumnIndex(columnHeading)!=ResultsTable.COLUMN_NOT_FOUND;
}
/**
* Inner class scripting the ij.gui.DialogListener interface. We'll
* use it to read in real-time all of the options in the prompt.
*/
DialogListener dl = new DialogListener() {
// This method is invoked by the GenericDialog upon changes
dialogItemChanged(GenericDialog gd, java.awt.AWTEvent e) {
// Read parameters from dialog
x1Col = gd.getNextChoiceIndex();
x2Col = gd.getNextChoiceIndex();
y1Col = gd.getNextChoiceIndex();
y2Col = gd.getNextChoiceIndex();
super.vectorData = gd.getNextBoolean();
super.dShape = gd.getNextRadioButton();
super.dColor = gd.getNextRadioButton();
// Define the dataset: A vector field or a X,Y series (using
// incremental x-values if user chose a non-numeric column)
super.x1 = validColumn(super.colChoices[x1Col])?super.rt.getColumnAsDoubles(x1Col):null;
if (!super.vectorData && super.x1==null)
super.x1 = generateX(super.rt);
validX1 = super.x1 != null;
super.datasetLabel = super.colChoices[y1Col];
super.y1 = validColumn(super.datasetLabel)?super.rt.getColumnAsDoubles(y1Col):null;
validY1 = super.y1 != null;
super.x2 = validColumn(super.colChoices[x2Col])?super.rt.getColumnAsDoubles(x2Col):null;
validX2 = super.x2 != null;
super.y2 = validColumn(super.colChoices[y2Col])?super.rt.getColumnAsDoubles(y2Col):null;
validY2 = super.y2 != null;
// Assess if choices are valid and plot/table remain available
if (super.vectorData)
super.validData = validX1 && validY1 && validX2 && validY2;
else
super.validData = validX1 && validY1;
window = (ImageWindow)super.pw;
pwClosed = window!=null && window.closed;
rtClosed = ResultsTable.getResultsWindow()==null;
// Update dialog components if dialog is being displayed
if (e != null && !IJ.macroRunning()) {
// Update "Add Dataset" button
if (pwClosed)
super.datasetButton.setLabel("Reopen Closed Plot");
if (rtClosed)
super.datasetButton.setLabel("Results Table Closed...");
super.datasetButton.setEnabled(!rtClosed && super.validData);
// Update choice labels
gd.getComponent(0).setText(super.vectorData?"X-start (X1)":"X-values");
gd.getComponent(0).setForeground(validX1?Color.BLACK:Color.GRAY);
gd.getComponent(2).setText(super.vectorData?"X-end (X2)":"X-error bars");
gd.getComponent(2).setForeground(validX2?Color.BLACK:Color.GRAY);
gd.getComponent(4).setText(super.vectorData?"Y-start (Y1)":"Y-values");
gd.getComponent(4).setForeground(validY1?Color.BLACK:Color.GRAY);
gd.getComponent(6).setText(super.vectorData?"Y-end (Y2)":"Y-error bars");
gd.getComponent(6).setForeground(validY2?Color.BLACK:Color.GRAY);
}
// Check if the 'Add dataset' button has been pressed
if (e != null && e.getSource()==super.datasetButton) {
if (pwClosed) {
super.pw = super.plot.show(); // Redisplay plot
} else if (!rtClosed) {
// Plot data and auto-select new defaults for next dataset
addDataset();
choices = gd.getChoices();
y1idx = (super.datasetCounter % (super.colChoices.length-1));
choices.elementAt(2).select(y1idx);
cIdx = (super.datasetCounter % (Colors.colors.length-1));
colorRadioButtonGroupPanel = gd.getComponent(13); // empirically determined
activateRadioCheckbox(colorRadioButtonGroupPanel, Colors.colors[cIdx]);
//x1idx = (y1idx+1 % super.colChoices.length);
//choices.elementAt(0).select(x1idx);
//shapeRadioButtonGroupPanel = gd.getComponent(11);
//activateRadioCheckbox(shapeRadioButtonGroupPanel, S_LABELS[cIdx])
}
super.datasetButton.setLabel("Add Dataset "+ String.valueOf(super.datasetCounter+1));
// Check if the 'Options' button has been pressed
} else if (e != null && e.toString().contains("Options")) {
if (super.plot==null)
IJ.showMessage("No data",
"At least one dataset must be added\nto the plot before customizing it.");
else
new PlotDialog(super.plot, PlotDialog.AXIS_OPTIONS).showDialog(super.pw);
}
return true;
}
};
/** Definitions of dataset shapes as per ij.gui.Plot.toShape() */
String[] S_LABELS = {"line","x","box","connected","triangle","dot","circle","cross"};
/** Parameters */
ResultsTable rt; // ResultsTable from which data is retrieved
double[] x1, y1, x2, y2; // Dataset arrays (ResultsTable numeric columns)
String[] colChoices; // Array holding choices for ResultsTable columns
String datasetLabel; // The ResultsTable heading describing the dataset
boolean validData = true; // Flag monitoring if all dataset values are valid
boolean vectorData = false; // Flag monitoring if dataset is a vector field
String dShape = "line"; // Descriptive label of the dataset shape
String dColor = "red"; // Descriptive label of the dataset color
int datasetCounter = 0; // The number of datasets being plotted
Plot plot = null; // The plot produced by this script
PlotWindow pw = null; // The plot's window
String legend = ""; // The plot legend
Button datasetButton; // The dialog button used to append datasets to the plot
// After setting-up everything, we try to retrieve valid data from
// the Results table. If the table is valid, we display the prompt:
rt = Utils.getResultsTable();
if (rt!=null)
promptForData();