package org.csstudio.swt.xygraph.linearscale;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Calendar;
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";//$NON-NLS-1$
/**
* 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"; //$NON-NLS-1$
/** ticks label position */
private LabelSide tickLableSide = LabelSide.Primary;
/** the default minimum value of log scale range */
public final static double DEFAULT_LOG_SCALE_MIN = 0.1d;
/** 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 = "############.##"; //$NON-NLS-1$
/** 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*/
protected 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;
private Range range = new Range(min, max);
/**
* Formats the given object.
*
* @param obj
* the object
* @return the formatted string
*/
public String format(Object obj) {
return format(obj, false);
}
/**
* Formats the given object.
*
* @param obj
* the object
* @param minOrMaxDate
* true if it is the min or max date on the scale.
* @return the formatted string
*/
public String format(Object obj, boolean minOrMaxDate) {
if (isDateEnabled()) {
if (autoFormat || formatPattern == null || formatPattern.equals("")
|| formatPattern.equals(default_decimal_format)
|| formatPattern.equals(DEFAULT_ENGINEERING_FORMAT)) {
formatPattern = DEFAULT_DATE_FORMAT;
double length = Math.abs(max - min);
if (length <=5000 || timeUnit == Calendar.MILLISECOND) { //less than five second
formatPattern = "ss.SSS";//$NON-NLS-1$
} else if (length <=1800000d || timeUnit == Calendar.SECOND) { //less than 30 min
formatPattern = "HH:mm:ss";//$NON-NLS-1$
} else if (length <= 86400000d || timeUnit == Calendar.MINUTE) { // less than a day
formatPattern = "HH:mm";//$NON-NLS-1$
} else if (length <= 604800000d || timeUnit == Calendar.HOUR_OF_DAY) { //less than a week
formatPattern = "MM-dd\nHH:mm";//$NON-NLS-1$
} else if (length <= 2592000000d || timeUnit == Calendar.DATE) { //less than a month
formatPattern = "MM-dd";//$NON-NLS-1$
// } else if (length <= 31536000000d ||timeUnit == Calendar.MONTH) { //less than a year
// formatPattern = "yyyy-MM-dd";//$NON-NLS-1$
} else { //more than a month
formatPattern = "yyyy-MM-dd"; //$NON-NLS-1$
}
autoFormat = true;
}
if(minOrMaxDate && autoFormat){
if(Math.abs(max - min)<5000)
return new SimpleDateFormat("yyyy-MM-dd\nHH:mm:ss.SSS").format(obj); //$NON-NLS-1$
return new SimpleDateFormat(DEFAULT_DATE_FORMAT).format(obj);
}
return new SimpleDateFormat(formatPattern).format(obj);
}
if (formatPattern == null || formatPattern.equals("")) {
formatPattern = default_decimal_format;
autoFormat = true;
}
// Edited by scouter.project@gmail.com
//return new DecimalFormat(formatPattern).format(obj);
String value = new DecimalFormat(formatPattern).format(obj);
if (decreaseValue) {
return formatDecreasedValue(value);
}
return value;
}
// ***************Added by scouter.project@gmail.com *****************
/**
* 1,000 -> 1K
* 1,000,000 -> 1M
* 1,000,000,000 -> 1G
* 1,000,000,000,000 -> 1T
*/
private boolean decreaseValue = true;
public void setDecreaseValue(boolean decrease) {
this.decreaseValue = decrease;
}
public String formatDecreasedValue(String value) {
int index = -1;
if ((index = value.lastIndexOf(",000,000,000,000")) != -1) {
return value.substring(0, index) + "T";
} else if ((index = value.lastIndexOf(",000,000,000")) != -1) {
return value.substring(0, index) + "G";
} else if ((index = value.lastIndexOf(",000,000")) != -1) {
return value.substring(0, index) + "M";
} else if ((index = value.lastIndexOf(",000")) != -1) {
return value.substring(0, index) + "K";
}
return value;
}
// *****************************************************************
/**
* @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 getTickLablesSide() {
return tickLableSide;
}
/**
* @return the timeUnit
*/
public int getTimeUnit() {
return timeUnit;
}
/**
* @return the dateEnabled
*/
public boolean isDateEnabled() {
return dateEnabled;
}
/**
* @return the dirty
*/
public boolean isDirty() {
return dirty;
}
/**
* 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;
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) {
try {
new DecimalFormat(formatPattern);
} catch (NullPointerException e) {
throw e;
} catch (IllegalArgumentException e){
throw e;
}
this.formatPattern = formatPattern;
autoFormat = false;
setDirty(true);
revalidate();
repaint();
}
/**
* @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(min <= 0) {
min = DEFAULT_LOG_SCALE_MIN;
}
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;
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());
}
/**Set the range with option to honor its original direction.
* @param t1 value 1 of the range
* @param t2 value 2 of the range
* @param honorOriginDirection if true, the start and end value of the range
* will set according to its original direction.
*/
public void setRange(double t1, double t2, boolean honorOriginDirection){
if(honorOriginDirection){
if(getRange().isMinBigger()){
setRange(t1>t2? t1:t2, t1>t2?t2:t1);
}else
setRange(t1>t2? t2:t1, t1>t2?t1:t2);
}else
setRange(t1, t2);
}
/**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);
}
//in case of lower > upper, reverse them.
// if(lower > upper){
// double temp = lower;
// lower = upper;
// upper = temp;
// }
// if (min == lower && max == upper) {
// return;
// }
if (lower == upper) {
upper = lower +1;
if(Double.isInfinite(upper))
throw new IllegalArgumentException("Illegal range: lower=" + lower + ", upper=" + upper);
}
if (logScaleEnabled && lower <= 0) {
lower = DEFAULT_LOG_SCALE_MIN;
}
min = lower;
max = upper;
//calculate the default decimal format
if(formatPattern ==null || formatPattern == default_decimal_format) {
if(Math.abs(max-min) > 0.1)
default_decimal_format = "############.##";
else {
default_decimal_format = "##.##";
double mantissa = Math.abs(max-min);
while (mantissa < 1) {
mantissa *= 10.0;
default_decimal_format += "#";
}
}
formatPattern = default_decimal_format;
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;
autoFormat = true;
}
range = new Range(min, max);
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 setTickLableSide(LabelSide tickLabelSide) {
this.tickLableSide = 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;
setRange(getRange());
format(0);
}
}
/**
* @return the autoFormat
*/
public boolean isAutoFormat() {
return autoFormat;
}
}