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