package data_plot; import java.awt.*; import java.util.*; import ij.*; import ij.gui.*; import ij.process.*; import ij.util.*; import ij.plugin.filter.Analyzer; import ij.macro.Interpreter; import ij.measure.Calibration; /* DataPlot is largely based on the Plot class and extends its functionality * towards accepting ArrayList data input and allowing to display arrow * plots, logarithmic (log in x and/or y) plots, minor ticks (decimal and * logarithmic), change of the label font, draw dotted lines... * * @version 1.0; 22 June 2012 * * @author Philippe CARL * @author University of Strasbourg * @author philippe.carl@unistra.fr * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** This class is an image that line graphs can be drawn on. */ public class DataPlot { /** Text justification. */ public static final int LEFT=0, CENTER=1, RIGHT=2; /** Display points using a circle 5 pixels in diameter. */ public static final int CIRCLE = 0; /** Display points using an X-shaped mark. */ public static final int X = 1; /** Display points using an box-shaped mark. */ public static final int BOX = 3; /** Display points using an tiangular mark. */ public static final int TRIANGLE = 4; /** Display points using an cross-shaped mark. */ public static final int CROSS = 5; /** Display points using a single pixel. */ public static final int DOT = 6; /** Connect points with solid lines. */ public static final int LINE = 2; ///** flag multiplier for maximum number of ticks&grid lines along x */ //public static final int X_INTERVALS_M = 0x1; ///** flag multiplier for maximum number of ticks&grid lines along y */ //public static final int Y_INTERVALS_M = 0x100; /** flag for numeric labels of x-axis ticks */ public static final int X_NUMBERS = 0x1; /** flag for numeric labels of x-axis ticks */ public static final int Y_NUMBERS = 0x2; /** flag for drawing x-axis ticks */ public static final int X_TICKS = 0x4; /** flag for drawing x-axis ticks */ public static final int Y_TICKS = 0x8; /** flag for drawing vertical grid lines for x-axis ticks */ public static final int X_GRID = 0x10; /** flag for drawing horizontal grid lines for y-axis ticks */ public static final int Y_GRID = 0x20; /** flag for forcing frame to coincide with the grid/ticks in x direction (results in unused space) */ public static final int X_FORCE2GRID = 0x40; /** flag for forcing frame to coincide with the grid/ticks in y direction (results in unused space) */ public static final int Y_FORCE2GRID = 0x80; /** flag for drawing x-axis minor ticks */ public static final int X_MINOR_TICKS = 0x100; /** flag for drawing y-axis minor ticks */ public static final int Y_MINOR_TICKS = 0x200; /** flag for drawing x-axis logarithmic minor ticks */ public static final int X_LOG_MINOR_TICKS = 0x400; /** flag for drawing y-axis logarithmic minor ticks */ public static final int Y_LOG_MINOR_TICKS = 0x800; /** flag for numeric labels of x-axis ticks */ public static final int X_LOG_NUMBERS = 0x1000; /** flag for numeric labels of x-axis ticks */ public static final int Y_LOG_NUMBERS = 0x2000; /** the default flags */ public static final int DEFAULT_FLAGS = X_NUMBERS + Y_NUMBERS + /*X_TICKS + Y_TICKS +*/ X_GRID + Y_GRID; /** the margin width left of the plot frame (enough for 5-digit numbers such as unscaled 16-bit data*/ public static final int LEFT_MARGIN = 60; /** the margin width right of the plot frame */ public static final int RIGHT_MARGIN = 18; /** the margin width above the plot frame */ public static final int TOP_MARGIN = 15; /** the margin width below the plot frame */ public static final int BOTTOM_MARGIN = 40; private static final int WIDTH = 450; private static final int HEIGHT = 200; private static final int MAX_INTERVALS = 12; //maximum number of intervals between ticks or grid lines private static final int MIN_X_GRIDWIDTH = 60; //minimum distance between grid lines or ticks along x private static final int MIN_Y_GRIDWIDTH = 40; //minimum distance between grid lines or ticks along y private static int TICK_LENGTH = 3; //length of ticks private static int MINOR_TICK_LENGTH = 3; //length of minor ticks private final Color gridColor = new Color(0xc0c0c0); //light gray private int frameWidth; private int frameHeight; Rectangle frame = null; float[] xValues, yValues; float[] errorBars; int nPoints; double xMin, xMax, yMin, yMax; private double xScale, yScale; private static String defaultDirectory = null; private String xLabel; private String yLabel; private int flags; private Font font = new Font("Helvetica", Font.PLAIN, 12); private Font fontSmall = new Font("Helvetica", Font.PLAIN, 9 ); private Font xLabelFont = font; private Font yLabelFont = font; private boolean fixedYScale; private int lineWidth = 1; // Line.getWidth(); private int markSize = 5; private int minimalArrowSize = 5; private ImageProcessor ip; private String title; private boolean initialized; private boolean plotDrawn; private int plotWidth = DataPlotWindow.plotWidth; private int plotHeight = DataPlotWindow.plotHeight; private boolean multiplePlots; private boolean drawPending; private int sourceImageID; /** keeps a reference to all of the data that is going to be plotted. */ ArrayList storedData; /** Construct a new DataPlotWindow. * @param title the window title * @param xLabel the x-axis label * @param yLabel the y-axis label * @param xValues the x-coodinates, or null * @param yValues the y-coodinates, or null */ public DataPlot(String title, String xLabel, String yLabel, float[] xValues, float[] yValues) { this(title, xLabel, yLabel, xValues, yValues, DEFAULT_FLAGS); } /** This version of the constructor accepts double arrays. */ public DataPlot(String title, String xLabel, String yLabel, double[] xValues, double[] yValues) { this(title, xLabel, yLabel, xValues!=null?Tools.toFloat(xValues):null, yValues!=null?Tools.toFloat(yValues):null, DEFAULT_FLAGS); } /** This is a constructor that works with JavaScript. */ public DataPlot(String dummy, String title, String xLabel, String yLabel, float[] xValues, float[] yValues) { this(title, xLabel, yLabel, xValues, yValues, DEFAULT_FLAGS); } /** This is a version of the constructor with no intial arrays. */ public DataPlot(String title, String xLabel, String yLabel) { this(title, xLabel, yLabel, (float[])null, (float[])null, DEFAULT_FLAGS); } /** This is a version of the constructor with no intial arrays. */ public DataPlot(String title, String xLabel, String yLabel, int flags) { this(title, xLabel, yLabel, (float[])null, (float[])null, flags); } /** This version of the constructor has a 'flags' argument for controlling the appearance of ticks, grid, etc. The default is Plot.X_NUMBERS+Plot.Y_NUMBERS+Plot.X_GRID+Plot.Y_GRID. */ public DataPlot(String title, String xLabel, String yLabel, float[] xValues, float[] yValues, int flags) { this.title = title; this.xLabel = xLabel; this.yLabel = yLabel; this.flags = flags; storedData = new ArrayList(); if ((xValues==null || xValues.length==0) && yValues!=null) { xValues = new float[yValues.length]; for (int i=0; iOptions>Profile Plot Options. */ public void setSize(int width, int height) { if (!initialized && width>LEFT_MARGIN+RIGHT_MARGIN+20 && height>TOP_MARGIN+BOTTOM_MARGIN+20) { plotWidth = width-LEFT_MARGIN-RIGHT_MARGIN; plotHeight = height-TOP_MARGIN-BOTTOM_MARGIN; } } /** Sets the plot frame size in pixels. */ public void setFrameSize(int width, int height) { plotWidth = width; plotHeight = height; } /** Sets the length of the major tick in pixels. */ public void setTickLength(int tickLength) { TICK_LENGTH = tickLength; } /** Sets the length of the minor tick in pixels. */ public void setMinorTickLength(int minorTickLength) { MINOR_TICK_LENGTH = minorTickLength; } /** Adds a set of points to the plot or adds a curve if shape is set to LINE. * @param x the x-coodinates * @param y the y-coodinates * @param shape CIRCLE, X, BOX, TRIANGLE, CROSS, DOT or LINE */ public void addPoints(float[] x, float[] y, int shape) { setup(); switch(shape) { case CIRCLE: case X: case BOX: case TRIANGLE: case CROSS: case DOT: ip.setClipRect(frame); for (int i=0; i arrowSize) if(dist > minimalArrowSize) drawArrow(xt1, yt1, xt2, yt2, dist / minimalArrowSize); else drawArrow(xt1, yt1, xt2, yt2, minimalArrowSize); else ip.drawLine(xt1, yt1, xt2, yt2); } ip.setClipRect(null); multiplePlots = true; if (xValues==null || xValues.length==0) { xValues = x1; yValues = y1; nPoints = x1.length; drawPending = false; } storeData(x1, y1); } public double calculateDistance(int x1, int y1, int x2, int y2) { return java.lang.Math.sqrt(java.lang.Math.pow(x2 - x1, 2.0) + java.lang.Math.pow(y2 - y1, 2.0)); } public void drawArrow(int x1, int y1, int x2, int y2, double size) { double dx = x2 - x1; double dy = y2 - y1; double ra = java.lang.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); } /** Adds a set of points to the plot using double arrays. * Must be called before the plot is displayed. */ public void addPoints(double[] x1, double[] y1, double[] x2, double[] y2, int arrowSize) { addPoints(Tools.toFloat(x1), Tools.toFloat(y1), Tools.toFloat(x2), Tools.toFloat(y2), arrowSize); } /** This a version of addPoints that works with JavaScript. */ public void addPoints(String dummy, float[] x1, float[] y1, float[] x2, float[] y2, int arrowSize) { addPoints(x1, y1, x2, y2, arrowSize); } /** Adds a set of points to the plot using double ArrayLists. * Must be called before the plot is displayed. */ public void addPoints(ArrayList x1, ArrayList y1, ArrayList x2, ArrayList y2, int arrowSize) { addPoints(getDoubleFromArrayList(x1), getDoubleFromArrayList(y1), getDoubleFromArrayList(x2), getDoubleFromArrayList(y2), arrowSize); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** Adds error bars to the plot. */ public void addErrorBars(float[] errorBars) { this.errorBars = errorBars ; } /** Adds error bars to the plot. */ public void addErrorBars(double[] errorBars) { addErrorBars(Tools.toFloat(errorBars)); } /** This is a version of addErrorBars that works with JavaScript. */ public void addErrorBars(String dummy, float[] errorBars) { addErrorBars(errorBars); } /** Draws text at the specified location, where (0,0) * is the upper left corner of the the plot frame and (1,1) is * the lower right corner. */ public void addLabel(double x, double y, String label) { setup(); int xt = LEFT_MARGIN + (int)(x*frameWidth); int yt = TOP_MARGIN + (int)(y*frameHeight); ip.drawString(label, xt, yt); } /* Draws text at the specified location, using the coordinate system defined by setLimits() and the justification specified by setJustification(). */ // public void addText(String text, double x, double y) { // setup(); // int xt = LEFT_MARGIN + (int)((x-xMin)*xScale); // int yt = TOP_MARGIN + frameHeight - (int)((y-yMin)*yScale); // if (justification==CENTER) // xt -= ip.getStringWidth(text)/2; // else if (justification==RIGHT) // xt -= ip.getStringWidth(text); // ip.drawString(text, xt, yt); // } /** Sets the justification used by addLabel(), where justification * is Plot.LEFT, Plot.CENTER or Plot.RIGHT. */ public void setJustification(int justification) { setup(); ip.setJustification(justification); } /** Changes the drawing color. For selecting the color of the data passed with the constructor, * use setColor before draw. * The frame and labels are always drawn in black. */ public void setColor(Color c) { setup(); if (!(ip instanceof ColorProcessor)) { ip = ip.convertToRGB(); ip.setLineWidth(lineWidth); ip.setFont(font); ip.setAntialiasedText(true); } ip.setColor(c); } /** Changes the line width. */ public void setLineWidth(int lineWidth) { if (lineWidth<1) lineWidth = 1; setup(); ip.setLineWidth(lineWidth); this.lineWidth = lineWidth; markSize = lineWidth==1?5:7; } /* Draws a line using the coordinate system defined by setLimits(). */ public void drawLine(double x1, double y1, double x2, double y2) { setup(); int ix1 = LEFT_MARGIN + (int)Math.round((x1-xMin)*xScale); int iy1 = TOP_MARGIN + frameHeight - (int)Math.round((y1-yMin)*yScale); int ix2 = LEFT_MARGIN + (int)Math.round((x2-xMin)*xScale); int iy2 = TOP_MARGIN + frameHeight - (int)Math.round((y2-yMin)*yScale); ip.drawLine(ix1, iy1, ix2, iy2); } /* Draws a line using the coordinate system defined by setLimits(). */ public void drawDottedLine(double x1, double y1, double x2, double y2, int step) { setup(); int ix1 = LEFT_MARGIN + (int)Math.round((x1-xMin)*xScale); int iy1 = TOP_MARGIN + frameHeight - (int)Math.round((y1-yMin)*yScale); int ix2 = LEFT_MARGIN + (int)Math.round((x2-xMin)*xScale); int iy2 = TOP_MARGIN + frameHeight - (int)Math.round((y2-yMin)*yScale); for(int i = ix1; i <= ix2; i = i + step) for(int j = iy1; j <= iy2; j = j + step) ip.drawDot(i, j); } /** Sets the font. */ public void setFont(Font font) { setup(); ip.setFont(font); this.font = font; } /** Sets the xLabelFont. */ public void setXLabelFont(Font font) { xLabelFont = font; } /** Sets the yLabelFont. */ public void setYLabelFont(Font font) { yLabelFont = font; } /** Obsolete; replaced by setFont(). */ public void changeFont(Font font) { setFont(font); } void setup() { if (initialized) return; initialized = true; createImage(); ip.setColor(Color.black); if (lineWidth>3) lineWidth = 3; ip.setLineWidth(lineWidth); ip.setFont(font); ip.setAntialiasedText(true); if (frameWidth==0) { frameWidth = plotWidth; frameHeight = plotHeight; } frame = new Rectangle(LEFT_MARGIN, TOP_MARGIN, frameWidth, frameHeight); setScaleAndDrawAxisLabels(); } void setScaleAndDrawAxisLabels() { if ((xMax-xMin)==0.0) xScale = 1.0; else xScale = frame.width/(xMax-xMin); if ((yMax-yMin)==0.0) yScale = 1.0; else yScale = frame.height/(yMax-yMin); if (DataPlotWindow.noGridLines) drawAxisLabels(); else drawTicksEtc(); } void drawAxisLabels() { int digits = getDigits(yMin, yMax); String s = IJ.d2s(yMax, digits); int sw = ip.getStringWidth(s); if ((sw+4)>LEFT_MARGIN) ip.drawString(s, 4, TOP_MARGIN-4); else ip.drawString(s, LEFT_MARGIN-ip.getStringWidth(s)-4, TOP_MARGIN+10); s = IJ.d2s(yMin, digits); sw = ip.getStringWidth(s); if ((sw+4)>LEFT_MARGIN) ip.drawString(s, 4, TOP_MARGIN+frame.height); else ip.drawString(s, LEFT_MARGIN-ip.getStringWidth(s)-4, TOP_MARGIN+frame.height); FontMetrics fm = ip.getFontMetrics(); int x = LEFT_MARGIN; int y = TOP_MARGIN + frame.height + fm.getAscent() + 6; digits = getDigits(xMin, xMax); ip.drawString(IJ.d2s(xMin,digits), x, y); s = IJ.d2s(xMax,digits); ip.drawString(s, x + frame.width-ip.getStringWidth(s)+6, y); if(xLabelFont != font) { ip.setFont(xLabelFont); ip.drawString(xLabel, LEFT_MARGIN+(frame.width-ip.getStringWidth(xLabel))/2, y+3); ip.setFont(font); } else ip.drawString(xLabel, LEFT_MARGIN+(frame.width-ip.getStringWidth(xLabel))/2, y+3); if(yLabelFont != font) { ip.setFont(yLabelFont); drawYLabel(yLabel,LEFT_MARGIN-4,TOP_MARGIN,frame.height, fm); ip.setFont(font); } else drawYLabel(yLabel,LEFT_MARGIN-4,TOP_MARGIN,frame.height, fm); } void drawTicksEtc() { int fontAscent = ip.getFontMetrics().getAscent(); int fontMaxAscent = ip.getFontMetrics().getMaxAscent(); if ((flags&(X_NUMBERS + X_TICKS + X_GRID)) != 0) { double step = Math.abs(Math.max(frame.width/MAX_INTERVALS, MIN_X_GRIDWIDTH)/xScale); //the smallest allowable increment step = niceNumber(step); int i1, i2; if ((flags&X_FORCE2GRID) != 0) { i1 = (int)Math.floor(Math.min(xMin,xMax)/step+1.e-10); //this also allows for inverted xMin, xMax i2 = (int)Math.ceil(Math.max(xMin,xMax)/step-1.e-10); xMin = i1*step; xMax = i2*step; xScale = frame.width/(xMax-xMin); //rescale to make it fit } else { i1 = (int)Math.ceil(Math.min(xMin,xMax)/step-1.e-10); i2 = (int)Math.floor(Math.max(xMin,xMax)/step+1.e-10); } int digits = -(int)Math.floor(Math.log(step)/Math.log(10)+1e-6); if (digits < 0) digits = 0; if (digits>5) digits = -3; // use scientific notation int y1 = TOP_MARGIN; int y2 = TOP_MARGIN+frame.height; int yNumbers = y2 + fontAscent + 7; for (int i=0; i<=(i2-i1); i++) { double v = (i+i1)*step; int x = (int)Math.round((v - xMin)*xScale) + LEFT_MARGIN; if ((flags&X_GRID) != 0) { ip.setColor(gridColor); ip.drawLine(x, y1, x, y2); ip.setColor(Color.black); } if ((flags&X_TICKS) !=0) { ip.drawLine(x, y1, x, y1+TICK_LENGTH); ip.drawLine(x, y2, x, y2-TICK_LENGTH); } if ((flags&X_NUMBERS) != 0) { String s = IJ.d2s(v,digits); ip.drawString(s, x-ip.getStringWidth(s)/2, yNumbers); } if ((flags&X_LOG_NUMBERS) != 0) { ip.setFont(fontSmall); String s = IJ.d2s(v,digits); ip.drawString(s, x-ip.getStringWidth(s)/4+9, yNumbers-7); ip.setFont(font); String t = "10"; ip.drawString(t, x-ip.getStringWidth(t)/2, yNumbers+2); } } if ((flags&X_MINOR_TICKS) !=0) { step /= 10; i1 = (int)Math.ceil(Math.min(xMin,xMax)/step-1.e-10); i2 = (int)Math.floor(Math.max(xMin,xMax)/step+1.e-10); for (int i=0; i<=(i2-i1); i++) { double v = (i+i1)*step; int x = (int)Math.round((v - xMin)*xScale) + LEFT_MARGIN; ip.drawLine(x, y1, x, y1+MINOR_TICK_LENGTH); ip.drawLine(x, y2, x, y2-MINOR_TICK_LENGTH); } } if ((flags&X_LOG_MINOR_TICKS) !=0) { int i10 = (int)Math.ceil(Math.min(xMin,xMax)/step-1.e-10); step /= 10; i1 = (int)Math.ceil(Math.min(xMin,xMax)/step-1.e-10); i2 = (int)Math.floor(Math.max(xMin,xMax)/step+1.e-10); for (int i=0; i<=(i2-i1); i++) { int diff = i1+(1-i10)*10; if((i+diff)%10>1) { double v = (i-(i+diff)%10+10*Math.log((i+diff)%10)/Math.log(10)+i1)*step; int x = (int)Math.round((v - xMin)*xScale) + LEFT_MARGIN; ip.drawLine(x, y1, x, y1+MINOR_TICK_LENGTH); ip.drawLine(x, y2, x, y2-MINOR_TICK_LENGTH); } } } } int maxNumWidth = 0; if ((flags&(Y_NUMBERS + Y_TICKS + Y_GRID)) != 0) { double step = Math.abs(Math.max(frame.width/MAX_INTERVALS, MIN_Y_GRIDWIDTH)/yScale); //the smallest allowable increment step = niceNumber(step); int i1, i2; if ((flags&X_FORCE2GRID) != 0) { i1 = (int)Math.floor(Math.min(yMin,yMax)/step+1.e-10); //this also allows for inverted xMin, xMax i2 = (int)Math.ceil(Math.max(yMin,yMax)/step-1.e-10); yMin = i1*step; yMax = i2*step; yScale = frame.height/(yMax-yMin); //rescale to make it fit } else { i1 = (int)Math.ceil(Math.min(yMin,yMax)/step-1.e-10); i2 = (int)Math.floor(Math.max(yMin,yMax)/step+1.e-10); } int digits = -(int)Math.floor(Math.log(step)/Math.log(10)+1e-6); if (digits < 0) digits = 0; if (digits > 5) digits = -3; // use scientific notation int x1 = LEFT_MARGIN; int x2 = LEFT_MARGIN+frame.width; for (int i=0; i<=(i2-i1); i++) { double v = (i+i1)*step; int y = TOP_MARGIN + frame.height - (int)Math.round((v - yMin)*yScale); if ((flags&Y_GRID) != 0) { ip.setColor(gridColor); ip.drawLine(x1, y, x2, y); ip.setColor(Color.black); } if ((flags&Y_TICKS) !=0) { ip.drawLine(x1, y, x1+TICK_LENGTH, y); ip.drawLine(x2, y, x2-TICK_LENGTH, y); } if ((flags&Y_NUMBERS) != 0) { String s = IJ.d2s(v,digits); int w = ip.getStringWidth(s); if (w>maxNumWidth) maxNumWidth = w; ip.drawString(s, LEFT_MARGIN-w-4, y+fontMaxAscent/2+1); } if ((flags&Y_LOG_NUMBERS) != 0) { ip.setFont(fontSmall); String s = IJ.d2s(v,digits); int ws = ip.getStringWidth(s); if (ws>maxNumWidth) maxNumWidth = ws; ip.drawString(s, LEFT_MARGIN-ws-1, y+fontMaxAscent/2-8); ip.setFont(font); String t = "10"; int w = ip.getStringWidth(t); if (w>maxNumWidth) maxNumWidth = w; ip.drawString(t, LEFT_MARGIN-w-11, y+fontMaxAscent/2+2); } } if ((flags&Y_MINOR_TICKS) !=0) { step /= 10; i1 = (int)Math.ceil(Math.min(yMin,yMax)/step-1.e-10); i2 = (int)Math.floor(Math.max(yMin,yMax)/step+1.e-10); for (int i=0; i<=(i2-i1); i++) { double v = (i+i1)*step; int y = TOP_MARGIN + frame.height - (int)Math.round((v - yMin)*yScale); ip.drawLine(x1, y, x1+MINOR_TICK_LENGTH, y); ip.drawLine(x2, y, x2-MINOR_TICK_LENGTH, y); } } if ((flags&Y_LOG_MINOR_TICKS) !=0) { int i10 = (int)Math.ceil(Math.min(yMin,yMax)/step-1.e-10); step /= 10; i1 = (int)Math.ceil(Math.min(yMin,yMax)/step-1.e-10); i2 = (int)Math.floor(Math.max(yMin,yMax)/step+1.e-10); for (int i=0; i<=(i2-i1); i++) { int diff = i1+(1-i10)*10; if(i%10>1) { double v = (i-(i+diff)%10+10*Math.log((i+diff)%10)/Math.log(10)+i1)*step; // double v = (i-i%10+10*Math.log(i%10)/Math.log(10)+i1)*step; int y = TOP_MARGIN + frame.height - (int)Math.round((v - yMin)*yScale); ip.drawLine(x1, y, x1+MINOR_TICK_LENGTH, y); ip.drawLine(x2, y, x2-MINOR_TICK_LENGTH, y); } } } } if ((flags&(Y_NUMBERS+Y_LOG_NUMBERS))==0) { //simply note y-axis min&max int digits = getDigits(yMin, yMax); String s = IJ.d2s(yMax, digits); int sw = ip.getStringWidth(s); if ((sw+4)>LEFT_MARGIN) ip.drawString(s, 4, TOP_MARGIN-4); else ip.drawString(s, LEFT_MARGIN-ip.getStringWidth(s)-4, TOP_MARGIN+10); s = IJ.d2s(yMin, digits); sw = ip.getStringWidth(s); if ((sw+4)>LEFT_MARGIN) ip.drawString(s, 4, TOP_MARGIN+frame.height); else ip.drawString(s, LEFT_MARGIN-ip.getStringWidth(s)-4, TOP_MARGIN+frame.height); } FontMetrics fm = ip.getFontMetrics(); int x = LEFT_MARGIN; int y = TOP_MARGIN + frame.height + fm.getAscent() + 6; if ((flags&X_NUMBERS)==0 && (flags&X_LOG_NUMBERS)==0) { //simply note x-axis min&max int digits = getDigits(xMin, xMax); ip.drawString(IJ.d2s(xMin,digits), x, y); String s = IJ.d2s(xMax,digits); ip.drawString(s, x + frame.width-ip.getStringWidth(s)+6, y); } else y += fm.getAscent(); //space needed for x numbers if(xLabelFont != font) { ip.setFont(xLabelFont); ip.drawString(xLabel, LEFT_MARGIN+(frame.width-ip.getStringWidth(xLabel))/2, y+6); ip.setFont(font); } else ip.drawString(xLabel, LEFT_MARGIN+(frame.width-ip.getStringWidth(xLabel))/2, y+6); if(xLabelFont != font) { ip.setFont(xLabelFont); if ((flags&Y_LOG_NUMBERS) != 0) drawYLabel(yLabel,LEFT_MARGIN-maxNumWidth-20,TOP_MARGIN,frame.height, fm); else drawYLabel(yLabel,LEFT_MARGIN-maxNumWidth-4,TOP_MARGIN,frame.height, fm); ip.setFont(font); } else { if ((flags&Y_LOG_NUMBERS) != 0) drawYLabel(yLabel,LEFT_MARGIN-maxNumWidth-20,TOP_MARGIN,frame.height, fm); else drawYLabel(yLabel,LEFT_MARGIN-maxNumWidth-4,TOP_MARGIN,frame.height, fm); } } double niceNumber(double v) { //the smallest "nice" number >= v. "Nice" numbers are .. 0.5, 1, 2, 5, 10, 20 ... double base = Math.pow(10,Math.floor(Math.log(v)/Math.log(10)-1.e-6)); if (v > 5.0000001*base) return 10*base; else if (v > 2.0000001*base) return 5*base; else return 2*base; } void createImage() { if (ip!=null) return; int width = plotWidth+LEFT_MARGIN+RIGHT_MARGIN; int height = plotHeight+TOP_MARGIN+BOTTOM_MARGIN; byte[] pixels = new byte[width*height]; for (int i=0; i0.0?n1:n2; double diff = Math.abs(n2-n1); if (diff>0.0 && diff5) ip.setLineWidth(5); ip.drawRect(frame.x, frame.y, frame.width+1, frame.height+1); ip.setLineWidth(lineWidth); } void drawPolyline(ImageProcessor ip, int[] x, int[] y, int n, boolean clip) { if (clip) ip.setClipRect(frame); ip.moveTo(x[0], y[0]); for (int i=0; i