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 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-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 * 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,
137 Serializable {
138
139 /** For serialization. */
140 private static final long serialVersionUID = 3698345477322391456L;
141
142 /** The default axis range. */
143 public static final Range DEFAULT_RANGE = new Range(0.0, 1.0);
144
145 /** The default auto-range value. */
146 public static final boolean DEFAULT_AUTO_RANGE = true;
147
148 /** The default inverted flag setting. */
149 public static final boolean DEFAULT_INVERTED = false;
150
151 /** The default minimum auto range. */
152 public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001;
153
154 /** The default value for the lower margin (0.05 = 5%). */
155 public static final double DEFAULT_LOWER_MARGIN = 0.05;
156
157 /** The default value for the upper margin (0.05 = 5%). */
158 public static final double DEFAULT_UPPER_MARGIN = 0.05;
159
160 /**
161 * The default lower bound for the axis.
162 *
163 * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
164 * attribute (see {@link #getDefaultAutoRange()}).
165 */
166 public static final double DEFAULT_LOWER_BOUND = 0.0;
167
168 /**
169 * The default upper bound for the axis.
170 *
171 * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
172 * attribute (see {@link #getDefaultAutoRange()}).
173 */
174 public static final double DEFAULT_UPPER_BOUND = 1.0;
175
176 /** The default auto-tick-unit-selection value. */
177 public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true;
178
179 /** The maximum tick count. */
180 public static final int MAXIMUM_TICK_COUNT = 500;
181
182 /**
183 * A flag that controls whether an arrow is drawn at the positive end of
184 * the axis line.
185 */
186 private boolean positiveArrowVisible;
187
188 /**
189 * A flag that controls whether an arrow is drawn at the negative end of
190 * the axis line.
191 */
192 private boolean negativeArrowVisible;
193
194 /** The shape used for an up arrow. */
195 private transient Shape upArrow;
196
197 /** The shape used for a down arrow. */
198 private transient Shape downArrow;
199
200 /** The shape used for a left arrow. */
201 private transient Shape leftArrow;
202
203 /** The shape used for a right arrow. */
204 private transient Shape rightArrow;
205
206 /** A flag that affects the orientation of the values on the axis. */
207 private boolean inverted;
208
209 /** The axis range. */
210 private Range range;
211
212 /**
213 * Flag that indicates whether the axis automatically scales to fit the
214 * chart data.
215 */
216 private boolean autoRange;
217
218 /** The minimum size for the 'auto' axis range (excluding margins). */
219 private double autoRangeMinimumSize;
220
221 /**
222 * The default range is used when the dataset is empty and the axis needs
223 * to determine the auto range.
224 *
225 * @since 1.0.5
226 */
227 private Range defaultAutoRange;
228
229 /**
230 * The upper margin percentage. This indicates the amount by which the
231 * maximum axis value exceeds the maximum data value (as a percentage of
232 * the range on the axis) when the axis range is determined automatically.
233 */
234 private double upperMargin;
235
236 /**
237 * The lower margin. This is a percentage that indicates the amount by
238 * which the minimum axis value is "less than" the minimum data value when
239 * the axis range is determined automatically.
240 */
241 private double lowerMargin;
242
243 /**
244 * If this value is positive, the amount is subtracted from the maximum
245 * data value to determine the lower axis range. This can be used to
246 * provide a fixed "window" on dynamic data.
247 */
248 private double fixedAutoRange;
249
250 /**
251 * Flag that indicates whether or not the tick unit is selected
252 * automatically.
253 */
254 private boolean autoTickUnitSelection;
255
256 /** The standard tick units for the axis. */
257 private TickUnitSource standardTickUnits;
258
259 /** An index into an array of standard tick values. */
260 private int autoTickIndex;
261
262 /** A flag indicating whether or not tick labels are rotated to vertical. */
263 private boolean verticalTickLabels;
264
265 /**
266 * Constructs a value axis.
267 *
268 * @param label the axis label (<code>null</code> permitted).
269 * @param standardTickUnits the source for standard tick units
270 * (<code>null</code> permitted).
271 */
272 protected ValueAxis(String label, TickUnitSource standardTickUnits) {
273
274 super(label);
275
276 this.positiveArrowVisible = false;
277 this.negativeArrowVisible = false;
278
279 this.range = DEFAULT_RANGE;
280 this.autoRange = DEFAULT_AUTO_RANGE;
281 this.defaultAutoRange = DEFAULT_RANGE;
282
283 this.inverted = DEFAULT_INVERTED;
284 this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE;
285
286 this.lowerMargin = DEFAULT_LOWER_MARGIN;
287 this.upperMargin = DEFAULT_UPPER_MARGIN;
288
289 this.fixedAutoRange = 0.0;
290
291 this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION;
292 this.standardTickUnits = standardTickUnits;
293
294 Polygon p1 = new Polygon();
295 p1.addPoint(0, 0);
296 p1.addPoint(-2, 2);
297 p1.addPoint(2, 2);
298
299 this.upArrow = p1;
300
301 Polygon p2 = new Polygon();
302 p2.addPoint(0, 0);
303 p2.addPoint(-2, -2);
304 p2.addPoint(2, -2);
305
306 this.downArrow = p2;
307
308 Polygon p3 = new Polygon();
309 p3.addPoint(0, 0);
310 p3.addPoint(-2, -2);
311 p3.addPoint(-2, 2);
312
313 this.rightArrow = p3;
314
315 Polygon p4 = new Polygon();
316 p4.addPoint(0, 0);
317 p4.addPoint(2, -2);
318 p4.addPoint(2, 2);
319
320 this.leftArrow = p4;
321
322 this.verticalTickLabels = false;
323
324 }
325
326 /**
327 * Returns <code>true</code> if the tick labels should be rotated (to
328 * vertical), and <code>false</code> otherwise.
329 *
330 * @return <code>true</code> or <code>false</code>.
331 *
332 * @see #setVerticalTickLabels(boolean)
333 */
334 public boolean isVerticalTickLabels() {
335 return this.verticalTickLabels;
336 }
337
338 /**
339 * Sets the flag that controls whether the tick labels are displayed
340 * vertically (that is, rotated 90 degrees from horizontal). If the flag
341 * is changed, an {@link AxisChangeEvent} is sent to all registered
342 * listeners.
343 *
344 * @param flag the flag.
345 *
346 * @see #isVerticalTickLabels()
347 */
348 public void setVerticalTickLabels(boolean flag) {
349 if (this.verticalTickLabels != flag) {
350 this.verticalTickLabels = flag;
351 notifyListeners(new AxisChangeEvent(this));
352 }
353 }
354
355 /**
356 * Returns a flag that controls whether or not the axis line has an arrow
357 * drawn that points in the positive direction for the axis.
358 *
359 * @return A boolean.
360 *
361 * @see #setPositiveArrowVisible(boolean)
362 */
363 public boolean isPositiveArrowVisible() {
364 return this.positiveArrowVisible;
365 }
366
367 /**
368 * Sets a flag that controls whether or not the axis lines has an arrow
369 * drawn that points in the positive direction for the axis, and sends an
370 * {@link AxisChangeEvent} to all registered listeners.
371 *
372 * @param visible the flag.
373 *
374 * @see #isPositiveArrowVisible()
375 */
376 public void setPositiveArrowVisible(boolean visible) {
377 this.positiveArrowVisible = visible;
378 notifyListeners(new AxisChangeEvent(this));
379 }
380
381 /**
382 * Returns a flag that controls whether or not the axis line has an arrow
383 * drawn that points in the negative direction for the axis.
384 *
385 * @return A boolean.
386 *
387 * @see #setNegativeArrowVisible(boolean)
388 */
389 public boolean isNegativeArrowVisible() {
390 return this.negativeArrowVisible;
391 }
392
393 /**
394 * Sets a flag that controls whether or not the axis lines has an arrow
395 * drawn that points in the negative direction for the axis, and sends an
396 * {@link AxisChangeEvent} to all registered listeners.
397 *
398 * @param visible the flag.
399 *
400 * @see #setNegativeArrowVisible(boolean)
401 */
402 public void setNegativeArrowVisible(boolean visible) {
403 this.negativeArrowVisible = visible;
404 notifyListeners(new AxisChangeEvent(this));
405 }
406
407 /**
408 * Returns a shape that can be displayed as an arrow pointing upwards at
409 * the end of an axis line.
410 *
411 * @return A shape (never <code>null</code>).
412 *
413 * @see #setUpArrow(Shape)
414 */
415 public Shape getUpArrow() {
416 return this.upArrow;
417 }
418
419 /**
420 * Sets the shape that can be displayed as an arrow pointing upwards at
421 * the end of an axis line and sends an {@link AxisChangeEvent} to all
422 * registered listeners.
423 *
424 * @param arrow the arrow shape (<code>null</code> not permitted).
425 *
426 * @see #getUpArrow()
427 */
428 public void setUpArrow(Shape arrow) {
429 if (arrow == null) {
430 throw new IllegalArgumentException("Null 'arrow' argument.");
431 }
432 this.upArrow = arrow;
433 notifyListeners(new AxisChangeEvent(this));
434 }
435
436 /**
437 * Returns a shape that can be displayed as an arrow pointing downwards at
438 * the end of an axis line.
439 *
440 * @return A shape (never <code>null</code>).
441 *
442 * @see #setDownArrow(Shape)
443 */
444 public Shape getDownArrow() {
445 return this.downArrow;
446 }
447
448 /**
449 * Sets the shape that can be displayed as an arrow pointing downwards at
450 * the end of an axis line and sends an {@link AxisChangeEvent} to all
451 * registered listeners.
452 *
453 * @param arrow the arrow shape (<code>null</code> not permitted).
454 *
455 * @see #getDownArrow()
456 */
457 public void setDownArrow(Shape arrow) {
458 if (arrow == null) {
459 throw new IllegalArgumentException("Null 'arrow' argument.");
460 }
461 this.downArrow = arrow;
462 notifyListeners(new AxisChangeEvent(this));
463 }
464
465 /**
466 * Returns a shape that can be displayed as an arrow pointing left at the
467 * end of an axis line.
468 *
469 * @return A shape (never <code>null</code>).
470 *
471 * @see #setLeftArrow(Shape)
472 */
473 public Shape getLeftArrow() {
474 return this.leftArrow;
475 }
476
477 /**
478 * Sets the shape that can be displayed as an arrow pointing left at the
479 * end of an axis line and sends an {@link AxisChangeEvent} to all
480 * registered listeners.
481 *
482 * @param arrow the arrow shape (<code>null</code> not permitted).
483 *
484 * @see #getLeftArrow()
485 */
486 public void setLeftArrow(Shape arrow) {
487 if (arrow == null) {
488 throw new IllegalArgumentException("Null 'arrow' argument.");
489 }
490 this.leftArrow = arrow;
491 notifyListeners(new AxisChangeEvent(this));
492 }
493
494 /**
495 * Returns a shape that can be displayed as an arrow pointing right at the
496 * end of an axis line.
497 *
498 * @return A shape (never <code>null</code>).
499 *
500 * @see #setRightArrow(Shape)
501 */
502 public Shape getRightArrow() {
503 return this.rightArrow;
504 }
505
506 /**
507 * Sets the shape that can be displayed as an arrow pointing rightwards at
508 * the end of an axis line and sends an {@link AxisChangeEvent} to all
509 * registered listeners.
510 *
511 * @param arrow the arrow shape (<code>null</code> not permitted).
512 *
513 * @see #getRightArrow()
514 */
515 public void setRightArrow(Shape arrow) {
516 if (arrow == null) {
517 throw new IllegalArgumentException("Null 'arrow' argument.");
518 }
519 this.rightArrow = arrow;
520 notifyListeners(new AxisChangeEvent(this));
521 }
522
523 /**
524 * Draws an axis line at the current cursor position and edge.
525 *
526 * @param g2 the graphics device.
527 * @param cursor the cursor position.
528 * @param dataArea the data area.
529 * @param edge the edge.
530 */
531 protected void drawAxisLine(Graphics2D g2, double cursor,
532 Rectangle2D dataArea, RectangleEdge edge) {
533 Line2D axisLine = null;
534 if (edge == RectangleEdge.TOP) {
535 axisLine = new Line2D.Double(dataArea.getX(), cursor,
536 dataArea.getMaxX(), cursor);
537 }
538 else if (edge == RectangleEdge.BOTTOM) {
539 axisLine = new Line2D.Double(dataArea.getX(), cursor,
540 dataArea.getMaxX(), cursor);
541 }
542 else if (edge == RectangleEdge.LEFT) {
543 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
544 dataArea.getMaxY());
545 }
546 else if (edge == RectangleEdge.RIGHT) {
547 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
548 dataArea.getMaxY());
549 }
550 g2.setPaint(getAxisLinePaint());
551 g2.setStroke(getAxisLineStroke());
552 g2.draw(axisLine);
553
554 boolean drawUpOrRight = false;
555 boolean drawDownOrLeft = false;
556 if (this.positiveArrowVisible) {
557 if (this.inverted) {
558 drawDownOrLeft = true;
559 }
560 else {
561 drawUpOrRight = true;
562 }
563 }
564 if (this.negativeArrowVisible) {
565 if (this.inverted) {
566 drawUpOrRight = true;
567 }
568 else {
569 drawDownOrLeft = true;
570 }
571 }
572 if (drawUpOrRight) {
573 double x = 0.0;
574 double y = 0.0;
575 Shape arrow = null;
576 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
577 x = dataArea.getMaxX();
578 y = cursor;
579 arrow = this.rightArrow;
580 }
581 else if (edge == RectangleEdge.LEFT
582 || edge == RectangleEdge.RIGHT) {
583 x = cursor;
584 y = dataArea.getMinY();
585 arrow = this.upArrow;
586 }
587
588 // draw the arrow...
589 AffineTransform transformer = new AffineTransform();
590 transformer.setToTranslation(x, y);
591 Shape shape = transformer.createTransformedShape(arrow);
592 g2.fill(shape);
593 g2.draw(shape);
594 }
595
596 if (drawDownOrLeft) {
597 double x = 0.0;
598 double y = 0.0;
599 Shape arrow = null;
600 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
601 x = dataArea.getMinX();
602 y = cursor;
603 arrow = this.leftArrow;
604 }
605 else if (edge == RectangleEdge.LEFT
606 || edge == RectangleEdge.RIGHT) {
607 x = cursor;
608 y = dataArea.getMaxY();
609 arrow = this.downArrow;
610 }
611
612 // draw the arrow...
613 AffineTransform transformer = new AffineTransform();
614 transformer.setToTranslation(x, y);
615 Shape shape = transformer.createTransformedShape(arrow);
616 g2.fill(shape);
617 g2.draw(shape);
618 }
619
620 }
621
622 /**
623 * Calculates the anchor point for a tick label.
624 *
625 * @param tick the tick.
626 * @param cursor the cursor.
627 * @param dataArea the data area.
628 * @param edge the edge on which the axis is drawn.
629 *
630 * @return The x and y coordinates of the anchor point.
631 */
632 protected float[] calculateAnchorPoint(ValueTick tick,
633 double cursor,
634 Rectangle2D dataArea,
635 RectangleEdge edge) {
636
637 RectangleInsets insets = getTickLabelInsets();
638 float[] result = new float[2];
639 if (edge == RectangleEdge.TOP) {
640 result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
641 result[1] = (float) (cursor - insets.getBottom() - 2.0);
642 }
643 else if (edge == RectangleEdge.BOTTOM) {
644 result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
645 result[1] = (float) (cursor + insets.getTop() + 2.0);
646 }
647 else if (edge == RectangleEdge.LEFT) {
648 result[0] = (float) (cursor - insets.getLeft() - 2.0);
649 result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
650 }
651 else if (edge == RectangleEdge.RIGHT) {
652 result[0] = (float) (cursor + insets.getRight() + 2.0);
653 result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
654 }
655 return result;
656 }
657
658 /**
659 * Draws the axis line, tick marks and tick mark labels.
660 *
661 * @param g2 the graphics device.
662 * @param cursor the cursor.
663 * @param plotArea the plot area.
664 * @param dataArea the data area.
665 * @param edge the edge that the axis is aligned with.
666 *
667 * @return The width or height used to draw the axis.
668 */
669 protected AxisState drawTickMarksAndLabels(Graphics2D g2,
670 double cursor,
671 Rectangle2D plotArea,
672 Rectangle2D dataArea,
673 RectangleEdge edge) {
674
675 AxisState state = new AxisState(cursor);
676
677 if (isAxisLineVisible()) {
678 drawAxisLine(g2, cursor, dataArea, edge);
679 }
680
681 double ol = getTickMarkOutsideLength();
682 double il = getTickMarkInsideLength();
683
684 List ticks = refreshTicks(g2, state, dataArea, edge);
685 state.setTicks(ticks);
686 g2.setFont(getTickLabelFont());
687 Iterator iterator = ticks.iterator();
688 while (iterator.hasNext()) {
689 ValueTick tick = (ValueTick) iterator.next();
690 if (isTickLabelsVisible()) {
691 g2.setPaint(getTickLabelPaint());
692 float[] anchorPoint = calculateAnchorPoint(tick, cursor,
693 dataArea, edge);
694 TextUtilities.drawRotatedString(tick.getText(), g2,
695 anchorPoint[0], anchorPoint[1], tick.getTextAnchor(),
696 tick.getAngle(), tick.getRotationAnchor());
697 }
698
699 if (isTickMarksVisible() && tick.getTickType().equals(
700 TickType.MAJOR)) {
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 *
1038 * @since 1.0.5
1039 */
1040 public Range getDefaultAutoRange() {
1041 return this.defaultAutoRange;
1042 }
1043
1044 /**
1045 * Sets the default auto range and sends an {@link AxisChangeEvent} to all
1046 * registered listeners.
1047 *
1048 * @param range the range (<code>null</code> not permitted).
1049 *
1050 * @see #getDefaultAutoRange()
1051 *
1052 * @since 1.0.5
1053 */
1054 public void setDefaultAutoRange(Range range) {
1055 if (range == null) {
1056 throw new IllegalArgumentException("Null 'range' argument.");
1057 }
1058 this.defaultAutoRange = range;
1059 notifyListeners(new AxisChangeEvent(this));
1060 }
1061
1062 /**
1063 * Returns the lower margin for the axis, expressed as a percentage of the
1064 * axis range. This controls the space added to the lower end of the axis
1065 * when the axis range is automatically calculated (it is ignored when the
1066 * axis range is set explicitly). The default value is 0.05 (five percent).
1067 *
1068 * @return The lower margin.
1069 *
1070 * @see #setLowerMargin(double)
1071 */
1072 public double getLowerMargin() {
1073 return this.lowerMargin;
1074 }
1075
1076 /**
1077 * Sets the lower margin for the axis (as a percentage of the axis range)
1078 * and sends an {@link AxisChangeEvent} to all registered listeners. This
1079 * margin is added only when the axis range is auto-calculated - if you set
1080 * the axis range manually, the margin is ignored.
1081 *
1082 * @param margin the margin percentage (for example, 0.05 is five percent).
1083 *
1084 * @see #getLowerMargin()
1085 * @see #setUpperMargin(double)
1086 */
1087 public void setLowerMargin(double margin) {
1088 this.lowerMargin = margin;
1089 if (isAutoRange()) {
1090 autoAdjustRange();
1091 }
1092 notifyListeners(new AxisChangeEvent(this));
1093 }
1094
1095 /**
1096 * Returns the upper margin for the axis, expressed as a percentage of the
1097 * axis range. This controls the space added to the lower end of the axis
1098 * when the axis range is automatically calculated (it is ignored when the
1099 * axis range is set explicitly). The default value is 0.05 (five percent).
1100 *
1101 * @return The upper margin.
1102 *
1103 * @see #setUpperMargin(double)
1104 */
1105 public double getUpperMargin() {
1106 return this.upperMargin;
1107 }
1108
1109 /**
1110 * Sets the upper margin for the axis (as a percentage of the axis range)
1111 * and sends an {@link AxisChangeEvent} to all registered listeners. This
1112 * margin is added only when the axis range is auto-calculated - if you set
1113 * the axis range manually, the margin is ignored.
1114 *
1115 * @param margin the margin percentage (for example, 0.05 is five percent).
1116 *
1117 * @see #getLowerMargin()
1118 * @see #setLowerMargin(double)
1119 */
1120 public void setUpperMargin(double margin) {
1121 this.upperMargin = margin;
1122 if (isAutoRange()) {
1123 autoAdjustRange();
1124 }
1125 notifyListeners(new AxisChangeEvent(this));
1126 }
1127
1128 /**
1129 * Returns the fixed auto range.
1130 *
1131 * @return The length.
1132 *
1133 * @see #setFixedAutoRange(double)
1134 */
1135 public double getFixedAutoRange() {
1136 return this.fixedAutoRange;
1137 }
1138
1139 /**
1140 * Sets the fixed auto range for the axis.
1141 *
1142 * @param length the range length.
1143 *
1144 * @see #getFixedAutoRange()
1145 */
1146 public void setFixedAutoRange(double length) {
1147 this.fixedAutoRange = length;
1148 if (isAutoRange()) {
1149 autoAdjustRange();
1150 }
1151 notifyListeners(new AxisChangeEvent(this));
1152 }
1153
1154 /**
1155 * Returns the lower bound of the axis range.
1156 *
1157 * @return The lower bound.
1158 *
1159 * @see #setLowerBound(double)
1160 */
1161 public double getLowerBound() {
1162 return this.range.getLowerBound();
1163 }
1164
1165 /**
1166 * Sets the lower bound for the axis range. An {@link AxisChangeEvent} is
1167 * sent to all registered listeners.
1168 *
1169 * @param min the new minimum.
1170 *
1171 * @see #getLowerBound()
1172 */
1173 public void setLowerBound(double min) {
1174 if (this.range.getUpperBound() > min) {
1175 setRange(new Range(min, this.range.getUpperBound()));
1176 }
1177 else {
1178 setRange(new Range(min, min + 1.0));
1179 }
1180 }
1181
1182 /**
1183 * Returns the upper bound for the axis range.
1184 *
1185 * @return The upper bound.
1186 *
1187 * @see #setUpperBound(double)
1188 */
1189 public double getUpperBound() {
1190 return this.range.getUpperBound();
1191 }
1192
1193 /**
1194 * Sets the upper bound for the axis range, and sends an
1195 * {@link AxisChangeEvent} to all registered listeners.
1196 *
1197 * @param max the new maximum.
1198 *
1199 * @see #getUpperBound()
1200 */
1201 public void setUpperBound(double max) {
1202 if (this.range.getLowerBound() < max) {
1203 setRange(new Range(this.range.getLowerBound(), max));
1204 }
1205 else {
1206 setRange(max - 1.0, max);
1207 }
1208 }
1209
1210 /**
1211 * Returns the range for the axis.
1212 *
1213 * @return The axis range (never <code>null</code>).
1214 *
1215 * @see #setRange(Range)
1216 */
1217 public Range getRange() {
1218 return this.range;
1219 }
1220
1221 /**
1222 * Sets the range attribute and sends an {@link AxisChangeEvent} to all
1223 * registered listeners. As a side-effect, the auto-range flag is set to
1224 * <code>false</code>.
1225 *
1226 * @param range the range (<code>null</code> not permitted).
1227 *
1228 * @see #getRange()
1229 */
1230 public void setRange(Range range) {
1231 // defer argument checking
1232 setRange(range, true, true);
1233 }
1234
1235 /**
1236 * Sets the range for the axis, if requested, sends an
1237 * {@link AxisChangeEvent} to all registered listeners. As a side-effect,
1238 * the auto-range flag is set to <code>false</code> (optional).
1239 *
1240 * @param range the range (<code>null</code> not permitted).
1241 * @param turnOffAutoRange a flag that controls whether or not the auto
1242 * range is turned off.
1243 * @param notify a flag that controls whether or not listeners are
1244 * notified.
1245 *
1246 * @see #getRange()
1247 */
1248 public void setRange(Range range, boolean turnOffAutoRange,
1249 boolean notify) {
1250 if (range == null) {
1251 throw new IllegalArgumentException("Null 'range' argument.");
1252 }
1253 if (turnOffAutoRange) {
1254 this.autoRange = false;
1255 }
1256 this.range = range;
1257 if (notify) {
1258 notifyListeners(new AxisChangeEvent(this));
1259 }
1260 }
1261
1262 /**
1263 * Sets the axis range and sends an {@link AxisChangeEvent} to all
1264 * registered listeners. As a side-effect, the auto-range flag is set to
1265 * <code>false</code>.
1266 *
1267 * @param lower the lower axis limit.
1268 * @param upper the upper axis limit.
1269 *
1270 * @see #getRange()
1271 * @see #setRange(Range)
1272 */
1273 public void setRange(double lower, double upper) {
1274 setRange(new Range(lower, upper));
1275 }
1276
1277 /**
1278 * Sets the range for the axis (after first adding the current margins to
1279 * the specified range) and sends an {@link AxisChangeEvent} to all
1280 * registered listeners.
1281 *
1282 * @param range the range (<code>null</code> not permitted).
1283 */
1284 public void setRangeWithMargins(Range range) {
1285 setRangeWithMargins(range, true, true);
1286 }
1287
1288 /**
1289 * Sets the range for the axis after first adding the current margins to
1290 * the range and, if requested, sends an {@link AxisChangeEvent} to all
1291 * registered listeners. As a side-effect, the auto-range flag is set to
1292 * <code>false</code> (optional).
1293 *
1294 * @param range the range (excluding margins, <code>null</code> not
1295 * permitted).
1296 * @param turnOffAutoRange a flag that controls whether or not the auto
1297 * range is turned off.
1298 * @param notify a flag that controls whether or not listeners are
1299 * notified.
1300 */
1301 public void setRangeWithMargins(Range range, boolean turnOffAutoRange,
1302 boolean notify) {
1303 if (range == null) {
1304 throw new IllegalArgumentException("Null 'range' argument.");
1305 }
1306 setRange(Range.expand(range, getLowerMargin(), getUpperMargin()),
1307 turnOffAutoRange, notify);
1308 }
1309
1310 /**
1311 * Sets the axis range (after first adding the current margins to the
1312 * range) and sends an {@link AxisChangeEvent} to all registered listeners.
1313 * As a side-effect, the auto-range flag is set to <code>false</code>.
1314 *
1315 * @param lower the lower axis limit.
1316 * @param upper the upper axis limit.
1317 */
1318 public void setRangeWithMargins(double lower, double upper) {
1319 setRangeWithMargins(new Range(lower, upper));
1320 }
1321
1322 /**
1323 * Sets the axis range, where the new range is 'size' in length, and
1324 * centered on 'value'.
1325 *
1326 * @param value the central value.
1327 * @param length the range length.
1328 */
1329 public void setRangeAboutValue(double value, double length) {
1330 setRange(new Range(value - length / 2, value + length / 2));
1331 }
1332
1333 /**
1334 * Returns a flag indicating whether or not the tick unit is automatically
1335 * selected from a range of standard tick units.
1336 *
1337 * @return A flag indicating whether or not the tick unit is automatically
1338 * selected.
1339 *
1340 * @see #setAutoTickUnitSelection(boolean)
1341 */
1342 public boolean isAutoTickUnitSelection() {
1343 return this.autoTickUnitSelection;
1344 }
1345
1346 /**
1347 * Sets a flag indicating whether or not the tick unit is automatically
1348 * selected from a range of standard tick units. If the flag is changed,
1349 * registered listeners are notified that the chart has changed.
1350 *
1351 * @param flag the new value of the flag.
1352 *
1353 * @see #isAutoTickUnitSelection()
1354 */
1355 public void setAutoTickUnitSelection(boolean flag) {
1356 setAutoTickUnitSelection(flag, true);
1357 }
1358
1359 /**
1360 * Sets a flag indicating whether or not the tick unit is automatically
1361 * selected from a range of standard tick units.
1362 *
1363 * @param flag the new value of the flag.
1364 * @param notify notify listeners?
1365 *
1366 * @see #isAutoTickUnitSelection()
1367 */
1368 public void setAutoTickUnitSelection(boolean flag, boolean notify) {
1369
1370 if (this.autoTickUnitSelection != flag) {
1371 this.autoTickUnitSelection = flag;
1372 if (notify) {
1373 notifyListeners(new AxisChangeEvent(this));
1374 }
1375 }
1376 }
1377
1378 /**
1379 * Returns the source for obtaining standard tick units for the axis.
1380 *
1381 * @return The source (possibly <code>null</code>).
1382 *
1383 * @see #setStandardTickUnits(TickUnitSource)
1384 */
1385 public TickUnitSource getStandardTickUnits() {
1386 return this.standardTickUnits;
1387 }
1388
1389 /**
1390 * Sets the source for obtaining standard tick units for the axis and sends
1391 * an {@link AxisChangeEvent} to all registered listeners. The axis will
1392 * try to select the smallest tick unit from the source that does not cause
1393 * the tick labels to overlap (see also the
1394 * {@link #setAutoTickUnitSelection(boolean)} method.
1395 *
1396 * @param source the source for standard tick units (<code>null</code>
1397 * permitted).
1398 *
1399 * @see #getStandardTickUnits()
1400 */
1401 public void setStandardTickUnits(TickUnitSource source) {
1402 this.standardTickUnits = source;
1403 notifyListeners(new AxisChangeEvent(this));
1404 }
1405
1406 /**
1407 * Converts a data value to a coordinate in Java2D space, assuming that the
1408 * axis runs along one edge of the specified dataArea.
1409 * <p>
1410 * Note that it is possible for the coordinate to fall outside the area.
1411 *
1412 * @param value the data value.
1413 * @param area the area for plotting the data.
1414 * @param edge the edge along which the axis lies.
1415 *
1416 * @return The Java2D coordinate.
1417 *
1418 * @see #java2DToValue(double, Rectangle2D, RectangleEdge)
1419 */
1420 public abstract double valueToJava2D(double value, Rectangle2D area,
1421 RectangleEdge edge);
1422
1423 /**
1424 * Converts a length in data coordinates into the corresponding length in
1425 * Java2D coordinates.
1426 *
1427 * @param length the length.
1428 * @param area the plot area.
1429 * @param edge the edge along which the axis lies.
1430 *
1431 * @return The length in Java2D coordinates.
1432 */
1433 public double lengthToJava2D(double length, Rectangle2D area,
1434 RectangleEdge edge) {
1435 double zero = valueToJava2D(0.0, area, edge);
1436 double l = valueToJava2D(length, area, edge);
1437 return Math.abs(l - zero);
1438 }
1439
1440 /**
1441 * Converts a coordinate in Java2D space to the corresponding data value,
1442 * assuming that the axis runs along one edge of the specified dataArea.
1443 *
1444 * @param java2DValue the coordinate in Java2D space.
1445 * @param area the area in which the data is plotted.
1446 * @param edge the edge along which the axis lies.
1447 *
1448 * @return The data value.
1449 *
1450 * @see #valueToJava2D(double, Rectangle2D, RectangleEdge)
1451 */
1452 public abstract double java2DToValue(double java2DValue,
1453 Rectangle2D area,
1454 RectangleEdge edge);
1455
1456 /**
1457 * Automatically sets the axis range to fit the range of values in the
1458 * dataset. Sometimes this can depend on the renderer used as well (for
1459 * example, the renderer may "stack" values, requiring an axis range
1460 * greater than otherwise necessary).
1461 */
1462 protected abstract void autoAdjustRange();
1463
1464 /**
1465 * Centers the axis range about the specified value and sends an
1466 * {@link AxisChangeEvent} to all registered listeners.
1467 *
1468 * @param value the center value.
1469 */
1470 public void centerRange(double value) {
1471
1472 double central = this.range.getCentralValue();
1473 Range adjusted = new Range(this.range.getLowerBound() + value - central,
1474 this.range.getUpperBound() + value - central);
1475 setRange(adjusted);
1476
1477 }
1478
1479 /**
1480 * Increases or decreases the axis range by the specified percentage about
1481 * the central value and sends an {@link AxisChangeEvent} to all registered
1482 * listeners.
1483 * <P>
1484 * To double the length of the axis range, use 200% (2.0).
1485 * To halve the length of the axis range, use 50% (0.5).
1486 *
1487 * @param percent the resize factor.
1488 *
1489 * @see #resizeRange(double, double)
1490 */
1491 public void resizeRange(double percent) {
1492 resizeRange(percent, this.range.getCentralValue());
1493 }
1494
1495 /**
1496 * Increases or decreases the axis range by the specified percentage about
1497 * the specified anchor value and sends an {@link AxisChangeEvent} to all
1498 * registered listeners.
1499 * <P>
1500 * To double the length of the axis range, use 200% (2.0).
1501 * To halve the length of the axis range, use 50% (0.5).
1502 *
1503 * @param percent the resize factor.
1504 * @param anchorValue the new central value after the resize.
1505 *
1506 * @see #resizeRange(double)
1507 */
1508 public void resizeRange(double percent, double anchorValue) {
1509 if (percent > 0.0) {
1510 double halfLength = this.range.getLength() * percent / 2;
1511 Range adjusted = new Range(anchorValue - halfLength,
1512 anchorValue + halfLength);
1513 setRange(adjusted);
1514 }
1515 else {
1516 setAutoRange(true);
1517 }
1518 }
1519
1520 /**
1521 * Zooms in on the current range.
1522 *
1523 * @param lowerPercent the new lower bound.
1524 * @param upperPercent the new upper bound.
1525 */
1526 public void zoomRange(double lowerPercent, double upperPercent) {
1527 double start = this.range.getLowerBound();
1528 double length = this.range.getLength();
1529 Range adjusted = null;
1530 if (isInverted()) {
1531 adjusted = new Range(start + (length * (1 - upperPercent)),
1532 start + (length * (1 - lowerPercent)));
1533 }
1534 else {
1535 adjusted = new Range(start + length * lowerPercent,
1536 start + length * upperPercent);
1537 }
1538 setRange(adjusted);
1539 }
1540
1541 /**
1542 * Returns the auto tick index.
1543 *
1544 * @return The auto tick index.
1545 *
1546 * @see #setAutoTickIndex(int)
1547 */
1548 protected int getAutoTickIndex() {
1549 return this.autoTickIndex;
1550 }
1551
1552 /**
1553 * Sets the auto tick index.
1554 *
1555 * @param index the new value.
1556 *
1557 * @see #getAutoTickIndex()
1558 */
1559 protected void setAutoTickIndex(int index) {
1560 this.autoTickIndex = index;
1561 }
1562
1563 /**
1564 * Tests the axis for equality with an arbitrary object.
1565 *
1566 * @param obj the object (<code>null</code> permitted).
1567 *
1568 * @return <code>true</code> or <code>false</code>.
1569 */
1570 public boolean equals(Object obj) {
1571
1572 if (obj == this) {
1573 return true;
1574 }
1575 if (!(obj instanceof ValueAxis)) {
1576 return false;
1577 }
1578
1579 ValueAxis that = (ValueAxis) obj;
1580
1581 if (this.positiveArrowVisible != that.positiveArrowVisible) {
1582 return false;
1583 }
1584 if (this.negativeArrowVisible != that.negativeArrowVisible) {
1585 return false;
1586 }
1587 if (this.inverted != that.inverted) {
1588 return false;
1589 }
1590 if (!ObjectUtilities.equal(this.range, that.range)) {
1591 return false;
1592 }
1593 if (this.autoRange != that.autoRange) {
1594 return false;
1595 }
1596 if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) {
1597 return false;
1598 }
1599 if (!this.defaultAutoRange.equals(that.defaultAutoRange)) {
1600 return false;
1601 }
1602 if (this.upperMargin != that.upperMargin) {
1603 return false;
1604 }
1605 if (this.lowerMargin != that.lowerMargin) {
1606 return false;
1607 }
1608 if (this.fixedAutoRange != that.fixedAutoRange) {
1609 return false;
1610 }
1611 if (this.autoTickUnitSelection != that.autoTickUnitSelection) {
1612 return false;
1613 }
1614 if (!ObjectUtilities.equal(this.standardTickUnits,
1615 that.standardTickUnits)) {
1616 return false;
1617 }
1618 if (this.verticalTickLabels != that.verticalTickLabels) {
1619 return false;
1620 }
1621
1622 return super.equals(obj);
1623
1624 }
1625
1626 /**
1627 * Returns a clone of the object.
1628 *
1629 * @return A clone.
1630 *
1631 * @throws CloneNotSupportedException if some component of the axis does
1632 * not support cloning.
1633 */
1634 public Object clone() throws CloneNotSupportedException {
1635 ValueAxis clone = (ValueAxis) super.clone();
1636 return clone;
1637 }
1638
1639 /**
1640 * Provides serialization support.
1641 *
1642 * @param stream the output stream.
1643 *
1644 * @throws IOException if there is an I/O error.
1645 */
1646 private void writeObject(ObjectOutputStream stream) throws IOException {
1647 stream.defaultWriteObject();
1648 SerialUtilities.writeShape(this.upArrow, stream);
1649 SerialUtilities.writeShape(this.downArrow, stream);
1650 SerialUtilities.writeShape(this.leftArrow, stream);
1651 SerialUtilities.writeShape(this.rightArrow, stream);
1652 }
1653
1654 /**
1655 * Provides serialization support.
1656 *
1657 * @param stream the input stream.
1658 *
1659 * @throws IOException if there is an I/O error.
1660 * @throws ClassNotFoundException if there is a classpath problem.
1661 */
1662 private void readObject(ObjectInputStream stream)
1663 throws IOException, ClassNotFoundException {
1664
1665 stream.defaultReadObject();
1666 this.upArrow = SerialUtilities.readShape(stream);
1667 this.downArrow = SerialUtilities.readShape(stream);
1668 this.leftArrow = SerialUtilities.readShape(stream);
1669 this.rightArrow = SerialUtilities.readShape(stream);
1670
1671 }
1672
1673 }