package org.timepedia.chronoscope.client.axis;
import org.timepedia.chronoscope.client.Dataset;
import org.timepedia.chronoscope.client.Datasets;
import org.timepedia.chronoscope.client.XYPlot;
import org.timepedia.chronoscope.client.canvas.View;
import org.timepedia.chronoscope.client.data.MipMap;
import org.timepedia.chronoscope.client.plot.DefaultXYPlot;
import org.timepedia.chronoscope.client.render.DatasetRenderer;
import org.timepedia.chronoscope.client.render.RangeAxisPanel;
import org.timepedia.chronoscope.client.util.Interval;
import org.timepedia.chronoscope.client.util.MathUtil;
import org.timepedia.exporter.client.Export;
import org.timepedia.exporter.client.ExportPackage;
import org.timepedia.exporter.client.Exportable;
/**
* A RangeAxis is an ValueAxis that represents values, typically on the y-axis.
*/
@ExportPackage("chronoscope")
// TODO - might have to expose a way to change these or otherwise choose the
// most appropriate ones for the locale or units. E.g. "billion" and "trillion" aren't
// the same magnitude everywhere.
public class RangeAxis extends ValueAxis implements Exportable {
private static int MAX_TICKS = 10; // the default max number of ticks
private static final String posExponentLabels[] = {"", "(tens)", "(hundreds)",
"(thousands)", "(tens of thousands)", "(hundreds of thousands)",
"(millions)", "(tens of millions)", "(hundreds of millions)",
"(billions)", "(tens of billions)", "(hundreds of billions)",
"(trillions)", "(tens of trillions)", "(hundreds of trillions)"};
private static final String negExponentLabels[] = {"", "(tenths)",
"(hundredths)", "(thousandths)", "(ten thousandths)",
"(hundred thousandths)", "(millionths)", "(ten millionths)",
"(hundred millionths)", "(billionths)", "(ten billionths)",
"(hundred billionths)", "(trillionths)", "(ten trillionths)",
"(hundred trillionths)"};
// by 1000^n: 1000^0, 1000^1, 1000^2, ...
private static final String SIposSymbol[] = {"", "k", "M", "G", "T", "P", "E", "Z", "Y"};
private static final String SIposPrefix[] = {"", "kilo", "mega", "giga", "tera", "peta", "exa", "zetta", "yotta"};
// private static final String SInegSymbol[] = {"", "m", "mu", "n", "p", "f", "a", "z", "y"};
private static final String SInegSymbol[] = {"", "m", "0x03BC", "n", "p", "f", "a", "z", "y"};
private static final String SInegPrefix[] = {"", "milli", "micro", "nano", "pico", "femto", "atto", "zepto", "yocto"};
private class DefaultTickLabelNumberFormatter implements TickLabelNumberFormatter {
String labelFormat = "###0.####";
// String labelFormat = "0." + "0#########".substring(MAX_DIGITS);
public String format(double value) {
String symbol = ""; // , prefix = "";
double rscale = getScale();
int scaleLog10 = (int) Math.log10(Math.abs(rscale));
int sigdig = getTickLabelSigDig();
if (isCalcRangeAsPercent()) {
scientificNotationOn = false;
labelFormat = zeroish(value) ? "0" : "###.##";
symbol = "%";
// value = value/getRangeExtrema.length
} else if (isForceScientificNotation() ||
(isAllowScientificNotation() && (Math.abs(scaleLog10) >= MAX_DIGITS))) {
scientificNotationOn = true;
// labelFormat = "0." + "##########".substring(MAX_DIGITS-1) + "E0";
labelFormat = zeroish(value) ? "0" : "0."+"00000000".substring(0, sigdig-1)+"E0";
} else if (isScaleSI() || (isAllowAutoScale() && (Math.abs(scaleLog10) >= MAX_DIGITS))) {
scientificNotationOn = false;
int SIscale = scaleLog10/3;
// prefix = (rangeAxisScale<0) ? SInegPrefix[-SIscale] : SIposPrefix[SIscale];
// symbol = (rangeAxisScale<0) ? SInegSymbol[-SIscale] : SIposSymbol[SIscale];
if (zeroish(value - getRangeInterval().getEnd())) {
symbol = (scaleLog10<0) ? SInegSymbol [-SIscale] + getAxisId() + negExponentLabels[-scaleLog10] : SIposSymbol[SIscale];
} else {
symbol = (scaleLog10<0) ? SInegSymbol [-SIscale] + getAxisId() : SIposSymbol[SIscale];
}
value = value/rscale;
labelFormat = zeroish(value) ? "0" : sigformat(rscale, sigdig);
}
return view.numberFormat(labelFormat + symbol, value);
}
public String getFormat() {
return labelFormat;
}
}
private class UserTickLabelNumberFormatter implements TickLabelNumberFormatter {
private View view;
private String format;
public UserTickLabelNumberFormatter(View view, String format) {
this.view = view;
this.format = format;
}
public String format(double value) {
return view.numberFormat(format, value);
}
public String getFormat() {
return format;
}
}
public static final int MAX_DIGITS = 5;
/**
* Calculates the percentage difference from value v1 to value v2. For
* example, if v1 = 10 and v2 = 15, then the % difference is 50% ( 15.0/10.0 )
*/
public static double calcRatio(double ref, double delta) {
return Math.abs(delta-ref)/Math.abs(ref);
}
/**
* Calculates the percentage difference from value v1 to value v2 in a given interval
* example, if v1 = 10 and v2 = 15 on an interval from 0,100, then percent difference
* is 5% ( 5.0/100.0 )
*/
public static double calcPercentDifference(Interval length, double ref, double delta) {
return Math.abs(delta-ref)/length.length();
}
// private static double[] computeLinearTickPositions(double lrangeLow,
private double[] computeLinearTickPositions(double lrangeLow,
double lrangeHigh, double axisHeight, double tickLabelHeight,
boolean forceLowestTick, boolean forceHighestTick) {
// TESTING
forceHighestTick = true;
forceLowestTick = true; // isCalcRangeAsPercent() ? true : forceLowestTick;
if (zeroish(lrangeHigh - lrangeLow)) {
if (lrangeHigh != 0.0) {
int logRange = ((int) Math.floor(Math.log10(lrangeHigh)));
if (logRange < 0) {
logRange += 1;
}
double exponent = Math.pow(10, logRange);
double rounded = Math.floor(lrangeHigh / exponent);
lrangeHigh = forceHighestTick ? lrangeHigh : (rounded + 1) * exponent;
lrangeLow = forceLowestTick ? lrangeLow : (rounded - 1) * exponent;
} else {
lrangeHigh = 1.0;
lrangeLow = -1.0;
}
}
int numTicks = (int) (axisHeight/(2.5 * tickLabelHeight));
numTicks = MAX_TICKS < numTicks ? MAX_TICKS : numTicks;
// if values straddle zero, try to make zero one of the ticks
boolean spansZero = !zeroish(lrangeHigh) && !zeroish(lrangeLow) && (lrangeHigh * lrangeLow < 0);
double larger = Math.max(lrangeLow, lrangeHigh);
double range = Math.abs(lrangeHigh - lrangeLow);
// int ticksInSmaller = (int)(numTicks * (range-larger)/range);
// double roughInterval = spansZero ? range/(numTicks - 2): range/(numTicks - 2);
// double roughInterval = spansZero? larger/(numTicks - 3) : range/(numTicks - 2);
double roughInterval = range / (1.0 * (numTicks - 1));
int logRange = ((int) Math.floor(Math.log10(roughInterval))) - 1;
double exponent = Math.pow(10, logRange);
int smoothSigDigits = (int) (roughInterval / exponent);
smoothSigDigits = smoothSigDigits % 5 == 0 ? smoothSigDigits : smoothSigDigits - (int) MathUtil.mod(smoothSigDigits, 5.0);
double smoothInterval = smoothSigDigits * exponent;
double epsilon = smoothInterval/2.0;
// double edge = MathUtil.mod(larger,smoothInterval);
/*
if (forceHighestTick) {
if (spansZero) {
// back off if crowded
edge = larger % smoothInterval;
if (!zeroish(edge) && (epsilon > edge)) {
// numTicks = numTicks > 3 ? numTicks - 1 : numTicks + 1;
numTicks = numTicks > 5 ? numTicks - 2 : numTicks++;
roughInterval = range/(numTicks-1);
numTicks = (int) (range / roughInterval) + 1;
logRange = ((int) Math.floor(Math.log10(roughInterval))) - 1;
exponent = Math.pow(10, logRange);
roughInterval = range/(numTicks -1);
smoothSigDigits = (int) (roughInterval / exponent);
smoothInterval = smoothSigDigits * exponent;
}
} else {
// back off if the top is crowded
edge = lrangeHigh % smoothInterval;
if (!zeroish(edge) && (epsilon > edge)) {
// numTicks = numTicks > 2 ? numTicks - 1 : numTicks + 1;
// numTicks--;
roughInterval = range/(numTicks - 1);
numTicks = (int) (range/roughInterval) + 1;
logRange = ((int) Math.floor(Math.log10(roughInterval))) - 1;
exponent = Math.pow(10, logRange);
roughInterval = range/(numTicks-1);
smoothSigDigits = (int) (roughInterval / exponent);
smoothInterval = smoothSigDigits * exponent;
}
}
} */
tickLabelSigDig = String.valueOf(smoothSigDigits*numTicks).length();
// double roundedStart = lrangeLow - MathUtil.mod(lrangeLow, 5.0 * exponent);
double roundedStart = lrangeLow < 0 ? -Math.ceil(-lrangeLow/smoothInterval) * smoothInterval :
Math.floor(lrangeLow/smoothInterval) * smoothInterval;
if (lrangeLow > 0 && (lrangeLow + smoothInterval) == 0) {
roundedStart = 0;
}
double tickValue = forceLowestTick ? lrangeLow : roundedStart;
if (spansZero) {
int posTicks = (int) Math.floor(lrangeHigh / smoothInterval);
int negTicks = (int) Math.ceil(Math.abs(lrangeLow / smoothInterval));
numTicks = posTicks + negTicks + 1; // +1 for zero/axis tick
} // double delta = lrangeHigh - posTicks*smoothInterval;
// if (delta > epsilon) {
// numTicks += (int) Math.ceil((delta-epsilon)/smoothInterval);
else {
numTicks = (int)Math.ceil((range)/smoothInterval);
// double delta = lrangeHigh - smoothTicks*smoothInterval - roundedStart;
// if (delta > epsilon) {
// numTicks += (int) Math.ceil((delta-epsilon)/smoothInterval);
// }
}
// TODO - confirm various non-zero crossing cases have round numbered tick labels
double tickPositions[] = new double[numTicks];
tickPositions[0] = tickValue;
for (int i = 1; i < tickPositions.length; i++) {
if (tickValue >= 0 && forceLowestTick && (1 == i)) {
if (smoothInterval *.88 > roundedStart+smoothInterval-tickValue) {
tickValue = roundedStart + 2*smoothInterval;
} else {
tickValue = roundedStart + smoothInterval;
}
} else
if (tickValue < 0) {
int negIntervals = (int) (Math.abs(tickValue*.75)/smoothInterval);
if (negIntervals > 1) {
if ((tickValue - smoothInterval*negIntervals) < smoothInterval) {
tickValue = -smoothInterval * (negIntervals-1);
} else {
tickValue = - smoothInterval * negIntervals;
}
} else {
tickValue += smoothInterval;
if (Math.abs(tickValue) < Math.abs(epsilon)) { // if close to zero but still negative, use zero
tickValue = 0;
} else if ((tickValue * (tickValue - smoothInterval) < 0)) { // if crossed from neg to pos, use zero
tickValue = 0;
}
}
} else {
int posIntervals = (int) (Math.abs(lrangeHigh - epsilon - tickValue)/(smoothInterval));
double lastTick = forceHighestTick ? lrangeHigh : smoothInterval * posIntervals;
if ((tickPositions.length-2 == i)
&& ((lastTick - (tickValue + smoothInterval)) < (.88 * smoothInterval))) {
tickValue = lastTick;
} else
if ((tickPositions.length-1)== i) {
tickValue = lastTick;
} else {
tickValue += smoothInterval;
}
}
tickPositions[i] = tickValue;
}
return tickPositions;
}
private boolean allowAutoScale = true;
private boolean allowScientificNotation = false;
private boolean autoZoom = false;
private int axisIndex;
private boolean calcRangeAsPercent = false;
private TickLabelNumberFormatter defaultTickLabelNumberFormatter;
private boolean forceScientificNotation = false;
private XYPlot plot;
private double absRangeMin = Double.POSITIVE_INFINITY, absRangeMax = Double.NEGATIVE_INFINITY;
private double visRangeMin = Double.POSITIVE_INFINITY, visRangeMax = Double.NEGATIVE_INFINITY;
private double adjustedRangeMin = Double.POSITIVE_INFINITY, adjustedRangeMax = Double.NEGATIVE_INFINITY;
private RangeAxisPanel rangeAxisPanel;
private boolean rangeOveriddenLow, rangeOveriddenHigh;
private double absScale = Double.NaN, visScale = Double.NaN;
private boolean scaleSI = false;
private boolean scientificNotationOn;
private boolean showExponents = false;
private double[] ticks;
private TickLabelNumberFormatter tickLabelNumberFormatter;
private int tickLabelSigDig;
private double absAxisLength, visAxisLength;
private double tickLabelHeight;
private double rangeAxisHeight;
private View view;
public RangeAxis(String rangeLabel, String axisId) {
super(rangeLabel, axisId);
tickLabelNumberFormatter = defaultTickLabelNumberFormatter
= new DefaultTickLabelNumberFormatter();
}
/**
* Increases the interval of this object's current range extrema so that it's
* at least as wide as <tt>rangeExtrema(0)</tt> of the specified dataset
* object.
*/
public void adjustAbsRange(Dataset ds) {
if (!rangeOveriddenLow || !rangeOveriddenHigh) {
DatasetRenderer dr = plot.getDatasetRenderer(plot.getDatasets().indexOf(ds));
MipMap m = ds.getMipMapChain().getMipMap(0);
while (m != null) {
if (m.getLevel() > 1 && m.size() < plot.getMaxDrawableDataPoints()) {
break;
}
Interval rangeExtrema = dr.getRangeExtrema(m);
double rangeMin = rangeExtrema.getStart();
double rangeMax = rangeExtrema.getEnd();
// rangeLength = rangeExtrema.length();
setAbsLength(rangeMin, rangeMax);
if (calcRangeAsPercent) {
final double refY = dr.getRange(ds.getFlyweightTuple(0));
double rmin = rangeOveriddenLow ? absRangeMin : rangeMin;
double rmax = rangeOveriddenHigh ? absRangeMax : rangeMax;
double normalizedMin = rangeExtrema.getPercentChange(refY, rmin);
double normalizedMax = rangeExtrema.getPercentChange(refY, rmax);
setAbsRange( normalizedMin, normalizedMax);
} else {
setAbsRange(
Math.min(absRangeMin, rangeOveriddenLow ? absRangeMin : rangeMin),
Math.max(absRangeMax, rangeOveriddenHigh ? absRangeMax : rangeMax));
}
m = m.next();
}
}
}
/**
* Increases the interval of this object's current visible range extrema so
* that it's at least as wide as the specified <tt>visRange</tt> object.
*/
public void adjustVisibleRange(Interval visRange) {
setVisibleRange(Math.min(visRangeMin, visRange.getStart()),
Math.max(visRangeMax, visRange.getEnd()));
}
public double[] calcTickPositions() {
if (ticks != null) {
return ticks;
}
final double rangeMin = autoZoom ? this.visRangeMin : this.absRangeMin;
final double rangeMax = autoZoom ? this.visRangeMax : this.absRangeMax;
// tickLabelHeight = Math.min(rangeAxisPanel.getMaxLabelHeight(), 12);
// rangeAxisPanel height might not be set yet, so use 75% of view height as initial estimate=
// double rangeAxisHeight = rangeAxisPanel.getBounds().height < 20 ?
// view.getHeight()*0.75 : rangeAxisPanel.getBounds().height;
// rangeAxisHeight -= rangeAxisHeight > tickLabelHeight*2 ? tickLabelHeight : 0;
ticks = computeLinearTickPositions(rangeMin, rangeMax,
rangeAxisHeight, tickLabelHeight,
rangeOveriddenLow, rangeOveriddenHigh);
if (ticks.length == 0) {
return ticks;
}
adjustedRangeMin = rangeOveriddenLow ? rangeMin : Math.min(ticks[0], rangeMin);
adjustedRangeMax = rangeOveriddenHigh ? rangeMax : Math.max(ticks[ticks.length-1], rangeMax);
return ticks;
}
public TickLabelNumberFormatter createDefaultTickLabelNumberFormatter() {
return new DefaultTickLabelNumberFormatter();
}
public double dataToUser(double dataValue) {
return (dataValue - adjustedRangeMin) / (adjustedRangeMax - adjustedRangeMin);
// return (dataValue - getRangeInterval().getStart()) / getAxisLength();
}
/**
* Make this range axis the current focus. Has not effect the plot has
* multi-axis set to true. Otherwise, this axis becomes the primary left axis
* displayed.
*/
@Export
public void focus() {
getAxisPanel().setValueAxis(this);
plot.damageAxes();
getAxisPanel().getParent().layout();
((DefaultXYPlot) plot).redraw(true);
}
// TODO - export as axis.index rather than axis.getIndex() ?
@Export
public int getAxisIndex() {
return axisIndex;
}
public RangeAxisPanel getAxisPanel() {
return this.rangeAxisPanel;
}
public Interval getExtrema() {
return new Interval(adjustedRangeMin, adjustedRangeMax);
}
public String getFormattedLabel(double value) {
return tickLabelNumberFormatter.format(value);
}
// TODO - export as axis.label rather than axis.getLabel() ?
@Export
public String getLabel() {
// double s = Double.isNaN(getScale()) ? 1.0 : getScale();
// return super.so wgetLabel() + getLabelSuffix(getRange());
String axisId = getAxisId();
if (axisId == null || "".equals(axisId)) {
return getLabelPrefix() + super.getLabel() + getLabelSuffix();
} else {
return getLabelPrefix() + axisId + getLabelSuffix();
// String symbol = (intDigits<0) ? SInegSymbol[SIscale] : SIposSymbol[SIscale];
// return axisId;
}
}
public String getLabelPrefix() {
return "";
/*
if (isCalcRangeAsPercent()) {
// double percent = Math.abs(getAxisLength()/100.0);
// String fmt = sigformat(percent, getTickLabelSigDig());
return ""; // return "1% = "+ view.numberFormat(fmt, percent) + " ";
} else if (getScale()<1) { //getScale isn't safe to call during axis label rangepanel setup
return ""; // return "1/"+ view.numberFormat("###,###,###,###", (int) (1/getScale())) + " ";
} else {
return ""; // return view.numberFormat("###,###,###,###", (int) getScale()) + " ";
} */
}
public String getLabelSuffix() {
if (isCalcRangeAsPercent()) {
return " %";
// TODO - maybe describe the real range value span or what each percent means
// eg: 1% ~ 1,000,000 barrels of oil
}
return "";
}
/**
*
* @return range interval of tick labels, to get the underlying real range length when isCalcPercent use getAxisLength
*/
public Interval getRangeInterval() {
return new Interval(absRangeMin, absRangeMax);
}
/**
*
* @return range extrema interval length of real underlying values (not percentages)
*/
public double getAxisLength() {
if (isCalcRangeAsPercent()) {
// TODO - should use the right dimension/tupleCoord if >0
plot.getDatasets().get(this.getAxisIndex()).getRangeExtrema(0).length();
}
return isAutoZoomVisibleRange() ? visAxisLength : absAxisLength;
}
@Export
public double getScale() {
double rscale = isAutoZoomVisibleRange() ? visScale : absScale;
if (Double.isNaN(rscale)) {
// Interval realExtrema = plot.getRangeAxis(this.getAxisIndex()).getExtrema();
// this.setAbsRange(realExtrema.getStart(), realExtrema.getEnd());
// this.setVisibleRange(realExtrema.getStart(), realExtrema.getEnd());
adjustAbsRanges();
}
return isAutoZoomVisibleRange() ? visScale : absScale;
}
public double getTickLabelHeight() {
return tickLabelHeight;
}
public int getTickLabelSigDig() {
return tickLabelSigDig;
}
public TickLabelNumberFormatter getTickLabelFormatter() {
return tickLabelNumberFormatter;
}
@Export
public String getTickNumberFormat() {
return tickLabelNumberFormatter.getFormat();
}
@Export
public boolean isAllowAutoScale() {
return allowAutoScale;
}
@Export
public boolean isAllowScientificNotation() {
return allowScientificNotation;
}
@Export
public boolean isAutoZoomVisibleRange() {
return autoZoom;
}
/**
* See {@link #setCalcRangeAsPercent(boolean)}
*/
@Export
public boolean isCalcRangeAsPercent() {
return calcRangeAsPercent;
}
@Export
public boolean isForceScientificNotation() {
return forceScientificNotation;
}
@Export
public boolean isScaleSI() {
return scaleSI;
}
public boolean isScientificNotationOn() {
return scientificNotationOn;
}
public boolean isShowScale() {
return showExponents;
}
public void resetVisibleRange() {
visRangeMin = Double.POSITIVE_INFINITY;
visRangeMax = Double.NEGATIVE_INFINITY;
}
/**
* If set to true, allow axis ticks to be scaled automatically by powers of
* thousand if they exceed maxDigits settings. For example, if max digits is
* 4, then the number 25000 will be rendered as 25, and the axis label will be
* modified to include the word "Thousands". setAllowScientificNotation() will
* override this and take priority, as well as setScale().
*/
@Export
public void setAllowAutoScale(boolean allowAutoScale) {
this.allowAutoScale = allowAutoScale;
}
/**
* If enabled (true by default), when maxTickLabelDigits is exceeded, labels
* will be rendered in scientific notation.
*/
@Export
public void setAllowScientificNotation(boolean enable) {
allowScientificNotation = enable;
if (enable) {
allowAutoScale = false;
scaleSI = false;
}
}
/**
* Setting to <tt>true</tt> will cause the range-Y to be between min and max
* values of the visible domain, making this range calculate each time the zoom
* or the domain is changed, otherwise the range-Y is fixed to the min and max
* values of all the domain.
*
* @param autoZoom
*/
@Export
public void setAutoZoomVisibleRange(boolean autoZoom) {
if (this.autoZoom != autoZoom) {
this.autoZoom = autoZoom;
if (plot != null) {
plot.damageAxes();
adjustAbsRanges();
((DefaultXYPlot) plot).redraw(true);
// plot.reloadStyles();
}
}
}
public void setAxisIndex(int axisIndex) {
this.axisIndex = axisIndex;
}
public void setAxisPanel(RangeAxisPanel r) {
this.rangeAxisPanel = r;
}
/**
* Setting to <tt>true</tt> will cause all of the range-Y values to be
* converted to a percentage increase/decrease from some reference range-Y
* value (either the first datapoint of the dataset or the leftmost datapoint
* that's visible in the plot).
*/
@Export
public void setCalcRangeAsPercent(boolean calcRangeAsPercent) {
if (calcRangeAsPercent != this.calcRangeAsPercent) {
if (calcRangeAsPercent) {
this.calcRangeAsPercent = calcRangeAsPercent;
this.plot.damageAxes();
adjustAbsRanges();
} else {
this.rangeOveriddenHigh = false;
this.rangeOveriddenLow = false;
this.calcRangeAsPercent = calcRangeAsPercent;
adjustAbsRanges();
// Interval realExtrema = plot.getDatasets().get(this.getAxisIndex()).getRangeExtrema(0);
// this.setAbsRange(realExtrema.getStart(), realExtrema.getEnd());
// this.setVisibleRange(realExtrema.getStart(), realExtrema.getEnd());
this.plot.damageAxes();
}
}
}
/**
* Force tick labels to always be rendered in scientific notation. (Default
* false);
*/
@Export
public void setForceScientificNotation(boolean force) {
if (forceScientificNotation != force) {
if (force) {
allowAutoScale = false;
calcRangeAsPercent = false;
scaleSI = false;
} // else { }
forceScientificNotation = force;
}
}
@Export
public void setLabel(String label) {
super.setLabel(label);
plot.damageAxes();
rangeAxisPanel.computeLabelWidths(view);
}
public void setPlot(XYPlot plot) {
this.plot = plot;
}
/**
* Set the minimum and maximum visible axis range.
*/
@Export
public void setRange(double rangeLow, double rangeHigh) {
rangeOveriddenLow = rangeOveriddenHigh = true;
setAbsRange(rangeLow, rangeHigh);
setVisibleRange(rangeLow, rangeHigh);
this.plot.reloadStyles();
}
/**
* Set the maximum visible axis range.
*/
@Export
public void setRangeMax(double rangeHigh) {
rangeOveriddenHigh = true;
setAbsRange(absRangeMin, rangeHigh);
this.plot.reloadStyles();
}
/**
* Set the minimum visible axis range.
*/
@Export
public void setRangeMin(double rangeLow) {
rangeOveriddenLow = true;
setAbsRange(rangeLow, absRangeMax);
this.plot.reloadStyles();
}
/**
* Set a scale factor for displaying axis tick values
*/
@Export
public void setScale(double scale) {
if (isAutoZoomVisibleRange()) {
this.visScale = scale;
} else {
this.absScale = scale;
}
}
/**
* Use SI scale prefixes and symbol abbreviations (kilo,k), (milli,m), (giga, G), etc.
* @param scaleSI
*/
@Export
public void setScaleSI(boolean scaleSI) {
if (this.scaleSI != scaleSI) {
if(scaleSI){
forceScientificNotation = false;
calcRangeAsPercent = false;
} // else { }
this.scaleSI = scaleSI;
}
}
public void setRangeAxisHeight(double rangeAxisHeight) {
this.rangeAxisHeight = rangeAxisHeight;
}
public void setShowExponents(boolean showExponents) {
this.showExponents = showExponents;
}
public void setTickLabelHeight(double tickLabelHeight){
this.tickLabelHeight = tickLabelHeight;
}
/**
* Set custom TickLabelNumberFormatter callbacks.
*/
@Export
public void setTickLabelNumberFormatter(
TickLabelNumberFormatter tickLabelNumberFormatter) {
this.tickLabelNumberFormatter = tickLabelNumberFormatter;
}
/**
* Set the number format used to render ticks
*/
@Export
public void setTickNumberFormat(String format) {
if (format == null) {
tickLabelNumberFormatter = defaultTickLabelNumberFormatter;
} else {
setTickLabelNumberFormatter(
new UserTickLabelNumberFormatter(view, format));
}
}
public void setView(View view) {
this.view = view;
}
public void setVisibleRange(double visRangeMin, double visRangeMax) {
ticks = null;
this.visRangeMin = visRangeMin;
this.visRangeMax = visRangeMax;
ticks = calcTickPositions();
setVisScale(calcScale(visRangeMin, visRangeMax));
setVisLength(visRangeMin, visRangeMax);
this.visRangeMin = visRangeMin;
this.visRangeMax = visRangeMax;
}
public void setVisibleRangeMax(double visRangeMax) {
this.visRangeMax = visRangeMax;
ticks = null;
}
public void setVisibleRangeMin(double visRangeMin) {
this.visRangeMin = visRangeMin;
ticks = null;
}
public double userToData(double userValue) {
return adjustedRangeMin + ((adjustedRangeMax - adjustedRangeMin) * userValue);
}
/**
* Adjusts {@link #absRangeMin} and {@link #absRangeMax} to the smallest
* interval that fully encompasses
*/
private void adjustAbsRanges() {
this.setAbsRange(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
Datasets datasets = plot.getDatasets();
for (int i = 0; i < datasets.size(); i++) {
Dataset dataset = datasets.get(i);
if (dataset.getAxisId(0).equals(this.getAxisId())) {
this.adjustAbsRange(dataset);
}
}
}
/**
* Sets {@link #absRangeMin} and {@link #absRangeMax} to the specified
* values.
*/
private void setAbsRange(double absRangeMin, double absRangeMax) {
ticks = null;
this.absRangeMin = absRangeMin;
this.absRangeMax = absRangeMax;
setAbsScale(calcScale(absRangeMin, absRangeMax));
setAbsLength(absRangeMin, absRangeMax);
}
static double calcScale(double min, double max) {
if (Double.isNaN(min) || Double.isInfinite(min) || Double.isNaN(max) || Double.isInfinite(max)) {
return 1;
}
int minDigits = zeroish(min) ? 1 : (int) Math.floor(Math.log10(Math.abs(min)));
int maxDigits = zeroish(max) ? 1 : (int) Math.floor(Math.log10(Math.abs(max)));
double rscale = 1;
int SIscale = Math.max(maxDigits, minDigits)/3;
rscale = Math.pow(1000, SIscale);
/* if ((minDigits * maxDigits) < 0) {
rscale = (int)Math.pow(1000,((Math.max(maxDigits, minDigits))/3));
} else {
rscale = (int)Math.pow(1000,((maxDigits + minDigits)/6));
} */
return rscale;
}
private void setAbsScale(double absScale) {
this.absScale = absScale;
}
private void setVisScale(double visScale) {
this.visScale = visScale;
}
private void setAbsLength(double min, double max) {
if (Double.isInfinite(min) || Double.isInfinite(max) || Double.isNaN(min) || Double.isNaN(max)) {
return;
}
this.absAxisLength = Math.abs(max-min);
}
private void setVisLength(double min, double max) {
if (Double.isInfinite(min) || Double.isInfinite(max) || Double.isNaN(min) || Double.isNaN(max)) {
return;
}
this.visAxisLength = Math.abs(max-min);
}
// TODO - move to util?
private static boolean zeroish(double val) {
return (Double.MIN_VALUE * 10 > Math.abs(val));
}
private static String sigformat(double rscale, int sigDigits) {
String sigfmt = "##0.##";
int scaleLog10 = (int) Math.log10(Math.abs(rscale));
if (scaleLog10 >= 0) {
String digStr = "#########0";
sigfmt = digStr.substring(Math.max(digStr.length() - scaleLog10, 1));
int leftOver = Math.max(sigDigits - scaleLog10, 0);
if (leftOver > 0) {
String decStr = "0000000000";
sigfmt += "." + decStr.substring(decStr.length()-leftOver);
}
} else { // 1 > val > -1
String decStr = "0000000000";
sigfmt = "0." + decStr.substring(decStr.length()-sigDigits);
}
return sigfmt;
}
}