001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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-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 }