/******************************************************************************* * 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; } }