001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ------------
028 * LogAxis.java
029 * ------------
030 * (C) Copyright 2006, 2007, by Object Refinery Limited and Contributors.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): -;
034 *
035 * Changes
036 * -------
037 * 24-Aug-2006 : Version 1 (DG);
038 * 22-Mar-2007 : Use defaultAutoArrange attribute (DG);
039 * 02-Aug-2007 : Fixed zooming bug, added support for margins (DG);
040 *
041 */
042
043 package org.jfree.chart.axis;
044
045 import java.awt.Font;
046 import java.awt.FontMetrics;
047 import java.awt.Graphics2D;
048 import java.awt.font.FontRenderContext;
049 import java.awt.font.LineMetrics;
050 import java.awt.geom.Rectangle2D;
051 import java.text.DecimalFormat;
052 import java.text.NumberFormat;
053 import java.util.ArrayList;
054 import java.util.List;
055 import java.util.Locale;
056
057 import org.jfree.chart.event.AxisChangeEvent;
058 import org.jfree.chart.plot.Plot;
059 import org.jfree.chart.plot.PlotRenderingInfo;
060 import org.jfree.chart.plot.ValueAxisPlot;
061 import org.jfree.data.Range;
062 import org.jfree.ui.RectangleEdge;
063 import org.jfree.ui.RectangleInsets;
064 import org.jfree.ui.TextAnchor;
065
066 /**
067 * A numerical axis that uses a logarithmic scale. The class is an
068 * alternative to the {@link LogarithmicAxis} class.
069 *
070 * @since 1.0.7
071 */
072 public class LogAxis extends ValueAxis {
073
074 /** The logarithm base. */
075 private double base = 10.0;
076
077 /** The logarithm of the base value - cached for performance. */
078 private double baseLog = Math.log(10.0);
079
080 /** The smallest value permitted on the axis. */
081 private double smallestValue = 1E-100;
082
083 /** The current tick unit. */
084 private NumberTickUnit tickUnit;
085
086 /** The override number format. */
087 private NumberFormat numberFormatOverride;
088
089 /** The number of minor ticks per major tick unit. */
090 private int minorTickCount;
091
092 /**
093 * Creates a new <code>LogAxis</code> with no label.
094 */
095 public LogAxis() {
096 this(null);
097 }
098
099 /**
100 * Creates a new <code>LogAxis</code> with the given label.
101 *
102 * @param label the axis label (<code>null</code> permitted).
103 */
104 public LogAxis(String label) {
105 super(label, createLogTickUnits(Locale.getDefault()));
106 setDefaultAutoRange(new Range(0.01, 1.0));
107 this.tickUnit = new NumberTickUnit(1.0, new DecimalFormat("0.#"));
108 this.minorTickCount = 10;
109 }
110
111 /**
112 * Returns the base for the logarithm calculation.
113 *
114 * @return The base for the logarithm calculation.
115 *
116 * @see #setBase(double)
117 */
118 public double getBase() {
119 return this.base;
120 }
121
122 /**
123 * Sets the base for the logarithm calculation and sends an
124 * {@link AxisChangeEvent} to all registered listeners.
125 *
126 * @param base the base value (must be > 1.0).
127 *
128 * @see #getBase()
129 */
130 public void setBase(double base) {
131 if (base <= 1.0) {
132 throw new IllegalArgumentException("Requires 'base' > 1.0.");
133 }
134 this.base = base;
135 this.baseLog = Math.log(base);
136 notifyListeners(new AxisChangeEvent(this));
137 }
138
139 /**
140 * Returns the smallest value represented by the axis.
141 *
142 * @return The smallest value represented by the axis.
143 *
144 * @see #setSmallestValue(double)
145 */
146 public double getSmallestValue() {
147 return this.smallestValue;
148 }
149
150 /**
151 * Sets the smallest value represented by the axis and sends an
152 * {@link AxisChangeEvent} to all registered listeners.
153 *
154 * @param value the value.
155 *
156 * @see #getSmallestValue()
157 */
158 public void setSmallestValue(double value) {
159 if (value <= 0.0) {
160 throw new IllegalArgumentException("Requires 'value' > 0.0.");
161 }
162 this.smallestValue = value;
163 notifyListeners(new AxisChangeEvent(this));
164 }
165
166 /**
167 * Returns the current tick unit.
168 *
169 * @return The current tick unit.
170 *
171 * @see #setTickUnit(NumberTickUnit)
172 */
173 public NumberTickUnit getTickUnit() {
174 return this.tickUnit;
175 }
176
177 /**
178 * Sets the tick unit for the axis and sends an {@link AxisChangeEvent} to
179 * all registered listeners. A side effect of calling this method is that
180 * the "auto-select" feature for tick units is switched off (you can
181 * restore it using the {@link ValueAxis#setAutoTickUnitSelection(boolean)}
182 * method).
183 *
184 * @param unit the new tick unit (<code>null</code> not permitted).
185 *
186 * @see #getTickUnit()
187 */
188 public void setTickUnit(NumberTickUnit unit) {
189 // defer argument checking...
190 setTickUnit(unit, true, true);
191 }
192
193 /**
194 * Sets the tick unit for the axis and, if requested, sends an
195 * {@link AxisChangeEvent} to all registered listeners. In addition, an
196 * option is provided to turn off the "auto-select" feature for tick units
197 * (you can restore it using the
198 * {@link ValueAxis#setAutoTickUnitSelection(boolean)} method).
199 *
200 * @param unit the new tick unit (<code>null</code> not permitted).
201 * @param notify notify listeners?
202 * @param turnOffAutoSelect turn off the auto-tick selection?
203 *
204 * @see #getTickUnit()
205 */
206 public void setTickUnit(NumberTickUnit unit, boolean notify,
207 boolean turnOffAutoSelect) {
208
209 if (unit == null) {
210 throw new IllegalArgumentException("Null 'unit' argument.");
211 }
212 this.tickUnit = unit;
213 if (turnOffAutoSelect) {
214 setAutoTickUnitSelection(false, false);
215 }
216 if (notify) {
217 notifyListeners(new AxisChangeEvent(this));
218 }
219
220 }
221
222 /**
223 * Returns the number format override. If this is non-null, then it will
224 * be used to format the numbers on the axis.
225 *
226 * @return The number formatter (possibly <code>null</code>).
227 *
228 * @see #setNumberFormatOverride(NumberFormat)
229 */
230 public NumberFormat getNumberFormatOverride() {
231 return this.numberFormatOverride;
232 }
233
234 /**
235 * Sets the number format override. If this is non-null, then it will be
236 * used to format the numbers on the axis.
237 *
238 * @param formatter the number formatter (<code>null</code> permitted).
239 *
240 * @see #getNumberFormatOverride()
241 */
242 public void setNumberFormatOverride(NumberFormat formatter) {
243 this.numberFormatOverride = formatter;
244 notifyListeners(new AxisChangeEvent(this));
245 }
246
247 /**
248 * Returns the number of minor tick marks to display.
249 *
250 * @return The number of minor tick marks to display.
251 *
252 * @see #setMinorTickCount(int)
253 */
254 public int getMinorTickCount() {
255 return this.minorTickCount;
256 }
257
258 /**
259 * Sets the number of minor tick marks to display, and sends an
260 * {@link AxisChangeEvent} to all registered listeners.
261 *
262 * @param count the count.
263 *
264 * @see #getMinorTickCount()
265 */
266 public void setMinorTickCount(int count) {
267 if (count <= 0) {
268 throw new IllegalArgumentException("Requires 'count' > 0.");
269 }
270 this.minorTickCount = count;
271 notifyListeners(new AxisChangeEvent(this));
272 }
273
274 /**
275 * Calculates the log of the given value, using the current base.
276 *
277 * @param value the value.
278 *
279 * @return The log of the given value.
280 *
281 * @see #calculateValue(double)
282 * @see #getBase()
283 */
284 public double calculateLog(double value) {
285 return Math.log(value) / this.baseLog;
286 }
287
288 /**
289 * Calculates the value from a given log.
290 *
291 * @param log the log value (must be > 0.0).
292 *
293 * @return The value with the given log.
294 *
295 * @see #calculateLog(double)
296 * @see #getBase()
297 */
298 public double calculateValue(double log) {
299 return Math.pow(this.base, log);
300 }
301
302 /**
303 * Converts a Java2D coordinate to an axis value, assuming that the
304 * axis covers the specified <code>edge</code> of the <code>area</code>.
305 *
306 * @param java2DValue the Java2D coordinate.
307 * @param area the area.
308 * @param edge the edge that the axis belongs to.
309 *
310 * @return A value along the axis scale.
311 */
312 public double java2DToValue(double java2DValue, Rectangle2D area,
313 RectangleEdge edge) {
314
315 Range range = getRange();
316 double axisMin = calculateLog(range.getLowerBound());
317 double axisMax = calculateLog(range.getUpperBound());
318
319 double min = 0.0;
320 double max = 0.0;
321 if (RectangleEdge.isTopOrBottom(edge)) {
322 min = area.getX();
323 max = area.getMaxX();
324 }
325 else if (RectangleEdge.isLeftOrRight(edge)) {
326 min = area.getMaxY();
327 max = area.getY();
328 }
329 double log = 0.0;
330 if (isInverted()) {
331 log = axisMax - (java2DValue - min) / (max - min)
332 * (axisMax - axisMin);
333 }
334 else {
335 log = axisMin + (java2DValue - min) / (max - min)
336 * (axisMax - axisMin);
337 }
338 return calculateValue(log);
339 }
340
341 /**
342 * Converts a value on the axis scale to a Java2D coordinate relative to
343 * the given <code>area</code>, based on the axis running along the
344 * specified <code>edge</code>.
345 *
346 * @param value the data value.
347 * @param area the area.
348 * @param edge the edge.
349 *
350 * @return The Java2D coordinate corresponding to <code>value</code>.
351 */
352 public double valueToJava2D(double value, Rectangle2D area,
353 RectangleEdge edge) {
354
355 Range range = getRange();
356 double axisMin = calculateLog(range.getLowerBound());
357 double axisMax = calculateLog(range.getUpperBound());
358 value = calculateLog(value);
359
360 double min = 0.0;
361 double max = 0.0;
362 if (RectangleEdge.isTopOrBottom(edge)) {
363 min = area.getX();
364 max = area.getMaxX();
365 }
366 else if (RectangleEdge.isLeftOrRight(edge)) {
367 max = area.getMinY();
368 min = area.getMaxY();
369 }
370 if (isInverted()) {
371 return max
372 - ((value - axisMin) / (axisMax - axisMin)) * (max - min);
373 }
374 else {
375 return min
376 + ((value - axisMin) / (axisMax - axisMin)) * (max - min);
377 }
378 }
379
380 /**
381 * Configures the axis. This method is typically called when an axis
382 * is assigned to a new plot.
383 */
384 public void configure() {
385 if (isAutoRange()) {
386 autoAdjustRange();
387 }
388 }
389
390 /**
391 * Adjusts the axis range to match the data range that the axis is
392 * required to display.
393 */
394 protected void autoAdjustRange() {
395 Plot plot = getPlot();
396 if (plot == null) {
397 return; // no plot, no data
398 }
399
400 if (plot instanceof ValueAxisPlot) {
401 ValueAxisPlot vap = (ValueAxisPlot) plot;
402
403 Range r = vap.getDataRange(this);
404 if (r == null) {
405 r = getDefaultAutoRange();
406 }
407
408 double upper = r.getUpperBound();
409 double lower = Math.max(r.getLowerBound(), this.smallestValue);
410 double range = upper - lower;
411
412 // if fixed auto range, then derive lower bound...
413 double fixedAutoRange = getFixedAutoRange();
414 if (fixedAutoRange > 0.0) {
415 lower = Math.max(upper - fixedAutoRange, this.smallestValue);
416 }
417 else {
418 // ensure the autorange is at least <minRange> in size...
419 double minRange = getAutoRangeMinimumSize();
420 if (range < minRange) {
421 double expand = (minRange - range) / 2;
422 upper = upper + expand;
423 lower = lower - expand;
424 }
425
426 // apply the margins - these should apply to the exponent range
427 double logUpper = calculateLog(upper);
428 double logLower = calculateLog(lower);
429 double logRange = logUpper - logLower;
430 logUpper = logUpper + getUpperMargin() * logRange;
431 logLower = logLower - getLowerMargin() * logRange;
432 upper = calculateValue(logUpper);
433 lower = calculateValue(logLower);
434 }
435
436 setRange(new Range(lower, upper), false, false);
437 }
438
439 }
440
441 /**
442 * Draws the axis on a Java 2D graphics device (such as the screen or a
443 * printer).
444 *
445 * @param g2 the graphics device (<code>null</code> not permitted).
446 * @param cursor the cursor location (determines where to draw the axis).
447 * @param plotArea the area within which the axes and plot should be drawn.
448 * @param dataArea the area within which the data should be drawn.
449 * @param edge the axis location (<code>null</code> not permitted).
450 * @param plotState collects information about the plot
451 * (<code>null</code> permitted).
452 *
453 * @return The axis state (never <code>null</code>).
454 */
455 public AxisState draw(Graphics2D g2, double cursor, Rectangle2D plotArea,
456 Rectangle2D dataArea, RectangleEdge edge,
457 PlotRenderingInfo plotState) {
458
459 AxisState state = null;
460 // if the axis is not visible, don't draw it...
461 if (!isVisible()) {
462 state = new AxisState(cursor);
463 // even though the axis is not visible, we need ticks for the
464 // gridlines...
465 List ticks = refreshTicks(g2, state, dataArea, edge);
466 state.setTicks(ticks);
467 return state;
468 }
469 state = drawTickMarksAndLabels(g2, cursor, plotArea, dataArea, edge);
470 state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state);
471 return state;
472 }
473
474 /**
475 * Calculates the positions of the tick labels for the axis, storing the
476 * results in the tick label list (ready for drawing).
477 *
478 * @param g2 the graphics device.
479 * @param state the axis state.
480 * @param dataArea the area in which the plot should be drawn.
481 * @param edge the location of the axis.
482 *
483 * @return A list of ticks.
484 *
485 */
486 public List refreshTicks(Graphics2D g2, AxisState state,
487 Rectangle2D dataArea, RectangleEdge edge) {
488
489 List result = new java.util.ArrayList();
490 if (RectangleEdge.isTopOrBottom(edge)) {
491 result = refreshTicksHorizontal(g2, dataArea, edge);
492 }
493 else if (RectangleEdge.isLeftOrRight(edge)) {
494 result = refreshTicksVertical(g2, dataArea, edge);
495 }
496 return result;
497
498 }
499
500 /**
501 * Returns a list of ticks for an axis at the top or bottom of the chart.
502 *
503 * @param g2 the graphics device.
504 * @param dataArea the data area.
505 * @param edge the edge.
506 *
507 * @return A list of ticks.
508 */
509 protected List refreshTicksHorizontal(Graphics2D g2, Rectangle2D dataArea,
510 RectangleEdge edge) {
511
512 Range range = getRange();
513 List ticks = new ArrayList();
514 Font tickLabelFont = getTickLabelFont();
515 g2.setFont(tickLabelFont);
516
517 if (isAutoTickUnitSelection()) {
518 selectAutoTickUnit(g2, dataArea, edge);
519 }
520 double start = Math.floor(calculateLog(getLowerBound()));
521 double end = Math.ceil(calculateLog(getUpperBound()));
522 double current = start;
523 while (current <= end) {
524 double v = calculateValue(current);
525 if (range.contains(v)) {
526 ticks.add(new NumberTick(TickType.MAJOR, v, createTickLabel(v),
527 TextAnchor.TOP_CENTER, TextAnchor.CENTER, 0.0));
528 }
529 // add minor ticks (for gridlines)
530 double next = Math.pow(this.base, current
531 + this.tickUnit.getSize());
532 for (int i = 1; i < this.minorTickCount; i++) {
533 double minorV = v + i * ((next - v) / this.minorTickCount);
534 if (range.contains(minorV)) {
535 ticks.add(new NumberTick(TickType.MINOR, minorV,
536 "", TextAnchor.TOP_CENTER, TextAnchor.CENTER, 0.0));
537 }
538 }
539 current = current + this.tickUnit.getSize();
540 }
541 return ticks;
542 }
543
544 /**
545 * Returns a list of ticks for an axis at the left or right of the chart.
546 *
547 * @param g2 the graphics device.
548 * @param dataArea the data area.
549 * @param edge the edge.
550 *
551 * @return A list of ticks.
552 */
553 protected List refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea,
554 RectangleEdge edge) {
555
556 Range range = getRange();
557 List ticks = new ArrayList();
558 Font tickLabelFont = getTickLabelFont();
559 g2.setFont(tickLabelFont);
560
561 if (isAutoTickUnitSelection()) {
562 selectAutoTickUnit(g2, dataArea, edge);
563 }
564 double start = Math.floor(calculateLog(getLowerBound()));
565 double end = Math.ceil(calculateLog(getUpperBound()));
566 double current = start;
567 while (current <= end) {
568 double v = calculateValue(current);
569 if (range.contains(v)) {
570 ticks.add(new NumberTick(TickType.MINOR, v, createTickLabel(v),
571 TextAnchor.CENTER_RIGHT, TextAnchor.CENTER, 0.0));
572 }
573 // add minor ticks (for gridlines)
574 double next = Math.pow(this.base, current
575 + this.tickUnit.getSize());
576 for (int i = 1; i < this.minorTickCount; i++) {
577 double minorV = v + i * ((next - v) / this.minorTickCount);
578 if (range.contains(minorV)) {
579 ticks.add(new NumberTick(TickType.MINOR, minorV, "",
580 TextAnchor.CENTER_RIGHT, TextAnchor.CENTER, 0.0));
581 }
582 }
583 current = current + this.tickUnit.getSize();
584 }
585 return ticks;
586 }
587
588 /**
589 * Selects an appropriate tick value for the axis. The strategy is to
590 * display as many ticks as possible (selected from an array of 'standard'
591 * tick units) without the labels overlapping.
592 *
593 * @param g2 the graphics device.
594 * @param dataArea the area defined by the axes.
595 * @param edge the axis location.
596 *
597 * @since 1.0.7
598 */
599 protected void selectAutoTickUnit(Graphics2D g2, Rectangle2D dataArea,
600 RectangleEdge edge) {
601
602 if (RectangleEdge.isTopOrBottom(edge)) {
603 selectHorizontalAutoTickUnit(g2, dataArea, edge);
604 }
605 else if (RectangleEdge.isLeftOrRight(edge)) {
606 selectVerticalAutoTickUnit(g2, dataArea, edge);
607 }
608
609 }
610
611 /**
612 * Selects an appropriate tick value for the axis. The strategy is to
613 * display as many ticks as possible (selected from an array of 'standard'
614 * tick units) without the labels overlapping.
615 *
616 * @param g2 the graphics device.
617 * @param dataArea the area defined by the axes.
618 * @param edge the axis location.
619 *
620 * @since 1.0.7
621 */
622 protected void selectHorizontalAutoTickUnit(Graphics2D g2,
623 Rectangle2D dataArea, RectangleEdge edge) {
624
625 double tickLabelWidth = estimateMaximumTickLabelWidth(g2,
626 getTickUnit());
627
628 // start with the current tick unit...
629 TickUnitSource tickUnits = getStandardTickUnits();
630 TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
631 double unit1Width = exponentLengthToJava2D(unit1.getSize(), dataArea,
632 edge);
633
634 // then extrapolate...
635 double guess = (tickLabelWidth / unit1Width) * unit1.getSize();
636
637 NumberTickUnit unit2 = (NumberTickUnit)
638 tickUnits.getCeilingTickUnit(guess);
639 double unit2Width = exponentLengthToJava2D(unit2.getSize(), dataArea,
640 edge);
641
642 tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
643 if (tickLabelWidth > unit2Width) {
644 unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
645 }
646
647 setTickUnit(unit2, false, false);
648
649 }
650
651 /**
652 * Converts a length in data coordinates into the corresponding length in
653 * Java2D coordinates.
654 *
655 * @param length the length.
656 * @param area the plot area.
657 * @param edge the edge along which the axis lies.
658 *
659 * @return The length in Java2D coordinates.
660 *
661 * @since 1.0.7
662 */
663 public double exponentLengthToJava2D(double length, Rectangle2D area,
664 RectangleEdge edge) {
665 double one = valueToJava2D(calculateValue(1.0), area, edge);
666 double l = valueToJava2D(calculateValue(length + 1.0), area, edge);
667 return Math.abs(l - one);
668 }
669
670 /**
671 * Selects an appropriate tick value for the axis. The strategy is to
672 * display as many ticks as possible (selected from an array of 'standard'
673 * tick units) without the labels overlapping.
674 *
675 * @param g2 the graphics device.
676 * @param dataArea the area in which the plot should be drawn.
677 * @param edge the axis location.
678 *
679 * @since 1.0.7
680 */
681 protected void selectVerticalAutoTickUnit(Graphics2D g2,
682 Rectangle2D dataArea,
683 RectangleEdge edge) {
684
685 double tickLabelHeight = estimateMaximumTickLabelHeight(g2);
686
687 // start with the current tick unit...
688 TickUnitSource tickUnits = getStandardTickUnits();
689 TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
690 double unitHeight = exponentLengthToJava2D(unit1.getSize(), dataArea,
691 edge);
692
693 // then extrapolate...
694 double guess = (tickLabelHeight / unitHeight) * unit1.getSize();
695
696 NumberTickUnit unit2 = (NumberTickUnit)
697 tickUnits.getCeilingTickUnit(guess);
698 double unit2Height = exponentLengthToJava2D(unit2.getSize(), dataArea,
699 edge);
700
701 tickLabelHeight = estimateMaximumTickLabelHeight(g2);
702 if (tickLabelHeight > unit2Height) {
703 unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
704 }
705
706 setTickUnit(unit2, false, false);
707
708 }
709
710 /**
711 * Estimates the maximum tick label height.
712 *
713 * @param g2 the graphics device.
714 *
715 * @return The maximum height.
716 *
717 * @since 1.0.7
718 */
719 protected double estimateMaximumTickLabelHeight(Graphics2D g2) {
720
721 RectangleInsets tickLabelInsets = getTickLabelInsets();
722 double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom();
723
724 Font tickLabelFont = getTickLabelFont();
725 FontRenderContext frc = g2.getFontRenderContext();
726 result += tickLabelFont.getLineMetrics("123", frc).getHeight();
727 return result;
728
729 }
730
731 /**
732 * Estimates the maximum width of the tick labels, assuming the specified
733 * tick unit is used.
734 * <P>
735 * Rather than computing the string bounds of every tick on the axis, we
736 * just look at two values: the lower bound and the upper bound for the
737 * axis. These two values will usually be representative.
738 *
739 * @param g2 the graphics device.
740 * @param unit the tick unit to use for calculation.
741 *
742 * @return The estimated maximum width of the tick labels.
743 *
744 * @since 1.0.7
745 */
746 protected double estimateMaximumTickLabelWidth(Graphics2D g2,
747 TickUnit unit) {
748
749 RectangleInsets tickLabelInsets = getTickLabelInsets();
750 double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight();
751
752 if (isVerticalTickLabels()) {
753 // all tick labels have the same width (equal to the height of the
754 // font)...
755 FontRenderContext frc = g2.getFontRenderContext();
756 LineMetrics lm = getTickLabelFont().getLineMetrics("0", frc);
757 result += lm.getHeight();
758 }
759 else {
760 // look at lower and upper bounds...
761 FontMetrics fm = g2.getFontMetrics(getTickLabelFont());
762 Range range = getRange();
763 double lower = range.getLowerBound();
764 double upper = range.getUpperBound();
765 String lowerStr = "";
766 String upperStr = "";
767 NumberFormat formatter = getNumberFormatOverride();
768 if (formatter != null) {
769 lowerStr = formatter.format(lower);
770 upperStr = formatter.format(upper);
771 }
772 else {
773 lowerStr = unit.valueToString(lower);
774 upperStr = unit.valueToString(upper);
775 }
776 double w1 = fm.stringWidth(lowerStr);
777 double w2 = fm.stringWidth(upperStr);
778 result += Math.max(w1, w2);
779 }
780
781 return result;
782
783 }
784
785 /**
786 * Zooms in on the current range.
787 *
788 * @param lowerPercent the new lower bound.
789 * @param upperPercent the new upper bound.
790 */
791 public void zoomRange(double lowerPercent, double upperPercent) {
792 Range range = getRange();
793 double start = range.getLowerBound();
794 double end = range.getUpperBound();
795 double log1 = calculateLog(start);
796 double log2 = calculateLog(end);
797 double length = log2 - log1;
798 Range adjusted = null;
799 if (isInverted()) {
800 double logA = log1 + length * (1 - upperPercent);
801 double logB = log1 + length * (1 - lowerPercent);
802 adjusted = new Range(calculateValue(logA), calculateValue(logB));
803 }
804 else {
805 double logA = log1 + length * lowerPercent;
806 double logB = log1 + length * upperPercent;
807 adjusted = new Range(calculateValue(logA), calculateValue(logB));
808 }
809 setRange(adjusted);
810 }
811
812 /**
813 * Creates a tick label for the specified value.
814 *
815 * @param value the value.
816 *
817 * @return The label.
818 */
819 private String createTickLabel(double value) {
820 if (this.numberFormatOverride != null) {
821 return this.numberFormatOverride.format(value);
822 }
823 else {
824 return this.tickUnit.valueToString(value);
825 }
826 }
827
828 /**
829 * Tests this axis for equality with an arbitrary object.
830 *
831 * @param obj the object (<code>null</code> permitted).
832 *
833 * @return A boolean.
834 */
835 public boolean equals(Object obj) {
836 if (obj == this) {
837 return true;
838 }
839 if (!(obj instanceof LogAxis)) {
840 return false;
841 }
842 LogAxis that = (LogAxis) obj;
843 if (this.base != that.base) {
844 return false;
845 }
846 if (this.smallestValue != that.smallestValue) {
847 return false;
848 }
849 if (this.minorTickCount != that.minorTickCount) {
850 return false;
851 }
852 return super.equals(obj);
853 }
854
855 /**
856 * Returns a hash code for this instance.
857 *
858 * @return A hash code.
859 */
860 public int hashCode() {
861 int result = 193;
862 long temp = Double.doubleToLongBits(this.base);
863 result = 37 * result + (int) (temp ^ (temp >>> 32));
864 result = 37 * result + this.minorTickCount;
865 temp = Double.doubleToLongBits(this.smallestValue);
866 result = 37 * result + (int) (temp ^ (temp >>> 32));
867 if (this.numberFormatOverride != null) {
868 result = 37 * result + this.numberFormatOverride.hashCode();
869 }
870 result = 37 * result + this.tickUnit.hashCode();
871 return result;
872 }
873
874 /**
875 * Returns a collection of tick units for log (base 10) values.
876 * Uses a given Locale to create the DecimalFormats.
877 *
878 * @param locale the locale to use to represent Numbers.
879 *
880 * @return A collection of tick units for integer values.
881 *
882 * @since 1.0.7
883 */
884 public static TickUnitSource createLogTickUnits(Locale locale) {
885
886 TickUnits units = new TickUnits();
887
888 NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);
889
890 units.add(new NumberTickUnit(1, numberFormat));
891 units.add(new NumberTickUnit(2, numberFormat));
892 units.add(new NumberTickUnit(5, numberFormat));
893 units.add(new NumberTickUnit(10, numberFormat));
894 units.add(new NumberTickUnit(20, numberFormat));
895 units.add(new NumberTickUnit(50, numberFormat));
896 units.add(new NumberTickUnit(100, numberFormat));
897 units.add(new NumberTickUnit(200, numberFormat));
898 units.add(new NumberTickUnit(500, numberFormat));
899 units.add(new NumberTickUnit(1000, numberFormat));
900 units.add(new NumberTickUnit(2000, numberFormat));
901 units.add(new NumberTickUnit(5000, numberFormat));
902 units.add(new NumberTickUnit(10000, numberFormat));
903 units.add(new NumberTickUnit(20000, numberFormat));
904 units.add(new NumberTickUnit(50000, numberFormat));
905 units.add(new NumberTickUnit(100000, numberFormat));
906 units.add(new NumberTickUnit(200000, numberFormat));
907 units.add(new NumberTickUnit(500000, numberFormat));
908 units.add(new NumberTickUnit(1000000, numberFormat));
909 units.add(new NumberTickUnit(2000000, numberFormat));
910 units.add(new NumberTickUnit(5000000, numberFormat));
911 units.add(new NumberTickUnit(10000000, numberFormat));
912 units.add(new NumberTickUnit(20000000, numberFormat));
913 units.add(new NumberTickUnit(50000000, numberFormat));
914 units.add(new NumberTickUnit(100000000, numberFormat));
915 units.add(new NumberTickUnit(200000000, numberFormat));
916 units.add(new NumberTickUnit(500000000, numberFormat));
917 units.add(new NumberTickUnit(1000000000, numberFormat));
918 units.add(new NumberTickUnit(2000000000, numberFormat));
919 units.add(new NumberTickUnit(5000000000.0, numberFormat));
920 units.add(new NumberTickUnit(10000000000.0, numberFormat));
921
922 return units;
923
924 }
925 }