/*******************************************************************************
* Copyright (c) 2010, 2017 Oak Ridge National Laboratory and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
******************************************************************************/
package org.eclipse.nebula.visualization.xygraph.linearscale;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.TextUtilities;
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
*/
protected static final int ENGINEERING_LIMIT = 4;
protected static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd\nHH:mm:ss"; //$NON-NLS-1$
private static final Map<String, Format> formatCache = new HashMap<String, Format>();
/** 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.1d;
/** the default maximum value of log scale range */
public final static double DEFAULT_LOG_SCALE_MAX = 100d;
/** the default label format */
protected 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 = 40;
/** 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);
private int formatPatternSize = 0;
/**
* Formats the given object as a DateFormat if Date is enabled or as a
* DecimalFormat. This is based on an internal format pattern given the
* object in parameter.
*
* @param obj
* the object
* @return the formatted string
*/
public String format(Object obj) {
return format(obj, false);
}
/**
* Formats the given object as a DateFormat if Date is enabled or as a
* DecimalFormat. This is based on an internal format pattern given the
* object in parameter. When formatting a date, if minOrMaxDate is true as
* well as autoFormat, then the SimpleDateFormat us used to format the
* 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)) {
double length = Math.abs(max - min);
if (length <= 5000 || timeUnit == Calendar.MILLISECOND) { // less
// than
// five
// second
internalSetFormatPattern("ss.SSS");//$NON-NLS-1$
} else if (length <= 1800000d || timeUnit == Calendar.SECOND) { // less
// than
// 30
// min
internalSetFormatPattern("HH:mm:ss");//$NON-NLS-1$
} else if (length <= 86400000d || timeUnit == Calendar.MINUTE) { // less
// than
// a
// day
internalSetFormatPattern("HH:mm");//$NON-NLS-1$
} else if (length <= 604800000d || timeUnit == Calendar.HOUR_OF_DAY) { // less
// than
// a
// week
internalSetFormatPattern("MM-dd\nHH:mm");//$NON-NLS-1$
} else if (length <= 2592000000d || timeUnit == Calendar.DATE) { // less
// than
// a
// month
internalSetFormatPattern("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
internalSetFormatPattern("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 getFormat(DEFAULT_DATE_FORMAT, true).format(obj);
}
return getFormat(formatPattern, true).format(obj);
}
if (formatPattern == null || formatPattern.equals("")) {
formatPattern = default_decimal_format;
autoFormat = true;
}
return getFormat(formatPattern, false).format(obj);
}
private Format getFormat(String pattern, boolean isDateFormat) {
Format result = formatCache.get(pattern);
if (result == null) {
if (isDateFormat)
result = new SimpleDateFormat(pattern);
else
result = new DecimalFormat(pattern);
formatCache.put(pattern, result);
}
return result;
}
/**
* @return the majorTickMarkStepHint
*/
public int getMajorTickMarkStepHint() {
if (isDateEnabled()) {
return Math.max(majorTickMarkStepHint, formatPatternSize);
}
return majorTickMarkStepHint;
}
/** get the scale range */
public Range getRange() {
return range;
}
/**
* @deprecated use correctly spelled {@link #getTickLabelSide(LabelSide)}
*/
@Deprecated
public LabelSide getTickLablesSide() {
return getTickLabelSide();
}
/**
* @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;
}
/**
* 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;
}
internalSetFormatPattern(formatPattern);
autoFormat = false;
setDirty(true);
revalidate();
repaint();
}
private void internalSetFormatPattern(String formatPattern) {
if (formatPattern.equals(this.formatPattern))
return;
this.formatPattern = formatPattern;
if (isDateEnabled())
formatPatternSize = TextUtilities.INSTANCE.getTextExtents(formatPattern, getFont()).width;
}
/**
* @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 setTickLabelSide(LabelSide tickLabelSide) {
this.tickLabelSide = tickLabelSide;
revalidate();
}
/**
* @deprecated use correctly spelled {@link #setTickLabelSide(LabelSide)}
*/
@Deprecated
public void setTickLableSide(LabelSide tickLabelSide) {
setTickLabelSide(tickLabelSide);
}
/**
* 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) {
internalSetAutoFormat(autoFormat);
if (autoFormat) {
formatPattern = null;
setRange(getRange());
format(0);
}
}
/**
* Sets ONLY the autoFormat value
*
* @param autoFormat
*/
protected void internalSetAutoFormat(boolean autoFormat) {
this.autoFormat = autoFormat;
}
/**
* @return the autoFormat
*/
public boolean isAutoFormat() {
return autoFormat;
}
}