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 }