/* Plot XY_Data.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, v2016.04 */ 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.text.TextWindow; 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(String datasetLabel, double[]x1, double[]y1, double[]x2, double[]y2, boolean vectorField) { // Create and display plot with ajusted limits if first dataset boolean firstRun = false; if (super.plot==null || super.pw==null) { firstRun = true; super.plot = new Plot(WindowManager.makeUniqueName("Plotted Results"), "", "", Plot.DEFAULT_FLAGS); if (super.font!=null) super.plot.setFont(super.font); xLimits = Tools.getMinMax(x1); yLimits = Tools.getMinMax(y1); super.plot.setLimits(xLimits[0], xLimits[1], yLimits[0], yLimits[1]); super.pw = PWindowWithListener(plot); } // Add the dataset (with vectors, all column choices must be valid) super.plot.setColor(super.dColor, super.dColor2); if (vectorField) super.plot.drawVectors(x1, y1, x2, y2); else { super.plot.addPoints(x1, y1, Plot.toShape(super.dShape)); if (super.x2!=null) super.plot.addHorizontalErrorBars(x2); if (y2!=null) super.plot.addErrorBars(y2); } // Update plot legend, dataset tally, and axis limits if (!firstRun) super.legend += "\n"; super.legend += 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; ipanel 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; idl below). */ void promptForData() { // Do nothing if table is not available if (super.rt==null) { IJ.showMessage("No Data to Be Plotted", "Since no data is available, Plot Builder will now quit."); return; } // Define column choices for drop-down menus super.colChoices = getColumnChoices(super.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.setInsets(0, 20, 0); 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", "Data..."); gd.hideCancelButton(); gd.addDialogListener(dl); gd.addWindowFocusListener(WListener); // Display prompt at last screen position LOC_KEY = "bar.PRloc"; loc = Prefs.getLocation(LOC_KEY); if (loc!=null) { gd.centerDialog(false); gd.setLocation(loc); } // Update prompt before displaying it if (!IJ.macroRunning()) dl.dialogItemChanged(gd, null); 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 (super.plot!=null) super.plot.setLimitsToFit(true); } else { // "Reload" button pressed // Replace data source only if it is valid newRt = getTableWithListener(); if (newRT != null) super.rt = newRt; promptForData(); // Re-displaying the prompt is problematic when recording macros // due to duplicated keywords. So, we'll reset the recording if (Recorder.record) Recorder.setCommand(Recorder.getCommand()); } 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 public boolean dialogItemChanged(GenericDialog gd, java.awt.AWTEvent e) { // Read parameters from dialog x1Col = gd.getNextChoiceIndex(); x2Col = gd.getNextChoiceIndex(); y1Col = gd.getNextChoiceIndex(); y2Col = gd.getNextChoiceIndex(); vectorData = gd.getNextBoolean(); dShape = gd.getNextRadioButton(); 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) x1 = validColumn(super.colChoices[x1Col])?super.rt.getColumnAsDoubles(x1Col):null; if (!vectorData && x1==null) x1 = generateX(super.rt); validX1 = x1 != null; datasetLabel = super.colChoices[y1Col]; y1 = validColumn(datasetLabel)?super.rt.getColumnAsDoubles(y1Col):null; validY1 = y1 != null; x2 = validColumn(super.colChoices[x2Col])?super.rt.getColumnAsDoubles(x2Col):null; validX2 = x2 != null; y2 = validColumn(super.colChoices[y2Col])?super.rt.getColumnAsDoubles(y2Col):null; validY2 = y2 != null; // Assess if all dataset values are valid and plot/table remain available boolean validData = true; if (vectorData) validData = validX1 && validY1 && validX2 && validY2; else validData = validX1 && validY1; // Set parameters super.vectorData = vectorData; super.dShape = dShape; super.dColor = dColor; // Update dialog components if dialog is being displayed if (e != null && !IJ.macroRunning()) { super.datasetButton.setEnabled(!super.rtClosed && validData); // Update choice labels gd.getComponent(0).setText(vectorData?"X-start (X1)":"X-values"); gd.getComponent(0).setForeground(validX1?Color.BLACK:Color.GRAY); gd.getComponent(2).setText(vectorData?"X-end (X2)":"X-error bars"); gd.getComponent(2).setForeground(validX2?Color.BLACK:Color.GRAY); gd.getComponent(4).setText(vectorData?"Y-start (Y1)":"Y-values"); gd.getComponent(4).setForeground(validY1?Color.BLACK:Color.GRAY); gd.getComponent(6).setText(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 (super.pwClosed) { super.pw = PWindowWithListener(super.plot); // Redisplay plot } else if (!super.rtClosed) { // Plot data and auto-select new defaults for next dataset if (validData) { addDataset(datasetLabel, x1, y1, x2, x2, vectorData); super.datasetButton.setLabel("Add Dataset "+ (super.datasetCounter+1)); } else { IJ.showMessage("Invalid data", "Chosen column(s) do not seem to contain numeric data."); return; } 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]) } // Check if the 'Options' button has been pressed } else if (e != null && e.toString().contains("Options")) { showOptions(gd.getParent()); // Plot a single dataset if called from macro } else if (validData && IJ.macroRunning()) addDataset(datasetLabel, x1, y1, x2, x2, vectorData); return validData; } }; /** Displays the "Options" dialog for plot customization */ public static void showOptions(Frame parentFrame) { FONTS = new String[]{Font.SANS_SERIF, Font.MONOSPACED, Font.SERIF}; ogd = new GenericDialog("Plot Builder Options", parentFrame); ogd.addChoice("Font for new plots:", FONTS, FONTS[0]); ogd.addChoice("Secondary color:", Colors.colors, super.dColor2); if (super.plot!=null) { ogd.addMessage("\n \nOptions below apply only to the current plot:"); ogd.addNumericField("Line width:", 1, 0, 3, "(Default: 1 pixel)"); ogd.addNumericField("Major ticks length:", 7, 0, 3, "(Default: 7 pixels)"); ogd.addNumericField("Minor ticks length:", 3, 0, 3, "(Default: 3 pixels)"); ogd.addNumericField("Maximum n. of intervals:", 12, 0, 3, "(Default: 12)"); } ogd.enableYesNoCancel("Apply", "Start New Plot"); ogd.hideCancelButton(); ogd.showDialog(); if (ogd.wasOKed()) { return; } else if (ogd.wasOKed()) { super.font = new Font(FONTS[ogd.getNextChoiceIndex()], Font.PLAIN, ij.gui.PlotWindow.fontSize); super.dColor2 = Colors.colors[ogd.getNextChoiceIndex()]; if (super.plot!=null) { super.plot.setLineWidth((int)ogd.getNextNumber()); super.plot.setTickLength((int)ogd.getNextNumber()); super.plot.setMinorTickLength((int)ogd.getNextNumber()); super.plot.setMaxIntervals((int)ogd.getNextNumber()); super.plot.updateImage(); } } else { // Start new plot super.datasetCounter = 0; super.legend = ""; super.plot = null; super.pw = null; } } /** * Inner class scripting the java.awt.event.WindowFocusListener * interface. We'll use it to update the main dialog on every * focus change. */ WindowFocusListener WListener = new WindowFocusListener() { public void windowGainedFocus(WindowEvent e) { updateDatasetButton(); } public void windowLostFocus(WindowEvent e) { updateDatasetButton(); } }; /** * Inner class scripting java.awt.WindowAdapter, the adapter that * receives window events. We'll use it to keep track of which windows * have been closed, so that main prompt can be updated accordingly. */ WindowAdapter WinAdapter = new WindowAdapter() { public void windowClosed(WindowEvent e) { if (e.getSource() instanceof ij.text.TextWindow) // ResultsTable super.rtClosed = true; else // Plot Window super.pwClosed = true; updateDatasetButton(); } }; /** * Displays the specified plot and returns its PlotWindow. * A WindowListener is used to monitor it is closure. */ PlotWindow PWindowWithListener(Plot plot) { PlotWindow pw = null; if (plot!=null) { super.pwClosed = false; pw = plot.show(); pw.addWindowListener(WinAdapter); } return pw; } /** * Returns the ResultsTable to be used as data input. * A WindowListener is used to monitor it is closure. */ ResultsTable getTableWithListener() { ResultsTable rt = null; rt = Utils.getTable(false, WinAdapter); if (rt!=null) super.rtClosed = false; return rt; } /** * Updates datasetButton according to the availability * of required windows when running in interactive mode. */ void updateDatasetButton() { if (!IJ.macroRunning()) { if (super.pwClosed) super.datasetButton.setLabel("Reopen Closed Plot"); else if (super.rtClosed) super.datasetButton.setLabel("Table Closed..."); else super.datasetButton.setLabel("Add Dataset "+ (super.datasetCounter+1)); super.datasetButton.setEnabled(!super.rtClosed || super.pwClosed); } } /** 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 String[] colChoices; // Array holding choices for ResultsTable columns boolean vectorData = false; // Flag setting vector field data boolean pwClosed = false; // Flag monitoring if Plotwindow has been closed boolean rtClosed = false; // Flag monitoring if ResultsTable has been closed String dShape = "line"; // Descriptive label of the dataset shape String dColor = "red"; // Descriptive label of the dataset main color String dColor2 = "black"; // Descriptive label of the dataset secondary 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 Font font = null; // The custom plot font (Advanced Options prompt) // After setting-up everything, we try retrieve valid data // from an opened ResultsTable and display the prompt super.rt = getTableWithListener(); promptForData();