/*******************************************************************************
* Copyright (c) 2010 Oak Ridge National Laboratory.
* 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.widgets.figureparts;
import org.eclipse.draw2d.FigureUtilities;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.nebula.visualization.xygraph.linearscale.AbstractScale;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
/**
* Round scale has the tick labels and tick marks on a circle.
* It can be used for any round scale based widget, such meter, gauge, knob etc. <br>
* A round scale is comprised of Scale line, tick labels and tick marks which include
* minor ticks and major ticks. <br>
* The endAngle is on the clockwise side of startAngle. Regardless the startAngle and endAngle,
* the scale will always be drawn in a square. The bounds will be automatically cropped to the
* square with the max possible size.
*
* @author Xihui Chen
*
*/
public class RoundScale extends AbstractScale {
public static final int SPACE_BTW_MARK_LABEL = 1;
/** the scale tick labels */
private RoundScaleTickLabels tickLabels;
/** the scale tick marks */
private RoundScaleTickMarks tickMarks;
/** the length of the whole scale in pixels */
private int lengthInPixels;
/** the length of the whole scale in degrees */
private double lengthInDegrees;
/** The estimated donut width which is used calculate the radius. */
private int estimatedDonutWidth;
/** the start angle of the scale in degrees, which is the angle position of minimum */
private double startAngle = 225;
/** the end angle of the scale in degrees, which is the angle position of maximum.
* The end angle is in the clockwise of startAngle. */
private double endAngle = 315;
/** the radius of the scale */
private int radius;
// private final Font DEFAULT_FONT = CustomMediaFactory.getInstance().getFont(
// CustomMediaFactory.FONT_ARIAL);
/**
* Constructor.
*/
public RoundScale() {
tickLabels = new RoundScaleTickLabels(this);
tickMarks = new RoundScaleTickMarks(this);
setMajorTickMarkStepHint(50);
add(tickMarks);
add(tickLabels);
// setFont(DEFAULT_FONT);
}
private void calcEstimatedDonutWidth() {
estimatedDonutWidth = Math.max(FigureUtilities.getTextExtents(
format(getRange().getLower()),getFont()).width,
FigureUtilities.getTextExtents(format(getRange().getUpper()), getFont()).width)
+ SPACE_BTW_MARK_LABEL + RoundScaleTickMarks.MAJOR_TICK_LENGTH;
}
/**
* @return the length of the whole scale in pixels
*/
public int getLengthInPixels() {
return lengthInPixels;
}
/**
* @return the length of the whole scale in degrees
*/
public double getLengthInDegrees() {
return lengthInDegrees;
}
/**@param pixels the pixels to be converted
* @return the corresponding length in radians
*/
public double convertPixelToRadians(int pixels) {
return lengthInDegrees * (Math.PI/180) * pixels / lengthInPixels;
}
/**
* @return the estimated donut width, which is used to calculate the radius
*/
public int getEstimatedDonutWidth() {
if(isDirty())
calcEstimatedDonutWidth();
return estimatedDonutWidth;
}
@Override
public Dimension getPreferredSize(int wHint, int hHint) {
wHint = Math.min(wHint, hHint);
hHint = wHint;
Dimension size = new Dimension(wHint, hHint);
return size;
}
/**
* Gets the scale tick labels.
*
* @return the scale tick labels
*/
public RoundScaleTickLabels getScaleTickLabels() {
return tickLabels;
}
/**
* Gets the scale tick marks.
*
* @return the scale tick marks
*/
public RoundScaleTickMarks getScaleTickMarks() {
return tickMarks;
}
public double getCoercedValuePosition(double value, boolean relative){
//coerce to range
double min = getRange().getLower();
double max = getRange().getUpper();
if(max>=min)
value = value < min ? min : (value > max ? max : value);
else
value = value > min? min: (value<max? max: value);
return getValuePosition(value, relative);
}
/**
* Get the position of the value in degrees. Which is the angular coordinate in the polar
* coordinate system, whose pole(the origin) is the center of the bounds, whose polar axis
* is from left to right on horizontal if relative is false.
* @param value the value to find its position. It can be value out of range.
* @param relative the polar axs would be counterclockwisely rotated to the endAngle if true.
* @return position in degrees
*/
public double getValuePosition(double value, boolean relative) {
updateTick();
double valuePosition;
if(isLogScaleEnabled()) {
if(value <=0)
throw new IllegalArgumentException(
"Invalid value: value must be greater than 0");
valuePosition = startAngle - ((Math.log10(value) - Math
.log10(min))
/ (Math.log10(max) - Math.log10(min)) * lengthInDegrees);
}
else
valuePosition = startAngle - ((value - min)/(max-min)*lengthInDegrees);
//rotate the axis to endAngle
if(relative)
valuePosition -= endAngle;
if(valuePosition < 0)
valuePosition += 360;
return valuePosition;
}
@Override
protected void layout() {
super.layout();
updateTick();
Rectangle area = getClientArea();
tickLabels.setBounds(area);
tickMarks.setBounds(area);
}
@Override
public void setBounds(Rectangle rect) {
if(!bounds.equals(rect))
setDirty(true);
//get the square in the rect
rect.width = Math.min(rect.width, rect.height);
rect.height = rect.width;
super.setBounds(rect);
}
public void setFont(Font font) {
if (font != null && font.isDisposed()) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
tickLabels.setFont(font);
super.setFont(font);
}
public void setForegroundColor(Color color) {
tickMarks.setForegroundColor(color);
tickLabels.setForegroundColor(color);
}
/**
* Updates the tick, recalculate all parameters, such as margin, length...
*/
public void updateTick() {
if(isDirty()){
//set radius
if(getTickLabelSide() == LabelSide.Primary) {
//set an estimated radius first
radius = bounds.width/2 - getEstimatedDonutWidth();
if(endAngle - startAngle > 0) {
lengthInDegrees = 360 - (endAngle - startAngle);
lengthInPixels =(int) (2*Math.PI*radius*( 1 - (endAngle-startAngle)/360));
}
else {
lengthInDegrees = startAngle - endAngle;
lengthInPixels =(int) (2*Math.PI*radius*((startAngle-endAngle)/360));
}
tickLabels.update(lengthInDegrees, lengthInPixels);
//adjust the radius so the tick labels have enough space to
//be drawn inside the bounds
radius -= tickLabels.getTickLabelMaxOutLength();
}
else
radius = bounds.width/2 - 1;
if(endAngle - startAngle > 0) {
lengthInDegrees = 360 - (endAngle - startAngle);
lengthInPixels =(int) (2*Math.PI*radius*( 1 - (endAngle-startAngle)/360));
}
else {
lengthInDegrees = startAngle - endAngle;
lengthInPixels =(int) (2*Math.PI*radius*((startAngle-endAngle)/360));
}
tickLabels.update(lengthInDegrees, lengthInPixels);
setDirty(false);
}
}
/**
* Updates the tick layout.
protected void updateLayoutData() {
axisTickLabels.updateLayoutData();
axisTickMarks.updateLayoutData();
}
*/
@Override
protected boolean useLocalCoordinates() {
return true;
}
/**
* @param startAngle the startAngle to set
*/
public void setStartAngle(double startAngle) {
this.startAngle = startAngle;
}
/**
* @return the startAngle
*/
public double getStartAngle() {
return startAngle;
}
/**
* @param endAngle the endAngle to set
*/
public void setEndAngle(double endAngle) {
this.endAngle = endAngle;
}
/**
* @return the endAngle
*/
public double getEndAngle() {
return endAngle;
}
/**
* @param radius the radius to set
*/
public void setRadius(int radius) {
this.radius = radius;
}
/**
* @return the radius
*/
public int getRadius() {
return radius;
}
/**
* @return the inner radius for a primary tick label side scale.
*/
public int getInnerRadius() {
updateTick();
return radius;
}
}