package ij.gui;
import java.awt.*;
import java.util.*;
import java.io.*;
import java.lang.reflect.Method;
import java.awt.geom.Point2D;
import ij.*;
import ij.process.*;
import ij.util.*;
import ij.plugin.Colors;
import ij.plugin.filter.Analyzer;
import ij.macro.Interpreter;
import ij.measure.Calibration;
import ij.measure.Measurements;
import ij.measure.ResultsTable;
public class Plot implements Cloneable {
public static final int LEFT=ImageProcessor.LEFT_JUSTIFY, CENTER=ImageProcessor.CENTER_JUSTIFY, RIGHT=ImageProcessor.RIGHT_JUSTIFY;
public static final int TOP_LEFT=0x90, TOP_RIGHT=0xA0, BOTTOM_LEFT=0xB0, BOTTOM_RIGHT=0xC0, AUTO_POSITION=0x80;
static final int LEGEND_POSITION_MASK = 0xf0;
public static final int LEGEND_BOTTOM_UP = 0x100;
public static final int LEGEND_TRANSPARENT = 0x200;
public static final int CIRCLE = 0;
public static final int X = 1;
public static final int LINE = 2;
public static final int BOX = 3;
public static final int TRIANGLE = 4;
public static final int CROSS = 5;
public static final int DOT = 6;
public static final int CONNECTED_CIRCLES = 7;
public static final int DIAMOND = 8;
public static final int CUSTOM = 9;
public static final int FILLED = 10;
public static final int BAR = 11;
public static final int SEPARATED_BAR = 12;
final static String[] SHAPE_NAMES = new String[] {
"Circle", "X", "Line", "Box", "Triangle", "+", "Dot", "Connected Circles", "Diamond",
"Custom", "Filled", "Bar", "Separated Bars"};
final static String[] SORTED_SHAPES = new String[] {
SHAPE_NAMES[LINE], SHAPE_NAMES[CONNECTED_CIRCLES], SHAPE_NAMES[FILLED], SHAPE_NAMES[BAR], SHAPE_NAMES[SEPARATED_BAR],
SHAPE_NAMES[CIRCLE], SHAPE_NAMES[BOX], SHAPE_NAMES[TRIANGLE], SHAPE_NAMES[CROSS],
SHAPE_NAMES[DIAMOND], SHAPE_NAMES[X], SHAPE_NAMES[DOT]};
public static final int X_NUMBERS = 0x1;
public static final int Y_NUMBERS = 0x2;
public static final int X_TICKS = 0x4;
public static final int Y_TICKS = 0x8;
public static final int X_GRID = 0x10;
public static final int Y_GRID = 0x20;
public static final int X_FORCE2GRID = 0x40;
public static final int Y_FORCE2GRID = 0x80;
public static final int X_MINOR_TICKS = 0x100;
public static final int Y_MINOR_TICKS = 0x200;
public static final int X_LOG_NUMBERS = 0x400;
public static final int Y_LOG_NUMBERS = 0x800;
public static final int X_LOG_TICKS = 0x1000;
public static final int Y_LOG_TICKS = 0x2000;
public static final int DEFAULT_FLAGS = X_NUMBERS + Y_NUMBERS +
X_GRID + Y_GRID + X_LOG_TICKS + Y_LOG_TICKS;
public static final int X_RANGE = 0x1;
public static final int Y_RANGE = 0x2;
static final int ALL_AXES_RANGE = X_RANGE | Y_RANGE;
public static final int COPY_SIZE = 0x10;
public static final int COPY_LABELS = 0x20;
public static final int COPY_LEGEND = 0x40;
public static final int COPY_AXIS_STYLE = 0x80;
public static final int COPY_CONTENTS_STYLE = 0x100;
public static final int COPY_EXTRA_OBJECTS = 0x200;
public static final int LEFT_MARGIN = 65;
public static final int RIGHT_MARGIN = 18;
public static final int TOP_MARGIN = 15;
public static final int BOTTOM_MARGIN = 40;
public static final int MIN_FRAMEWIDTH = 160;
public static final int MIN_FRAMEHEIGHT = 90;
public static final String PROPERTY_KEY = "thePlot";
static final float DEFAULT_FRAME_LINE_WIDTH = 1.0001f; private static final int MIN_X_GRIDSPACING = 45; private static final int MIN_Y_GRIDSPACING = 30; private final double MIN_LOG_RATIO = 3; private static final int LEGEND_PADDING = 4; private static final int LEGEND_LINELENGTH = 20; private static final int USUALLY_ENLARGE = 1, ALWAYS_ENLARGE = 2; private static final double RELATIVE_ARROWHEAD_SIZE = 0.2; private static final int MIN_ARROWHEAD_LENGTH = 3;
private static final int MAX_ARROWHEAD_LENGTH = 20;
private static final String MULTIPLY_SYMBOL = "\u00B7";
PlotProperties pp = new PlotProperties(); PlotProperties ppSnapshot; Vector<PlotObject> allPlotObjects = new Vector<PlotObject>(); Vector<PlotObject> allPlotObjectsSnapshot; private PlotVirtualStack stack;
float scale = 1.0f;
Rectangle frame = null; int leftMargin = LEFT_MARGIN, rightMargin = RIGHT_MARGIN, topMargin = TOP_MARGIN, bottomMargin = BOTTOM_MARGIN;
int frameWidth; int frameHeight; int preferredPlotWidth = PlotWindow.plotWidth; int preferredPlotHeight = PlotWindow.plotHeight;
double xMin = Double.NaN, xMax, yMin, yMax; double[] currentMinMax = new double[]{Double.NaN, 0, Double.NaN, 0}; double[] defaultMinMax = new double[]{Double.NaN, 0, Double.NaN, 0}; double[] savedMinMax = new double[]{Double.NaN, 0, Double.NaN, 0}; int[] enlargeRange; boolean logXAxis, logYAxis; int templateFlags = COPY_SIZE | COPY_LABELS | COPY_AXIS_STYLE | COPY_CONTENTS_STYLE | COPY_LEGEND;
private int dsize = PlotWindow.getDefaultFontSize();
Font defaultFont = FontUtil.getFont("Arial",Font.PLAIN,dsize); Font currentFont = defaultFont; private double xScale, yScale; private int xBasePxl, yBasePxl; private int maxIntervals = 12; private int tickLength = 7; private int minorTickLength = 3; private Color gridColor = new Color(0xc0c0c0); private ImageProcessor ip;
private ImagePlus imp; private String title;
private boolean invertedLut; private boolean plotDrawn;
PlotMaker plotMaker; private Color currentColor; private Color currentColor2; float currentLineWidth;
private int currentJustification = LEFT;
private boolean ignoreForce2Grid; private static double SEPARATED_BAR_WIDTH=0.5; double[] steps; private int objectToReplace = -1; private Point2D.Double textLoc; private int textIndex;
public Plot(String title, String xLabel, String yLabel) {
this(title, xLabel, yLabel, (float[])null, (float[])null, getDefaultFlags());
}
public Plot(String title, String xLabel, String yLabel, float[] x, float[] y) {
this(title, xLabel, yLabel, x, y, getDefaultFlags());
}
public Plot(String title, String xLabel, String yLabel, double[] x, double[] y) {
this(title, xLabel, yLabel, x!=null?Tools.toFloat(x):null, y!=null?Tools.toFloat(y):null, getDefaultFlags());
}
public Plot(String title, String xLabel, String yLabel, int flags) {
this(title, xLabel, yLabel, (float[])null, (float[])null, flags);
}
public Plot(String title, String xLabel, String yLabel, float[] xValues, float[] yValues, int flags) {
this.title = title;
pp.axisFlags = flags;
setXYLabels(xLabel, yLabel);
if (yValues != null && yValues.length>0) {
addPoints(xValues, yValues, null, LINE, null);
allPlotObjects.get(0).flags = PlotObject.CONSTRUCTOR_DATA;
}
}
public Plot(String title, String xLabel, String yLabel, double[] x, double[] y, int flags) {
this(title, xLabel, yLabel, x!=null?Tools.toFloat(x):null, y!=null?Tools.toFloat(y):null, flags);
}
public Plot(ImagePlus imp, InputStream is) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(is);
pp = (PlotProperties)in.readObject();
allPlotObjects = (Vector<PlotObject>)in.readObject();
in.close();
if (pp.xLabel.type==8) {
pp.xLabel.updateType(); pp.yLabel.updateType();
pp.frame.updateType();
if (pp.legend != null) pp.legend.updateType();
for (PlotObject plotObject : allPlotObjects)
plotObject.updateType();
}
defaultMinMax = pp.rangeMinMax;
currentFont = nonNullFont(pp.frame.getFont(), currentFont); getProcessor(); this.title = imp != null ? imp.getTitle() : "Untitled Plot";
if (imp != null) {
this.imp = imp;
ip = imp.getProcessor();
imp.setIgnoreGlobalCalibration(true);
adjustCalibration(imp.getCalibration());
imp.setProperty(PROPERTY_KEY, this);
}
}
public Plot(String dummy, String title, String xLabel, String yLabel, float[] x, float[] y) {
this(title, xLabel, yLabel, x, y, getDefaultFlags());
}
void toStream(OutputStream os) throws IOException {
for (PlotObject plotObject : pp.getAllPlotObjects()) if (plotObject != null)
plotObject.setFont(nonNullFont(plotObject.getFont(), currentFont));
pp.rangeMinMax = currentMinMax;
ObjectOutputStream out = new ObjectOutputStream(os);
out.writeObject(pp);
out.writeObject(allPlotObjects);
}
public byte[] toByteArray() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
toStream(bos);
bos.close();
return bos.toByteArray();
} catch (Exception e) {
IJ.handleException(e);
return null;
}
}
public String getTitle() {
return imp == null ? title : imp.getTitle();
}
public void setLimits(double xMin, double xMax, double yMin, double yMax) {
setLimitsNoUpdate(xMin, xMax, yMin, yMax);
makeLimitsDefault();
ignoreForce2Grid = true;
if (plotDrawn)
setLimitsToDefaults(true);
}
void setLimitsNoUpdate(double xMin, double xMax, double yMin, double yMax) {
boolean containsNaN = (Double.isNaN(xMin + xMax + yMin + yMax));
if (containsNaN && getNumPlotObjects(PlotObject.XY_DATA|PlotObject.ARROWS, false)==0) return;
double[] range = {xMin, xMax, yMin, yMax};
if (containsNaN) { double[] extrema = getMinAndMax(true, ALL_AXES_RANGE);
boolean[] auto = new boolean[range.length];
for (int i = 0; i < range.length; i++)
if (Double.isNaN(range[i])) {
auto[i] = true;
range[i] = extrema[i];
}
for (int a = 0; a<range.length; a+=2) { if (auto[a] == auto[a+1]) continue; boolean currentAxisReverse = defaultMinMax[a+1] < defaultMinMax[a];
if ((!currentAxisReverse && range[a+1] <= range[a]) || (currentAxisReverse && range[a] <= range[a+1])) {
auto[a] = true; auto[a+1] = true;
range[a] = extrema[a];
range[a+1] = extrema[a+1];
}
}
for (int i = 0; i < range.length; i++)
if (!auto[i]) enlargeRange[i] = 0;
enlargeRange(range); }
System.arraycopy(range, 0, currentMinMax, 0, Math.min(range.length, currentMinMax.length));
ignoreForce2Grid = true;
}
void makeLimitsDefault() {
System.arraycopy(currentMinMax, 0, defaultMinMax, 0, Math.min(currentMinMax.length, defaultMinMax.length));
}
public double[] getLimits() {
return currentMinMax.clone(); }
public void setLimits(double[] limits) {
System.arraycopy(limits, 0, currentMinMax, 0, Math.min(limits.length, defaultMinMax.length));
}
public void setOptions(String options) {
pp.frame.options = options.toLowerCase();
}
public void setSize(int width, int height) {
if (ip != null && width == ip.getWidth() && height == ip.getHeight())
return;
Dimension minSize = getMinimumSize();
pp.width = Math.max(width, minSize.width);
pp.height = Math.max(height, minSize.height);
scale = 1.0f;
ip = null;
if (plotDrawn) updateImage();
}
public Dimension getSize() {
if (ip == null)
getBlankProcessor();
return new Dimension(ip.getWidth(), ip.getHeight());
}
public void setFrameSize(int width, int height) {
if (pp.width <= 0) { preferredPlotWidth = width;
preferredPlotHeight = height;
scale = 1.0f;
} else {
makeMarginValues();
width += leftMargin+rightMargin;
height += topMargin+bottomMargin;
setSize(width, height);
}
}
public void setWindowSize(int width, int height) {
scale = 1.0f;
makeMarginValues();
int titleBarHeight = 22;
int infoHeight = 11;
double scale = Prefs.getGuiScale();
if (scale>1.0)
infoHeight = (int)(infoHeight*scale);
int buttonPanelHeight = 45;
if (pp.width <= 0) { int extraWidth = leftMargin+rightMargin+ImageWindow.HGAP*2;
int extraHeight = topMargin+bottomMargin+titleBarHeight+infoHeight+buttonPanelHeight;
if (extraWidth<width)
width -= extraWidth;
if (extraHeight<height)
height -= extraHeight;
preferredPlotWidth = width;
preferredPlotHeight = height;
} else {
int extraWidth = ImageWindow.HGAP*2;
int extraHeight = titleBarHeight+infoHeight+buttonPanelHeight;
if (extraWidth<width)
width -= extraWidth;
if (extraHeight<height)
height -= extraHeight;
setSize(width, height);
}
}
public Dimension getMinimumSize() {
return new Dimension(MIN_FRAMEWIDTH + leftMargin + rightMargin,
MIN_FRAMEHEIGHT + topMargin + bottomMargin);
}
public void useTemplate(Plot plot) {
useTemplate(plot, templateFlags);
}
public void useTemplate(Plot plot, int templateFlags) {
if (plot == null) return;
this.defaultFont = plot.defaultFont;
this.currentFont = plot.currentFont;
this.currentLineWidth = plot.currentLineWidth;
this.currentColor = plot.currentColor;
if ((templateFlags & COPY_AXIS_STYLE) != 0) {
this.pp.axisFlags = plot.pp.axisFlags;
this.pp.frame = plot.pp.frame.deepClone();
}
if ((templateFlags & COPY_LABELS) != 0) {
this.pp.xLabel.label = plot.pp.xLabel.label;
this.pp.yLabel.label = plot.pp.yLabel.label;
this.pp.xLabel.setFont(plot.pp.xLabel.getFont());
this.pp.yLabel.setFont(plot.pp.yLabel.getFont());
}
for (int i=0; i<currentMinMax.length; i++)
if ((templateFlags>>(i/2)&0x1) != 0) {
currentMinMax[i] = plot.currentMinMax[i];
if (!plotDrawn) defaultMinMax[i] = plot.currentMinMax[i];
}
if ((templateFlags & COPY_LEGEND) != 0 && plot.pp.legend != null)
this.pp.legend = plot.pp.legend.deepClone();
if ((templateFlags & (COPY_LEGEND | COPY_CONTENTS_STYLE)) != 0) {
int plotPObjectIndex = 0; int plotPObjectsSize = plot.allPlotObjects.size();
for (PlotObject plotObject : allPlotObjects) {
if (plotObject.type == PlotObject.XY_DATA && !plotObject.hasFlag(PlotObject.HIDDEN)) {
while(plotPObjectIndex<plotPObjectsSize &&
(plot.allPlotObjects.get(plotPObjectIndex).type != PlotObject.XY_DATA ||
plot.allPlotObjects.get(plotPObjectIndex).hasFlag(PlotObject.HIDDEN)))
plotPObjectIndex++; if (plotPObjectIndex>=plotPObjectsSize) break;
if ((templateFlags & COPY_LEGEND) != 0)
plotObject.label = plot.allPlotObjects.get(plotPObjectIndex).label;
if ((templateFlags & COPY_CONTENTS_STYLE) != 0)
setPlotObjectStyle(plotObject, getPlotObjectStyle(plot.allPlotObjects.get(plotPObjectIndex)));
plotPObjectIndex++;
}
}
}
if ((templateFlags & COPY_SIZE) != 0)
setSize(plot.pp.width, plot.pp.height);
if ((templateFlags & COPY_EXTRA_OBJECTS) != 0)
for (int p = allPlotObjects.size(); p < plot.allPlotObjects.size(); p++)
allPlotObjects.add(plot.allPlotObjects.get(p));
this.templateFlags = templateFlags;
}
public void setScale(float scale) {
this.scale = scale;
if (scale > 20f) scale = 20f;
if (scale < 0.7f) scale = 0.7f;
pp.width = sc(pp.width);
pp.height = sc(pp.height);
plotDrawn = false;
}
public void setXYLabels(String xLabel, String yLabel) {
pp.xLabel.label = xLabel!=null ? xLabel : "";
pp.yLabel.label = yLabel!=null ? yLabel : "";
}
public void setMaxIntervals(int intervals) {
maxIntervals = intervals;
}
public void setTickLength(int tickLength) {
tickLength = tickLength;
}
public void setMinorTickLength(int minorTickLength) {
minorTickLength = minorTickLength;
}
public void setFormatFlags(int flags) {
int unchangedFlags = X_LOG_NUMBERS | Y_LOG_NUMBERS | X_FORCE2GRID | Y_FORCE2GRID;
flags = flags & (~unchangedFlags); pp.axisFlags = (pp.axisFlags & unchangedFlags) | flags;
}
public int getFlags() {
return pp.axisFlags;
}
public void setAxisXLog(boolean axisXLog) {
pp.axisFlags = axisXLog ? pp.axisFlags | X_LOG_NUMBERS : pp.axisFlags & (~X_LOG_NUMBERS);
}
public void setAxisYLog(boolean axisYLog) {
pp.axisFlags = axisYLog ? pp.axisFlags | Y_LOG_NUMBERS : pp.axisFlags & (~Y_LOG_NUMBERS);
}
public void setXTicks(boolean xTicks) {
pp.axisFlags = xTicks ? pp.axisFlags | X_TICKS : pp.axisFlags & (~X_TICKS);
}
public void setYTicks(boolean yTicks) {
pp.axisFlags = yTicks ? pp.axisFlags | Y_TICKS : pp.axisFlags & (~Y_TICKS);
}
public void setXMinorTicks(boolean xMinorTicks) {
pp.axisFlags = xMinorTicks ? pp.axisFlags | X_MINOR_TICKS : pp.axisFlags & (~X_MINOR_TICKS);
if (xMinorTicks && !hasFlag(X_GRID))
pp.axisFlags |= X_TICKS;
}
public void setYMinorTicks(boolean yMinorTicks) {
pp.axisFlags = yMinorTicks ? pp.axisFlags | Y_MINOR_TICKS : pp.axisFlags & (~Y_MINOR_TICKS);
if (yMinorTicks && !hasFlag(Y_GRID))
pp.axisFlags |= Y_TICKS;
}
public void setAxes(boolean xLog, boolean yLog, boolean xTicks, boolean yTicks, boolean xMinorTicks, boolean yMinorTicks,
int tickLenght, int minorTickLenght) {
setAxisXLog (xLog);
setAxisYLog (yLog);
setXMinorTicks (xMinorTicks);
setYMinorTicks (yMinorTicks);
setXTicks (xTicks);
setYTicks (yTicks);
setTickLength (tickLenght);
setMinorTickLength(minorTickLenght);
}
public void setLogScaleX() {
setAxisXLog(true);
}
public void setLogScaleY() {
setAxisYLog(true);
}
public static int getDefaultFlags() {
int defaultFlags = 0;
if (!PlotWindow.noGridLines) defaultFlags |= X_GRID | Y_GRID | X_NUMBERS | Y_NUMBERS | X_LOG_TICKS | Y_LOG_TICKS;
if (!PlotWindow.noTicks)
defaultFlags |= X_TICKS | Y_TICKS | X_MINOR_TICKS | Y_MINOR_TICKS | X_NUMBERS | Y_NUMBERS | X_LOG_TICKS | Y_LOG_TICKS;
return defaultFlags;
}
public void add(String type, double[] xvalues, double[] yvalues) {
int iShape = toShape(type);
addPoints(Tools.toFloat(xvalues), Tools.toFloat(yvalues), null, iShape, iShape==CUSTOM?type.substring(5, type.length()):null);
}
public void replace(int index, String type, double[] xvalues, double[] yvalues) {
if (index>=0 && index<allPlotObjects.size()) {
objectToReplace = allPlotObjects.size()>0?index:-1;
add(type, xvalues, yvalues);
}
}
public void add(String type, double[] yvalues) {
int iShape = toShape(type);
if (iShape==-1)
addErrorBars(yvalues);
else if (iShape==-2)
addHorizontalErrorBars(yvalues);
else
addPoints(null, Tools.toFloat(yvalues), null, iShape, iShape==CUSTOM?type.substring(5, type.length()):null);
}
public void addPoints(float[] xValues, float[] yValues, float[] yErrorBars, int shape, String label) {
if (xValues==null || xValues.length==0) {
xValues = new float[yValues.length];
for (int i=0; i<yValues.length; i++)
xValues[i] = i;
}
if (objectToReplace>=0)
allPlotObjects.set(objectToReplace, new PlotObject(xValues, yValues, yErrorBars, shape, currentLineWidth, currentColor, currentColor2, label));
else
allPlotObjects.add(new PlotObject(xValues, yValues, yErrorBars, shape, currentLineWidth, currentColor, currentColor2, label));
objectToReplace = -1;
if (plotDrawn) updateImage();
}
public void addPoints(float[] x, float[] y, int shape) {
addPoints(x, y, null, shape, null);
}
public void addPoints(double[] x, double[] y, int shape) {
addPoints(Tools.toFloat(x), Tools.toFloat(y), shape);
}
public static int toShape(String str) {
str = str.toLowerCase(Locale.US);
int shape = Plot.CIRCLE;
if (str.contains("curve") || str.contains("line"))
shape = Plot.LINE;
else if (str.contains("connected"))
shape = Plot.CONNECTED_CIRCLES;
else if (str.contains("filled"))
shape = Plot.FILLED;
else if (str.contains("circle"))
shape = Plot.CIRCLE;
else if (str.contains("box"))
shape = Plot.BOX;
else if (str.contains("triangle"))
shape = Plot.TRIANGLE;
else if (str.contains("cross") || str.contains("+"))
shape = Plot.CROSS;
else if (str.contains("diamond"))
shape = Plot.DIAMOND;
else if (str.contains("dot"))
shape = Plot.DOT;
else if (str.contains("xerror"))
shape = -2;
else if (str.contains("error"))
shape = -1;
else if (str.contains("x"))
shape = Plot.X;
else if (str.contains("separate"))
shape = Plot.SEPARATED_BAR;
else if (str.contains("bar"))
shape = Plot.BAR;
if (str.startsWith("code:"))
shape = CUSTOM;
return shape;
}
public void addPoints(ArrayList x, ArrayList y, int shape) {
addPoints(getDoubleFromArrayList(x), getDoubleFromArrayList(y), shape);
}
public void addPoints(double[] x, double[] y, double[] errorBars, int shape) {
addPoints(Tools.toFloat(x), Tools.toFloat(y), Tools.toFloat(errorBars), shape, null);
}
public void addPoints(ArrayList x, ArrayList y, ArrayList errorBars, int shape) {
addPoints(getDoubleFromArrayList(x), getDoubleFromArrayList(y), getDoubleFromArrayList(errorBars), shape);
}
public double[] getDoubleFromArrayList(ArrayList list) {
if (list == null) return null;
double[] targ = new double[list.size()];
for (int i = 0; i < list.size(); i++)
targ[i] = ((Double) list.get(i)).doubleValue();
return targ;
}
public void drawVectors(double[] x1, double[] y1, double[] x2, double[] y2) {
allPlotObjects.add(new PlotObject(Tools.toFloat(x1), Tools.toFloat(y1),
Tools.toFloat(x2), Tools.toFloat(y2), currentLineWidth, currentColor));
}
public void drawShapes(String shapeType, ArrayList floatCoords) {
allPlotObjects.add(new PlotObject(shapeType, floatCoords, currentLineWidth, currentColor, currentColor2));
}
public static double calculateDistance(int x1, int y1, int x2, int y2) {
return java.lang.Math.sqrt((x2 - x1)*(double)(x2 - x1) + (y2 - y1)*(double)(y2 - y1));
}
public void drawVectors(ArrayList x1, ArrayList y1, ArrayList x2, ArrayList y2) {
drawVectors(getDoubleFromArrayList(x1), getDoubleFromArrayList(y1), getDoubleFromArrayList(x2), getDoubleFromArrayList(y2));
}
public void addErrorBars(float[] errorBars) {
PlotObject mainObject = getLastCurveObject();
if (mainObject != null)
mainObject.yEValues = errorBars;
else throw new RuntimeException("Plot can't add y error bars without data");
}
public void addErrorBars(double[] errorBars) {
addErrorBars(Tools.toFloat(errorBars));
}
public void addHorizontalErrorBars(float[] xErrorBars) {
PlotObject mainObject = getLastCurveObject();
if (mainObject != null)
mainObject.xEValues = xErrorBars;
else throw new RuntimeException("Plot can't add x error bars without data");
}
public void addHorizontalErrorBars(double[] xErrorBars) {
addHorizontalErrorBars(Tools.toFloat(xErrorBars));
}
public void addLabel(double x, double y, String label) {
if (textLoc!=null && x==textLoc.getX() && y==textLoc.getY())
allPlotObjects.set(textIndex, new PlotObject(label, x, y, currentJustification, currentFont, currentColor, PlotObject.NORMALIZED_LABEL));
else {
allPlotObjects.add(new PlotObject(label, x, y, currentJustification, currentFont, currentColor, PlotObject.NORMALIZED_LABEL));
textLoc = new Point2D.Double(x,y);
textIndex = allPlotObjects.size()-1;
}
}
public void addText(String label, double x, double y) {
allPlotObjects.add(new PlotObject(label, x, y, currentJustification, currentFont, currentColor, PlotObject.LABEL));
}
public void addLegend(String labels) {
addLegend(labels, "auto");
}
public void addLegend(String labels, String options) {
int flags = 0;
if (options!=null) {
options = options.toLowerCase();
if (options.contains("top-left"))
flags |= Plot.TOP_LEFT;
else if (options.contains("top-right"))
flags |= Plot.TOP_RIGHT;
else if (options.contains("bottom-left"))
flags |= Plot.BOTTOM_LEFT;
else if (options.contains("bottom-right"))
flags |= Plot.BOTTOM_RIGHT;
else if (!options.contains("off") && !options.contains("no"))
flags |= Plot.AUTO_POSITION;
if (options.contains("bottom-to-top"))
flags |= Plot.LEGEND_BOTTOM_UP;
if (options.contains("transparent"))
flags |= Plot.LEGEND_TRANSPARENT;
}
setLegend(labels, flags);
}
public void setLegend(String labels, int flags) {
if (labels != null && labels.length()>0) {
String[] allLabels = labels.split("[\n\t]");
int iPart = 0;
for (PlotObject plotObject : allPlotObjects)
if (plotObject.type == PlotObject.XY_DATA && !plotObject.hasFlag(PlotObject.HIDDEN))
if (iPart < allLabels.length) {
String label = allLabels[iPart++];
if (label!=null && label.length()>0)
plotObject.label = label;
}
}
pp.legend = new PlotObject(currentLineWidth == 0 ? 1 : currentLineWidth,
currentFont, currentColor == null ? Color.black : currentColor, flags);
if (plotDrawn) updateImage();
}
public void setLabel(int index, String label) {
if (index < 0) index = allPlotObjects.size() + index;
allPlotObjects.get(index).label = label;
}
public void removeNaNs() {
for (PlotObject plotObj : allPlotObjects){
if(plotObj != null && plotObj.xValues!= null && plotObj.yValues != null ){
int oldSize = plotObj.xValues.length;
float[] xVals = new float[oldSize];
float[] yVals = new float[oldSize];
int newSize = 0;
for (int kk = 0; kk < oldSize; kk++) {
if (!Float.isNaN(plotObj.xValues[kk] + plotObj.yValues[kk])) {
xVals[newSize] = plotObj.xValues[kk];
yVals[newSize] = plotObj.yValues[kk];
newSize++;
}
}
if (newSize < oldSize) {
plotObj.xValues = new float[newSize];
plotObj.yValues = new float[newSize];
System.arraycopy(xVals, 0, plotObj.xValues, 0, newSize);
System.arraycopy(yVals, 0, plotObj.yValues, 0, newSize);
}
}
}
}
public String[] getTypes() {
return SORTED_SHAPES;
}
public void setJustification(int justification) {
currentJustification = justification;
}
public void setColor(Color c) {
currentColor = c;
currentColor2 = null;
}
public void setColor(String color) {
setColor(Colors.decode(color, Color.black));
}
public void setColor(Color c, Color c2) {
currentColor = c;
currentColor2 = c2;
}
public void setColor(String c1, String c2) {
setColor(Colors.decode(c1, Color.black), Colors.decode(c2, null));
}
public void setBackgroundColor(Color c) {
pp.frame.color2 = c;
}
public void setBackgroundColor(String c) {
setBackgroundColor(Colors.decode(c,Color.white));
}
public void setLineWidth(int lineWidth) {
currentLineWidth = lineWidth > 0 ? lineWidth : 0.01f;
}
public void setLineWidth(float lineWidth) {
currentLineWidth = lineWidth > 0.01 ? lineWidth : 0.01f;
}
public void drawLine(double x1, double y1, double x2, double y2) {
allPlotObjects.add(new PlotObject(x1, y1, x2, y2, currentLineWidth, 0, currentColor, PlotObject.LINE));
}
public void drawNormalizedLine(double x1, double y1, double x2, double y2) {
allPlotObjects.add(new PlotObject(x1, y1, x2, y2, currentLineWidth, 0, currentColor, PlotObject.NORMALIZED_LINE));
}
public void drawDottedLine(double x1, double y1, double x2, double y2, int step) {
allPlotObjects.add(new PlotObject(x1, y1, x2, y2, currentLineWidth, step, currentColor, PlotObject.DOTTED_LINE));
}
public void setFontSize(int size) {
setFont(-1, (float)size);
}
public void setFont(Font font) {
if (font == null) font = defaultFont;
currentFont = font;
if (plotDrawn) {
pp.frame.setFont(font);
if (pp.legend != null)
pp.legend.setFont(font);
}
}
public void setFont(int style, float size) {
if (size < 9) size = 9f;
if (size > 36) size = 36f;
Font previousFont = nonNullFont(pp.frame.getFont(), currentFont);
if (style < 0) style = previousFont.getStyle();
setFont(previousFont.deriveFont(style, size));
}
public void setAxisLabelFont(int style, float size) {
if (size < 9) size = 9f;
if (size > 33) size = 33f;
pp.xLabel.setFont(nonNullFont(pp.xLabel.getFont(), currentFont));
pp.yLabel.setFont(nonNullFont(pp.yLabel.getFont(), currentFont));
setXLabelFont(pp.xLabel.getFont().deriveFont(style < 0 ? pp.xLabel.getFont().getStyle() : style, size));
setYLabelFont(pp.xLabel.getFont().deriveFont(style < 0 ? pp.yLabel.getFont().getStyle() : style, size));
}
public void setXLabelFont(Font font) {
pp.xLabel.setFont(font);
}
public void setYLabelFont(Font font) {
pp.yLabel.setFont(font);
}
public void setAntialiasedText(boolean antialiasedText) {
pp.antialiasedText = antialiasedText;
}
public Font getCurrentFont() {
return currentFont != null ? currentFont : defaultFont;
}
public Font getDefaultFont() {
return defaultFont;
}
public Font getFont(char c) {
PlotObject plotObject = pp.getPlotObject(c);
if (plotObject != null)
return plotObject.getFont();
else
return null;
}
public void setFont(char c, Font font) {
PlotObject plotObject = pp.getPlotObject(c);
if (plotObject != null)
plotObject.setFont(font);
}
public String getLabel(char c) {
PlotObject plotObject = pp.getPlotObject(c);
if (plotObject != null)
return plotObject.label;
else
return null;
}
public int getObjectFlags(char c) {
PlotObject plotObject = pp.getPlotObject(c);
if (plotObject != null)
return plotObject.flags;
else
return -1;
}
public float[] getXValues() {
PlotObject p = getMainCurveObject();
return p==null ? null : p.xValues;
}
public float[] getYValues() {
PlotObject p = getMainCurveObject();
return p==null ? null : p.yValues;
}
public float[][] getDataObjectArrays(int index) {
int i = 0;
for (PlotObject plotObject : allPlotObjects) {
if (plotObject.type != PlotObject.XY_DATA || plotObject.hasFlag(PlotObject.HIDDEN)) continue;
if (index == i)
return new float[][] {plotObject.xValues, plotObject.yValues, plotObject.xEValues, plotObject.yEValues};
i++;
}
return null;
}
public String[] getPlotObjectDesignations() {
return getPlotObjectDesignations(-1, true);
}
public String[] getDataObjectDesignations() {
return getPlotObjectDesignations(PlotObject.XY_DATA, false);
}
public int getNumPlotObjects() {
return allPlotObjects.size();
}
int getNumPlotObjects(int mask, boolean includeHidden) {
int nObjects = 0;
for (PlotObject plotObject : allPlotObjects)
if ((plotObject.type & mask) != 0 && (includeHidden || !plotObject.hasFlag(PlotObject.HIDDEN)))
nObjects++;
return nObjects;
}
String[] getPlotObjectDesignations(int mask, boolean includeHidden) {
int nObjects = getNumPlotObjects(mask, includeHidden);
String[] names = new String[nObjects];
if (names.length == 0) return names;
int iData = 1, iArrow = 1, iLine = 1, iText = 1, iBox = 1, iShape = 1; int i = 0;
for (PlotObject plotObject : allPlotObjects) {
int type = plotObject.type;
if ((type & mask) == 0 || (!includeHidden && plotObject.hasFlag(PlotObject.HIDDEN))) continue;
String label = plotObject.label;
switch (type) {
case PlotObject.XY_DATA:
names[i] = "Data Set "+iData+": "+(plotObject.label != null ?
plotObject.label : "(" + plotObject.yValues.length + " data points)");
iData++;
break;
case PlotObject.ARROWS:
names[i] = "Arrow Set "+iArrow+" ("+ plotObject.xValues.length + ")";
iArrow++;
break;
case PlotObject.LINE: case PlotObject.NORMALIZED_LINE: case PlotObject.DOTTED_LINE:
String detail = "";
if (type == PlotObject.DOTTED_LINE) detail = "dotted ";
if (plotObject.x ==plotObject.xEnd) detail += "vertical";
else if (plotObject.y ==plotObject.yEnd) detail += "horizontal";
if (detail.length()>0) detail = " ("+detail.trim()+")";
names[i] = "Straight Line "+iLine+detail;
iLine++;
break;
case PlotObject.LABEL: case PlotObject.NORMALIZED_LABEL:
String text = plotObject.label.replaceAll("\n"," ");
if (text.length()>45) text = text.substring(0, 40)+"...";
names[i] = "Text "+iText+": \""+text+'"';
iText++;
break;
case PlotObject.SHAPES:
String s = plotObject.shapeType;
String[] words = s.split(" ");
names[i] = "Shapes (" + words[0] +") " + iShape;
iShape++;
break;
}
i++;
}
return names;
}
public int addObjectFromPlot(Plot plot, int i) {
PlotObject plotObject = plot.getPlotObjectDeepClone(i);
plotObject.unsetFlag(PlotObject.CONSTRUCTOR_DATA);
allPlotObjects.add(plotObject);
int index = allPlotObjects.size() - 1;
return index;
}
public String getPlotObjectStyle(int i) {
return getPlotObjectStyle(allPlotObjects.get(i));
}
String getPlotObjectStyle(PlotObject plotObject) {
String styleString = Colors.colorToString(plotObject.color) + "," +
Colors.colorToString(plotObject.color2) + "," +
plotObject.lineWidth;
if (plotObject.type == PlotObject.XY_DATA)
styleString += ","+SHAPE_NAMES[plotObject.shape];
if (plotObject.hasFlag(PlotObject.HIDDEN))
styleString += ",hidden";
return styleString;
}
public String getPlotObjectLabel(int i) {
return allPlotObjects.get(i).label;
}
public void setPlotObjectLabel(int i, String label) {
allPlotObjects.get(i).label = label;
}
public void setStyle(int index, String style) {
if (index<0 || index>=allPlotObjects.size())
throw new IllegalArgumentException("Index out of range");
setPlotObjectStyle(allPlotObjects.get(index), style);
}
public void setPlotObjectStyle(int i, String styleString) {
setStyle(i, styleString);
}
void setPlotObjectStyle(PlotObject plotObject, String styleString) {
String[] items = styleString.split(",");
int nItems = items.length;
if (items[nItems-1].indexOf("hidden") >= 0) {
plotObject.setFlag(PlotObject.HIDDEN);
nItems = items.length - 1;
} else
plotObject.unsetFlag(PlotObject.HIDDEN);
plotObject.color = Colors.decode(items[0].trim(), plotObject.color);
plotObject.color2 = Colors.decode(items[1].trim(), null);
float lineWidth = plotObject.lineWidth;
if (items.length >= 3) try {
plotObject.lineWidth = Float.parseFloat(items[2].trim());
} catch (NumberFormatException e) {};
if (items.length >= 4 && plotObject.shape!=CUSTOM)
plotObject.shape = toShape(items[3].trim());
updateImage();
return;
}
public int getPlotObjectIndex(float[][] values) {
return getPlotObjectIndex(PlotObject.XY_DATA|PlotObject.ARROWS, values);
}
int getPlotObjectIndex(int typeMask, float[][] values) {
for (int i=0; i<allPlotObjects.size(); i++) {
PlotObject plotObject = allPlotObjects.get(i);
if ((plotObject.type & typeMask) == 0) continue;
float[][] plotObjectArrays = plotObject.getAllDataValues();
boolean equal = true;
for (int j=0; j<Math.min(plotObjectArrays.length, values.length); j++) {
if (!Arrays.equals(plotObjectArrays[j], values[j])) {
equal = false;
break;
}
}
if (equal) return i;
}
return -1;
}
public void savePlotObjects() {
allPlotObjectsSnapshot = new Vector<PlotObject>(allPlotObjects.size());
copyPlotObjectsVector(allPlotObjects, allPlotObjectsSnapshot);
}
public void restorePlotObjects() {
if (allPlotObjectsSnapshot != null)
copyPlotObjectsVector(allPlotObjectsSnapshot, allPlotObjects);
}
public void killPlotObjectsSnapshot() {
allPlotObjectsSnapshot = null;
}
public void savePlotPlotProperties() {
pp.rangeMinMax = currentMinMax;
ppSnapshot = pp.deepClone();
}
public void restorePlotProperties() {
pp = ppSnapshot.deepClone();
System.arraycopy(pp.rangeMinMax, 0, currentMinMax, 0, Math.min(pp.rangeMinMax.length, currentMinMax.length));
}
public void killPlotPropertiesSnapshot() {
ppSnapshot = null;
}
private void copyPlotObjectsVector(Vector<PlotObject> src, Vector<PlotObject>dest) {
if (dest.size() > 0) dest.removeAllElements();
for (PlotObject plotObject : src)
dest.add(plotObject.deepClone());
}
PlotObject getPlotObjectDeepClone(int i) {
return allPlotObjects.get(i).deepClone();
}
public void setLimitsToDefaults(boolean updateImg) {
saveMinMax();
System.arraycopy(defaultMinMax, 0, currentMinMax, 0, defaultMinMax.length);
if (plotDrawn && updateImg) updateImage();
}
public void setLimitsToFit(boolean updateImg) {
saveMinMax();
currentMinMax = getMinAndMax(true, ALL_AXES_RANGE);
if (Double.isNaN(defaultMinMax[0]) && Double.isNaN(defaultMinMax[2])) System.arraycopy(currentMinMax, 0, defaultMinMax, 0, Math.min(currentMinMax.length, defaultMinMax.length));
enlargeRange(currentMinMax); if (plotDrawn && updateImg) updateImage();
}
public void setPreviousMinMax() {
if (Double.isNaN(savedMinMax[0])) return; double[] swap = new double[currentMinMax.length];
System.arraycopy(currentMinMax, 0, swap, 0, currentMinMax.length);
System.arraycopy(savedMinMax, 0, currentMinMax, 0, currentMinMax.length);
System.arraycopy(swap, 0, savedMinMax, 0, currentMinMax.length);
updateImage();
}
public ImageProcessor getProcessor() {
draw();
return ip;
}
public ImagePlus getImagePlus() {
if (stack != null) {
if (imp != null)
return imp;
else {
imp = new ImagePlus(title, stack);
adjustCalibration(imp.getCalibration());
return imp;
}
}
if (plotDrawn)
updateImage();
else
draw();
if (imp != null) {
if (imp.getProcessor() != ip)
imp.setProcessor(ip);
return imp;
} else {
ImagePlus imp = new ImagePlus(title, ip);
setImagePlus(imp);
return imp;
}
}
public void setImagePlus(ImagePlus imp) {
if (imp != null && imp == this.imp && imp.getProcessor() == ip)
return;
if (stack != null)
return;
if (this.imp != null)
this.imp.setProperty(PROPERTY_KEY, null);
this.imp = imp;
if (imp != null) {
imp.setIgnoreGlobalCalibration(true);
adjustCalibration(imp.getCalibration());
imp.setProperty(PROPERTY_KEY, this);
if (ip != null && imp.getProcessor() != ip)
imp.setProcessor(ip);
}
}
public void adjustCalibration(Calibration cal) {
if (xMin == xMax) xScale = 1e6;
if (yMin == yMax)
yScale = 1e6;
cal.xOrigin = xBasePxl-xMin*xScale;
cal.pixelWidth = 1.0/Math.abs(xScale); cal.yOrigin = yBasePxl+yMin*yScale;
cal.pixelHeight = 1.0/Math.abs(yScale);
cal.setInvertY(yScale >= 0);
cal.setXUnit(" "); if (xMin == xMax)
xScale = Double.POSITIVE_INFINITY;
if (yMin == yMax)
yScale = Double.POSITIVE_INFINITY;
}
public PlotWindow show() {
PlotVirtualStack stack = getStack();
if (stack!=null) {
getImagePlus().show();
return null;
}
if ((IJ.macroRunning() && IJ.getInstance()==null) || Interpreter.isBatchMode()) {
imp = getImagePlus();
imp.setPlot(this);
WindowManager.setTempCurrentImage(imp);
if (getMainCurveObject() != null) {
imp.setProperty("XValues", getXValues()); imp.setProperty("YValues", getYValues()); }
Interpreter.addBatchModeImage(imp);
return null;
}
if (imp != null) {
Window win = imp.getWindow();
if (win instanceof PlotWindow && win.isVisible()) {
updateImage(); return (PlotWindow)win;
} else
setImagePlus(null);
}
PlotWindow pw = new PlotWindow(this); if (IJ.isMacro() && imp!=null) IJ.selectWindow(imp.getID());
return pw;
}
public void addToStack() {
if (stack==null)
stack = new PlotVirtualStack(getSize().width,getSize().height);
draw();
stack.addPlot(this);
IJ.showStatus("addToPlotStack: "+stack.size());
allPlotObjects.clear();
textLoc = null;
}
public void appendToStack() { addToStack(); }
public PlotVirtualStack getStack() {
IJ.showStatus("");
return stack;
}
public void draw() {
if (plotDrawn) return;
getInitialMinAndMax();
pp.frame.setFont(nonNullFont(pp.frame.getFont(), currentFont)); getBlankProcessor();
drawContents(ip);
}
public void setFrozen(boolean frozen) {
pp.isFrozen = frozen;
if (!pp.isFrozen) { if (imp != null && ip != null) {
ImageCanvas ic = imp.getCanvas();
if (ic instanceof PlotCanvas) {
((PlotCanvas)ic).resetMagnification();
imp.setTitle(imp.getTitle()); }
Undo.setup(Undo.TRANSFORM, imp);
}
updateImage();
ImageWindow win = imp == null ? null : imp.getWindow();
if (win != null) win.updateImage(imp); }
}
public boolean isFrozen() {
return pp.isFrozen;
}
public void update() {
updateImage();
}
public void updateImage() {
if (!plotDrawn || pp.isFrozen) return;
getBlankProcessor();
drawContents(ip);
if (imp == null || stack != null) return;
adjustCalibration(imp.getCalibration());
imp.updateAndDraw();
if (ip != imp.getProcessor())
imp.setProcessor(ip);
}
public Rectangle getDrawingFrame() {
if (frame == null)
getBlankProcessor(); return new Rectangle(frame.x, frame.y, frameWidth, frameHeight);
}
public ImagePlus makeHighResolution(String title, float scale, boolean antialiasedText, boolean showIt) {
Plot hiresPlot = null;
try {
hiresPlot = (Plot)clone(); } catch (Exception e) {return null;}
hiresPlot.ip = null;
hiresPlot.imp = null;
hiresPlot.pp = pp.clone();
if (!plotDrawn) hiresPlot.getInitialMinAndMax();
hiresPlot.setScale(scale);
hiresPlot.setAntialiasedText(antialiasedText);
hiresPlot.defaultMinMax = currentMinMax.clone();
ImageProcessor hiresIp = hiresPlot.getProcessor();
if (title == null || title.length() == 0)
title = getTitle()+"_HiRes";
title = WindowManager.makeUniqueName(title);
ImagePlus hiresImp = new ImagePlus(title, hiresIp);
Calibration cal = hiresImp.getCalibration();
hiresPlot.adjustCalibration(cal);
if (showIt) {
hiresImp.setIgnoreGlobalCalibration(true);
hiresImp.show();
}
hiresPlot.dispose(); return hiresImp;
}
public void dispose() {
if (imp != null)
imp.setProperty(PROPERTY_KEY, null);
imp = null;
ip = null;
}
public double descaleX(int x) {
if (xMin == xMax) return xMin;
double xv = (x-xBasePxl)/xScale + xMin;
if (logXAxis) xv = Math.pow(10, xv);
return xv;
}
public double descaleY(int y) {
if (yMin == yMax) return yMin;
double yv = (yBasePxl-y)/yScale +yMin;
if (logYAxis) yv = Math.pow(10, yv);
return yv;
}
public double scaleXtoPxl(double x) {
if (xMin == xMax) {
if (x==xMin) return xBasePxl;
else return x>xMin ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
}
if (logXAxis)
return xBasePxl+(Math.log10(x)-xMin)*xScale;
else
return xBasePxl+(x-xMin)*xScale;
}
public double scaleYtoPxl(double y) {
if (yMin == yMax) {
if (y==xMin) return yBasePxl;
else return y>yMin ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
}
if (logYAxis)
return yBasePxl-(Math.log10(y)-yMin)*yScale;
else
return yBasePxl-(y-yMin)*yScale;
}
private int scaleX(double x) {
if (Double.isNaN(x)) return -1;
if (xMin == xMax) {
if (x==xMin) return xBasePxl;
else return x>xMin ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
if (logXAxis)
return xBasePxl+(int)Math.round((Math.log10(x)-xMin)*xScale);
else
return xBasePxl+(int)Math.round((x-xMin)*xScale);
}
private int scaleY(double y) {
if (Double.isNaN(y)) return -1;
if (yMin == yMax) {
if (y==yMin) return yBasePxl;
else return y>yMin ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
if (logYAxis)
return yBasePxl-(int)Math.round((Math.log10(y)-yMin)*yScale);
else
return yBasePxl-(int)Math.round((y-yMin)*yScale);
}
private int scaleXWithOverflow(double x) {
if (!logXAxis || x>0)
return scaleX(x);
else
return xScale > 0 ? -1000000 : 1000000;
}
private int scaleYWithOverflow(double y) {
if (!logYAxis || y>0)
return scaleY(y);
else
return yScale > 0 ? 1000000 : -1000000;
}
int sc(float length) {
int pixels = (int)(length*scale + 0.5);
if (pixels < 1) pixels = 1;
return pixels;
}
Font scFont(Font font) {
float size = font.getSize2D();
return scale==1 ? font : font.deriveFont(size*scale);
}
boolean isColored() {
for (PlotObject plotObject : allPlotObjects)
if (isColored(plotObject.color) || isColored(plotObject.color2))
return true;
for (PlotObject plotObject : pp.getAllPlotObjects())
if (plotObject != null && (isColored(plotObject.color) || isColored(plotObject.color2)))
return true;
return false;
}
boolean isColored(Color c) {
if (c == null) return false;
return c.getRed() != c.getGreen() || c.getGreen() != c.getBlue();
}
void drawContents(ImageProcessor ip) {
makeRangeGetSteps();
ip.setColor(Color.black);
ip.setLineWidth(sc(1));
float lineWidth = 1;
Color color = Color.black;
Font font = defaultFont;
for (PlotObject plotObject : allPlotObjects)
if (!plotObject.hasFlag(PlotObject.CONSTRUCTOR_DATA)) {
if (plotObject.lineWidth > 0)
lineWidth = plotObject.lineWidth;
else
plotObject.lineWidth = lineWidth;
if (plotObject.color != null)
color = plotObject.color;
else
plotObject.color = color;
if (plotObject.getFont() != null)
font = plotObject.getFont();
else
plotObject.setFont(font);
drawPlotObject(plotObject, ip);
}
if (allPlotObjects.size()>0 && allPlotObjects.get(0).hasFlag(PlotObject.CONSTRUCTOR_DATA)) {
PlotObject mainPlotObject = allPlotObjects.get(0);
if (mainPlotObject.lineWidth == 0)
mainPlotObject.lineWidth = currentLineWidth == 0 ? 1 : currentLineWidth;
lineWidth = mainPlotObject.lineWidth;
if (mainPlotObject.color == null)
mainPlotObject.color = currentColor == null ? Color.black : currentColor;
drawPlotObject(mainPlotObject, ip);
} else {
if (currentLineWidth > 0) lineWidth = currentLineWidth; }
if (!plotDrawn && pp.frame.lineWidth==DEFAULT_FRAME_LINE_WIDTH) { pp.frame.lineWidth = lineWidth;
if (pp.frame.lineWidth == 0) pp.frame.lineWidth = 1;
if (pp.frame.lineWidth > 3) pp.frame.lineWidth = 3;
}
ip.setLineWidth(sc(pp.frame.lineWidth));
ip.setColor(pp.frame.color);
int x2 = frame.x + frame.width - 1;
int y2 = frame.y + frame.height - 1;
ip.moveTo(frame.x, frame.y); ip.lineTo(x2, frame.y);
ip.lineTo(x2, y2);
ip.lineTo(frame.x, y2);
ip.lineTo(frame.x, frame.y);
if (pp.legend != null && (pp.legend.flags & LEGEND_POSITION_MASK) != 0)
drawPlotObject(pp.legend, ip);
plotDrawn = true;
}
ImageProcessor getBlankProcessor() {
makeMarginValues();
if (pp.width <= 0 || pp.height <= 0) {
pp.width = sc(preferredPlotWidth) + leftMargin + rightMargin;
pp.height = sc(preferredPlotHeight) + topMargin + bottomMargin;
}
frameWidth = pp.width - (leftMargin + rightMargin);
frameHeight = pp.height - (topMargin + bottomMargin);
boolean isColored = isColored(); if (ip == null || pp.width != ip.getWidth() || pp.height != ip.getHeight() || (isColored != (ip instanceof ColorProcessor))) {
if (isColored) {
ip = new ColorProcessor(pp.width, pp.height);
} else {
ip = new ByteProcessor(pp.width, pp.height);
invertedLut = Prefs.useInvertingLut && !Interpreter.isBatchMode() && IJ.getInstance()!=null;
if (invertedLut) ip.invertLut();
}
if (imp != null && stack == null)
imp.setProcessor(ip);
}
if (ip instanceof ColorProcessor)
Arrays.fill((int[])(ip.getPixels()), 0xffffff);
else
Arrays.fill((byte[])(ip.getPixels()), invertedLut ? (byte)0 : (byte)0xff);
ip.setFont(scFont(defaultFont));
ip.setLineWidth(sc(1));
ip.setAntialiasedText(pp.antialiasedText);
frame = new Rectangle(leftMargin, topMargin, frameWidth+1, frameHeight+1);
if (pp.frame.color2 != null) { ip.setColor(pp.frame.color2);
ip.setRoi(frame);
ip.fill();
ip.resetRoi();
}
ip.setColor(Color.black);
return ip;
}
void makeMarginValues() {
Font font = nonNullFont(pp.frame.getFont(), currentFont);
float marginScale = 0.1f + 0.9f*font.getSize2D()/12f;
if (marginScale < 0.7f) marginScale = 0.7f;
if (marginScale > 2f) marginScale = 2f;
int addHspace = (int)Tools.getNumberFromList(pp.frame.options, "addhspace="); int addVspace = (int)Tools.getNumberFromList(pp.frame.options, "addvspace=");
leftMargin = sc(LEFT_MARGIN*marginScale + addHspace);
rightMargin = sc(RIGHT_MARGIN*marginScale + addHspace);
topMargin = sc(TOP_MARGIN*marginScale + addVspace);
bottomMargin = sc(BOTTOM_MARGIN*marginScale + 2 + addVspace);
if(pp != null && pp.xLabel != null && pp.xLabel.getFont() != null){
float numberSize = font.getSize2D();
float labelSize = pp.xLabel.getFont().getSize2D();
float extraHeight = 1.5f *(labelSize - numberSize);
if(extraHeight > 0){
bottomMargin += sc(extraHeight);
leftMargin += sc(extraHeight);
}
}
}
double[] makeRangeGetSteps() {
steps = new double[2];
logXAxis = hasFlag(X_LOG_NUMBERS);
logYAxis = hasFlag(Y_LOG_NUMBERS);
for (int i=0; i<currentMinMax.length; i+=2) { boolean logAxis = hasFlag(i==0 ? X_LOG_NUMBERS : Y_LOG_NUMBERS);
double range = currentMinMax[i+1]-currentMinMax[i];
double mid = 0.5*(currentMinMax[i+1]+currentMinMax[i]);
double relativeRange = Math.abs(range/mid);
if (!logAxis)
relativeRange = Math.min(relativeRange, Math.abs(range/(defaultMinMax[i+1]-defaultMinMax[i])));
if (range != 0 && relativeRange<1e-4) {
currentMinMax[i+1] = mid + 0.5*range*1e-4/relativeRange;
currentMinMax[i] = mid - 0.5*range*1e-4/relativeRange;
}
if (logAxis) {
double rangeRatio = currentMinMax[i+1]/currentMinMax[i];
if (!(rangeRatio > MIN_LOG_RATIO || 1./rangeRatio > MIN_LOG_RATIO) ||
!(currentMinMax[i] > 10*Float.MIN_VALUE) || !(currentMinMax[i+1] > 10*Float.MIN_VALUE))
logAxis = false;
}
if (logAxis) {
currentMinMax[i] = Math.log10(currentMinMax[i]);
currentMinMax[i+1] = Math.log10(currentMinMax[i+1]);
}
if ((i==0 && !simpleXAxis()) || (i==2 && !simpleYAxis())) {
int minGridspacing = i==0 ? MIN_X_GRIDSPACING : MIN_Y_GRIDSPACING;
int frameSize = i==0 ? frameWidth : frameHeight;
double step = Tools.getNumberFromList(pp.frame.options, i==0 ? "xinterval=" : "yinterval="); if (!Double.isNaN(step)) {
int nSteps = (int)(Math.floor(currentMinMax[i+1]/step+1e-10) - Math.ceil(currentMinMax[i]/step-1e-10));
if (nSteps < 1) step = Double.NaN; if ((i==0 && nSteps*sc(minGridspacing)*0.5 > frameSize) || i!=0 && nSteps*sc(pp.frame.getFont().getSize()) > frameSize)
step = Double.NaN; }
if (Double.isNaN(step)) { step = Math.abs((currentMinMax[i+1] - currentMinMax[i]) *
Math.max(1.0/maxIntervals, (float)sc(minGridspacing)/frameSize+(maxIntervals>12 ? 0.02 : 0.06))); step = niceNumber(step);
}
if (logAxis && step < 1)
step = 1;
steps[i/2] = step;
boolean force2grid = hasFlag(i==0 ? X_FORCE2GRID : Y_FORCE2GRID) && !ignoreForce2Grid;
if (force2grid) {
int i1 = (int)Math.floor(Math.min(currentMinMax[i],currentMinMax[i+1])/step+1.e-10);
int i2 = (int)Math.ceil (Math.max(currentMinMax[i],currentMinMax[i+1])/step-1.e-10);
if (currentMinMax[i+1] > currentMinMax[i]) { currentMinMax[i] = i1 * step;
currentMinMax[i+1] = i2 * step;
} else {
currentMinMax[i] = i2 * step;
currentMinMax[i+1] = i1 * step;
}
}
}
if (i==0) {
xMin = currentMinMax[i];
xMax = currentMinMax[i+1];
logXAxis = logAxis;
} else {
yMin = currentMinMax[i];
yMax = currentMinMax[i+1];
logYAxis = logAxis;
}
if (logAxis) {
currentMinMax[i] = Math.pow(10, currentMinMax[i]);
currentMinMax[i+1] = Math.pow(10, currentMinMax[i+1]);
}
}
ignoreForce2Grid = false;
xBasePxl = leftMargin;
yBasePxl = topMargin + frameHeight;
xScale = frameWidth/(xMax-xMin);
if (!(xMax-xMin!=0.0)) xBasePxl += sc(10);
yScale = frameHeight/(yMax-yMin);
if (!(yMax-yMin!=0.0))
yBasePxl -= sc(10);
drawAxesTicksGridNumbers(steps);
return steps;
}
public void redrawGrid(){
if (ip != null) {
ip.setColor(Color.black);
drawAxesTicksGridNumbers(steps);
ip.setColor(Color.black);
}
}
void getInitialMinAndMax() {
int axisRangeFlags = 0;
if (Double.isNaN(defaultMinMax[0])) axisRangeFlags |= X_RANGE;
if (Double.isNaN(defaultMinMax[2])) axisRangeFlags |= Y_RANGE;
if (axisRangeFlags != 0) {
defaultMinMax = getMinAndMax(false, axisRangeFlags);
enlargeRange(defaultMinMax);
}
setLimitsToDefaults(false); }
double[] getMinAndMax(boolean allObjects, int axisRangeFlags) {
boolean invertedXAxis = currentMinMax[1] < currentMinMax[0];
boolean invertedYAxis = currentMinMax[3] < currentMinMax[2];
double xSign = invertedXAxis ? -1 : 1;
double ySign = invertedYAxis ? -1 : 1;
double[] allMinMax = new double[]{xSign*Double.MAX_VALUE, -xSign*Double.MAX_VALUE, ySign*Double.MAX_VALUE, -ySign*Double.MAX_VALUE};
for (int i=0; i<allMinMax.length; i++)
if (((axisRangeFlags>>i/2) & 1)==0) allMinMax[i] = defaultMinMax[i];
enlargeRange = new int[allMinMax.length];
for (PlotObject plotObject : allPlotObjects) {
if ((plotObject.type == PlotObject.XY_DATA || plotObject.type == PlotObject.ARROWS) && !plotObject.hasFlag(PlotObject.HIDDEN)) {
getMinAndMax(allMinMax, enlargeRange, plotObject, axisRangeFlags);
if (!allObjects) break;
}
}
if ((axisRangeFlags & X_RANGE) != 0) {
String[] xCats = labelsInBraces('x'); if (xCats != null) {
allMinMax[0] = Math.min(allMinMax[0], -0.5);
allMinMax[1] = Math.min(allMinMax[1], xCats.length+0.5);
}
}
if ((axisRangeFlags & Y_RANGE) != 0) {
String[] yCats = labelsInBraces('y');
if (yCats != null) {
allMinMax[2] = Math.min(allMinMax[2], -0.5);
allMinMax[3] = Math.min(allMinMax[3], yCats.length+0.5);
}
}
if (allMinMax[0]==Double.MAX_VALUE && allMinMax[1]==-Double.MAX_VALUE) { allMinMax[0] = defaultMinMax[0];
allMinMax[1] = defaultMinMax[1];
}
if (allMinMax[2]==Double.MAX_VALUE && allMinMax[3]==-Double.MAX_VALUE) { allMinMax[2] = defaultMinMax[2];
allMinMax[3] = defaultMinMax[3];
}
return allMinMax;
}
void fitRangeToLastPlotObject() {
if (allPlotObjects.size() < 1) return;
PlotObject plotObject = allPlotObjects.lastElement();
if (Double.isNaN(currentMinMax[0]) || Double.isNaN(currentMinMax[2])) { setLimitsToFit(false);
} else { enlargeRange = new int[currentMinMax.length];
getMinAndMax(currentMinMax, enlargeRange, plotObject, ALL_AXES_RANGE);
enlargeRange(currentMinMax);
}
}
void getMinAndMax(double[] allMinAndMax, int[] enlargeRange, PlotObject plotObject, int axisRangeFlags) {
boolean invertedXAxis = currentMinMax[1] < currentMinMax[0];
boolean invertedYAxis = currentMinMax[3] < currentMinMax[2];
if (plotObject.type == PlotObject.XY_DATA) {
if ((axisRangeFlags & X_RANGE) != 0) {
int suggestedEnlarge = 0;
if (!(plotObject.shape == LINE || plotObject.shape == FILLED) || plotObject.yEValues != null)
suggestedEnlarge = ALWAYS_ENLARGE; getMinAndMax(allMinAndMax, enlargeRange, suggestedEnlarge, 0, plotObject.xValues, plotObject.xEValues, invertedXAxis);
if ((plotObject.shape == BAR || plotObject.shape == SEPARATED_BAR)&& plotObject.xValues.length > 1) {
int n = plotObject.xValues.length;
allMinAndMax[0] -= 0.5 * Math.abs(plotObject.xValues[1] - plotObject.xValues[0]);
allMinAndMax[1] += 0.5 * Math.abs(plotObject.xValues[n - 1] - plotObject.xValues[n - 2]);
}
}
if ((axisRangeFlags & Y_RANGE) != 0) {
int suggestedEnlarge = 0;
if (plotObject.shape==DOT || plotObject.xEValues != null) suggestedEnlarge = ALWAYS_ENLARGE;
else if (!(plotObject.shape == LINE || plotObject.shape == FILLED))
suggestedEnlarge = USUALLY_ENLARGE;
getMinAndMax(allMinAndMax, enlargeRange, suggestedEnlarge, 2, plotObject.yValues, plotObject.yEValues, invertedYAxis);
if ((plotObject.shape == BAR || plotObject.shape == SEPARATED_BAR) &&
(allMinAndMax[2] > 0 && allMinAndMax[3]/allMinAndMax[2] >= 2) && !logYAxis)
allMinAndMax[2] = 0; }
} else if (plotObject.type == PlotObject.ARROWS) {
if ((axisRangeFlags & X_RANGE) != 0) {
getMinAndMax(allMinAndMax, enlargeRange, ALWAYS_ENLARGE, 0, plotObject.xValues, null, invertedXAxis);
getMinAndMax(allMinAndMax, enlargeRange, ALWAYS_ENLARGE, 0, plotObject.xEValues, null, invertedXAxis);
}
if ((axisRangeFlags & Y_RANGE) != 0) {
getMinAndMax(allMinAndMax, enlargeRange, ALWAYS_ENLARGE, 2, plotObject.yValues, null, invertedYAxis);
getMinAndMax(allMinAndMax, enlargeRange, ALWAYS_ENLARGE, 2, plotObject.yEValues, null, invertedYAxis);
}
}
}
void getMinAndMax(double[] allMinAndMax, int[] enlargeRange, int suggestedEnlarge,
int axisIndex, float[] data, float[] errorBars, boolean invertedAxis) {
int nMinEqual = 0, nMaxEqual = 0;
int minIndex = invertedAxis ? axisIndex+1 : axisIndex; int maxIndex = invertedAxis ? axisIndex : axisIndex+1;
for (int i=0; i<data.length; i++) {
double v1 = data[i];
double v2 = data[i];
if (errorBars != null && i<errorBars.length) {
v1 -= errorBars[i];
v2 += errorBars[i];
}
if (v1 < allMinAndMax[minIndex]) {
allMinAndMax[minIndex] = v1;
nMinEqual = 1;
enlargeRange[minIndex] = suggestedEnlarge;
if (suggestedEnlarge == 0 && ((i>0 && i<data.length-1) || v2 != v1)) enlargeRange[minIndex] = USUALLY_ENLARGE;
} else if (v1 == allMinAndMax[minIndex])
nMinEqual++;
if (v2 > allMinAndMax[maxIndex]) {
allMinAndMax[maxIndex] = v2;
nMaxEqual = 1;
enlargeRange[maxIndex] = suggestedEnlarge;
if (suggestedEnlarge == 0 && ((i>0 && i<data.length-1) || v2 != v1)) enlargeRange[maxIndex] = USUALLY_ENLARGE;
} else if (v2 == allMinAndMax[maxIndex])
nMaxEqual++;
}
if (enlargeRange[minIndex] == 0 && nMinEqual > 2 && nMinEqual*10 > data.length)
enlargeRange[minIndex] = USUALLY_ENLARGE;
if (enlargeRange[maxIndex] == 0 && nMaxEqual > 2 && nMaxEqual*10 > data.length)
enlargeRange[maxIndex] = USUALLY_ENLARGE;
if (nMinEqual == data.length)
enlargeRange[minIndex] = ALWAYS_ENLARGE;
if (nMaxEqual == data.length)
enlargeRange[maxIndex] = ALWAYS_ENLARGE;
if (nMinEqual>0 && enlargeRange[minIndex]<suggestedEnlarge)
enlargeRange[minIndex] = suggestedEnlarge;
if (nMaxEqual>0 && enlargeRange[maxIndex]<suggestedEnlarge)
enlargeRange[maxIndex] = suggestedEnlarge;
}
void saveMinMax() {
if (!Arrays.equals(currentMinMax, savedMinMax))
System.arraycopy(currentMinMax, 0, savedMinMax, 0, currentMinMax.length);
}
void enlargeRange(double[] minMax) {
if (enlargeRange == null) return;
for (int a=0; a<Math.min(minMax.length, enlargeRange.length); a+=2) { boolean logAxis = a==0 ? logXAxis : logYAxis;
if (logAxis) {
minMax[a] = Math.log10(minMax[a]);
minMax[a+1] = Math.log10(minMax[a+1]);
}
double range = minMax[a+1] - minMax[a];
double tmpMin = minMax[a] - 0.015*range;
if (enlargeRange[a] == USUALLY_ENLARGE && !logAxis) minMax[a] = (tmpMin*minMax[a] <= 0) ? 0 : tmpMin;
else if (enlargeRange[a] == ALWAYS_ENLARGE)
minMax[a] = tmpMin;
double tmpMax = minMax[a+1] + 0.015*range;
if (enlargeRange[a+1] == USUALLY_ENLARGE && !logAxis)
minMax[a+1] = (tmpMax*minMax[a+1] <= 0) ? 0 : tmpMax;
else if (enlargeRange[a+1] == ALWAYS_ENLARGE)
minMax[a+1] = tmpMax;
if (logAxis) {
minMax[a] = Math.pow(10, minMax[a]);
minMax[a+1] = Math.pow(10, minMax[a+1]);
}
}
}
Font nonNullFont(Font font1, Font font2) {
if (font1 != null)
return font1;
else if (font2 != null)
return font2;
else return
defaultFont;
}
void zoomToRect(Rectangle r) {
saveMinMax();
currentMinMax[0] = descaleX(r.x);
currentMinMax[1] = descaleX(r.x + r.width);
currentMinMax[2] = descaleY(r.y + r.height);
currentMinMax[3] = descaleY(r.y);
updateImage();
}
void zoomOnRangeArrow(int arrowIndex) {
if (arrowIndex < 0) return;
if (arrowIndex < 8) { int axisIndex = (arrowIndex / 4) * 2; double min = axisIndex == 0 ? xMin : yMin;
double max = axisIndex == 0 ? xMax : yMax;
double range = max - min;
boolean isMin = (arrowIndex % 4) < 2;
boolean shrinkRange = arrowIndex % 4 == 1 || arrowIndex % 4 == 2;
double factor = Math.sqrt(2);
if (shrinkRange)
factor = 1.0 / factor;
if (isMin)
min = max - range * factor;
else
max = min + range * factor;
boolean logAxis = axisIndex == 0 ? logXAxis : logYAxis;
if (logAxis) {
min = Math.pow(10, min);
max = Math.pow(10, max);
}
currentMinMax[axisIndex] = min;
currentMinMax[axisIndex + 1] = max;
} else if (arrowIndex == 8) {
setLimitsToDefaults(false);
} else if (arrowIndex == 9) {
setLimitsToFit(false);
} else if (arrowIndex <= 15) {
int dialogType = arrowIndex;
new PlotDialog(this, dialogType).showDialog(imp.getWindow());
}
if (arrowIndex <= 9) updateImage();
}
void zoom(int x, int y, double zoomFactor) {
boolean wasLogX = logXAxis;
boolean wasLogY = logYAxis;
double plotX = descaleX(x);
double plotY = descaleY(y);
IJ.showStatus ("" + plotX);
boolean insideX = x > frame.x && x < frame.x + frame.width;
boolean insideY = y > frame.y && y < frame.y + frame.height;
if (!insideX && !insideY) {
insideX = true;
insideY = true;
x = frame.x + frame.width / 2;
y = frame.y + frame.height / 2;
}
int leftPart = x - frame.x;
int rightPart = frame.x + frame.width - x;
int highPart = y - frame.y;
int lowPart = frame.y + frame.height - y;
if (insideX) {
currentMinMax[0] = descaleX((int) (x - leftPart / zoomFactor));
currentMinMax[1] = descaleX((int) (x + rightPart / zoomFactor));
}
if (insideY) {
currentMinMax[2] = descaleY((int) (y + lowPart / zoomFactor));
currentMinMax[3] = descaleY((int) (y - highPart / zoomFactor));
}
updateImage();
if (wasLogX != logXAxis ){ int changedX = (int) scaleXtoPxl(plotX);
int left = changedX - leftPart;
int right = changedX + rightPart;
currentMinMax[0] = descaleX(left);
currentMinMax[1] = descaleX(right);
updateImage();
}
if (wasLogY != logYAxis){ int changedY = (int) scaleYtoPxl(plotY);
int bottom = changedY + lowPart;
int top = changedY + highPart;
currentMinMax[2] = descaleY(bottom);
currentMinMax[3] = descaleY(top);
updateImage();
}
}
void scroll(int dx, int dy) {
if (logXAxis) {
currentMinMax[0] /= Math.pow(10, dx/xScale);
currentMinMax[1] /= Math.pow(10, dx/xScale);
} else {
currentMinMax[0] -= dx/xScale;
currentMinMax[1] -= dx/xScale;
}
if (logYAxis) {
currentMinMax[2] *= Math.pow(10, dy/yScale);
currentMinMax[3] *= Math.pow(10, dy/yScale);
} else {
currentMinMax[2] += dy/yScale;
currentMinMax[3] += dy/yScale;
}
updateImage();
}
private boolean simpleXAxis() {
return !hasFlag(X_TICKS | X_MINOR_TICKS | X_LOG_TICKS | X_GRID | X_NUMBERS);
}
private boolean simpleYAxis() {
return !hasFlag(Y_TICKS | Y_MINOR_TICKS | Y_LOG_TICKS | Y_GRID | Y_NUMBERS);
}
void drawAxesTicksGridNumbers(double[] steps) {
if (ip==null)
return;
String[] xCats = labelsInBraces('x'); String[] yCats = labelsInBraces('y');
String multiplySymbol = getMultiplySymbol(); Font scFont = scFont(pp.frame.getFont());
Font scFontMedium = scFont.deriveFont(scFont.getSize2D()*10f/12f); Font scFontSmall = scFont.deriveFont(scFont.getSize2D()*9f/12f); ip.setFont(scFont);
FontMetrics fm = ip.getFontMetrics();
int fontAscent = fm.getAscent();
ip.setJustification(LEFT);
int yOfXAxisNumbers = topMargin + frameHeight + fm.getHeight()*5/4 + sc(2);
if (hasFlag(X_NUMBERS | (logXAxis ? (X_TICKS | X_MINOR_TICKS) : X_LOG_TICKS) + X_GRID)) {
Font baseFont = scFont;
boolean majorTicks = logXAxis ? hasFlag(X_LOG_TICKS) : hasFlag(X_TICKS);
boolean minorTicks = hasFlag(X_MINOR_TICKS);
minorTicks = minorTicks && (xCats == null);
double step = steps[0];
int i1 = (int)Math.ceil (Math.min(xMin, xMax)/step-1.e-10);
int i2 = (int)Math.floor(Math.max(xMin, xMax)/step+1.e-10);
int suggestedDigits = (int)Tools.getNumberFromList(pp.frame.options, "xdecimals="); int digits = getDigits(xMin, xMax, step, 7, suggestedDigits);
int y1 = topMargin;
int y2 = topMargin + frameHeight;
if (xMin==xMax) {
if (hasFlag(X_NUMBERS)) {
String s = IJ.d2s(xMin,getDigits(xMin, 0.001*xMin, 5, suggestedDigits));
int y = yBasePxl;
ip.drawString(s, xBasePxl-ip.getStringWidth(s)/2, yOfXAxisNumbers);
}
} else {
if (hasFlag(X_NUMBERS)) {
int w1 = ip.getStringWidth(IJ.d2s(currentMinMax[0], logXAxis ? -1 : digits));
int w2 = ip.getStringWidth(IJ.d2s(currentMinMax[1], logXAxis ? -1 : digits));
int wMax = Math.max(w1,w2);
if (wMax > Math.abs(step*xScale)-sc(8)) {
baseFont = scFontMedium; ip.setFont(baseFont);
}
}
for (int i=0; i<=(i2-i1); i++) {
double v = (i+i1)*step;
int x = (int)Math.round((v - xMin)*xScale) + leftMargin;
if (xCats!= null) {
int index = (int) v;
double remainder = Math.abs(v - Math.round(v));
if(index >= 0 && index < xCats.length && remainder < 1e-9){
String s = xCats[index];
String[] parts = s.split("\n");
int w = 0;
for(int jj = 0; jj < parts.length; jj++)
w = Math.max(w, ip.getStringWidth(parts[jj]));
ip.drawString(s, x-w/2, yOfXAxisNumbers);
}
continue;
}
if (hasFlag(X_GRID)) {
ip.setColor(gridColor);
ip.drawLine(x, y1, x, y2);
ip.setColor(Color.black);
}
if (majorTicks) {
ip.drawLine(x, y1, x, y1+sc(tickLength));
ip.drawLine(x, y2, x, y2-sc(tickLength));
}
if (hasFlag(X_NUMBERS)) {
if (logXAxis || digits<0) {
drawExpString(logXAxis ? Math.pow(10,v) : v, logXAxis ? -1 : -digits,
x, yOfXAxisNumbers-fontAscent/2, CENTER, fontAscent, baseFont, scFontSmall, multiplySymbol);
} else {
String s = IJ.d2s(v,digits);
ip.drawString(s, x-ip.getStringWidth(s)/2, yOfXAxisNumbers);
}
}
}
boolean haveMinorLogNumbers = i2-i1 < 2; if (minorTicks && (!logXAxis || step > 1.1)) { double mstep = niceNumber(step*0.19); double minorPerMajor = step/mstep;
if (Math.abs(minorPerMajor-Math.round(minorPerMajor)) > 1e-10) mstep = step/4;
if (logXAxis && mstep < 1) mstep = 1;
i1 = (int)Math.ceil (Math.min(xMin,xMax)/mstep-1.e-10);
i2 = (int)Math.floor(Math.max(xMin,xMax)/mstep+1.e-10);
for (int i=i1; i<=i2; i++) {
double v = i*mstep;
int x = (int)Math.round((v - xMin)*xScale) + leftMargin;
ip.drawLine(x, y1, x, y1+sc(minorTickLength));
ip.drawLine(x, y2, x, y2-sc(minorTickLength));
}
} else if (logXAxis && majorTicks && Math.abs(xScale)>sc(MIN_X_GRIDSPACING)) { int minorNumberLimit = haveMinorLogNumbers ? (int)(0.12*Math.abs(xScale)/(fm.charWidth('0')+sc(2))) : 0; i1 = (int)Math.floor(Math.min(xMin,xMax)-1.e-10);
i2 = (int)Math.ceil (Math.max(xMin,xMax)+1.e-10);
for (int i=i1; i<=i2; i++) {
for (int m=2; m<10; m++) {
double v = i+Math.log10(m);
if (v > Math.min(xMin,xMax) && v < Math.max(xMin,xMax)) {
int x = (int)Math.round((v - xMin)*xScale) + leftMargin;
ip.drawLine(x, y1, x, y1+sc(minorTickLength));
ip.drawLine(x, y2, x, y2-sc(minorTickLength));
if (m<=minorNumberLimit)
drawExpString(Math.pow(10,v), 0, x, yOfXAxisNumbers-fontAscent/2, CENTER,
fontAscent, baseFont, scFontSmall, multiplySymbol);
}
}
}
}
}
}
ip.setFont(scFont);
int maxNumWidth = 0;
int xNumberRight = leftMargin-sc(2)-ip.getStringWidth("0")/2;
Rectangle rect = ip.getStringBounds("0169");
int yNumberOffset = -rect.y-rect.height/2;
if (hasFlag(Y_NUMBERS | (logYAxis ? (Y_TICKS | Y_MINOR_TICKS) : Y_LOG_TICKS) + Y_GRID)) {
ip.setJustification(RIGHT);
Font baseFont = scFont;
boolean majorTicks = logYAxis ? hasFlag(Y_LOG_TICKS) : hasFlag(Y_TICKS);
boolean minorTicks = logYAxis ? hasFlag(Y_LOG_TICKS) : hasFlag(Y_MINOR_TICKS);
minorTicks = minorTicks && (yCats == null);
double step = steps[1];
int i1 = (int)Math.ceil (Math.min(yMin, yMax)/step-1.e-10);
int i2 = (int)Math.floor(Math.max(yMin, yMax)/step+1.e-10);
int suggestedDigits = (int)Tools.getNumberFromList(pp.frame.options, "ydecimals="); int digits = getDigits(yMin, yMax, step, 5, suggestedDigits);
int x1 = leftMargin;
int x2 = leftMargin + frameWidth;
if (yMin==yMax) {
if (hasFlag(Y_NUMBERS)) {
String s = IJ.d2s(yMin,getDigits(yMin, 0.001*yMin, 5, suggestedDigits));
maxNumWidth = ip.getStringWidth(s);
int y = yBasePxl;
ip.drawString(s, xNumberRight, y+fontAscent/2+sc(1));
}
} else {
int digitsForWidth = logYAxis ? -1 : digits;
if (digitsForWidth < 0) {
digitsForWidth--; xNumberRight += sc(1)+ip.getStringWidth("0")/4;
}
String str1 = IJ.d2s(currentMinMax[2], digitsForWidth);
String str2 = IJ.d2s(currentMinMax[3], digitsForWidth);
if (digitsForWidth < 0) {
str1 = str1.replaceFirst("E",multiplySymbol);
str2 = str2.replaceFirst("E",multiplySymbol);
}
int w1 = ip.getStringWidth(str1);
int w2 = ip.getStringWidth(str2);
int wMax = Math.max(w1,w2);
if (hasFlag(Y_NUMBERS)) {
if (wMax > xNumberRight - sc(4) - (pp.yLabel.label.length()>0 ? fm.getHeight() : 0)) {
baseFont = scFontMedium; ip.setFont(baseFont);
}
}
for (int i=i1; i<=i2; i++) {
double v = step==0 ? yMin : i*step;
int y = topMargin + frameHeight - (int)Math.round((v - yMin)*yScale);
if (yCats != null){
int index = (int) v;
double remainder = Math.abs(v - Math.round(v));
if(index >= 0 && index < yCats.length && remainder < 1e-9){
String s = yCats[index];
int multiLineOffset = 0; for(int jj = 0; jj < s.length(); jj++)
if(s.charAt(jj) == '\n')
multiLineOffset -= rect.height/2;
ip.drawString(s, xNumberRight, y+yNumberOffset+ multiLineOffset);
}
continue;
}
if (hasFlag(Y_GRID)) {
ip.setColor(gridColor);
ip.drawLine(x1, y, x2, y);
ip.setColor(Color.black);
}
if (majorTicks) {
ip.drawLine(x1, y, x1+sc(tickLength), y);
ip.drawLine(x2, y, x2-sc(tickLength), y);
}
if (hasFlag(Y_NUMBERS)) {
int w = 0;
if (logYAxis || digits<0) {
w = drawExpString(logYAxis ? Math.pow(10,v) : v, logYAxis ? -1 : -digits,
xNumberRight, y, RIGHT, fontAscent, baseFont, scFontSmall, multiplySymbol);
} else {
String s = IJ.d2s(v,digits);
w = ip.getStringWidth(s);
ip.drawString(s, xNumberRight, y+yNumberOffset);
}
if (w > maxNumWidth) maxNumWidth = w;
}
}
boolean haveMinorLogNumbers = i2-i1 < 2; if (minorTicks && (!logYAxis || step > 1.1)) { double mstep = niceNumber(step*0.19); double minorPerMajor = step/mstep;
if (Math.abs(minorPerMajor-Math.round(minorPerMajor)) > 1e-10) mstep = step/4;
if (logYAxis && step < 1) mstep = 1;
i1 = (int)Math.ceil (Math.min(yMin,yMax)/mstep-1.e-10);
i2 = (int)Math.floor(Math.max(yMin,yMax)/mstep+1.e-10);
for (int i=i1; i<=i2; i++) {
double v = i*mstep;
int y = topMargin + frameHeight - (int)Math.round((v - yMin)*yScale);
ip.drawLine(x1, y, x1+sc(minorTickLength), y);
ip.drawLine(x2, y, x2-sc(minorTickLength), y);
}
}
if (logYAxis && majorTicks && Math.abs(yScale)>sc(MIN_X_GRIDSPACING)) { int minorNumberLimit = haveMinorLogNumbers ? (int)(0.4*Math.abs(yScale)/fm.getHeight()) : 0; i1 = (int)Math.floor(Math.min(yMin,yMax)-1.e-10);
i2 = (int)Math.ceil(Math.max(yMin,yMax)+1.e-10);
for (int i=i1; i<=i2; i++) {
for (int m=2; m<10; m++) {
double v = i+Math.log10(m);
if (v > Math.min(yMin,yMax) && v < Math.max(yMin,yMax)) {
int y = topMargin + frameHeight - (int)Math.round((v - yMin)*yScale);
ip.drawLine(x1, y, x1+sc(minorTickLength), y);
ip.drawLine(x2, y, x2-sc(minorTickLength), y);
if (m<=minorNumberLimit) {
int w = drawExpString(Math.pow(10,v), 0, xNumberRight, y, RIGHT,
fontAscent, baseFont, scFontSmall, multiplySymbol);
if (w > maxNumWidth) maxNumWidth = w;
}
}
}
}
}
}
}
ip.setFont(scFont);
ip.setJustification(LEFT);
String xLabelToDraw = pp.xLabel.label;
String yLabelToDraw = pp.yLabel.label;
if (simpleYAxis()) { int digits = getDigits(yMin, yMax, 0.001*(yMax-yMin), 6, 0);
String s = IJ.d2s(yMax, digits);
int sw = ip.getStringWidth(s);
if ((sw+sc(4)) > leftMargin)
ip.drawString(s, sc(4), topMargin-sc(4));
else
ip.drawString(s, leftMargin-ip.getStringWidth(s)-sc(4), topMargin+10);
s = IJ.d2s(yMin, digits);
sw = ip.getStringWidth(s);
if ((sw+4)>leftMargin)
ip.drawString(s, sc(4), topMargin+frame.height);
else
ip.drawString(s, leftMargin-ip.getStringWidth(s)-sc(4), topMargin+frame.height);
if (logYAxis) yLabelToDraw += " (LOG)";
}
int y = yOfXAxisNumbers;
if (simpleXAxis()) { int digits = getDigits(xMin, xMax, 0.001*(xMax-xMin), 7, 0);
ip.drawString(IJ.d2s(xMin,digits), leftMargin, y);
String s = IJ.d2s(xMax,digits);
ip.drawString(s, leftMargin + frame.width-ip.getStringWidth(s)+6, y);
y -= fm.getHeight();
if (logXAxis) xLabelToDraw += " (LOG)";
} else
y += sc(1);
if (xCats == null) {
ip.setFont(pp.xLabel.getFont() == null ? scFont : scFont(pp.xLabel.getFont()));
ImageProcessor xLabel = stringToPixels(xLabelToDraw);
if(xLabel != null){
int xpos = leftMargin+(frame.width-xLabel.getWidth())/2;
int ypos = y + scFont.getSize()/3; ip.insert(xLabel, xpos, ypos);
}
}
if (yCats == null) {
ip.setFont(pp.yLabel.getFont() == null ? scFont : scFont(pp.yLabel.getFont()));
ImageProcessor yLabel = stringToPixels(yLabelToDraw);
if(yLabel != null){
yLabel = yLabel.rotateLeft();
int xRightOfYLabel = xNumberRight - maxNumWidth - sc(2);
int xpos = xRightOfYLabel - yLabel.getWidth() - sc(2);
int ypos = topMargin + (frame.height -yLabel.getHeight())/2;
ip.insert(yLabel, xpos, ypos);
}
}
}
String[] labelsInBraces(char labelCode) {
String s = getLabel(labelCode);
if (s.startsWith("{") && s.endsWith("}")) {
String inBraces = s.substring(1, s.length() - 1);
String[] catLabels = inBraces.split(",");
return catLabels;
} else {
return null;
}
}
double niceNumber(double v) {
double base = Math.pow(10,Math.floor(Math.log10(v)-1.e-6));
if (v > 5.0000001*base) return 10*base;
else if (v > 2.0000001*base) return 5*base;
else return 2*base;
}
int drawExpString(double value, int digits, int x, int y, int justification,
int fontAscent, Font baseFont, Font smallFont, String multiplySymbol) {
String base = "10";
String exponent = null;
String s = IJ.d2s(value, digits<=0 ? -1 : -digits);
if (Tools.parseDouble(s) == 0) s = "0"; int ePos = s.indexOf('E');
if (ePos < 0)
base = s; else {
if (digits>=0) {
base = s.substring(0,ePos);
if (digits == 0)
base = Integer.toString((int)Math.round(Tools.parseDouble(base)));
base += multiplySymbol+"10";
}
exponent = s.substring(ePos+1);
}
ip.setJustification(RIGHT);
int width = ip.getStringWidth(base);
if (exponent != null) {
ip.setFont(smallFont);
int wExponent = ip.getStringWidth(exponent);
width += wExponent;
if (justification == CENTER) x += width/2;
ip.drawString(exponent, x, y+fontAscent*3/10);
x -= wExponent;
ip.setFont(baseFont);
}
ip.drawString(base, x, y+fontAscent*7/10);
return width;
}
String getMultiplySymbol() {
String multiplySymbol = Tools.getStringFromList(pp.frame.options, "msymbol=");
if (multiplySymbol==null)
multiplySymbol = Tools.getStringFromList(pp.frame.options, "multiplysymbol=");
return multiplySymbol != null ? multiplySymbol : MULTIPLY_SYMBOL;
}
ByteProcessor stringToPixels(String labelStr) {
Font bigFont = ip.getFont();
Rectangle rect = ip.getStringBounds(labelStr);
int ww = rect.width * 2;
int hh = rect.height * 3; int y0 = rect.height * 2; if (ww <= 0 || hh <= 0) {
return null;
}
ByteProcessor box = new ByteProcessor(ww, hh);
box.setColor(Color.WHITE);
box.fill();
box.setColor(Color.black);
box.setAntialiasedText(pp.antialiasedText);
if (invertedLut) {
box.invertLut();
}
box.setFont(bigFont);
FontMetrics fm = box.getFontMetrics();
int ascent = fm.getAscent();
int offSub = ascent / 6;
int offSuper = -ascent / 2;
Font smallFont = bigFont.deriveFont((float) (bigFont.getSize() * 0.7));
Rectangle bigBounds = box.getStringBounds(labelStr);
boolean doParse = (labelStr.indexOf("^^") >= 0 || labelStr.indexOf("!!") >= 0);
doParse = doParse && (labelStr.indexOf("^^^") < 0 && labelStr.indexOf("!!!") < 0);
if (!doParse) {
box.drawString(labelStr, 0, y0);
Rectangle cropRect = new Rectangle(bigBounds);
cropRect.y += y0;
box.setRoi(cropRect);
ImageProcessor boxI = box.crop();
box = boxI.convertToByteProcessor();
return box;
}
if (labelStr.endsWith("^^") || labelStr.endsWith("!!")) {
labelStr = labelStr.substring(0, labelStr.length() - 2);
}
if (labelStr.startsWith("^^") || labelStr.startsWith("!!")) {
labelStr = " " + labelStr;
}
box.setFont(smallFont);
Rectangle smallBounds = box.getStringBounds(labelStr);
box.setFont(bigFont);
int upperBound = y0 + smallBounds.y + offSuper;
int lowerBound = y0 + smallBounds.y + smallBounds.height + offSub;
int h = fm.getHeight();
int len = labelStr.length();
int[] tags = new int[len];
int nTags = 0;
for (int jj = 0; jj < len - 2; jj++) { if (labelStr.substring(jj, jj + 2).equals("^^")) {
tags[nTags++] = jj;
}
if (labelStr.substring(jj, jj + 2).equals("!!")) {
tags[nTags++] = -jj;
}
}
tags[nTags++] = len;
tags = Arrays.copyOf(tags, nTags);
int leftIndex = 0;
int xRight = 0;
int y2 = y0;
boolean subscript = labelStr.startsWith("!!");
for (int pp = 0; pp < tags.length; pp++) { int rightIndex = tags[pp];
rightIndex = Math.abs(rightIndex);
String part = labelStr.substring(leftIndex, rightIndex);
boolean small = pp % 2 == 1; if (small) {
box.setFont(smallFont);
if (subscript) {
y2 = y0 + offSub;
} else { y2 = y0 + offSuper;
}
} else {
box.setFont(bigFont);
y2 = y0;
}
xRight++;
int partWidth = box.getStringWidth(part);
box.drawString(part, xRight, y2);
leftIndex = rightIndex + 2;
subscript = tags[pp] < 0; xRight += partWidth;
}
xRight += h / 4;
Rectangle cropRect = new Rectangle(0, upperBound, xRight, lowerBound - upperBound);
box.setRoi(cropRect);
ImageProcessor boxI = box.crop();
box = boxI.convertToByteProcessor();
return box;
}
static int getDigits(double n, double resolution, int maxDigits, int suggestedDigits) {
if (n==Math.round(n) && Math.abs(n) < Math.pow(10,maxDigits-1)-1) return suggestedDigits;
else
return getDigits2(n, resolution, maxDigits, suggestedDigits);
}
static int getDigits(double n1, double n2, double resolution, int maxDigits, int suggestedDigits) {
if (n1==0 && n2==0) return suggestedDigits;
return getDigits2(Math.max(Math.abs(n1),Math.abs(n2)), resolution, maxDigits, suggestedDigits);
}
static int getDigits2(double n, double resolution, int maxDigits, int suggestedDigits) {
if (Double.isNaN(n) || Double.isInfinite(n))
return 0; int log10ofN = (int)Math.floor(Math.log10(Math.abs(n))+1e-7);
int digits = resolution != 0 ?
-(int)Math.floor(Math.log10(Math.abs(resolution))+1e-7) :
Math.max(0, -log10ofN+maxDigits-2);
int sciDigits = -Math.max((log10ofN+digits),1);
if ((digits < -2 && log10ofN >= maxDigits) || suggestedDigits < 0)
digits = sciDigits; else if (digits < 0)
digits = 0;
else if (digits > maxDigits-1 && log10ofN < -2)
digits = sciDigits; return digits < 0 ? Math.min(sciDigits, suggestedDigits) : Math.max(digits, suggestedDigits);
}
static boolean isInteger(double n) {
return n==Math.round(n);
}
private void drawPlotObject(PlotObject plotObject, ImageProcessor ip) {
if (plotObject.hasFlag(PlotObject.HIDDEN)) return;
ip.setColor(plotObject.color);
ip.setLineWidth(sc(plotObject.lineWidth));
int type = plotObject.type;
switch (type) {
case PlotObject.XY_DATA:
ip.setClipRect(frame);
int nPoints = Math.min(plotObject.xValues.length, plotObject.yValues.length);
if (plotObject.shape==BAR || plotObject.shape==SEPARATED_BAR)
drawBarChart(plotObject);
if (plotObject.shape == FILLED) { ip.setColor(plotObject.color2 != null ? plotObject.color2 : plotObject.color);
drawFloatPolyLineFilled(ip, plotObject.xValues, plotObject.yValues, nPoints);
}
ip.setColor(plotObject.color);
ip.setLineWidth(sc(plotObject.lineWidth));
if (plotObject.yEValues != null) drawVerticalErrorBars(plotObject.xValues, plotObject.yValues, plotObject.yEValues);
if (plotObject.xEValues != null)
drawHorizontalErrorBars(plotObject.xValues, plotObject.yValues, plotObject.xEValues);
if (plotObject.hasFilledMarker()) { int markSize = plotObject.getMarkerSize();
ip.setColor(plotObject.color2);
ip.setLineWidth(1);
for (int i=0; i<nPoints; i++)
if ((!logXAxis || plotObject.xValues[i]>0) && (!logYAxis || plotObject.yValues[i]>0)
&& !Double.isNaN(plotObject.xValues[i]) && !Double.isNaN(plotObject.yValues[i]))
fillShape(plotObject.shape, scaleX(plotObject.xValues[i]), scaleY(plotObject.yValues[i]), markSize);
ip.setColor(plotObject.color);
ip.setLineWidth(sc(plotObject.lineWidth));
}
if (plotObject.hasCurve()) { if (plotObject.shape == CONNECTED_CIRCLES)
ip.setColor(plotObject.color2 == null ? Color.black : plotObject.color2);
drawFloatPolyline(ip, plotObject.xValues, plotObject.yValues, nPoints);
ip.setColor(plotObject.color);
}
if (plotObject.hasMarker()) { int markSize = plotObject.getMarkerSize();
ip.setColor(plotObject.color);
Font saveFont = ip.getFont();
for (int i=0; i<Math.min(plotObject.xValues.length, plotObject.yValues.length); i++) {
if ((!logXAxis || plotObject.xValues[i]>0) && (!logYAxis || plotObject.yValues[i]>0)
&& !Double.isNaN(plotObject.xValues[i]) && !Double.isNaN(plotObject.yValues[i]))
drawShape(plotObject, scaleX(plotObject.xValues[i]), scaleY(plotObject.yValues[i]), plotObject.shape, markSize, i);
}
if (plotObject.shape==CUSTOM)
ip.setFont(saveFont);
}
ip.setClipRect(null);
break;
case PlotObject.ARROWS:
ip.setClipRect(frame);
for (int i=0; i<plotObject.xValues.length; i++) {
int xt1 = scaleX(plotObject.xValues[i]);
int yt1 = scaleY(plotObject.yValues[i]);
int xt2 = scaleX(plotObject.xEValues[i]);
int yt2 = scaleY(plotObject.yEValues[i]);
double dist = calculateDistance(xt1, yt1, xt2, yt2);
if (xt1==xt2 && yt1==yt2)
ip.drawDot(xt1, yt1);
else if (dist < sc(1.5f*MIN_ARROWHEAD_LENGTH))
ip.drawLine(xt1, yt1, xt2, yt2);
else {
int arrowHeadLength = (int)(dist*RELATIVE_ARROWHEAD_SIZE+0.5);
if (arrowHeadLength > sc(MAX_ARROWHEAD_LENGTH)) arrowHeadLength = sc(MAX_ARROWHEAD_LENGTH);
if (arrowHeadLength < sc(MIN_ARROWHEAD_LENGTH)) arrowHeadLength = sc(MIN_ARROWHEAD_LENGTH);
drawArrow(xt1, yt1, xt2, yt2, arrowHeadLength);
}
}
ip.setClipRect(null);
break;
case PlotObject.SHAPES:
int iBoxWidth = 20;
ip.setClipRect(frame);
String shType = plotObject.shapeType.toLowerCase();
if (shType.contains("rectangles")) {
int nShapes = plotObject.shapeData.size();
for (int i = 0; i < nShapes; i++) {
float[] corners = (float[])(plotObject.shapeData.get(i));
int x1 = scaleX(corners[0]);
int y1 = scaleY(corners[1]);
int x2 = scaleX(corners[2]);
int y2 = scaleY(corners[3]);
ip.setLineWidth(sc(plotObject.lineWidth));
int left = Math.min(x1, x2);
int right = Math.max(x1, x2);
int top = Math.min(y1, y2);
int bottom = Math.max(y1, y2);
Rectangle r1 = new Rectangle(left, top, right-left, bottom - top);
Rectangle cBox = frame.intersection(r1);
if (plotObject.color2 != null) {
ip.setColor(plotObject.color2);
ip.fillRect(cBox.x, cBox.y, cBox.width, cBox.height);
}
ip.setColor(plotObject.color);
ip.drawRect(cBox.x, cBox.y, cBox.width, cBox.height);
}
ip.setClipRect(null);
break;
}
if (shType.equals("redraw_grid")) {
ip.setLineWidth(sc(1));
redrawGrid();
ip.setClipRect(null);
break;
}
if (shType.contains("boxes")) {
String[] parts = Tools.split(shType);
for (int jj = 0; jj < parts.length; jj++) {
String[] pairs = parts[jj].split("=");
if ((pairs.length == 2) && pairs[0].equals("width")) {
iBoxWidth = Integer.parseInt(pairs[1]);
}
}
boolean horizontal = shType.contains("boxesx");
int nShapes = plotObject.shapeData.size();
int halfWidth = Math.round(sc(iBoxWidth / 2));
for (int i = 0; i < nShapes; i++) {
float[] coords = (float[])(plotObject.shapeData.get(i));
if (!horizontal) {
int x = scaleX(coords[0]);
int y1 = scaleY(coords[1]);
int y2 = scaleY(coords[2]);
int y3 = scaleY(coords[3]);
int y4 = scaleY(coords[4]);
int y5 = scaleY(coords[5]);
ip.setLineWidth(sc(plotObject.lineWidth));
Rectangle r1 = new Rectangle(x - halfWidth, y4, halfWidth * 2, y2 - y4);
Rectangle cBox = frame.intersection(r1);
if (y1 != y2 || y4 != y5) {
ip.drawLine(x, y1, x, y5); }
if (plotObject.color2 != null) {
ip.setColor(plotObject.color2);
ip.fillRect(cBox.x, cBox.y, cBox.width, cBox.height);
}
ip.setColor(plotObject.color);
ip.drawRect(cBox.x, cBox.y, cBox.width, cBox.height);
ip.setClipRect(frame);
ip.drawLine(x - halfWidth, y3, x + halfWidth - 1, y3);
}
if (horizontal) {
int y = scaleY(coords[0]);
int x1 = scaleX(coords[1]);
int x2 = scaleX(coords[2]);
int x3 = scaleX(coords[3]);
int x4 = scaleX(coords[4]);
int x5 = scaleX(coords[5]);
ip.setLineWidth(sc(plotObject.lineWidth));
if(x1 !=x2 || x4 != x5) ip.drawLine(x1, y, x5, y); Rectangle r1 = new Rectangle(x2, y - halfWidth, x4 - x2, halfWidth * 2);
Rectangle cBox = frame.intersection(r1);
if (plotObject.color2 != null) {
ip.setColor(plotObject.color2);
ip.fillRect(cBox.x, cBox.y, cBox.width, cBox.height);
}
ip.setColor(plotObject.color);
ip.drawRect(cBox.x, cBox.y, cBox.width, cBox.height);
ip.setClipRect(frame);
ip.drawLine(x3, y - halfWidth, x3, y + halfWidth - 1);
}
}
ip.setClipRect(null);
break;
}
case PlotObject.LINE:
if (Double.isNaN(plotObject.x) || Double.isNaN(plotObject.y)) break;
ip.setClipRect(frame);
ip.drawLine(scaleX(plotObject.x), scaleY(plotObject.y), scaleX(plotObject.xEnd), scaleY(plotObject.yEnd));
ip.setClipRect(null);
break;
case PlotObject.NORMALIZED_LINE:
ip.setClipRect(frame);
int ix1 = leftMargin + (int)(plotObject.x*frameWidth);
int iy1 = topMargin + (int)(plotObject.y*frameHeight);
int ix2 = leftMargin + (int)(plotObject.xEnd*frameWidth);
int iy2 = topMargin + (int)(plotObject.yEnd*frameHeight);
ip.drawLine(ix1, iy1, ix2, iy2);
ip.setClipRect(null);
break;
case PlotObject.DOTTED_LINE:
ip.setClipRect(frame);
ix1 = scaleX(plotObject.x);
iy1 = scaleY(plotObject.y);
ix2 = scaleX(plotObject.xEnd);
iy2 = scaleY(plotObject.yEnd);
double length = calculateDistance(ix1, ix2, iy1, iy2) + 0.1;
int n = (int)(length/plotObject.step);
for (int i = 0; i<=n; i++)
ip.drawDot(ix1 + (int)Math.round((ix2-ix1)*(double)i/n), iy1 + (int)Math.round((iy2-iy1)*(double)i/n));
ip.setClipRect(null);
break;
case PlotObject.LABEL:
case PlotObject.NORMALIZED_LABEL:
ip.setJustification(plotObject.justification);
if (plotObject.getFont() != null)
ip.setFont(scFont(plotObject.getFont()));
int xt = type==PlotObject.LABEL ? scaleX(plotObject.x) : leftMargin + (int)(plotObject.x*frameWidth);
int yt = type==PlotObject.LABEL ? scaleY(plotObject.y) : topMargin + (int)(plotObject.y*frameHeight);
ip.drawString(plotObject.label, xt, yt);
break;
case PlotObject.LEGEND:
drawLegend(plotObject, ip);
break;
}
}
void drawBarChart(PlotObject plotObject) {
int n = Math.min(plotObject.xValues.length, plotObject.yValues.length);
String[] xCats = labelsInBraces('x'); boolean separatedBars = plotObject.shape == SEPARATED_BAR || xCats != null;
int halfBarWidthInPixels = n <= 1 ? Math.max(1, frameWidth/2-2) : 0;
if (separatedBars && n > 1)
halfBarWidthInPixels = Math.max(1, (int)Math.round(Math.abs
(0.5*(plotObject.xValues[n-1] - plotObject.xValues[0])/(n-1) * xScale * SEPARATED_BAR_WIDTH)));
int y0 = scaleYWithOverflow(0);
boolean yZeroInFrame = !logYAxis && yBasePxl>frame.y && yBasePxl<frame.y+frame.height;
int prevY = y0;
for (int i = 0; i < n; i++) {
int left=0, right=0;
if (halfBarWidthInPixels == 0) { left = scaleX(i > 0 ? 0.5f*(plotObject.xValues[i-1]+plotObject.xValues[i]) :
1.5f*plotObject.xValues[i] - 0.5f*plotObject.xValues[i+1]);
right = scaleX(i < n-1 ? 0.5f*(plotObject.xValues[i]+plotObject.xValues[i+1]) :
1.5f*plotObject.xValues[i] - 0.5f*plotObject.xValues[i-1]);
} else {
int x = scaleX(plotObject.xValues[i]);
left = x - halfBarWidthInPixels; right = x + halfBarWidthInPixels;
}
if (left < frame.x) left = frame.x;
if (left > frame.x+frame.width) left = frame.x+frame.width;
if (right < frame.x) right = frame.x;
if (right > frame.x+frame.width) right = frame.x+frame.width;
int y = scaleYWithOverflow(plotObject.yValues[i]);
if (plotObject.color2 != null) {
ip.setColor(plotObject.color2);
for (int x2 = Math.min(left,right); x2 <= Math.max(left,right); x2++)
ip.drawLine(x2, y0, x2, y); }
ip.setColor(plotObject.color);
ip.setLineWidth(sc(plotObject.lineWidth));
if (separatedBars) {
ip.drawLine(left, y0, left, y); ip.drawLine(left, y, right, y); ip.drawLine(right, y, right, y0); if (yZeroInFrame)
ip.drawLine(left, y0, right, y0); } else {
ip.drawLine(left, prevY, left, y); ip.drawLine(left, y, right, y); if (i == n - 1)
ip.drawLine(right, y, right, y0); prevY = y;
}
}
}
void drawShape(PlotObject plotObject, int x, int y, int shape, int size, int pointIndex) {
if (shape == DIAMOND) size = (int)(size*1.21);
int xbase = x-sc(size/2);
int ybase = y-sc(size/2);
int xend = x+sc(size/2);
int yend = y+sc(size/2);
if (ip==null)
return;
switch(shape) {
case X:
ip.drawLine(xbase,ybase,xend,yend);
ip.drawLine(xend,ybase,xbase,yend);
break;
case BOX:
ip.drawLine(xbase,ybase,xend,ybase);
ip.drawLine(xend,ybase,xend,yend);
ip.drawLine(xend,yend,xbase,yend);
ip.drawLine(xbase,yend,xbase,ybase);
break;
case TRIANGLE:
ip.drawLine(x,ybase-sc(1),xend+sc(1),yend); ip.drawLine(x,ybase-sc(1),xbase-sc(1),yend);
ip.drawLine(xend+sc(1),yend,xbase-sc(1),yend);
break;
case CROSS:
ip.drawLine(xbase,y,xend,y);
ip.drawLine(x,ybase,x,yend);
break;
case DIAMOND:
ip.drawLine(xbase,y,x,ybase);
ip.drawLine(x,ybase,xend,y);
ip.drawLine(xend,y,x,yend);
ip.drawLine(x,yend,xbase,y);
break;
case DOT:
ip.drawDot(x, y); break;
case CUSTOM:
if (plotObject.macroCode==null || frame==null)
break;
if (x<frame.x || y<frame.y || x>=frame.x+frame.width || y>=frame.y+frame.height)
break;
ImagePlus imp = new ImagePlus("", ip);
WindowManager.setTempCurrentImage(imp);
StringBuilder sb = new StringBuilder(140+plotObject.macroCode.length());
sb.append("x="); sb.append(x);
sb.append(";y="); sb.append(y);
sb.append(";setColor('");
sb.append(Tools.c2hex(plotObject.color));
sb.append("');s="); sb.append(sc(1));
boolean drawingLegend = pointIndex < 0;
double xVal = 0;
double yVal = 0;
if (!drawingLegend) {
xVal = plotObject.xValues[pointIndex];
yVal = plotObject.yValues[pointIndex];
}
sb.append(";i="); sb.append(drawingLegend ? 0 : pointIndex);
sb.append(";xval=" + xVal);
sb.append(";yval=" + yVal);
sb.append(";");
sb.append(plotObject.macroCode);
if (!drawingLegend ||!sb.toString().contains("d2s") ) { String rtn = IJ.runMacro(sb.toString()); if ("[aborted]".equals(rtn))
plotObject.macroCode = null;
}
WindowManager.setTempCurrentImage(null);
break;
default: if (sc(size) < 5.01) {
ip.drawLine(x-1, y-2, x+1, y-2);
ip.drawLine(x-1, y+2, x+1, y+2);
ip.drawLine(x+2, y+1, x+2, y-1);
ip.drawLine(x-2, y+1, x-2, y-1);
} else {
int r = sc(0.5f*size-0.5f);
ip.drawOval(x-r, y-r, 2*r, 2*r);
}
break;
}
}
void fillShape(int shape, int x0, int y0, int size) {
if (shape == DIAMOND) size = (int)(size*1.21);
int r = sc(size/2)-1;
switch(shape) {
case BOX:
for (int dy=-r; dy<=r; dy++)
for (int dx=-r; dx<=r; dx++)
ip.drawDot(x0+dx, y0+dy);
break;
case TRIANGLE:
int ybase = y0 - r - sc(1);
int yend = y0 + r;
double halfWidth = sc(size/2)+sc(1)-1;
double hwStep = halfWidth/(yend-ybase+1);
for (int y=yend; y>=ybase; y--, halfWidth -= hwStep) {
int dx = (int)(Math.round(halfWidth));
for (int x=x0-dx; x<=x0+dx; x++)
ip.drawDot(x,y);
}
break;
case DIAMOND:
ybase = y0 - r - sc(1);
yend = y0 + r;
halfWidth = sc(size/2)+sc(1)-1;
hwStep = halfWidth/(yend-ybase+1);
for (int y=yend; y>=ybase; y--) {
int dx = (int)(Math.round(halfWidth-(hwStep+1)*Math.abs(y-y0)));
for (int x=x0-dx; x<=x0+dx; x++)
ip.drawDot(x,y);
}
break;
case CIRCLE: case CONNECTED_CIRCLES:
int rsquare = (r+1)*(r+1);
for (int dy=-r; dy<=r; dy++)
for (int dx=-r; dx<=r; dx++)
if (dx*dx + dy*dy <= rsquare)
ip.drawDot(x0+dx, y0+dy);
break;
}
}
@Deprecated
public void drawArrow(int x1, int y1, int x2, int y2, double size) {
double dx = x2 - x1;
double dy = y2 - y1;
double ra = Math.sqrt(dx * dx + dy * dy);
dx /= ra;
dy /= ra;
int x3 = (int) Math.round(x2 - dx * size); int y3 = (int) Math.round(y2 - dy * size);
double r = 0.3 * size;
int x4 = (int) Math.round(x3 + dy * r);
int y4 = (int) Math.round(y3 - dx * r);
int x5 = (int) Math.round(x3 - dy * r);
int y5 = (int) Math.round(y3 + dx * r);
ip.moveTo(x1, y1); ip.lineTo(x2, y2);
ip.moveTo(x4, y4); ip.lineTo(x2, y2); ip.lineTo(x5, y5);
}
private void drawVerticalErrorBars(float[] x, float[] y, float[] e) {
int nPoints = Math.min(Math.min(x.length, y.length), e.length);
for (int i=0; i<nPoints; i++) {
if (Float.isNaN(x[i]) || Float.isNaN(y[i]) || (logXAxis && !(x[i] >0))) continue;
int x0 = scaleX(x[i]);
int yPlus = scaleYWithOverflow(y[i] + e[i]);
int yMinus = scaleYWithOverflow(y[i] - e[i]);
ip.moveTo(x0,yMinus);
ip.lineTo(x0, yPlus);
}
}
private void drawHorizontalErrorBars(float[] x, float[] y, float[] e) {
int nPoints = Math.min(Math.min(x.length, y.length), e.length);
float[] xpoints = new float[2];
float[] ypoints = new float[2];
for (int i=0; i<nPoints; i++) {
if (Float.isNaN(x[i]) || Float.isNaN(y[i]) || (logXAxis && !(y[i] >0))) continue;
int y0 = scaleY(y[i]);
int xPlus = scaleXWithOverflow(x[i] + e[i]);
int xMinus = scaleXWithOverflow(x[i] - e[i]);
ip.moveTo(xMinus,y0);
ip.lineTo(xPlus, y0);
}
}
void drawFloatPolyline(ImageProcessor ip, float[] x, float[] y, int n) {
if (x==null || x.length==0) return;
int x1, y1;
boolean isNaN0;
boolean isNaN1 = true; int x2 = scaleX(x[0]);
int y2 = scaleY(y[0]);
boolean isNaN2 = Float.isNaN(x[0]) || Float.isNaN(y[0]) || (logXAxis && x[0]<=0) || (logYAxis && y[0]<=0);
for (int i=1; i<n; i++) {
x1 = x2;
y1 = y2;
isNaN0 = isNaN1;
isNaN1 = isNaN2;
x2 = scaleX(x[i]);
y2 = scaleY(y[i]);
isNaN2 = Float.isNaN(x[i]) || Float.isNaN(y[i]) || (logXAxis && x[i]<=0) || (logYAxis && y[i]<=0);
if (!isNaN1 && !isNaN2)
ip.drawLine(x1, y1, x2, y2);
else if (isNaN0 && !isNaN1 && isNaN2) ip.drawLine(x1, y1, x1, y1);
}
if (isNaN1 && !isNaN2)
ip.drawLine(x2, y2, x2, y2); }
void drawFloatPolyLineFilled(ImageProcessor ip, float[] xF, float[] yF, int len) {
if (xF == null || len <=1)
return;
ip.setLineWidth(1);
int y0 = scaleYWithOverflow(0);
int x1, y1;
int x2 = scaleX(xF[0]);
int y2 = scaleY(yF[0]);
boolean isNaN1;
boolean isNaN2 = Float.isNaN(xF[0]) || Float.isNaN(yF[0]) || (logXAxis && xF[0]<=0) || (logYAxis && yF[0]<=0);
for (int i = 1; i < len; i++) {
isNaN1 = isNaN2;
isNaN2 = Float.isNaN(xF[i]) || Float.isNaN(yF[i]) || (logXAxis && xF[i]<=0) || (logYAxis && yF[i]<=0);
x1 = x2;
y1 = y2;
x2 = scaleX(xF[i]);
y2 = scaleY(yF[i]);
int left = x1;
int right = x2;
if (isNaN1 || isNaN2) continue;
if (left < frame.x && right < frame.x) continue; if (left >= frame.x+frame.width && right >= frame.x+frame.width) continue;
if (left < frame.x) left = frame.x;
if (left >= frame.x+frame.width) left = frame.x+frame.width-1;
if (right < frame.x) right = frame.x;
if (right >= frame.x+frame.width) right = frame.x+frame.width-1;
if (left != right) {
for (int xi = Math.min(left,right); xi <= Math.max(left,right); xi++) {
int yi = (int)Math.round(y1 + (double)(y2 - y1)*(double)(xi - x1)/(double)(x2 - x1));
ip.drawLine(xi, y0, xi, yi);
}
} else {
ip.drawLine(left, y0, left, y2);
}
}
}
Vector<PlotObject> getIndexedPlotObjects(){
boolean withIndex = false;
int len = allPlotObjects.size();
String[] labels = new String[len];
Vector<PlotObject> indexedObjects = new Vector<PlotObject>();
for(int jj = 0; jj < len; jj++){
PlotObject plotObject = allPlotObjects.get(jj);
labels[jj] = "";
if (plotObject.type == PlotObject.XY_DATA && !plotObject.hasFlag(PlotObject.HIDDEN) && plotObject.label != null) {
String label = plotObject.label;
if(label.indexOf("__") >=0 && label.indexOf("__") <= 2){
labels[jj]= plotObject.label;
withIndex = true;
}
}
}
int[] ranks = Tools.rank(labels);
for(int jj = 0; jj < len; jj++){
if(labels[ranks[jj]] != ""){
int index = ranks[jj];
indexedObjects.add(allPlotObjects.get(index));
}
}
if(!withIndex)
return null;
return indexedObjects;
}
void drawLegend(PlotObject legendObject, ImageProcessor ip) {
ip.setFont(scFont(legendObject.getFont()));
int nLabels = 0;
int maxStringWidth = 0;
float maxLineThickness = 0;
Vector<PlotObject> usedPlotObjects = allPlotObjects;
Vector<PlotObject> indexedObjects = getIndexedPlotObjects();
if(indexedObjects != null)
usedPlotObjects= indexedObjects;
for (PlotObject plotObject : usedPlotObjects)
if (plotObject.type == PlotObject.XY_DATA && !plotObject.hasFlag(PlotObject.HIDDEN) && plotObject.label != null) { nLabels++;
String label = plotObject.label;
if (indexedObjects != null)
label = label.substring(label.indexOf("__") + 2);
int w = ip.getStringWidth(label);
if (w > maxStringWidth) maxStringWidth = w;
if (plotObject.lineWidth > maxLineThickness) maxLineThickness = plotObject.lineWidth;
}
if (nLabels == 0) return;
if (pp.antialiasedText && scale > 1) maxStringWidth = (int)((1 + 0.004*scale) * maxStringWidth);
int frameThickness = sc(legendObject.lineWidth > 0 ? legendObject.lineWidth : 1);
FontMetrics fm = ip.getFontMetrics();
ip.setJustification(LEFT);
int lineHeight = fm.getHeight();
int height = nLabels*lineHeight + 2*sc(LEGEND_PADDING);
int width = maxStringWidth + sc(3*LEGEND_PADDING + LEGEND_LINELENGTH + maxLineThickness);
int positionCode = legendObject.flags & LEGEND_POSITION_MASK;
if (positionCode == AUTO_POSITION)
positionCode = autoLegendPosition(width, height, frameThickness);
Rectangle rect = legendRect(positionCode, width, height, frameThickness);
int x0 = rect.x;
int y0 = rect.y;
ip.setColor(Color.white);
ip.setLineWidth(1);
if (!legendObject.hasFlag(LEGEND_TRANSPARENT)) {
ip.setRoi(x0, y0, width, height);
ip.fill();
} else if (hasFlag(X_GRID | Y_GRID)) { int grid = ip instanceof ColorProcessor ? (gridColor.getRGB() & 0xffffff) : ip.getBestIndex(gridColor);
for (int y=y0; y<y0+height; y++)
for (int x=x0; x<x0+width; x++)
if ((ip.getPixel(x, y) & 0xffffff) == grid)
ip.drawPixel(x, y);
}
ip.setLineWidth(frameThickness);
ip.setColor(legendObject.color);
ip.drawRect(x0-frameThickness/2, y0-frameThickness/2, width+frameThickness, height);
boolean bottomUp = legendObject.hasFlag(LEGEND_BOTTOM_UP);
int y = y0 + frameThickness/2 + sc(LEGEND_PADDING) + lineHeight/2;
if (bottomUp) y += (nLabels-1) * lineHeight;
int xText = x0 + frameThickness/2 + sc(2f*LEGEND_PADDING + LEGEND_LINELENGTH + maxLineThickness);
int xMarker = x0 + frameThickness/2 + sc(LEGEND_PADDING + 0.5f*(LEGEND_LINELENGTH + maxLineThickness));
int xLine0 = x0 + frameThickness/2 + sc(LEGEND_PADDING) + 1;
for (PlotObject plotObject : usedPlotObjects)
if (plotObject.type == PlotObject.XY_DATA && !plotObject.hasFlag(PlotObject.HIDDEN) && plotObject.label != null) { int shape = plotObject.shape;
if (shape == SEPARATED_BAR) shape = BOX; int yShiftLine = 0;
if (shape == FILLED || shape == BAR && plotObject.color2 != null) yShiftLine = sc(0.1f*legendObject.getFontSize() + 0.3f*plotObject.lineWidth);
int markerSize = plotObject.getMarkerSize();
if (plotObject.shape == SEPARATED_BAR && markerSize < 0.6*legendObject.getFontSize())
markerSize = 2*(int)(0.3*legendObject.getFontSize()) + 1; if (plotObject.hasFilledMarker() || (plotObject.shape == SEPARATED_BAR && plotObject.color2 != null)) {
ip.setColor(plotObject.color2);
fillShape(shape, xMarker, y, markerSize);
} else if (yShiftLine != 0) { ip.setColor(plotObject.color2 == null ? plotObject.color : plotObject.color2);
ip.fillRect(xLine0, y-yShiftLine, 2*(xMarker - xLine0)+1, yShiftLine+(int)(0.3*legendObject.getFontSize()));
}
int lineWidth = sc(plotObject.lineWidth);
if (lineWidth < 1) lineWidth = 1;
ip.setLineWidth(lineWidth);
if (plotObject.hasCurve() || plotObject.shape==BAR) {
Color c = plotObject.shape == CONNECTED_CIRCLES ?
(plotObject.color2 == null ? Color.black : plotObject.color2) :
plotObject.color;
ip.setColor(c);
ip.fillRect(xLine0, y-lineWidth/2-yShiftLine, 2*(xMarker - xLine0)+1, lineWidth); }
if (plotObject.hasMarker() || plotObject.shape == SEPARATED_BAR) {
Font saveFont = ip.getFont();
ip.setColor(plotObject.color);
drawShape(plotObject, xMarker, y, shape, markerSize, -1);
if (plotObject.shape==CUSTOM) ip.setFont(saveFont);
}
ip.setColor(plotObject.color);
ip.setLineWidth(frameThickness);
String label = plotObject.label;
if (indexedObjects != null){
int start = label.indexOf("__");
if(start >=0)
label = label.substring(start+2);
}
ip.drawString(label, xText, y+ lineHeight/2);
y += bottomUp ? -lineHeight : lineHeight;
}
}
Rectangle legendRect(int positionCode, int width, int height, int frameThickness) {
boolean leftPosition = positionCode == TOP_LEFT || positionCode == BOTTOM_LEFT;
boolean topPosition = positionCode == TOP_LEFT || positionCode == TOP_RIGHT;
int x0 = (leftPosition) ?
leftMargin + sc(2*LEGEND_PADDING) + frameThickness/2 :
leftMargin + frameWidth - width - sc(2*LEGEND_PADDING) - frameThickness/2;
int y0 = (topPosition) ?
topMargin + sc(LEGEND_PADDING) + frameThickness/2 :
topMargin + frameHeight - height - sc(LEGEND_PADDING) + frameThickness/2;
if (hasFlag(Y_TICKS))
x0 += (leftPosition ? 1 : -1) * sc(tickLength - LEGEND_PADDING);
if (hasFlag(X_TICKS))
y0 += (topPosition ? 1 : -1) * sc(tickLength - LEGEND_PADDING/2);
return new Rectangle(x0, y0, width, height);
}
int autoLegendPosition(int width, int height, int frameThickness) {
int background = ip instanceof ColorProcessor ? (0xffffff) : (ip.isInvertedLut() ? 0 : 0xff);
int grid = ip instanceof ColorProcessor ? (gridColor.getRGB() & 0xffffff) : ip.getBestIndex(gridColor);
int bestPosition = 0;
int minCoveredPixels = Integer.MAX_VALUE;
for (int positionCode : new int[]{TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT}) {
Rectangle rect = legendRect(positionCode, width, height, frameThickness);
int coveredPixels = 0;
for (int y = rect.y - frameThickness/2; y <= rect.y + rect.height + frameThickness/2; y++)
for (int x = rect.x - frameThickness/2; x <= rect.x + rect.width + frameThickness/2; x++) {
int pixel = ip.getPixel(x, y) & 0xffffff;
if (pixel != background && pixel != grid)
coveredPixels ++;
}
if (coveredPixels < minCoveredPixels) {
minCoveredPixels = coveredPixels;
bestPosition = positionCode;
}
}
return bestPosition;
}
String getCoordinates(int x, int y) {
if (frame==null) return "";
String text = "";
if (!frame.contains(x, y))
return text;
double xv = descaleX(x); double yv = descaleY(y);
boolean yIsValue = false;
if (!hasMultiplePlots()) {
PlotObject p = getMainCurveObject(); if (p != null) {
double bestDx = Double.MAX_VALUE;
double xBest = 0, yBest = 0;
for (int i=0; i<Math.min(p.xValues.length, p.yValues.length); i++) {
double xp = p.xValues[i];
if (Math.abs(xp-xv) < bestDx) {
bestDx = Math.abs(xp-xv);
xBest = xp;
yBest = p.yValues[i];
}
}
if (Math.abs(scaleXtoPxl(xBest)-x) < 50) { xv = xBest;
yv = yBest;
yIsValue = true;
}
}
}
if (!Double.isNaN(xv)) {
int significantDigits = logXAxis ? -2 : getDigits(xv, 0.001*(xMax-xMin), 6, 0);
text = "X=" + IJ.d2s(xv, significantDigits)+", Y";
if (yIsValue) text += "(X)";
significantDigits = logYAxis ? -2 : getDigits(yv, 0.001*(yMax-yMin), 6, 0);
text +="="+ IJ.d2s(yv, significantDigits);
}
return text;
}
private PlotObject getMainCurveObject() {
for (PlotObject plotObject : allPlotObjects) {
if (plotObject.type == PlotObject.XY_DATA)
return plotObject;
}
return null;
}
private PlotObject getLastCurveObject() {
for (int i=allPlotObjects.size()-1; i>=0; i--) {
if (allPlotObjects.get(i).type == PlotObject.XY_DATA)
return allPlotObjects.get(i);
}
return null;
}
private boolean hasMultiplePlots() {
int nPlots = 0;
for (PlotObject plotObject : allPlotObjects) {
if (plotObject.type == PlotObject.ARROWS)
return true;
else if (plotObject.type == PlotObject.XY_DATA) {
nPlots ++;
if (nPlots > 1) return true;
}
}
return nPlots > 1;
}
public void setPlotMaker(PlotMaker plotMaker) {
this.plotMaker = plotMaker;
}
PlotMaker getPlotMaker() {
return plotMaker;
}
String getDataLabels() {
String labels = "";
boolean first = true;
for (PlotObject plotObject : allPlotObjects)
if (plotObject.type == PlotObject.XY_DATA && !plotObject.hasFlag(PlotObject.HIDDEN)) {
if (first)
first = false;
else
labels += '\n';
if (plotObject.label != null) labels += plotObject.label;
}
return labels;
}
public ResultsTable getResultsTable() {
return getResultsTable(true);
}
public ResultsTable getResultsTable(boolean writeFirstXColumn) {
return getResultsTable(writeFirstXColumn, false);
}
public ResultsTable getResultsTableWithLabels() {
return getResultsTable(true, true);
}
ResultsTable getResultsTable(boolean writeFirstXColumn, boolean useLabels) {
ResultsTable rt = new ResultsTable();
int nDataSets = 0;
int tableLength = 0;
for (PlotObject plotObject : allPlotObjects)
if (plotObject.xValues != null) {
nDataSets++;
tableLength = Math.max(tableLength, plotObject.xValues.length);
}
if (nDataSets == 0)
return null;
ArrayList<String> headings = new ArrayList<String>(2*nDataSets);
ArrayList<float[]> data = new ArrayList<float[]>(2*nDataSets);
int dataSetNumber = 0;
int arrowsNumber = 0;
PlotObject firstXYobject = null;
boolean allSameLength = true;
for (PlotObject plotObject : allPlotObjects) {
if (plotObject.type==PlotObject.XY_DATA) {
if (firstXYobject != null && firstXYobject.xValues.length!=plotObject.xValues.length) {
allSameLength = false;
break;
}
if (firstXYobject==null)
firstXYobject = plotObject;
}
}
firstXYobject = null;
for (PlotObject plotObject : allPlotObjects) {
if (plotObject.type==PlotObject.XY_DATA) {
boolean sameX = firstXYobject!=null && Arrays.equals(firstXYobject.xValues, plotObject.xValues) && allSameLength;
boolean sameXY = sameX && Arrays.equals(firstXYobject.yValues, plotObject.yValues); boolean writeX = firstXYobject==null ? writeFirstXColumn : !sameX;
addToLists(headings, data, plotObject, dataSetNumber, writeX, !sameXY, nDataSets>1, useLabels);
if (firstXYobject == null)
firstXYobject = plotObject;
dataSetNumber++;
} else if (plotObject.type==PlotObject.ARROWS) {
addToLists(headings, data, plotObject, arrowsNumber, true, true, nDataSets>1, false);
arrowsNumber++;
}
}
int nColumns = headings.size();
for (int line=0; line<tableLength; line++) {
for (int col=0; col<nColumns; col++) {
String heading = headings.get(col);
float[] values = data.get(col);
if (line<values.length)
rt.setValue(heading, line, values[line]);
else
rt.setValue(heading, line, "");
}
}
nColumns = rt.getLastColumn() + 1;
for (int i=0; i<nColumns; i++)
rt.setDecimalPlaces(i, getPrecision(rt.getColumn(i)));
return rt;
}
static final int MIN_SCIENTIFIC_DIGITS = 4;
static final double MIN_FLOAT_PRECISION = 1e-5;
void addToLists(ArrayList<String> headings, ArrayList<float[]>data, PlotObject plotObject,
int dataSetNumber, boolean writeX, boolean writeY, boolean multipleSets, boolean useLabels) {
String plotObjectLabel = useLabels ? replaceSpacesEtc(plotObject.label) : null;
if (writeX) {
String label = null; if (plotObject.type!=PlotObject.ARROWS) {
String plotXLabel = getLabel('x');
if (dataSetNumber==0 && plotXLabel!=null) { if (useLabels)
label = replaceSpacesEtc(plotXLabel);
else if (plotXLabel.startsWith(" ") && plotXLabel.endsWith(" ")) label = plotXLabel.substring(1,plotXLabel.length()-1);
} else if (plotObjectLabel != null && dataSetNumber>0)
label = "X_"+plotObjectLabel; if (label != null && headings.contains(label))
label = null; }
if (label == null) { label = plotObject.type == PlotObject.ARROWS ? "XStart" : "X";
if (multipleSets) label += dataSetNumber;
}
headings.add(label);
data.add(plotObject.xValues);
}
if (writeY) {
String label = null;; if (plotObject.type!=PlotObject.ARROWS) {
String plotYLabel = getLabel('y');
if (dataSetNumber==0 && plotYLabel!=null) {
if (useLabels && plotObjectLabel == null) label = replaceSpacesEtc(plotYLabel);
else if (plotYLabel.startsWith(" ") && plotYLabel.endsWith(" ")) label = plotYLabel.substring(1,plotYLabel.length()-1);
}
if (plotObjectLabel != null)
label = plotObjectLabel;
if (label != null && headings.contains(label))
label = null; }
if (label == null) { label = plotObject.type == PlotObject.ARROWS ? "YStart" : "Y";
if (multipleSets) label += dataSetNumber;
}
headings.add(label);
data.add(plotObject.yValues);
}
if (plotObject.xEValues != null) {
String label = plotObject.type == PlotObject.ARROWS ? "XEnd" : "XERR";
if (multipleSets) label += dataSetNumber;
headings.add(label);
data.add(plotObject.xEValues);
}
if (plotObject.yEValues != null) {
String label = plotObject.type == PlotObject.ARROWS ? "YEnd" : "ERR";
if (multipleSets) label += dataSetNumber;
headings.add(label);
data.add(plotObject.yEValues);
}
}
static String replaceSpacesEtc(String s) {
if (s == null) return null;
s = s.trim().replaceAll("[\\s,]", "_").replace("\"","''");
if (s.length() == 0) return null;
return s;
}
static int getPrecision(float[] values) {
int setDigits = Analyzer.getPrecision();
int measurements = Analyzer.getMeasurements();
boolean scientificNotation = (measurements&Measurements.SCIENTIFIC_NOTATION)!=0;
if (scientificNotation) {
if (setDigits<MIN_SCIENTIFIC_DIGITS)
setDigits = MIN_SCIENTIFIC_DIGITS;
return -setDigits;
}
boolean allInteger = true;
float min = Float.MAX_VALUE, max = -Float.MAX_VALUE;
for (int i=0; i<values.length; i++) {
if ((int)values[i]!=values[i] && !Float.isNaN(values[i])) {
allInteger = false;
if (values[i] < min) min = values[i];
if (values[i] > max) max = values[i];
}
}
if (allInteger)
return 0;
int digits = (max - min) > 0 ? getDigits(min, max, MIN_FLOAT_PRECISION*(max-min), 15, 0) :
getDigits(max, MIN_FLOAT_PRECISION*Math.abs(max), 15, 0);
if (setDigits>Math.abs(digits))
digits = setDigits * (digits < 0 ? -1 : 1); return digits;
}
boolean hasFlag(int what) {
return (pp.axisFlags&what) != 0;
}
public void addPoints(String dummy, float[] x, float[] y, int shape) {
addPoints(x, y, shape);
}
public void addHistogram(double[] values) {
addHistogram(values, 0, 0);
}
public void addHistogram(double[] values, double binWidth) {
addHistogram(values, binWidth, 0);
}
public void addHistogram(double[] values, double binWidth, double binCenter) {
int len = values.length;
double min = Double.POSITIVE_INFINITY;
double max = Double.NEGATIVE_INFINITY;
double[] cleanVals = new double[len];
int count = 0;
double sum = 0, sum2 = 0;
for (int i = 0; i < len; i++) {
double val = values[i];
if (!Double.isNaN(val)) {
cleanVals[count++] = val;
sum += val;
sum2 += val * val;
if (val < min)
min = val;
if (val > max)
max = val;
}
}
if (binWidth <= 0) { double stdDev = Math.sqrt(((count * sum2 - sum * sum) / count) / count); binWidth = 3.49 * stdDev * (Math.pow(count, -1.0 / 3));
}
double modCenter = binCenter % binWidth;
double modMin = min % binWidth;
double diff = modMin - modCenter;
double firstBin = min-diff;
while(firstBin - binWidth * 0.499 > min)
firstBin -= binWidth;
int nBins = (int) ((max - firstBin)/binWidth);
double lastBin = firstBin + nBins * binWidth;
while(lastBin + binWidth * 0.499 < max)
lastBin += binWidth;
nBins = (int) Math.round((lastBin - firstBin)/binWidth) + 1;
if (nBins == 1)
nBins = 2;
if (nBins > 9999) {
IJ.error("max bins > 9999");
return;
}
double[] histo = new double[nBins];
double[] xValues = new double[nBins];
for (int i = 0; i < nBins; i++)
xValues[i] = firstBin + i * binWidth;
for (int i = 0; i < count; i++) {
double val = cleanVals[i];
double indexD = (val - firstBin) / binWidth;
int index = (int) Math.round(indexD);
if (index < 0 || index >= nBins) {
IJ.error("index out of range");
return;
} else
histo[index]++;
}
add("bar", xValues, histo);
}
public void addErrorBars(String dummy, float[] errorBars) {
addErrorBars(errorBars);
}
public void changeFont(Font font) {
setFont(font);
}
}
class PlotProperties implements Cloneable, Serializable {
static final long serialVersionUID = 1L;
PlotObject frame = new PlotObject(Plot.DEFAULT_FRAME_LINE_WIDTH); PlotObject xLabel = new PlotObject(PlotObject.AXIS_LABEL); PlotObject yLabel = new PlotObject(PlotObject.AXIS_LABEL); PlotObject legend; int width = 0; int height = 0;
int axisFlags; double[] rangeMinMax; boolean antialiasedText = true;
boolean isFrozen;
PlotObject[] getAllPlotObjects() {
return new PlotObject[]{frame, xLabel, xLabel, legend};
}
PlotObject getPlotObject(char c) {
switch(c) {
case 'x': return xLabel;
case 'y': return yLabel;
case 'f': return frame;
case 'l': return legend;
default: return null;
}
}
public PlotProperties clone() {
try {
return (PlotProperties)(super.clone());
} catch (CloneNotSupportedException e) {
return null;
}
}
public PlotProperties deepClone() {
PlotProperties pp2 = clone(); if (frame != null) pp2.frame = frame.deepClone();
if (xLabel != null) pp2.xLabel = xLabel.deepClone();
if (yLabel != null) pp2.yLabel = yLabel.deepClone();
if (legend != null) pp2.legend = legend.deepClone();
if (rangeMinMax != null) pp2.rangeMinMax = rangeMinMax.clone();
return pp2;
}
}
class PlotObject implements Cloneable, Serializable {
static final long serialVersionUID = 1L;
public final static int XY_DATA = 1, ARROWS = 2, LINE = 4, NORMALIZED_LINE = 8, DOTTED_LINE = 16,
LABEL = 32, NORMALIZED_LABEL = 64, LEGEND = 128, AXIS_LABEL = 256, FRAME = 512, SHAPES = 1024;
final static int FONT_STYLE_MASK = 0x0f;
public final static int CONSTRUCTOR_DATA = 0x1000;
public final static int HIDDEN = 0x2000;
public int type = XY_DATA;
public int flags;
public String options;
public float[] xValues, yValues, xEValues, yEValues;
public ArrayList shapeData;
public String shapeType;
public int shape;
public float lineWidth;
public Color color;
public Color color2;
public double x, y;
public double xEnd, yEnd;
public int step;
public String label;
public int justification;
public String macroCode;
private transient Font font;
private String fontFamily;
private float fontSize;
PlotObject(int type) {
this.type = type;
}
PlotObject(float[] xValues, float[] yValues, float[] yErrorBars, int shape, float lineWidth, Color color, Color color2, String yLabel) {
this.type = XY_DATA;
this.xValues = xValues;
this.yValues = yValues;
this.yEValues = yErrorBars;
this.shape = shape;
this.lineWidth = lineWidth;
this.color = color;
this.color2 = color2;
this.label = yLabel;
if (shape==Plot.CUSTOM)
this.macroCode = yLabel;
}
PlotObject(float[] x1, float[] y1, float[] x2, float[] y2, float lineWidth, Color color) {
this.type = ARROWS;
this.xValues = x1;
this.yValues = y1;
this.xEValues = x2;
this.yEValues = y2;
this.lineWidth = lineWidth;
this.color = color;
}
PlotObject(String shapeType, ArrayList shapeData, float lineWidth, Color color, Color color2) {
this.type = SHAPES;
this.shapeData = shapeData;
this.shapeType = shapeType;
this.lineWidth = lineWidth;
this.color = color;
this.color2 = color2;
}
PlotObject(double x, double y, double xEnd, double yEnd, float lineWidth, int step, Color color, int type) {
this.type = type;
this.x = x;
this.y = y;
this.xEnd = xEnd;
this.yEnd = yEnd;
this.lineWidth = lineWidth;
this.step = step;
this.color = color;
}
PlotObject(String label, double x, double y, int justification, Font font, Color color, int type) {
this.type = type;
this.label = label;
this.x = x;
this.y = y;
this.justification = justification;
setFont(font);
this.color = color;
}
PlotObject(float lineWidth, Font font, Color color, int flags) {
this.type = LEGEND;
this.lineWidth = lineWidth;
setFont(font);
this.color = color;
this.flags = flags;
}
PlotObject(float lineWidth) {
this.type = FRAME;
this.color = Color.black;
this.lineWidth = lineWidth;
}
boolean hasFlag(int what) {
return (flags&what) != 0;
}
void setFlag(int what) {
flags |= what;
}
void unsetFlag(int what) {
flags = flags & (~what);
}
boolean hasCurve() {
return type == XY_DATA && (shape == Plot.LINE || shape == Plot.CONNECTED_CIRCLES || shape == Plot.FILLED);
}
boolean hasMarker() {
return type == XY_DATA && (shape == Plot.CIRCLE || shape == Plot.X || shape == Plot.BOX || shape == Plot.TRIANGLE
|| shape == Plot.CROSS || shape == Plot.DIAMOND || shape == Plot.DOT || shape == Plot.CONNECTED_CIRCLES
|| shape == Plot.CUSTOM);
}
boolean hasFilledMarker() {
return type == XY_DATA && color2 != null && (shape == Plot.CIRCLE || shape == Plot.BOX || shape == Plot.TRIANGLE ||
shape == Plot.DIAMOND || shape == Plot.CONNECTED_CIRCLES);
}
int getMarkerSize() {
return lineWidth<=1 ? 5 : 7;
}
void setFont(Font font) {
if (font == this.font) return;
this.font = font;
if (font == null) {
fontFamily = null;
} else {
fontFamily = font.getFamily();
flags = (flags & ~FONT_STYLE_MASK) | font.getStyle();
fontSize = font.getSize2D();
}
}
Font getFont() {
if (font == null && fontFamily != null) font = FontUtil.getFont(fontFamily, flags&FONT_STYLE_MASK, fontSize);
return font;
}
float getFontSize() {
return fontSize;
}
float[][] getAllDataValues() {
return new float[][] {xValues, yValues, xEValues, yEValues};
}
public PlotObject clone() {
try {
return (PlotObject)(super.clone());
} catch (CloneNotSupportedException e) {
return null;
}
}
public PlotObject deepClone() {
PlotObject po2 = clone();
if (xValues != null) po2.xValues = xValues.clone();
if (yValues != null) po2.yValues = yValues.clone();
if (xEValues != null) po2.xEValues = xEValues.clone();
if (yEValues != null) po2.yEValues = yEValues.clone();
if (shapeData != null) po2.shapeData = cloneArrayList(shapeData);
return po2;
}
private ArrayList cloneArrayList(ArrayList src) {
ArrayList dest = (ArrayList)(src.clone()); Class[] noClasses = new Class[0];
Object[] noObjects = new Object[0];
for (int i=0; i<dest.size(); i++) {
Object o = dest.get(i);
if (o != null) try {
Method cloneMethod = o.getClass().getMethod("clone", noClasses);
dest.set(i, cloneMethod.invoke(o, noObjects));
} catch (Exception e) {}
}
return dest;
}
void updateType() {
type = 1<<type;
}
public String toString() { String s = "PlotObject type="+type+" flags="+flags+" xV:"+(xValues==null ? "-":yValues.length)+" yV:"+(yValues==null ? "-":yValues.length)+" label="+label+" col="+color+" fSize="+fontSize+" ff="+fontFamily;
return s;
}
}