1:
51:
52: package ;
53:
54: import ;
55: import ;
56: import ;
57: import ;
58: import ;
59: import ;
60: import ;
61: import ;
62: import ;
63: import ;
64: import ;
65:
66: import ;
67: import ;
68: import ;
69: import ;
70: import ;
71: import ;
72: import ;
73: import ;
74:
75:
81: public class LogAxis extends ValueAxis {
82:
83:
84: private double base = 10.0;
85:
86:
87: private double baseLog = Math.log(10.0);
88:
89:
90: private double smallestValue = 1E-100;
91:
92:
93: private NumberTickUnit tickUnit;
94:
95:
96: private NumberFormat numberFormatOverride;
97:
98:
99: private int minorTickCount;
100:
101:
104: public LogAxis() {
105: this(null);
106: }
107:
108:
113: public LogAxis(String label) {
114: super(label, createLogTickUnits(Locale.getDefault()));
115: setDefaultAutoRange(new Range(0.01, 1.0));
116: this.tickUnit = new NumberTickUnit(1.0, new DecimalFormat("0.#"));
117: this.minorTickCount = 9;
118: }
119:
120:
127: public double getBase() {
128: return this.base;
129: }
130:
131:
139: public void setBase(double base) {
140: if (base <= 1.0) {
141: throw new IllegalArgumentException("Requires 'base' > 1.0.");
142: }
143: this.base = base;
144: this.baseLog = Math.log(base);
145: notifyListeners(new AxisChangeEvent(this));
146: }
147:
148:
155: public double getSmallestValue() {
156: return this.smallestValue;
157: }
158:
159:
167: public void setSmallestValue(double value) {
168: if (value <= 0.0) {
169: throw new IllegalArgumentException("Requires 'value' > 0.0.");
170: }
171: this.smallestValue = value;
172: notifyListeners(new AxisChangeEvent(this));
173: }
174:
175:
182: public NumberTickUnit getTickUnit() {
183: return this.tickUnit;
184: }
185:
186:
197: public void setTickUnit(NumberTickUnit unit) {
198:
199: setTickUnit(unit, true, true);
200: }
201:
202:
215: public void setTickUnit(NumberTickUnit unit, boolean notify,
216: boolean turnOffAutoSelect) {
217:
218: if (unit == null) {
219: throw new IllegalArgumentException("Null 'unit' argument.");
220: }
221: this.tickUnit = unit;
222: if (turnOffAutoSelect) {
223: setAutoTickUnitSelection(false, false);
224: }
225: if (notify) {
226: notifyListeners(new AxisChangeEvent(this));
227: }
228:
229: }
230:
231:
239: public NumberFormat getNumberFormatOverride() {
240: return this.numberFormatOverride;
241: }
242:
243:
251: public void setNumberFormatOverride(NumberFormat formatter) {
252: this.numberFormatOverride = formatter;
253: notifyListeners(new AxisChangeEvent(this));
254: }
255:
256:
263: public int getMinorTickCount() {
264: return this.minorTickCount;
265: }
266:
267:
275: public void setMinorTickCount(int count) {
276: if (count <= 0) {
277: throw new IllegalArgumentException("Requires 'count' > 0.");
278: }
279: this.minorTickCount = count;
280: notifyListeners(new AxisChangeEvent(this));
281: }
282:
283:
293: public double calculateLog(double value) {
294: return Math.log(value) / this.baseLog;
295: }
296:
297:
307: public double calculateValue(double log) {
308: return Math.pow(this.base, log);
309: }
310:
311:
321: public double java2DToValue(double java2DValue, Rectangle2D area,
322: RectangleEdge edge) {
323:
324: Range range = getRange();
325: double axisMin = calculateLog(range.getLowerBound());
326: double axisMax = calculateLog(range.getUpperBound());
327:
328: double min = 0.0;
329: double max = 0.0;
330: if (RectangleEdge.isTopOrBottom(edge)) {
331: min = area.getX();
332: max = area.getMaxX();
333: }
334: else if (RectangleEdge.isLeftOrRight(edge)) {
335: min = area.getMaxY();
336: max = area.getY();
337: }
338: double log = 0.0;
339: if (isInverted()) {
340: log = axisMax - (java2DValue - min) / (max - min)
341: * (axisMax - axisMin);
342: }
343: else {
344: log = axisMin + (java2DValue - min) / (max - min)
345: * (axisMax - axisMin);
346: }
347: return calculateValue(log);
348: }
349:
350:
361: public double valueToJava2D(double value, Rectangle2D area,
362: RectangleEdge edge) {
363:
364: Range range = getRange();
365: double axisMin = calculateLog(range.getLowerBound());
366: double axisMax = calculateLog(range.getUpperBound());
367: value = calculateLog(value);
368:
369: double min = 0.0;
370: double max = 0.0;
371: if (RectangleEdge.isTopOrBottom(edge)) {
372: min = area.getX();
373: max = area.getMaxX();
374: }
375: else if (RectangleEdge.isLeftOrRight(edge)) {
376: max = area.getMinY();
377: min = area.getMaxY();
378: }
379: if (isInverted()) {
380: return max
381: - ((value - axisMin) / (axisMax - axisMin)) * (max - min);
382: }
383: else {
384: return min
385: + ((value - axisMin) / (axisMax - axisMin)) * (max - min);
386: }
387: }
388:
389:
393: public void configure() {
394: if (isAutoRange()) {
395: autoAdjustRange();
396: }
397: }
398:
399:
403: protected void autoAdjustRange() {
404: Plot plot = getPlot();
405: if (plot == null) {
406: return;
407: }
408:
409: if (plot instanceof ValueAxisPlot) {
410: ValueAxisPlot vap = (ValueAxisPlot) plot;
411:
412: Range r = vap.getDataRange(this);
413: if (r == null) {
414: r = getDefaultAutoRange();
415: }
416:
417: double upper = r.getUpperBound();
418: double lower = Math.max(r.getLowerBound(), this.smallestValue);
419: double range = upper - lower;
420:
421:
422: double fixedAutoRange = getFixedAutoRange();
423: if (fixedAutoRange > 0.0) {
424: lower = Math.max(upper - fixedAutoRange, this.smallestValue);
425: }
426: else {
427:
428: double minRange = getAutoRangeMinimumSize();
429: if (range < minRange) {
430: double expand = (minRange - range) / 2;
431: upper = upper + expand;
432: lower = lower - expand;
433: }
434:
435:
436: double logUpper = calculateLog(upper);
437: double logLower = calculateLog(lower);
438: double logRange = logUpper - logLower;
439: logUpper = logUpper + getUpperMargin() * logRange;
440: logLower = logLower - getLowerMargin() * logRange;
441: upper = calculateValue(logUpper);
442: lower = calculateValue(logLower);
443: }
444:
445: setRange(new Range(lower, upper), false, false);
446: }
447:
448: }
449:
450:
464: public AxisState draw(Graphics2D g2, double cursor, Rectangle2D plotArea,
465: Rectangle2D dataArea, RectangleEdge edge,
466: PlotRenderingInfo plotState) {
467:
468: AxisState state = null;
469:
470: if (!isVisible()) {
471: state = new AxisState(cursor);
472:
473:
474: List ticks = refreshTicks(g2, state, dataArea, edge);
475: state.setTicks(ticks);
476: return state;
477: }
478: state = drawTickMarksAndLabels(g2, cursor, plotArea, dataArea, edge);
479: state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state);
480: return state;
481: }
482:
483:
495: public List refreshTicks(Graphics2D g2, AxisState state,
496: Rectangle2D dataArea, RectangleEdge edge) {
497:
498: List result = new java.util.ArrayList();
499: if (RectangleEdge.isTopOrBottom(edge)) {
500: result = refreshTicksHorizontal(g2, dataArea, edge);
501: }
502: else if (RectangleEdge.isLeftOrRight(edge)) {
503: result = refreshTicksVertical(g2, dataArea, edge);
504: }
505: return result;
506:
507: }
508:
509:
518: protected List refreshTicksHorizontal(Graphics2D g2, Rectangle2D dataArea,
519: RectangleEdge edge) {
520:
521: Range range = getRange();
522: List ticks = new ArrayList();
523: Font tickLabelFont = getTickLabelFont();
524: g2.setFont(tickLabelFont);
525: TextAnchor textAnchor;
526: if (edge == RectangleEdge.TOP) {
527: textAnchor = TextAnchor.BOTTOM_CENTER;
528: }
529: else {
530: textAnchor = TextAnchor.TOP_CENTER;
531: }
532:
533: if (isAutoTickUnitSelection()) {
534: selectAutoTickUnit(g2, dataArea, edge);
535: }
536: double start = Math.floor(calculateLog(getLowerBound()));
537: double end = Math.ceil(calculateLog(getUpperBound()));
538: double current = start;
539: while (current <= end) {
540: double v = calculateValue(current);
541: if (range.contains(v)) {
542: ticks.add(new NumberTick(TickType.MAJOR, v, createTickLabel(v),
543: textAnchor, TextAnchor.CENTER, 0.0));
544: }
545:
546: double next = Math.pow(this.base, current
547: + this.tickUnit.getSize());
548: for (int i = 1; i < this.minorTickCount; i++) {
549: double minorV = v + i * ((next - v) / this.minorTickCount);
550: if (range.contains(minorV)) {
551: ticks.add(new NumberTick(TickType.MINOR, minorV, "",
552: textAnchor, TextAnchor.CENTER, 0.0));
553: }
554: }
555: current = current + this.tickUnit.getSize();
556: }
557: return ticks;
558: }
559:
560:
569: protected List refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea,
570: RectangleEdge edge) {
571:
572: Range range = getRange();
573: List ticks = new ArrayList();
574: Font tickLabelFont = getTickLabelFont();
575: g2.setFont(tickLabelFont);
576: TextAnchor textAnchor;
577: if (edge == RectangleEdge.RIGHT) {
578: textAnchor = TextAnchor.CENTER_LEFT;
579: }
580: else {
581: textAnchor = TextAnchor.CENTER_RIGHT;
582: }
583:
584: if (isAutoTickUnitSelection()) {
585: selectAutoTickUnit(g2, dataArea, edge);
586: }
587: double start = Math.floor(calculateLog(getLowerBound()));
588: double end = Math.ceil(calculateLog(getUpperBound()));
589: double current = start;
590: while (current <= end) {
591: double v = calculateValue(current);
592: if (range.contains(v)) {
593: ticks.add(new NumberTick(TickType.MAJOR, v, createTickLabel(v),
594: textAnchor, TextAnchor.CENTER, 0.0));
595: }
596:
597: double next = Math.pow(this.base, current
598: + this.tickUnit.getSize());
599: for (int i = 1; i < this.minorTickCount; i++) {
600: double minorV = v + i * ((next - v) / this.minorTickCount);
601: if (range.contains(minorV)) {
602: ticks.add(new NumberTick(TickType.MINOR, minorV, "",
603: textAnchor, TextAnchor.CENTER, 0.0));
604: }
605: }
606: current = current + this.tickUnit.getSize();
607: }
608: return ticks;
609: }
610:
611:
622: protected void selectAutoTickUnit(Graphics2D g2, Rectangle2D dataArea,
623: RectangleEdge edge) {
624:
625: if (RectangleEdge.isTopOrBottom(edge)) {
626: selectHorizontalAutoTickUnit(g2, dataArea, edge);
627: }
628: else if (RectangleEdge.isLeftOrRight(edge)) {
629: selectVerticalAutoTickUnit(g2, dataArea, edge);
630: }
631:
632: }
633:
634:
645: protected void selectHorizontalAutoTickUnit(Graphics2D g2,
646: Rectangle2D dataArea, RectangleEdge edge) {
647:
648: double tickLabelWidth = estimateMaximumTickLabelWidth(g2,
649: getTickUnit());
650:
651:
652: TickUnitSource tickUnits = getStandardTickUnits();
653: TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
654: double unit1Width = exponentLengthToJava2D(unit1.getSize(), dataArea,
655: edge);
656:
657:
658: double guess = (tickLabelWidth / unit1Width) * unit1.getSize();
659:
660: NumberTickUnit unit2 = (NumberTickUnit)
661: tickUnits.getCeilingTickUnit(guess);
662: double unit2Width = exponentLengthToJava2D(unit2.getSize(), dataArea,
663: edge);
664:
665: tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
666: if (tickLabelWidth > unit2Width) {
667: unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
668: }
669:
670: setTickUnit(unit2, false, false);
671:
672: }
673:
674:
686: public double exponentLengthToJava2D(double length, Rectangle2D area,
687: RectangleEdge edge) {
688: double one = valueToJava2D(calculateValue(1.0), area, edge);
689: double l = valueToJava2D(calculateValue(length + 1.0), area, edge);
690: return Math.abs(l - one);
691: }
692:
693:
704: protected void selectVerticalAutoTickUnit(Graphics2D g2,
705: Rectangle2D dataArea,
706: RectangleEdge edge) {
707:
708: double tickLabelHeight = estimateMaximumTickLabelHeight(g2);
709:
710:
711: TickUnitSource tickUnits = getStandardTickUnits();
712: TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
713: double unitHeight = exponentLengthToJava2D(unit1.getSize(), dataArea,
714: edge);
715:
716:
717: double guess = (tickLabelHeight / unitHeight) * unit1.getSize();
718:
719: NumberTickUnit unit2 = (NumberTickUnit)
720: tickUnits.getCeilingTickUnit(guess);
721: double unit2Height = exponentLengthToJava2D(unit2.getSize(), dataArea,
722: edge);
723:
724: tickLabelHeight = estimateMaximumTickLabelHeight(g2);
725: if (tickLabelHeight > unit2Height) {
726: unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
727: }
728:
729: setTickUnit(unit2, false, false);
730:
731: }
732:
733:
742: protected double estimateMaximumTickLabelHeight(Graphics2D g2) {
743:
744: RectangleInsets tickLabelInsets = getTickLabelInsets();
745: double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom();
746:
747: Font tickLabelFont = getTickLabelFont();
748: FontRenderContext frc = g2.getFontRenderContext();
749: result += tickLabelFont.getLineMetrics("123", frc).getHeight();
750: return result;
751:
752: }
753:
754:
769: protected double estimateMaximumTickLabelWidth(Graphics2D g2,
770: TickUnit unit) {
771:
772: RectangleInsets tickLabelInsets = getTickLabelInsets();
773: double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight();
774:
775: if (isVerticalTickLabels()) {
776:
777:
778: FontRenderContext frc = g2.getFontRenderContext();
779: LineMetrics lm = getTickLabelFont().getLineMetrics("0", frc);
780: result += lm.getHeight();
781: }
782: else {
783:
784: FontMetrics fm = g2.getFontMetrics(getTickLabelFont());
785: Range range = getRange();
786: double lower = range.getLowerBound();
787: double upper = range.getUpperBound();
788: String lowerStr = "";
789: String upperStr = "";
790: NumberFormat formatter = getNumberFormatOverride();
791: if (formatter != null) {
792: lowerStr = formatter.format(lower);
793: upperStr = formatter.format(upper);
794: }
795: else {
796: lowerStr = unit.valueToString(lower);
797: upperStr = unit.valueToString(upper);
798: }
799: double w1 = fm.stringWidth(lowerStr);
800: double w2 = fm.stringWidth(upperStr);
801: result += Math.max(w1, w2);
802: }
803:
804: return result;
805:
806: }
807:
808:
814: public void zoomRange(double lowerPercent, double upperPercent) {
815: Range range = getRange();
816: double start = range.getLowerBound();
817: double end = range.getUpperBound();
818: double log1 = calculateLog(start);
819: double log2 = calculateLog(end);
820: double length = log2 - log1;
821: Range adjusted = null;
822: if (isInverted()) {
823: double logA = log1 + length * (1 - upperPercent);
824: double logB = log1 + length * (1 - lowerPercent);
825: adjusted = new Range(calculateValue(logA), calculateValue(logB));
826: }
827: else {
828: double logA = log1 + length * lowerPercent;
829: double logB = log1 + length * upperPercent;
830: adjusted = new Range(calculateValue(logA), calculateValue(logB));
831: }
832: setRange(adjusted);
833: }
834:
835:
845: protected String createTickLabel(double value) {
846: if (this.numberFormatOverride != null) {
847: return this.numberFormatOverride.format(value);
848: }
849: else {
850: return this.tickUnit.valueToString(value);
851: }
852: }
853:
854:
861: public boolean equals(Object obj) {
862: if (obj == this) {
863: return true;
864: }
865: if (!(obj instanceof LogAxis)) {
866: return false;
867: }
868: LogAxis that = (LogAxis) obj;
869: if (this.base != that.base) {
870: return false;
871: }
872: if (this.smallestValue != that.smallestValue) {
873: return false;
874: }
875: if (this.minorTickCount != that.minorTickCount) {
876: return false;
877: }
878: return super.equals(obj);
879: }
880:
881:
886: public int hashCode() {
887: int result = 193;
888: long temp = Double.doubleToLongBits(this.base);
889: result = 37 * result + (int) (temp ^ (temp >>> 32));
890: result = 37 * result + this.minorTickCount;
891: temp = Double.doubleToLongBits(this.smallestValue);
892: result = 37 * result + (int) (temp ^ (temp >>> 32));
893: if (this.numberFormatOverride != null) {
894: result = 37 * result + this.numberFormatOverride.hashCode();
895: }
896: result = 37 * result + this.tickUnit.hashCode();
897: return result;
898: }
899:
900:
910: public static TickUnitSource createLogTickUnits(Locale locale) {
911:
912: TickUnits units = new TickUnits();
913:
914: NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);
915:
916: units.add(new NumberTickUnit(1, numberFormat));
917: units.add(new NumberTickUnit(2, numberFormat));
918: units.add(new NumberTickUnit(5, numberFormat));
919: units.add(new NumberTickUnit(10, numberFormat));
920: units.add(new NumberTickUnit(20, numberFormat));
921: units.add(new NumberTickUnit(50, numberFormat));
922: units.add(new NumberTickUnit(100, numberFormat));
923: units.add(new NumberTickUnit(200, numberFormat));
924: units.add(new NumberTickUnit(500, numberFormat));
925: units.add(new NumberTickUnit(1000, numberFormat));
926: units.add(new NumberTickUnit(2000, numberFormat));
927: units.add(new NumberTickUnit(5000, numberFormat));
928: units.add(new NumberTickUnit(10000, numberFormat));
929: units.add(new NumberTickUnit(20000, numberFormat));
930: units.add(new NumberTickUnit(50000, numberFormat));
931: units.add(new NumberTickUnit(100000, numberFormat));
932: units.add(new NumberTickUnit(200000, numberFormat));
933: units.add(new NumberTickUnit(500000, numberFormat));
934: units.add(new NumberTickUnit(1000000, numberFormat));
935: units.add(new NumberTickUnit(2000000, numberFormat));
936: units.add(new NumberTickUnit(5000000, numberFormat));
937: units.add(new NumberTickUnit(10000000, numberFormat));
938: units.add(new NumberTickUnit(20000000, numberFormat));
939: units.add(new NumberTickUnit(50000000, numberFormat));
940: units.add(new NumberTickUnit(100000000, numberFormat));
941: units.add(new NumberTickUnit(200000000, numberFormat));
942: units.add(new NumberTickUnit(500000000, numberFormat));
943: units.add(new NumberTickUnit(1000000000, numberFormat));
944: units.add(new NumberTickUnit(2000000000, numberFormat));
945: units.add(new NumberTickUnit(5000000000.0, numberFormat));
946: units.add(new NumberTickUnit(10000000000.0, numberFormat));
947:
948: return units;
949:
950: }
951: }