Source Code Cross Referenced for Axis2D.java in  » GIS » GeoTools-2.4.1 » org » geotools » axis » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » GIS » GeoTools 2.4.1 » org.geotools.axis 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         *    GeoTools - OpenSource mapping toolkit
0003:         *    http://geotools.org
0004:         *    (C) 2003-2006, Geotools Project Managment Committee (PMC)
0005:         *    (C) 2000, Institut de Recherche pour le Développement
0006:         *    (C) 1999, Pêches et Océans Canada
0007:         *
0008:         *    This library is free software; you can redistribute it and/or
0009:         *    modify it under the terms of the GNU Lesser General Public
0010:         *    License as published by the Free Software Foundation; either
0011:         *    version 2.1 of the License, or (at your option) any later version.
0012:         *
0013:         *    This library is distributed in the hope that it will be useful,
0014:         *    but WITHOUT ANY WARRANTY; without even the implied warranty of
0015:         *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016:         *    Lesser General Public License for more details.
0017:         */
0018:        package org.geotools.axis;
0019:
0020:        // Graphics and geometry
0021:        import java.awt.Font;
0022:        import java.awt.Graphics2D;
0023:        import java.awt.RenderingHints;
0024:        import java.awt.Shape;
0025:        import java.awt.font.FontRenderContext;
0026:        import java.awt.font.GlyphVector;
0027:        import java.awt.geom.AffineTransform;
0028:        import java.awt.geom.Dimension2D;
0029:        import java.awt.geom.IllegalPathStateException;
0030:        import java.awt.geom.Line2D;
0031:        import java.awt.geom.Point2D;
0032:        import java.awt.geom.Rectangle2D;
0033:
0034:        // Other J2SE dependencies and extensions
0035:        import java.util.Map;
0036:        import java.util.Locale;
0037:        import java.util.Collections;
0038:        import java.util.ConcurrentModificationException;
0039:        import java.io.Serializable;
0040:        import java.beans.PropertyChangeEvent;
0041:        import java.beans.PropertyChangeListener;
0042:        import javax.units.Unit;
0043:
0044:        // OpenGIS dependencies
0045:        import org.opengis.util.Cloneable;
0046:        import org.opengis.referencing.IdentifiedObject;
0047:        import org.opengis.referencing.cs.AxisDirection;
0048:        import org.opengis.referencing.cs.CoordinateSystemAxis;
0049:
0050:        // Geotools dependencies
0051:        import org.geotools.resources.XMath;
0052:        import org.geotools.resources.Utilities;
0053:        import org.geotools.resources.geometry.XDimension2D;
0054:        import org.geotools.referencing.cs.DefaultCoordinateSystemAxis;
0055:        import org.geotools.referencing.operation.matrix.XAffineTransform;
0056:
0057:        /**
0058:         * An axis as a graduated line. {@code Axis2D} objets are really {@link Line2D}
0059:         * objects with a {@link Graduation}. Because axis are {@link Line2D}, they can be
0060:         * located anywhere in a widget with any orientation. Lines are drawn from starting
0061:         * point
0062:         * ({@linkplain #getX1 <var>x<sub>1</sub></var>},{@linkplain #getY1 <var>y<sub>1</sub></var>})
0063:         * to end point
0064:         * ({@linkplain #getX2 <var>x<sub>2</sub></var>},{@linkplain #getY2 <var>y<sub>2</sub></var>}),
0065:         * using a graduation from minimal value {@link Graduation#getMinimum} to maximal
0066:         * value {@link Graduation#getMaximum}.
0067:         *
0068:         * Note the line's coordinates (<var>x<sub>1</sub></var>,<var>y<sub>1</sub></var>) and
0069:         * (<var>x<sub>2</sub></var>,<var>y<sub>2</sub></var>) are completly independant of
0070:         * graduation minimal and maximal values. Line's coordinates should be expressed in
0071:         * some units convenient for rendering, as pixels or point (1/72 of inch). On the
0072:         * opposite, graduation can have any arbitrary units, which is given by
0073:         * {@link Graduation#getUnit}. The static method {@link #createAffineTransform} can
0074:         * be used for mapping logical coordinates to pixels coordinates for an arbitrary
0075:         * pair of {@code Axis2D} objects, which doesn't need to be perpendicular.
0076:         *
0077:         * @since 2.0
0078:         * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/axis/Axis2D.java $
0079:         * @version $Id: Axis2D.java 22710 2006-11-12 18:04:54Z desruisseaux $
0080:         * @author Martin Desruisseaux
0081:         *
0082:         * @see DefaultCoordinateSystemAxis
0083:         * @see AxisDirection
0084:         * @see Graduation
0085:         */
0086:        public class Axis2D extends Line2D implements  Cloneable, Serializable {
0087:            /**
0088:             * Serial number for interoperability with different versions.
0089:             */
0090:            private static final long serialVersionUID = -8396436909942389360L;
0091:
0092:            /**
0093:             * Coordonnées des premier et dernier points de l'axe. Ces coordonnées
0094:             * sont exprimées en "points" (1/72 de pouce), ce qui n'a rien à voir
0095:             * avec les unités de {@link Graduation#getMinimum} et {@link Graduation#getMaximum}.
0096:             */
0097:            private float x1 = 8, y1 = 8, x2 = 648, y2 = 8;
0098:
0099:            /**
0100:             * Longueur des graduations, en points. Chaque graduations sera tracée à partir de
0101:             * {@code [sub]TickStart} (généralement 0) jusqu'à {@code [sub]TickEnd}.
0102:             * Par convention, des valeurs positives désignent l'intérieur du graphique et des
0103:             * valeurs négatives l'extérieur.
0104:             */
0105:            private float tickStart = 0, tickEnd = 9, subTickStart = 0,
0106:                    subTickEnd = 5;
0107:
0108:            /**
0109:             * Indique dans quelle direction se trouve la graduation de l'axe. La valeur -1 indique
0110:             * qu'il faudrait tourner l'axe dans le sens des aiguilles d'une montre pour qu'il soit
0111:             * par-dessus sa graduation. La valeur +1 indique au contraire qu'il faudrait le tourner
0112:             * dans le sens inverse des aiguilles d'une montre pour le même effet.
0113:             */
0114:            private byte relativeCCW = +1;
0115:
0116:            /**
0117:             * Modèle qui contient les minimum, maximum et la graduation de l'axe.
0118:             */
0119:            private final Graduation graduation;
0120:
0121:            /**
0122:             * The coordinate system axis object associated to this axis, or {@code null} if it has
0123:             * not been created yet.
0124:             */
0125:            private transient DefaultCoordinateSystemAxis information;
0126:
0127:            /**
0128:             * Compte le nombre de modifications apportées à l'axe,
0129:             * afin de détecter les changements faits pendant qu'un
0130:             * itérateur balaye la graduation.
0131:             */
0132:            private transient int modCount;
0133:
0134:            /**
0135:             * Indique si {@link #getPathIterator} doit retourner {@link #iterator}.
0136:             * Ce champ prend temporairement la valeur de {@code true} pendant
0137:             * l'exécution de {@link #paint}.
0138:             */
0139:            private transient boolean isPainting;
0140:
0141:            /**
0142:             * Itérateur utilisé pour dessiner l'axe lors du dernier appel de
0143:             * la méthode {@link #paint}. Cet itérateur sera réutilisé autant
0144:             * que possible afin de diminuer le nombre d'objets créés lors de
0145:             * chaque traçage.
0146:             */
0147:            private transient TickPathIterator iterator;
0148:
0149:            /**
0150:             * Coordonnées de la boîte englobant l'axe (<u>sans</u> ses étiquettes
0151:             * de graduation) lors du dernier traçage par la méthode {@link #paint}.
0152:             * Ces coordonnées sont indépendantes de {@link #lastContext} et ont été
0153:             * obtenues sans transformation affine "utilisateur".
0154:             */
0155:            private transient Rectangle2D axisBounds;
0156:
0157:            /**
0158:             * Coordonnées de la boîte englobant les étiquettes de graduations (<u>sans</u>
0159:             * le reste de l'axe) lors du dernier traçage par la méthode {@link #paint}. Ces
0160:             * coordonnées ont été calculées en utilisant {@link #lastContext} mais ont été
0161:             * obtenues sans transformation affine "utilisateur".
0162:             */
0163:            private transient Rectangle2D labelBounds;
0164:
0165:            /**
0166:             * Coordonnées de la boîte englobant la légende de l'axe lors du dernier traçage
0167:             * par la méthode {@link #paint}. Ces coordonnées ont été calculées en utilisant
0168:             * {@link #lastContext} mais ont été obtenues sans transformation affine "utilisateur".
0169:             */
0170:            private transient Rectangle2D legendBounds;
0171:
0172:            /**
0173:             * Dernier objet {@link FontRenderContext} a avoir été
0174:             * utilisé lors du traçage par la méthode {@link #paint}.
0175:             */
0176:            private transient FontRenderContext lastContext;
0177:
0178:            /**
0179:             * Largeur et hauteur maximales des étiquettes de la graduation, ou
0180:             * {@code null} si cette dimension n'a pas encore été déterminée.
0181:             */
0182:            private transient Dimension2D maximumSize;
0183:
0184:            /**
0185:             * A default font to use when no rendering hint were provided for the
0186:             * {@link Graduation#TICK_LABEL_FONT} key. Cached here only for performance.
0187:             */
0188:            private transient Font defaultFont;
0189:
0190:            /**
0191:             * A set of rendering hints for this axis.
0192:             */
0193:            private RenderingHints hints;
0194:
0195:            /**
0196:             * Constructs an axis with a default {@link NumberGraduation}.
0197:             */
0198:            public Axis2D() {
0199:                this (new NumberGraduation(null));
0200:            }
0201:
0202:            /**
0203:             * Constructs an axis with the specified graduation.
0204:             */
0205:            public Axis2D(final Graduation graduation) {
0206:                this .graduation = graduation;
0207:                graduation
0208:                        .addPropertyChangeListener(new PropertyChangeListener() {
0209:                            public void propertyChange(
0210:                                    final PropertyChangeEvent event) {
0211:                                synchronized (Axis2D.this ) {
0212:                                    modCount++;
0213:                                    clearCache();
0214:                                }
0215:                            }
0216:                        });
0217:            }
0218:
0219:            /**
0220:             * Returns the axis's graduation.
0221:             */
0222:            public Graduation getGraduation() {
0223:                return graduation;
0224:            }
0225:
0226:            /**
0227:             * Returns the <var>x</var> coordinate of the start point. By convention,
0228:             * this coordinate should be in pixels or points (1/72 of inch) for proper
0229:             * positionning of ticks and labels.
0230:             *
0231:             * @see #getY1
0232:             * @see #getX2
0233:             * @see #setLine
0234:             */
0235:            public double getX1() {
0236:                return x1;
0237:            }
0238:
0239:            /**
0240:             * Returns the <var>x</var> coordinate of the end point. By convention,
0241:             * this coordinate should be in pixels or points (1/72 of inch) for proper
0242:             * positionning of ticks and labels.
0243:             *
0244:             * @see #getY2
0245:             * @see #getX1
0246:             * @see #setLine
0247:             */
0248:            public double getX2() {
0249:                return x2;
0250:            }
0251:
0252:            /**
0253:             * Returns the <var>y</var> coordinate of the start point. By convention,
0254:             * this coordinate should be in pixels or points (1/72 of inch) for proper
0255:             * positionning of ticks and labels.
0256:             *
0257:             * @see #getX1
0258:             * @see #getY2
0259:             * @see #setLine
0260:             */
0261:            public double getY1() {
0262:                return y1;
0263:            }
0264:
0265:            /**
0266:             * Returns the <var>y</var> coordinate of the end point. By convention,
0267:             * this coordinate should be in pixels or points (1/72 of inch) for proper
0268:             * positionning of ticks and labels.
0269:             *
0270:             * @see #getX2
0271:             * @see #getY1
0272:             * @see #setLine
0273:             */
0274:            public double getY2() {
0275:                return y2;
0276:            }
0277:
0278:            /**
0279:             * Returns the (<var>x</var>,<var>y</var>) coordinates of the start point.
0280:             * By convention, those coordinates should be in pixels or points (1/72 of
0281:             * inch) for proper positionning of ticks and labels.
0282:             */
0283:            public synchronized Point2D getP1() {
0284:                return new Point2D.Float(x1, y1);
0285:            }
0286:
0287:            /**
0288:             * Returns the (<var>x</var>,<var>y</var>) coordinates of the end point.
0289:             * By convention, those coordinates should be in pixels or points (1/72 of
0290:             * inch) for proper positionning of ticks and labels.
0291:             */
0292:            public synchronized Point2D getP2() {
0293:                return new Point2D.Float(x2, y2);
0294:            }
0295:
0296:            /**
0297:             * Returns the axis length. This is the distance between starting point (@link #getP1 P1})
0298:             * and end point ({@link #getP2 P2}). This length is usually measured in pixels or points
0299:             * (1/72 of inch).
0300:             */
0301:            public synchronized double getLength() {
0302:                return XMath.hypot(getX1() - getX2(), getY1() - getY2());
0303:            }
0304:
0305:            /**
0306:             * Returns a bounding box for this axis. The bounding box includes the axis's line
0307:             * ({@link #getP1 P1}) to ({@link #getP2 P2}), the axis's ticks and all labels.
0308:             *
0309:             * @see #getX1
0310:             * @see #getY1
0311:             * @see #getX2
0312:             * @see #getY2
0313:             */
0314:            public synchronized Rectangle2D getBounds2D() {
0315:                if (axisBounds == null) {
0316:                    paint(null); // Force the computation of bounding box size.
0317:                }
0318:                final Rectangle2D bounds = (Rectangle2D) axisBounds.clone();
0319:                if (labelBounds != null)
0320:                    bounds.add(labelBounds);
0321:                if (legendBounds != null)
0322:                    bounds.add(legendBounds);
0323:                return bounds;
0324:            }
0325:
0326:            /**
0327:             * Sets the location of the endpoints of this {@code Axis2D} to the specified
0328:             * coordinates. Coordinates should be in pixels (for screen rendering) or points
0329:             * (for paper rendering). Using points units make it easy to render labels with
0330:             * a raisonable font size, no matter the screen resolution or the axis graduation.
0331:             *
0332:             * @param x1 Coordinate <var>x</var> of starting point.
0333:             * @param y1 Coordinate <var>y</var> of starting point
0334:             * @param x2 Coordinate <var>x</var> of end point.
0335:             * @param y2 Coordinate <var>y</var> of end point.
0336:             * @throws IllegalArgumentException If a coordinate is {@code NaN} or infinite.
0337:             *
0338:             * @see #getX1
0339:             * @see #getY1
0340:             * @see #getX2
0341:             * @see #getY2
0342:             */
0343:            public synchronized void setLine(final double x1, final double y1,
0344:                    final double x2, final double y2)
0345:                    throws IllegalArgumentException {
0346:                final float fx1 = (float) x1;
0347:                AbstractGraduation.ensureFinite("x1", fx1);
0348:                final float fy1 = (float) y1;
0349:                AbstractGraduation.ensureFinite("y1", fy1);
0350:                final float fx2 = (float) x2;
0351:                AbstractGraduation.ensureFinite("x2", fx2);
0352:                final float fy2 = (float) y2;
0353:                AbstractGraduation.ensureFinite("y2", fy2);
0354:                modCount++; // Must be first
0355:                this .x1 = fx1;
0356:                this .y1 = fy1;
0357:                this .x2 = fx2;
0358:                this .y2 = fy2;
0359:                clearCache();
0360:            }
0361:
0362:            /**
0363:             * Returns {@code true} if the axis would have to rotate clockwise in order to
0364:             * overlaps its graduation.
0365:             */
0366:            public boolean isLabelClockwise() {
0367:                return relativeCCW < 0;
0368:            }
0369:
0370:            /**
0371:             * Sets the label's locations relative to this axis. Value {@code true} means
0372:             * that the axis would have to rotate clockwise in order to overlaps its graduation.
0373:             * Value {@code false} means that the axis would have to rotate counter-clockwise
0374:             * in order to overlaps its graduation.
0375:             */
0376:            public synchronized void setLabelClockwise(final boolean c) {
0377:                modCount++; // Must be first
0378:                relativeCCW = c ? (byte) -1 : (byte) +1;
0379:            }
0380:
0381:            /**
0382:             * Returns a default font to use when no rendering hint were provided for
0383:             * the {@link Graduation#TICK_LABEL_FONT} key.
0384:             *
0385:             * @return A default font (never {@code null}).
0386:             */
0387:            private synchronized Font getDefaultFont() {
0388:                if (defaultFont == null) {
0389:                    defaultFont = new Font("SansSerif", Font.PLAIN, 9);
0390:                }
0391:                return defaultFont;
0392:            }
0393:
0394:            /**
0395:             * Returns an iterator object that iterates along the {@code Axis2D} boundary
0396:             * and provides access to the geometry of the shape outline. The shape includes
0397:             * the axis line, graduation and labels. If an optional {@link AffineTransform}
0398:             * is specified, the coordinates returned in the iteration are transformed accordingly.
0399:             */
0400:            public java.awt.geom.PathIterator getPathIterator(
0401:                    final AffineTransform transform) {
0402:                return getPathIterator(transform, java.lang.Double.NaN);
0403:            }
0404:
0405:            /**
0406:             * Returns an iterator object that iterates along the {@code Axis2D} boundary
0407:             * and provides access to the geometry of the shape outline. The shape includes
0408:             * the axis line, graduation and labels. If an optional {@link AffineTransform}
0409:             * is specified, the coordinates returned in the iteration are transformed accordingly.
0410:             */
0411:            public synchronized java.awt.geom.PathIterator getPathIterator(
0412:                    final AffineTransform transform, final double flatness) {
0413:                if (isPainting) {
0414:                    if (iterator != null) {
0415:                        iterator.rewind(transform);
0416:                    } else {
0417:                        iterator = new TickPathIterator(transform);
0418:                    }
0419:                    return iterator;
0420:                }
0421:                return new PathIterator(transform, flatness);
0422:            }
0423:
0424:            /**
0425:             * Draw this axis in the specified graphics context. This method is equivalents
0426:             * to {@code Graphics2D.draw(this)}.  However, this method may be slightly
0427:             * faster and produce better quality output.
0428:             *
0429:             * @param graphics The graphics context to use for drawing.
0430:             */
0431:            public synchronized void paint(final Graphics2D graphics) {
0432:                if (!(getLength() > 0)) {
0433:                    return;
0434:                }
0435:                /*
0436:                 * Initialise l'itérateur en appelant 'init' (contrairement à 'getPathIterator'
0437:                 * qui n'appelle que 'rewind') pour des résultats plus rapides et plus constants.
0438:                 */
0439:                if (iterator != null) {
0440:                    iterator.init(null);
0441:                } else {
0442:                    iterator = new TickPathIterator(null);
0443:                }
0444:                final TickPathIterator iterator = this .iterator;
0445:                final boolean sameContext;
0446:                final Shape clip;
0447:                if (graphics != null) {
0448:                    clip = graphics.getClip();
0449:                    iterator.setFontRenderContext(graphics
0450:                            .getFontRenderContext());
0451:                    iterator.setRenderingHint(graphics,
0452:                            Graduation.AXIS_TITLE_FONT);
0453:                    iterator.setRenderingHint(graphics,
0454:                            Graduation.TICK_LABEL_FONT);
0455:                    final FontRenderContext context = iterator
0456:                            .getFontRenderContext();
0457:                    sameContext = clip != null && context.equals(lastContext);
0458:                } else {
0459:                    clip = null;
0460:                    sameContext = false;
0461:                    iterator.setFontRenderContext(null);
0462:                }
0463:                /*
0464:                 * Calcule (si ce n'était pas déjà fait) les coordonnées d'un rectangle qui englobe l'axe et
0465:                 * sa graduation (mais sans les étiquettes de graduation).  Cette information nous permettra
0466:                 * de vérifier s'il est vraiment nécessaire de redessiner l'axe en vérifiant s'il intercepte
0467:                 * avec le "clip" du graphique.
0468:                 */
0469:                if (axisBounds == null) {
0470:                    axisBounds = new Rectangle2D.Float(Math.min(x1, x2), Math
0471:                            .min(y1, y2), Math.abs(x2 - x1), Math.abs(y2 - y1));
0472:                    while (!iterator.isDone()) {
0473:                        axisBounds.add(iterator.point);
0474:                        iterator.next();
0475:                    }
0476:                }
0477:                /*
0478:                 * Dessine l'axe et ses barres de graduation (mais sans les étiquettes).
0479:                 */
0480:                if (graphics != null) {
0481:                    if (clip == null || clip.intersects(axisBounds))
0482:                        try {
0483:                            isPainting = true;
0484:                            graphics.draw(this );
0485:                        } finally {
0486:                            isPainting = false;
0487:                        }
0488:                }
0489:                /*
0490:                 * Dessine les étiquettes de graduations. Ce bloc peut etre exécuté même si
0491:                 * 'graphics' est nul.  Dans ce cas, les étiquettes ne seront pas dessinées
0492:                 * mais le calcul de l'espace qu'elles occupent sera quand même effectué.
0493:                 */
0494:                if (!sameContext || labelBounds == null
0495:                        || clip.intersects(labelBounds) || maximumSize == null) {
0496:                    Rectangle2D lastLabelBounds = labelBounds = null;
0497:                    double maxWidth = 0;
0498:                    double maxHeight = 0;
0499:                    iterator.rewind();
0500:                    while (iterator.hasNext()) {
0501:                        if (iterator.isMajorTick()) {
0502:                            final GlyphVector glyphs = iterator
0503:                                    .currentLabelGlyphs();
0504:                            final Rectangle2D bounds = iterator
0505:                                    .currentLabelBounds();
0506:                            if (glyphs != null && bounds != null) {
0507:                                if (lastLabelBounds == null
0508:                                        || !lastLabelBounds.intersects(bounds)) {
0509:                                    if (graphics != null
0510:                                            && (clip == null || clip
0511:                                                    .intersects(bounds))) {
0512:                                        graphics.drawGlyphVector(glyphs,
0513:                                                (float) bounds.getMinX(),
0514:                                                (float) bounds.getMaxY());
0515:                                    }
0516:                                    lastLabelBounds = bounds;
0517:                                    final double width = bounds.getWidth();
0518:                                    final double height = bounds.getHeight();
0519:                                    if (width > maxWidth)
0520:                                        maxWidth = width;
0521:                                    if (height > maxHeight)
0522:                                        maxHeight = height;
0523:                                }
0524:                                if (labelBounds == null) {
0525:                                    labelBounds = new Rectangle2D.Float();
0526:                                    labelBounds.setRect(bounds);
0527:                                } else {
0528:                                    labelBounds.add(bounds);
0529:                                }
0530:                            }
0531:                        }
0532:                        iterator.nextMajor();
0533:                    }
0534:                    maximumSize = new XDimension2D.Float((float) maxWidth,
0535:                            (float) maxHeight);
0536:                }
0537:                /*
0538:                 * Ecrit la légende de l'axe. Ce bloc peut etre exécuté même si
0539:                 * 'graphics' est nul.  Dans ce cas, la légende ne sera pas écrite
0540:                 * mais le calcul de l'espace qu'elle occupe sera quand même effectué.
0541:                 */
0542:                if (!sameContext || legendBounds == null
0543:                        || clip.intersects(legendBounds)) {
0544:                    final String title = graduation.getTitle(true);
0545:                    if (title != null) {
0546:                        final Font font = iterator.getTitleFont();
0547:                        final GlyphVector glyphs = font.createGlyphVector(
0548:                                iterator.getFontRenderContext(), title);
0549:                        final AffineTransform rotatedTr = new AffineTransform();
0550:                        final Rectangle2D bounds = iterator.centerAxisLabel(
0551:                                glyphs.getVisualBounds(), rotatedTr,
0552:                                maximumSize);
0553:                        if (graphics != null) {
0554:                            final AffineTransform currentTr = graphics
0555:                                    .getTransform();
0556:                            try {
0557:                                graphics.transform(rotatedTr);
0558:                                graphics.drawGlyphVector(glyphs, (float) bounds
0559:                                        .getMinX(), (float) bounds.getMaxY());
0560:                            } finally {
0561:                                graphics.setTransform(currentTr);
0562:                            }
0563:                        }
0564:                        legendBounds = XAffineTransform.transform(rotatedTr,
0565:                                bounds, bounds);
0566:                    }
0567:                }
0568:                lastContext = iterator.getFontRenderContext();
0569:            }
0570:
0571:            /**
0572:             * Returns the value of a single preference for the rendering algorithms. Hint categories
0573:             * include controls for label fonts and colors. Some of the keys and their associated values
0574:             * are defined in the {@link Graduation} interface.
0575:             *
0576:             * @param  key The key corresponding to the hint to get. 
0577:             * @return An object representing the value for the specified hint key, or {@code null}
0578:             *         if no value is associated to the specified key.
0579:             *
0580:             * @see Graduation#TICK_LABEL_FONT
0581:             * @see Graduation#AXIS_TITLE_FONT
0582:             */
0583:            public synchronized Object getRenderingHint(
0584:                    final RenderingHints.Key key) {
0585:                return (hints != null) ? hints.get(key) : null;
0586:            }
0587:
0588:            /**
0589:             * Sets the value of a single preference for the rendering algorithms. Hint categories
0590:             * include controls for label fonts and colors.  Some of the keys and their associated
0591:             * values are defined in the {@link Graduation} interface.
0592:             *
0593:             * @param key The key of the hint to be set.
0594:             * @param value The value indicating preferences for the specified hint category.
0595:             *              A {@code null} value removes any hint for the specified key.
0596:             *
0597:             * @see Graduation#TICK_LABEL_FONT
0598:             * @see Graduation#AXIS_TITLE_FONT
0599:             */
0600:            public synchronized void setRenderingHint(
0601:                    final RenderingHints.Key key, final Object value) {
0602:                modCount++;
0603:                if (value != null) {
0604:                    if (hints == null) {
0605:                        hints = new RenderingHints(key, value);
0606:                        clearCache();
0607:                    } else {
0608:                        if (!value.equals(hints.put(key, value))) {
0609:                            clearCache();
0610:                        }
0611:                    }
0612:                } else if (hints != null) {
0613:                    if (hints.remove(key) != null) {
0614:                        clearCache();
0615:                    }
0616:                    if (hints.isEmpty()) {
0617:                        hints = null;
0618:                    }
0619:                }
0620:            }
0621:
0622:            /**
0623:             * Efface la cache interne. Cette méthode doit être appelée
0624:             * chaque fois que des propriétés de l'axe ont changées.
0625:             */
0626:            private void clearCache() {
0627:                axisBounds = null;
0628:                labelBounds = null;
0629:                legendBounds = null;
0630:                maximumSize = null;
0631:                information = null;
0632:            }
0633:
0634:            /**
0635:             * Returns a string representation of this axis.
0636:             */
0637:            public String toString() {
0638:                final StringBuffer buffer = new StringBuffer(Utilities
0639:                        .getShortClassName(this ));
0640:                buffer.append("[\"");
0641:                buffer.append(graduation.getTitle(true));
0642:                buffer.append("\"]");
0643:                return buffer.toString();
0644:            }
0645:
0646:            /**
0647:             * Returns this axis name and direction. Information include a name (usually the
0648:             * {@linkplain Graduation#getTitle graduation title}) and an direction. The direction is usually
0649:             * {@linkplain AxisDirection#DISPLAY_UP up} or {@linkplain AxisDirection#DISPLAY_DOWN down}
0650:             * for vertical axis, {@linkplain AxisDirection#DISPLAY_RIGHT right} or
0651:             * {@linkplain AxisDirection#DISPLAY_LEFT left} for horizontal axis, or
0652:             * {@linkplain AxisDirection#OTHER other} otherwise.
0653:             */
0654:            public synchronized DefaultCoordinateSystemAxis toCoordinateSystemAxis() {
0655:                if (information == null) {
0656:                    String abbreviation = "z";
0657:                    AxisDirection direction = AxisDirection.OTHER;
0658:                    if (x1 == x2) {
0659:                        if (y1 < y2) {
0660:                            direction = AxisDirection.DISPLAY_UP;
0661:                        } else if (y1 > y2) {
0662:                            direction = AxisDirection.DISPLAY_DOWN;
0663:                        }
0664:                        abbreviation = "y";
0665:                    } else if (y1 == y2) {
0666:                        if (x1 < x2) {
0667:                            direction = AxisDirection.DISPLAY_RIGHT;
0668:                        } else if (x1 > x2) {
0669:                            direction = AxisDirection.DISPLAY_LEFT;
0670:                        }
0671:                        abbreviation = "x";
0672:                    }
0673:                    information = new DefaultCoordinateSystemAxis(Collections
0674:                            .singletonMap(IdentifiedObject.NAME_KEY, graduation
0675:                                    .getTitle(false)), abbreviation, direction,
0676:                            graduation.getUnit());
0677:                }
0678:                return information;
0679:            }
0680:
0681:            /**
0682:             * Creates an affine transform mapping logical to pixels coordinates for a pair
0683:             * of axis. The affine transform will maps coordinates in the following way:
0684:             *
0685:             * <ul>
0686:             *   <li>For each input coordinates (<var>x</var>,<var>y</var>), the <var>x</var> and
0687:             *       <var>y</var> values are expressed in the same units than the {@code xAxis}
0688:             *       and {@code yAxis} graduations, respectively.</li>
0689:             *   <li>The output point is the pixel's coordinates for the (<var>x</var>,<var>y</var>)
0690:             *       values. Changing the <var>x</var> value move the pixel location in parallel with
0691:             *       the {@code xAxis}, which may or may not be horizontal. Changing the <var>y</var>
0692:             *       value move the pixel location in parallel with the {@code yAxis}, which may or
0693:             *       may not be vertical.</li>
0694:             * </ul>
0695:             *
0696:             * @param  xAxis The <var>x</var> axis. This axis doesn't have to be horizontal;
0697:             *               it can have any orientation, including vertical.
0698:             * @param  yAxis The <var>y</var> axis. This axis doesn't have to be vertical;
0699:             *               it can have any orientation, including horizontal.
0700:             * @return An affine transform mapping logical to pixels coordinates.
0701:             */
0702:            public static AffineTransform createAffineTransform(
0703:                    final Axis2D xAxis, final Axis2D yAxis) {
0704:                /*   x
0705:                 *  |
0706:                 *  |\
0707:                 *  |  \ P     Soit:       X  :  l'axe des <var>x</var> du graphique.
0708:                 *  |   |                  Y  :  l'axe des <var>y</var> du graphique.
0709:                 *   \  |                  P  :  un point à placer sur le graphique.
0710:                 *     \|             (Px,Py) :  les composantes du point P selon les axes x et y.
0711:                 *       \ y          (Pi,Pj) :  les composantes du point P en coordonnées "pixels".
0712:                 *
0713:                 * Désignons par <b>ex</b> et <b>ey</b> des vecteurs unitaires dans la direction de l'axe des
0714:                 * <var>x</var> et l'axe des <var>y</var> respectivement. Désignons par <b>i</b> et <b>j</b>
0715:                 * des vecteurs unitaires vers le droite et vers le haut de l'écran respectivement. On peut
0716:                 * décomposer les vecteurs unitaires <b>ex</b> et <b>ey</b> par:
0717:                 *
0718:                 *          ex = exi*i + exj*j
0719:                 *          ey = eyi*i + eyj*j
0720:                 * Donc,    P  = Px*ex + Py*ey   =   (Px*exi+Py*eyi)*i + (Px*exj + Py*eyj)*j
0721:                 *
0722:                 * Cette relation ne s'applique que si les deux systèmes de coordonnées (xy et ij) ont
0723:                 * la même origine. En pratique, ce ne sera pas le cas. Il faut donc compliquer un peu:
0724:                 *
0725:                 *      Pi = (Px-Ox)*exi+(Py-Oy)*eyi + Oi        où (Ox,Oy) sont les minimums des axes des x et y.
0726:                 *      Pj = (Px-Ox)*exj+(Py-Oy)*eyj + Oj           (Oi,Oj) est l'origine du système d'axe ij.
0727:                 *
0728:                 * [ Pi ]   [ exi   eyi   Oi-(Ox*exi+Oy*eyi) ][ Px ]     [ exi*Px + eyi*Py + Oi-(Ox*exi+Oy*oyi) ]
0729:                 * [ Pj ] = [ exj   eyj   Oj-(Ox*exj+Oy*eyj) ][ Py ]  =  [ exj*Px + eyj*Py + Oj-(Ox*exj+Oy*oyj) ]
0730:                 * [  1 ]   [  0    0              1         ][  1 ]     [                   1                  ]
0731:                 */
0732:                synchronized (xAxis) {
0733:                    synchronized (yAxis) {
0734:                        final Graduation mx = xAxis.getGraduation();
0735:                        final Graduation my = yAxis.getGraduation();
0736:                        double ox = mx.getRange();
0737:                        double oy = my.getRange();
0738:                        double exi = ((double) xAxis.getX2() - (double) xAxis
0739:                                .getX1())
0740:                                / ox;
0741:                        double exj = ((double) xAxis.getY2() - (double) xAxis
0742:                                .getY1())
0743:                                / ox;
0744:                        double eyi = ((double) yAxis.getX2() - (double) yAxis
0745:                                .getX1())
0746:                                / oy;
0747:                        double eyj = ((double) yAxis.getY2() - (double) yAxis
0748:                                .getY1())
0749:                                / oy;
0750:                        ox = mx.getMinimum();
0751:                        oy = my.getMinimum();
0752:                        return new AffineTransform(exi, exj, eyi, eyj, xAxis.x1
0753:                                - (ox * exi + oy * eyi), yAxis.y1
0754:                                - (ox * exj + oy * eyj));
0755:                    }
0756:                }
0757:            }
0758:
0759:            ////////////////////////////////////////////////////////////////////////////////////////////
0760:            ////////                                                                            ////////
0761:            ////////                            TICK AND PATH ITERATORS                         ////////
0762:            ////////                                                                            ////////
0763:            ////////////////////////////////////////////////////////////////////////////////////////////
0764:
0765:            /**
0766:             * Iterates along the graduation ticks and provides access to the graduation values. Each
0767:             * {@code Axis2D.TickIterator} object traverses the graduation of the unclosing {@link Axis2D}
0768:             * object independently from any other {@link TickIterator} objects in use at the same time.
0769:             * If a change occurs in the underlying {@link Axis2D} object during the iteration, then
0770:             * {@link #refresh} must be invoked in order to reset the iterator as if a new instance was
0771:             * created. Except for {@link #refresh} method, using the iterator after a change in the
0772:             * underlying {@link Axis2D} may thrown a {@link ConcurrentModificationException}.
0773:             *
0774:             * @since 2.0
0775:             * @version $Id: Axis2D.java 22710 2006-11-12 18:04:54Z desruisseaux $
0776:             * @author Martin Desruisseaux
0777:             */
0778:            public class TickIterator implements  org.geotools.axis.TickIterator {
0779:                /**
0780:                 * The underyling tick iterator.
0781:                 */
0782:                private org.geotools.axis.TickIterator iterator;
0783:
0784:                /**
0785:                 * A copy of {@link Axis2D#hints} rendering hints. A copy is required because some hints
0786:                 * (especially {@link Graduation#VISUAL_AXIS_LENGTH} and
0787:                 * {@link Graduation#VISUAL_TICK_SPACING}) are going to be overwriten. This set may also
0788:                 * contains additional hints provided by {@link Graphics2D} in the {@link Axis2D#paint}
0789:                 * method. This object will never be {@code null}.
0790:                 */
0791:                private final RenderingHints hints;
0792:
0793:                /**
0794:                 * {@code scaleX} and {@code scaleY} are used for scaling
0795:                 * logical coordinates to pixel coordinates. Those scale factors
0796:                 * <strong>must</strong> be the same than the one that appears in
0797:                 * {@link Axis2D#createAffineTransform}.
0798:                 */
0799:                private double scaleX, scaleY;
0800:
0801:                /**
0802:                 * {@code (tickX, tickY)} is a unitary vector perpendicular to the axis.
0803:                 */
0804:                private double tickX, tickY;
0805:
0806:                /**
0807:                 * The minimum value {@link Graduation#getMinimum}.
0808:                 * This value is copied here for faster access.
0809:                 */
0810:                private double minimum;
0811:
0812:                /**
0813:                 * Value returned by the last call to {@link #currentLabel}.
0814:                 * This value is cached here in order to avoid that
0815:                 * {@link #getGlyphVector} compute it again.
0816:                 */
0817:                private transient String label;
0818:
0819:                /**
0820:                 * Value returned by the last call to {@link #getGlyphVector}. This
0821:                 * value is cached here in order to avoir that {@link #getBounds}
0822:                 * compute it again.
0823:                 */
0824:                private transient GlyphVector glyphs;
0825:
0826:                /**
0827:                 * The font to use for rendering tick. If a rendering hint was provided for the
0828:                 * {@link Graduation#TICK_LABEL_FONT} key, then the value is used as the font.
0829:                 * Otherwise, a default font is created and used.
0830:                 */
0831:                private transient Font font;
0832:
0833:                /**
0834:                 * The font context from {@link Graphics2D#getFontContext},
0835:                 * or {@code null} for a default one.
0836:                 */
0837:                private transient FontRenderContext fontContext;
0838:
0839:                /**
0840:                 * Value of {@link Axis2D#modCount} when {@link #init} was
0841:                 * last invoked. This value is used in order to detect
0842:                 * changes to the underlying {@link Axis2D} during iteration.
0843:                 */
0844:                private transient int modCount;
0845:
0846:                /**
0847:                 * Construct an iterator.
0848:                 *
0849:                 * @param fontContext Information needed to correctly measure text, or
0850:                 *        {@code null} if unknow. This object is usually given by
0851:                 *        {@link Graphics2D#getFontRenderContext}.
0852:                 */
0853:                public TickIterator(final FontRenderContext fontContext) {
0854:                    this .hints = new RenderingHints((Map) Axis2D.this .hints);
0855:                    this .fontContext = fontContext;
0856:                    refresh();
0857:                }
0858:
0859:                /**
0860:                 * Copy a rendering hints from the specified {@link Graphics2D}, providing that
0861:                 * it is not already defined.
0862:                 */
0863:                final void setRenderingHint(final Graphics2D graphics,
0864:                        final RenderingHints.Key key) {
0865:                    if (hints.get(key) == null) {
0866:                        final Object value = graphics.getRenderingHint(key);
0867:                        if (value != null) {
0868:                            hints.put(key, value);
0869:                        }
0870:                    }
0871:                }
0872:
0873:                /**
0874:                 * Tests if the iterator has more ticks.
0875:                 */
0876:                public boolean hasNext() {
0877:                    return iterator.hasNext();
0878:                }
0879:
0880:                /**
0881:                 * Tests if the current tick is a major one.
0882:                 *
0883:                 * @return {@code true} if current tick is a major tick,
0884:                 *         or {@code false} if it is a minor tick.
0885:                 */
0886:                public boolean isMajorTick() {
0887:                    return iterator.isMajorTick();
0888:                }
0889:
0890:                /**
0891:                 * Returns the position where to draw the current tick.  The position is scaled
0892:                 * from the graduation's minimum to maximum.    This is usually the same number
0893:                 * than {@link #currentValue}. The mean exception is for logarithmic graduation,
0894:                 * in which the tick position is not proportional to the tick value.
0895:                 */
0896:                public double currentPosition() {
0897:                    return iterator.currentPosition();
0898:                }
0899:
0900:                /**
0901:                 * Returns the value for current tick. The current tick may be major or minor.
0902:                 */
0903:                public double currentValue() {
0904:                    return iterator.currentValue();
0905:                }
0906:
0907:                /**
0908:                 * Returns the coordinates of the intersection point between current tick
0909:                 * and the underlying axis. Units are the same than axis start point
0910:                 * ({@linkplain #getX1 <var>x<sub>1</sub></var>},{@linkplain #getY1 <var>y<sub>1</sub></var>})
0911:                 * and end point
0912:                 * ({@linkplain #getX2 <var>x<sub>2</sub></var>},{@linkplain #getY2 <var>y<sub>2</sub></var>}).
0913:                 * This is usually pixels.
0914:                 *
0915:                 * @param  dest A destination point that stores the intersection coordinates,
0916:                 *         or {@code null} to create a new {@link Point2D} object.
0917:                 * @return {@code dest}, or a new {@link Point2D} object if {@code dest} was null.
0918:                 */
0919:                public Point2D currentPosition(final Point2D dest) {
0920:                    final double position = currentPosition() - minimum;
0921:                    final double x = position * scaleX + getX1();
0922:                    final double y = position * scaleY + getY1();
0923:                    ensureValid();
0924:                    if (dest != null) {
0925:                        dest.setLocation(x, y);
0926:                        return dest;
0927:                    }
0928:                    return new Point2D.Float((float) x, (float) y);
0929:                }
0930:
0931:                /**
0932:                 * Returns the coordinates of the current tick.
0933:                 * Units are the same than axis start point
0934:                 * ({@linkplain #getX1 <var>x<sub>1</sub></var>},{@linkplain #getY1 <var>y<sub>1</sub></var>})
0935:                 * and end point
0936:                 * ({@linkplain #getX2 <var>x<sub>2</sub></var>},{@linkplain #getY2 <var>y<sub>2</sub></var>}).
0937:                 * This is usually pixels.
0938:                 *
0939:                 * @param  dest A destination line that stores the current tick coordinates,
0940:                 *         or {@code null} to create a new {@link Line2D} object.
0941:                 * @return {@code dest}, or a new {@link Line2D} object if {@code dest} was null.
0942:                 */
0943:                public Line2D currentTick(final Line2D dest) {
0944:                    final boolean isMajorTick = isMajorTick();
0945:                    final double position = currentPosition() - minimum;
0946:                    final double x = position * scaleX + getX1();
0947:                    final double y = position * scaleY + getY1();
0948:                    final double s1 = isMajorTick ? tickStart : subTickStart;
0949:                    final double s2 = isMajorTick ? tickEnd : subTickEnd;
0950:                    final double x1 = x + tickX * s1;
0951:                    final double y1 = y + tickY * s1;
0952:                    final double x2 = x + tickX * s2;
0953:                    final double y2 = y + tickY * s2;
0954:                    ensureValid();
0955:                    if (dest != null) {
0956:                        dest.setLine(x1, y1, x2, y2);
0957:                        return dest;
0958:                    }
0959:                    return new Line2D.Float((float) x1, (float) y1, (float) x2,
0960:                            (float) y2);
0961:                }
0962:
0963:                /**
0964:                 * Returns the label for current tick. This method is usually invoked
0965:                 * only for major ticks, but may be invoked for minor ticks as well.
0966:                 * This method returns {@code null} if it can't produces a label
0967:                 * for current tick.
0968:                 */
0969:                public String currentLabel() {
0970:                    if (label == null) {
0971:                        label = iterator.currentLabel();
0972:                    }
0973:                    return label;
0974:                }
0975:
0976:                /**
0977:                 * Returns the label for current tick as a glyphs vector. This method is used
0978:                 * together with {@link #currentLabelBounds} for labels rendering. <strong>Do
0979:                 * not change the returned {@link GlyphVector}</strong>, since the glyphs vector
0980:                 * is not cloned for performance raisons. This method returns {@code null}
0981:                 * if it can't produces a glyph vector for current tick.
0982:                 */
0983:                public GlyphVector currentLabelGlyphs() {
0984:                    if (glyphs == null) {
0985:                        final String label = currentLabel();
0986:                        if (label != null) {
0987:                            glyphs = getTickFont().createGlyphVector(
0988:                                    getFontRenderContext(), label);
0989:                        }
0990:                    }
0991:                    return glyphs;
0992:                }
0993:
0994:                /**
0995:                 * Returns a bounding vector for the current tick label.
0996:                 * Units are the same than axis start point
0997:                 * ({@linkplain #getX1 <var>x<sub>1</sub></var>},{@linkplain #getY1 <var>y<sub>1</sub></var>})
0998:                 * and end point
0999:                 * ({@linkplain #getX2 <var>x<sub>2</sub></var>},{@linkplain #getY2 <var>y<sub>2</sub></var>}).
1000:                 * This is usually pixels. This method can be used as in the example below:
1001:                 *
1002:                 * <pre>
1003:                 * {@link Axis2D.TickIterator} iterator = axis.new {@link Axis2D.TickIterator TickIterator}(null};
1004:                 * while (iterator.{@link #hasNext() hasNext()}) {
1005:                 *     {@link GlyphVector} glyphs = iterator.{@link Axis2D.TickIterator#currentLabelGlyphs() currentLabelGlyphs()};
1006:                 *     {@link Rectangle2D} bounds = iterator.{@link Axis2D.TickIterator#currentLabelBounds() currentLabelBounds()};
1007:                 *     graphics.drawGlyphVector(glyphs, (float)bounds.getMinX(), (float)bounds.getMaxY());
1008:                 *     iterator.{@link #next() next()};
1009:                 * }
1010:                 * </pre>
1011:                 *
1012:                 * This method returns {@code null} if it can't compute bounding box for current tick.
1013:                 */
1014:                public Rectangle2D currentLabelBounds() {
1015:                    final GlyphVector glyphs = currentLabelGlyphs();
1016:                    if (glyphs == null) {
1017:                        return null;
1018:                    }
1019:                    final Rectangle2D bounds = glyphs.getVisualBounds();
1020:                    final double height = bounds.getHeight();
1021:                    final double width = bounds.getWidth();
1022:                    final double tickStart = (0.5 * height)
1023:                            - Math.min(Axis2D.this .tickStart, 0);
1024:                    final double position = currentPosition() - minimum;
1025:                    final double x = position * scaleX + getX1();
1026:                    final double y = position * scaleY + getY1();
1027:                    bounds.setRect(x - (1 + tickX) * (0.5 * width) - tickX
1028:                            * tickStart, y + (1 - tickY) * (0.5 * height)
1029:                            - tickY * tickStart - height, width, height);
1030:                    ensureValid();
1031:                    return bounds;
1032:                }
1033:
1034:                /**
1035:                 * Returns the font for tick labels. This is the font used for drawing the tick label
1036:                 * formatted by {@link TickIterator#currentLabel}.
1037:                 *
1038:                 * @return The font (never {@code null}).
1039:                 */
1040:                private Font getTickFont() {
1041:                    if (font == null) {
1042:                        Object candidate = hints
1043:                                .get(Graduation.TICK_LABEL_FONT);
1044:                        if (candidate instanceof  Font) {
1045:                            font = (Font) candidate;
1046:                        } else {
1047:                            font = getDefaultFont();
1048:                        }
1049:                    }
1050:                    return font;
1051:                }
1052:
1053:                /**
1054:                 * Returns the font for axis title. This is the font used for drawing the title
1055:                 * formatted by {@link Graduation#getTitle}.
1056:                 *
1057:                 * @return The font (never {@code null}).
1058:                 */
1059:                final Font getTitleFont() {
1060:                    Object candidate = hints.get(Graduation.AXIS_TITLE_FONT);
1061:                    if (candidate instanceof  Font) {
1062:                        return (Font) candidate;
1063:                    }
1064:                    final Font font = getTickFont();
1065:                    return font.deriveFont(Font.BOLD, font.getSize2D()
1066:                            * (12f / 9));
1067:                }
1068:
1069:                /**
1070:                 * Retourne un rectangle centré vis-à-vis l'axe. Les coordonnées de ce rectangle seront
1071:                 * les mêmes que celles de l'axe, habituellement des pixels ou des points (1/72 de pouce).
1072:                 * Cette méthode s'utilise typiquement comme suit:
1073:                 *
1074:                 * <pre>
1075:                 * Graphics2D           graphics = ...
1076:                 * FontRenderContext fontContext = graphics.getFontRenderContext();
1077:                 * TickIterator         iterator = axis.new TickIterator(graphics.getFontRenderContext());
1078:                 * Font                     font = iterator.getTitleFont();
1079:                 * String                  title = axis.getGraduation().getTitle(true);
1080:                 * GlyphVector            glyphs = font.createGlyphVector(fontContext, title);
1081:                 * Rectangle2D            bounds = centerAxisLabel(glyphs.getVisualBounds());
1082:                 * graphics.drawGlyphVector(glyphs, (float)bounds.getMinX(), (float)bounds.getMaxY());
1083:                 * </pre>
1084:                 *
1085:                 * @param  bounds  Un rectangle englobant les caractères à écrire. La position
1086:                 *                 (<var>x</var>,<var>y</var>) de ce rectangle est généralement
1087:                 *                 (mais pas obligatoirement) l'origine (0,0). Ce rectangle est
1088:                 *                 habituellement obtenu par un appel à
1089:                 *                 {@link Font#createGlyphVector(FontContext,String)}.
1090:                 * @param toRotate Si non-nul, transformation affine sur laquelle appliquer une rotation
1091:                 *                 égale à l'angle de l'axe.  Cette méthode peut limiter la rotation aux
1092:                 *                 quadrants 1 et 2 afin de conserver une lecture agréable du texte.
1093:                 * @param maximumSize Largeur et hauteur maximales des étiquettes de graduation. Cette
1094:                 *                 information est utilisée pour écarter l'étiquette de l'axe suffisament
1095:                 *                 pour qu'elle n'écrase pas les étiquettes de graduation.
1096:                 * @return Le rectangle {@code bounds}, modifié pour être centré sur l'axe.
1097:                 */
1098:                final Rectangle2D centerAxisLabel(final Rectangle2D bounds,
1099:                        final AffineTransform toRotate,
1100:                        final Dimension2D maximumSize) {
1101:                    final double height = bounds.getHeight();
1102:                    final double width = bounds.getWidth();
1103:                    final double tx = 0;
1104:                    final double ty = height
1105:                            + Math.abs(maximumSize.getWidth() * tickX)
1106:                            + Math.abs(maximumSize.getHeight() * tickY);
1107:                    final double x1 = getX1();
1108:                    final double y1 = getY1();
1109:                    final double x2 = getX2();
1110:                    final double y2 = getY2();
1111:                    /////////////////////////////////////
1112:                    //// Compute unit vector (ux,uy) ////
1113:                    /////////////////////////////////////
1114:                    double ux = (double) x2 - (double) x1;
1115:                    double uy = (double) y2 - (double) y1;
1116:                    double ul = Math.sqrt(ux * ux + uy * uy);
1117:                    ux /= ul;
1118:                    uy /= ul;
1119:                    //////////////////////////////////////////////
1120:                    //// Get the central position of the axis ////
1121:                    //////////////////////////////////////////////
1122:                    double x = 0.5 * (x1 + x2);
1123:                    double y = 0.5 * (y1 + y2);
1124:                    ////////////////////////////////////////
1125:                    //// Apply the parallel translation ////
1126:                    ////////////////////////////////////////
1127:                    x += ux * tx;
1128:                    y += uy * tx;
1129:                    ////////////////////////////////////
1130:                    //// Adjust sign of unit vector ////
1131:                    ////////////////////////////////////
1132:                    ux *= relativeCCW;
1133:                    uy *= relativeCCW;
1134:                    /////////////////////////////////////////////
1135:                    //// Apply the perpendicular translation ////
1136:                    /////////////////////////////////////////////
1137:                    x += uy * ty;
1138:                    y -= ux * ty;
1139:                    ///////////////////////////////////
1140:                    //// Offset the point for text ////
1141:                    ///////////////////////////////////
1142:                    final double anchorX = x;
1143:                    final double anchorY = y;
1144:                    if (toRotate == null) {
1145:                        y += 0.5 * height * (1 - ux);
1146:                        x -= 0.5 * width * (1 - uy);
1147:                    } else {
1148:                        if (ux < 0) {
1149:                            ux = -ux;
1150:                            uy = -uy;
1151:                            y += height;
1152:                        }
1153:                        x -= 0.5 * width;
1154:                        toRotate.rotate(Math.atan2(uy, ux), anchorX, anchorY);
1155:                    }
1156:                    bounds.setRect(x, y - height, width, height);
1157:                    ensureValid();
1158:                    return bounds;
1159:                }
1160:
1161:                /**
1162:                 * Moves the iterator to the next minor or major tick.
1163:                 */
1164:                public void next() {
1165:                    this .label = null;
1166:                    this .glyphs = null;
1167:                    iterator.next();
1168:                }
1169:
1170:                /**
1171:                 * Moves the iterator to the next major tick. This move ignore any minor ticks
1172:                 * between current position and the next major tick.
1173:                 */
1174:                public void nextMajor() {
1175:                    this .label = null;
1176:                    this .glyphs = null;
1177:                    iterator.nextMajor();
1178:                }
1179:
1180:                /**
1181:                 * Reset the iterator on its first tick. All other properties are left unchanged.
1182:                 */
1183:                public void rewind() {
1184:                    this .label = null;
1185:                    this .glyphs = null;
1186:                    iterator.rewind();
1187:                }
1188:
1189:                /**
1190:                 * Reset the iterator on its first tick. If some axis properies have changed (e.g. minimum
1191:                 * and/or maximum values), then the new settings are taken in account. This {@link #refresh}
1192:                 * method help to reduce garbage-collection by constructing an {@code Axis2D.TickIterator}
1193:                 * object only once and reuse it for each axis's rendering.
1194:                 */
1195:                public void refresh() {
1196:                    synchronized (Axis2D.this ) {
1197:                        this .label = null;
1198:                        this .glyphs = null;
1199:                        // Do NOT modify 'fontContext'.
1200:
1201:                        final Graduation graduation = getGraduation();
1202:                        final double dx = getX2() - getX1();
1203:                        final double dy = getY2() - getY1();
1204:                        final double range = graduation.getRange();
1205:                        final double length = Math.sqrt(dx * dx + dy * dy);
1206:                        hints.put(Graduation.VISUAL_AXIS_LENGTH,
1207:                                new java.lang.Double(length));
1208:
1209:                        this .scaleX = dx / range;
1210:                        this .scaleY = dy / range;
1211:                        this .tickX = -dy / length * relativeCCW;
1212:                        this .tickY = +dx / length * relativeCCW;
1213:                        this .minimum = graduation.getMinimum();
1214:                        this .iterator = graduation.getTickIterator(hints,
1215:                                iterator);
1216:                        this .modCount = Axis2D.this .modCount;
1217:                    }
1218:                }
1219:
1220:                /**
1221:                 * Returns the locale used for formatting tick labels.
1222:                 */
1223:                public Locale getLocale() {
1224:                    return iterator.getLocale();
1225:                }
1226:
1227:                /**
1228:                 * Retourne le contexte utilisé pour dessiner les caractères.
1229:                 * Cette méthode ne retourne jamais {@code null}.
1230:                 */
1231:                final FontRenderContext getFontRenderContext() {
1232:                    if (fontContext == null) {
1233:                        fontContext = new FontRenderContext(null, false, false);
1234:                    }
1235:                    return fontContext;
1236:                }
1237:
1238:                /**
1239:                 * Spécifie le contexte à utiliser pour dessiner les caractères,
1240:                 * ou {@code null} pour utiliser un contexte par défaut.
1241:                 */
1242:                final void setFontRenderContext(final FontRenderContext context) {
1243:                    fontContext = context;
1244:                }
1245:
1246:                /**
1247:                 * Vérifie que l'axe n'a pas changé depuis le dernier appel de {@link #init}.
1248:                 * Cette méthode doit être appelée <u>à la fin</u> des méthodes de cette classe
1249:                 * qui lisent les champs de {@link Axis2D}.
1250:                 */
1251:                final void ensureValid() {
1252:                    if (this .modCount != Axis2D.this .modCount) {
1253:                        throw new ConcurrentModificationException();
1254:                    }
1255:                }
1256:            }
1257:
1258:            /**
1259:             * Itérateur balayant l'axe et ses barres de graduations pour leur traçage. Cet itérateur ne
1260:             * balaye pas les étiquettes de graduations. Puisque cet itérateur ne retourne que des droites
1261:             * et jamais de courbes, il ne prend pas d'argument {@code flatness}.
1262:             *
1263:             * @version $Id: Axis2D.java 22710 2006-11-12 18:04:54Z desruisseaux $
1264:             * @author Martin Desruisseaux
1265:             */
1266:            private class TickPathIterator extends TickIterator implements 
1267:                    java.awt.geom.PathIterator {
1268:                /**
1269:                 * Transformation affine à appliquer sur les données. Il doit s'agir d'une transformation
1270:                 * affine appropriée pour l'écriture de texte (généralement en pixels ou en points). Il ne
1271:                 * s'agit <u>pas</u> de la transformation affine créée par
1272:                 * {@link Axis2D#createAffineTransform}.
1273:                 */
1274:                protected AffineTransform transform;
1275:
1276:                /**
1277:                 * Coordonnées de la prochaine graduation à retourner par une des méthodes
1278:                 * {@code currentSegment(...)}. Ces coordonnées n'auront <u>pas</u>
1279:                 * été transformées selon la transformation affine {@link #transform}.
1280:                 */
1281:                private final Line2D.Double line = new Line2D.Double();
1282:
1283:                /**
1284:                 * Coordonnées du prochain point à retourner par une des méthodes
1285:                 * {@code currentSegment(...)}. Ces coordonnées auront été
1286:                 * transformées selon la transformation affine {@link #transform}.
1287:                 */
1288:                private final Point2D.Double point = new Point2D.Double();
1289:
1290:                /**
1291:                 * Type du prochain segment. Ce type est retourné par les méthodes
1292:                 * {@code currentSegment(...)}. Il doit s'agir en général d'une
1293:                 * des constantes {@link #SEG_MOVETO} ou {@link #SEG_LINETO}.
1294:                 */
1295:                private int type = SEG_MOVETO;
1296:
1297:                /**
1298:                 * Entier indiquant quel sera le prochain item a retourner (début ou
1299:                 * fin d'une graduation, début ou fin de l'axe, etc.). Il doit s'agir
1300:                 * d'une des constantes {@link #AXIS_MOVETO}, {@link #AXIS_LINETO},
1301:                 * {@link #TICK_MOVETO},  {@link #TICK_LINETO}, etc.
1302:                 */
1303:                private int nextType = AXIS_MOVETO;
1304:
1305:                /** Constante pour {@link #nextType}.*/
1306:                private static final int AXIS_MOVETO = 0;
1307:                /** Constante pour {@link #nextType}.*/
1308:                private static final int AXIS_LINETO = 1;
1309:                /** Constante pour {@link #nextType}.*/
1310:                private static final int TICK_MOVETO = 2;
1311:                /** Constante pour {@link #nextType}.*/
1312:                private static final int TICK_LINETO = 3;
1313:
1314:                /**
1315:                 * Construit un itérateur.
1316:                 *
1317:                 * @param transform Transformation affine à appliquer sur les données. Il doit
1318:                 *            s'agir d'une transformation affine appropriée pour l'écriture de
1319:                 *            texte (généralement en pixels ou en points). Il ne s'agit <u>pas</u>
1320:                 *            de la transformation affine créée par {@link Axis2D#createAffineTransform}.
1321:                 */
1322:                public TickPathIterator(final AffineTransform transform) {
1323:                    super (null);
1324:                    // 'refresh' est appelée par le constructeur parent.
1325:                    this .transform = transform;
1326:                    next();
1327:                }
1328:
1329:                /**
1330:                 * Initialise cet itérateur. Cette méthode peut être appelée pour réutiliser un itérateur
1331:                 * qui a déjà servit, plutôt que d'en construire un autre.
1332:                 *
1333:                 * @param transform Transformation affine à appliquer sur les données. Il doit
1334:                 *        s'agir d'une transformation affine appropriée pour l'écriture de
1335:                 *        texte (généralement en pixels ou en points). Il ne s'agit <u>pas</u>
1336:                 *        de la transformation affine créée par {@link Axis2D#createAffineTransform}.
1337:                 */
1338:                final void init(final AffineTransform transform) {
1339:                    refresh();
1340:                    setFontRenderContext(null);
1341:                    this .type = SEG_MOVETO;
1342:                    this .nextType = AXIS_MOVETO;
1343:                    this .transform = transform;
1344:                    next();
1345:                }
1346:
1347:                /**
1348:                 * Repositione l'itérateur au début de la graduation
1349:                 * avec une nouvelle transformation affine.
1350:                 */
1351:                public void rewind(final AffineTransform transform) {
1352:                    super .rewind();
1353:                    // Keep 'fontContext'.
1354:                    this .type = SEG_MOVETO;
1355:                    this .nextType = AXIS_MOVETO;
1356:                    this .transform = transform;
1357:                    next();
1358:                }
1359:
1360:                /**
1361:                 * Repositione l'itérateur au début de la graduation
1362:                 * en conservant la transformation affine actuelle.
1363:                 */
1364:                public final void rewind() {
1365:                    rewind(transform);
1366:                }
1367:
1368:                /**
1369:                 * Return the winding rule for determining the insideness of the path.
1370:                 */
1371:                public int getWindingRule() {
1372:                    return WIND_NON_ZERO;
1373:                }
1374:
1375:                /**
1376:                 * Tests if the iteration is complete.
1377:                 */
1378:                public boolean isDone() {
1379:                    return nextType == TICK_LINETO && !hasNext();
1380:                }
1381:
1382:                /**
1383:                 * Returns the coordinates and type of the current path segment
1384:                 * in the iteration. The return value is the path segment type:
1385:                 * {@code SEG_MOVETO} or {@code SEG_LINETO}.
1386:                 */
1387:                public int currentSegment(final float[] coords) {
1388:                    coords[0] = (float) point.x;
1389:                    coords[1] = (float) point.y;
1390:                    return type;
1391:                }
1392:
1393:                /**
1394:                 * Returns the coordinates and type of the current path segment
1395:                 * in the iteration. The return value is the path segment type:
1396:                 * {@code SEG_MOVETO} or {@code SEG_LINETO}.
1397:                 */
1398:                public int currentSegment(final double[] coords) {
1399:                    coords[0] = point.x;
1400:                    coords[1] = point.y;
1401:                    return type;
1402:                }
1403:
1404:                /**
1405:                 * Moves the iterator to the next segment of the path forwards
1406:                 * along the primary direction of traversal as long as there are
1407:                 * more points in that direction.
1408:                 */
1409:                public void next() {
1410:                    switch (nextType) {
1411:                    default: { // Should not happen
1412:                        throw new IllegalPathStateException(Integer
1413:                                .toString(nextType));
1414:                    }
1415:                    case AXIS_MOVETO: { // Premier point de l'axe
1416:                        point.x = getX1();
1417:                        point.y = getY1();
1418:                        type = SEG_MOVETO;
1419:                        nextType = AXIS_LINETO;
1420:                        break;
1421:                    }
1422:                    case AXIS_LINETO: { // Fin de l'axe
1423:                        point.x = getX2();
1424:                        point.y = getY2();
1425:                        type = SEG_LINETO;
1426:                        nextType = TICK_MOVETO;
1427:                        break;
1428:                    }
1429:                    case TICK_MOVETO: { // Premier point d'une graduation
1430:                        currentTick(line);
1431:                        point.x = line.x1;
1432:                        point.y = line.y1;
1433:                        type = SEG_MOVETO;
1434:                        nextType = TICK_LINETO;
1435:                        break;
1436:                    }
1437:                    case TICK_LINETO: { // Dernier point d'une graduation
1438:                        point.x = line.x2;
1439:                        point.y = line.y2;
1440:                        type = SEG_LINETO;
1441:                        nextType = TICK_MOVETO;
1442:                        prepareLabel();
1443:                        super .next();
1444:                        break;
1445:                    }
1446:                    }
1447:                    if (transform != null) {
1448:                        transform.transform(point, point);
1449:                    }
1450:                    ensureValid();
1451:                }
1452:
1453:                /**
1454:                 * Méthode appelée automatiquement par {@link #next} pour
1455:                 * indiquer qu'il faudra se préparer à tracer une étiquette.
1456:                 */
1457:                protected void prepareLabel() {
1458:                }
1459:            }
1460:
1461:            /**
1462:             * Itérateur balayant l'axe et ses barres de graduations pour leur traçage.
1463:             * Cet itérateur balaye aussi les étiquettes de graduations.
1464:             *
1465:             * @version $Id: Axis2D.java 22710 2006-11-12 18:04:54Z desruisseaux $
1466:             * @author Martin Desruisseaux
1467:             */
1468:            private final class PathIterator extends TickPathIterator {
1469:                /**
1470:                 * Controle le remplacement des courbes par des droites. La valeur
1471:                 * {@link Double#NaN} indique qu'un tel remplacement n'a pas lieu.
1472:                 */
1473:                private final double flatness;
1474:
1475:                /**
1476:                 * Chemin de l'étiquette {@link #label}.
1477:                 */
1478:                private java.awt.geom.PathIterator path;
1479:
1480:                /**
1481:                 * Etiquette de graduation à tracer.
1482:                 */
1483:                private Shape label;
1484:
1485:                /**
1486:                 * Rectangle englobant l'étiquette {@link #label} courante.
1487:                 */
1488:                private Rectangle2D labelBounds;
1489:
1490:                /**
1491:                 * Valeur maximale de {@code labelBounds.getWidth()} trouvée jusqu'à maintenant.
1492:                 */
1493:                private double maxWidth = 0;
1494:
1495:                /**
1496:                 * Valeur maximale de {@code labelBounds.getHeight()} trouvée jusqu'à maintenant.
1497:                 */
1498:                private double maxHeight = 0;
1499:
1500:                /**
1501:                 * Prend la valeur {@code true} lorsque la légende de l'axe a été écrite.
1502:                 */
1503:                private boolean isDone;
1504:
1505:                /**
1506:                 * Construit un itérateur.
1507:                 *
1508:                 * @param transform Transformation affine à appliquer sur les données. Il doit
1509:                 *        s'agir d'une transformation affine appropriée pour l'écriture de
1510:                 *        texte (généralement en pixels ou en points). Il ne s'agit <u>pas</u>
1511:                 *        de la transformation affine créée par {@link Axis2D#createAffineTransform}.
1512:                 * @param flatness Contrôle le remplacement des courbes par des droites. La valeur
1513:                 *        {@link Double#NaN} indique qu'un tel remplacement ne doit pas être fait.
1514:                 */
1515:                public PathIterator(final AffineTransform transform,
1516:                        final double flatness) {
1517:                    super (transform);
1518:                    this .flatness = flatness;
1519:                }
1520:
1521:                /**
1522:                 * Retourne un itérateur balayant la forme géométrique spécifiée.
1523:                 */
1524:                private java.awt.geom.PathIterator getPathIterator(
1525:                        final Shape shape) {
1526:                    return java.lang.Double.isNaN(flatness) ? shape
1527:                            .getPathIterator(transform) : shape
1528:                            .getPathIterator(transform, flatness);
1529:                }
1530:
1531:                /**
1532:                 * Lance une exception; cet itérateur n'est conçu pour n'être utilisé qu'une seule fois.
1533:                 */
1534:                public void rewind(final AffineTransform transform) {
1535:                    throw new UnsupportedOperationException();
1536:                }
1537:
1538:                /**
1539:                 * Tests if the iteration is complete.
1540:                 */
1541:                public boolean isDone() {
1542:                    return (path != null) ? path.isDone() : super .isDone();
1543:                }
1544:
1545:                /**
1546:                 * Returns the coordinates and type of the current path segment in the iteration.
1547:                 */
1548:                public int currentSegment(final float[] coords) {
1549:                    return (path != null) ? path.currentSegment(coords) : super 
1550:                            .currentSegment(coords);
1551:                }
1552:
1553:                /**
1554:                 * Returns the coordinates and type of the current path segment in the iteration.
1555:                 */
1556:                public int currentSegment(final double[] coords) {
1557:                    return (path != null) ? path.currentSegment(coords) : super 
1558:                            .currentSegment(coords);
1559:                }
1560:
1561:                /**
1562:                 * Moves the iterator to the next segment of the path forwards along the primary
1563:                 * direction of traversal as long as there are more points in that direction.
1564:                 */
1565:                public void next() {
1566:                    if (path != null) {
1567:                        path.next();
1568:                        if (!path.isDone()) {
1569:                            return;
1570:                        }
1571:                        path = null;
1572:                    }
1573:                    if (label != null) {
1574:                        path = getPathIterator(label);
1575:                        label = null;
1576:                        if (path != null) {
1577:                            if (!path.isDone()) {
1578:                                return;
1579:                            }
1580:                            path = null;
1581:                        }
1582:                    }
1583:                    if (!isDone) {
1584:                        super .next();
1585:                        if (isDone()) {
1586:                            /*
1587:                             * Quand tout le reste est terminé, prépare l'écriture de la légende de l'axe.
1588:                             */
1589:                            isDone = true;
1590:                            final String title = graduation.getTitle(true);
1591:                            if (title != null) {
1592:                                final GlyphVector glyphs;
1593:                                glyphs = getTitleFont().createGlyphVector(
1594:                                        getFontRenderContext(), title);
1595:                                if (transform != null) {
1596:                                    transform = new AffineTransform(transform);
1597:                                } else {
1598:                                    transform = new AffineTransform();
1599:                                }
1600:                                final Rectangle2D bounds = centerAxisLabel(
1601:                                        glyphs.getVisualBounds(), transform,
1602:                                        new XDimension2D.Double(maxWidth,
1603:                                                maxHeight));
1604:                                path = getPathIterator(glyphs.getOutline(
1605:                                        (float) bounds.getMinX(),
1606:                                        (float) bounds.getMaxY()));
1607:                            }
1608:                        }
1609:                    }
1610:                }
1611:
1612:                /**
1613:                 * Méthode appelée automatiquement par {@link #next} pour
1614:                 * indiquer qu'il faudra se préparer à tracer une étiquette.
1615:                 */
1616:                protected void prepareLabel() {
1617:                    if (isMajorTick()) {
1618:                        final GlyphVector glyphs = currentLabelGlyphs();
1619:                        final Rectangle2D bounds = currentLabelBounds();
1620:                        if (glyphs != null && bounds != null) {
1621:                            if (labelBounds == null
1622:                                    || !labelBounds.intersects(bounds)) {
1623:                                label = glyphs.getOutline((float) bounds
1624:                                        .getMinX(), (float) bounds.getMaxY());
1625:                                final double width = bounds.getWidth();
1626:                                final double height = bounds.getHeight();
1627:                                if (width > maxWidth)
1628:                                    maxWidth = width;
1629:                                if (height > maxHeight)
1630:                                    maxHeight = height;
1631:                                labelBounds = bounds;
1632:                            }
1633:                        }
1634:                    }
1635:                }
1636:            }
1637:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.