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 * ValueAxis.java
029 * --------------
030 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Jonathan Nash;
034 * Nicolas Brodu (for Astrium and EADS Corporate Research
035 * Center);
036 *
037 * $Id: ValueAxis.java,v 1.10.2.5 2007/03/22 12:13:27 mungady Exp $
038 *
039 * Changes (from 18-Sep-2001)
040 * --------------------------
041 * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
042 * 23-Nov-2001 : Overhauled standard tick unit code (DG);
043 * 04-Dec-2001 : Changed constructors to protected, and tidied up default
044 * values (DG);
045 * 12-Dec-2001 : Fixed vertical gridlines bug (DG);
046 * 16-Jan-2002 : Added an optional crosshair, based on the implementation by
047 * Jonathan Nash (DG);
048 * 23-Jan-2002 : Moved the minimum and maximum values to here from NumberAxis,
049 * and changed the type from Number to double (DG);
050 * 25-Feb-2002 : Added default value for autoRange. Changed autoAdjustRange
051 * from public to protected. Updated import statements (DG);
052 * 23-Apr-2002 : Added setRange() method (DG);
053 * 29-Apr-2002 : Added range adjustment methods (DG);
054 * 13-Jun-2002 : Modified setCrosshairValue() to notify listeners only when the
055 * crosshairs are visible, to avoid unnecessary repaints, as
056 * suggested by Kees Kuip (DG);
057 * 25-Jul-2002 : Moved lower and upper margin attributes from the NumberAxis
058 * class (DG);
059 * 05-Sep-2002 : Updated constructor for changes in Axis class (DG);
060 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
061 * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG);
062 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
063 * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG);
064 * 27-Nov-2002 : Moved the 'inverted' attributed from NumberAxis to
065 * ValueAxis (DG);
066 * 03-Jan-2003 : Small fix to ensure auto-range minimum is observed
067 * immediately (DG);
068 * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);
069 * 20-Jan-2003 : Replaced monolithic constructor (DG);
070 * 26-Mar-2003 : Implemented Serializable (DG);
071 * 09-May-2003 : Added AxisLocation parameter to translation methods (DG);
072 * 13-Aug-2003 : Implemented Cloneable (DG);
073 * 01-Sep-2003 : Fixed bug 793167 (setMaximumAxisValue exception) (DG);
074 * 02-Sep-2003 : Fixed bug 795366 (zooming on inverted axes) (DG);
075 * 08-Sep-2003 : Completed Serialization support (NB);
076 * 08-Sep-2003 : Renamed get/setMinimumValue --> get/setLowerBound,
077 * and get/setMaximumValue --> get/setUpperBound (DG);
078 * 27-Oct-2003 : Changed DEFAULT_AUTO_RANGE_MINIMUM_SIZE value - see bug ID
079 * 829606 (DG);
080 * 07-Nov-2003 : Changes to tick mechanism (DG);
081 * 06-Jan-2004 : Moved axis line attributes to Axis class (DG);
082 * 21-Jan-2004 : Removed redundant axisLineVisible attribute. Renamed
083 * translateJava2DToValue --> java2DToValue, and
084 * translateValueToJava2D --> valueToJava2D (DG);
085 * 23-Jan-2004 : Fixed setAxisLinePaint() and setAxisLineStroke() which had no
086 * effect (andreas.gawecki@coremedia.com);
087 * 07-Apr-2004 : Changed text bounds calculation (DG);
088 * 26-Apr-2004 : Added getter/setter methods for arrow shapes (DG);
089 * 18-May-2004 : Added methods to set axis range *including* current
090 * margins (DG);
091 * 02-Jun-2004 : Fixed bug in setRangeWithMargins() method (DG);
092 * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities
093 * --> TextUtilities (DG);
094 * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0
095 * release (DG);
096 * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
097 * ------------- JFREECHART 1.0.x ---------------------------------------------
098 * 10-Oct-2006 : Source reformatting (DG);
099 * 22-Mar-2007 : Added new defaultAutoRange attribute (DG);
100 *
101 */
102
103 package org.jfree.chart.axis;
104
105 import java.awt.Font;
106 import java.awt.FontMetrics;
107 import java.awt.Graphics2D;
108 import java.awt.Polygon;
109 import java.awt.Shape;
110 import java.awt.font.LineMetrics;
111 import java.awt.geom.AffineTransform;
112 import java.awt.geom.Line2D;
113 import java.awt.geom.Rectangle2D;
114 import java.io.IOException;
115 import java.io.ObjectInputStream;
116 import java.io.ObjectOutputStream;
117 import java.io.Serializable;
118 import java.util.Iterator;
119 import java.util.List;
120
121 import org.jfree.chart.event.AxisChangeEvent;
122 import org.jfree.chart.plot.Plot;
123 import org.jfree.data.Range;
124 import org.jfree.io.SerialUtilities;
125 import org.jfree.text.TextUtilities;
126 import org.jfree.ui.RectangleEdge;
127 import org.jfree.ui.RectangleInsets;
128 import org.jfree.util.ObjectUtilities;
129 import org.jfree.util.PublicCloneable;
130
131 /**
132 * The base class for axes that display value data, where values are measured
133 * using the <code>double</code> primitive. The two key subclasses are
134 * {@link DateAxis} and {@link NumberAxis}.
135 */
136 public abstract class ValueAxis extends Axis
137 implements Cloneable, PublicCloneable,
138 Serializable {
139
140 /** For serialization. */
141 private static final long serialVersionUID = 3698345477322391456L;
142
143 /** The default axis range. */
144 public static final Range DEFAULT_RANGE = new Range(0.0, 1.0);
145
146 /** The default auto-range value. */
147 public static final boolean DEFAULT_AUTO_RANGE = true;
148
149 /** The default inverted flag setting. */
150 public static final boolean DEFAULT_INVERTED = false;
151
152 /** The default minimum auto range. */
153 public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001;
154
155 /** The default value for the lower margin (0.05 = 5%). */
156 public static final double DEFAULT_LOWER_MARGIN = 0.05;
157
158 /** The default value for the upper margin (0.05 = 5%). */
159 public static final double DEFAULT_UPPER_MARGIN = 0.05;
160
161 /**
162 * The default lower bound for the axis.
163 *
164 * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
165 * attribute (see {@link #getDefaultAutoRange()}).
166 */
167 public static final double DEFAULT_LOWER_BOUND = 0.0;
168
169 /**
170 * The default upper bound for the axis.
171 *
172 * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
173 * attribute (see {@link #getDefaultAutoRange()}).
174 */
175 public static final double DEFAULT_UPPER_BOUND = 1.0;
176
177 /** The default auto-tick-unit-selection value. */
178 public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true;
179
180 /** The maximum tick count. */
181 public static final int MAXIMUM_TICK_COUNT = 500;
182
183 /**
184 * A flag that controls whether an arrow is drawn at the positive end of
185 * the axis line.
186 */
187 private boolean positiveArrowVisible;
188
189 /**
190 * A flag that controls whether an arrow is drawn at the negative end of
191 * the axis line.
192 */
193 private boolean negativeArrowVisible;
194
195 /** The shape used for an up arrow. */
196 private transient Shape upArrow;
197
198 /** The shape used for a down arrow. */
199 private transient Shape downArrow;
200
201 /** The shape used for a left arrow. */
202 private transient Shape leftArrow;
203
204 /** The shape used for a right arrow. */
205 private transient Shape rightArrow;
206
207 /** A flag that affects the orientation of the values on the axis. */
208 private boolean inverted;
209
210 /** The axis range. */
211 private Range range;
212
213 /**
214 * Flag that indicates whether the axis automatically scales to fit the
215 * chart data.
216 */
217 private boolean autoRange;
218
219 /** The minimum size for the 'auto' axis range (excluding margins). */
220 private double autoRangeMinimumSize;
221
222 /**
223 * The default range is used when the dataset is empty and the axis needs
224 * to determine the auto range.
225 *
226 * @since 1.0.5
227 */
228 private Range defaultAutoRange;
229
230 /**
231 * The upper margin percentage. This indicates the amount by which the
232 * maximum axis value exceeds the maximum data value (as a percentage of
233 * the range on the axis) when the axis range is determined automatically.
234 */
235 private double upperMargin;
236
237 /**
238 * The lower margin. This is a percentage that indicates the amount by
239 * which the minimum axis value is "less than" the minimum data value when
240 * the axis range is determined automatically.
241 */
242 private double lowerMargin;
243
244 /**
245 * If this value is positive, the amount is subtracted from the maximum
246 * data value to determine the lower axis range. This can be used to
247 * provide a fixed "window" on dynamic data.
248 */
249 private double fixedAutoRange;
250
251 /**
252 * Flag that indicates whether or not the tick unit is selected
253 * automatically.
254 */
255 private boolean autoTickUnitSelection;
256
257 /** The standard tick units for the axis. */
258 private TickUnitSource standardTickUnits;
259
260 /** An index into an array of standard tick values. */
261 private int autoTickIndex;
262
263 /** A flag indicating whether or not tick labels are rotated to vertical. */
264 private boolean verticalTickLabels;
265
266 /**
267 * Constructs a value axis.
268 *
269 * @param label the axis label (<code>null</code> permitted).
270 * @param standardTickUnits the source for standard tick units
271 * (<code>null</code> permitted).
272 */
273 protected ValueAxis(String label, TickUnitSource standardTickUnits) {
274
275 super(label);
276
277 this.positiveArrowVisible = false;
278 this.negativeArrowVisible = false;
279
280 this.range = DEFAULT_RANGE;
281 this.autoRange = DEFAULT_AUTO_RANGE;
282 this.defaultAutoRange = DEFAULT_RANGE;
283
284 this.inverted = DEFAULT_INVERTED;
285 this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE;
286
287 this.lowerMargin = DEFAULT_LOWER_MARGIN;
288 this.upperMargin = DEFAULT_UPPER_MARGIN;
289
290 this.fixedAutoRange = 0.0;
291
292 this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION;
293 this.standardTickUnits = standardTickUnits;
294
295 Polygon p1 = new Polygon();
296 p1.addPoint(0, 0);
297 p1.addPoint(-2, 2);
298 p1.addPoint(2, 2);
299
300 this.upArrow = p1;
301
302 Polygon p2 = new Polygon();
303 p2.addPoint(0, 0);
304 p2.addPoint(-2, -2);
305 p2.addPoint(2, -2);
306
307 this.downArrow = p2;
308
309 Polygon p3 = new Polygon();
310 p3.addPoint(0, 0);
311 p3.addPoint(-2, -2);
312 p3.addPoint(-2, 2);
313
314 this.rightArrow = p3;
315
316 Polygon p4 = new Polygon();
317 p4.addPoint(0, 0);
318 p4.addPoint(2, -2);
319 p4.addPoint(2, 2);
320
321 this.leftArrow = p4;
322
323 this.verticalTickLabels = false;
324
325 }
326
327 /**
328 * Returns <code>true</code> if the tick labels should be rotated (to
329 * vertical), and <code>false</code> otherwise.
330 *
331 * @return <code>true</code> or <code>false</code>.
332 *
333 * @see #setVerticalTickLabels(boolean)
334 */
335 public boolean isVerticalTickLabels() {
336 return this.verticalTickLabels;
337 }
338
339 /**
340 * Sets the flag that controls whether the tick labels are displayed
341 * vertically (that is, rotated 90 degrees from horizontal). If the flag
342 * is changed, an {@link AxisChangeEvent} is sent to all registered
343 * listeners.
344 *
345 * @param flag the flag.
346 *
347 * @see #isVerticalTickLabels()
348 */
349 public void setVerticalTickLabels(boolean flag) {
350 if (this.verticalTickLabels != flag) {
351 this.verticalTickLabels = flag;
352 notifyListeners(new AxisChangeEvent(this));
353 }
354 }
355
356 /**
357 * Returns a flag that controls whether or not the axis line has an arrow
358 * drawn that points in the positive direction for the axis.
359 *
360 * @return A boolean.
361 *
362 * @see #setPositiveArrowVisible(boolean)
363 */
364 public boolean isPositiveArrowVisible() {
365 return this.positiveArrowVisible;
366 }
367
368 /**
369 * Sets a flag that controls whether or not the axis lines has an arrow
370 * drawn that points in the positive direction for the axis, and sends an
371 * {@link AxisChangeEvent} to all registered listeners.
372 *
373 * @param visible the flag.
374 *
375 * @see #isPositiveArrowVisible()
376 */
377 public void setPositiveArrowVisible(boolean visible) {
378 this.positiveArrowVisible = visible;
379 notifyListeners(new AxisChangeEvent(this));
380 }
381
382 /**
383 * Returns a flag that controls whether or not the axis line has an arrow
384 * drawn that points in the negative direction for the axis.
385 *
386 * @return A boolean.
387 *
388 * @see #setNegativeArrowVisible(boolean)
389 */
390 public boolean isNegativeArrowVisible() {
391 return this.negativeArrowVisible;
392 }
393
394 /**
395 * Sets a flag that controls whether or not the axis lines has an arrow
396 * drawn that points in the negative direction for the axis, and sends an
397 * {@link AxisChangeEvent} to all registered listeners.
398 *
399 * @param visible the flag.
400 *
401 * @see #setNegativeArrowVisible(boolean)
402 */
403 public void setNegativeArrowVisible(boolean visible) {
404 this.negativeArrowVisible = visible;
405 notifyListeners(new AxisChangeEvent(this));
406 }
407
408 /**
409 * Returns a shape that can be displayed as an arrow pointing upwards at
410 * the end of an axis line.
411 *
412 * @return A shape (never <code>null</code>).
413 *
414 * @see #setUpArrow(Shape)
415 */
416 public Shape getUpArrow() {
417 return this.upArrow;
418 }
419
420 /**
421 * Sets the shape that can be displayed as an arrow pointing upwards at
422 * the end of an axis line and sends an {@link AxisChangeEvent} to all
423 * registered listeners.
424 *
425 * @param arrow the arrow shape (<code>null</code> not permitted).
426 *
427 * @see #getUpArrow()
428 */
429 public void setUpArrow(Shape arrow) {
430 if (arrow == null) {
431 throw new IllegalArgumentException("Null 'arrow' argument.");
432 }
433 this.upArrow = arrow;
434 notifyListeners(new AxisChangeEvent(this));
435 }
436
437 /**
438 * Returns a shape that can be displayed as an arrow pointing downwards at
439 * the end of an axis line.
440 *
441 * @return A shape (never <code>null</code>).
442 *
443 * @see #setDownArrow(Shape)
444 */
445 public Shape getDownArrow() {
446 return this.downArrow;
447 }
448
449 /**
450 * Sets the shape that can be displayed as an arrow pointing downwards at
451 * the end of an axis line and sends an {@link AxisChangeEvent} to all
452 * registered listeners.
453 *
454 * @param arrow the arrow shape (<code>null</code> not permitted).
455 *
456 * @see #getDownArrow()
457 */
458 public void setDownArrow(Shape arrow) {
459 if (arrow == null) {
460 throw new IllegalArgumentException("Null 'arrow' argument.");
461 }
462 this.downArrow = arrow;
463 notifyListeners(new AxisChangeEvent(this));
464 }
465
466 /**
467 * Returns a shape that can be displayed as an arrow pointing left at the
468 * end of an axis line.
469 *
470 * @return A shape (never <code>null</code>).
471 *
472 * @see #setLeftArrow(Shape)
473 */
474 public Shape getLeftArrow() {
475 return this.leftArrow;
476 }
477
478 /**
479 * Sets the shape that can be displayed as an arrow pointing left at the
480 * end of an axis line and sends an {@link AxisChangeEvent} to all
481 * registered listeners.
482 *
483 * @param arrow the arrow shape (<code>null</code> not permitted).
484 *
485 * @see #getLeftArrow()
486 */
487 public void setLeftArrow(Shape arrow) {
488 if (arrow == null) {
489 throw new IllegalArgumentException("Null 'arrow' argument.");
490 }
491 this.leftArrow = arrow;
492 notifyListeners(new AxisChangeEvent(this));
493 }
494
495 /**
496 * Returns a shape that can be displayed as an arrow pointing right at the
497 * end of an axis line.
498 *
499 * @return A shape (never <code>null</code>).
500 *
501 * @see #setRightArrow(Shape)
502 */
503 public Shape getRightArrow() {
504 return this.rightArrow;
505 }
506
507 /**
508 * Sets the shape that can be displayed as an arrow pointing rightwards at
509 * the end of an axis line and sends an {@link AxisChangeEvent} to all
510 * registered listeners.
511 *
512 * @param arrow the arrow shape (<code>null</code> not permitted).
513 *
514 * @see #getRightArrow()
515 */
516 public void setRightArrow(Shape arrow) {
517 if (arrow == null) {
518 throw new IllegalArgumentException("Null 'arrow' argument.");
519 }
520 this.rightArrow = arrow;
521 notifyListeners(new AxisChangeEvent(this));
522 }
523
524 /**
525 * Draws an axis line at the current cursor position and edge.
526 *
527 * @param g2 the graphics device.
528 * @param cursor the cursor position.
529 * @param dataArea the data area.
530 * @param edge the edge.
531 */
532 protected void drawAxisLine(Graphics2D g2, double cursor,
533 Rectangle2D dataArea, RectangleEdge edge) {
534 Line2D axisLine = null;
535 if (edge == RectangleEdge.TOP) {
536 axisLine = new Line2D.Double(dataArea.getX(), cursor,
537 dataArea.getMaxX(), cursor);
538 }
539 else if (edge == RectangleEdge.BOTTOM) {
540 axisLine = new Line2D.Double(dataArea.getX(), cursor,
541 dataArea.getMaxX(), cursor);
542 }
543 else if (edge == RectangleEdge.LEFT) {
544 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
545 dataArea.getMaxY());
546 }
547 else if (edge == RectangleEdge.RIGHT) {
548 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
549 dataArea.getMaxY());
550 }
551 g2.setPaint(getAxisLinePaint());
552 g2.setStroke(getAxisLineStroke());
553 g2.draw(axisLine);
554
555 boolean drawUpOrRight = false;
556 boolean drawDownOrLeft = false;
557 if (this.positiveArrowVisible) {
558 if (this.inverted) {
559 drawDownOrLeft = true;
560 }
561 else {
562 drawUpOrRight = true;
563 }
564 }
565 if (this.negativeArrowVisible) {
566 if (this.inverted) {
567 drawUpOrRight = true;
568 }
569 else {
570 drawDownOrLeft = true;
571 }
572 }
573 if (drawUpOrRight) {
574 double x = 0.0;
575 double y = 0.0;
576 Shape arrow = null;
577 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
578 x = dataArea.getMaxX();
579 y = cursor;
580 arrow = this.rightArrow;
581 }
582 else if (edge == RectangleEdge.LEFT
583 || edge == RectangleEdge.RIGHT) {
584 x = cursor;
585 y = dataArea.getMinY();
586 arrow = this.upArrow;
587 }
588
589 // draw the arrow...
590 AffineTransform transformer = new AffineTransform();
591 transformer.setToTranslation(x, y);
592 Shape shape = transformer.createTransformedShape(arrow);
593 g2.fill(shape);
594 g2.draw(shape);
595 }
596
597 if (drawDownOrLeft) {
598 double x = 0.0;
599 double y = 0.0;
600 Shape arrow = null;
601 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
602 x = dataArea.getMinX();
603 y = cursor;
604 arrow = this.leftArrow;
605 }
606 else if (edge == RectangleEdge.LEFT
607 || edge == RectangleEdge.RIGHT) {
608 x = cursor;
609 y = dataArea.getMaxY();
610 arrow = this.downArrow;
611 }
612
613 // draw the arrow...
614 AffineTransform transformer = new AffineTransform();
615 transformer.setToTranslation(x, y);
616 Shape shape = transformer.createTransformedShape(arrow);
617 g2.fill(shape);
618 g2.draw(shape);
619 }
620
621 }
622
623 /**
624 * Calculates the anchor point for a tick label.
625 *
626 * @param tick the tick.
627 * @param cursor the cursor.
628 * @param dataArea the data area.
629 * @param edge the edge on which the axis is drawn.
630 *
631 * @return The x and y coordinates of the anchor point.
632 */
633 protected float[] calculateAnchorPoint(ValueTick tick,
634 double cursor,
635 Rectangle2D dataArea,
636 RectangleEdge edge) {
637
638 RectangleInsets insets = getTickLabelInsets();
639 float[] result = new float[2];
640 if (edge == RectangleEdge.TOP) {
641 result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
642 result[1] = (float) (cursor - insets.getBottom() - 2.0);
643 }
644 else if (edge == RectangleEdge.BOTTOM) {
645 result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
646 result[1] = (float) (cursor + insets.getTop() + 2.0);
647 }
648 else if (edge == RectangleEdge.LEFT) {
649 result[0] = (float) (cursor - insets.getLeft() - 2.0);
650 result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
651 }
652 else if (edge == RectangleEdge.RIGHT) {
653 result[0] = (float) (cursor + insets.getRight() + 2.0);
654 result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
655 }
656 return result;
657 }
658
659 /**
660 * Draws the axis line, tick marks and tick mark labels.
661 *
662 * @param g2 the graphics device.
663 * @param cursor the cursor.
664 * @param plotArea the plot area.
665 * @param dataArea the data area.
666 * @param edge the edge that the axis is aligned with.
667 *
668 * @return The width or height used to draw the axis.
669 */
670 protected AxisState drawTickMarksAndLabels(Graphics2D g2,
671 double cursor,
672 Rectangle2D plotArea,
673 Rectangle2D dataArea,
674 RectangleEdge edge) {
675
676 AxisState state = new AxisState(cursor);
677
678 if (isAxisLineVisible()) {
679 drawAxisLine(g2, cursor, dataArea, edge);
680 }
681
682 double ol = getTickMarkOutsideLength();
683 double il = getTickMarkInsideLength();
684
685 List ticks = refreshTicks(g2, state, dataArea, edge);
686 state.setTicks(ticks);
687 g2.setFont(getTickLabelFont());
688 Iterator iterator = ticks.iterator();
689 while (iterator.hasNext()) {
690 ValueTick tick = (ValueTick) iterator.next();
691 if (isTickLabelsVisible()) {
692 g2.setPaint(getTickLabelPaint());
693 float[] anchorPoint = calculateAnchorPoint(tick, cursor,
694 dataArea, edge);
695 TextUtilities.drawRotatedString(tick.getText(), g2,
696 anchorPoint[0], anchorPoint[1], tick.getTextAnchor(),
697 tick.getAngle(), tick.getRotationAnchor());
698 }
699
700 if (isTickMarksVisible()) {
701 float xx = (float) valueToJava2D(tick.getValue(), dataArea,
702 edge);
703 Line2D mark = null;
704 g2.setStroke(getTickMarkStroke());
705 g2.setPaint(getTickMarkPaint());
706 if (edge == RectangleEdge.LEFT) {
707 mark = new Line2D.Double(cursor - ol, xx, cursor + il, xx);
708 }
709 else if (edge == RectangleEdge.RIGHT) {
710 mark = new Line2D.Double(cursor + ol, xx, cursor - il, xx);
711 }
712 else if (edge == RectangleEdge.TOP) {
713 mark = new Line2D.Double(xx, cursor - ol, xx, cursor + il);
714 }
715 else if (edge == RectangleEdge.BOTTOM) {
716 mark = new Line2D.Double(xx, cursor + ol, xx, cursor - il);
717 }
718 g2.draw(mark);
719 }
720 }
721
722 // need to work out the space used by the tick labels...
723 // so we can update the cursor...
724 double used = 0.0;
725 if (isTickLabelsVisible()) {
726 if (edge == RectangleEdge.LEFT) {
727 used += findMaximumTickLabelWidth(ticks, g2, plotArea,
728 isVerticalTickLabels());
729 state.cursorLeft(used);
730 }
731 else if (edge == RectangleEdge.RIGHT) {
732 used = findMaximumTickLabelWidth(ticks, g2, plotArea,
733 isVerticalTickLabels());
734 state.cursorRight(used);
735 }
736 else if (edge == RectangleEdge.TOP) {
737 used = findMaximumTickLabelHeight(ticks, g2, plotArea,
738 isVerticalTickLabels());
739 state.cursorUp(used);
740 }
741 else if (edge == RectangleEdge.BOTTOM) {
742 used = findMaximumTickLabelHeight(ticks, g2, plotArea,
743 isVerticalTickLabels());
744 state.cursorDown(used);
745 }
746 }
747
748 return state;
749 }
750
751 /**
752 * Returns the space required to draw the axis.
753 *
754 * @param g2 the graphics device.
755 * @param plot the plot that the axis belongs to.
756 * @param plotArea the area within which the plot should be drawn.
757 * @param edge the axis location.
758 * @param space the space already reserved (for other axes).
759 *
760 * @return The space required to draw the axis (including pre-reserved
761 * space).
762 */
763 public AxisSpace reserveSpace(Graphics2D g2, Plot plot,
764 Rectangle2D plotArea,
765 RectangleEdge edge, AxisSpace space) {
766
767 // create a new space object if one wasn't supplied...
768 if (space == null) {
769 space = new AxisSpace();
770 }
771
772 // if the axis is not visible, no additional space is required...
773 if (!isVisible()) {
774 return space;
775 }
776
777 // if the axis has a fixed dimension, return it...
778 double dimension = getFixedDimension();
779 if (dimension > 0.0) {
780 space.ensureAtLeast(dimension, edge);
781 }
782
783 // calculate the max size of the tick labels (if visible)...
784 double tickLabelHeight = 0.0;
785 double tickLabelWidth = 0.0;
786 if (isTickLabelsVisible()) {
787 g2.setFont(getTickLabelFont());
788 List ticks = refreshTicks(g2, new AxisState(), plotArea, edge);
789 if (RectangleEdge.isTopOrBottom(edge)) {
790 tickLabelHeight = findMaximumTickLabelHeight(ticks, g2,
791 plotArea, isVerticalTickLabels());
792 }
793 else if (RectangleEdge.isLeftOrRight(edge)) {
794 tickLabelWidth = findMaximumTickLabelWidth(ticks, g2, plotArea,
795 isVerticalTickLabels());
796 }
797 }
798
799 // get the axis label size and update the space object...
800 Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge);
801 double labelHeight = 0.0;
802 double labelWidth = 0.0;
803 if (RectangleEdge.isTopOrBottom(edge)) {
804 labelHeight = labelEnclosure.getHeight();
805 space.add(labelHeight + tickLabelHeight, edge);
806 }
807 else if (RectangleEdge.isLeftOrRight(edge)) {
808 labelWidth = labelEnclosure.getWidth();
809 space.add(labelWidth + tickLabelWidth, edge);
810 }
811
812 return space;
813
814 }
815
816 /**
817 * A utility method for determining the height of the tallest tick label.
818 *
819 * @param ticks the ticks.
820 * @param g2 the graphics device.
821 * @param drawArea the area within which the plot and axes should be drawn.
822 * @param vertical a flag that indicates whether or not the tick labels
823 * are 'vertical'.
824 *
825 * @return The height of the tallest tick label.
826 */
827 protected double findMaximumTickLabelHeight(List ticks,
828 Graphics2D g2,
829 Rectangle2D drawArea,
830 boolean vertical) {
831
832 RectangleInsets insets = getTickLabelInsets();
833 Font font = getTickLabelFont();
834 double maxHeight = 0.0;
835 if (vertical) {
836 FontMetrics fm = g2.getFontMetrics(font);
837 Iterator iterator = ticks.iterator();
838 while (iterator.hasNext()) {
839 Tick tick = (Tick) iterator.next();
840 Rectangle2D labelBounds = TextUtilities.getTextBounds(
841 tick.getText(), g2, fm);
842 if (labelBounds.getWidth() + insets.getTop()
843 + insets.getBottom() > maxHeight) {
844 maxHeight = labelBounds.getWidth()
845 + insets.getTop() + insets.getBottom();
846 }
847 }
848 }
849 else {
850 LineMetrics metrics = font.getLineMetrics("ABCxyz",
851 g2.getFontRenderContext());
852 maxHeight = metrics.getHeight()
853 + insets.getTop() + insets.getBottom();
854 }
855 return maxHeight;
856
857 }
858
859 /**
860 * A utility method for determining the width of the widest tick label.
861 *
862 * @param ticks the ticks.
863 * @param g2 the graphics device.
864 * @param drawArea the area within which the plot and axes should be drawn.
865 * @param vertical a flag that indicates whether or not the tick labels
866 * are 'vertical'.
867 *
868 * @return The width of the tallest tick label.
869 */
870 protected double findMaximumTickLabelWidth(List ticks,
871 Graphics2D g2,
872 Rectangle2D drawArea,
873 boolean vertical) {
874
875 RectangleInsets insets = getTickLabelInsets();
876 Font font = getTickLabelFont();
877 double maxWidth = 0.0;
878 if (!vertical) {
879 FontMetrics fm = g2.getFontMetrics(font);
880 Iterator iterator = ticks.iterator();
881 while (iterator.hasNext()) {
882 Tick tick = (Tick) iterator.next();
883 Rectangle2D labelBounds = TextUtilities.getTextBounds(
884 tick.getText(), g2, fm);
885 if (labelBounds.getWidth() + insets.getLeft()
886 + insets.getRight() > maxWidth) {
887 maxWidth = labelBounds.getWidth()
888 + insets.getLeft() + insets.getRight();
889 }
890 }
891 }
892 else {
893 LineMetrics metrics = font.getLineMetrics("ABCxyz",
894 g2.getFontRenderContext());
895 maxWidth = metrics.getHeight()
896 + insets.getTop() + insets.getBottom();
897 }
898 return maxWidth;
899
900 }
901
902 /**
903 * Returns a flag that controls the direction of values on the axis.
904 * <P>
905 * For a regular axis, values increase from left to right (for a horizontal
906 * axis) and bottom to top (for a vertical axis). When the axis is
907 * 'inverted', the values increase in the opposite direction.
908 *
909 * @return The flag.
910 *
911 * @see #setInverted(boolean)
912 */
913 public boolean isInverted() {
914 return this.inverted;
915 }
916
917 /**
918 * Sets a flag that controls the direction of values on the axis, and
919 * notifies registered listeners that the axis has changed.
920 *
921 * @param flag the flag.
922 *
923 * @see #isInverted()
924 */
925 public void setInverted(boolean flag) {
926
927 if (this.inverted != flag) {
928 this.inverted = flag;
929 notifyListeners(new AxisChangeEvent(this));
930 }
931
932 }
933
934 /**
935 * Returns the flag that controls whether or not the axis range is
936 * automatically adjusted to fit the data values.
937 *
938 * @return The flag.
939 *
940 * @see #setAutoRange(boolean)
941 */
942 public boolean isAutoRange() {
943 return this.autoRange;
944 }
945
946 /**
947 * Sets a flag that determines whether or not the axis range is
948 * automatically adjusted to fit the data, and notifies registered
949 * listeners that the axis has been modified.
950 *
951 * @param auto the new value of the flag.
952 *
953 * @see #isAutoRange()
954 */
955 public void setAutoRange(boolean auto) {
956 setAutoRange(auto, true);
957 }
958
959 /**
960 * Sets the auto range attribute. If the <code>notify</code> flag is set,
961 * an {@link AxisChangeEvent} is sent to registered listeners.
962 *
963 * @param auto the flag.
964 * @param notify notify listeners?
965 *
966 * @see #isAutoRange()
967 */
968 protected void setAutoRange(boolean auto, boolean notify) {
969 if (this.autoRange != auto) {
970 this.autoRange = auto;
971 if (this.autoRange) {
972 autoAdjustRange();
973 }
974 if (notify) {
975 notifyListeners(new AxisChangeEvent(this));
976 }
977 }
978 }
979
980 /**
981 * Returns the minimum size allowed for the axis range when it is
982 * automatically calculated.
983 *
984 * @return The minimum range.
985 *
986 * @see #setAutoRangeMinimumSize(double)
987 */
988 public double getAutoRangeMinimumSize() {
989 return this.autoRangeMinimumSize;
990 }
991
992 /**
993 * Sets the auto range minimum size and sends an {@link AxisChangeEvent}
994 * to all registered listeners.
995 *
996 * @param size the size.
997 *
998 * @see #getAutoRangeMinimumSize()
999 */
1000 public void setAutoRangeMinimumSize(double size) {
1001 setAutoRangeMinimumSize(size, true);
1002 }
1003
1004 /**
1005 * Sets the minimum size allowed for the axis range when it is
1006 * automatically calculated.
1007 * <p>
1008 * If requested, an {@link AxisChangeEvent} is forwarded to all registered
1009 * listeners.
1010 *
1011 * @param size the new minimum.
1012 * @param notify notify listeners?
1013 */
1014 public void setAutoRangeMinimumSize(double size, boolean notify) {
1015 if (size <= 0.0) {
1016 throw new IllegalArgumentException(
1017 "NumberAxis.setAutoRangeMinimumSize(double): must be > 0.0.");
1018 }
1019 if (this.autoRangeMinimumSize != size) {
1020 this.autoRangeMinimumSize = size;
1021 if (this.autoRange) {
1022 autoAdjustRange();
1023 }
1024 if (notify) {
1025 notifyListeners(new AxisChangeEvent(this));
1026 }
1027 }
1028
1029 }
1030
1031 /**
1032 * Returns the default auto range.
1033 *
1034 * @return The default auto range (never <code>null</code>).
1035 *
1036 * @see #setDefaultAutoRange(Range)
1037 * @since 1.0.5
1038 */
1039 public Range getDefaultAutoRange() {
1040 return this.defaultAutoRange;
1041 }
1042
1043 /**
1044 * Sets the default auto range and sends an {@link AxisChangeEvent} to all
1045 * registered listeners.
1046 *
1047 * @param range the range (<code>null</code> not permitted).
1048 *
1049 * @see #getDefaultAutoRange()
1050 *
1051 * @since 1.0.5
1052 */
1053 public void setDefaultAutoRange(Range range) {
1054 if (range == null) {
1055 throw new IllegalArgumentException("Null 'range' argument.");
1056 }
1057 this.defaultAutoRange = range;
1058 notifyListeners(new AxisChangeEvent(this));
1059 }
1060
1061 /**
1062 * Returns the lower margin for the axis, expressed as a percentage of the
1063 * axis range. This controls the space added to the lower end of the axis
1064 * when the axis range is automatically calculated (it is ignored when the
1065 * axis range is set explicitly). The default value is 0.05 (five percent).
1066 *
1067 * @return The lower margin.
1068 *
1069 * @see #setLowerMargin(double)
1070 */
1071 public double getLowerMargin() {
1072 return this.lowerMargin;
1073 }
1074
1075 /**
1076 * Sets the lower margin for the axis (as a percentage of the axis range)
1077 * and sends an {@link AxisChangeEvent} to all registered listeners. This
1078 * margin is added only when the axis range is auto-calculated - if you set
1079 * the axis range manually, the margin is ignored.
1080 *
1081 * @param margin the margin percentage (for example, 0.05 is five percent).
1082 *
1083 * @see #getLowerMargin()
1084 * @see #setUpperMargin(double)
1085 */
1086 public void setLowerMargin(double margin) {
1087 this.lowerMargin = margin;
1088 if (isAutoRange()) {
1089 autoAdjustRange();
1090 }
1091 notifyListeners(new AxisChangeEvent(this));
1092 }
1093
1094 /**
1095 * Returns the upper margin for the axis, expressed as a percentage of the
1096 * axis range. This controls the space added to the lower end of the axis
1097 * when the axis range is automatically calculated (it is ignored when the
1098 * axis range is set explicitly). The default value is 0.05 (five percent).
1099 *
1100 * @return The upper margin.
1101 *
1102 * @see #setUpperMargin(double)
1103 */
1104 public double getUpperMargin() {
1105 return this.upperMargin;
1106 }
1107
1108 /**
1109 * Sets the upper margin for the axis (as a percentage of the axis range)
1110 * and sends an {@link AxisChangeEvent} to all registered listeners. This
1111 * margin is added only when the axis range is auto-calculated - if you set
1112 * the axis range manually, the margin is ignored.
1113 *
1114 * @param margin the margin percentage (for example, 0.05 is five percent).
1115 *
1116 * @see #getLowerMargin()
1117 * @see #setLowerMargin(double)
1118 */
1119 public void setUpperMargin(double margin) {
1120 this.upperMargin = margin;
1121 if (isAutoRange()) {
1122 autoAdjustRange();
1123 }
1124 notifyListeners(new AxisChangeEvent(this));
1125 }
1126
1127 /**
1128 * Returns the fixed auto range.
1129 *
1130 * @return The length.
1131 *
1132 * @see #setFixedAutoRange(double)
1133 */
1134 public double getFixedAutoRange() {
1135 return this.fixedAutoRange;
1136 }
1137
1138 /**
1139 * Sets the fixed auto range for the axis.
1140 *
1141 * @param length the range length.
1142 *
1143 * @see #getFixedAutoRange()
1144 */
1145 public void setFixedAutoRange(double length) {
1146 this.fixedAutoRange = length;
1147 if (isAutoRange()) {
1148 autoAdjustRange();
1149 }
1150 notifyListeners(new AxisChangeEvent(this));
1151 }
1152
1153 /**
1154 * Returns the lower bound of the axis range.
1155 *
1156 * @return The lower bound.
1157 *
1158 * @see #setLowerBound(double)
1159 */
1160 public double getLowerBound() {
1161 return this.range.getLowerBound();
1162 }
1163
1164 /**
1165 * Sets the lower bound for the axis range. An {@link AxisChangeEvent} is
1166 * sent to all registered listeners.
1167 *
1168 * @param min the new minimum.
1169 *
1170 * @see #getLowerBound()
1171 */
1172 public void setLowerBound(double min) {
1173 if (this.range.getUpperBound() > min) {
1174 setRange(new Range(min, this.range.getUpperBound()));
1175 }
1176 else {
1177 setRange(new Range(min, min + 1.0));
1178 }
1179 }
1180
1181 /**
1182 * Returns the upper bound for the axis range.
1183 *
1184 * @return The upper bound.
1185 *
1186 * @see #setUpperBound(double)
1187 */
1188 public double getUpperBound() {
1189 return this.range.getUpperBound();
1190 }
1191
1192 /**
1193 * Sets the upper bound for the axis range, and sends an
1194 * {@link AxisChangeEvent} to all registered listeners.
1195 *
1196 * @param max the new maximum.
1197 *
1198 * @see #getUpperBound()
1199 */
1200 public void setUpperBound(double max) {
1201 if (this.range.getLowerBound() < max) {
1202 setRange(new Range(this.range.getLowerBound(), max));
1203 }
1204 else {
1205 setRange(max - 1.0, max);
1206 }
1207 }
1208
1209 /**
1210 * Returns the range for the axis.
1211 *
1212 * @return The axis range (never <code>null</code>).
1213 *
1214 * @see #setRange(Range)
1215 */
1216 public Range getRange() {
1217 return this.range;
1218 }
1219
1220 /**
1221 * Sets the range attribute and sends an {@link AxisChangeEvent} to all
1222 * registered listeners. As a side-effect, the auto-range flag is set to
1223 * <code>false</code>.
1224 *
1225 * @param range the range (<code>null</code> not permitted).
1226 *
1227 * @see #getRange()
1228 */
1229 public void setRange(Range range) {
1230 // defer argument checking
1231 setRange(range, true, true);
1232 }
1233
1234 /**
1235 * Sets the range for the axis, if requested, sends an
1236 * {@link AxisChangeEvent} to all registered listeners. As a side-effect,
1237 * the auto-range flag is set to <code>false</code> (optional).
1238 *
1239 * @param range the range (<code>null</code> not permitted).
1240 * @param turnOffAutoRange a flag that controls whether or not the auto
1241 * range is turned off.
1242 * @param notify a flag that controls whether or not listeners are
1243 * notified.
1244 *
1245 * @see #getRange()
1246 */
1247 public void setRange(Range range, boolean turnOffAutoRange,
1248 boolean notify) {
1249 if (range == null) {
1250 throw new IllegalArgumentException("Null 'range' argument.");
1251 }
1252 if (turnOffAutoRange) {
1253 this.autoRange = false;
1254 }
1255 this.range = range;
1256 if (notify) {
1257 notifyListeners(new AxisChangeEvent(this));
1258 }
1259 }
1260
1261 /**
1262 * Sets the axis range and sends an {@link AxisChangeEvent} to all
1263 * registered listeners. As a side-effect, the auto-range flag is set to
1264 * <code>false</code>.
1265 *
1266 * @param lower the lower axis limit.
1267 * @param upper the upper axis limit.
1268 *
1269 * @see #getRange()
1270 * @see #setRange(Range)
1271 */
1272 public void setRange(double lower, double upper) {
1273 setRange(new Range(lower, upper));
1274 }
1275
1276 /**
1277 * Sets the range for the axis (after first adding the current margins to
1278 * the specified range) and sends an {@link AxisChangeEvent} to all
1279 * registered listeners.
1280 *
1281 * @param range the range (<code>null</code> not permitted).
1282 */
1283 public void setRangeWithMargins(Range range) {
1284 setRangeWithMargins(range, true, true);
1285 }
1286
1287 /**
1288 * Sets the range for the axis after first adding the current margins to
1289 * the range and, if requested, sends an {@link AxisChangeEvent} to all
1290 * registered listeners. As a side-effect, the auto-range flag is set to
1291 * <code>false</code> (optional).
1292 *
1293 * @param range the range (excluding margins, <code>null</code> not
1294 * permitted).
1295 * @param turnOffAutoRange a flag that controls whether or not the auto
1296 * range is turned off.
1297 * @param notify a flag that controls whether or not listeners are
1298 * notified.
1299 */
1300 public void setRangeWithMargins(Range range, boolean turnOffAutoRange,
1301 boolean notify) {
1302 if (range == null) {
1303 throw new IllegalArgumentException("Null 'range' argument.");
1304 }
1305 setRange(Range.expand(range, getLowerMargin(), getUpperMargin()),
1306 turnOffAutoRange, notify);
1307 }
1308
1309 /**
1310 * Sets the axis range (after first adding the current margins to the
1311 * range) and sends an {@link AxisChangeEvent} to all registered listeners.
1312 * As a side-effect, the auto-range flag is set to <code>false</code>.
1313 *
1314 * @param lower the lower axis limit.
1315 * @param upper the upper axis limit.
1316 */
1317 public void setRangeWithMargins(double lower, double upper) {
1318 setRangeWithMargins(new Range(lower, upper));
1319 }
1320
1321 /**
1322 * Sets the axis range, where the new range is 'size' in length, and
1323 * centered on 'value'.
1324 *
1325 * @param value the central value.
1326 * @param length the range length.
1327 */
1328 public void setRangeAboutValue(double value, double length) {
1329 setRange(new Range(value - length / 2, value + length / 2));
1330 }
1331
1332 /**
1333 * Returns a flag indicating whether or not the tick unit is automatically
1334 * selected from a range of standard tick units.
1335 *
1336 * @return A flag indicating whether or not the tick unit is automatically
1337 * selected.
1338 *
1339 * @see #setAutoTickUnitSelection(boolean)
1340 */
1341 public boolean isAutoTickUnitSelection() {
1342 return this.autoTickUnitSelection;
1343 }
1344
1345 /**
1346 * Sets a flag indicating whether or not the tick unit is automatically
1347 * selected from a range of standard tick units. If the flag is changed,
1348 * registered listeners are notified that the chart has changed.
1349 *
1350 * @param flag the new value of the flag.
1351 *
1352 * @see #isAutoTickUnitSelection()
1353 */
1354 public void setAutoTickUnitSelection(boolean flag) {
1355 setAutoTickUnitSelection(flag, true);
1356 }
1357
1358 /**
1359 * Sets a flag indicating whether or not the tick unit is automatically
1360 * selected from a range of standard tick units.
1361 *
1362 * @param flag the new value of the flag.
1363 * @param notify notify listeners?
1364 *
1365 * @see #isAutoTickUnitSelection()
1366 */
1367 public void setAutoTickUnitSelection(boolean flag, boolean notify) {
1368
1369 if (this.autoTickUnitSelection != flag) {
1370 this.autoTickUnitSelection = flag;
1371 if (notify) {
1372 notifyListeners(new AxisChangeEvent(this));
1373 }
1374 }
1375 }
1376
1377 /**
1378 * Returns the source for obtaining standard tick units for the axis.
1379 *
1380 * @return The source (possibly <code>null</code>).
1381 *
1382 * @see #setStandardTickUnits(TickUnitSource)
1383 */
1384 public TickUnitSource getStandardTickUnits() {
1385 return this.standardTickUnits;
1386 }
1387
1388 /**
1389 * Sets the source for obtaining standard tick units for the axis and sends
1390 * an {@link AxisChangeEvent} to all registered listeners. The axis will
1391 * try to select the smallest tick unit from the source that does not cause
1392 * the tick labels to overlap (see also the
1393 * {@link #setAutoTickUnitSelection(boolean)} method.
1394 *
1395 * @param source the source for standard tick units (<code>null</code>
1396 * permitted).
1397 *
1398 * @see #getStandardTickUnits()
1399 */
1400 public void setStandardTickUnits(TickUnitSource source) {
1401 this.standardTickUnits = source;
1402 notifyListeners(new AxisChangeEvent(this));
1403 }
1404
1405 /**
1406 * Converts a data value to a coordinate in Java2D space, assuming that the
1407 * axis runs along one edge of the specified dataArea.
1408 * <p>
1409 * Note that it is possible for the coordinate to fall outside the area.
1410 *
1411 * @param value the data value.
1412 * @param area the area for plotting the data.
1413 * @param edge the edge along which the axis lies.
1414 *
1415 * @return The Java2D coordinate.
1416 *
1417 * @see #java2DToValue(double, Rectangle2D, RectangleEdge)
1418 */
1419 public abstract double valueToJava2D(double value, Rectangle2D area,
1420 RectangleEdge edge);
1421
1422 /**
1423 * Converts a length in data coordinates into the corresponding length in
1424 * Java2D coordinates.
1425 *
1426 * @param length the length.
1427 * @param area the plot area.
1428 * @param edge the edge along which the axis lies.
1429 *
1430 * @return The length in Java2D coordinates.
1431 */
1432 public double lengthToJava2D(double length, Rectangle2D area,
1433 RectangleEdge edge) {
1434 double zero = valueToJava2D(0.0, area, edge);
1435 double l = valueToJava2D(length, area, edge);
1436 return Math.abs(l - zero);
1437 }
1438
1439 /**
1440 * Converts a coordinate in Java2D space to the corresponding data value,
1441 * assuming that the axis runs along one edge of the specified dataArea.
1442 *
1443 * @param java2DValue the coordinate in Java2D space.
1444 * @param area the area in which the data is plotted.
1445 * @param edge the edge along which the axis lies.
1446 *
1447 * @return The data value.
1448 *
1449 * @see #valueToJava2D(double, Rectangle2D, RectangleEdge)
1450 */
1451 public abstract double java2DToValue(double java2DValue,
1452 Rectangle2D area,
1453 RectangleEdge edge);
1454
1455 /**
1456 * Automatically sets the axis range to fit the range of values in the
1457 * dataset. Sometimes this can depend on the renderer used as well (for
1458 * example, the renderer may "stack" values, requiring an axis range
1459 * greater than otherwise necessary).
1460 */
1461 protected abstract void autoAdjustRange();
1462
1463 /**
1464 * Centers the axis range about the specified value and sends an
1465 * {@link AxisChangeEvent} to all registered listeners.
1466 *
1467 * @param value the center value.
1468 */
1469 public void centerRange(double value) {
1470
1471 double central = this.range.getCentralValue();
1472 Range adjusted = new Range(this.range.getLowerBound() + value - central,
1473 this.range.getUpperBound() + value - central);
1474 setRange(adjusted);
1475
1476 }
1477
1478 /**
1479 * Increases or decreases the axis range by the specified percentage about
1480 * the central value and sends an {@link AxisChangeEvent} to all registered
1481 * listeners.
1482 * <P>
1483 * To double the length of the axis range, use 200% (2.0).
1484 * To halve the length of the axis range, use 50% (0.5).
1485 *
1486 * @param percent the resize factor.
1487 */
1488 public void resizeRange(double percent) {
1489 resizeRange(percent, this.range.getCentralValue());
1490 }
1491
1492 /**
1493 * Increases or decreases the axis range by the specified percentage about
1494 * the specified anchor value and sends an {@link AxisChangeEvent} to all
1495 * registered listeners.
1496 * <P>
1497 * To double the length of the axis range, use 200% (2.0).
1498 * To halve the length of the axis range, use 50% (0.5).
1499 *
1500 * @param percent the resize factor.
1501 * @param anchorValue the new central value after the resize.
1502 */
1503 public void resizeRange(double percent, double anchorValue) {
1504 if (percent > 0.0) {
1505 double halfLength = this.range.getLength() * percent / 2;
1506 Range adjusted = new Range(anchorValue - halfLength,
1507 anchorValue + halfLength);
1508 setRange(adjusted);
1509 }
1510 else {
1511 setAutoRange(true);
1512 }
1513 }
1514
1515 /**
1516 * Zooms in on the current range.
1517 *
1518 * @param lowerPercent the new lower bound.
1519 * @param upperPercent the new upper bound.
1520 */
1521 public void zoomRange(double lowerPercent, double upperPercent) {
1522 double start = this.range.getLowerBound();
1523 double length = this.range.getLength();
1524 Range adjusted = null;
1525 if (isInverted()) {
1526 adjusted = new Range(start + (length * (1 - upperPercent)),
1527 start + (length * (1 - lowerPercent)));
1528 }
1529 else {
1530 adjusted = new Range(start + length * lowerPercent,
1531 start + length * upperPercent);
1532 }
1533 setRange(adjusted);
1534 }
1535
1536 /**
1537 * Returns the auto tick index.
1538 *
1539 * @return The auto tick index.
1540 *
1541 * @see #setAutoTickIndex(int)
1542 */
1543 protected int getAutoTickIndex() {
1544 return this.autoTickIndex;
1545 }
1546
1547 /**
1548 * Sets the auto tick index.
1549 *
1550 * @param index the new value.
1551 *
1552 * @see #getAutoTickIndex()
1553 */
1554 protected void setAutoTickIndex(int index) {
1555 this.autoTickIndex = index;
1556 }
1557
1558 /**
1559 * Tests the axis for equality with an arbitrary object.
1560 *
1561 * @param obj the object (<code>null</code> permitted).
1562 *
1563 * @return <code>true</code> or <code>false</code>.
1564 */
1565 public boolean equals(Object obj) {
1566
1567 if (obj == this) {
1568 return true;
1569 }
1570 if (!(obj instanceof ValueAxis)) {
1571 return false;
1572 }
1573
1574 ValueAxis that = (ValueAxis) obj;
1575
1576 if (this.positiveArrowVisible != that.positiveArrowVisible) {
1577 return false;
1578 }
1579 if (this.negativeArrowVisible != that.negativeArrowVisible) {
1580 return false;
1581 }
1582 if (this.inverted != that.inverted) {
1583 return false;
1584 }
1585 if (!ObjectUtilities.equal(this.range, that.range)) {
1586 return false;
1587 }
1588 if (this.autoRange != that.autoRange) {
1589 return false;
1590 }
1591 if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) {
1592 return false;
1593 }
1594 if (!this.defaultAutoRange.equals(that.defaultAutoRange)) {
1595 return false;
1596 }
1597 if (this.upperMargin != that.upperMargin) {
1598 return false;
1599 }
1600 if (this.lowerMargin != that.lowerMargin) {
1601 return false;
1602 }
1603 if (this.fixedAutoRange != that.fixedAutoRange) {
1604 return false;
1605 }
1606 if (this.autoTickUnitSelection != that.autoTickUnitSelection) {
1607 return false;
1608 }
1609 if (!ObjectUtilities.equal(this.standardTickUnits,
1610 that.standardTickUnits)) {
1611 return false;
1612 }
1613 if (this.verticalTickLabels != that.verticalTickLabels) {
1614 return false;
1615 }
1616
1617 return super.equals(obj);
1618
1619 }
1620
1621 /**
1622 * Returns a clone of the object.
1623 *
1624 * @return A clone.
1625 *
1626 * @throws CloneNotSupportedException if some component of the axis does
1627 * not support cloning.
1628 */
1629 public Object clone() throws CloneNotSupportedException {
1630 ValueAxis clone = (ValueAxis) super.clone();
1631 return clone;
1632 }
1633
1634 /**
1635 * Provides serialization support.
1636 *
1637 * @param stream the output stream.
1638 *
1639 * @throws IOException if there is an I/O error.
1640 */
1641 private void writeObject(ObjectOutputStream stream) throws IOException {
1642 stream.defaultWriteObject();
1643 SerialUtilities.writeShape(this.upArrow, stream);
1644 SerialUtilities.writeShape(this.downArrow, stream);
1645 SerialUtilities.writeShape(this.leftArrow, stream);
1646 SerialUtilities.writeShape(this.rightArrow, stream);
1647 }
1648
1649 /**
1650 * Provides serialization support.
1651 *
1652 * @param stream the input stream.
1653 *
1654 * @throws IOException if there is an I/O error.
1655 * @throws ClassNotFoundException if there is a classpath problem.
1656 */
1657 private void readObject(ObjectInputStream stream)
1658 throws IOException, ClassNotFoundException {
1659
1660 stream.defaultReadObject();
1661 this.upArrow = SerialUtilities.readShape(stream);
1662 this.downArrow = SerialUtilities.readShape(stream);
1663 this.leftArrow = SerialUtilities.readShape(stream);
1664 this.rightArrow = SerialUtilities.readShape(stream);
1665
1666 }
1667
1668 }