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