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 implements IScaleProvider {
/** 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;
/** if true, then ticks are based on axis dataset indexes */
private boolean ticksIndexBased;
/**
* Constructor.
*/
public LinearScale() {
tickLabels = new LinearScaleTickLabels(this);
tickMarks = new LinearScaleTickMarks(this);
add(tickMarks);
add(tickLabels);
// setFont(XYGraphMediaFactory.getInstance().getFont(
// XYGraphMediaFactory.FONT_ARIAL));
}
/**
* Calculate span of a textual form of object in scale's orientation
* @param obj object
* @return span in pixel
*/
public int calculateSpan(Object obj) {
final Dimension extent = calculateDimension(obj);
if (isHorizontal()) {
return extent.width;
}
return extent.height;
}
/**
* Calculate dimension of a textual form of object
* @param obj object
* @return dimension
*/
@Override
public Dimension calculateDimension(Object obj) {
if (obj == null)
return new Dimension();
if (obj instanceof String)
return FigureUtilities.getTextExtents((String) obj, getFont());
return FigureUtilities.getTextExtents(format(obj), getFont());
}
/**
* @return the length of the whole scale (include margin)
*/
@Override
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
*/
@Override
public int getMargin() {
if(isDirty())
margin = getTicksProvider().getHeadMargin();
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 = fakeTickLabels.getTickLabelMaxHeight()
+ SPACE_BTW_MARK_LABEL + LinearScaleTickMarks.MAJOR_TICK_LENGTH;
} else {
//length = hHint;
fakeTickLabels.update(hHint-2*getMargin());
size.width = fakeTickLabels.getTickLabelMaxLength()
+ SPACE_BTW_MARK_LABEL + LinearScaleTickMarks.MAJOR_TICK_LENGTH;
}
return size;
}
@Override
public ITicksProvider getTicksProvider() {
return tickLabels.getTicksProvider();
}
/**
* 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) {
updateTick();
//coerce to range
//value = value < min ? min : (value > max ? max : value);
Range r = getLocalRange();
double min = r.getLower();
double max = r.getUpper();
int pixelsToStart =0;
if(logScaleEnabled){
if(value <=0)
value = min;
// throw new IllegalArgumentException(
// "Invalid value: value must be greater than 0");
pixelsToStart = (int)Math.round( ((Math.log10(value) - Math.log10(min))/
(Math.log10(max) - Math.log10(min)) * ((double)length - 2d*margin)) + margin);
}else
pixelsToStart = (int)Math.round(((value - min)/(max-min)*((double)length-2d*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
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;
}
Range r = getLocalRange();
double min = r.getLower();
double max = r.getUpper();
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;
}
/**
* Get scaling for axis in terms of pixels/unit
* @return
*/
public double getScaling() {
if (isLogScaleEnabled())
return (Math.log10(max) - Math.log10(min)) / (length - 2 * margin);
return (max - min) / (length - 2 * margin);
}
@Override
public boolean isHorizontal() {
return orientation == Orientation.HORIZONTAL;
}
@Override
protected void layout() {
super.layout();
layoutTicks();
}
protected void layoutTicks() {
updateTick();
Rectangle area = getClientArea();
if (isHorizontal()) {
if (getTickLabelSide() == 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 {
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 (getTickLabelSide() == 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 - LinearScaleTickMarks.LINE_WIDTH,
area.y, LinearScaleTickMarks.MAJOR_TICK_LENGTH + LinearScaleTickMarks.LINE_WIDTH, 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);
}
@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();
}
private Range localRange = null;
/**
* @return range used for axis (not range given by data)
*/
public Range getLocalRange() {
return localRange == null ? super.getRange() : localRange;
}
/**
* 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()) {
Range r = tickLabels.update(length - 2 * getMargin());
if (r != null && !r.equals(range) && !forceRange) {
localRange = r;
} else {
localRange = null;
}
// getMargin();
}
setDirty(false);
}
}
@Override
public Range getScaleRange() {
return getRange();
}
@Override
public boolean isPrimary() {
return getTickLabelSide() == LabelSide.Primary;
}
/**
* Override to provide custom axis labels.
*/
@Override
public double getLabel(double value) {
return value;
}
/**
* @param isTicksIndexBased if true, make ticks based on axis dataset indexes
*/
public void setTicksIndexBased(boolean isTicksIndexBased) {
if (ticksIndexBased != isTicksIndexBased)
tickLabels.setTicksIndexBased(isTicksIndexBased);
ticksIndexBased = isTicksIndexBased;
}
@Override
public boolean isTicksIndexBased() {
return ticksIndexBased;
}
@Override
public boolean areLabelCustomised() {
return false;
}
}