KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jfree > experimental > chart > axis > LogAxis


1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
6  *
7  * Project Info: http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22  * USA.
23  *
24  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
25  * in the United States and other countries.]
26  *
27  * ------------
28  * LogAxis.java
29  * ------------
30  * (C) Copyright 2006, by Object Refinery Limited and Contributors.
31  *
32  * Original Author: David Gilbert (for Object Refinery Limited);
33  * Contributor(s): -;
34  *
35  * $Id: LogAxis.java,v 1.1.2.1 2006/10/12 09:30:49 mungady Exp $
36  *
37  * Changes
38  * -------
39  * 24-Aug-2006 : Version 1 (DG);
40  *
41  */

42
43 package org.jfree.experimental.chart.axis;
44
45 import java.awt.Graphics2D JavaDoc;
46 import java.awt.geom.Rectangle2D JavaDoc;
47 import java.text.NumberFormat JavaDoc;
48 import java.util.ArrayList JavaDoc;
49 import java.util.List JavaDoc;
50
51 import org.jfree.chart.axis.AxisState;
52 import org.jfree.chart.axis.LogarithmicAxis;
53 import org.jfree.chart.axis.NumberAxis;
54 import org.jfree.chart.axis.NumberTick;
55 import org.jfree.chart.axis.NumberTickUnit;
56 import org.jfree.chart.axis.ValueAxis;
57 import org.jfree.chart.event.AxisChangeEvent;
58 import org.jfree.chart.plot.Plot;
59 import org.jfree.chart.plot.PlotRenderingInfo;
60 import org.jfree.chart.plot.ValueAxisPlot;
61 import org.jfree.data.Range;
62 import org.jfree.ui.RectangleEdge;
63 import org.jfree.ui.TextAnchor;
64
65 /**
66  * A numerical axis that uses a logarithmic scale. The plan is for this class
67  * to replace the {@link LogarithmicAxis} class.
68  *
69  * WARNING: THIS CLASS IS NOT PART OF THE STANDARD JFREECHART API AND IS
70  * SUBJECT TO ALTERATION OR REMOVAL. DO NOT RELY ON THIS CLASS FOR
71  * PRODUCTION USE. Please experiment with this code and provide feedback.
72  */

73
74 // TODO: support for margins that get inherited from ValueAxis
75
// TODO: add auto tick unit selection
76
// TODO: number formatting options
77
// TODO: write JUnit tests
78

