001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2005, 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: * ModuloAxis.java
029: * ---------------
030: * (C) Copyright 2004, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): -;
034: *
035: * $Id: ModuloAxis.java,v 1.3.2.1 2005/10/25 20:37:34 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 13-Aug-2004 : Version 1 (DG);
040: *
041: */
042:
043: package org.jfree.chart.axis;
044:
045: import java.awt.geom.Rectangle2D;
046:
047: import org.jfree.chart.event.AxisChangeEvent;
048: import org.jfree.data.Range;
049: import org.jfree.ui.RectangleEdge;
050:
051: /**
052: * An axis that displays numerical values within a fixed range using a modulo
053: * calculation.
054: */
055: public class ModuloAxis extends NumberAxis {
056:
057: /**
058: * The fixed range for the axis - all data values will be mapped to this
059: * range using a modulo calculation.
060: */
061: private Range fixedRange;
062:
063: /**
064: * The display start value (this will sometimes be > displayEnd, in which
065: * case the axis wraps around at some point in the middle of the axis).
066: */
067: private double displayStart;
068:
069: /**
070: * The display end value.
071: */
072: private double displayEnd;
073:
074: /**
075: * Creates a new axis.
076: *
077: * @param label the axis label (<code>null</code> permitted).
078: * @param fixedRange the fixed range (<code>null</code> not permitted).
079: */
080: public ModuloAxis(String label, Range fixedRange) {
081: super (label);
082: this .fixedRange = fixedRange;
083: this .displayStart = 270.0;
084: this .displayEnd = 90.0;
085: }
086:
087: /**
088: * Returns the display start value.
089: *
090: * @return The display start value.
091: */
092: public double getDisplayStart() {
093: return this .displayStart;
094: }
095:
096: /**
097: * Returns the display end value.
098: *
099: * @return The display end value.
100: */
101: public double getDisplayEnd() {
102: return this .displayEnd;
103: }
104:
105: /**
106: * Sets the display range. The values will be mapped to the fixed range if
107: * necessary.
108: *
109: * @param start the start value.
110: * @param end the end value.
111: */
112: public void setDisplayRange(double start, double end) {
113: this .displayStart = mapValueToFixedRange(start);
114: this .displayEnd = mapValueToFixedRange(end);
115: if (this .displayStart < this .displayEnd) {
116: setRange(this .displayStart, this .displayEnd);
117: } else {
118: setRange(this .displayStart, this .fixedRange.getUpperBound()
119: + (this .displayEnd - this .fixedRange
120: .getLowerBound()));
121: }
122: notifyListeners(new AxisChangeEvent(this ));
123: }
124:
125: /**
126: * This method should calculate a range that will show all the data values.
127: * For now, it just sets the axis range to the fixedRange.
128: */
129: protected void autoAdjustRange() {
130: setRange(this .fixedRange, false, false);
131: }
132:
133: /**
134: * Translates a data value to a Java2D coordinate.
135: *
136: * @param value the value.
137: * @param area the area.
138: * @param edge the edge.
139: *
140: * @return A Java2D coordinate.
141: */
142: public double valueToJava2D(double value, Rectangle2D area,
143: RectangleEdge edge) {
144: double result = 0.0;
145: double v = mapValueToFixedRange(value);
146: if (this .displayStart < this .displayEnd) { // regular number axis
147: result = trans(v, area, edge);
148: } else { // displayStart > displayEnd, need to handle split
149: double cutoff = (this .displayStart + this .displayEnd) / 2.0;
150: double length1 = this .fixedRange.getUpperBound()
151: - this .displayStart;
152: double length2 = this .displayEnd
153: - this .fixedRange.getLowerBound();
154: if (v > cutoff) {
155: result = transStart(v, area, edge, length1, length2);
156: } else {
157: result = transEnd(v, area, edge, length1, length2);
158: }
159: }
160: return result;
161: }
162:
163: /**
164: * A regular translation from a data value to a Java2D value.
165: *
166: * @param value the value.
167: * @param area the data area.
168: * @param edge the edge along which the axis lies.
169: *
170: * @return The Java2D coordinate.
171: */
172: private double trans(double value, Rectangle2D area,
173: RectangleEdge edge) {
174: double min = 0.0;
175: double max = 0.0;
176: if (RectangleEdge.isTopOrBottom(edge)) {
177: min = area.getX();
178: max = area.getX() + area.getWidth();
179: } else if (RectangleEdge.isLeftOrRight(edge)) {
180: min = area.getMaxY();
181: max = area.getMaxY() - area.getHeight();
182: }
183: if (isInverted()) {
184: return max
185: - ((value - this .displayStart) / (this .displayEnd - this .displayStart))
186: * (max - min);
187: } else {
188: return min
189: + ((value - this .displayStart) / (this .displayEnd - this .displayStart))
190: * (max - min);
191: }
192:
193: }
194:
195: /**
196: * Translates a data value to a Java2D value for the first section of the
197: * axis.
198: *
199: * @param value the value.
200: * @param area the data area.
201: * @param edge the edge along which the axis lies.
202: * @param length1 the length of the first section.
203: * @param length2 the length of the second section.
204: *
205: * @return The Java2D coordinate.
206: */
207: private double transStart(double value, Rectangle2D area,
208: RectangleEdge edge, double length1, double length2) {
209: double min = 0.0;
210: double max = 0.0;
211: if (RectangleEdge.isTopOrBottom(edge)) {
212: min = area.getX();
213: max = area.getX() + area.getWidth() * length1
214: / (length1 + length2);
215: } else if (RectangleEdge.isLeftOrRight(edge)) {
216: min = area.getMaxY();
217: max = area.getMaxY() - area.getHeight() * length1
218: / (length1 + length2);
219: }
220: if (isInverted()) {
221: return max
222: - ((value - this .displayStart) / (this .fixedRange
223: .getUpperBound() - this .displayStart))
224: * (max - min);
225: } else {
226: return min
227: + ((value - this .displayStart) / (this .fixedRange
228: .getUpperBound() - this .displayStart))
229: * (max - min);
230: }
231:
232: }
233:
234: /**
235: * Translates a data value to a Java2D value for the second section of the
236: * axis.
237: *
238: * @param value the value.
239: * @param area the data area.
240: * @param edge the edge along which the axis lies.
241: * @param length1 the length of the first section.
242: * @param length2 the length of the second section.
243: *
244: * @return The Java2D coordinate.
245: */
246: private double transEnd(double value, Rectangle2D area,
247: RectangleEdge edge, double length1, double length2) {
248: double min = 0.0;
249: double max = 0.0;
250: if (RectangleEdge.isTopOrBottom(edge)) {
251: max = area.getMaxX();
252: min = area.getMaxX() - area.getWidth() * length2
253: / (length1 + length2);
254: } else if (RectangleEdge.isLeftOrRight(edge)) {
255: max = area.getMinY();
256: min = area.getMinY() + area.getHeight() * length2
257: / (length1 + length2);
258: }
259: if (isInverted()) {
260: return max
261: - ((value - this .fixedRange.getLowerBound()) / (this .displayEnd - this .fixedRange
262: .getLowerBound())) * (max - min);
263: } else {
264: return min
265: + ((value - this .fixedRange.getLowerBound()) / (this .displayEnd - this .fixedRange
266: .getLowerBound())) * (max - min);
267: }
268:
269: }
270:
271: /**
272: * Maps a data value into the fixed range.
273: *
274: * @param value the value.
275: *
276: * @return The mapped value.
277: */
278: private double mapValueToFixedRange(double value) {
279: double lower = this .fixedRange.getLowerBound();
280: double length = this .fixedRange.getLength();
281: if (value < lower) {
282: return lower + length + ((value - lower) % length);
283: } else {
284: return lower + ((value - lower) % length);
285: }
286: }
287:
288: /**
289: * Translates a Java2D coordinate into a data value.
290: *
291: * @param java2DValue the Java2D coordinate.
292: * @param area the area.
293: * @param edge the edge.
294: *
295: * @return The Java2D coordinate.
296: */
297: public double java2DToValue(double java2DValue, Rectangle2D area,
298: RectangleEdge edge) {
299: double result = 0.0;
300: if (this .displayStart < this .displayEnd) { // regular number axis
301: result = super .java2DToValue(java2DValue, area, edge);
302: } else { // displayStart > displayEnd, need to handle split
303:
304: }
305: return result;
306: }
307:
308: /**
309: * Returns the display length for the axis.
310: *
311: * @return The display length.
312: */
313: private double getDisplayLength() {
314: if (this .displayStart < this .displayEnd) {
315: return (this .displayEnd - this .displayStart);
316: } else {
317: return (this .fixedRange.getUpperBound() - this .displayStart)
318: + (this .displayEnd - this .fixedRange
319: .getLowerBound());
320: }
321: }
322:
323: /**
324: * Returns the central value of the current display range.
325: *
326: * @return The central value.
327: */
328: private double getDisplayCentralValue() {
329: return mapValueToFixedRange(this .displayStart
330: + (getDisplayLength() / 2));
331: }
332:
333: /**
334: * Increases or decreases the axis range by the specified percentage about
335: * the central value and sends an {@link AxisChangeEvent} to all registered
336: * listeners.
337: * <P>
338: * To double the length of the axis range, use 200% (2.0).
339: * To halve the length of the axis range, use 50% (0.5).
340: *
341: * @param percent the resize factor.
342: */
343: public void resizeRange(double percent) {
344: resizeRange(percent, getDisplayCentralValue());
345: }
346:
347: /**
348: * Increases or decreases the axis range by the specified percentage about
349: * the specified anchor value and sends an {@link AxisChangeEvent} to all
350: * registered listeners.
351: * <P>
352: * To double the length of the axis range, use 200% (2.0).
353: * To halve the length of the axis range, use 50% (0.5).
354: *
355: * @param percent the resize factor.
356: * @param anchorValue the new central value after the resize.
357: */
358: public void resizeRange(double percent, double anchorValue) {
359:
360: if (percent > 0.0) {
361: double halfLength = getDisplayLength() * percent / 2;
362: setDisplayRange(anchorValue - halfLength, anchorValue
363: + halfLength);
364: } else {
365: setAutoRange(true);
366: }
367:
368: }
369:
370: /**
371: * Converts a length in data coordinates into the corresponding length in
372: * Java2D coordinates.
373: *
374: * @param length the length.
375: * @param area the plot area.
376: * @param edge the edge along which the axis lies.
377: *
378: * @return The length in Java2D coordinates.
379: */
380: public double lengthToJava2D(double length, Rectangle2D area,
381: RectangleEdge edge) {
382: double axisLength = 0.0;
383: if (this .displayEnd > this .displayStart) {
384: axisLength = this .displayEnd - this .displayStart;
385: } else {
386: axisLength = (this .fixedRange.getUpperBound() - this .displayStart)
387: + (this .displayEnd - this .fixedRange
388: .getLowerBound());
389: }
390: double areaLength = 0.0;
391: if (RectangleEdge.isLeftOrRight(edge)) {
392: areaLength = area.getHeight();
393: } else {
394: areaLength = area.getWidth();
395: }
396: return (length / axisLength) * areaLength;
397: }
398:
399: }
|