props ) {
float[] point = new float[6];
float fx0 = Float.NaN, fy0 = Float.NaN;
double slen = 0;
int pathlenIndex = 0;
int type;
if (pathlen == null) {
pathlen = new double[0];
}
if ( !it.isDone() ) {
if ( props!=null ) props.put( "PROP_FIRST_POINT", Arrays.copyOf( point, point.length ) );
}
while (!it.isDone()) {
type = it.currentSegment(point);
it.next();
if (!Float.isNaN(fx0) && type == PathIterator.SEG_MOVETO && stopAtMoveTo) {
break;
}
switch (type) {
case PathIterator.SEG_CUBICTO:
throw new IllegalArgumentException("cubicto not supported");
case PathIterator.SEG_QUADTO:
throw new IllegalArgumentException("quadto not supported");
case PathIterator.SEG_LINETO:
break;
default:
break;
}
if (Float.isNaN(fx0)) {
fx0 = point[0];
fy0 = point[1];
continue;
}
double thislen = (float) Point.distance(fx0, fy0, point[0], point[1]);
if (thislen == 0) {
continue;
} else {
slen += thislen;
}
while (pathlenIndex < pathlen.length && slen >= pathlen[pathlenIndex]) {
double alpha = 1 - (slen - pathlen[pathlenIndex]) / thislen;
double dx = point[0] - fx0;
double dy = point[1] - fy0;
if (result != null) {
result[pathlenIndex] = new Point2D.Double(fx0 + dx * alpha, fy0 + dy * alpha);
}
if (orientation != null) {
orientation[pathlenIndex] = Math.atan2(dy, dx);
}
pathlenIndex++;
}
fx0 = point[0];
fy0 = point[1];
}
if ( props!=null ) props.put( "PROP_LAST_POINT", Arrays.copyOf( point, point.length ) );
double remaining;
if (pathlenIndex > 0) {
remaining = slen - pathlen[pathlenIndex - 1];
} else {
remaining = slen;
}
if (result != null) {
for (; pathlenIndex < result.length; pathlenIndex++) {
result[pathlenIndex] = null;
}
}
return remaining;
}
/**
* Parses a semicolon-delimited list of name/value pairs into a map.
*
* Each entry normally has the form {@code name=value}. Entries without an equals sign are also permitted and are stored with an
* empty string as their value. For example:
*
*
*
* color=red;bold;width=2
*
*
* produces entries equivalent to:
*
*
* color -> Color.RED
* bold -> ""
* width -> Double
*
*
*
* Values may be enclosed in double quotes. Semicolons appearing inside quoted values are treated as part of the value rather
* than as entry delimiters. The surrounding quotes are removed from the resulting value. For example:
*
*
*
* color=red;label="Hello; world";width=2
*
*
*
* Leading and trailing whitespace around names, values, and entries is ignored. The returned map preserves the order in which
* entries appear in the input string.
*
*
* Suggested names and types for parsing are below. These types should always be used, often with scheme
*
* - Color color -- color to draw with
*
- Color foreground -- color to draw with
*
- Color background -- background color
*
- Color fillColor -- color to fill space with
*
- String fillTexture -- hash,crosshash,backhash,solid,name
*
- String lineStyle -- Dashes,DashFine,DotDashes,DotFine,Dots,None,Solid
*
- String lineThick -- in pixels, ems, or percent
*
- String width -- width, in pixels, or ems or percent
*
- String height -- height, in pixels, or ems or percent
*
* @see Renderer#parseControl(java.lang.String) which should eventually use this.
* @see #parseLayoutLength(java.lang.String, double, double) for dimension parsing
* @param s the string containing the semicolon-delimited entries
* @return a map containing the parsed names and values
* @throws IllegalArgumentException if a quoted value is not terminated
*/
public static Map parseControlString(String s) {
Map result = new LinkedHashMap<>();
int start = 0;
boolean quoted = false;
for (int i = 0; i <= s.length(); i++) {
char c = (i < s.length()) ? s.charAt(i) : ';';
if (c == '"') {
quoted = !quoted;
}
if (c == ';' && !quoted) {
String item = s.substring(start, i).trim();
if (item.length() > 0) {
int eq = item.indexOf('=');
String name, value;
Object ovalue;
if (eq < 0) {
name = item.trim();
value = "";
} else {
name = item.substring(0, eq).trim();
value = item.substring(eq + 1).trim();
}
// Remove surrounding quotes.
if (value.length() >= 2
&& value.charAt(0) == '"'
&& value.charAt(value.length() - 1) == '"') {
value = value.substring(1, value.length() - 1);
}
result.put(name, value);
}
start = i + 1;
}
}
if (quoted) {
throw new IllegalArgumentException("Unterminated quoted string");
}
return result;
}
/**
* parse strings like "14em+2pt" into a length in pixels.
*
* - "1em",0,8 -> 8
*
- "50%",240,0 -> 120
*
- "4pt",240,8 -> 4
*
- "4px",240,8 -> 4
*
- "1em+4pt",240,8 -> 12
*
* @param s the string specifying ems and pxs
* @param totalWidth the total with for the normalized length.
* @param em the size of an em in pixels.
* @return the length in pixels
* @see DasDevicePosition#parseLayoutStr(java.lang.String)
*/
public static double parseLayoutLength( String s, double totalWidth, double em ) {
try {
double[] dd= DasDevicePosition.parseLayoutStr((String)s);
if ( dd[0]==0 && dd[1]==1 && dd[2]==0 ) {
return em;
} else {
double parentSize= em;
double newSize= dd[0]*totalWidth + dd[1]*parentSize + dd[2];
return newSize;
}
} catch (ParseException ex) {
try {
double d= Double.parseDouble(s);
return d;
} catch ( NumberFormatException ex2 ) {
logger.log( Level.WARNING, null, ex.getMessage() );
return 0.f;
}
}
}
/**
* return a string representation of the affine transforms used in DasPlot for
* debugging.
* @param at the affine transform
* @return a string representation of the affine transforms used in DasPlot for
* debugging.
*/
public static String getATScaleTranslateString(AffineTransform at) {
String atDesc;
NumberFormat nf = new DecimalFormat("0.00");
if (at == null) {
return "null";
} else if (!at.isIdentity()) {
atDesc = "scaleX:" + nf.format(at.getScaleX()) + " translateX:" + nf.format(at.getTranslateX());
atDesc += "!c" + "scaleY:" + nf.format(at.getScaleY()) + " translateY:" + nf.format(at.getTranslateY());
return atDesc;
} else {
return "identity";
}
}
/**
* calculates the slope and intercept of a line going through two points.
* @param x0 the first point x
* @param y0 the first point y
* @param x1 the second point x
* @param y1 the second point y
* @return a double array with two elements [ slope, intercept ].
*/
public static double[] getSlopeIntercept(double x0, double y0, double x1, double y1) {
double slope = (y1 - y0) / (x1 - x0);
double intercept = y0 - slope * x0;
return new double[]{slope, intercept};
}
/**
* return translucent white color for indicating the application is busy.
* @return translucent white color
*/
public static Color getRicePaperColor() {
return ColorUtil.getRicePaperColor();
}
/**
* return a Gaussian filter for blurring images.
* @param radius the radius filter in pixels.
* @param horizontal true if horizontal blur.
* @return the ConvolveOp
*/
public static ConvolveOp getGaussianBlurFilter(int radius,
boolean horizontal) {
if (radius < 1) {
throw new IllegalArgumentException("Radius must be >= 1");
}
int size = radius * 2 + 1;
float[] data = new float[size];
float sigma = radius / 3.0f;
float twoSigmaSquare = 2.0f * sigma * sigma;
float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI);
float total = 0.0f;
for (int i = -radius; i <= radius; i++) {
float distance = i * i;
int index = i + radius;
data[index] = (float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot;
total += data[index];
}
for (int i = 0; i < data.length; i++) {
data[i] /= total;
}
Kernel kernel;
if (horizontal) {
kernel = new Kernel(size, 1, data);
} else {
kernel = new Kernel(1, size, data);
}
return new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
}
/**
* blur the image with a Guassian blur.
* @param im
* @param size the size of the blur, roughly in pixels.
* @return image
*/
public static BufferedImage blurImage( BufferedImage im, int size ) {
ConvolveOp op= getGaussianBlurFilter( size, true );
BufferedImage out= new BufferedImage( im.getWidth(), im.getHeight(), im.getType() );
op.filter( im, out );
op= getGaussianBlurFilter( size, false );
im= out;
out= new BufferedImage( im.getWidth(), im.getHeight(), im.getType() );
return op.filter( im, out );
}
/**
* describe the path for debugging.
* @param path the Path to describe
* @param enumeratePoints if true, print all the points as well.
* @return String description.
*/
public static String describe(GeneralPath path, boolean enumeratePoints) {
PathIterator it = path.getPathIterator(null);
int count = 0;
int lineToCount = 0;
double[] seg = new double[6];
while (!it.isDone()) {
int type = it.currentSegment(seg);
if (type == PathIterator.SEG_LINETO) {
lineToCount++;
}
if (enumeratePoints) {
if ( type==PathIterator.SEG_MOVETO ) {
System.err.println( String.format( Locale.US, "moveTo( %9.2f, %9.2f )\n", seg[0], seg[1] ) );
} else if ( type==PathIterator.SEG_LINETO ) {
System.err.println( String.format( Locale.US, "lineTo( %9.2f, %9.2f )\n", seg[0], seg[1] ) );
} else {
System.err.println( String.format( Locale.US, "%4d( %9.2f, %9.2f )\n", type, seg[0], seg[1] ) );
}
}
count++;
it.next();
}
System.err.println("count: " + count + " lineToCount: " + lineToCount);
return "count: " + count + " lineToCount: " + lineToCount;
}
static String toString(Line2D line) {
return ""+line.getX1()+","+line.getY1()+" "+line.getX2()+","+line.getY2();
}
//TODO: sun.awt.geom.Curve and sun.awt.geom.Crossings are GPL open-source, so
// these methods will provide reliable methods for getting rectangle, line
// intersections.
/**
* returns the point where the two line segments intersect, or null.
* @param line1
* @param line2
* @param noBoundsCheck if true, then do not check the segment bounds.
* @return
*/
public static Point2D lineIntersection(Line2D line1, Line2D line2, boolean noBoundsCheck) {
Point2D result;
double a1, b1, c1, a2, b2, c2, denom;
a1 = line1.getY2() - line1.getY1();
b1 = line1.getX1() - line1.getX2();
c1 = line1.getX2() * line1.getY1() - line1.getX1() * line1.getY2();
a2 = line2.getY2() - line2.getY1();
b2 = line2.getX1() - line2.getX2();
c2 = line2.getX2() * line2.getY1() - line2.getX1() * line2.getY2();
denom = a1 * b2 - a2 * b1;
if (denom != 0) {
result = new Point2D.Double((b1 * c2 - b2 * c1) / denom, (a2 * c1 -
a1 * c2) / denom);
if (noBoundsCheck ) {
return result;
} else {
// calculate small number which can be treated as zero.
double epsilon= -1 * Math.min( ( line1.getP1().distance(line1.getP2()) ), line2.getP1().distance(line2.getP2() ) ) / 10000.;
if (((result.getX() - line1.getX1()) * (line1.getX2() - result.getX()) >= epsilon )
&& ((result.getY() - line1.getY1()) * (line1.getY2() - result.getY()) >= epsilon )
&& ((result.getX() - line2.getX1()) * (line2.getX2() - result.getX()) >= epsilon )
&& ((result.getY() - line2.getY1()) * (line2.getY2() - result.getY()) >= epsilon ) ) {
return result;
} else {
return null;
}
}
} else {
return null;
}
}
/**
* return the line segment which is within the rectangle mask.
* @param p0 the first point
* @param p1 the second point
* @param r the rectangle
* @return null when they do not intersect, or the segment
*/
public static Line2D lineRectangleMask( Point2D p0, Point2D p1, Rectangle2D r ) {
Line2D.Double line= new Line2D.Double( p0, p1 );
Point2D.Double r0= new Point2D.Double( r.getX(), r.getY() );
Point2D.Double r1= new Point2D.Double( r.getX()+r.getWidth(), r.getY()+r.getHeight() );
Point2D point1=null;
Point2D point2=null;
Point2D p;
p= lineIntersection( line, new Line2D.Double( r0.x, r0.y, r1.x, r0.y ), false );
if ( p!=null ) point1= p;
p= lineIntersection( line, new Line2D.Double( r1.x, r0.y, r1.x, r1.y ), false );
if ( p!=null ) if ( point1==null ) point1= p; else point2= p;
p= lineIntersection( line, new Line2D.Double( r1.x, r1.y, r0.x, r1.y ), false );
if ( p!=null ) if ( point1==null ) point1= p; else point2= p;
p= lineIntersection( line, new Line2D.Double( r0.x, r1.y, r0.x, r0.y ), false );
if ( p!=null ) if ( point1==null ) point1= p; else point2= p;
if ( point1==null ) {
return null;
} else if ( point2==null ) {
if ( r.contains( p1 ) ) {
return new Line2D.Double( point1, p1 );
} else {
return new Line2D.Double( p0, point1 );
}
} else if ( Point2D.distance( p0.getX(), p0.getY(), point1.getX(), point1.getY() ) < Point2D.distance( p0.getX(), p0.getY(), point2.getX(), point2.getY() ) ) {
return new Line2D.Double( point1, point2 );
} else {
return new Line2D.Double( point2, point1 );
}
}
/**
* return the intersection of a line segment and the edge of a rectangle,
* where one point is outside of the rectangle and one is inside.
* @param p0
* @param p1
* @param r0
* @return null or the point along the rectangle
*/
public static Point2D lineRectangleIntersection( Point2D p0, Point2D p1, Rectangle2D r0) {
PathIterator it = r0.getPathIterator(null);
Line2D line = new Line2D.Double( p0, p1 );
float[] c0 = new float[6];
float[] c1 = new float[6];
it.currentSegment(c0);
it.next();
while ( !it.isDone() ) {
int type= it.currentSegment(c1);
if ( type==PathIterator.SEG_LINETO ) {
Line2D seg = new Line2D.Double(c0[0], c0[1], c1[0], c1[1]);
Point2D result = lineIntersection(line, seg, false);
if (result != null) {
return result;
}
}
it.next();
c0[0]= c1[0];
c0[1]= c1[1];
}
return null;
}
/**
* returns pixel range of the datum range, guarenteeing that the first
* element will be less than or equal to the second.
* @param axis
* @param range
* @return
*/
public static double[] transformRange( DasAxis axis, DatumRange range ) {
double x1= axis.transform(range.min());
double x2= axis.transform(range.max());
if ( x1>x2 ) {
double t= x2;
x2= x1;
x1= t;
}
return new double[] { x1, x2 };
}
public static DatumRange invTransformRange( DasAxis axis, double x1, double x2 ) {
Datum d1= axis.invTransform(x1);
Datum d2= axis.invTransform(x2);
if ( d1.gt(d2) ) {
Datum t= d2;
d2= d1;
d1= t;
}
return new DatumRange( d1, d2 );
}
/**
* return an icon block with the color and size.
* @param iconColor the color
* @param w the width in pixels
* @param h the height in pixels
* @return an icon.
*/
public static Icon colorIcon( Color iconColor, int w, int h ) {
return colorImageIcon(iconColor, w, h);
}
/**
* return an ImageIcon with the color and size.
* @param iconColor
* @param w
* @param h
* @return
*/
public static ImageIcon colorImageIcon( Color iconColor, int w, int h ) {
BufferedImage image= new BufferedImage( w, h, BufferedImage.TYPE_INT_ARGB );
Graphics g= image.getGraphics();
if ( iconColor.getAlpha()!=255 ) { // draw checkerboard to indicate transparency
for ( int j=0; j<16/4; j++ ) {
for ( int i=0; i<16/4; i++ ) {
g.setColor( (i-j)%2 ==0 ? Color.GRAY : Color.WHITE );
g.fillRect( 0+i*4,0+j*4,4,4);
}
}
}
g.setColor(iconColor);
g.fillRect( 0, 0, w, h );
return new ImageIcon(image);
}
/**
* return rectangle with same center that is percent/100 of the
* original width and height.
* @param bounds the original rectangle.
* @param percent the percent to increase (110% is 10% bigger)
* @return a rectangle with same center that is percent/100. of the
* original width and height.
*/
public static Rectangle shrinkRectangle(Rectangle bounds, int percent ) {
Rectangle result= new Rectangle(
bounds.x + (int)(bounds.width*(100.-percent)/2/100),
bounds.y + (int)(bounds.height*(100.-percent)/2/100),
(int)( bounds.width * ( percent / 100. ) ),
(int)( bounds.height * ( percent / 100. ) ) );
return result;
}
/**
* return line shorted by so many pixels at each end.
* @param line the line
* @param l1 number of units to adjust the first point, towards the center
* @param l2 number of units to adjust the second point, towards the center
* @return the new line
*/
public static Line2D shortenLine( Line2D line, double l1, double l2 ) {
double len= line.getP1().distance( line.getP2() );
if ( len==0 ) return line;
double sx= ( line.getX2() - line.getX1() ) / len;
double sy= ( line.getY2() - line.getY1() ) / len;
return new Line2D.Double( line.getX1()+sx*l1, line.getY1()+sy*l1, line.getX2()-sx*l2, line.getY2()-sy*l2 );
}
/**
* create a line perpendicular to the line segment line, which
* would go through p, and have length abs(len).
* If len is negative, then line.p1,line.p2,p is counter-clockwise.
* This is left unimplemented as it's a nice student project.
* @param line a line segment.
* @param p a point, whose projection is necessarily within the line segment.
* @param len the length of the resulting line, or
* @return line colinear with p and having length abs(len).
*/
public static Line2D perpendicularLine( Line2D line, Point p, double len ) {
throw new IllegalArgumentException("not implemented.");
}
/**
* DebuggingGeneralPath can be used for debugging.
*/
public static class DebuggingGeneralPath {
GeneralPath delegate;
int count= 0;
double lastfx0=0;
double lastfy0=0;
double initx=0;
double inity=0;
boolean arrows=false;
boolean printRoute= true;
DebuggingGeneralPath( int rule, int capacity ) {
delegate= new GeneralPath( rule, capacity );
System.err.println(String.format("==newPath=="));
count= 0;
}
DebuggingGeneralPath( ) {
delegate= new GeneralPath(GeneralPath.WIND_NON_ZERO, 20 );
System.err.println(String.format("==newPath=="));
count= 0;
}
public void setArrows( boolean drawArrows ) {
this.arrows= drawArrows;
}
public void lineTo(double fx, double fy) {
if ( printRoute ) {
System.err.println(new Formatter().format( Locale.US, "lineTo(%5.1f,%5.1f) %d",fx,fy,count ).toString());
}
if ( arrows ) {
if ( inity==lastfy0 && initx==lastfx0 ) {
double perpy= fx-lastfx0;
double perpx= -1 * ( fy-lastfy0 );
double n= Math.sqrt( perpx*perpx + perpy*perpy );
perpx= perpx/n;
perpy= perpy/n;
int len=4;
delegate.lineTo( lastfx0 - perpx*len, lastfy0 - perpy*len );
delegate.lineTo( lastfx0 + perpx*len, lastfy0 + perpy*len );
delegate.moveTo( lastfx0, lastfy0 );
}
}
delegate.lineTo(fx, fy);
if ( arrows ) {
double perpy= fx-lastfx0;
double perpx= -1 * ( fy-lastfy0 );
double n= Math.sqrt( perpx*perpx + perpy*perpy );
perpx= perpx/n;
perpy= perpy/n;
int len=4;
delegate.lineTo( fx+perpx*len - perpy*len, fy+perpy*len + perpx*len );
delegate.lineTo( fx , fy );
}
lastfx0= fx;
lastfy0= fy;
count++;
}
public void moveTo(double fx, double fy) {
if ( printRoute ) {
System.err.println(new Formatter().format( Locale.US, "moveTo(%5.1f,%5.1f) %d",fx,fy,count ).toString());
}
//if ( count==3 ) {
// System.err.println("here1112");
//}
delegate.moveTo(fx,fy);
lastfx0= fx;
lastfy0= fy;
if ( count==0 ) {
initx= fx;
inity= fy;
}
count++;
}
PathIterator getPathIterator(AffineTransform at) {
return delegate.getPathIterator(at);
}
GeneralPath getGeneralPath() {
return delegate;
}
}
/**
* converts forward from relative font spec to point size, used by
* the annotation and axis nodes.
* @param dcc the canvas component.
* @param fallbackFont the font to use when a font is not available, like "sans-8"
* @return the converter that converts between strings like "1em" and the font.
*/
public static Converter getFontConverter( final DasCanvasComponent dcc, final String fallbackFont ) {
return new Converter() {
@Override
public Object convertForward(Object s) {
try {
double[] dd= DasDevicePosition.parseLayoutStr((String)s);
Font f= dcc.getFont();
if ( f==null ) {
f= Font.decode( fallbackFont );
}
if ( dd[1]==1 && dd[2]==0 ) {
return f.getSize2D();
} else {
double parentSize= f.getSize2D();
double newSize= dd[1]*parentSize + dd[2];
return (float)newSize;
}
} catch (ParseException ex) {
ex.printStackTrace();
return 0.f;
}
}
@Override
public Object convertReverse(Object t) {
float size= (float)t;
Font f= dcc.getFont();
if ( f==null ) {
f= Font.decode( fallbackFont );
}
if ( size==0 ) {
return "1em";
} else {
double parentSize= f.getSize2D();
double relativeSize= size / parentSize;
return String.format( Locale.US, "%.2fem", relativeSize );
}
}
};
}
/**
* return the number of minor ticks for the spacing. This should be
* return by searching for the first two factors.
* @param dt the step size
* @return the number of minor ticks
*/
private static int updateTickVManualTicksMinor( double dt ) {
int scale= (int)Math.log10(dt);
dt= dt/Math.pow(10,scale);
if ( dt==1. ) return 4;
if ( dt==2. ) return 2;
if ( dt==4. ) return 2;
if ( dt==5. ) return 5;
if ( dt==3. ) return 3;
if ( dt==9. ) return 3;
if ( dt==1.5 ) return 3;
return 1;
}
/**
* limit the number of ticks which are computed
*/
public static final int MAX_TICKS = 480;
/**
* calculate a TickVDescriptor for the ticks.
* Example specifications:
* - +20 - every 20 units, whatever the data units are.
*
- +20s - every 20 seconds
*
- 0,20,40,60,100 - explicit locations.
*
- +20s/4 - every 20 seconds, with four minor divisions.
*
- +20s/5,10,15 - minor ticks repeat each 20s.
*
- 100,200,300/50,150,250,350 - explicit list of major and minor ticks.
*
- *10/+1 - log ticks with linear minor ticks.
*
- none - no ticks
*
*
* @see https://github.com/autoplot/dev/blob/master/demos/2021/20211130/demoCalculateManualTicks.jy
* @param lticks the specification
* @param dr the range to cover
* @param log
* @return null if the string can't be parsed, or the TickVDescriptor
* @see #MAX_TICKS the maximum number of minor or major ticks calculated
*/
public static TickVDescriptor calculateManualTicks( String lticks, DatumRange dr, boolean log ) {
TickVDescriptor result;
Units u= dr.getUnits();
int islash= lticks.indexOf('/');
int minorMult= 0;
double[] minorList= null;
double[] minorListAbs= null;
String minorTicksSpec=null;
if ( islash>-1 ) {
minorTicksSpec = lticks.substring(islash+1);
lticks= lticks.substring(0,islash);
if ( minorTicksSpec.startsWith("+") ) {
TickVDescriptor minorT= calculateManualTicks( minorTicksSpec, dr, log );
if ( minorT!=null ) minorListAbs= minorT.tickV.toDoubleArray(u);
} else if ( minorTicksSpec.startsWith("*") ) {
TickVDescriptor minorT= calculateManualTicks( minorTicksSpec, dr, log );
if ( minorT!=null ) minorListAbs= minorT.tickV.toDoubleArray(u);
} else {
if ( minorTicksSpec.contains(",") ) {
String[] ss= minorTicksSpec.split(",");
minorList= new double[ss.length];
for ( int i=0; i0 ? minorMult : updateTickVManualTicksMinor(dt);
dt= dt/minorTicks;
dticksMinor= new double[ ntick*minorTicks ];
for ( int i=0; i dticksMinorList= new ArrayList<>();
if ( minorTicksSpec!=null && minorTicksSpec.startsWith("+") ) {
TickVDescriptor minorTicksOneCycle= calculateManualTicks( minorTicksSpec,
DatumRange.newDatumRange( 1, tickM.value(), Units.dimensionless ), false );
minorList= minorTicksOneCycle.getMajorTicks().toDoubleArray( Units.dimensionless );
} else {
//double[] dticksMinor= new double[ ntick*minorTicks ];
if ( minorList==null ) {
switch (minorMult) {
case 2:
minorList= new double[] { 10 };
break;
case 3:
minorList= new double[] { 10, 100 };
break;
default:
minorList= new double[] { 2,3,4,5,6,7,8,9 };
break;
}
}
}
for ( int i=0; i2 ) {
double dt= DasMath.gcd( dticks, (dticks[1]-dticks[0])/100. );
int minorTicks= minorMult>0 ? minorMult : updateTickVManualTicksMinor(dt);
dt= dt/minorTicks;
double firstTick= DasMath.min(dticks);
double lastTick= DasMath.max(dticks);
int ntick= (int)(Math.ceil(lastTick-firstTick)/dt) + 1;
dticksMinor= new double[ ntick ];
for ( int i=0; i