79 public class LogAxis extends ValueAxis {
80
81     /** The default lower bound for the axis. */
82     public static final double DEFAULT_LOWER_BOUND = 0.01;
83
84     /** The logarithm base. */
85     private double base = 10.0;
86     
87     /** The logarithm of the base value - cached for performance. */
88     private double baseLog = Math.log(10.0);
89     
90     /** The smallest value permitted on the axis. */
91     private double smallestValue = 1E-100;
92     
93     /** The current tick unit. */
94     private NumberTickUnit tickUnit;
95     
96     /** The override number format. */
97     private NumberFormat JavaDoc numberFormatOverride;
98
99     /** The number of minor ticks per major tick unit. */
100     private int minorTickCount;
101     
102     /**
103      * Creates a new <code>LogAxis</code> with no label.
104      */

105     public LogAxis() {
106         this(null);
107     }
108     
109     /**
110      * Creates a new <code>LogAxis</code> with the given label.
111      *
112      * @param label the axis label (<code>null</code> permitted).
113      */

114     public LogAxis(String JavaDoc label) {
115         super(label, NumberAxis.createIntegerTickUnits());
116         this.tickUnit = new NumberTickUnit(1.0);
117         this.minorTickCount = 10;
118         this.setTickMarksVisible(false);
119     }
120     
121     /**
122      * Returns the base for the logarithm calculation.
123      *
124      * @return The base for the logarithm calculation.
125      */

126     public double getBase() {
127         return this.base;
128     }
129     
130     /**
131      * Sets the base for the logarithm calculation and sends an
132      * {@link AxisChangeEvent} to all registered listeners.
133      *
134      * @param base the base value (must be > 1.0).
135      */

136     public void setBase(double base) {
137         if (base <= 1.0) {
138             throw new IllegalArgumentException JavaDoc("Requires 'base' > 1.0.");
139         }
140         this.base = base;
141         this.baseLog = Math.log(base);
142         notifyListeners(new AxisChangeEvent(this));
143     }
144     
145     /**
146      * Returns the smallest value represented by the axis.
147      *
148      * @return The smallest value represented by the axis.
149      */

150     public double getSmallestValue() {
151         return this.smallestValue;
152     }
153     
154     /**
155      * Sets the smallest value represented by the axis.
156      *
157      * @param value the value.
158      */

159     public void setSmallestValue(double value) {
160         if (value <= 0.0) {
161             throw new IllegalArgumentException JavaDoc("Requires 'value' > 0.0.");
162         }
163         this.smallestValue = value;
164     }
165     
166     /**
167      * Returns the current tick unit.
168      *
169      * @return The current tick unit.
170      */

171     public NumberTickUnit getTickUnit() {
172         return this.tickUnit;
173     }
174     
175     /**
176      * Sets the tick unit for the axis and sends an {@link AxisChangeEvent} to
177      * all registered listeners. A side effect of calling this method is that
178      * the "auto-select" feature for tick units is switched off (you can
179      * restore it using the {@link ValueAxis#setAutoTickUnitSelection(boolean)}
180      * method).
181      *
182      * @param unit the new tick unit (<code>null</code> not permitted).
183      */

184     public void setTickUnit(NumberTickUnit unit) {
185         // defer argument checking...
186
setTickUnit(unit, true, true);
187     }
188
189     /**
190      * Sets the tick unit for the axis and, if requested, sends an
191      * {@link AxisChangeEvent} to all registered listeners. In addition, an
192      * option is provided to turn off the "auto-select" feature for tick units
193      * (you can restore it using the
194      * {@link ValueAxis#setAutoTickUnitSelection(boolean)} method).
195      *
196      * @param unit the new tick unit (<code>null</code> not permitted).
197      * @param notify notify listeners?
198      * @param turnOffAutoSelect turn off the auto-tick selection?
199      */

200     public void setTickUnit(NumberTickUnit unit, boolean notify,
201                             boolean turnOffAutoSelect) {
202
203         if (unit == null) {
204             throw new IllegalArgumentException JavaDoc("Null 'unit' argument.");
205         }
206         this.tickUnit = unit;
207         if (turnOffAutoSelect) {
208             setAutoTickUnitSelection(false, false);
209         }
210         if (notify) {
211             notifyListeners(new AxisChangeEvent(this));
212         }
213
214     }
215     
216     /**
217      * Returns the number format override. If this is non-null, then it will
218      * be used to format the numbers on the axis.
219      *
220      * @return The number formatter (possibly <code>null</code>).
221      */

222     public NumberFormat JavaDoc getNumberFormatOverride() {
223         return this.numberFormatOverride;
224     }
225
226     /**
227      * Sets the number format override. If this is non-null, then it will be
228      * used to format the numbers on the axis.
229      *
230      * @param formatter the number formatter (<code>null</code> permitted).
231      */

232     public void setNumberFormatOverride(NumberFormat JavaDoc formatter) {
233         this.numberFormatOverride = formatter;
234         notifyListeners(new AxisChangeEvent(this));
235     }
236
237     /**
238      * Returns the number of minor tick marks to display.
239      *
240      * @return The number of minor tick marks to display.
241      */

242     public int getMinorTickCount() {
243         return this.minorTickCount;
244     }
245     
246     /**
247      * Sets the number of minor tick marks to display.
248      *
249      * @param count the count.
250      */

251     public void setMinorTickCount(int count) {
252         if (count <= 0) {
253             throw new IllegalArgumentException JavaDoc("Requires 'count' > 0.");
254         }
255         this.minorTickCount = count;
256         notifyListeners(new AxisChangeEvent(this));
257     }
258     
259     /**
260      * Calculates the log of the given value, using the current base.
261      *
262      * @param value the value.
263      *
264      * @return The log of the given value.
265      *
266      * @see #getBase()
267      */

268     public double calculateLog(double value) {
269         return Math.log(value) / this.baseLog;
270     }
271     
272     /**
273      * Calculates the value from a given log.
274      *
275      * @param log the log value (must be > 0.0).
276      *
277      * @return The value with the given log.
278      */

279     public double calculateValue(double log) {
280         return Math.pow(this.base, log);
281     }
282     
283     /**
284      * Converts a Java2D coordinate to an axis value, assuming that the
285      * axis covers the specified <code>edge</code> of the <code>area</code>.
286      *
287      * @param java2DValue the Java2D coordinate.
288      * @param area the area.
289      * @param edge the edge that the axis belongs to.
290      *
291      * @return A value along the axis scale.
292      */

293     public double java2DToValue(double java2DValue, Rectangle2D JavaDoc area,
294             RectangleEdge edge) {
295         
296         Range range = getRange();
297         double axisMin = calculateLog(range.getLowerBound());
298         double axisMax = calculateLog(range.getUpperBound());
299
300         double min = 0.0;
301         double max = 0.0;
302         if (RectangleEdge.isTopOrBottom(edge)) {
303             min = area.getX();
304             max = area.getMaxX();
305         }
306         else if (RectangleEdge.isLeftOrRight(edge)) {
307             min = area.getMaxY();
308             max = area.getY();
309         }
310         double log = 0.0;
311         if (isInverted()) {
312             log = axisMax - (java2DValue - min) / (max - min)
313                     * (axisMax - axisMin);
314         }
315         else {
316             log = axisMin + (java2DValue - min) / (max - min)
317                     * (axisMax - axisMin);
318         }
319         return calculateValue(log);
320     }
321
322     /**
323      * Converts a value on the axis scale to a Java2D coordinate relative to
324      * the given <code>area</code>, based on the axis running along the
325      * specified <code>edge</code>.
326      *
327      * @param value the data value.
328      * @param area the area.
329      * @param edge the edge.
330      *
331      * @return The Java2D coordinate corresponding to <code>value</code>.
332      */

333     public double valueToJava2D(double value, Rectangle2D JavaDoc area,
334             RectangleEdge edge) {
335         
336         Range range = getRange();
337         double axisMin = calculateLog(range.getLowerBound());
338         double axisMax = calculateLog(range.getUpperBound());
339         value = calculateLog(value);
340         
341         double min = 0.0;
342         double max = 0.0;
343         if (RectangleEdge.isTopOrBottom(edge)) {
344             min = area.getX();
345             max = area.getMaxX();
346         }
347         else if (RectangleEdge.isLeftOrRight(edge)) {
348             max = area.getMinY();
349             min = area.getMaxY();
350         }
351         if (isInverted()) {
352             return max
353                    - ((value - axisMin) / (axisMax - axisMin)) * (max - min);
354         }
355         else {
356             return min
357                    + ((value - axisMin) / (axisMax - axisMin)) * (max - min);
358         }
359     }
360     
361     /**
362      * Configures the axis. This method is typically called when an axis
363      * is assigned to a new plot.
364      */

365     public void configure() {
366         if (isAutoRange()) {
367             autoAdjustRange();
368         }
369     }
370
371     /**
372      * Adjusts the axis range to match the data range that the axis is
373      * required to display.
374      */

375     protected void autoAdjustRange() {
376         Plot plot = getPlot();
377         if (plot == null) {
378             return; // no plot, no data
379
}
380
381         if (plot instanceof ValueAxisPlot) {
382             ValueAxisPlot vap = (ValueAxisPlot) plot;
383
384             Range r = vap.getDataRange(this);
385             if (r == null) {
386                 r = new Range(DEFAULT_LOWER_BOUND, DEFAULT_UPPER_BOUND);
387             }
388             
389             double upper = r.getUpperBound();
390             double lower = r.getLowerBound();
391             double range = upper - lower;
392
393             // if fixed auto range, then derive lower bound...
394
double fixedAutoRange = getFixedAutoRange();
395             if (fixedAutoRange > 0.0) {
396                 lower = Math.max(upper - fixedAutoRange, this.smallestValue);
397             }
398             else {
399                 // ensure the autorange is at least <minRange> in size...
400
double minRange = getAutoRangeMinimumSize();
401                 if (range < minRange) {
402                     double expand = (minRange - range) / 2;
403                     upper = upper + expand;
404                     lower = lower - expand;
405                 }
406
407                 // apply the margins - these should apply to the exponent range
408
// upper = upper + getUpperMargin() * range;
409
// lower = lower - getLowerMargin() * range;
410
}
411
412             setRange(new Range(lower, upper), false, false);
413         }
414
415     }
416
417     /**
418      * Draws the axis on a Java 2D graphics device (such as the screen or a
419      * printer).
420      *
421      * @param g2 the graphics device (<code>null</code> not permitted).
422      * @param cursor the cursor location (determines where to draw the axis).
423      * @param plotArea the area within which the axes and plot should be drawn.
424      * @param dataArea the area within which the data should be drawn.
425      * @param edge the axis location (<code>null</code> not permitted).
426      * @param plotState collects information about the plot
427      * (<code>null</code> permitted).
428      *
429      * @return The axis state (never <code>null</code>).
430      */

431     public AxisState draw(Graphics2D JavaDoc g2, double cursor, Rectangle2D JavaDoc plotArea,
432             Rectangle2D JavaDoc dataArea, RectangleEdge edge,
433             PlotRenderingInfo plotState) {
434         
435         AxisState state = null;
436         // if the axis is not visible, don't draw it...
437
if (!isVisible()) {
438             state = new AxisState(cursor);
439             // even though the axis is not visible, we need ticks for the
440
// gridlines...
441
List JavaDoc ticks = refreshTicks(g2, state, dataArea, edge);
442             state.setTicks(ticks);
443             return state;
444         }
445         state = drawTickMarksAndLabels(g2, cursor, plotArea, dataArea, edge);
446         state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state);
447         return state;
448     }
449
450     /**
451      * Calculates the positions of the tick labels for the axis, storing the
452      * results in the tick label list (ready for drawing).
453      *
454      * @param g2 the graphics device.
455      * @param state the axis state.
456      * @param dataArea the area in which the plot should be drawn.
457      * @param edge the location of the axis.
458      *
459      * @return A list of ticks.
460      *
461      */

