/* Create_Boxplot.bsh * IJ BAR: https://github.com/tferr/Scripts#scripts * * Displays a box-and-whisker plot from data in the IJ Results table using BAR and the * JFreeChart library, bundled with Fiji. The displayed plot is highly customized to * exemplify JFreeChart[1] scripting and how to integrate JFreeChart graphs in ImageJ. * It also exemplifies how to use BAR to export JFreeCharts as vector graphics. * * NB: Data can be split into series. In addition to Minimum--[Q1|Median|Q3]--Maximum * and Average (filled ellipses), other properties are also plotted, as per [2]: * - Outliers (presence highlighted by open ellipses), values outside the 'regular * range' defined as: * Q1 - 2.0*IQR > Lower Outliers < Q1 - 1.5*IQR * Q3 + 1.5*IQR > Higher Outliers < Q3 + 2.0*IQR * - Far-outs (presence highlighted by open triangles), extreme values defined as: * Lower Far-outs < Q1 - 2.0*IQR * Upper Far-outs > Q3 + 2.0*IQR * * [1] http://www.jfree.org/jfreechart/api/javadoc/ * [2] http://www.jfree.org/jfreechart/api/javadoc/src-html/org/jfree/data/statistics/BoxAndWhiskerCalculator.html * * Tiago Ferreira, v1.0.2 2016.03 */ import bar.Utils; import bar.PlotUtils; import ij.IJ; import ij.WindowManager; import ij.gui.GenericDialog; import ij.measure.ResultsTable; import ij.plugin.frame.PlugInFrame; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.plot.PlotOrientation; import org.jfree.data.statistics.DefaultBoxAndWhiskerCategoryDataset; import java.util.Arrays; import java.util.ArrayList; import java.util.HashSet; /** Checks if an object remains undefined */ boolean unset(Object o) { return o==void || o ==null; } /** Prompts user to specify which ResultsTable columns to use as input */ boolean setupDataset(ResultsTable rt) { // Extract column headings w/ numeric data (ignore "Label" column, if present) headings = rt.getHeadings(); numIdx = 0; if (!unset(rt.getLabel(0)) || headings[0].equals("Label")) numIdx = 1; numHeadings = Arrays.copyOfRange(headings, numIdx, headings.length); numItems = headings.length - numIdx; numChoices = new boolean[numItems]; gd = new GenericDialog("Boxplot Builder"); cols = (numItems<6) ? 1 : 3; rows = (numItems%cols>0) ? numItems/cols+1 : numItems/cols; gd.addCheckboxGroup(rows, cols, numHeadings, numChoices, new String[]{"Measurements:"}); gd.setInsets(0, 0, 20); gd.addCheckbox("Plot_all (Select all measurements)", false); seriesList = new ArrayList(Arrays.asList(headings)); seriesList.add(0, "*None*"); gd.addChoice("Group data by:", seriesList.toArray(new String[numItems+1]), null); gd.addHelp("https://github.com/tferr/Scripts/tree/master/Data_Analysis#create-boxplot"); gd.showDialog(); if (gd.wasCanceled()) return false; IJ.showStatus("Building Plot..."); // Retrieve options from prompt for (i=0; isColumn equals the specified string */ boolean rowMatches(int row, String s) { if (super.singleSeries) return true; else if (super.sColIsLabel) return super.rt.getLabel(row).equals(s); else return super.rt.getStringValue(super.sColumn, row).equals(s); } ArrayList categories = new ArrayList(); // Holds categories list HashSet series = new HashSet(); // Holds series list (unique items) String sColumn = "Label"; // Heading of column defining groups (series) boolean sColIsLabel = true; // Flag tracking if sColumn is the "Label" column boolean singleSeries = false; // Flag tracking if multiple groups (series) exist PlugInFrame frame; // Frame displaying the analysis String TITLE = "Box Plot"; // Frame title // Retrieve valid data rt = Utils.getTable(); if (rt==void || rt==null) return; // Prepare dataset: Specify categories and series if (!setupDataset(rt)) return; dataset = new DefaultBoxAndWhiskerCategoryDataset(); // Assemble dataset: Populate lists n = rt.getCounter(); for (s : series) { for (c : categories) { values = new ArrayList(); for (row=0; row10) { plot.setOrientation(PlotOrientation.HORIZONTAL); cp.setPreferredSize(new java.awt.Dimension(height, width)); } else cp.setPreferredSize(new java.awt.Dimension(width, height)); // Tweak: Ensure chart is always drawn and not scaled to avoid rendering artifacts cp.setMinimumDrawWidth(0); cp.setMaximumDrawWidth(Integer.MAX_VALUE); cp.setMinimumDrawHeight(0); cp.setMaximumDrawHeight(Integer.MAX_VALUE); // Tweak: Make JTooltips displaying computed data less transient cp.setDismissDelay(5 * cp.getDismissDelay()); // Tweak: Support mouse wheel and provide feedback in IJ while navigating ChartPanel cp.setMouseWheelEnabled(true); cp.addMouseMotionListener(new MouseAdapter() { public void mouseMoved(MouseEvent me) { p = me.getPoint(); plotArea = cp.getScreenDataArea(); sPos = (plot.getOrientation()==PlotOrientation.VERTICAL) ? p.getY() : p.getX(); pPos = plot.getRangeAxis().java2DToValue(sPos, plotArea, plot.getRangeAxisEdge()); IJ.showStatus(IJ.d2s(pPos,3) +"... Mouse over datasets for details | Right-click for options..."); } }); // Tweak: Add right-click options for SVG and PDF export menu = cp.getPopupMenu(); menu.addSeparator(); mi = new JMenuItem("Export as PDF..."); mi.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { PlotUtils.exportChartAsPDF(chart, cp.getBounds()); } }); menu.add(mi); mi = new JMenuItem("Export as SVG..."); mi.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { PlotUtils.exportChartAsSVG(chart, cp.getBounds()); } }); menu.add(mi); // Display analysis super.frame.add(cp); super.frame.pack(); ij.gui.GUI.center(super.frame); super.frame.setVisible(true);