Source for org.jfree.chart.axis.ModuloAxis

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2005, 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:  * ModuloAxis.java
  29:  * ---------------
  30:  * (C) Copyright 2004, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: ModuloAxis.java,v 1.3.2.1 2005/10/25 20:37:34 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 13-Aug-2004 : Version 1 (DG);
  40:  * 
  41:  */
  42: 
  43: package org.jfree.chart.axis;
  44: 
  45: import java.awt.geom.Rectangle2D;
  46: 
  47: import org.jfree.chart.event.AxisChangeEvent;
  48: import org.jfree.data.Range;
  49: import org.jfree.ui.RectangleEdge;
  50: 
  51: /**
  52:  * An axis that displays numerical values within a fixed range using a modulo 
  53:  * calculation.
  54:  */
  55: public class ModuloAxis extends NumberAxis {
  56:     
  57:     /** 
  58:      * The fixed range for the axis - all data values will be mapped to this
  59:      * range using a modulo calculation. 
  60:      */
  61:     private Range fixedRange;
  62:     
  63:     /**
  64:      * The display start value (this will sometimes be > displayEnd, in which
  65:      * case the axis wraps around at some point in the middle of the axis).
  66:      */
  67:     private double displayStart;
  68:     
  69:     /**
  70:      * The display end value.
  71:      */
  72:     private double displayEnd;
  73:     
  74:     /**
  75:      * Creates a new axis.
  76:      * 
  77:      * @param label  the axis label (<code>null</code> permitted).
  78:      * @param fixedRange  the fixed range (<code>null</code> not permitted).
  79:      */
  80:     public ModuloAxis(String label, Range fixedRange) {
  81:         super(label);
  82:         this.fixedRange = fixedRange;
  83:         this.displayStart = 270.0;
  84:         this.displayEnd = 90.0;
  85:     }
  86: 
  87:     /**
  88:      * Returns the display start value.
  89:      * 
  90:      * @return The display start value.
  91:      */
  92:     public double getDisplayStart() {
  93:         return this.displayStart;
  94:     }
  95: 
  96:     /**
  97:      * Returns the display end value.
  98:      * 
  99:      * @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:         }
 118:         else {
 119:             setRange(
 120:                 this.displayStart, 
 121:                 this.fixedRange.getUpperBound() 
 122:                   + (this.displayEnd - this.fixedRange.getLowerBound())
 123:             );
 124:         }
 125:         notifyListeners(new AxisChangeEvent(this));        
 126:     }
 127:     
 128:     /**
 129:      * This method should calculate a range that will show all the data values.
 130:      * For now, it just sets the axis range to the fixedRange.
 131:      */
 132:     protected void autoAdjustRange() {
 133:         setRange(this.fixedRange, false, false);
 134:     }
 135:     
 136:     /**
 137:      * Translates a data value to a Java2D coordinate.
 138:      * 
 139:      * @param value  the value.
 140:      * @param area  the area.
 141:      * @param edge  the edge.
 142:      * 
 143:      * @return A Java2D coordinate.
 144:      */
 145:     public double valueToJava2D(double value, Rectangle2D area, 
 146:                                 RectangleEdge edge) {
 147:         double result = 0.0;
 148:         double v = mapValueToFixedRange(value);
 149:         if (this.displayStart < this.displayEnd) {  // regular number axis
 150:             result = trans(v, area, edge);
 151:         }
 152:         else {  // displayStart > displayEnd, need to handle split
 153:             double cutoff = (this.displayStart + this.displayEnd) / 2.0;
 154:             double length1 = this.fixedRange.getUpperBound() 
 155:                              - this.displayStart;
 156:             double length2 = this.displayEnd - this.fixedRange.getLowerBound();
 157:             if (v > cutoff) {
 158:                 result = transStart(v, area, edge, length1, length2);
 159:             }
 160:             else {
 161:                 result = transEnd(v, area, edge, length1, length2);
 162:             }
 163:         }
 164:         return result;
 165:     }
 166: 
 167:     /**
 168:      * A regular translation from a data value to a Java2D value.
 169:      * 
 170:      * @param value  the value.
 171:      * @param area  the data area.
 172:      * @param edge  the edge along which the axis lies.
 173:      * 
 174:      * @return The Java2D coordinate.
 175:      */
 176:     private double trans(double value, Rectangle2D area, RectangleEdge edge) {
 177:         double min = 0.0;
 178:         double max = 0.0;
 179:         if (RectangleEdge.isTopOrBottom(edge)) {
 180:             min = area.getX();
 181:             max = area.getX() + area.getWidth();
 182:         }
 183:         else if (RectangleEdge.isLeftOrRight(edge)) {
 184:             min = area.getMaxY();
 185:             max = area.getMaxY() - area.getHeight();
 186:         }
 187:         if (isInverted()) {
 188:             return max - ((value - this.displayStart) 
 189:                    / (this.displayEnd - this.displayStart)) * (max - min);
 190:         }
 191:         else {
 192:             return min + ((value - this.displayStart) 
 193:                    / (this.displayEnd - this.displayStart)) * (max - min);
 194:         }
 195: 
 196:     }
 197: 
 198:     /**
 199:      * Translates a data value to a Java2D value for the first section of the 
 200:      * axis.
 201:      * 
 202:      * @param value  the value.
 203:      * @param area  the data area.
 204:      * @param edge  the edge along which the axis lies.
 205:      * @param length1  the length of the first section.
 206:      * @param length2  the length of the second section.
 207:      * 
 208:      * @return The Java2D coordinate.
 209:      */
 210:     private double transStart(double value, Rectangle2D area, 
 211:                               RectangleEdge edge,
 212:                               double length1, double length2) {
 213:         double min = 0.0;
 214:         double max = 0.0;
 215:         if (RectangleEdge.isTopOrBottom(edge)) {
 216:             min = area.getX();
 217:             max = area.getX() + area.getWidth() * length1 / (length1 + length2);
 218:         }
 219:         else if (RectangleEdge.isLeftOrRight(edge)) {
 220:             min = area.getMaxY();
 221:             max = area.getMaxY() - area.getHeight() * length1 
 222:                   / (length1 + length2);
 223:         }
 224:         if (isInverted()) {
 225:             return max - ((value - this.displayStart) 
 226:                 / (this.fixedRange.getUpperBound() - this.displayStart)) 
 227:                 * (max - min);
 228:         }
 229:         else {
 230:             return min + ((value - this.displayStart) 
 231:                 / (this.fixedRange.getUpperBound() - this.displayStart)) 
 232:                 * (max - min);
 233:         }
 234: 
 235:     }
 236:     
 237:     /**
 238:      * Translates a data value to a Java2D value for the second section of the 
 239:      * axis.
 240:      * 
 241:      * @param value  the value.
 242:      * @param area  the data area.
 243:      * @param edge  the edge along which the axis lies.
 244:      * @param length1  the length of the first section.
 245:      * @param length2  the length of the second section.
 246:      * 
 247:      * @return The Java2D coordinate.
 248:      */
 249:     private double transEnd(double value, Rectangle2D area, RectangleEdge edge,
 250:                             double length1, double length2) {
 251:         double min = 0.0;
 252:         double max = 0.0;
 253:         if (RectangleEdge.isTopOrBottom(edge)) {
 254:             max = area.getMaxX();
 255:             min = area.getMaxX() - area.getWidth() * length2 
 256:                   / (length1 + length2);
 257:         }
 258:         else if (RectangleEdge.isLeftOrRight(edge)) {
 259:             max = area.getMinY();
 260:             min = area.getMinY() + area.getHeight() * length2 
 261:                   / (length1 + length2);
 262:         }
 263:         if (isInverted()) {
 264:             return max - ((value - this.fixedRange.getLowerBound()) 
 265:                     / (this.displayEnd - this.fixedRange.getLowerBound())) 
 266:                     * (max - min);
 267:         }
 268:         else {
 269:             return min + ((value - this.fixedRange.getLowerBound()) 
 270:                     / (this.displayEnd - this.fixedRange.getLowerBound())) 
 271:                     * (max - min);
 272:         }
 273: 
 274:     }
 275: 
 276:     /**
 277:      * Maps a data value into the fixed range.
 278:      * 
 279:      * @param value  the value.
 280:      * 
 281:      * @return The mapped value.
 282:      */
 283:     private double mapValueToFixedRange(double value) {
 284:         double lower = this.fixedRange.getLowerBound();
 285:         double length = this.fixedRange.getLength();
 286:         if (value < lower) {
 287:             return lower + length + ((value - lower) % length);
 288:         }
 289:         else {
 290:             return lower + ((value - lower) % length);
 291:         }
 292:     }
 293:     
 294:     /**
 295:      * Translates a Java2D coordinate into a data value.
 296:      * 
 297:      * @param java2DValue  the Java2D coordinate.
 298:      * @param area  the area.
 299:      * @param edge  the edge.
 300:      * 
 301:      * @return The Java2D coordinate.
 302:      */
 303:     public double java2DToValue(double java2DValue, Rectangle2D area, 
 304:                                 RectangleEdge edge) {
 305:         double result = 0.0;
 306:         if (this.displayStart < this.displayEnd) {  // regular number axis
 307:             result = super.java2DToValue(java2DValue, area, edge);
 308:         }
 309:         else {  // displayStart > displayEnd, need to handle split
 310:             
 311:         }
 312:         return result;
 313:     }
 314:     
 315:     /**
 316:      * Returns the display length for the axis.
 317:      * 
 318:      * @return The display length.
 319:      */
 320:     private double getDisplayLength() {
 321:         if (this.displayStart < this.displayEnd) {
 322:             return (this.displayEnd - this.displayStart);
 323:         }
 324:         else {
 325:             return (this.fixedRange.getUpperBound() - this.displayStart)
 326:                 + (this.displayEnd - this.fixedRange.getLowerBound());
 327:         }
 328:     }
 329:     
 330:     /**
 331:      * Returns the central value of the current display range.
 332:      * 
 333:      * @return The central value.
 334:      */
 335:     private double getDisplayCentralValue() {
 336:         return mapValueToFixedRange(
 337:             this.displayStart + (getDisplayLength() / 2)
 338:         );    
 339:     }
 340:     
 341:     /**
 342:      * Increases or decreases the axis range by the specified percentage about 
 343:      * the central value and sends an {@link AxisChangeEvent} to all registered
 344:      * listeners.
 345:      * <P>
 346:      * To double the length of the axis range, use 200% (2.0).
 347:      * To halve the length of the axis range, use 50% (0.5).
 348:      *
 349:      * @param percent  the resize factor.
 350:      */
 351:     public void resizeRange(double percent) {
 352:         resizeRange(percent, getDisplayCentralValue());
 353:     }
 354: 
 355:     /**
 356:      * Increases or decreases the axis range by the specified percentage about 
 357:      * the specified anchor value and sends an {@link AxisChangeEvent} to all 
 358:      * registered listeners.
 359:      * <P>
 360:      * To double the length of the axis range, use 200% (2.0).
 361:      * To halve the length of the axis range, use 50% (0.5).
 362:      *
 363:      * @param percent  the resize factor.
 364:      * @param anchorValue  the new central value after the resize.
 365:      */
 366:     public void resizeRange(double percent, double anchorValue) {
 367: 
 368:         if (percent > 0.0) {
 369:             double halfLength = getDisplayLength() * percent / 2;
 370:             setDisplayRange(anchorValue - halfLength, anchorValue + halfLength);
 371:         }
 372:         else {
 373:             setAutoRange(true);
 374:         }
 375: 
 376:     } 
 377:     
 378:     /**
 379:      * Converts a length in data coordinates into the corresponding length in 
 380:      * Java2D coordinates.
 381:      * 
 382:      * @param length  the length.
 383:      * @param area  the plot area.
 384:      * @param edge  the edge along which the axis lies.
 385:      * 
 386:      * @return The length in Java2D coordinates.
 387:      */
 388:     public double lengthToJava2D(double length, Rectangle2D area, 
 389:                                  RectangleEdge edge) {
 390:         double axisLength = 0.0;
 391:         if (this.displayEnd > this.displayStart) {
 392:             axisLength = this.displayEnd - this.displayStart;
 393:         }
 394:         else {
 395:             axisLength = (this.fixedRange.getUpperBound() - this.displayStart) 
 396:                 + (this.displayEnd - this.fixedRange.getLowerBound());
 397:         }
 398:         double areaLength = 0.0;
 399:         if (RectangleEdge.isLeftOrRight(edge)) {
 400:             areaLength = area.getHeight();
 401:         }
 402:         else {
 403:             areaLength = area.getWidth();
 404:         }
 405:         return (length / axisLength) * areaLength;
 406:     }
 407:     
 408: }