1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.g2d.nodes;
14 import java.awt.AlphaComposite;
15 import java.awt.BasicStroke;
16 import java.awt.Color;
18 import java.awt.FontMetrics;
19 import java.awt.Graphics2D;
20 import java.awt.geom.AffineTransform;
21 import java.awt.geom.Rectangle2D;
22 import java.util.Locale;
24 import org.simantics.scenegraph.g2d.G2DNode;
25 import org.simantics.scenegraph.utils.DPIUtil;
26 import org.simantics.scenegraph.utils.GridUtils;
28 public class RulerNode extends G2DNode {
32 private static final long serialVersionUID = 2490944880914577411L;
34 private static final Color GRAY = new Color(100, 100, 100);
36 protected Boolean enabled = true;
38 protected double gridSize = 1.0;
40 protected double rulerSize = 20;
43 public void setEnabled(Boolean enabled) {
44 this.enabled = enabled;
47 @SyncField("gridSize")
48 public void setGridSize(double gridSize) {
51 this.gridSize = gridSize;
54 @SyncField("rulerSize")
55 public void setRulerSize(double rulerSize) {
56 this.rulerSize = rulerSize;
60 public void render(Graphics2D g) {
64 AffineTransform tr = g.getTransform();
65 double scaleX = Math.abs(tr.getScaleX());
66 double scaleY = Math.abs(tr.getScaleY());
67 if (scaleX <= 0 || scaleY <= 0) {
68 // Make sure that we don't end up in an eternal loop below.
71 double offsetX = tr.getTranslateX();
72 double offsetY = tr.getTranslateY();
73 g.setTransform(new AffineTransform());
75 Font rulerFont = new Font("Tahoma", Font.PLAIN, DPIUtil.upscale(9));
77 //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
78 g.setStroke(new BasicStroke(1));
79 g.setColor(new Color(0.9f, 0.9f, 0.9f, 0.75f));
81 Rectangle2D bounds = g.getClipBounds();
82 if(bounds == null) return; // FIXME
84 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
86 int rulerPixelSize = (int) DPIUtil.upscale(rulerSize);
87 int u7 = DPIUtil.upscale(7);
88 int u4 = DPIUtil.upscale(4);
89 int u2 = DPIUtil.upscale(2);
91 Rectangle2D vertical = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMinY()+rulerPixelSize);
94 Rectangle2D horizontal = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY()+rulerPixelSize, bounds.getMinX()+rulerPixelSize, bounds.getMaxY());
97 // stepX and stepY should be something between 50 and 100
101 stepX = GridUtils.limitedEvenGridSpacing(stepX, scaleX, 100, gridSize, true);
102 stepY = GridUtils.limitedEvenGridSpacing(stepY, scaleY, 100, gridSize, true);
103 //while(stepX * scaleX > 100) stepX /= 2;
104 //while(stepY * scaleY > 100) stepY /= 2;
106 while(stepX * scaleX < 50) stepX *= 2;
107 while(stepY * scaleY < 50) stepY *= 2;
113 g.setFont(rulerFont);
114 FontMetrics fm = g.getFontMetrics();
116 double previousText = -100;
119 for(double x = offsetX%stepX-stepX; x < bounds.getMaxX(); x+=stepX) {
120 if(x > rulerPixelSize) {
121 double val = (x-offsetX)/scaleX / getTransform().getScaleX();
122 double modifiedValue = modifyHorizontalValue(val);
123 String str = formatValue(modifiedValue, getMaxDigits());
124 Rectangle2D r = fm.getStringBounds(str, g);
125 if((x-r.getWidth()/2) > previousText) {
126 g.setColor(Color.BLACK);
127 g.drawString(str, (int)(x-r.getWidth()/2), (int)(bounds.getMinY()+1+r.getHeight()));
128 previousText = x+r.getWidth()/2+stepX/4;
132 g.drawLine((int)x, (int)bounds.getMinY()+rulerPixelSize-1-u7, (int)x, (int)bounds.getMinY()+rulerPixelSize-1);
135 for(double x2 = x+stepX/5; x2 < x+stepX; x2+=stepX/5) {
136 if(x2 > rulerPixelSize) {
137 g.drawLine((int)x2, (int)bounds.getMinY()+rulerPixelSize-1-u4, (int)x2, (int)bounds.getMinY()+rulerPixelSize-1);
140 for(double x2 = x+stepX/10; x2 < x+stepX; x2+=stepX/5) {
141 if(x2 > rulerPixelSize) {
142 g.drawLine((int)x2, (int)bounds.getMinY()+rulerPixelSize-1-u2, (int)x2, (int)bounds.getMinY()+rulerPixelSize-1);
150 for(double y = offsetY%stepY-stepY; y < bounds.getMaxY(); y+=stepY) {
151 if(y > rulerPixelSize) {
152 double val = (y-offsetY)/scaleY / getTransform().getScaleY();
153 double modifiedValue = modifyVerticalValue(val);
154 String str = formatValue(modifiedValue, getMaxDigits());
155 Rectangle2D r = fm.getStringBounds(str, g);
156 if(y-1+r.getHeight()/2 > previousText) {
157 g.setColor(Color.BLACK);
158 AffineTransform origTr = g.getTransform();
159 g.translate((int)(bounds.getMinX()), (int)(y+r.getWidth()/2));
160 g.rotate(-Math.PI / 2.0);
161 g.drawString(str, 0, (int)r.getHeight());
162 g.setTransform(origTr);
163 previousText = y-1+r.getHeight();
166 g.drawLine((int)bounds.getMinX()+rulerPixelSize-1-u7, (int)y, (int)bounds.getMinX()+rulerPixelSize-1, (int)y);
169 for(double y2 = y+stepY/5; y2 < y+stepY; y2+=stepY/5) {
170 if(y2 > rulerPixelSize) {
171 g.drawLine((int)bounds.getMinX()+rulerPixelSize-1-u4, (int)y2, (int)bounds.getMinX()+rulerPixelSize-1, (int)y2);
174 for(double y2 = y+stepY/10; y2 < y+stepY; y2+=stepY/5) {
175 if(y2 > rulerPixelSize) {
176 g.drawLine((int)bounds.getMinX()+rulerPixelSize-1-u2, (int)y2, (int)bounds.getMinX()+rulerPixelSize-1, (int)y2);
186 * A method for subclasses to alter the actual X-value of the ruler
189 * @return possibly modified X-value
191 protected double modifyHorizontalValue(double value) {
196 * A method for subclasses to alter the actual Y-value of the ruler
199 * @return possibly modified Y-value
201 protected double modifyVerticalValue(double value) {
205 private static final transient int MAX_DIGITS = 5;
206 private static final transient double EPSILON = 0.01;
207 private static final transient double TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 4);
208 private static final transient String[] SI_UNIT_LARGE_PREFIXES = {
209 "k", "M", "G", "T", "P", "E", "Z", "Y"
212 protected int getMaxDigits() {
216 public static String formatValue(double value, int maxDigits) {
217 int magnitude = (int) Math.round(Math.log10(value));
218 //System.out.println("magnitude: " + magnitude + ", " + value);
219 int allowedDecimals = maxDigits;
220 allowedDecimals -= Math.abs(magnitude);
221 if (allowedDecimals < 0)
224 String valueStr = String.format(Locale.US, "%." + allowedDecimals + "f", value);
225 if (allowedDecimals > 0) {
226 for (int trunc = valueStr.length() - 1; trunc > 0; --trunc) {
227 char ch = valueStr.charAt(trunc);
229 valueStr = valueStr.substring(0, trunc);
232 if (valueStr.charAt(trunc) != '0') {
233 valueStr = valueStr.substring(0, trunc + 1);
237 if (Math.abs(value) + EPSILON > TRIM_THRESHOLD_MAX_VALUE) {
238 // Cut anything beyond a possible decimal dot out since they
239 // should not show anyway. This is a complete hack that tries to
240 // circumvent floating-point inaccuracy problems.
241 int dotIndex = valueStr.lastIndexOf('.');
243 valueStr = valueStr.substring(0, dotIndex);
248 double trimValue = value;
249 if (Math.abs(value)+EPSILON >= TRIM_THRESHOLD_MAX_VALUE) {
250 for (int i = 0; Math.abs(trimValue)+EPSILON >= TRIM_THRESHOLD_MAX_VALUE; ++i) {
251 double trim = trimValue / 1000;
252 if (Math.abs(trim)-EPSILON < TRIM_THRESHOLD_MAX_VALUE) {
253 valueStr = valueStr.substring(0, valueStr.length() - (i + 1) * 3);
254 valueStr += SI_UNIT_LARGE_PREFIXES[i];
261 if (valueStr.equals("-0"))
268 public Rectangle2D getBoundsInLocal() {