1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.scenegraph.g2d.nodes;
14 import java.awt.AlphaComposite;
\r
15 import java.awt.BasicStroke;
\r
16 import java.awt.Color;
\r
17 import java.awt.Font;
\r
18 import java.awt.FontMetrics;
\r
19 import java.awt.Graphics2D;
\r
20 import java.awt.geom.AffineTransform;
\r
21 import java.awt.geom.Rectangle2D;
\r
22 import java.util.Locale;
\r
24 import org.simantics.scenegraph.g2d.G2DNode;
\r
25 import org.simantics.scenegraph.utils.GridUtils;
\r
27 public class RulerNode extends G2DNode {
31 private static final long serialVersionUID = 2490944880914577411L;
\r
34 * FIXME: this is a hack for the map UI that has to be solved some other way.
\r
36 private static final boolean MAP_Y_SCALING = false;
\r
38 private static final Color GRAY = new Color(100, 100, 100);
\r
40 protected Boolean enabled = true;
\r
42 protected double gridSize = 1.0;
\r
44 @SyncField("enabled")
\r
45 public void setEnabled(Boolean enabled) {
\r
46 this.enabled = enabled;
\r
49 @SyncField("gridSize")
\r
50 public void setGridSize(double gridSize) {
\r
51 if (gridSize < 1e-6)
\r
53 this.gridSize = gridSize;
\r
57 public void render(Graphics2D g) {
61 AffineTransform tr = g.getTransform();
62 double scaleX = Math.abs(tr.getScaleX());
63 double scaleY = Math.abs(tr.getScaleY());
64 if (scaleX <= 0 || scaleY <= 0) {
65 // Make sure that we don't end up in an eternal loop below.
68 double offsetX = tr.getTranslateX();
69 double offsetY = tr.getTranslateY();
70 g.setTransform(new AffineTransform());
72 Font rulerFont = new Font("Tahoma", Font.PLAIN, 9);
74 //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
75 g.setStroke(new BasicStroke(1));
76 g.setColor(new Color(0.9f, 0.9f, 0.9f, 0.75f));
78 Rectangle2D bounds = g.getClipBounds();
79 if(bounds == null) return; // FIXME
81 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
83 Rectangle2D vertical = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMinY()+20);
86 Rectangle2D horizontal = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY()+20, bounds.getMinX()+20, bounds.getMaxY());
89 // stepX and stepY should be something between 50 and 100
93 stepX = GridUtils.limitedEvenGridSpacing(stepX, scaleX, 100, gridSize, true);
\r
94 stepY = GridUtils.limitedEvenGridSpacing(stepY, scaleY, 100, gridSize, true);
\r
95 //while(stepX * scaleX > 100) stepX /= 2;
96 //while(stepY * scaleY > 100) stepY /= 2;
98 while(stepX * scaleX < 50) stepX *= 2;
99 while(stepY * scaleY < 50) stepY *= 2;
105 g.setFont(rulerFont);
107 double previousText = -100;
110 for(double x = offsetX%stepX-stepX; x < bounds.getMaxX(); x+=stepX) {
\r
112 String str = formatValue((x-offsetX)/scaleX);
113 FontMetrics fm = g.getFontMetrics();
114 Rectangle2D r = fm.getStringBounds(str, g);
115 if((x-r.getWidth()/2) > previousText) {
116 g.setColor(Color.BLACK);
117 g.drawString(str, (int)(x-r.getWidth()/2), (int)(bounds.getMinY()+1+r.getHeight()));
118 previousText = x+r.getWidth()/2+stepX/4;
122 g.drawLine((int)x, (int)bounds.getMinY()+12, (int)x, (int)bounds.getMinY()+19);
125 for(double x2 = x+stepX/5; x2 < x+stepX; x2+=stepX/5) {
127 g.drawLine((int)x2, (int)bounds.getMinY()+15, (int)x2, (int)bounds.getMinY()+19);
130 for(double x2 = x+stepX/10; x2 < x+stepX; x2+=stepX/5) {
132 g.drawLine((int)x2, (int)bounds.getMinY()+17, (int)x2, (int)bounds.getMinY()+19);
141 for(double y = offsetY%stepY-stepY; y < bounds.getMaxY(); y+=stepY) {
\r
143 double val = (y-offsetY)/scaleY;
\r
145 val = Math.toDegrees(Math.atan(Math.sinh(Math.toRadians(val))));
\r
146 String str = formatValue(val);
147 FontMetrics fm = g.getFontMetrics();
148 Rectangle2D r = fm.getStringBounds(str, g);
149 if(y-1+r.getHeight()/2 > previousText) {
150 g.setColor(Color.BLACK);
151 AffineTransform origTr = g.getTransform();
152 g.translate((int)(bounds.getMinX()), (int)(y+r.getWidth()/2));
153 g.rotate(-Math.PI / 2.0);
154 g.drawString(str, 0, (int)r.getHeight());
155 g.setTransform(origTr);
156 previousText = y-1+r.getHeight();
159 g.drawLine((int)bounds.getMinX()+12, (int)y, (int)bounds.getMinX()+19, (int)y);
162 for(double y2 = y+stepY/5; y2 < y+stepY; y2+=stepY/5) {
164 g.drawLine((int)bounds.getMinX()+15, (int)y2, (int)bounds.getMinX()+19, (int)y2);
167 for(double y2 = y+stepY/10; y2 < y+stepY; y2+=stepY/5) {
169 g.drawLine((int)bounds.getMinX()+17, (int)y2, (int)bounds.getMinX()+19, (int)y2);
178 private static final transient int MAX_DIGITS = 5;
179 private static final transient double EPSILON = 0.01;
180 private static final transient double TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 4);
181 private static final transient String[] SI_UNIT_LARGE_PREFIXES = {
182 "k", "M", "G", "T", "P", "E", "Z", "Y"
185 public static String formatValue(double value) {
186 int magnitude = (int) Math.round(Math.log10(value));
187 //System.out.println("magnitude: " + magnitude + ", " + value);
188 int allowedDecimals = MAX_DIGITS;
189 allowedDecimals -= Math.abs(magnitude);
190 if (allowedDecimals < 0)
193 String valueStr = String.format(Locale.US, "%." + allowedDecimals + "f", value);
194 if (allowedDecimals > 0) {
195 for (int trunc = valueStr.length() - 1; trunc > 0; --trunc) {
196 char ch = valueStr.charAt(trunc);
198 valueStr = valueStr.substring(0, trunc);
201 if (valueStr.charAt(trunc) != '0') {
202 valueStr = valueStr.substring(0, trunc + 1);
206 if (Math.abs(value) + EPSILON > TRIM_THRESHOLD_MAX_VALUE) {
207 // Cut anything beyond a possible decimal dot out since they
208 // should not show anyway. This is a complete hack that tries to
209 // circumvent floating-point inaccuracy problems.
210 int dotIndex = valueStr.lastIndexOf('.');
212 valueStr = valueStr.substring(0, dotIndex);
217 double trimValue = value;
218 if (Math.abs(value)+EPSILON >= TRIM_THRESHOLD_MAX_VALUE) {
219 for (int i = 0; Math.abs(trimValue)+EPSILON >= TRIM_THRESHOLD_MAX_VALUE; ++i) {
220 double trim = trimValue / 1000;
221 if (Math.abs(trim)-EPSILON < TRIM_THRESHOLD_MAX_VALUE) {
222 valueStr = valueStr.substring(0, valueStr.length() - (i + 1) * 3);
223 valueStr += SI_UNIT_LARGE_PREFIXES[i];
230 if (valueStr.equals("-0"))
237 public Rectangle2D getBoundsInLocal() {