462     public List JavaDoc refreshTicks(Graphics2D JavaDoc g2, AxisState state,
463             Rectangle2D JavaDoc dataArea, RectangleEdge edge) {
464
465         List JavaDoc result = new java.util.ArrayList JavaDoc();
466         if (RectangleEdge.isTopOrBottom(edge)) {
467             result = refreshTicksHorizontal(g2, dataArea, edge);
468         }
469         else if (RectangleEdge.isLeftOrRight(edge)) {
470             result = refreshTicksVertical(g2, dataArea, edge);
471         }
472         return result;
473
474     }
475
476     /**
477      * Returns a list of ticks for an axis at the top or bottom of the chart.
478      *
479      * @param g2 the graphics device.
480      * @param dataArea the data area.
481      * @param edge the edge.
482      *
483      * @return A list of ticks.
484      */

485     protected List JavaDoc refreshTicksHorizontal(Graphics2D JavaDoc g2, Rectangle2D JavaDoc dataArea,
486             RectangleEdge edge) {
487         Range range = getRange();
488         List JavaDoc ticks = new ArrayList JavaDoc();
489         double start = Math.floor(calculateLog(getLowerBound()));
490         double end = Math.ceil(calculateLog(getUpperBound()));
491         double current = start;
492         while (current <= end) {
493             double v = calculateValue(current);
494             if (range.contains(v)) {
495                 ticks.add(new NumberTick(new Double JavaDoc(v), createTickLabel(v),
496                         TextAnchor.TOP_CENTER, TextAnchor.CENTER, 0.0));
497             }
498             // add minor ticks (for gridlines)
499
double next = Math.pow(this.base, current
500                     + this.tickUnit.getSize());
501             for (int i = 1; i < this.minorTickCount; i++) {
502                 double minorV = v + i * ((next - v) / this.minorTickCount);
503                 if (range.contains(minorV)) {
504                     ticks.add(new NumberTick(new Double JavaDoc(minorV),
505                         "", TextAnchor.TOP_CENTER, TextAnchor.CENTER, 0.0));
506                 }
507             }
508             current = current + this.tickUnit.getSize();
509         }
510         return ticks;
511     }
512     
513     /**
514      * Returns a list of ticks for an axis at the left or right of the chart.
515      *
516      * @param g2 the graphics device.
517      * @param dataArea the data area.
518      * @param edge the edge.
519      *
520      * @return A list of ticks.
521      */

