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 * LogarithmicAxis.java
029 * --------------------
030 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
031 *
032 * Original Author: Michael Duffy / Eric Thomas;
033 * Contributor(s): David Gilbert (for Object Refinery Limited);
034 * David M. O'Donnell;
035 * Scott Sams;
036 * Sergei Ivanov;
037 *
038 * Changes
039 * -------
040 * 14-Mar-2002 : Version 1 contributed by Michael Duffy (DG);
041 * 19-Apr-2002 : drawVerticalString() is now drawRotatedString() in
042 * RefineryUtilities (DG);
043 * 23-Apr-2002 : Added a range property (DG);
044 * 15-May-2002 : Modified to be able to deal with negative and zero values (via
045 * new 'adjustedLog10()' method); occurrences of "Math.log(10)"
046 * changed to "LOG10_VALUE"; changed 'intValue()' to
047 * 'longValue()' in 'refreshTicks()' to fix label-text value
048 * out-of-range problem; removed 'draw()' method; added
049 * 'autoRangeMinimumSize' check; added 'log10TickLabelsFlag'
050 * parameter flag and implementation (ET);
051 * 25-Jun-2002 : Removed redundant import (DG);
052 * 25-Jul-2002 : Changed order of parameters in ValueAxis constructor (DG);
053 * 16-Jul-2002 : Implemented support for plotting positive values arbitrarily
054 * close to zero (added 'allowNegativesFlag' flag) (ET).
055 * 05-Sep-2002 : Updated constructor reflecting changes in the Axis class (DG);
056 * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
057 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
058 * 22-Nov-2002 : Bug fixes from David M. O'Donnell (DG);
059 * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);
060 * 20-Jan-2003 : Removed unnecessary constructors (DG);
061 * 26-Mar-2003 : Implemented Serializable (DG);
062 * 08-May-2003 : Fixed plotting of datasets with lower==upper bounds when
063 * 'minAutoRange' is very small; added 'strictValuesFlag'
064 * and default functionality of throwing a runtime exception
065 * if 'allowNegativesFlag' is false and any values are less
066 * than or equal to zero; added 'expTickLabelsFlag' and
067 * changed to use "1e#"-style tick labels by default
068 * ("10^n"-style tick labels still supported via 'set'
069 * method); improved generation of tick labels when range of
070 * values is small; changed to use 'NumberFormat.getInstance()'
071 * to create 'numberFormatterObj' (ET);
072 * 14-May-2003 : Merged HorizontalLogarithmicAxis and
073 * VerticalLogarithmicAxis (DG);
074 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
075 * 07-Nov-2003 : Modified to use new NumberTick class (DG);
076 * 08-Apr-2004 : Use numberFormatOverride if set - see patch 930139 (DG);
077 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
078 * 21-Apr-2005 : Added support for upper and lower margins; added
079 * get/setAutoRangeNextLogFlag() methods and changed
080 * default to 'autoRangeNextLogFlag'==false (ET);
081 * 22-Apr-2005 : Removed refreshTicks() and fixed names and parameters for
082 * refreshHorizontalTicks() & refreshVerticalTicks();
083 * changed javadoc on setExpTickLabelsFlag() to specify
084 * proper default (ET);
085 * 22-Apr-2005 : Renamed refreshHorizontalTicks --> refreshTicksHorizontal
086 * (and likewise the vertical version) for consistency with
087 * other axis classes (DG);
088 * ------------- JFREECHART 1.0.x ---------------------------------------------
089 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
090 * 02-Mar-2007 : Applied patch 1671069 to fix zooming (DG);
091 * 22-Mar-2007 : Use new defaultAutoRange attribute (DG);
092 *
093 */
094
095 package org.jfree.chart.axis;
096
097 import java.awt.Graphics2D;
098 import java.awt.geom.Rectangle2D;
099 import java.text.DecimalFormat;
100 import java.text.NumberFormat;
101 import java.util.List;
102
103 import org.jfree.chart.plot.Plot;
104 import org.jfree.chart.plot.ValueAxisPlot;
105 import org.jfree.data.Range;
106 import org.jfree.ui.RectangleEdge;
107 import org.jfree.ui.TextAnchor;
108
109 /**
110 * A numerical axis that uses a logarithmic scale.
111 */
112 public class LogarithmicAxis extends NumberAxis {
113
114 /** For serialization. */
115 private static final long serialVersionUID = 2502918599004103054L;
116
117 /** Useful constant for log(10). */
118 public static final double LOG10_VALUE = Math.log(10.0);
119
120 /** Smallest arbitrarily-close-to-zero value allowed. */
121 public static final double SMALL_LOG_VALUE = 1e-100;
122
123 /** Flag set true to allow negative values in data. */
124 protected boolean allowNegativesFlag = false;
125
126 /**
127 * Flag set true make axis throw exception if any values are
128 * <= 0 and 'allowNegativesFlag' is false.
129 */
130 protected boolean strictValuesFlag = true;
131
132 /** Number formatter for generating numeric strings. */
133 protected final NumberFormat numberFormatterObj
134 = NumberFormat.getInstance();
135
136 /** Flag set true for "1e#"-style tick labels. */
137 protected boolean expTickLabelsFlag = false;
138
139 /** Flag set true for "10^n"-style tick labels. */
140 protected boolean log10TickLabelsFlag = false;
141
142 /** True to make 'autoAdjustRange()' select "10^n" values. */
143 protected boolean autoRangeNextLogFlag = false;
144
145 /** Helper flag for log axis processing. */
146 protected boolean smallLogFlag = false;
147
148 /**
149 * Creates a new axis.
150 *
151 * @param label the axis label.
152 */
153 public LogarithmicAxis(String label) {
154 super(label);
155 setupNumberFmtObj(); //setup number formatter obj
156 }
157
158 /**
159 * Sets the 'allowNegativesFlag' flag; true to allow negative values
160 * in data, false to be able to plot positive values arbitrarily close to
161 * zero.
162 *
163 * @param flgVal the new value of the flag.
164 */
165 public void setAllowNegativesFlag(boolean flgVal) {
166 this.allowNegativesFlag = flgVal;
167 }
168
169 /**
170 * Returns the 'allowNegativesFlag' flag; true to allow negative values
171 * in data, false to be able to plot positive values arbitrarily close
172 * to zero.
173 *
174 * @return The flag.
175 */
176 public boolean getAllowNegativesFlag() {
177 return this.allowNegativesFlag;
178 }
179
180 /**
181 * Sets the 'strictValuesFlag' flag; if true and 'allowNegativesFlag'
182 * is false then this axis will throw a runtime exception if any of its
183 * values are less than or equal to zero; if false then the axis will
184 * adjust for values less than or equal to zero as needed.
185 *
186 * @param flgVal true for strict enforcement.
187 */
188 public void setStrictValuesFlag(boolean flgVal) {
189 this.strictValuesFlag = flgVal;
190 }
191
192 /**
193 * Returns the 'strictValuesFlag' flag; if true and 'allowNegativesFlag'
194 * is false then this axis will throw a runtime exception if any of its
195 * values are less than or equal to zero; if false then the axis will
196 * adjust for values less than or equal to zero as needed.
197 *
198 * @return <code>true</code> if strict enforcement is enabled.
199 */
200 public boolean getStrictValuesFlag() {
201 return this.strictValuesFlag;
202 }
203
204 /**
205 * Sets the 'expTickLabelsFlag' flag. If the 'log10TickLabelsFlag'
206 * is false then this will set whether or not "1e#"-style tick labels
207 * are used. The default is to use regular numeric tick labels.
208 *
209 * @param flgVal true for "1e#"-style tick labels, false for
210 * log10 or regular numeric tick labels.
211 */
212 public void setExpTickLabelsFlag(boolean flgVal) {
213 this.expTickLabelsFlag = flgVal;
214 setupNumberFmtObj(); //setup number formatter obj
215 }
216
217 /**
218 * Returns the 'expTickLabelsFlag' flag.
219 *
220 * @return <code>true</code> for "1e#"-style tick labels,
221 * <code>false</code> for log10 or regular numeric tick labels.
222 */
223 public boolean getExpTickLabelsFlag() {
224 return this.expTickLabelsFlag;
225 }
226
227 /**
228 * Sets the 'log10TickLabelsFlag' flag. The default value is false.
229 *
230 * @param flag true for "10^n"-style tick labels, false for "1e#"-style
231 * or regular numeric tick labels.
232 */
233 public void setLog10TickLabelsFlag(boolean flag) {
234 this.log10TickLabelsFlag = flag;
235 }
236
237 /**
238 * Returns the 'log10TickLabelsFlag' flag.
239 *
240 * @return <code>true</code> for "10^n"-style tick labels,
241 * <code>false</code> for "1e#"-style or regular numeric tick
242 * labels.
243 */
244 public boolean getLog10TickLabelsFlag() {
245 return this.log10TickLabelsFlag;
246 }
247
248 /**
249 * Sets the 'autoRangeNextLogFlag' flag. This determines whether or
250 * not the 'autoAdjustRange()' method will select the next "10^n"
251 * values when determining the upper and lower bounds. The default
252 * value is false.
253 *
254 * @param flag <code>true</code> to make the 'autoAdjustRange()'
255 * method select the next "10^n" values, <code>false</code> to not.
256 */
257 public void setAutoRangeNextLogFlag(boolean flag) {
258 this.autoRangeNextLogFlag = flag;
259 }
260
261 /**
262 * Returns the 'autoRangeNextLogFlag' flag.
263 *
264 * @return <code>true</code> if the 'autoAdjustRange()' method will
265 * select the next "10^n" values, <code>false</code> if not.
266 */
267 public boolean getAutoRangeNextLogFlag() {
268 return this.autoRangeNextLogFlag;
269 }
270
271 /**
272 * Overridden version that calls original and then sets up flag for
273 * log axis processing.
274 *
275 * @param range the new range.
276 */
277 public void setRange(Range range) {
278 super.setRange(range); // call parent method
279 setupSmallLogFlag(); // setup flag based on bounds values
280 }
281
282 /**
283 * Sets up flag for log axis processing. Set true if negative values
284 * not allowed and the lower bound is between 0 and 10.
285 */
286 protected void setupSmallLogFlag() {
287 // set flag true if negative values not allowed and the
288 // lower bound is between 0 and 10:
289 double lowerVal = getRange().getLowerBound();
290 this.smallLogFlag = (!this.allowNegativesFlag && lowerVal < 10.0
291 && lowerVal > 0.0);
292 }
293
294 /**
295 * Sets up the number formatter object according to the
296 * 'expTickLabelsFlag' flag.
297 */
298 protected void setupNumberFmtObj() {
299 if (this.numberFormatterObj instanceof DecimalFormat) {
300 //setup for "1e#"-style tick labels or regular
301 // numeric tick labels, depending on flag:
302 ((DecimalFormat) this.numberFormatterObj).applyPattern(
303 this.expTickLabelsFlag ? "0E0" : "0.###");
304 }
305 }
306
307 /**
308 * Returns the log10 value, depending on if values between 0 and
309 * 1 are being plotted. If negative values are not allowed and
310 * the lower bound is between 0 and 10 then a normal log is
311 * returned; otherwise the returned value is adjusted if the
312 * given value is less than 10.
313 *
314 * @param val the value.
315 *
316 * @return log<sub>10</sub>(val).
317 *
318 * @see #switchedPow10(double)
319 */
320 protected double switchedLog10(double val) {
321 return this.smallLogFlag ? Math.log(val)
322 / LOG10_VALUE : adjustedLog10(val);
323 }
324
325 /**
326 * Returns a power of 10, depending on if values between 0 and
327 * 1 are being plotted. If negative values are not allowed and
328 * the lower bound is between 0 and 10 then a normal power is
329 * returned; otherwise the returned value is adjusted if the
330 * given value is less than 1.
331 *
332 * @param val the value.
333 *
334 * @return 10<sup>val</sup>.
335 *
336 * @since 1.0.5
337 * @see #switchedLog10(double)
338 */
339 public double switchedPow10(double val) {
340 return this.smallLogFlag ? Math.pow(10.0, val) : adjustedPow10(val);
341 }
342
343 /**
344 * Returns an adjusted log10 value for graphing purposes. The first
345 * adjustment is that negative values are changed to positive during
346 * the calculations, and then the answer is negated at the end. The
347 * second is that, for values less than 10, an increasingly large
348 * (0 to 1) scaling factor is added such that at 0 the value is
349 * adjusted to 1, resulting in a returned result of 0.
350 *
351 * @param val value for which log10 should be calculated.
352 *
353 * @return An adjusted log<sub>10</sub>(val).
354 *
355 * @see #adjustedPow10(double)
356 */
357 public double adjustedLog10(double val) {
358 boolean negFlag = (val < 0.0);
359 if (negFlag) {
360 val = -val; // if negative then set flag and make positive
361 }
362 if (val < 10.0) { // if < 10 then
363 val += (10.0 - val) / 10.0; //increase so 0 translates to 0
364 }
365 //return value; negate if original value was negative:
366 double res = Math.log(val) / LOG10_VALUE;
367 return negFlag ? (-res) : res;
368 }
369
370 /**
371 * Returns an adjusted power of 10 value for graphing purposes. The first
372 * adjustment is that negative values are changed to positive during
373 * the calculations, and then the answer is negated at the end. The
374 * second is that, for values less than 1, a progressive logarithmic
375 * offset is subtracted such that at 0 the returned result is also 0.
376 *
377 * @param val value for which power of 10 should be calculated.
378 *
379 * @return An adjusted 10<sup>val</sup>.
380 *
381 * @since 1.0.5
382 * @see #adjustedLog10(double)
383 */
384 public double adjustedPow10(double val) {
385 boolean negFlag = (val < 0.0);
386 if (negFlag) {
387 val = -val; // if negative then set flag and make positive
388 }
389 double res;
390 if (val < 1.0) {
391 res = (Math.pow(10, val + 1.0) - 10.0) / 9.0; //invert adjustLog10
392 }
393 else {
394 res = Math.pow(10, val);
395 }
396 return negFlag ? (-res) : res;
397 }
398
399 /**
400 * Returns the largest (closest to positive infinity) double value that is
401 * not greater than the argument, is equal to a mathematical integer and
402 * satisfying the condition that log base 10 of the value is an integer
403 * (i.e., the value returned will be a power of 10: 1, 10, 100, 1000, etc.).
404 *
405 * @param lower a double value below which a floor will be calcualted.
406 *
407 * @return 10<sup>N</sup> with N .. { 1 ... }
408 */
409 protected double computeLogFloor(double lower) {
410
411 double logFloor;
412 if (this.allowNegativesFlag) {
413 //negative values are allowed
414 if (lower > 10.0) { //parameter value is > 10
415 // The Math.log() function is based on e not 10.
416 logFloor = Math.log(lower) / LOG10_VALUE;
417 logFloor = Math.floor(logFloor);
418 logFloor = Math.pow(10, logFloor);
419 }
420 else if (lower < -10.0) { //parameter value is < -10
421 //calculate log using positive value:
422 logFloor = Math.log(-lower) / LOG10_VALUE;
423 //calculate floor using negative value:
424 logFloor = Math.floor(-logFloor);
425 //calculate power using positive value; then negate
426 logFloor = -Math.pow(10, -logFloor);
427 }
428 else {
429 //parameter value is -10 > val < 10
430 logFloor = Math.floor(lower); //use as-is
431 }
432 }
433 else {
434 //negative values not allowed
435 if (lower > 0.0) { //parameter value is > 0
436 // The Math.log() function is based on e not 10.
437 logFloor = Math.log(lower) / LOG10_VALUE;
438 logFloor = Math.floor(logFloor);
439 logFloor = Math.pow(10, logFloor);
440 }
441 else {
442 //parameter value is <= 0
443 logFloor = Math.floor(lower); //use as-is
444 }
445 }
446 return logFloor;
447 }
448
449 /**
450 * Returns the smallest (closest to negative infinity) double value that is
451 * not less than the argument, is equal to a mathematical integer and
452 * satisfying the condition that log base 10 of the value is an integer
453 * (i.e., the value returned will be a power of 10: 1, 10, 100, 1000, etc.).
454 *
455 * @param upper a double value above which a ceiling will be calcualted.
456 *
457 * @return 10<sup>N</sup> with N .. { 1 ... }
458 */
459 protected double computeLogCeil(double upper) {
460
461 double logCeil;
462 if (this.allowNegativesFlag) {
463 //negative values are allowed
464 if (upper > 10.0) {
465 //parameter value is > 10
466 // The Math.log() function is based on e not 10.
467 logCeil = Math.log(upper) / LOG10_VALUE;
468 logCeil = Math.ceil(logCeil);
469 logCeil = Math.pow(10, logCeil);
470 }
471 else if (upper < -10.0) {
472 //parameter value is < -10
473 //calculate log using positive value:
474 logCeil = Math.log(-upper) / LOG10_VALUE;
475 //calculate ceil using negative value:
476 logCeil = Math.ceil(-logCeil);
477 //calculate power using positive value; then negate
478 logCeil = -Math.pow(10, -logCeil);
479 }
480 else {
481 //parameter value is -10 > val < 10
482 logCeil = Math.ceil(upper); //use as-is
483 }
484 }
485 else {
486 //negative values not allowed
487 if (upper > 0.0) {
488 //parameter value is > 0
489 // The Math.log() function is based on e not 10.
490 logCeil = Math.log(upper) / LOG10_VALUE;
491 logCeil = Math.ceil(logCeil);
492 logCeil = Math.pow(10, logCeil);
493 }
494 else {
495 //parameter value is <= 0
496 logCeil = Math.ceil(upper); //use as-is
497 }
498 }
499 return logCeil;
500 }
501
502 /**
503 * Rescales the axis to ensure that all data is visible.
504 */
505 public void autoAdjustRange() {
506
507 Plot plot = getPlot();
508 if (plot == null) {
509 return; // no plot, no data.
510 }
511
512 if (plot instanceof ValueAxisPlot) {
513 ValueAxisPlot vap = (ValueAxisPlot) plot;
514
515 double lower;
516 Range r = vap.getDataRange(this);
517 if (r == null) {
518 //no real data present
519 r = getDefaultAutoRange();
520 lower = r.getLowerBound(); //get lower bound value
521 }
522 else {
523 //actual data is present
524 lower = r.getLowerBound(); //get lower bound value
525 if (this.strictValuesFlag
526 && !this.allowNegativesFlag && lower <= 0.0) {
527 //strict flag set, allow-negatives not set and values <= 0
528 throw new RuntimeException("Values less than or equal to "
529 + "zero not allowed with logarithmic axis");
530 }
531 }
532
533 //apply lower margin by decreasing lower bound:
534 final double lowerMargin;
535 if (lower > 0.0 && (lowerMargin = getLowerMargin()) > 0.0) {
536 //lower bound and margin OK; get log10 of lower bound
537 final double logLower = (Math.log(lower) / LOG10_VALUE);
538 double logAbs; //get absolute value of log10 value
539 if ((logAbs = Math.abs(logLower)) < 1.0) {
540 logAbs = 1.0; //if less than 1.0 then make it 1.0
541 } //subtract out margin and get exponential value:
542 lower = Math.pow(10, (logLower - (logAbs * lowerMargin)));
543 }
544
545 //if flag then change to log version of lowest value
546 // to make range begin at a 10^n value:
547 if (this.autoRangeNextLogFlag) {
548 lower = computeLogFloor(lower);
549 }
550
551 if (!this.allowNegativesFlag && lower >= 0.0
552 && lower < SMALL_LOG_VALUE) {
553 //negatives not allowed and lower range bound is zero
554 lower = r.getLowerBound(); //use data range bound instead
555 }
556
557 double upper = r.getUpperBound();
558
559 //apply upper margin by increasing upper bound:
560 final double upperMargin;
561 if (upper > 0.0 && (upperMargin = getUpperMargin()) > 0.0) {
562 //upper bound and margin OK; get log10 of upper bound
563 final double logUpper = (Math.log(upper) / LOG10_VALUE);
564 double logAbs; //get absolute value of log10 value
565 if ((logAbs = Math.abs(logUpper)) < 1.0) {
566 logAbs = 1.0; //if less than 1.0 then make it 1.0
567 } //add in margin and get exponential value:
568 upper = Math.pow(10, (logUpper + (logAbs * upperMargin)));
569 }
570
571 if (!this.allowNegativesFlag && upper < 1.0 && upper > 0.0
572 && lower > 0.0) {
573 //negatives not allowed and upper bound between 0 & 1
574 //round up to nearest significant digit for bound:
575 //get negative exponent:
576 double expVal = Math.log(upper) / LOG10_VALUE;
577 expVal = Math.ceil(-expVal + 0.001); //get positive exponent
578 expVal = Math.pow(10, expVal); //create multiplier value
579 //multiply, round up, and divide for bound value:
580 upper = (expVal > 0.0) ? Math.ceil(upper * expVal) / expVal
581 : Math.ceil(upper);
582 }
583 else {
584 //negatives allowed or upper bound not between 0 & 1
585 //if flag then change to log version of highest value to
586 // make range begin at a 10^n value; else use nearest int
587 upper = (this.autoRangeNextLogFlag) ? computeLogCeil(upper)
588 : Math.ceil(upper);
589 }
590 // ensure the autorange is at least <minRange> in size...
591 double minRange = getAutoRangeMinimumSize();
592 if (upper - lower < minRange) {
593 upper = (upper + lower + minRange) / 2;
594 lower = (upper + lower - minRange) / 2;
595 //if autorange still below minimum then adjust by 1%
596 // (can be needed when minRange is very small):
597 if (upper - lower < minRange) {
598 double absUpper = Math.abs(upper);
599 //need to account for case where upper==0.0
600 double adjVal = (absUpper > SMALL_LOG_VALUE) ? absUpper
601 / 100.0 : 0.01;
602 upper = (upper + lower + adjVal) / 2;
603 lower = (upper + lower - adjVal) / 2;
604 }
605 }
606
607 setRange(new Range(lower, upper), false, false);
608 setupSmallLogFlag(); //setup flag based on bounds values
609 }
610 }
611
612 /**
613 * Converts a data value to a coordinate in Java2D space, assuming that
614 * the axis runs along one edge of the specified plotArea.
615 * Note that it is possible for the coordinate to fall outside the
616 * plotArea.
617 *
618 * @param value the data value.
619 * @param plotArea the area for plotting the data.
620 * @param edge the axis location.
621 *
622 * @return The Java2D coordinate.
623 */
624 public double valueToJava2D(double value, Rectangle2D plotArea,
625 RectangleEdge edge) {
626
627 Range range = getRange();
628 double axisMin = switchedLog10(range.getLowerBound());
629 double axisMax = switchedLog10(range.getUpperBound());
630
631 double min = 0.0;
632 double max = 0.0;
633 if (RectangleEdge.isTopOrBottom(edge)) {
634 min = plotArea.getMinX();
635 max = plotArea.getMaxX();
636 }
637 else if (RectangleEdge.isLeftOrRight(edge)) {
638 min = plotArea.getMaxY();
639 max = plotArea.getMinY();
640 }
641
642 value = switchedLog10(value);
643
644 if (isInverted()) {
645 return max - (((value - axisMin) / (axisMax - axisMin))
646 * (max - min));
647 }
648 else {
649 return min + (((value - axisMin) / (axisMax - axisMin))
650 * (max - min));
651 }
652
653 }
654
655 /**
656 * Converts a coordinate in Java2D space to the corresponding data
657 * value, assuming that the axis runs along one edge of the specified
658 * plotArea.
659 *
660 * @param java2DValue the coordinate in Java2D space.
661 * @param plotArea the area in which the data is plotted.
662 * @param edge the axis location.
663 *
664 * @return The data value.
665 */
666 public double java2DToValue(double java2DValue, Rectangle2D plotArea,
667 RectangleEdge edge) {
668
669 Range range = getRange();
670 double axisMin = switchedLog10(range.getLowerBound());
671 double axisMax = switchedLog10(range.getUpperBound());
672
673 double plotMin = 0.0;
674 double plotMax = 0.0;
675 if (RectangleEdge.isTopOrBottom(edge)) {
676 plotMin = plotArea.getX();
677 plotMax = plotArea.getMaxX();
678 }
679 else if (RectangleEdge.isLeftOrRight(edge)) {
680 plotMin = plotArea.getMaxY();
681 plotMax = plotArea.getMinY();
682 }
683
684 if (isInverted()) {
685 return switchedPow10(axisMax - ((java2DValue - plotMin)
686 / (plotMax - plotMin)) * (axisMax - axisMin));
687 }
688 else {
689 return switchedPow10(axisMin + ((java2DValue - plotMin)
690 / (plotMax - plotMin)) * (axisMax - axisMin));
691 }
692 }
693
694 /**
695 * Zooms in on the current range.
696 *
697 * @param lowerPercent the new lower bound.
698 * @param upperPercent the new upper bound.
699 */
700 public void zoomRange(double lowerPercent, double upperPercent) {
701 double startLog = switchedLog10(getRange().getLowerBound());
702 double lengthLog = switchedLog10(getRange().getUpperBound()) - startLog;
703 Range adjusted;
704
705 if (isInverted()) {
706 adjusted = new Range(
707 switchedPow10(
708 startLog + (lengthLog * (1 - upperPercent))),
709 switchedPow10(
710 startLog + (lengthLog * (1 - lowerPercent))));
711 }
712 else {
713 adjusted = new Range(
714 switchedPow10(startLog + (lengthLog * lowerPercent)),
715 switchedPow10(startLog + (lengthLog * upperPercent)));
716 }
717
718 setRange(adjusted);
719 }
720
721 /**
722 * Calculates the positions of the tick labels for the axis, storing the
723 * results in the tick label list (ready for drawing).
724 *
725 * @param g2 the graphics device.
726 * @param dataArea the area in which the plot should be drawn.
727 * @param edge the location of the axis.
728 *
729 * @return A list of ticks.
730 */
731 protected List refreshTicksHorizontal(Graphics2D g2,
732 Rectangle2D dataArea,
733 RectangleEdge edge) {
734
735 List ticks = new java.util.ArrayList();
736 Range range = getRange();
737
738 //get lower bound value:
739 double lowerBoundVal = range.getLowerBound();
740 //if small log values and lower bound value too small
741 // then set to a small value (don't allow <= 0):
742 if (this.smallLogFlag && lowerBoundVal < SMALL_LOG_VALUE) {
743 lowerBoundVal = SMALL_LOG_VALUE;
744 }
745
746 //get upper bound value
747 double upperBoundVal = range.getUpperBound();
748
749 //get log10 version of lower bound and round to integer:
750 int iBegCount = (int) Math.rint(switchedLog10(lowerBoundVal));
751 //get log10 version of upper bound and round to integer:
752 int iEndCount = (int) Math.rint(switchedLog10(upperBoundVal));
753
754 if (iBegCount == iEndCount && iBegCount > 0
755 && Math.pow(10, iBegCount) > lowerBoundVal) {
756 //only 1 power of 10 value, it's > 0 and its resulting
757 // tick value will be larger than lower bound of data
758 --iBegCount; //decrement to generate more ticks
759 }
760
761 double currentTickValue;
762 String tickLabel;
763 boolean zeroTickFlag = false;
764 for (int i = iBegCount; i <= iEndCount; i++) {
765 //for each power of 10 value; create ten ticks
766 for (int j = 0; j < 10; ++j) {
767 //for each tick to be displayed
768 if (this.smallLogFlag) {
769 //small log values in use; create numeric value for tick
770 currentTickValue = Math.pow(10, i) + (Math.pow(10, i) * j);
771 if (this.expTickLabelsFlag
772 || (i < 0 && currentTickValue > 0.0
773 && currentTickValue < 1.0)) {
774 //showing "1e#"-style ticks or negative exponent
775 // generating tick value between 0 & 1; show fewer
776 if (j == 0 || (i > -4 && j < 2)
777 || currentTickValue >= upperBoundVal) {
778 //first tick of series, or not too small a value and
779 // one of first 3 ticks, or last tick to be displayed
780 // set exact number of fractional digits to be shown
781 // (no effect if showing "1e#"-style ticks):
782 this.numberFormatterObj
783 .setMaximumFractionDigits(-i);
784 //create tick label (force use of fmt obj):
785 tickLabel = makeTickLabel(currentTickValue, true);
786 }
787 else { //no tick label to be shown
788 tickLabel = "";
789 }
790 }
791 else { //tick value not between 0 & 1
792 //show tick label if it's the first or last in
793 // the set, or if it's 1-5; beyond that show
794 // fewer as the values get larger:
795 tickLabel = (j < 1 || (i < 1 && j < 5) || (j < 4 - i)
796 || currentTickValue >= upperBoundVal)
797 ? makeTickLabel(currentTickValue) : "";
798 }
799 }
800 else { //not small log values in use; allow for values <= 0
801 if (zeroTickFlag) { //if did zero tick last iter then
802 --j; //decrement to do 1.0 tick now
803 } //calculate power-of-ten value for tick:
804 currentTickValue = (i >= 0)
805 ? Math.pow(10, i) + (Math.pow(10, i) * j)
806 : -(Math.pow(10, -i) - (Math.pow(10, -i - 1) * j));
807 if (!zeroTickFlag) { // did not do zero tick last iteration
808 if (Math.abs(currentTickValue - 1.0) < 0.0001
809 && lowerBoundVal <= 0.0 && upperBoundVal >= 0.0) {
810 //tick value is 1.0 and 0.0 is within data range
811 currentTickValue = 0.0; //set tick value to zero
812 zeroTickFlag = true; //indicate zero tick
813 }
814 }
815 else { //did zero tick last iteration
816 zeroTickFlag = false; //clear flag
817 } //create tick label string:
818 //show tick label if "1e#"-style and it's one
819 // of the first two, if it's the first or last
820 // in the set, or if it's 1-5; beyond that
821 // show fewer as the values get larger:
822 tickLabel = ((this.expTickLabelsFlag && j < 2)
823 || j < 1
824 || (i < 1 && j < 5) || (j < 4 - i)
825 || currentTickValue >= upperBoundVal)
826 ? makeTickLabel(currentTickValue) : "";
827 }
828
829 if (currentTickValue > upperBoundVal) {
830 return ticks; // if past highest data value then exit
831 // method
832 }
833
834 if (currentTickValue >= lowerBoundVal - SMALL_LOG_VALUE) {
835 //tick value not below lowest data value
836 TextAnchor anchor = null;
837 TextAnchor rotationAnchor = null;
838 double angle = 0.0;
839 if (isVerticalTickLabels()) {
840 anchor = TextAnchor.CENTER_RIGHT;
841 rotationAnchor = TextAnchor.CENTER_RIGHT;
842 if (edge == RectangleEdge.TOP) {
843 angle = Math.PI / 2.0;
844 }
845 else {
846 angle = -Math.PI / 2.0;
847 }
848 }
849 else {
850 if (edge == RectangleEdge.TOP) {
851 anchor = TextAnchor.BOTTOM_CENTER;
852 rotationAnchor = TextAnchor.BOTTOM_CENTER;
853 }
854 else {
855 anchor = TextAnchor.TOP_CENTER;
856 rotationAnchor = TextAnchor.TOP_CENTER;
857 }
858 }
859
860 Tick tick = new NumberTick(new Double(currentTickValue),
861 tickLabel, anchor, rotationAnchor, angle);
862 ticks.add(tick);
863 }
864 }
865 }
866 return ticks;
867
868 }
869
870 /**
871 * Calculates the positions of the tick labels for the axis, storing the
872 * results in the tick label list (ready for drawing).
873 *
874 * @param g2 the graphics device.
875 * @param dataArea the area in which the plot should be drawn.
876 * @param edge the location of the axis.
877 *
878 * @return A list of ticks.
879 */
880 protected List refreshTicksVertical(Graphics2D g2,
881 Rectangle2D dataArea,
882 RectangleEdge edge) {
883
884 List ticks = new java.util.ArrayList();
885
886 //get lower bound value:
887 double lowerBoundVal = getRange().getLowerBound();
888 //if small log values and lower bound value too small
889 // then set to a small value (don't allow <= 0):
890 if (this.smallLogFlag && lowerBoundVal < SMALL_LOG_VALUE) {
891 lowerBoundVal = SMALL_LOG_VALUE;
892 }
893 //get upper bound value
894 double upperBoundVal = getRange().getUpperBound();
895
896 //get log10 version of lower bound and round to integer:
897 int iBegCount = (int) Math.rint(switchedLog10(lowerBoundVal));
898 //get log10 version of upper bound and round to integer:
899 int iEndCount = (int) Math.rint(switchedLog10(upperBoundVal));
900
901 if (iBegCount == iEndCount && iBegCount > 0
902 && Math.pow(10, iBegCount) > lowerBoundVal) {
903 //only 1 power of 10 value, it's > 0 and its resulting
904 // tick value will be larger than lower bound of data
905 --iBegCount; //decrement to generate more ticks
906 }
907
908 double tickVal;
909 String tickLabel;
910 boolean zeroTickFlag = false;
911 for (int i = iBegCount; i <= iEndCount; i++) {
912 //for each tick with a label to be displayed
913 int jEndCount = 10;
914 if (i == iEndCount) {
915 jEndCount = 1;
916 }
917
918 for (int j = 0; j < jEndCount; j++) {
919 //for each tick to be displayed
920 if (this.smallLogFlag) {
921 //small log values in use
922 tickVal = Math.pow(10, i) + (Math.pow(10, i) * j);
923 if (j == 0) {
924 //first tick of group; create label text
925 if (this.log10TickLabelsFlag) {
926 //if flag then
927 tickLabel = "10^" + i; //create "log10"-type label
928 }
929 else { //not "log10"-type label
930 if (this.expTickLabelsFlag) {
931 //if flag then
932 tickLabel = "1e" + i; //create "1e#"-type label
933 }
934 else { //not "1e#"-type label
935 if (i >= 0) { // if positive exponent then
936 // make integer
937 NumberFormat format
938 = getNumberFormatOverride();
939 if (format != null) {
940 tickLabel = format.format(tickVal);
941 }
942 else {
943 tickLabel = Long.toString((long)
944 Math.rint(tickVal));
945 }
946 }
947 else {
948 //negative exponent; create fractional value
949 //set exact number of fractional digits to
950 // be shown:
951 this.numberFormatterObj
952 .setMaximumFractionDigits(-i);
953 //create tick label:
954 tickLabel = this.numberFormatterObj.format(
955 tickVal);
956 }
957 }
958 }
959 }
960 else { //not first tick to be displayed
961 tickLabel = ""; //no tick label
962 }
963 }
964 else { //not small log values in use; allow for values <= 0
965 if (zeroTickFlag) { //if did zero tick last iter then
966 --j;
967 } //decrement to do 1.0 tick now
968 tickVal = (i >= 0) ? Math.pow(10, i) + (Math.pow(10, i) * j)
969 : -(Math.pow(10, -i) - (Math.pow(10, -i - 1) * j));
970 if (j == 0) { //first tick of group
971 if (!zeroTickFlag) { // did not do zero tick last
972 // iteration
973 if (i > iBegCount && i < iEndCount
974 && Math.abs(tickVal - 1.0) < 0.0001) {
975 // not first or last tick on graph and value
976 // is 1.0
977 tickVal = 0.0; //change value to 0.0
978 zeroTickFlag = true; //indicate zero tick
979 tickLabel = "0"; //create label for tick
980 }
981 else {
982 //first or last tick on graph or value is 1.0
983 //create label for tick:
984 if (this.log10TickLabelsFlag) {
985 //create "log10"-type label
986 tickLabel = (((i < 0) ? "-" : "")
987 + "10^" + Math.abs(i));
988 }
989 else {
990 if (this.expTickLabelsFlag) {
991 //create "1e#"-type label
992 tickLabel = (((i < 0) ? "-" : "")
993 + "1e" + Math.abs(i));
994 }
995 else {
996 NumberFormat format
997 = getNumberFormatOverride();
998 if (format != null) {
999 tickLabel = format.format(tickVal);
1000 }
1001 else {
1002 tickLabel = Long.toString(
1003 (long) Math.rint(tickVal));
1004 }
1005 }
1006 }
1007 }
1008 }
1009 else { // did zero tick last iteration
1010 tickLabel = ""; //no label
1011 zeroTickFlag = false; //clear flag
1012 }
1013 }
1014 else { // not first tick of group
1015 tickLabel = ""; //no label
1016 zeroTickFlag = false; //make sure flag cleared
1017 }
1018 }
1019
1020 if (tickVal > upperBoundVal) {
1021 return ticks; //if past highest data value then exit method
1022 }
1023
1024 if (tickVal >= lowerBoundVal - SMALL_LOG_VALUE) {
1025 //tick value not below lowest data value
1026 TextAnchor anchor = null;
1027 TextAnchor rotationAnchor = null;
1028 double angle = 0.0;
1029 if (isVerticalTickLabels()) {
1030 if (edge == RectangleEdge.LEFT) {
1031 anchor = TextAnchor.BOTTOM_CENTER;
1032 rotationAnchor = TextAnchor.BOTTOM_CENTER;
1033 angle = -Math.PI / 2.0;
1034 }
1035 else {
1036 anchor = TextAnchor.BOTTOM_CENTER;
1037 rotationAnchor = TextAnchor.BOTTOM_CENTER;
1038 angle = Math.PI / 2.0;
1039 }
1040 }
1041 else {
1042 if (edge == RectangleEdge.LEFT) {
1043 anchor = TextAnchor.CENTER_RIGHT;
1044 rotationAnchor = TextAnchor.CENTER_RIGHT;
1045 }
1046 else {
1047 anchor = TextAnchor.CENTER_LEFT;
1048 rotationAnchor = TextAnchor.CENTER_LEFT;
1049 }
1050 }
1051 //create tick object and add to list:
1052 ticks.add(new NumberTick(new Double(tickVal), tickLabel,
1053 anchor, rotationAnchor, angle));
1054 }
1055 }
1056 }
1057 return ticks;
1058 }
1059
1060 /**
1061 * Converts the given value to a tick label string.
1062 *
1063 * @param val the value to convert.
1064 * @param forceFmtFlag true to force the number-formatter object
1065 * to be used.
1066 *
1067 * @return The tick label string.
1068 */
1069 protected String makeTickLabel(double val, boolean forceFmtFlag) {
1070 if (this.expTickLabelsFlag || forceFmtFlag) {
1071 //using exponents or force-formatter flag is set
1072 // (convert 'E' to lower-case 'e'):
1073 return this.numberFormatterObj.format(val).toLowerCase();
1074 }
1075 return getTickUnit().valueToString(val);
1076 }
1077
1078 /**
1079 * Converts the given value to a tick label string.
1080 * @param val the value to convert.
1081 *
1082 * @return The tick label string.
1083 */
1084 protected String makeTickLabel(double val) {
1085 return makeTickLabel(val, false);
1086 }
1087
1088 }