1:
42:
43: package ;
44:
45: import ;
46: import ;
47: import ;
48: import ;
49: import ;
50: import ;
51: import ;
52: import ;
53: import ;
54: import ;
55: import ;
56:
57: import ;
58: import ;
59: import ;
60: import ;
61: import ;
62: import ;
63: import ;
64: import ;
65:
66:
72: public class LogAxis extends ValueAxis {
73:
74:
75: private double base = 10.0;
76:
77:
78: private double baseLog = Math.log(10.0);
79:
80:
81: private double smallestValue = 1E-100;
82:
83:
84: private NumberTickUnit tickUnit;
85:
86:
87: private NumberFormat numberFormatOverride;
88:
89:
90: private int minorTickCount;
91:
92:
95: public LogAxis() {
96: this(null);
97: }
98:
99:
104: public LogAxis(String label) {
105: super(label, createLogTickUnits(Locale.getDefault()));
106: setDefaultAutoRange(new Range(0.01, 1.0));
107: this.tickUnit = new NumberTickUnit(1.0, new DecimalFormat("0.#"));
108: this.minorTickCount = 10;
109: }
110:
111:
118: public double getBase() {
119: return this.base;
120: }
121:
122:
130: public void setBase(double base) {
131: if (base <= 1.0) {
132: throw new IllegalArgumentException("Requires 'base' > 1.0.");
133: }
134: this.base = base;
135: this.baseLog = Math.log(base);
136: notifyListeners(new AxisChangeEvent(this));
137: }
138:
139:
146: public double getSmallestValue() {
147: return this.smallestValue;
148: }
149:
150:
158: public void setSmallestValue(double value) {
159: if (value <= 0.0) {
160: throw new IllegalArgumentException("Requires 'value' > 0.0.");
161: }
162: this.smallestValue = value;
163: notifyListeners(new AxisChangeEvent(this));
164: }
165:
166:
173: public NumberTickUnit getTickUnit() {
174: return this.tickUnit;
175: }
176:
177:
188: public void setTickUnit(NumberTickUnit unit) {
189:
190: setTickUnit(unit, true, true);
191: }
192:
193:
206: public void setTickUnit(NumberTickUnit unit, boolean notify,
207: boolean turnOffAutoSelect) {
208:
209: if (unit == null) {
210: throw new IllegalArgumentException("Null 'unit' argument.");
211: }
212: this.tickUnit = unit;
213: if (turnOffAutoSelect) {
214: setAutoTickUnitSelection(false, false);
215: }
216: if (notify) {
217: notifyListeners(new AxisChangeEvent(this));
218: }
219:
220: }
221:
222:
230: public NumberFormat getNumberFormatOverride() {
231: return this.numberFormatOverride;
232: }
233:
234:
242: public void setNumberFormatOverride(NumberFormat formatter) {
243: this.numberFormatOverride = formatter;
244: notifyListeners(new AxisChangeEvent(this));
245: }
246:
247:
254: public int getMinorTickCount() {
255: return this.minorTickCount;
256: }
257:
258:
266: public void setMinorTickCount(int count) {
267: if (count <= 0) {
268: throw new IllegalArgumentException("Requires 'count' > 0.");
269: }
270: this.minorTickCount = count;
271: notifyListeners(new AxisChangeEvent(this));
272: }
273:
274:
284: public double calculateLog(double value) {
285: return Math.log(value) / this.baseLog;
286: }
287:
288:
298: public double calculateValue(double log) {
299: return Math.pow(this.base, log);
300: }
301:
302:
312: public double java2DToValue(double java2DValue, Rectangle2D area,
313: RectangleEdge edge) {
314:
315: Range range = getRange();
316: double axisMin = calculateLog(range.getLowerBound());
317: double axisMax = calculateLog(range.getUpperBound());
318:
319: double min = 0.0;
320: double max = 0.0;
321: if (RectangleEdge.isTopOrBottom(edge)) {
322: min = area.getX();
323: max = area.getMaxX();
324: }
325: else if (RectangleEdge.isLeftOrRight(edge)) {
326: min = area.getMaxY();
327: max = area.getY();
328: }
329: double log = 0.0;
330: if (isInverted()) {
331: log = axisMax - (java2DValue - min) / (max - min)
332: * (axisMax - axisMin);
333: }
334: else {
335: log = axisMin + (java2DValue - min) / (max - min)
336: * (axisMax - axisMin);
337: }
338: return calculateValue(log);
339: }
340:
341:
352: public double valueToJava2D(double value, Rectangle2D area,
353: RectangleEdge edge) {
354:
355: Range range = getRange();
356: double axisMin = calculateLog(range.getLowerBound());
357: double axisMax = calculateLog(range.getUpperBound());
358: value = calculateLog(value);
359:
360: double min = 0.0;
361: double max = 0.0;
362: if (RectangleEdge.isTopOrBottom(edge)) {
363: min = area.getX();
364: max = area.getMaxX();
365: }
366: else if (RectangleEdge.isLeftOrRight(edge)) {
367: max = area.getMinY();
368: min = area.getMaxY();
369: }
370: if (isInverted()) {
371: return max
372: - ((value - axisMin) / (axisMax - axisMin)) * (max - min);
373: }
374: else {
375: return min
376: + ((value - axisMin) / (axisMax - axisMin)) * (max - min);
377: }
378: }
379:
380:
384: public void configure() {
385: if (isAutoRange()) {
386: autoAdjustRange();
387: }
388: }
389:
390:
394: protected void autoAdjustRange() {
395: Plot plot = getPlot();
396: if (plot == null) {
397: return;
398: }
399:
400: if (plot instanceof ValueAxisPlot) {
401: ValueAxisPlot vap = (ValueAxisPlot) plot;
402:
403: Range r = vap.getDataRange(this);
404: if (r == null) {
405: r = getDefaultAutoRange();
406: }
407:
408: double upper = r.getUpperBound();
409: double lower = Math.max(r.getLowerBound(), this.smallestValue);
410: double range = upper - lower;
411:
412:
413: double fixedAutoRange = getFixedAutoRange();
414: if (fixedAutoRange > 0.0) {
415: lower = Math.max(upper - fixedAutoRange, this.smallestValue);
416: }
417: else {
418:
419: double minRange = getAutoRangeMinimumSize();
420: if (range < minRange) {
421: double expand = (minRange - range) / 2;
422: upper = upper + expand;
423: lower = lower - expand;
424: }
425:
426:
427: double logUpper = calculateLog(upper);
428: double logLower = calculateLog(lower);
429: double logRange = logUpper - logLower;
430: logUpper = logUpper + getUpperMargin() * logRange;
431: logLower = logLower - getLowerMargin() * logRange;
432: upper = calculateValue(logUpper);
433: lower = calculateValue(logLower);
434: }
435:
436: setRange(new Range(lower, upper), false, false);
437: }
438:
439: }
440:
441:
455: public AxisState draw(Graphics2D g2, double cursor, Rectangle2D plotArea,
456: Rectangle2D dataArea, RectangleEdge edge,
457: PlotRenderingInfo plotState) {
458:
459: AxisState state = null;
460:
461: if (!isVisible()) {
462: state = new AxisState(cursor);
463:
464:
465: List ticks = refreshTicks(g2, state, dataArea, edge);
466: state.setTicks(ticks);
467: return state;
468: }
469: state = drawTickMarksAndLabels(g2, cursor, plotArea, dataArea, edge);
470: state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state);
471: return state;
472: }
473:
474:
486: public List refreshTicks(Graphics2D g2, AxisState state,
487: Rectangle2D dataArea, RectangleEdge edge) {
488:
489: List result = new java.util.ArrayList();
490: if (RectangleEdge.isTopOrBottom(edge)) {
491: result = refreshTicksHorizontal(g2, dataArea, edge);
492: }
493: else if (RectangleEdge.isLeftOrRight(edge)) {
494: result = refreshTicksVertical(g2, dataArea, edge);
495: }
496: return result;
497:
498: }
499:
500:
509: protected List refreshTicksHorizontal(Graphics2D g2, Rectangle2D dataArea,
510: RectangleEdge edge) {
511:
512: Range range = getRange();
513: List ticks = new ArrayList();
514: Font tickLabelFont = getTickLabelFont();
515: g2.setFont(tickLabelFont);
516:
517: if (isAutoTickUnitSelection()) {
518: selectAutoTickUnit(g2, dataArea, edge);
519: }
520: double start = Math.floor(calculateLog(getLowerBound()));
521: double end = Math.ceil(calculateLog(getUpperBound()));
522: double current = start;
523: while (current <= end) {
524: double v = calculateValue(current);
525: if (range.contains(v)) {
526: ticks.add(new NumberTick(TickType.MAJOR, v, createTickLabel(v),
527: TextAnchor.TOP_CENTER, TextAnchor.CENTER, 0.0));
528: }
529:
530: double next = Math.pow(this.base, current
531: + this.tickUnit.getSize());
532: for (int i = 1; i < this.minorTickCount; i++) {
533: double minorV = v + i * ((next - v) / this.minorTickCount);
534: if (range.contains(minorV)) {
535: ticks.add(new NumberTick(TickType.MINOR, minorV,
536: "", TextAnchor.TOP_CENTER, TextAnchor.CENTER, 0.0));
537: }
538: }
539: current = current + this.tickUnit.getSize();
540: }
541: return ticks;
542: }
543:
544:
553: protected List refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea,
554: RectangleEdge edge) {
555:
556: Range range = getRange();
557: List ticks = new ArrayList();
558: Font tickLabelFont = getTickLabelFont();
559: g2.setFont(tickLabelFont);
560:
561: if (isAutoTickUnitSelection()) {
562: selectAutoTickUnit(g2, dataArea, edge);
563: }
564: double start = Math.floor(calculateLog(getLowerBound()));
565: double end = Math.ceil(calculateLog(getUpperBound()));
566: double current = start;
567: while (current <= end) {
568: double v = calculateValue(current);
569: if (range.contains(v)) {
570: ticks.add(new NumberTick(TickType.MINOR, v, createTickLabel(v),
571: TextAnchor.CENTER_RIGHT, TextAnchor.CENTER, 0.0));
572: }
573:
574: double next = Math.pow(this.base, current
575: + this.tickUnit.getSize());
576: for (int i = 1; i < this.minorTickCount; i++) {
577: double minorV = v + i * ((next - v) / this.minorTickCount);
578: if (range.contains(minorV)) {
579: ticks.add(new NumberTick(TickType.MINOR, minorV, "",
580: TextAnchor.CENTER_RIGHT, TextAnchor.CENTER, 0.0));
581: }
582: }
583: current = current + this.tickUnit.getSize();
584: }
585: return ticks;
586: }
587:
588:
599: protected void selectAutoTickUnit(Graphics2D g2, Rectangle2D dataArea,
600: RectangleEdge edge) {
601:
602: if (RectangleEdge.isTopOrBottom(edge)) {
603: selectHorizontalAutoTickUnit(g2, dataArea, edge);
604: }
605: else if (RectangleEdge.isLeftOrRight(edge)) {
606: selectVerticalAutoTickUnit(g2, dataArea, edge);
607: }
608:
609: }
610:
611:
622: protected void selectHorizontalAutoTickUnit(Graphics2D g2,
623: Rectangle2D dataArea, RectangleEdge edge) {
624:
625: double tickLabelWidth = estimateMaximumTickLabelWidth(g2,
626: getTickUnit());
627:
628:
629: TickUnitSource tickUnits = getStandardTickUnits();
630: TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
631: double unit1Width = exponentLengthToJava2D(unit1.getSize(), dataArea,
632: edge);
633:
634:
635: double guess = (tickLabelWidth / unit1Width) * unit1.getSize();
636:
637: NumberTickUnit unit2 = (NumberTickUnit)
638: tickUnits.getCeilingTickUnit(guess);
639: double unit2Width = exponentLengthToJava2D(unit2.getSize(), dataArea,
640: edge);
641:
642: tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
643: if (tickLabelWidth > unit2Width) {
644: unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
645: }
646:
647: setTickUnit(unit2, false, false);
648:
649: }
650:
651:
663: public double exponentLengthToJava2D(double length, Rectangle2D area,
664: RectangleEdge edge) {
665: double one = valueToJava2D(calculateValue(1.0), area, edge);
666: double l = valueToJava2D(calculateValue(length + 1.0), area, edge);
667: return Math.abs(l - one);
668: }
669:
670:
681: protected void selectVerticalAutoTickUnit(Graphics2D g2,
682: Rectangle2D dataArea,
683: RectangleEdge edge) {
684:
685: double tickLabelHeight = estimateMaximumTickLabelHeight(g2);
686:
687:
688: TickUnitSource tickUnits = getStandardTickUnits();
689: TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
690: double unitHeight = exponentLengthToJava2D(unit1.getSize(), dataArea,
691: edge);
692:
693:
694: double guess = (tickLabelHeight / unitHeight) * unit1.getSize();
695:
696: NumberTickUnit unit2 = (NumberTickUnit)
697: tickUnits.getCeilingTickUnit(guess);
698: double unit2Height = exponentLengthToJava2D(unit2.getSize(), dataArea,
699: edge);
700:
701: tickLabelHeight = estimateMaximumTickLabelHeight(g2);
702: if (tickLabelHeight > unit2Height) {
703: unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
704: }
705:
706: setTickUnit(unit2, false, false);
707:
708: }
709:
710:
719: protected double estimateMaximumTickLabelHeight(Graphics2D g2) {
720:
721: RectangleInsets tickLabelInsets = getTickLabelInsets();
722: double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom();
723:
724: Font tickLabelFont = getTickLabelFont();
725: FontRenderContext frc = g2.getFontRenderContext();
726: result += tickLabelFont.getLineMetrics("123", frc).getHeight();
727: return result;
728:
729: }
730:
731:
746: protected double estimateMaximumTickLabelWidth(Graphics2D g2,
747: TickUnit unit) {
748:
749: RectangleInsets tickLabelInsets = getTickLabelInsets();
750: double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight();
751:
752: if (isVerticalTickLabels()) {
753:
754:
755: FontRenderContext frc = g2.getFontRenderContext();
756: LineMetrics lm = getTickLabelFont().getLineMetrics("0", frc);
757: result += lm.getHeight();
758: }
759: else {
760:
761: FontMetrics fm = g2.getFontMetrics(getTickLabelFont());
762: Range range = getRange();
763: double lower = range.getLowerBound();
764: double upper = range.getUpperBound();
765: String lowerStr = "";
766: String upperStr = "";
767: NumberFormat formatter = getNumberFormatOverride();
768: if (formatter != null) {
769: lowerStr = formatter.format(lower);
770: upperStr = formatter.format(upper);
771: }
772: else {
773: lowerStr = unit.valueToString(lower);
774: upperStr = unit.valueToString(upper);
775: }
776: double w1 = fm.stringWidth(lowerStr);
777: double w2 = fm.stringWidth(upperStr);
778: result += Math.max(w1, w2);
779: }
780:
781: return result;
782:
783: }
784:
785:
791: public void zoomRange(double lowerPercent, double upperPercent) {
792: Range range = getRange();
793: double start = range.getLowerBound();
794: double end = range.getUpperBound();
795: double log1 = calculateLog(start);
796: double log2 = calculateLog(end);
797: double length = log2 - log1;
798: Range adjusted = null;
799: if (isInverted()) {
800: double logA = log1 + length * (1 - upperPercent);
801: double logB = log1 + length * (1 - lowerPercent);
802: adjusted = new Range(calculateValue(logA), calculateValue(logB));
803: }
804: else {
805: double logA = log1 + length * lowerPercent;
806: double logB = log1 + length * upperPercent;
807: adjusted = new Range(calculateValue(logA), calculateValue(logB));
808: }
809: setRange(adjusted);
810: }
811:
812:
819: private String createTickLabel(double value) {
820: if (this.numberFormatOverride != null) {
821: return this.numberFormatOverride.format(value);
822: }
823: else {
824: return this.tickUnit.valueToString(value);
825: }
826: }
827:
828:
835: public boolean equals(Object obj) {
836: if (obj == this) {
837: return true;
838: }
839: if (!(obj instanceof LogAxis)) {
840: return false;
841: }
842: LogAxis that = (LogAxis) obj;
843: if (this.base != that.base) {
844: return false;
845: }
846: if (this.smallestValue != that.smallestValue) {
847: return false;
848: }
849: if (this.minorTickCount != that.minorTickCount) {
850: return false;
851: }
852: return super.equals(obj);
853: }
854:
855:
860: public int hashCode() {
861: int result = 193;
862: long temp = Double.doubleToLongBits(this.base);
863: result = 37 * result + (int) (temp ^ (temp >>> 32));
864: result = 37 * result + this.minorTickCount;
865: temp = Double.doubleToLongBits(this.smallestValue);
866: result = 37 * result + (int) (temp ^ (temp >>> 32));
867: if (this.numberFormatOverride != null) {
868: result = 37 * result + this.numberFormatOverride.hashCode();
869: }
870: result = 37 * result + this.tickUnit.hashCode();
871: return result;
872: }
873:
874:
884: public static TickUnitSource createLogTickUnits(Locale locale) {
885:
886: TickUnits units = new TickUnits();
887:
888: NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);
889:
890: units.add(new NumberTickUnit(1, numberFormat));
891: units.add(new NumberTickUnit(2, numberFormat));
892: units.add(new NumberTickUnit(5, numberFormat));
893: units.add(new NumberTickUnit(10, numberFormat));
894: units.add(new NumberTickUnit(20, numberFormat));
895: units.add(new NumberTickUnit(50, numberFormat));
896: units.add(new NumberTickUnit(100, numberFormat));
897: units.add(new NumberTickUnit(200, numberFormat));
898: units.add(new NumberTickUnit(500, numberFormat));
899: units.add(new NumberTickUnit(1000, numberFormat));
900: units.add(new NumberTickUnit(2000, numberFormat));
901: units.add(new NumberTickUnit(5000, numberFormat));
902: units.add(new NumberTickUnit(10000, numberFormat));
903: units.add(new NumberTickUnit(20000, numberFormat));
904: units.add(new NumberTickUnit(50000, numberFormat));
905: units.add(new NumberTickUnit(100000, numberFormat));
906: units.add(new NumberTickUnit(200000, numberFormat));
907: units.add(new NumberTickUnit(500000, numberFormat));
908: units.add(new NumberTickUnit(1000000, numberFormat));
909: units.add(new NumberTickUnit(2000000, numberFormat));
910: units.add(new NumberTickUnit(5000000, numberFormat));
911: units.add(new NumberTickUnit(10000000, numberFormat));
912: units.add(new NumberTickUnit(20000000, numberFormat));
913: units.add(new NumberTickUnit(50000000, numberFormat));
914: units.add(new NumberTickUnit(100000000, numberFormat));
915: units.add(new NumberTickUnit(200000000, numberFormat));
916: units.add(new NumberTickUnit(500000000, numberFormat));
917: units.add(new NumberTickUnit(1000000000, numberFormat));
918: units.add(new NumberTickUnit(2000000000, numberFormat));
919: units.add(new NumberTickUnit(5000000000.0, numberFormat));
920: units.add(new NumberTickUnit(10000000000.0, numberFormat));
921:
922: return units;
923:
924: }
925: }