// Charles A. Loomis, Jr., and University of California, Santa Cruz,
// Copyright (c) 2000
package org.freehep.swing.graphics;
import java.awt.geom.GeneralPath;
/**
* This class contains static methods which are useful for the
* ScaleBorder class and any potential subclasses.
*
* @author Charles Loomis
* @version $Id: Scale.java 8584 2006-08-10 23:06:37Z duns $ */
public class Scale {
private static int[] p = {2,2,5,5,5,10,10,10,20,20,50,50};
private static int[] ps = {2,10,5,10,20,10,20,50,20,100,50,100};
final static public int LEFT_TICKS = 0;
final static public int RIGHT_TICKS = 1;
final static public int BOTH_TICKS = 2;
/**
* The size in pixels of the primary tick marks. This is a global
* property for all scales. */
protected static float primaryTickSize = 8.f;
/**
* The size in pixels of the secondary tick marks. This is a
* global property for all scales. */
protected static float secondaryTickSize = 5.f;
/**
* Set the tick sizes (in pixels) for the primary and secondary
* tick marks. */
static public void setTickSizes(float primaryTickSize,
float secondaryTickSize) {
Scale.primaryTickSize = primaryTickSize;
Scale.secondaryTickSize = secondaryTickSize;
}
/**
* Get the primary tick size (in pixels). */
static public float getPrimaryTickSize() {
return Scale.primaryTickSize;
}
/**
* Get the secondary tick size (in pixels). */
static public float getSecondaryTickSize() {
return Scale.secondaryTickSize;
}
/**
* A utility method to add a tick mark to the given GeneralPath at
* the given position. */
static private void addTickMark(GeneralPath gp,
int location,
float tickPosition,
float tickLength) {
switch (location) {
case (Scale.RIGHT_TICKS):
gp.moveTo(tickPosition,0.f);
gp.lineTo(tickPosition,tickLength);
break;
case (Scale.LEFT_TICKS):
gp.moveTo(tickPosition,0.f);
gp.lineTo(tickPosition,-tickLength);
break;
case (Scale.BOTH_TICKS):
gp.moveTo(tickPosition,-tickLength);
gp.lineTo(tickPosition,tickLength);
break;
}
}
static public void drawLinearScale(double value0,
double value1,
int scaleSize,
int minPrimary,
int minSeparation,
int location,
GeneralPath primaryTicks,
GeneralPath secondaryTicks,
String[] label,
double[] position) {
// First calculate the range of the scale.
double range = Math.abs(value1-value0);
// Calculate the smallest order of magnitude which will NOT
// fit in the range.
double m = Math.pow(10.,Math.ceil(Math.log(range)/Math.log(10.)));
// Get the size of the window.
double w = scaleSize;
// Calculate the minimum value of np (number of primary tick
// marks in m).
double minNP = m*minPrimary/range;
// Calculate the maximum value of np*ns.
double maxNPNS = w*m/(range*minSeparation);
// Find the optimal combination of primary and secondary tick
// marks.
int optimal = -1;
int oldPS = -1;
int oldP = Integer.MAX_VALUE;
for (int i=0; i<p.length; i++) {
if (p[i]>minNP && ps[i]<maxNPNS) {
if (p[i]<=oldP && ps[i]>oldPS) {
optimal = i;
oldP = p[i];
oldPS = ps[i];
}
}
}
// Setup the two paths which will contain the primary and
// secondary tick marks.
primaryTicks.reset();
secondaryTicks.reset();
primaryTicks.moveTo(0.f,0.f);
primaryTicks.lineTo((float) w,0.f);
secondaryTicks.moveTo(0.f,0.f);
secondaryTicks.lineTo((float) w,0.f);
// Only do something if a solution was found.
if (optimal>0) {
// Calculate the size of the secondary tick marks.
double size = m/ps[optimal];
// Calculate the limits for the tick marks. Make sure
// that we take the integers which are on the interior of
// the interval!
int average = (int) (0.5*(value0+value1)/size);
int limit0 = (int) (value0/size-average) + average;
int limit1 = (int) (value1/size-average) + average;
// Now make the secondary tick marks.
for (int iv= Math.min(limit0,limit1);
iv <= Math.max(limit0,limit1);
iv++) {
double val = iv*size;
float tickPosition = interpolate(val,w,value0,value1);
addTickMark(secondaryTicks,location,
tickPosition,secondaryTickSize);
}
// Calculate the size of the primary tick marks.
size = m/p[optimal];
// Again calculate the limits for the tick marks on the
// interior of the interval.
average = (int) (0.5*(value0+value1)/size);
limit0 = (int) (value0/size-average) + average;
limit1 = (int) (value1/size-average) + average;
// Make the primary tick marks.
for (int iv = Math.min(limit0,limit1);
iv <= Math.max(limit0,limit1);
iv++) {
double val = iv*size;
float tickPosition = interpolate(val,w,value0,value1);
addTickMark(primaryTicks,location,
tickPosition,primaryTickSize);
}
// Now the labels with the correct precision must be made.
// First calculate the necessary precision.
int ndigits = (int) (Math.floor(Math.log(size)/Math.log(10.)));
ndigits = -Math.min(ndigits,0);
// Now make the labels.
int iv = Math.min(limit0,limit1);
int zero = iv;
double val = iv*size;
float tickPosition = interpolate(val,w,value0,value1);
label[0] = fixedPrecision(val, ndigits);
position[0] = (double) tickPosition;
iv = Math.max(limit0,limit1);
zero *= iv;
val = iv*size;
tickPosition = interpolate(val,w,value0,value1);
label[1] = fixedPrecision(val, ndigits);
position[1] = (double) tickPosition;
// Check to see if zero is inside the interval.
if (zero<=0) {
tickPosition = interpolate(0.,w,value0,value1);
label[2] = fixedPrecision(0., ndigits);
position[2] = (double) tickPosition;
} else {
label[2] = null;
position[2] = position[1];
}
}
}
static private float interpolate(double value, double size,
double value0, double value1) {
return (float) (size*(value-value0)/(value1-value0));
}
public static String fixedPrecision(double d, int ndigits) {
String dstring = Double.toString(d);
StringBuffer buffer = new StringBuffer(dstring);
int index = dstring.lastIndexOf(".");
if (index<0) {
buffer.append(".");
index = buffer.length()-1;
}
buffer.setLength(index+ndigits+1);
for (int i=0; i<buffer.length(); i++) {
if (buffer.charAt(i)=='\u0000') buffer.setCharAt(i,'0');
}
return buffer.toString();
}
}