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