package org.csstudio.swt.xygraph.linearscale; import org.eclipse.draw2d.FigureUtilities; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; /** * Linear(straight) scale has the tick labels and tick marks on a straight line. * It can be used for any scale based widget, such as 2D plot, chart, graph, * thermometer or tank etc. <br> * A scale is comprised of Margins, Scale line, tick labels and tick marks which include * minor ticks and major ticks. <br> * * Margin is half of the label's length(Horizontal Scale) or * height(Vertical scale), so that the label can be displayed correctly. * So the range must be set before you can get the correct margin.<br><br> * * |Margin|______|______|______|______|______|______|Margin| <br> * * * @author Xihui Chen * */ public class LinearScale extends AbstractScale { /** scale direction */ public enum Orientation { /** the constant to represent horizontal scales */ HORIZONTAL, /** the constant to represent vertical scales */ VERTICAL } private static final int SPACE_BTW_MARK_LABEL = 2; /** scale direction, no meaning for round scale */ private Orientation orientation = Orientation.HORIZONTAL; /** the scale tick labels */ private LinearScaleTickLabels tickLabels; /** the scale tick marks */ private LinearScaleTickMarks tickMarks; /** the length of the whole scale */ private int length; private int margin; /** * Constructor. */ public LinearScale() { tickLabels = new LinearScaleTickLabels(this); tickMarks = new LinearScaleTickMarks(this); add(tickMarks); add(tickLabels); // setFont(XYGraphMediaFactory.getInstance().getFont( // XYGraphMediaFactory.FONT_ARIAL)); } private void calcMargin() { if(isHorizontal()) { margin = (int) Math.ceil(Math.max(FigureUtilities.getTextExtents( format(getRange().getLower(), true),getFont()).width, FigureUtilities.getTextExtents(format(getRange().getUpper(), true), getFont()).width)/2.0); }else margin = (int) Math.ceil(Math.max(FigureUtilities.getTextExtents( format(getRange().getLower(), true), getFont()).height, FigureUtilities.getTextExtents(format(getRange().getUpper(), true), getFont()).height)/2.0); } /** * @return the length of the whole scale (include margin) */ public int getLength() { return length; } /**Margin is half of the label's length(Horizontal Scale) or * height(Vertical scale), so that the label can be displayed correctly. * So the range and format pattern must be set correctly * before you can get the correct margin. * @return the margin */ public int getMargin() { if(isDirty()) calcMargin(); return margin; } /** * @return the orientation */ public Orientation getOrientation() { return orientation; } @Override public Dimension getPreferredSize(int wHint, int hHint) { Dimension size = new Dimension(wHint, hHint); LinearScaleTickLabels fakeTickLabels = new LinearScaleTickLabels(this); if(isHorizontal()) { //length = wHint; fakeTickLabels.update(wHint-2*getMargin()); size.height = (int)fakeTickLabels.getTickLabelMaxHeight() + SPACE_BTW_MARK_LABEL + LinearScaleTickMarks.MAJOR_TICK_LENGTH; } else { //length = hHint; fakeTickLabels.update(hHint-2*getMargin()); size.width = (int)fakeTickLabels.getTickLabelMaxLength() + SPACE_BTW_MARK_LABEL + LinearScaleTickMarks.MAJOR_TICK_LENGTH; } return size; } /** * Gets the scale tick labels. * * @return the scale tick labels */ public LinearScaleTickLabels getScaleTickLabels() { return tickLabels; } /** * Gets the scale tick marks. * * @return the scale tick marks */ public LinearScaleTickMarks getScaleTickMarks() { return tickMarks; } /** * @return the length of the tick part (without margin) */ public int getTickLength() { return length - 2*getMargin(); } /** * Get the position of the value based on scale. * @param value the value to find its position. Support value out of range. * @param relative return the position relative to the left/bottom bound of the scale if true. * If false, return the absolute position which has the scale bounds counted. * @return position in pixels */ public int getValuePosition(double value, boolean relative) { if(dirty) updateTick(); //coerce to range //value = value < min ? min : (value > max ? max : value); int pixelsToStart =0; if(logScaleEnabled){ if(value <=0) value = min; // throw new IllegalArgumentException( // "Invalid value: value must be greater than 0"); pixelsToStart = (int) ((Math.log10(value) - Math.log10(min))/ (Math.log10(max) - Math.log10(min)) * (length - 2*margin)) + margin; }else pixelsToStart = (int) ((value - min)/(max-min)*(length-2*margin)) + margin; if(relative) { if(orientation == Orientation.HORIZONTAL) return pixelsToStart; else return length - pixelsToStart; } else { if(orientation == Orientation.HORIZONTAL) return pixelsToStart + bounds.x; else return length - pixelsToStart + bounds.y; } } /** * Get the corresponding value on the position of the scale. * @param the position. * @param true if the position is relative to the left/bottom bound of the scale; * False if it is the absolute position. * @return the value corresponding to the position. */ public double getPositionValue(int position, boolean relative) { updateTick(); //coerce to range double min = getRange().getLower(); double max = getRange().getUpper(); int pixelsToStart; double value; if(relative){ if(isHorizontal()) pixelsToStart = position; else pixelsToStart = length - position; } else { if(isHorizontal()) pixelsToStart = position - bounds.x; else pixelsToStart = length + bounds.y - position; } if(isLogScaleEnabled()) value = Math.pow(10, (pixelsToStart - margin)*(Math.log10(max)-Math.log10(min))/(length - 2*margin) + Math.log10(min)); else value = (pixelsToStart - margin)*(max - min)/(length - 2*margin) + min; return value; } public boolean isHorizontal() { return orientation == Orientation.HORIZONTAL; } @Override protected void layout() { super.layout(); updateTick(); Rectangle area = getClientArea(); if(isHorizontal() && getTickLablesSide() == LabelSide.Primary) { tickLabels.setBounds(new Rectangle(area.x, area.y + LinearScaleTickMarks.MAJOR_TICK_LENGTH + SPACE_BTW_MARK_LABEL, area.width, area.height - LinearScaleTickMarks.MAJOR_TICK_LENGTH )); tickMarks.setBounds(area); }else if(isHorizontal() && getTickLablesSide() == LabelSide.Secondary) { tickLabels.setBounds(new Rectangle(area.x, area.y + area.height -LinearScaleTickMarks.MAJOR_TICK_LENGTH - tickLabels.getTickLabelMaxHeight() - SPACE_BTW_MARK_LABEL, area.width, tickLabels.getTickLabelMaxHeight() )); tickMarks.setBounds(new Rectangle(area.x, area.y + area.height - LinearScaleTickMarks.MAJOR_TICK_LENGTH, area.width, LinearScaleTickMarks.MAJOR_TICK_LENGTH )); }else if(getTickLablesSide() == LabelSide.Primary) { tickLabels.setBounds(new Rectangle(area.x + area.width - LinearScaleTickMarks.MAJOR_TICK_LENGTH - tickLabels.getTickLabelMaxLength() -SPACE_BTW_MARK_LABEL, area.y, tickLabels.getTickLabelMaxLength(), area.height)); tickMarks.setBounds(new Rectangle(area.x + area.width - LinearScaleTickMarks.MAJOR_TICK_LENGTH, area.y, LinearScaleTickMarks.MAJOR_TICK_LENGTH, area.height)); }else { tickLabels.setBounds(new Rectangle(area.x+ LinearScaleTickMarks.MAJOR_TICK_LENGTH +SPACE_BTW_MARK_LABEL, area.y, tickLabels.getTickLabelMaxLength(), area.height)); tickMarks.setBounds(new Rectangle(area.x, area.y, LinearScaleTickMarks.MAJOR_TICK_LENGTH, area.height)); } } @Override public void setBounds(Rectangle rect) { if(!bounds.equals(rect)){ setDirty(true); if(isHorizontal()) length = rect.width - getInsets().getWidth(); else length = rect.height - getInsets().getHeight(); } super.setBounds(rect); } /* * @see IAxisTick#setFont(Font) */ @Override public void setFont(Font font) { if (font != null && font.isDisposed()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } tickLabels.setFont(font); super.setFont(font); } /* * @see IAxisTick#setForeground(Color) */ @Override public void setForegroundColor(Color color) { tickMarks.setForegroundColor(color); tickLabels.setForegroundColor(color); super.setForegroundColor(color); } /** * @param orientation the orientation to set */ public void setOrientation(Orientation orientation) { this.orientation = orientation; setDirty(true); revalidate(); } /** * Updates the tick, recalculate all parameters, such as margin, length... */ @Override public void updateTick() { if(isDirty()){ length = isHorizontal() ? getClientArea().width: getClientArea().height; if(length > 2*getMargin()) tickLabels.update(length-2*getMargin()); setDirty(false); } } @Override protected boolean useLocalCoordinates() { return true; } }