package org.csstudio.swt.xygraph.linearscale;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.eclipse.draw2d.Figure;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
/**
* The abstract scale has the common properties for linear(straight) scale and
* round scale.
* @author Xihui Chen
*
*/
public abstract class AbstractScale extends Figure{
/** ticks label's position relative to tick marks*/
public enum LabelSide {
/** bottom or left side of tick marks for linear scale,
* or outside for round scale */
Primary,
/** top or right side of tick marks for linear scale,
* or inside for round scale*/
Secondary
}
public static final double DEFAULT_MAX = 100d;
public static final double DEFAULT_MIN = 0d;
public static final String DEFAULT_ENGINEERING_FORMAT = "0.####E0";
/**
* the digits limit to be displayed in engineering format
*/
private static final int ENGINEERING_LIMIT = 4;
private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd\nHH:mm:ss";
/** ticks label position */
private LabelSide tickLabelSide = LabelSide.Primary;
/** the default minimum value of log scale range */
public final static double DEFAULT_LOG_SCALE_MIN = 0.0001d;
/** the default maximum value of log scale range */
public final static double DEFAULT_LOG_SCALE_MAX = 100d;
/** the default label format */
private String default_decimal_format = "############.##";
/** the state if the axis scale is log scale */
protected boolean logScaleEnabled = false;
/** The minimum value of the scale */
protected double min = DEFAULT_MIN;
/** The maximum value of the scale */
protected double max = DEFAULT_MAX;
/** the format for tick labels */
private String formatPattern;
/** the time unit for tick step */
private int timeUnit = 0;
/** Whenever any parameter has been changed, the scale should be marked as dirty,
* so all the inner parameters could be recalculated before the next paint*/
private boolean dirty = true;
private boolean dateEnabled = false;
private boolean scaleLineVisible = true;
/** the pixels hint for major tick mark step */
private int majorTickMarkStepHint = 30;
/** the pixels hint for minor tick mark step */
private int minorTickMarkStepHint = 4;
private boolean minorTicksVisible= true;
private double majorGridStep = 0;
private boolean autoFormat = true;
protected Range range = new Range(min, max);
private Format cachedFormat = null;
private boolean ticksAtEnds = true;
/**
* Formats the given object.
*
* @param obj
* the object
* @return the formatted string
*/
public String format(Object obj) {
if (cachedFormat == null) {
if (isDateEnabled()) {
if (autoFormat || formatPattern == null
|| formatPattern.equals("")
|| formatPattern.equals(default_decimal_format)
|| formatPattern.equals(DEFAULT_ENGINEERING_FORMAT)) {
formatPattern = DEFAULT_DATE_FORMAT; // (?) overridden anyway
double length = Math.abs(max - min);
if (length <= 1000 || timeUnit == Calendar.MILLISECOND) { // less than a second
formatPattern = "HH:mm:ss.SSS";
} else if (length <= 3600000d
|| timeUnit == Calendar.SECOND) { // less than a hour
formatPattern = "HH:mm:ss";
} else if (length <= 86400000d
|| timeUnit == Calendar.MINUTE) { // less than a day
formatPattern = "HH:mm";
} else if (length <= 604800000d
|| timeUnit == Calendar.HOUR_OF_DAY) { // less than a week
formatPattern = "dd HH:mm";
} else if (length <= 2592000000d
|| timeUnit == Calendar.DATE) { // less than a month
formatPattern = "MMMMM d";
} else if (length <= 31536000000d
|| timeUnit == Calendar.MONTH) { // less than a year
formatPattern = "yyyy MMMMM";
} else {// if (timeUnit == Calendar.YEAR) {
formatPattern = "yyyy";
}
if (formatPattern == null || formatPattern.equals("")) {
autoFormat = true;
}
}
cachedFormat = new SimpleDateFormat(formatPattern);
} else {
if (formatPattern == null || formatPattern == default_decimal_format || formatPattern.equals("")) {
ITicksProvider ticks = getTicksProvider();
formatPattern = ticks == null ? default_decimal_format : ticks.getDefaultFormatPattern(min, max);
if (formatPattern == null || formatPattern.equals("")) {
autoFormat = true;
}
}
if (formatPattern.equals(default_decimal_format)
|| formatPattern.equals(DEFAULT_ENGINEERING_FORMAT)) {
if ((max != 0 && Math.abs(Math.log10(Math.abs(max))) >= ENGINEERING_LIMIT)
|| (min != 0 && Math.abs(Math.log10(Math.abs(min))) >= ENGINEERING_LIMIT))
formatPattern = DEFAULT_ENGINEERING_FORMAT;
else
formatPattern = default_decimal_format;
}
cachedFormat = new DecimalFormat(formatPattern);
}
}
if (isDateEnabled() && obj instanceof Number)
return cachedFormat.format(new Date(((Number) obj).longValue()));
return cachedFormat.format(obj);
}
/**
* Gets the ticks provider
* @return tick provider
*/
abstract public ITicksProvider getTicksProvider();
/**
* @return the majorTickMarkStepHint
*/
public int getMajorTickMarkStepHint() {
return majorTickMarkStepHint;
}
/** get the scale range */
public Range getRange() {
return range;
}
/**
* @return the side of the tick label relative to the tick marks
*/
public LabelSide getTickLabelSide() {
return tickLabelSide;
}
/**
* @return the timeUnit
*/
public int getTimeUnit() {
return timeUnit;
}
/**
* @return the dateEnabled
*/
public boolean isDateEnabled() {
return dateEnabled;
}
/**
* @return the dirty
*/
public boolean isDirty() {
return dirty;
}
private boolean userDefinedFormat = false;
protected boolean forceRange;
/**
* Gets the state indicating if log scale is enabled.
*
* @return true if log scale is enabled
*/
public boolean isLogScaleEnabled() {
return logScaleEnabled;
}
/**
* @return the minorTicksVisible
*/
public boolean isMinorTicksVisible() {
return minorTicksVisible;
}
/**
* @return the scaleLineVisible
*/
public boolean isScaleLineVisible() {
return scaleLineVisible;
}
/**
* @param dateEnabled the dateEnabled to set
*/
public void setDateEnabled(boolean dateEnabled) {
this.dateEnabled = dateEnabled;
cachedFormat = null;
setDirty(true);
revalidate();
}
/** Whenever any parameter has been changed, the scale should be marked as dirty,
* so all the inner parameters could be recalculated before the next paint
* @param dirty the dirty to set
*/
protected void setDirty(boolean dirty) {
this.dirty = dirty;
}
/**
* Sets the format pattern for axis tick label. see {@link Format}
* <p>
* If <tt>null</tt> is set, default format will be used.
*
* @param format
* the format
* @exception NullPointerException if <code>pattern</code> is null
* @exception IllegalArgumentException if the given pattern is invalid.
*/
public void setFormatPattern(String formatPattern) {
this.userDefinedFormat = true;
setFormat(formatPattern);
}
public void setDefaultFormatPattern(String formatPattern) {
setFormat(formatPattern);
}
@SuppressWarnings("unused")
private void setFormat(String formatPattern) {
try {
new DecimalFormat(formatPattern);
} catch (NullPointerException e) {
throw e;
} catch (IllegalArgumentException e){
throw e;
}
cachedFormat = null;
this.formatPattern = formatPattern;
autoFormat = false;
setDirty(true);
revalidate();
repaint();
}
public boolean hasUserDefinedFormat() {
return userDefinedFormat;
}
/**
* @return the formatPattern
*/
public String getFormatPattern() {
return formatPattern;
}
@Override
public void setFont(Font f) {
super.setFont(f);
setDirty(true);
revalidate();
}
/**
* @param enabled true if enabling log scales
* @throws IllegalStateException
*/
public void setLogScale(boolean enabled) throws IllegalStateException {
if (logScaleEnabled == enabled) {
return;
}
if(enabled) {
if(min == DEFAULT_MIN && max == DEFAULT_MAX) {
min = DEFAULT_LOG_SCALE_MIN;
max = DEFAULT_LOG_SCALE_MAX;
}
if(max <= 0) {
max = DEFAULT_LOG_SCALE_MAX;
}
if(min <= 0) {
min = DEFAULT_LOG_SCALE_MIN * max;
}
if(max <= min) {
max = min + DEFAULT_LOG_SCALE_MAX;
}
} else if(min == DEFAULT_LOG_SCALE_MIN && max == DEFAULT_LOG_SCALE_MAX) {
min = DEFAULT_MIN;
max = DEFAULT_MAX;
}
logScaleEnabled = enabled;
setTicksAtEnds(true);
range = new Range(min, max);
setDirty(true);
revalidate();
repaint();
}
/**
* @param majorTickMarkStepHint the majorTickMarkStepHint to set, should be less than 1000.
*/
public void setMajorTickMarkStepHint(int majorTickMarkStepHint) {
this.majorTickMarkStepHint = majorTickMarkStepHint;
setDirty(true);
revalidate();
repaint();
}
/**
* @param minorTicksVisible the minorTicksVisible to set
*/
public void setMinorTicksVisible(boolean minorTicksVisible) {
this.minorTicksVisible = minorTicksVisible;
}
/** set the scale range */
public void setRange(final Range range) {
if (range == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
return; // to suppress warnings...
}
setRange(range.getLower(), range.getUpper());
}
private static final double ZERO_RANGE_FRACTION = 0.125; // used if difference between min and max is too small
/**set the scale range
* @param lower the lower limit
* @param upper the upper limit
* @throws IllegalArgumentException
* if lower or upper is Nan of Infinite, or lower >= upper or (upper - lower) is Infinite
*/
public void setRange(double lower, double upper){
if (Double.isNaN(lower) || Double.isNaN(upper)
|| Double.isInfinite(lower) || Double.isInfinite(upper) || Double.isInfinite(upper-lower)) {
throw new IllegalArgumentException("Illegal range: lower=" + lower + ", upper=" + upper);
}
forceRange = lower == upper;
if (forceRange) {
final double delta = (lower == 0 ? 1 : Math.abs(lower)) * ZERO_RANGE_FRACTION;
upper += delta;
lower -= delta;
if(Double.isInfinite(upper))
throw new IllegalArgumentException("Illegal range: lower=" + lower + ", upper=" + upper);
}
if (logScaleEnabled) {
if (upper <= 0)
upper = DEFAULT_LOG_SCALE_MAX;
if (lower <= 0)
lower = DEFAULT_LOG_SCALE_MIN * upper;
}
min = lower;
max = upper;
range = new Range(min, max);
cachedFormat = null;
setDirty(true);
revalidate();
repaint();
}
/**
* @param scaleLineVisible the scaleLineVisible to set
*/
public void setScaleLineVisible(boolean scaleLineVisible) {
this.scaleLineVisible = scaleLineVisible;
}
/**
* @param tickLabelSide the side of the tick label relative to tick mark
*/
public void setTickLabelSide(LabelSide tickLabelSide) {
this.tickLabelSide = tickLabelSide;
revalidate();
}
/**Set the time unit for a date enabled scale. The format of the time
* would be determined by it.
* @param timeUnit the timeUnit to set. It should be one of:
* <tt>Calendar.MILLISECOND</tt>, <tt>Calendar.SECOND</tt>,
* <tt>Calendar.MINUTE</tt>, <tt>Calendar.HOUR_OF_DAY</tt>,
* <tt>Calendar.DATE</tt>, <tt>Calendar.MONTH</tt>,
* <tt>Calendar.YEAR</tt>.
* @see Calendar
*/
public void setTimeUnit(int timeUnit) {
this.timeUnit = timeUnit;
setDirty(true);
}
/**
* Updates the tick, recalculate all inner parameters
*/
public abstract void updateTick();
/**
* @param majorGridStep the majorGridStep to set
*/
public void setMajorGridStep(double majorGridStep) {
this.majorGridStep = majorGridStep;
setDirty(true);
}
/**
* @return the majorGridStep
*/
public double getMajorGridStep() {
return majorGridStep;
}
/**
* @param minorTickMarkStepHint the minorTickMarkStepHint to set
*/
public void setMinorTickMarkStepHint(int minorTickMarkStepHint) {
this.minorTickMarkStepHint = minorTickMarkStepHint;
}
/**
* @return the minorTickMarkStepHint
*/
public int getMinorTickMarkStepHint() {
return minorTickMarkStepHint;
}
/**
* @param autoFormat the autoFormat to set
*/
public void setAutoFormat(boolean autoFormat) {
this.autoFormat = autoFormat;
if(autoFormat){
formatPattern = null;
cachedFormat = null;
setRange(getRange());
format(0);
}
}
/**
* @return the autoFormat
*/
public boolean isAutoFormat() {
return autoFormat;
}
public boolean hasTicksAtEnds() {
return ticksAtEnds;
}
public void setTicksAtEnds(boolean ticksAtEnds) {
this.ticksAtEnds = ticksAtEnds;
}
}