package org.csstudio.swt.xygraph.linearscale; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import org.eclipse.draw2d.Figure; import org.eclipse.draw2d.FigureUtilities; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.geometry.Dimension; /** * Linear Scale tick labels. * @author Xihui Chen */ public class LinearScaleTickLabels extends Figure { private static final int TICK_LABEL_GAP = 20; // Edited by scouter.project@gmail.com 8 -> 20 /** the array of tick label vales */ private ArrayList<Double> tickLabelValues; /** the array of tick label */ private ArrayList<String> tickLabels; /** the array of tick label position in pixels */ private ArrayList<Integer> tickLabelPositions; /** the array of visibility state of tick label */ private ArrayList<Boolean> tickVisibilities; /** the maximum length of tick labels */ private int tickLabelMaxLength; /** the maximum height of tick labels */ private int tickLabelMaxHeight; private int gridStepInPixel; private LinearScale scale; /** * Constructor. * * @param linearScale * the scale */ protected LinearScaleTickLabels(LinearScale linearScale) { this.scale = linearScale; tickLabelValues = new ArrayList<Double>(); tickLabels = new ArrayList<String>(); tickLabelPositions = new ArrayList<Integer>(); tickVisibilities = new ArrayList<Boolean>(); setFont(this.scale.getFont()); setForegroundColor(this.scale.getForegroundColor()); } /** * Updates the tick labels. * * @param length * scale tick length (without margin) */ protected void update(int length) { tickLabelValues.clear(); tickLabels.clear(); tickLabelPositions.clear(); if (scale.isLogScaleEnabled()) { updateTickLabelForLogScale(length); }else { updateTickLabelForLinearScale(length); } updateTickVisibility(); updateTickLabelMaxLengthAndHeight(); } /** * Updates tick label for log scale. * * @param length * the length of scale */ private void updateTickLabelForLogScale(int length) { double min = scale.getRange().getLower(); double max = scale.getRange().getUpper(); if(min <= 0 || max <= 0) throw new IllegalArgumentException( "the range for log scale must be in positive range"); boolean minBigger = max < min; // if (min >= max) { // throw new IllegalArgumentException("min must be less than max."); // } int digitMin = (int) Math.ceil(Math.log10(min)); int digitMax = (int) Math.ceil(Math.log10(max)); final BigDecimal MIN = new BigDecimal(new Double(min).toString()); BigDecimal tickStep = pow(10, digitMin - 1); BigDecimal firstPosition; if (MIN.remainder(tickStep).doubleValue() <= 0) { firstPosition = MIN.subtract(MIN.remainder(tickStep)); } else { if(minBigger) firstPosition = MIN.subtract(MIN.remainder(tickStep)); else firstPosition = MIN.subtract(MIN.remainder(tickStep)).add(tickStep); } //add min boolean minDateAdded =false; if(MIN.compareTo(firstPosition) == (minBigger? 1:-1) ) { tickLabelValues.add(min); if (scale.isDateEnabled()) { Date date = new Date((long) MIN.doubleValue()); tickLabels.add(scale.format(date, true)); minDateAdded = true; } else { tickLabels.add(scale.format(MIN.doubleValue())); } tickLabelPositions.add(scale.getMargin()); } for (int i = digitMin; minBigger? i>=digitMax : i <=digitMax; i+=minBigger?-1:1) { if(Math.abs(digitMax - digitMin) > 20){//if the range is too big, skip minor ticks. BigDecimal v = pow(10,i); if(v.doubleValue() > max) break; if (scale.isDateEnabled()) { Date date = new Date((long) v.doubleValue()); tickLabels.add(scale.format(date, i==digitMin && !minDateAdded)); } else { tickLabels.add(scale.format(v.doubleValue())); } tickLabelValues.add(v.doubleValue()); int tickLabelPosition = (int) ((Math.log10(v.doubleValue()) - Math .log10(min)) / (Math.log10(max) - Math.log10(min)) * length) + scale.getMargin(); tickLabelPositions.add(tickLabelPosition); }else{ for (BigDecimal j = firstPosition; minBigger? j.doubleValue() >= pow(10, i-1) .doubleValue() : j.doubleValue() <= pow(10, i).doubleValue(); j = minBigger? j.subtract(tickStep) : j.add(tickStep)) { if (minBigger? j.doubleValue() < max : j.doubleValue() > max) { break; } if (scale.isDateEnabled()) { Date date = new Date((long) j.doubleValue()); tickLabels.add(scale.format(date, j==firstPosition && !minDateAdded)); } else { tickLabels.add(scale.format(j.doubleValue())); } tickLabelValues.add(j.doubleValue()); int tickLabelPosition = (int) ((Math.log10(j.doubleValue()) - Math .log10(min)) / (Math.log10(max) - Math.log10(min)) * length) + scale.getMargin(); tickLabelPositions.add(tickLabelPosition); } tickStep = minBigger? tickStep.divide(pow(10,1)) : tickStep.multiply(pow(10, 1)); firstPosition = minBigger? pow(10,i-1) : tickStep.add(pow(10, i)); } } //add max if(minBigger? max < tickLabelValues.get(tickLabelValues.size()-1) : max > tickLabelValues.get(tickLabelValues.size()-1)) { tickLabelValues.add(max); if (scale.isDateEnabled()) { Date date = new Date((long) max); tickLabels.add(scale.format(date, true)); } else { tickLabels.add(scale.format(max)); } tickLabelPositions.add(scale.getMargin() + length); } } /** * Updates tick label for normal scale. * * @param length * scale tick length (without margin) */ private void updateTickLabelForLinearScale(int length) { double min = scale.getRange().getLower(); double max = scale.getRange().getUpper(); BigDecimal gridStepBigDecimal = getGridStep(length, min, max); gridStepInPixel = (int) (length * gridStepBigDecimal.doubleValue()/(max - min)); updateTickLabelForLinearScale(length, gridStepBigDecimal); } /** * Updates tick label for normal scale. * * @param length * scale tick length (without margin) * @param tickStep * the tick step */ private void updateTickLabelForLinearScale(int length, BigDecimal tickStep) { double min = scale.getRange().getLower(); double max = scale.getRange().getUpper(); boolean minBigger = max < min; final BigDecimal MIN = new BigDecimal(new Double(min).toString()); BigDecimal firstPosition; //make firstPosition as the right most of min based on tickStep /* if (min % tickStep <= 0) */ if (MIN.remainder(tickStep).doubleValue() <= 0) { /* firstPosition = min - min % tickStep */ firstPosition = MIN.subtract(MIN.remainder(tickStep)); } else { /* firstPosition = min - min % tickStep + tickStep */ firstPosition = MIN.subtract(MIN.remainder(tickStep)).add(tickStep); } // the unit time starts from 1:00 if (scale.isDateEnabled()) { BigDecimal zeroOclock = firstPosition.subtract(new BigDecimal( new Double(3600000).toString())); if (MIN.compareTo(zeroOclock) == -1) { firstPosition = zeroOclock; } } //add min int r = minBigger? 1 : -1; boolean minDateAdded = false; if(MIN.compareTo(firstPosition) == r ) { tickLabelValues.add(min); if (scale.isDateEnabled()) { Date date = new Date((long) MIN.doubleValue()); tickLabels.add(scale.format(date, true)); minDateAdded = true; } else { tickLabels.add(scale.format(MIN.doubleValue())); } tickLabelPositions.add(scale.getMargin()); } for (BigDecimal b = firstPosition; max >= min ? b.doubleValue() < max : b.doubleValue() >max; b = b .add(tickStep)) { if (scale.isDateEnabled()) { Date date = new Date((long) b.doubleValue()); tickLabels.add(scale.format(date, b==firstPosition && !minDateAdded)); } else { tickLabels.add(scale.format(b.doubleValue())); } tickLabelValues.add(b.doubleValue()); int tickLabelPosition = (int) ((b.doubleValue() - min) / (max - min) * length) + scale.getMargin(); //- LINE_WIDTH; tickLabelPositions.add(tickLabelPosition); } //always add max // if((minBigger ? max < tickLabelValues.get(tickLabelValues.size()-1) : // max > tickLabelValues.get(tickLabelValues.size()-1) )) { tickLabelValues.add(max); if (scale.isDateEnabled()) { Date date = new Date((long) max); tickLabels.add(scale.format(date, true)); } else { tickLabels.add(scale.format(max)); } tickLabelPositions.add(scale.getMargin() + length); // } } /** * Updates the visibility of tick labels. */ private void updateTickVisibility() { // initialize the array of tick label visibility state tickVisibilities.clear(); for (int i = 0; i < tickLabelPositions.size(); i++) { tickVisibilities.add(Boolean.TRUE); } if (tickLabelPositions.size() == 0) { return; } // set the tick label visibility int previousPosition = 0; String previousLabel = null; for (int i = 0; i < tickLabelPositions.size(); i++) { // check if there is enough space to draw tick label boolean hasSpaceToDraw = true; if (i != 0) { hasSpaceToDraw = hasSpaceToDraw(previousPosition, tickLabelPositions.get(i), previousLabel, tickLabels.get(i)); } // check if the same tick label is repeated String currentLabel = tickLabels.get(i); boolean isRepeatSameTickAndNotEnd = currentLabel.equals(previousLabel) && (i!=0 && i!=tickLabelPositions.size()-1); // check if the tick label value is major boolean isMajorTickOrEnd = true; if (scale.isLogScaleEnabled()) { isMajorTickOrEnd = isMajorTick(tickLabelValues.get(i)) || i==0 || i==tickLabelPositions.size()-1; } if (!hasSpaceToDraw || isRepeatSameTickAndNotEnd || !isMajorTickOrEnd) { tickVisibilities.set(i, Boolean.FALSE); } else { previousPosition = tickLabelPositions.get(i); previousLabel = currentLabel; } } } /** * Checks if the tick label is major (...,0.01,0.1,1,10,100,...). * * @param tickValue * the tick label value * @return true if the tick label is major */ private boolean isMajorTick(double tickValue) { if (!scale.isLogScaleEnabled()) { return true; } if (Math.log10(tickValue) % 1 == 0) { return true; } return false; } /** * Returns the state indicating if there is a space to draw tick label. * * @param previousPosition * the previously drawn tick label position. * @param tickLabelPosition * the tick label position. * @param previousTickLabel * the prevoius tick label. * @param tickLabel * the tick label text * @return true if there is a space to draw tick label */ private boolean hasSpaceToDraw(int previousPosition, int tickLabelPosition, String previousTickLabel, String tickLabel) { Dimension tickLabelSize = FigureUtilities.getTextExtents(tickLabel, scale.getFont()); Dimension previousTickLabelSize = FigureUtilities.getTextExtents(previousTickLabel, scale.getFont()); int interval = tickLabelPosition - previousPosition; int textLength = (int) (scale.isHorizontal() ? (tickLabelSize.width/2.0 + previousTickLabelSize.width/2.0) : tickLabelSize.height); boolean noLapOnPrevoius = true; boolean noLapOnEnd = true; if(tickLabelPosition != tickLabelPositions.get(tickLabelPositions.size() - 1)){ //if it is not the end tick label. noLapOnPrevoius = interval > (textLength+TICK_LABEL_GAP); Dimension endTickLabelSize = FigureUtilities.getTextExtents( tickLabels.get(tickLabels.size()-1), scale.getFont()); interval = tickLabelPositions.get(tickLabelPositions.size() - 1) - tickLabelPosition; textLength = (int) (scale.isHorizontal() ? (tickLabelSize.width/2.0 + endTickLabelSize.width/2.0) : tickLabelSize.height); noLapOnEnd = interval > textLength+TICK_LABEL_GAP; } return noLapOnPrevoius && noLapOnEnd; } /** * Gets max length of tick label. */ private void updateTickLabelMaxLengthAndHeight() { int maxLength = 0; int maxHeight = 0; for (int i = 0; i < tickLabels.size(); i++) { if (tickVisibilities.size() > i && tickVisibilities.get(i) == true) { Dimension p = FigureUtilities.getTextExtents(tickLabels.get(i), scale.getFont()); if (tickLabels.get(0).startsWith("-") && !tickLabels.get(i).startsWith("-")) { p.width += FigureUtilities.getTextExtents("-", getFont()).width; } if (p.width > maxLength) { maxLength = p.width; } if(p.height > maxHeight){ maxHeight = p.height; } } } tickLabelMaxLength = maxLength; tickLabelMaxHeight = maxHeight; } /** * Calculates the value of the first argument raised to the power of the * second argument. * * @param base * the base * @param expornent * the exponent * @return the value <tt>a<sup>b</sup></tt> in <tt>BigDecimal</tt> */ private BigDecimal pow(double base, int expornent) { BigDecimal value; if (expornent > 0) { value = new BigDecimal(new Double(base).toString()).pow(expornent); } else { value = BigDecimal.ONE.divide(new BigDecimal(new Double(base) .toString()).pow(-expornent)); } return value; } /** * Gets the grid step. * * @param lengthInPixels * scale length in pixels * @param min * minimum value * @param max * maximum value * @return rounded value. */ private BigDecimal getGridStep(int lengthInPixels, double min, double max) { if((int) scale.getMajorGridStep() != 0) { return new BigDecimal(scale.getMajorGridStep()); } if (lengthInPixels <= 0) { lengthInPixels = 1; } boolean minBigger = false; if (min >= max) { if(max == min) max ++; else{ minBigger = true; double swap = min; min = max; max= swap; } // throw new IllegalArgumentException("min must be less than max."); } double length = Math.abs(max - min); double majorTickMarkStepHint = scale.getMajorTickMarkStepHint(); if(majorTickMarkStepHint > lengthInPixels) majorTickMarkStepHint = lengthInPixels; // if(min > max) // majorTickMarkStepHint = -majorTickMarkStepHint; double gridStepHint = length / lengthInPixels * majorTickMarkStepHint; if(scale.isDateEnabled()) { //by default, make the least step to be minutes long timeStep; if(max-min<10000) //<10 sec, step = 1 ms timeStep=1l; else if(max - min < 60000) // < 1 min, step = 1 sec timeStep = 1000l; else if(max - min < 600000) // < 10 min, step = 10 sec timeStep= 10000l; else if (max -min < 6400000) // < 2 hour, step = 1 min timeStep = 60000l; else if (max -min < 43200000) // < 12 hour, step = 10 min timeStep = 600000l; else if (max -min < 86400000) // < 24 hour, step = 30 min timeStep = 1800000l; else if (max - min < 604800000) // < 7 days, step = 1 hour timeStep = 3600000l; else timeStep = 86400000l; if (scale.getTimeUnit() == Calendar.SECOND) { timeStep = 1000l; } else if (scale.getTimeUnit() == Calendar.MINUTE) { timeStep = 60000l; }else if (scale.getTimeUnit() == Calendar.HOUR_OF_DAY) { timeStep = 3600000l; }else if (scale.getTimeUnit() == Calendar.DATE) { timeStep = 86400000l; }else if (scale.getTimeUnit() == Calendar.MONTH) { timeStep = 30l*86400000l; }else if (scale.getTimeUnit() == Calendar.YEAR) { timeStep = 365l*86400000l; } double temp = gridStepHint + (timeStep - gridStepHint%timeStep); if(minBigger) temp = -temp; return new BigDecimal(temp); } double mantissa = gridStepHint; int exponent = 0; if (mantissa < 1) { if(mantissa != 0) while (mantissa < 1) { mantissa *= 10.0; exponent--; } } else { while (mantissa >= 10) { mantissa /= 10.0; exponent++; } } BigDecimal gridStep; if (mantissa > 7.5) { gridStep = BigDecimal.TEN.multiply(pow(10, exponent)); // 10.0 * 10 ** exponent } else if (mantissa > 3.5) { gridStep = new BigDecimal(new Double(5).toString()).multiply(pow( // 5.0 * 10 ** exponent 10, exponent)); } else if (mantissa > 1.5) { gridStep = new BigDecimal(new Double(2).toString()).multiply(pow( // 2.0 * 10 ** exponent 10, exponent)); } else { gridStep = pow(10, exponent); // 1.0 * 10 ** exponent } if(minBigger) gridStep = gridStep.negate(); return gridStep; } /** * Gets the tick label positions. * * @return the tick label positions */ public ArrayList<Integer> getTickLabelPositions() { return tickLabelPositions; } @Override protected void paintClientArea(Graphics graphics) { graphics.translate(bounds.x, bounds.y); if (scale.isHorizontal()) { drawXTick(graphics); } else { drawYTick(graphics); } super.paintClientArea(graphics); }; /** * Draw the X tick. * * @param grahics * the graphics context */ private void drawXTick(Graphics grahics) { // draw tick labels grahics.setFont(scale.getFont()); for (int i = 0; i < tickLabelPositions.size(); i++) { if (tickVisibilities.get(i) == true) { String text = tickLabels.get(i); int fontWidth = FigureUtilities.getTextExtents(text, getFont()).width; int x = (int) Math.ceil(tickLabelPositions.get(i) - fontWidth / 2.0);// + offset); grahics.drawText(text, x, 0); } } } /** * Draw the Y tick. * * @param grahpics * the graphics context */ private void drawYTick(Graphics grahpics) { // draw tick labels grahpics.setFont(scale.getFont()); int fontHeight = tickLabelMaxHeight; for (int i = 0; i < tickLabelPositions.size(); i++) { if (tickVisibilities.size() == 0 || tickLabels.size() == 0) { break; } if (tickVisibilities.get(i) == true) { String text = tickLabels.get(i); int x = 0; if (tickLabels.get(0).startsWith("-") && !text.startsWith("-")) { x += FigureUtilities.getTextExtents("-", getFont()).width; } int y = (int) Math.ceil(scale.getLength() - tickLabelPositions.get(i) - fontHeight / 2.0); grahpics.drawText(text, x, y); } } } /** * @return the tickLabelMaxLength */ public int getTickLabelMaxLength() { return tickLabelMaxLength; } /** * @return the tickLabelMaxHeight */ public int getTickLabelMaxHeight() { return tickLabelMaxHeight; } /** * @return the tickVisibilities */ public ArrayList<Boolean> getTickVisibilities() { return tickVisibilities; } /** * @return the gridStepInPixel */ public int getGridStepInPixel() { return gridStepInPixel; } }