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 publihed 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-2008, 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 * Changes
038 * -------
039 * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
040 * 23-Nov-2001 : Overhauled standard tick unit code (DG);
041 * 04-Dec-2001 : Changed constructors to protected, and tidied up default
042 * values (DG);
043 * 12-Dec-2001 : Fixed vertical gridlines bug (DG);
044 * 16-Jan-2002 : Added an optional crosshair, based on the implementation by
045 * Jonathan Nash (DG);
046 * 23-Jan-2002 : Moved the minimum and maximum values to here from NumberAxis,
047 * and changed the type from Number to double (DG);
048 * 25-Feb-2002 : Added default value for autoRange. Changed autoAdjustRange
049 * from public to protected. Updated import statements (DG);
050 * 23-Apr-2002 : Added setRange() method (DG);
051 * 29-Apr-2002 : Added range adjustment methods (DG);
052 * 13-Jun-2002 : Modified setCrosshairValue() to notify listeners only when the
053 * crosshairs are visible, to avoid unnecessary repaints, as
054 * suggested by Kees Kuip (DG);
055 * 25-Jul-2002 : Moved lower and upper margin attributes from the NumberAxis
056 * class (DG);
057 * 05-Sep-2002 : Updated constructor for changes in Axis class (DG);
058 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
059 * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG);
060 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
061 * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG);
062 * 27-Nov-2002 : Moved the 'inverted' attributed from NumberAxis to
063 * ValueAxis (DG);
064 * 03-Jan-2003 : Small fix to ensure auto-range minimum is observed
065 * immediately (DG);
066 * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);
067 * 20-Jan-2003 : Replaced monolithic constructor (DG);
068 * 26-Mar-2003 : Implemented Serializable (DG);
069 * 09-May-2003 : Added AxisLocation parameter to translation methods (DG);
070 * 13-Aug-2003 : Implemented Cloneable (DG);
071 * 01-Sep-2003 : Fixed bug 793167 (setMaximumAxisValue exception) (DG);
072 * 02-Sep-2003 : Fixed bug 795366 (zooming on inverted axes) (DG);
073 * 08-Sep-2003 : Completed Serialization support (NB);
074 * 08-Sep-2003 : Renamed get/setMinimumValue --> get/setLowerBound,
075 * and get/setMaximumValue --> get/setUpperBound (DG);
076 * 27-Oct-2003 : Changed DEFAULT_AUTO_RANGE_MINIMUM_SIZE value - see bug ID
077 * 829606 (DG);
078 * 07-Nov-2003 : Changes to tick mechanism (DG);
079 * 06-Jan-2004 : Moved axis line attributes to Axis class (DG);
080 * 21-Jan-2004 : Removed redundant axisLineVisible attribute. Renamed
081 * translateJava2DToValue --> java2DToValue, and
082 * translateValueToJava2D --> valueToJava2D (DG);
083 * 23-Jan-2004 : Fixed setAxisLinePaint() and setAxisLineStroke() which had no
084 * effect (andreas.gawecki@coremedia.com);
085 * 07-Apr-2004 : Changed text bounds calculation (DG);
086 * 26-Apr-2004 : Added getter/setter methods for arrow shapes (DG);
087 * 18-May-2004 : Added methods to set axis range *including* current
088 * margins (DG);
089 * 02-Jun-2004 : Fixed bug in setRangeWithMargins() method (DG);
090 * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities
091 * --> TextUtilities (DG);
092 * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0
093 * release (DG);
094 * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
095 * ------------- JFREECHART 1.0.x ---------------------------------------------
096 * 10-Oct-2006 : Source reformatting (DG);
097 * 22-Mar-2007 : Added new defaultAutoRange attribute (DG);
098 * 02-Aug-2007 : Check for major tick when drawing label (DG);
099 *
100 */
101
102 package org.jfree.chart.axis;
103
104 import java.awt.Font;
105 import java.awt.FontMetrics;
106 import java.awt.Graphics2D;
107 import java.awt.Polygon;
108 import java.awt.Shape;
109 import java.awt.font.LineMetrics;
110 import java.awt.geom.AffineTransform;
111 import java.awt.geom.Line2D;
112 import java.awt.geom.Rectangle2D;
113 import java.io.IOException;
114 import java.io.ObjectInputStream;
115 import java.io.ObjectOutputStream;
116 import java.io.Serializable;
117 import java.util.Iterator;
118 import java.util.List;
119
120 import org.jfree.chart.event.AxisChangeEvent;
121 import org.jfree.chart.plot.Plot;
122 import org.jfree.data.Range;
123 import org.jfree.io.SerialUtilities;
124 import org.jfree.text.TextUtilities;
125 import org.jfree.ui.RectangleEdge;
126 import org.jfree.ui.RectangleInsets;
127 import org.jfree.util.ObjectUtilities;
128 import org.jfree.util.PublicCloneable;
129
130 /**
131 * The base class for axes that display value data, where values are measured
132 * using the <code>double</code> primitive. The two key subclasses are
133 * {@link DateAxis} and {@link NumberAxis}.
134 */
135 public abstract class ValueAxis extends Axis
136 implements Cloneable, PublicCloneable, Serializable {
137
138 /** For serialization. */
139 private static final long serialVersionUID = 3698345477322391456L;
140
141 /** The default axis range. */
142 public static final Range DEFAULT_RANGE = new Range(0.0, 1.0);
143
144 /** The default auto-range value. */
145 public static final boolean DEFAULT_AUTO_RANGE = true;
146
147 /** The default inverted flag setting. */
148 public static final boolean DEFAULT_INVERTED = false;
149
150 /** The default minimum auto range. */
151 public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001;
152
153 /** The default value for the lower margin (0.05 = 5%). */
154 public static final double DEFAULT_LOWER_MARGIN = 0.05;
155
156 /** The default value for the upper margin (0.05 = 5%). */
157 public static final double DEFAULT_UPPER_MARGIN = 0.05;
158
159 /**
160 * The default lower bound for the axis.
161 *
162 * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
163 * attribute (see {@link #getDefaultAutoRange()}).
164 */
165 public static final double DEFAULT_LOWER_BOUND = 0.0;
166
167 /**
168 * The default upper bound for the axis.
169 *
170 * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
171 * attribute (see {@link #getDefaultAutoRange()}).
172 */
173 public static final double DEFAULT_UPPER_BOUND = 1.0;
174
175 /** The default auto-tick-unit-selection value. */
176 public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true;
177
178 /** The maximum tick count. */
179 public static final int MAXIMUM_TICK_COUNT = 500;
180
181 /**
182 * A flag that controls whether an arrow is drawn at the positive end of
183 * the axis line.
184 */
185 private boolean positiveArrowVisible;
186
187 /**
188 * A flag that controls whether an arrow is drawn at the negative end of
189 * the axis line.
190 */
191 private boolean negativeArrowVisible;
192
193 /** The shape used for an up arrow. */
194 private transient Shape upArrow;
195
196 /** The shape used for a down arrow. */
197 private transient Shape downArrow;
198
199 /** The shape used for a left arrow. */
200 private transient Shape leftArrow;
201
202 /** The shape used for a right arrow. */
203 private transient Shape rightArrow;
204
205 /** A flag that affects the orientation of the values on the axis. */
206 private boolean inverted;
207
208 /** The axis range. */
209 private Range range;
210
211 /**
212 * Flag that indicates whether the axis automatically scales to fit the
213 * chart data.
214 */
215 private boolean autoRange;
216
217 /** The minimum size for the 'auto' axis range (excluding margins). */
218 private double autoRangeMinimumSize;
219
220 /**
221 * The default range is used when the dataset is empty and the axis needs
222 * to determine the auto range.
223 *
224 * @since 1.0.5
225 */
226 private Range defaultAutoRange;
227
228 /**
229 * The upper margin percentage. This indicates the amount by which the
230 * maximum axis value exceeds the maximum data value (as a percentage of
231 * the range on the axis) when the axis range is determined automatically.
232 */
233 private double upperMargin;
234
235 /**
236 * The lower margin. This is a percentage that indicates the amount by
237 * which the minimum axis value is "less than" the minimum data value when
238 * the axis range is determined automatically.
239 */
240 private double lowerMargin;
241
242 /**
243 * If this value is positive, the amount is subtracted from the maximum
244 * data value to determine the lower axis range. This can be used to
245 * provide a fixed "window" on dynamic data.
246 */
247 private double fixedAutoRange;
248
249 /**
250 * Flag that indicates whether or not the tick unit is selected
251 * automatically.
252 */
253 private boolean autoTickUnitSelection;
254
255 /** The standard tick units for the axis. */
256 private TickUnitSource standardTickUnits;
257
258 /** An index into an array of standard tick values. */
259 private int autoTickIndex;
260
261 /** A flag indicating whether or not tick labels are rotated to vertical. */
262 private boolean verticalTickLabels;
263
264 /**
265 * Constructs a value axis.
266 *
267 * @param label the axis label (<code>null</code> permitted).
268 * @param standardTickUnits the source for standard tick units
269 * (<code>null</code> permitted).
270 */
271 protected ValueAxis(String label, TickUnitSource standardTickUnits) {
272
273 super(label);
274
275 this.positiveArrowVisible = false;
276 this.negativeArrowVisible = false;
277
278 this.range = DEFAULT_RANGE;
279 this.autoRange = DEFAULT_AUTO_RANGE;
280 this.defaultAutoRange = DEFAULT_RANGE;
281
282 this.inverted = DEFAULT_INVERTED;
283 this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE;
284
285 this.lowerMargin = DEFAULT_LOWER_MARGIN;
286 this.upperMargin = DEFAULT_UPPER_MARGIN;
287
288 this.fixedAutoRange = 0.0;
289
290 this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION;
291 this.standardTickUnits = standardTickUnits;
292
293 Polygon p1 = new Polygon();
294 p1.addPoint(0, 0);
295 p1.addPoint(-2, 2);
296 p1.addPoint(2, 2);
297
298 this.upArrow = p1;
299
300 Polygon p2 = new Polygon();
301 p2.addPoint(0, 0);
302 p2.addPoint(-2, -2);
303 p2.addPoint(2, -2);
304
305 this.downArrow = p2;
306
307 Polygon p3 = new Polygon();
308 p3.addPoint(0, 0);
309 p3.addPoint(-2, -2);
310 p3.addPoint(-2, 2);
311
312 this.rightArrow = p3;
313
314 Polygon p4 = new Polygon();
315 p4.addPoint(0, 0);
316 p4.addPoint(2, -2);
317 p4.addPoint(2, 2);
318
319 this.leftArrow = p4;
320
321 this.verticalTickLabels = false;
322
323 }
324
325 /**
326 * Returns <code>true</code> if the tick labels should be rotated (to
327 * vertical), and <code>false</code> otherwise.
328 *
329 * @return <code>true</code> or <code>false</code>.
330 *
331 * @see #setVerticalTickLabels(boolean)
332 */
333 public boolean isVerticalTickLabels() {
334 return this.verticalTickLabels;
335 }
336
337 /**
338 * Sets the flag that controls whether the tick labels are displayed
339 * vertically (that is, rotated 90 degrees from horizontal). If the flag
340 * is changed, an {@link AxisChangeEvent} is sent to all registered
341 * listeners.
342 *
343 * @param flag the flag.
344 *
345 * @see #isVerticalTickLabels()
346 */
347 public void setVerticalTickLabels(boolean flag) {
348 if (this.verticalTickLabels != flag) {
349 this.verticalTickLabels = flag;
350 notifyListeners(new AxisChangeEvent(this));
351 }
352 }
353
354 /**
355 * Returns a flag that controls whether or not the axis line has an arrow
356 * drawn that points in the positive direction for the axis.
357 *
358 * @return A boolean.
359 *
360 * @see #setPositiveArrowVisible(boolean)
361 */
362 public boolean isPositiveArrowVisible() {
363 return this.positiveArrowVisible;
364 }
365
366 /**
367 * Sets a flag that controls whether or not the axis lines has an arrow
368 * drawn that points in the positive direction for the axis, and sends an
369 * {@link AxisChangeEvent} to all registered listeners.
370 *
371 * @param visible the flag.
372 *
373 * @see #isPositiveArrowVisible()
374 */
375 public void setPositiveArrowVisible(boolean visible) {
376 this.positiveArrowVisible = visible;
377 notifyListeners(new AxisChangeEvent(this));
378 }
379
380 /**
381 * Returns a flag that controls whether or not the axis line has an arrow
382 * drawn that points in the negative direction for the axis.
383 *
384 * @return A boolean.
385 *
386 * @see #setNegativeArrowVisible(boolean)
387 */
388 public boolean isNegativeArrowVisible() {
389 return this.negativeArrowVisible;
390 }
391
392 /**
393 * Sets a flag that controls whether or not the axis lines has an arrow
394 * drawn that points in the negative direction for the axis, and sends an
395 * {@link AxisChangeEvent} to all registered listeners.
396 *
397 * @param visible the flag.
398 *
399 * @see #setNegativeArrowVisible(boolean)
400 */
401 public void setNegativeArrowVisible(boolean visible) {
402 this.negativeArrowVisible = visible;
403 notifyListeners(new AxisChangeEvent(this));
404 }
405
406 /**
407 * Returns a shape that can be displayed as an arrow pointing upwards at
408 * the end of an axis line.
409 *
410 * @return A shape (never <code>null</code>).
411 *
412 * @see #setUpArrow(Shape)
413 */
414 public Shape getUpArrow() {
415 return this.upArrow;
416 }
417
418 /**
419 * Sets the shape that can be displayed as an arrow pointing upwards at
420 * the end of an axis line and sends an {@link AxisChangeEvent} to all
421 * registered listeners.
422 *
423 * @param arrow the arrow shape (<code>null</code> not permitted).
424 *
425 * @see #getUpArrow()
426 */
427 public void setUpArrow(Shape arrow) {
428 if (arrow == null) {
429 throw new IllegalArgumentException("Null 'arrow' argument.");
430 }
431 this.upArrow = arrow;
432 notifyListeners(new AxisChangeEvent(this));
433 }
434
435 /**
436 * Returns a shape that can be displayed as an arrow pointing downwards at
437 * the end of an axis line.
438 *
439 * @return A shape (never <code>null</code>).
440 *
441 * @see #setDownArrow(Shape)
442 */
443 public Shape getDownArrow() {
444 return this.downArrow;
445 }
446
447 /**
448 * Sets the shape that can be displayed as an arrow pointing downwards at
449 * the end of an axis line and sends an {@link AxisChangeEvent} to all
450 * registered listeners.
451 *
452 * @param arrow the arrow shape (<code>null</code> not permitted).
453 *
454 * @see #getDownArrow()
455 */
456 public void setDownArrow(Shape arrow) {
457 if (arrow == null) {
458 throw new IllegalArgumentException("Null 'arrow' argument.");
459 }
460 this.downArrow = arrow;
461 notifyListeners(new AxisChangeEvent(this));
462 }
463
464 /**
465 * Returns a shape that can be displayed as an arrow pointing left at the
466 * end of an axis line.
467 *
468 * @return A shape (never <code>null</code>).
469 *
470 * @see #setLeftArrow(Shape)
471 */
472 public Shape getLeftArrow() {
473 return this.leftArrow;
474 }
475
476 /**
477 * Sets the shape that can be displayed as an arrow pointing left at the
478 * end of an axis line and sends an {@link AxisChangeEvent} to all
479 * registered listeners.
480 *
481 * @param arrow the arrow shape (<code>null</code> not permitted).
482 *
483 * @see #getLeftArrow()
484 */
485 public void setLeftArrow(Shape arrow) {
486 if (arrow == null) {
487 throw new IllegalArgumentException("Null 'arrow' argument.");
488 }
489 this.leftArrow = arrow;
490 notifyListeners(new AxisChangeEvent(this));
491 }
492
493 /**
494 * Returns a shape that can be displayed as an arrow pointing right at the
495 * end of an axis line.
496 *
497 * @return A shape (never <code>null</code>).
498 *
499 * @see #setRightArrow(Shape)
500 */
501 public Shape getRightArrow() {
502 return this.rightArrow;
503 }
504
505 /**
506 * Sets the shape that can be displayed as an arrow pointing rightwards at
507 * the end of an axis line and sends an {@link AxisChangeEvent} to all
508 * registered listeners.
509 *
510 * @param arrow the arrow shape (<code>null</code> not permitted).
511 *
512 * @see #getRightArrow()
513 */
514 public void setRightArrow(Shape arrow) {
515 if (arrow == null) {
516 throw new IllegalArgumentException("Null 'arrow' argument.");
517 }
518 this.rightArrow = arrow;
519 notifyListeners(new AxisChangeEvent(this));
520 }
521
522 /**
523 * Draws an axis line at the current cursor position and edge.
524 *
525 * @param g2 the graphics device.
526 * @param cursor the cursor position.
527 * @param dataArea the data area.
528 * @param edge the edge.
529 */
530 protected void drawAxisLine(Graphics2D g2, double cursor,
531 Rectangle2D dataArea, RectangleEdge edge) {
532 Line2D axisLine = null;
533 if (edge == RectangleEdge.TOP) {
534 axisLine = new Line2D.Double(dataArea.getX(), cursor,
535 dataArea.getMaxX(), cursor);
536 }
537 else if (edge == RectangleEdge.BOTTOM) {
538 axisLine = new Line2D.Double(dataArea.getX(), cursor,
539 dataArea.getMaxX(), cursor);
540 }
541 else if (edge == RectangleEdge.LEFT) {
542 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
543 dataArea.getMaxY());
544 }
545 else if (edge == RectangleEdge.RIGHT) {
546 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
547 dataArea.getMaxY());
548 }
549 g2.setPaint(getAxisLinePaint());
550 g2.setStroke(getAxisLineStroke());
551 g2.draw(axisLine);
552
553 boolean drawUpOrRight = false;
554 boolean drawDownOrLeft = false;
555 if (this.positiveArrowVisible) {
556 if (this.inverted) {
557 drawDownOrLeft = true;
558 }
559 else {
560 drawUpOrRight = true;
561 }
562 }
563 if (this.negativeArrowVisible) {
564 if (this.inverted) {
565 drawUpOrRight = true;
566 }
567 else {
568 drawDownOrLeft = true;
569 }
570 }
571 if (drawUpOrRight) {
572 double x = 0.0;
573 double y = 0.0;
574 Shape arrow = null;
575 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
576 x = dataArea.getMaxX();
577 y = cursor;
578 arrow = this.rightArrow;
579 }
580 else if (edge == RectangleEdge.LEFT
581 || edge == RectangleEdge.RIGHT) {
582 x = cursor;
583 y = dataArea.getMinY();
584 arrow = this.upArrow;
585 }
586
587 // draw the arrow...
588 AffineTransform transformer = new AffineTransform();
589 transformer.setToTranslation(x, y);
590 Shape shape = transformer.createTransformedShape(arrow);
591 g2.fill(shape);
592 g2.draw(shape);
593 }
594
595 if (drawDownOrLeft) {
596 double x = 0.0;
597 double y = 0.0;
598 Shape arrow = null;
599 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
600 x = dataArea.getMinX();
601 y = cursor;
602 arrow = this.leftArrow;
603 }
604 else if (edge == RectangleEdge.LEFT
605 || edge == RectangleEdge.RIGHT) {
606 x = cursor;
607 y = dataArea.getMaxY();
608 arrow = this.downArrow;
609 }
610
611 // draw the arrow...
612 AffineTransform transformer = new AffineTransform();
613 transformer.setToTranslation(x, y);
614 Shape shape = transformer.createTransformedShape(arrow);
615 g2.fill(shape);
616 g2.draw(shape);
617 }
618
619 }
620
621 /**
622 * Calculates the anchor point for a tick label.
623 *
624 * @param tick the tick.
625 * @param cursor the cursor.
626 * @param dataArea the data area.
627 * @param edge the edge on which the axis is drawn.
628 *
629 * @return The x and y coordinates of the anchor point.
630 */
631 protected float[] calculateAnchorPoint(ValueTick tick,
632 double cursor,
633 Rectangle2D dataArea,
634 RectangleEdge edge) {
635
636 RectangleInsets insets = getTickLabelInsets();
637 float[] result = new float[2];
638 if (edge == RectangleEdge.TOP) {
639 result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
640 result[1] = (float) (cursor - insets.getBottom() - 2.0);
641 }
642 else if (edge == RectangleEdge.BOTTOM) {
643 result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
644 result[1] = (float) (cursor + insets.getTop() + 2.0);
645 }
646 else if (edge == RectangleEdge.LEFT) {
647 result[0] = (float) (cursor - insets.getLeft() - 2.0);
648 result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
649 }
650 else if (edge == RectangleEdge.RIGHT) {
651 result[0] = (float) (cursor + insets.getRight() + 2.0);
652 result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
653 }
654 return result;
655 }
656
657 /**
658 * Draws the axis line, tick marks and tick mark labels.
659 *
660 * @param g2 the graphics device.
661 * @param cursor the cursor.
662 * @param plotArea the plot area.
663 * @param dataArea the data area.
664 * @param edge the edge that the axis is aligned with.
665 *
666 * @return The width or height used to draw the axis.
667 */
668 protected AxisState drawTickMarksAndLabels(Graphics2D g2,
669 double cursor,
670 Rectangle2D plotArea,
671 Rectangle2D dataArea,
672 RectangleEdge edge) {
673
674 AxisState state = new AxisState(cursor);
675
676 if (isAxisLineVisible()) {
677 drawAxisLine(g2, cursor, dataArea, edge);
678 }
679
680 double ol = getTickMarkOutsideLength();
681 double il = getTickMarkInsideLength();
682
683 List ticks = refreshTicks(g2, state, dataArea, edge);
684 state.setTicks(ticks);
685 g2.setFont(getTickLabelFont());
686 Iterator iterator = ticks.iterator();
687 while (iterator.hasNext()) {
688 ValueTick tick = (ValueTick) iterator.next();
689 if (isTickLabelsVisible()) {
690 g2.setPaint(getTickLabelPaint());
691 float[] anchorPoint = calculateAnchorPoint(tick, cursor,
692 dataArea, edge);
693 TextUtilities.drawRotatedString(tick.getText(), g2,
694 anchorPoint[0], anchorPoint[1], tick.getTextAnchor(),
695 tick.getAngle(), tick.getRotationAnchor());
696 }
697
698 if (isTickMarksVisible() && tick.getTickType().equals(
699 TickType.MAJOR)) {
700 float xx = (float) valueToJava2D(tick.getValue(), dataArea,
701 edge);
702 Line2D mark = null;
703 g2.setStroke(getTickMarkStroke());
704 g2.setPaint(getTickMarkPaint());
705 if (edge == RectangleEdge.LEFT) {
706 mark = new Line2D.Double(cursor - ol, xx, cursor + il, xx);
707 }
708 else if (edge == RectangleEdge.RIGHT) {
709 mark = new Line2D.Double(cursor + ol, xx, cursor - il, xx);
710 }
711 else if (edge == RectangleEdge.TOP) {
712 mark = new Line2D.Double(xx, cursor - ol, xx, cursor + il);
713 }
714 else if (edge == RectangleEdge.BOTTOM) {
715 mark = new Line2D.Double(xx, cursor + ol, xx, cursor - il);
716 }
717 g2.draw(mark);
718 }
719 }
720
721 // need to work out the space used by the tick labels...
722 // so we can update the cursor...
723 double used = 0.0;
724 if (isTickLabelsVisible()) {
725 if (edge == RectangleEdge.LEFT) {
726 used += findMaximumTickLabelWidth(ticks, g2, plotArea,
727 isVerticalTickLabels());
728 state.cursorLeft(used);
729 }
730 else if (edge == RectangleEdge.RIGHT) {
731 used = findMaximumTickLabelWidth(ticks, g2, plotArea,
732 isVerticalTickLabels());
733 state.cursorRight(used);
734 }
735 else if (edge == RectangleEdge.TOP) {
736 used = findMaximumTickLabelHeight(ticks, g2, plotArea,
737 isVerticalTickLabels());
738 state.cursorUp(used);
739 }
740 else if (edge == RectangleEdge.BOTTOM) {
741 used = findMaximumTickLabelHeight(ticks, g2, plotArea,
742 isVerticalTickLabels());
743 state.cursorDown(used);
744 }
745 }
746
747 return state;
748 }
749
750 /**
751 * Returns the space required to draw the axis.
752 *
753 * @param g2 the graphics device.
754 * @param plot the plot that the axis belongs to.
755 * @param plotArea the area within which the plot should be drawn.
756 * @param edge the axis location.
757 * @param space the space already reserved (for other axes).
758 *
759 * @return The space required to draw the axis (including pre-reserved
760 * space).
761 */
762 public AxisSpace reserveSpace(Graphics2D g2, Plot plot,
763 Rectangle2D plotArea,
764 RectangleEdge edge, AxisSpace space) {
765
766 // create a new space object if one wasn't supplied...
767 if (space == null) {
768 space = new AxisSpace();
769 }
770
771 // if the axis is not visible, no additional space is required...
772 if (!isVisible()) {
773 return space;
774 }
775
776 // if the axis has a fixed dimension, return it...
777 double dimension = getFixedDimension();
778 if (dimension > 0.0) {
779 space.ensureAtLeast(dimension, edge);
780 }
781
782 // calculate the max size of the tick labels (if visible)...
783 double tickLabelHeight = 0.0;
784 double tickLabelWidth = 0.0;
785 if (isTickLabelsVisible()) {
786 g2.setFont(getTickLabelFont());
787 List ticks = refreshTicks(g2, new AxisState(), plotArea, edge);
788 if (RectangleEdge.isTopOrBottom(edge)) {
789 tickLabelHeight = findMaximumTickLabelHeight(ticks, g2,
790 plotArea, isVerticalTickLabels());
791 }
792 else if (RectangleEdge.isLeftOrRight(edge)) {
793 tickLabelWidth = findMaximumTickLabelWidth(ticks, g2, plotArea,
794 isVerticalTickLabels());
795 }
796 }
797
798 // get the axis label size and update the space object...
799 Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge);
800 double labelHeight = 0.0;
801 double labelWidth = 0.0;
802 if (RectangleEdge.isTopOrBottom(edge)) {
803 labelHeight = labelEnclosure.getHeight();
804 space.add(labelHeight + tickLabelHeight, edge);
805 }
806 else if (RectangleEdge.isLeftOrRight(edge)) {
807 labelWidth = labelEnclosure.getWidth();
808 space.add(labelWidth + tickLabelWidth, edge);
809 }
810
811 return space;
812
813 }
814
815 /**
816 * A utility method for determining the height of the tallest tick label.
817 *
818 * @param ticks the ticks.
819 * @param g2 the graphics device.
820 * @param drawArea the area within which the plot and axes should be drawn.
821 * @param vertical a flag that indicates whether or not the tick labels
822 * are 'vertical'.
823 *
824 * @return The height of the tallest tick label.
825 */
826 protected double findMaximumTickLabelHeight(List ticks,
827 Graphics2D g2,
828 Rectangle2D drawArea,
829 boolean vertical) {
830
831 RectangleInsets insets = getTickLabelInsets();
832 Font font = getTickLabelFont();
833 double maxHeight = 0.0;
834 if (vertical) {
835 FontMetrics fm = g2.getFontMetrics(font);
836 Iterator iterator = ticks.iterator();
837 while (iterator.hasNext()) {
838 Tick tick = (Tick) iterator.next();
839 Rectangle2D labelBounds = TextUtilities.getTextBounds(
840 tick.getText(), g2, fm);
841 if (labelBounds.getWidth() + insets.getTop()
842 + insets.getBottom() > maxHeight) {
843 maxHeight = labelBounds.getWidth()
844 + insets.getTop() + insets.getBottom();
845 }
846 }
847 }
848 else {
849 LineMetrics metrics = font.getLineMetrics("ABCxyz",
850 g2.getFontRenderContext());
851 maxHeight = metrics.getHeight()
852 + insets.getTop() + insets.getBottom();
853 }
854 return maxHeight;
855
856 }
857
858 /**
859 * A utility method for determining the width of the widest tick label.
860 *
861 * @param ticks the ticks.
862 * @param g2 the graphics device.
863 * @param drawArea the area within which the plot and axes should be drawn.
864 * @param vertical a flag that indicates whether or not the tick labels
865 * are 'vertical'.
866 *
867 * @return The width of the tallest tick label.
868 */
869 protected double findMaximumTickLabelWidth(List ticks,
870 Graphics2D g2,
871 Rectangle2D drawArea,
872 boolean vertical) {
873
874 RectangleInsets insets = getTickLabelInsets();
875 Font font = getTickLabelFont();
876 double maxWidth = 0.0;
877 if (!vertical) {
878 FontMetrics fm = g2.getFontMetrics(font);
879 Iterator iterator = ticks.iterator();
880 while (iterator.hasNext()) {
881 Tick tick = (Tick) iterator.next();
882 Rectangle2D labelBounds = TextUtilities.getTextBounds(
883 tick.getText(), g2, fm);
884 if (labelBounds.getWidth() + insets.getLeft()
885 + insets.getRight() > maxWidth) {
886 maxWidth = labelBounds.getWidth()
887 + insets.getLeft() + insets.getRight();
888 }
889 }
890 }
891 else {
892 LineMetrics metrics = font.getLineMetrics("ABCxyz",
893 g2.getFontRenderContext());
894 maxWidth = metrics.getHeight()
895 + insets.getTop() + insets.getBottom();
896 }
897 return maxWidth;
898
899 }
900
901 /**
902 * Returns a flag that controls the direction of values on the axis.
903 * <P>
904 * For a regular axis, values increase from left to right (for a horizontal
905 * axis) and bottom to top (for a vertical axis). When the axis is
906 * 'inverted', the values increase in the opposite direction.
907 *
908 * @return The flag.
909 *
910 * @see #setInverted(boolean)
911 */
912 public boolean isInverted() {
913 return this.inverted;
914 }
915
916 /**
917 * Sets a flag that controls the direction of values on the axis, and
918 * notifies registered listeners that the axis has changed.
919 *
920 * @param flag the flag.
921 *
922 * @see #isInverted()
923 */
924 public void setInverted(boolean flag) {
925
926 if (this.inverted != flag) {
927 this.inverted = flag;
928 notifyListeners(new AxisChangeEvent(this));
929 }
930
931 }
932
933 /**
934 * Returns the flag that controls whether or not the axis range is
935 * automatically adjusted to fit the data values.
936 *
937 * @return The flag.
938 *
939 * @see #setAutoRange(boolean)
940 */
941 public boolean isAutoRange() {
942 return this.autoRange;
943 }
944
945 /**
946 * Sets a flag that determines whether or not the axis range is
947 * automatically adjusted to fit the data, and notifies registered
948 * listeners that the axis has been modified.
949 *
950 * @param auto the new value of the flag.
951 *
952 * @see #isAutoRange()
953 */
954 public void setAutoRange(boolean auto) {
955 setAutoRange(auto, true);
956 }
957
958 /**
959 * Sets the auto range attribute. If the <code>notify</code> flag is set,
960 * an {@link AxisChangeEvent} is sent to registered listeners.
961 *
962 * @param auto the flag.
963 * @param notify notify listeners?
964 *
965 * @see #isAutoRange()
966 */
967 protected void setAutoRange(boolean auto, boolean notify) {
968 if (this.autoRange != auto) {
969 this.autoRange = auto;
970 if (this.autoRange) {
971 autoAdjustRange();
972 }
973 if (notify) {
974 notifyListeners(new AxisChangeEvent(this));
975 }
976 }
977 }
978
979 /**
980 * Returns the minimum size allowed for the axis range when it is
981 * automatically calculated.
982 *
983 * @return The minimum range.
984 *
985 * @see #setAutoRangeMinimumSize(double)
986 */
987 public double getAutoRangeMinimumSize() {
988 return this.autoRangeMinimumSize;
989 }
990
991 /**
992 * Sets the auto range minimum size and sends an {@link AxisChangeEvent}
993 * to all registered listeners.
994 *
995 * @param size the size.
996 *
997 * @see #getAutoRangeMinimumSize()
998 */
999 public void setAutoRangeMinimumSize(double size) {
1000 setAutoRangeMinimumSize(size, true);
1001 }
1002
1003 /**
1004 * Sets the minimum size allowed for the axis range when it is
1005 * automatically calculated.
1006 * <p>
1007 * If requested, an {@link AxisChangeEvent} is forwarded to all registered
1008 * listeners.
1009 *
1010 * @param size the new minimum.
1011 * @param notify notify listeners?
1012 */
1013 public void setAutoRangeMinimumSize(double size, boolean notify) {
1014 if (size <= 0.0) {
1015 throw new IllegalArgumentException(
1016 "NumberAxis.setAutoRangeMinimumSize(double): must be > 0.0.");
1017 }
1018 if (this.autoRangeMinimumSize != size) {
1019 this.autoRangeMinimumSize = size;
1020 if (this.autoRange) {
1021 autoAdjustRange();
1022 }
1023 if (notify) {
1024 notifyListeners(new AxisChangeEvent(this));
1025 }
1026 }
1027
1028 }
1029
1030 /**
1031 * Returns the default auto range.
1032 *
1033 * @return The default auto range (never <code>null</code>).
1034 *
1035 * @see #setDefaultAutoRange(Range)
1036 *
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 * @see #resizeRange(double, double)
1489 */
1490 public void resizeRange(double percent) {
1491 resizeRange(percent, this.range.getCentralValue());
1492 }
1493
1494 /**
1495 * Increases or decreases the axis range by the specified percentage about
1496 * the specified anchor value and sends an {@link AxisChangeEvent} to all
1497 * registered listeners.
1498 * <P>
1499 * To double the length of the axis range, use 200% (2.0).
1500 * To halve the length of the axis range, use 50% (0.5).
1501 *
1502 * @param percent the resize factor.
1503 * @param anchorValue the new central value after the resize.
1504 *
1505 * @see #resizeRange(double)
1506 */
1507 public void resizeRange(double percent, double anchorValue) {
1508 if (percent > 0.0) {
1509 double halfLength = this.range.getLength() * percent / 2;
1510 Range adjusted = new Range(anchorValue - halfLength,
1511 anchorValue + halfLength);
1512 setRange(adjusted);
1513 }
1514 else {
1515 setAutoRange(true);
1516 }
1517 }
1518
1519 /**
1520 * Zooms in on the current range.
1521 *
1522 * @param lowerPercent the new lower bound.
1523 * @param upperPercent the new upper bound.
1524 */
1525 public void zoomRange(double lowerPercent, double upperPercent) {
1526 double start = this.range.getLowerBound();
1527 double length = this.range.getLength();
1528 Range adjusted = null;
1529 if (isInverted()) {
1530 adjusted = new Range(start + (length * (1 - upperPercent)),
1531 start + (length * (1 - lowerPercent)));
1532 }
1533 else {
1534 adjusted = new Range(start + length * lowerPercent,
1535 start + length * upperPercent);
1536 }
1537 setRange(adjusted);
1538 }
1539
1540 /**
1541 * Returns the auto tick index.
1542 *
1543 * @return The auto tick index.
1544 *
1545 * @see #setAutoTickIndex(int)
1546 */
1547 protected int getAutoTickIndex() {
1548 return this.autoTickIndex;
1549 }
1550
1551 /**
1552 * Sets the auto tick index.
1553 *
1554 * @param index the new value.
1555 *
1556 * @see #getAutoTickIndex()
1557 */
1558 protected void setAutoTickIndex(int index) {
1559 this.autoTickIndex = index;
1560 }
1561
1562 /**
1563 * Tests the axis for equality with an arbitrary object.
1564 *
1565 * @param obj the object (<code>null</code> permitted).
1566 *
1567 * @return <code>true</code> or <code>false</code>.
1568 */
1569 public boolean equals(Object obj) {
1570
1571 if (obj == this) {
1572 return true;
1573 }
1574 if (!(obj instanceof ValueAxis)) {
1575 return false;
1576 }
1577
1578 ValueAxis that = (ValueAxis) obj;
1579
1580 if (this.positiveArrowVisible != that.positiveArrowVisible) {
1581 return false;
1582 }
1583 if (this.negativeArrowVisible != that.negativeArrowVisible) {
1584 return false;
1585 }
1586 if (this.inverted != that.inverted) {
1587 return false;
1588 }
1589 if (!ObjectUtilities.equal(this.range, that.range)) {
1590 return false;
1591 }
1592 if (this.autoRange != that.autoRange) {
1593 return false;
1594 }
1595 if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) {
1596 return false;
1597 }
1598 if (!this.defaultAutoRange.equals(that.defaultAutoRange)) {
1599 return false;
1600 }
1601 if (this.upperMargin != that.upperMargin) {
1602 return false;
1603 }
1604 if (this.lowerMargin != that.lowerMargin) {
1605 return false;
1606 }
1607 if (this.fixedAutoRange != that.fixedAutoRange) {
1608 return false;
1609 }
1610 if (this.autoTickUnitSelection != that.autoTickUnitSelection) {
1611 return false;
1612 }
1613 if (!ObjectUtilities.equal(this.standardTickUnits,
1614 that.standardTickUnits)) {
1615 return false;
1616 }
1617 if (this.verticalTickLabels != that.verticalTickLabels) {
1618 return false;
1619 }
1620
1621 return super.equals(obj);
1622
1623 }
1624
1625 /**
1626 * Returns a clone of the object.
1627 *
1628 * @return A clone.
1629 *
1630 * @throws CloneNotSupportedException if some component of the axis does
1631 * not support cloning.
1632 */
1633 public Object clone() throws CloneNotSupportedException {
1634 ValueAxis clone = (ValueAxis) super.clone();
1635 return clone;
1636 }
1637
1638 /**
1639 * Provides serialization support.
1640 *
1641 * @param stream the output stream.
1642 *
1643 * @throws IOException if there is an I/O error.
1644 */
1645 private void writeObject(ObjectOutputStream stream) throws IOException {
1646 stream.defaultWriteObject();
1647 SerialUtilities.writeShape(this.upArrow, stream);
1648 SerialUtilities.writeShape(this.downArrow, stream);
1649 SerialUtilities.writeShape(this.leftArrow, stream);
1650 SerialUtilities.writeShape(this.rightArrow, stream);
1651 }
1652
1653 /**
1654 * Provides serialization support.
1655 *
1656 * @param stream the input stream.
1657 *
1658 * @throws IOException if there is an I/O error.
1659 * @throws ClassNotFoundException if there is a classpath problem.
1660 */
1661 private void readObject(ObjectInputStream stream)
1662 throws IOException, ClassNotFoundException {
1663
1664 stream.defaultReadObject();
1665 this.upArrow = SerialUtilities.readShape(stream);
1666 this.downArrow = SerialUtilities.readShape(stream);
1667 this.leftArrow = SerialUtilities.readShape(stream);
1668 this.rightArrow = SerialUtilities.readShape(stream);
1669
1670 }
1671
1672 }