    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * ---------------
    028     * SymbolAxis.java
    029     * ---------------
    030     * (C) Copyright 2002-2007, by Anthony Boulestreau and Contributors.
    031     *
    032     * Original Author:  Anthony Boulestreau;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     *
    036     * Changes
    037     * -------
    038     * 29-Mar-2002 : First version (AB);
    039     * 19-Apr-2002 : Updated formatting and import statements (DG);
    040     * 21-Jun-2002 : Make change to use the class TickUnit - remove valueToString()
    041     *               method and add SymbolicTickUnit (AB);
    042     * 25-Jun-2002 : Removed redundant code (DG);
    043     * 25-Jul-2002 : Changed order of parameters in ValueAxis constructor (DG);
    044     * 05-Sep-2002 : Updated constructor to reflect changes in the Axis class (DG);
    045     * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
    046     * 14-Feb-2003 : Added back missing constructor code (DG);
    047     * 26-Mar-2003 : Implemented Serializable (DG);
    048     * 14-May-2003 : Renamed HorizontalSymbolicAxis --> SymbolicAxis and merged in
    049     *               VerticalSymbolicAxis (DG);
    050     * 12-Aug-2003 : Fixed bug where refreshTicks() method has different signature
    051     *               to super class (DG);
    052     * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
    053     * 02-Nov-2003 : Added code to avoid overlapping labels (MR);
    054     * 07-Nov-2003 : Modified to use new tick classes (DG);
    055     * 18-Nov-2003 : Fixed bug where symbols are not being displayed on the
    056     *               axis (DG);
    057     * 24-Nov-2003 : Added fix for gridlines on zooming (bug id 834643) (DG);
    058     * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
    059     * 11-Mar-2004 : Modified the way the background grid color is being drawn, see
    060     *               this thread:
    061     *               http://www.jfree.org/phpBB2/viewtopic.php?p=22973 (DG);
    062     * 16-Mar-2004 : Added plotState to draw() method (DG);
    063     * 07-Apr-2004 : Modified string bounds calculation (DG);
    064     * 28-Mar-2005 : Renamed autoRangeIncludesZero() --> getAutoRangeIncludesZero()
    065     *               and autoRangeStickyZero() --> getAutoRangeStickyZero() (DG);
    066     * 05-Jul-2005 : Fixed signature on refreshTicks() method - see bug report
    067     *               1232264 (DG);
    068     * 06-Jul-2005 : Renamed SymbolicAxis --> SymbolAxis, added equals() method,
    069     *               renamed getSymbolicValue() --> getSymbols(), renamed
    070     *               symbolicGridPaint --> gridBandPaint, fixed serialization of
    071     *               gridBandPaint, renamed symbolicGridLinesVisible -->
    072     *               gridBandsVisible, eliminated symbolicGridLineList (DG);
    073     * ------------- JFREECHART 1.0.x ---------------------------------------------
    074     * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
    075     * 28-Feb-2007 : Fixed bug 1669302 (tick label overlap) (DG);
    076     * 25-Jul-2007 : Added new field for alternate grid band paint (DG);
    077     *
    078     */
    079
    080    package org.jfree.chart.axis;
    081
    082    import java.awt.BasicStroke;
    083    import java.awt.Color;
    084    import java.awt.Font;
    085    import java.awt.Graphics2D;
    086    import java.awt.Paint;
    087    import java.awt.Shape;
    088    import java.awt.Stroke;
    089    import java.awt.geom.Rectangle2D;
    090    import java.io.IOException;
    091    import java.io.ObjectInputStream;
    092    import java.io.ObjectOutputStream;
    093    import java.io.Serializable;
    094    import java.text.NumberFormat;
    095    import java.util.Arrays;
    096    import java.util.Iterator;
    097    import java.util.List;
    098
    099    import org.jfree.chart.event.AxisChangeEvent;
    100    import org.jfree.chart.plot.Plot;
    101    import org.jfree.chart.plot.PlotRenderingInfo;
    102    import org.jfree.chart.plot.ValueAxisPlot;
    103    import org.jfree.data.Range;
    104    import org.jfree.io.SerialUtilities;
    105    import org.jfree.text.TextUtilities;
    106    import org.jfree.ui.RectangleEdge;
    107    import org.jfree.ui.TextAnchor;
    108    import org.jfree.util.PaintUtilities;
    109
    110    /**
    111     * A standard linear value axis that replaces integer values with symbols.
    112     */
    113    public class SymbolAxis extends NumberAxis implements Serializable {
    114
    115        /** For serialization. */
    116        private static final long serialVersionUID = 7216330468770619716L;
    117
    118        /** The default grid band paint. */
    119        public static final Paint DEFAULT_GRID_BAND_PAINT
    120                = new Color(232, 234, 232, 128);
    121
    122        /**
    123         * The default paint for alternate grid bands.
    124         *
    125         * @since 1.0.7
    126         */
    127        public static final Paint DEFAULT_GRID_BAND_ALTERNATE_PAINT
    128                = new Color(0, 0, 0, 0);  // transparent
    129
    130        /** The list of symbols to display instead of the numeric values. */
    131        private List symbols;
    132
    133        /** Flag that indicates whether or not grid bands are visible. */
    134        private boolean gridBandsVisible;
    135
    136        /** The paint used to color the grid bands (if the bands are visible). */
    137        private transient Paint gridBandPaint;
    138
    139        /**
    140         * The paint used to fill the alternate grid bands.
    141         *
    142         * @since 1.0.7
    143         */
    144        private transient Paint gridBandAlternatePaint;
    145
    146        /**
    147         * Constructs a symbol axis, using default attribute values where
    148         * necessary.
    149         *
    150         * @param label  the axis label (<code>null</code> permitted).
    151         * @param sv  the list of symbols to display instead of the numeric
    152         *            values.
    153         */
    154        public SymbolAxis(String label, String[] sv) {
    155            super(label);
    156            this.symbols = Arrays.asList(sv);
    157            this.gridBandsVisible = true;
    158            this.gridBandPaint = DEFAULT_GRID_BAND_PAINT;
    159            this.gridBandAlternatePaint = DEFAULT_GRID_BAND_ALTERNATE_PAINT;
    160            setAutoTickUnitSelection(false, false);
    161            setAutoRangeStickyZero(false);
    162
    163        }
    164
    165        /**
    166         * Returns an array of the symbols for the axis.
    167         *
    168         * @return The symbols.
    169         */
    170        public String[] getSymbols() {
    171            String[] result = new String[this.symbols.size()];
    172            result = (String[]) this.symbols.toArray(result);
    173            return result;
    174        }
    175
    176        /**
    177         * Returns <code>true</code> if the grid bands are showing, and
    178         * <code>false</code> otherwise.
    179         *
    180         * @return <code>true</code> if the grid bands are showing, and
    181         *         <code>false</code> otherwise.
    182         *
    183         * @see #setGridBandsVisible(boolean)
    184         */
    185        public boolean isGridBandsVisible() {
    186            return this.gridBandsVisible;
    187        }
    188
    189        /**
    190         * Sets the visibility of the grid bands and notifies registered
    191         * listeners that the axis has been modified.
    192         *
    193         * @param flag  the new setting.
    194         *
    195         * @see #isGridBandsVisible()
    196         */
    197        public void setGridBandsVisible(boolean flag) {
    198            if (this.gridBandsVisible != flag) {
    199                this.gridBandsVisible = flag;
    200                notifyListeners(new AxisChangeEvent(this));
    201            }
    202        }
    203
    204        /**
    205         * Returns the paint used to color the grid bands.
    206         *
    207         * @return The grid band paint (never <code>null</code>).
    208         *
    209         * @see #setGridBandPaint(Paint)
    210         * @see #isGridBandsVisible()
    211         */
    212        public Paint getGridBandPaint() {
    213            return this.gridBandPaint;
    214        }
    215
    216        /**
    217         * Sets the grid band paint and sends an {@link AxisChangeEvent} to
    218         * all registered listeners.
    219         *
    220         * @param paint  the paint (<code>null</code> not permitted).
    221         *
    222         * @see #getGridBandPaint()
    223         */
    224        public void setGridBandPaint(Paint paint) {
    225            if (paint == null) {
    226                throw new IllegalArgumentException("Null 'paint' argument.");
    227            }
    228            this.gridBandPaint = paint;
    229            notifyListeners(new AxisChangeEvent(this));
    230        }
    231
    232        /**
    233         * Returns the paint used for alternate grid bands.
    234         *
    235         * @return The paint (never <code>null</code>).
    236         *
    237         * @see #setGridBandAlternatePaint(Paint)
    238         * @see #getGridBandPaint()
    239         *
    240         * @since 1.0.7
    241         */
    242        public Paint getGridBandAlternatePaint() {
    243            return this.gridBandAlternatePaint;
    244        }
    245
    246        /**
    247         * Sets the paint used for alternate grid bands and sends a
    248         * {@link AxisChangeEvent} to all registered listeners.
    249         *
    250         * @param paint  the paint (<code>null</code> not permitted).
    251         *
    252         * @see #getGridBandAlternatePaint()
    253         * @see #setGridBandPaint(Paint)
    254         *
    255         * @since 1.0.7
    256         */
    257        public void setGridBandAlternatePaint(Paint paint) {
    258            if (paint == null) {
    259                throw new IllegalArgumentException("Null 'paint' argument.");
    260            }
    261            this.gridBandAlternatePaint = paint;
    262            notifyListeners(new AxisChangeEvent(this));
    263        }
    264
    265        /**
    266         * This operation is not supported by this axis.
    267         *
    268         * @param g2  the graphics device.
    269         * @param dataArea  the area in which the plot and axes should be drawn.
    270         * @param edge  the edge along which the axis is drawn.
    271         */
    272        protected void selectAutoTickUnit(Graphics2D g2, Rectangle2D dataArea,
    273                                          RectangleEdge edge) {
    274            throw new UnsupportedOperationException();
    275        }
    276
    277        /**
    278         * Draws the axis on a Java 2D graphics device (such as the screen or a
    279         * printer).
    280         *
    281         * @param g2  the graphics device (<code>null</code> not permitted).
    282         * @param cursor  the cursor location.
    283         * @param plotArea  the area within which the plot and axes should be drawn
    284         *                  (<code>null</code> not permitted).
    285         * @param dataArea  the area within which the data should be drawn
    286         *                  (<code>null</code> not permitted).
    287         * @param edge  the axis location (<code>null</code> not permitted).
    288         * @param plotState  collects information about the plot
    289         *                   (<code>null</code> permitted).
    290         *
    291         * @return The axis state (never <code>null</code>).
    292         */
    293        public AxisState draw(Graphics2D g2,
    294                              double cursor,
    295                              Rectangle2D plotArea,
    296                              Rectangle2D dataArea,
    297                              RectangleEdge edge,
    298                              PlotRenderingInfo plotState) {
    299
    300            AxisState info = new AxisState(cursor);
    301            if (isVisible()) {
    302                info = super.draw(g2, cursor, plotArea, dataArea, edge, plotState);
    303            }
    304            if (this.gridBandsVisible) {
    305                drawGridBands(g2, plotArea, dataArea, edge, info.getTicks());
    306            }
    307            return info;
    308
    309        }
    310
    311        /**
    312         * Draws the grid bands.  Alternate bands are colored using
    313         * <CODE>gridBandPaint<CODE> (<CODE>DEFAULT_GRID_BAND_PAINT</CODE> by
    314         * default).
    315         *
    316         * @param g2  the graphics device.
    317         * @param plotArea  the area within which the chart should be drawn.
    318         * @param dataArea  the area within which the plot should be drawn (a
    319         *                  subset of the drawArea).
    320         * @param edge  the axis location.
    321         * @param ticks  the ticks.
    322         */
    323        protected void drawGridBands(Graphics2D g2,
    324                                     Rectangle2D plotArea,
    325                                     Rectangle2D dataArea,
    326                                     RectangleEdge edge,
    327                                     List ticks) {
    328
    329            Shape savedClip = g2.getClip();
    330            g2.clip(dataArea);
    331            if (RectangleEdge.isTopOrBottom(edge)) {
    332                drawGridBandsHorizontal(g2, plotArea, dataArea, true, ticks);
    333            }
    334            else if (RectangleEdge.isLeftOrRight(edge)) {
    335                drawGridBandsVertical(g2, plotArea, dataArea, true, ticks);
    336            }
    337            g2.setClip(savedClip);
    338
    339        }
    340
    341        /**
    342         * Draws the grid bands for the axis when it is at the top or bottom of
    343         * the plot.
    344         *
    345         * @param g2  the graphics device.
    346         * @param plotArea  the area within which the chart should be drawn.
    347         * @param dataArea  the area within which the plot should be drawn
    348         *                  (a subset of the drawArea).
    349         * @param firstGridBandIsDark  True: the first grid band takes the
    350         *                             color of <CODE>gridBandPaint<CODE>.
    351         *                             False: the second grid band takes the
    352         *                             color of <CODE>gridBandPaint<CODE>.
    353         * @param ticks  the ticks.
    354         */
    355        protected void drawGridBandsHorizontal(Graphics2D g2,
    356                                               Rectangle2D plotArea,
    357                                               Rectangle2D dataArea,
    358                                               boolean firstGridBandIsDark,
    359                                               List ticks) {
    360
    361            boolean currentGridBandIsDark = firstGridBandIsDark;
    362            double yy = dataArea.getY();
    363            double xx1, xx2;
    364
    365            //gets the outline stroke width of the plot
    366            double outlineStrokeWidth;
    367            if (getPlot().getOutlineStroke() !=  null) {
    368                outlineStrokeWidth
    369                    = ((BasicStroke) getPlot().getOutlineStroke()).getLineWidth();
    370            }
    371            else {
    372                outlineStrokeWidth = 1d;
    373            }
    374
    375            Iterator iterator = ticks.iterator();
    376            ValueTick tick;
    377            Rectangle2D band;
    378            while (iterator.hasNext()) {
    379                tick = (ValueTick) iterator.next();
    380                xx1 = valueToJava2D(tick.getValue() - 0.5d, dataArea,
    381                        RectangleEdge.BOTTOM);
    382                xx2 = valueToJava2D(tick.getValue() + 0.5d, dataArea,
    383                        RectangleEdge.BOTTOM);
    384                if (currentGridBandIsDark) {
    385                    g2.setPaint(this.gridBandPaint);
    386                }
    387                else {
    388                    g2.setPaint(Color.white);
    389                }
    390                band = new Rectangle2D.Double(xx1, yy + outlineStrokeWidth,
    391                    xx2 - xx1, dataArea.getMaxY() - yy - outlineStrokeWidth);
    392                g2.fill(band);
    393                currentGridBandIsDark = !currentGridBandIsDark;
    394            }
    395            g2.setPaintMode();
    396        }
    397
    398        /**
    399         * Draws the grid bands for the axis when it is at the top or bottom of
    400         * the plot.
    401         *
    402         * @param g2  the graphics device.
    403         * @param drawArea  the area within which the chart should be drawn.
    404         * @param plotArea  the area within which the plot should be drawn (a
    405         *                  subset of the drawArea).
    406         * @param firstGridBandIsDark  True: the first grid band takes the
    407         *                             color of <CODE>gridBandPaint<CODE>.
    408         *                             False: the second grid band takes the
    409         *                             color of <CODE>gridBandPaint<CODE>.
    410         * @param ticks  a list of ticks.
    411         */
    412        protected void drawGridBandsVertical(Graphics2D g2,
    413                                             Rectangle2D drawArea,
    414                                             Rectangle2D plotArea,
    415                                             boolean firstGridBandIsDark,
    416                                             List ticks) {
    417
    418            boolean currentGridBandIsDark = firstGridBandIsDark;
    419            double xx = plotArea.getX();
    420            double yy1, yy2;
    421
    422            //gets the outline stroke width of the plot
    423            double outlineStrokeWidth;
    424            Stroke outlineStroke = getPlot().getOutlineStroke();
    425            if (outlineStroke != null && outlineStroke instanceof BasicStroke) {
    426                outlineStrokeWidth = ((BasicStroke) outlineStroke).getLineWidth();
    427            }
    428            else {
    429                outlineStrokeWidth = 1d;
    430            }
    431
    432            Iterator iterator = ticks.iterator();
    433            ValueTick tick;
    434            Rectangle2D band;
    435            while (iterator.hasNext()) {
    436                tick = (ValueTick) iterator.next();
    437                yy1 = valueToJava2D(tick.getValue() + 0.5d, plotArea,
    438                        RectangleEdge.LEFT);
    439                yy2 = valueToJava2D(tick.getValue() - 0.5d, plotArea,
    440                        RectangleEdge.LEFT);
    441                if (currentGridBandIsDark) {
    442                    g2.setPaint(this.gridBandPaint);
    443                }
    444                else {
    445                    g2.setPaint(Color.white);
    446                }
    447                band = new Rectangle2D.Double(xx + outlineStrokeWidth, yy1,
    448                        plotArea.getMaxX() - xx - outlineStrokeWidth, yy2 - yy1);
    449                g2.fill(band);
    450                currentGridBandIsDark = !currentGridBandIsDark;
    451            }
    452            g2.setPaintMode();
    453        }
    454
    455        /**
    456         * Rescales the axis to ensure that all data is visible.
    457         */
    458        protected void autoAdjustRange() {
    459
    460            Plot plot = getPlot();
    461            if (plot == null) {
    462                return;  // no plot, no data
    463            }
    464
    465            if (plot instanceof ValueAxisPlot) {
    466
    467                // ensure that all the symbols are displayed
    468                double upper = this.symbols.size() - 1;
    469                double lower = 0;
    470                double range = upper - lower;
    471
    472                // ensure the autorange is at least <minRange> in size...
    473                double minRange = getAutoRangeMinimumSize();
    474                if (range < minRange) {
    475                    upper = (upper + lower + minRange) / 2;
    476                    lower = (upper + lower - minRange) / 2;
    477                }
    478
    479                // this ensure that the grid bands will be displayed correctly.
    480                double upperMargin = 0.5;
    481                double lowerMargin = 0.5;
    482
    483                if (getAutoRangeIncludesZero()) {
    484                    if (getAutoRangeStickyZero()) {
    485                        if (upper <= 0.0) {
    486                            upper = 0.0;
    487                        }
    488                        else {
    489                            upper = upper + upperMargin;
    490                        }
    491                        if (lower >= 0.0) {
    492                            lower = 0.0;
    493                        }
    494                        else {
    495                            lower = lower - lowerMargin;
    496                        }
    497                    }
    498                    else {
    499                        upper = Math.max(0.0, upper + upperMargin);
    500                        lower = Math.min(0.0, lower - lowerMargin);
    501                    }
    502                }
    503                else {
    504                    if (getAutoRangeStickyZero()) {
    505                        if (upper <= 0.0) {
    506                            upper = Math.min(0.0, upper + upperMargin);
    507                        }
    508                        else {
    509                            upper = upper + upperMargin * range;
    510                        }
    511                        if (lower >= 0.0) {
    512                            lower = Math.max(0.0, lower - lowerMargin);
    513                        }
    514                        else {
    515                            lower = lower - lowerMargin;
    516                        }
    517                    }
    518                    else {
    519                        upper = upper + upperMargin;
    520                        lower = lower - lowerMargin;
    521                    }
    522                }
    523
    524                setRange(new Range(lower, upper), false, false);
    525
    526            }
    527
    528        }
    529
    530        /**
    531         * Calculates the positions of the tick labels for the axis, storing the
    532         * results in the tick label list (ready for drawing).
    533         *
    534         * @param g2  the graphics device.
    535         * @param state  the axis state.
    536         * @param dataArea  the area in which the data should be drawn.
    537         * @param edge  the location of the axis.
    538         *
    539         * @return A list of ticks.
    540         */
    541        public List refreshTicks(Graphics2D g2,
    542                                 AxisState state,
    543                                 Rectangle2D dataArea,
    544                                 RectangleEdge edge) {
    545            List ticks = null;
    546            if (RectangleEdge.isTopOrBottom(edge)) {
    547                ticks = refreshTicksHorizontal(g2, dataArea, edge);
    548            }
    549            else if (RectangleEdge.isLeftOrRight(edge)) {
    550                ticks = refreshTicksVertical(g2, dataArea, edge);
    551            }
    552            return ticks;
    553        }
    554
    555        /**
    556         * Calculates the positions of the tick labels for the axis, storing the
    557         * results in the tick label list (ready for drawing).
    558         *
    559         * @param g2  the graphics device.
    560         * @param dataArea  the area in which the data should be drawn.
    561         * @param edge  the location of the axis.
    562         *
    563         * @return The ticks.
    564         */
    565        protected List refreshTicksHorizontal(Graphics2D g2,
    566                                              Rectangle2D dataArea,
    567                                              RectangleEdge edge) {
    568
    569            List ticks = new java.util.ArrayList();
    570
    571            Font tickLabelFont = getTickLabelFont();
    572            g2.setFont(tickLabelFont);
    573
    574            double size = getTickUnit().getSize();
    575            int count = calculateVisibleTickCount();
    576            double lowestTickValue = calculateLowestVisibleTickValue();
    577
    578            double previousDrawnTickLabelPos = 0.0;
    579            double previousDrawnTickLabelLength = 0.0;
    580
    581            if (count <= ValueAxis.MAXIMUM_TICK_COUNT) {
    582                for (int i = 0; i < count; i++) {
    583                    double currentTickValue = lowestTickValue + (i * size);
    584                    double xx = valueToJava2D(currentTickValue, dataArea, edge);
    585                    String tickLabel;
    586                    NumberFormat formatter = getNumberFormatOverride();
    587                    if (formatter != null) {
    588                        tickLabel = formatter.format(currentTickValue);
    589                    }
    590                    else {
    591                        tickLabel = valueToString(currentTickValue);
    592                    }
    593
    594                    // avoid to draw overlapping tick labels
    595                    Rectangle2D bounds = TextUtilities.getTextBounds(tickLabel, g2,
    596                            g2.getFontMetrics());
    597                    double tickLabelLength = isVerticalTickLabels()
    598                            ? bounds.getHeight() : bounds.getWidth();
    599                    boolean tickLabelsOverlapping = false;
    600                    if (i > 0) {
    601                        double avgTickLabelLength = (previousDrawnTickLabelLength
    602                                + tickLabelLength) / 2.0;
    603                        if (Math.abs(xx - previousDrawnTickLabelPos)
    604                                < avgTickLabelLength) {
    605                            tickLabelsOverlapping = true;
    606                        }
    607                    }
    608                    if (tickLabelsOverlapping) {
    609                        tickLabel = ""; // don't draw this tick label
    610                    }
    611                    else {
    612                        // remember these values for next comparison
    613                        previousDrawnTickLabelPos = xx;
    614                        previousDrawnTickLabelLength = tickLabelLength;
    615                    }
    616
    617                    TextAnchor anchor = null;
    618                    TextAnchor rotationAnchor = null;
    619                    double angle = 0.0;
    620                    if (isVerticalTickLabels()) {
    621                        anchor = TextAnchor.CENTER_RIGHT;
    622                        rotationAnchor = TextAnchor.CENTER_RIGHT;
    623                        if (edge == RectangleEdge.TOP) {
    624                            angle = Math.PI / 2.0;
    625                        }
    626                        else {
    627                            angle = -Math.PI / 2.0;
    628                        }
    629                    }
    630                    else {
    631                        if (edge == RectangleEdge.TOP) {
    632                            anchor = TextAnchor.BOTTOM_CENTER;
    633                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
    634                        }
    635                        else {
    636                            anchor = TextAnchor.TOP_CENTER;
    637                            rotationAnchor = TextAnchor.TOP_CENTER;
    638                        }
    639                    }
    640                    Tick tick = new NumberTick(new Double(currentTickValue),
    641                            tickLabel, anchor, rotationAnchor, angle);
    642                    ticks.add(tick);
    643                }
    644            }
    645            return ticks;
    646
    647        }
    648
    649        /**
    650         * Calculates the positions of the tick labels for the axis, storing the
    651         * results in the tick label list (ready for drawing).
    652         *
    653         * @param g2  the graphics device.
    654         * @param dataArea  the area in which the plot should be drawn.
    655         * @param edge  the location of the axis.
    656         *
    657         * @return The ticks.
    658         */
    659        protected List refreshTicksVertical(Graphics2D g2,
    660                                            Rectangle2D dataArea,
    661                                            RectangleEdge edge) {
    662
    663            List ticks = new java.util.ArrayList();
    664
    665            Font tickLabelFont = getTickLabelFont();
    666            g2.setFont(tickLabelFont);
    667
    668            double size = getTickUnit().getSize();
    669            int count = calculateVisibleTickCount();
    670            double lowestTickValue = calculateLowestVisibleTickValue();
    671
    672            double previousDrawnTickLabelPos = 0.0;
    673            double previousDrawnTickLabelLength = 0.0;
    674
    675            if (count <= ValueAxis.MAXIMUM_TICK_COUNT) {
    676                for (int i = 0; i < count; i++) {
    677                    double currentTickValue = lowestTickValue + (i * size);
    678                    double yy = valueToJava2D(currentTickValue, dataArea, edge);
    679                    String tickLabel;
    680                    NumberFormat formatter = getNumberFormatOverride();
    681                    if (formatter != null) {
    682                        tickLabel = formatter.format(currentTickValue);
    683                    }
    684                    else {
    685                        tickLabel = valueToString(currentTickValue);
    686                    }
    687
    688                    // avoid to draw overlapping tick labels
    689                    Rectangle2D bounds = TextUtilities.getTextBounds(tickLabel, g2,
    690                            g2.getFontMetrics());
    691                    double tickLabelLength = isVerticalTickLabels()
    692                        ? bounds.getWidth() : bounds.getHeight();
    693                    boolean tickLabelsOverlapping = false;
    694                    if (i > 0) {
    695                        double avgTickLabelLength = (previousDrawnTickLabelLength
    696                                + tickLabelLength) / 2.0;
    697                        if (Math.abs(yy - previousDrawnTickLabelPos)
    698                                < avgTickLabelLength) {
    699                            tickLabelsOverlapping = true;
    700                        }
    701                    }
    702                    if (tickLabelsOverlapping) {
    703                        tickLabel = ""; // don't draw this tick label
    704                    }
    705                    else {
    706                        // remember these values for next comparison
    707                        previousDrawnTickLabelPos = yy;
    708                        previousDrawnTickLabelLength = tickLabelLength;
    709                    }
    710
    711                    TextAnchor anchor = null;
    712                    TextAnchor rotationAnchor = null;
    713                    double angle = 0.0;
    714                    if (isVerticalTickLabels()) {
    715                        anchor = TextAnchor.BOTTOM_CENTER;
    716                        rotationAnchor = TextAnchor.BOTTOM_CENTER;
    717                        if (edge == RectangleEdge.LEFT) {
    718                            angle = -Math.PI / 2.0;
    719                        }
    720                        else {
    721                            angle = Math.PI / 2.0;
    722                        }
    723                    }
    724                    else {
    725                        if (edge == RectangleEdge.LEFT) {
    726                            anchor = TextAnchor.CENTER_RIGHT;
    727                            rotationAnchor = TextAnchor.CENTER_RIGHT;
    728                        }
    729                        else {
    730                            anchor = TextAnchor.CENTER_LEFT;
    731                            rotationAnchor = TextAnchor.CENTER_LEFT;
    732                        }
    733                    }
    734                    Tick tick = new NumberTick(new Double(currentTickValue),
    735                            tickLabel, anchor, rotationAnchor, angle);
    736                    ticks.add(tick);
    737                }
    738            }
    739            return ticks;
    740
    741        }
    742
    743        /**
    744         * Converts a value to a string, using the list of symbols.
    745         *
    746         * @param value  value to convert.
    747         *
    748         * @return The symbol.
    749         */
    750        public String valueToString(double value) {
    751            String strToReturn;
    752            try {
    753                strToReturn = (String) this.symbols.get((int) value);
    754            }
    755            catch (IndexOutOfBoundsException  ex) {
    756                strToReturn = "";
    757            }
    758            return strToReturn;
    759        }
    760
    761        /**
    762         * Tests this axis for equality with an arbitrary object.
    763         *
    764         * @param obj  the object (<code>null</code> permitted).
    765         *
    766         * @return A boolean.
    767         */
    768        public boolean equals(Object obj) {
    769            if (obj == this) {
    770                return true;
    771            }
    772            if (!(obj instanceof SymbolAxis)) {
    773                return false;
    774            }
    775            SymbolAxis that = (SymbolAxis) obj;
    776            if (!this.symbols.equals(that.symbols)) {
    777                return false;
    778            }
    779            if (this.gridBandsVisible != that.gridBandsVisible) {
    780                return false;
    781            }
    782            if (!PaintUtilities.equal(this.gridBandPaint, that.gridBandPaint)) {
    783                return false;
    784            }
    785            if (!PaintUtilities.equal(this.gridBandAlternatePaint,
    786                    that.gridBandAlternatePaint)) {
    787                return false;
    788            }
    789            return super.equals(obj);
    790        }
    791
    792        /**
    793         * Provides serialization support.
    794         *
    795         * @param stream  the output stream.
    796         *
    797         * @throws IOException  if there is an I/O error.
    798         */
    799        private void writeObject(ObjectOutputStream stream) throws IOException {
    800            stream.defaultWriteObject();
    801            SerialUtilities.writePaint(this.gridBandPaint, stream);
    802            SerialUtilities.writePaint(this.gridBandAlternatePaint, stream);
    803        }
    804
    805        /**
    806         * Provides serialization support.
    807         *
    808         * @param stream  the input stream.
    809         *
    810         * @throws IOException  if there is an I/O error.
    811         * @throws ClassNotFoundException  if there is a classpath problem.
    812         */
    813        private void readObject(ObjectInputStream stream)
    814            throws IOException, ClassNotFoundException {
    815            stream.defaultReadObject();
    816            this.gridBandPaint = SerialUtilities.readPaint(stream);
    817            this.gridBandAlternatePaint = SerialUtilities.readPaint(stream);
    818        }
    819
    820    }