522     protected List JavaDoc refreshTicksVertical(Graphics2D JavaDoc g2, Rectangle2D JavaDoc dataArea,
523             RectangleEdge edge) {
524         Range range = getRange();
525         List JavaDoc ticks = new ArrayList JavaDoc();
526         double start = Math.floor(calculateLog(getLowerBound()));
527         double end = Math.ceil(calculateLog(getUpperBound()));
528         double current = start;
529         while (current <= end) {
530             double v = calculateValue(current);
531             if (range.contains(v)) {
532                 ticks.add(new NumberTick(new Double JavaDoc(v), createTickLabel(v),
533                         TextAnchor.CENTER_RIGHT, TextAnchor.CENTER, 0.0));
534             }
535             // add minor ticks (for gridlines)
536
double next = Math.pow(this.base, current
537                     + this.tickUnit.getSize());
538             for (int i = 1; i < this.minorTickCount; i++) {
539                 double minorV = v + i * ((next - v) / this.minorTickCount);
540                 if (range.contains(minorV)) {
541                     ticks.add(new NumberTick(new Double JavaDoc(minorV), "",
542                             TextAnchor.CENTER_RIGHT, TextAnchor.CENTER, 0.0));
543                 }
544             }
545             current = current + this.tickUnit.getSize();
546         }
547         return ticks;
548     }
549
550     /**
551      * Creates a tick label for the specified value.
552      *
553      * @param value the value.
554      *
555      * @return The label.
556      */

557     private String JavaDoc createTickLabel(double value) {
558         if (this.numberFormatOverride != null) {
559             return this.numberFormatOverride.format(value);
560         }
561         else {
562             return this.tickUnit.valueToString(value);
563         }
564     }
565
566 }
567
Popular Tags