1:
78:
79: package ;
80:
81: import ;
82: import ;
83: import ;
84: import ;
85: import ;
86: import ;
87: import ;
88: import ;
89: import ;
90: import ;
91: import ;
92: import ;
93: import ;
94: import ;
95: import ;
96: import ;
97:
98: import ;
99: import ;
100: import ;
101: import ;
102: import ;
103: import ;
104: import ;
105: import ;
106: import ;
107: import ;
108:
109:
112: public class SymbolAxis extends NumberAxis implements Serializable {
113:
114:
115: private static final long serialVersionUID = 7216330468770619716L;
116:
117:
118: public static final Paint DEFAULT_GRID_BAND_PAINT
119: = new Color(232, 234, 232, 128);
120:
121:
122: private List symbols;
123:
124:
125: private transient Paint gridBandPaint;
126:
127:
128: private boolean gridBandsVisible;
129:
130:
138: public SymbolAxis(String label, String[] sv) {
139: super(label);
140: this.symbols = Arrays.asList(sv);
141: this.gridBandsVisible = true;
142: this.gridBandPaint = DEFAULT_GRID_BAND_PAINT;
143:
144: setAutoTickUnitSelection(false, false);
145: setAutoRangeStickyZero(false);
146:
147: }
148:
149:
154: public String[] getSymbols() {
155: String[] result = new String[this.symbols.size()];
156: result = (String[]) this.symbols.toArray(result);
157: return result;
158: }
159:
160:
168: public Paint getGridBandPaint() {
169: return this.gridBandPaint;
170: }
171:
172:
180: public void setGridBandPaint(Paint paint) {
181: if (paint == null) {
182: throw new IllegalArgumentException("Null 'paint' argument.");
183: }
184: this.gridBandPaint = paint;
185: notifyListeners(new AxisChangeEvent(this));
186: }
187:
188:
197: public boolean isGridBandsVisible() {
198: return this.gridBandsVisible;
199: }
200:
201:
209: public void setGridBandsVisible(boolean flag) {
210: if (this.gridBandsVisible != flag) {
211: this.gridBandsVisible = flag;
212: notifyListeners(new AxisChangeEvent(this));
213: }
214: }
215:
216:
223: protected void selectAutoTickUnit(Graphics2D g2, Rectangle2D dataArea,
224: RectangleEdge edge) {
225: throw new UnsupportedOperationException();
226: }
227:
228:
244: public AxisState draw(Graphics2D g2,
245: double cursor,
246: Rectangle2D plotArea,
247: Rectangle2D dataArea,
248: RectangleEdge edge,
249: PlotRenderingInfo plotState) {
250:
251: AxisState info = new AxisState(cursor);
252: if (isVisible()) {
253: info = super.draw(g2, cursor, plotArea, dataArea, edge, plotState);
254: }
255: if (this.gridBandsVisible) {
256: drawGridBands(g2, plotArea, dataArea, edge, info.getTicks());
257: }
258: return info;
259:
260: }
261:
262:
274: protected void drawGridBands(Graphics2D g2,
275: Rectangle2D plotArea,
276: Rectangle2D dataArea,
277: RectangleEdge edge,
278: List ticks) {
279:
280: Shape savedClip = g2.getClip();
281: g2.clip(dataArea);
282: if (RectangleEdge.isTopOrBottom(edge)) {
283: drawGridBandsHorizontal(g2, plotArea, dataArea, true, ticks);
284: }
285: else if (RectangleEdge.isLeftOrRight(edge)) {
286: drawGridBandsVertical(g2, plotArea, dataArea, true, ticks);
287: }
288: g2.setClip(savedClip);
289:
290: }
291:
292:
306: protected void drawGridBandsHorizontal(Graphics2D g2,
307: Rectangle2D plotArea,
308: Rectangle2D dataArea,
309: boolean firstGridBandIsDark,
310: List ticks) {
311:
312: boolean currentGridBandIsDark = firstGridBandIsDark;
313: double yy = dataArea.getY();
314: double xx1, xx2;
315:
316:
317: double outlineStrokeWidth;
318: if (getPlot().getOutlineStroke() != null) {
319: outlineStrokeWidth
320: = ((BasicStroke) getPlot().getOutlineStroke()).getLineWidth();
321: }
322: else {
323: outlineStrokeWidth = 1d;
324: }
325:
326: Iterator iterator = ticks.iterator();
327: ValueTick tick;
328: Rectangle2D band;
329: while (iterator.hasNext()) {
330: tick = (ValueTick) iterator.next();
331: xx1 = valueToJava2D(tick.getValue() - 0.5d, dataArea,
332: RectangleEdge.BOTTOM);
333: xx2 = valueToJava2D(tick.getValue() + 0.5d, dataArea,
334: RectangleEdge.BOTTOM);
335: if (currentGridBandIsDark) {
336: g2.setPaint(this.gridBandPaint);
337: }
338: else {
339: g2.setPaint(Color.white);
340: }
341: band = new Rectangle2D.Double(xx1, yy + outlineStrokeWidth,
342: xx2 - xx1, dataArea.getMaxY() - yy - outlineStrokeWidth);
343: g2.fill(band);
344: currentGridBandIsDark = !currentGridBandIsDark;
345: }
346: g2.setPaintMode();
347: }
348:
349:
363: protected void drawGridBandsVertical(Graphics2D g2,
364: Rectangle2D drawArea,
365: Rectangle2D plotArea,
366: boolean firstGridBandIsDark,
367: List ticks) {
368:
369: boolean currentGridBandIsDark = firstGridBandIsDark;
370: double xx = plotArea.getX();
371: double yy1, yy2;
372:
373:
374: double outlineStrokeWidth;
375: Stroke outlineStroke = getPlot().getOutlineStroke();
376: if (outlineStroke != null && outlineStroke instanceof BasicStroke) {
377: outlineStrokeWidth = ((BasicStroke) outlineStroke).getLineWidth();
378: }
379: else {
380: outlineStrokeWidth = 1d;
381: }
382:
383: Iterator iterator = ticks.iterator();
384: ValueTick tick;
385: Rectangle2D band;
386: while (iterator.hasNext()) {
387: tick = (ValueTick) iterator.next();
388: yy1 = valueToJava2D(tick.getValue() + 0.5d, plotArea,
389: RectangleEdge.LEFT);
390: yy2 = valueToJava2D(tick.getValue() - 0.5d, plotArea,
391: RectangleEdge.LEFT);
392: if (currentGridBandIsDark) {
393: g2.setPaint(this.gridBandPaint);
394: }
395: else {
396: g2.setPaint(Color.white);
397: }
398: band = new Rectangle2D.Double(xx + outlineStrokeWidth, yy1,
399: plotArea.getMaxX() - xx - outlineStrokeWidth, yy2 - yy1);
400: g2.fill(band);
401: currentGridBandIsDark = !currentGridBandIsDark;
402: }
403: g2.setPaintMode();
404: }
405:
406:
409: protected void autoAdjustRange() {
410:
411: Plot plot = getPlot();
412: if (plot == null) {
413: return;
414: }
415:
416: if (plot instanceof ValueAxisPlot) {
417:
418:
419: double upper = this.symbols.size() - 1;
420: double lower = 0;
421: double range = upper - lower;
422:
423:
424: double minRange = getAutoRangeMinimumSize();
425: if (range < minRange) {
426: upper = (upper + lower + minRange) / 2;
427: lower = (upper + lower - minRange) / 2;
428: }
429:
430:
431: double upperMargin = 0.5;
432: double lowerMargin = 0.5;
433:
434: if (getAutoRangeIncludesZero()) {
435: if (getAutoRangeStickyZero()) {
436: if (upper <= 0.0) {
437: upper = 0.0;
438: }
439: else {
440: upper = upper + upperMargin;
441: }
442: if (lower >= 0.0) {
443: lower = 0.0;
444: }
445: else {
446: lower = lower - lowerMargin;
447: }
448: }
449: else {
450: upper = Math.max(0.0, upper + upperMargin);
451: lower = Math.min(0.0, lower - lowerMargin);
452: }
453: }
454: else {
455: if (getAutoRangeStickyZero()) {
456: if (upper <= 0.0) {
457: upper = Math.min(0.0, upper + upperMargin);
458: }
459: else {
460: upper = upper + upperMargin * range;
461: }
462: if (lower >= 0.0) {
463: lower = Math.max(0.0, lower - lowerMargin);
464: }
465: else {
466: lower = lower - lowerMargin;
467: }
468: }
469: else {
470: upper = upper + upperMargin;
471: lower = lower - lowerMargin;
472: }
473: }
474:
475: setRange(new Range(lower, upper), false, false);
476:
477: }
478:
479: }
480:
481:
492: public List refreshTicks(Graphics2D g2,
493: AxisState state,
494: Rectangle2D dataArea,
495: RectangleEdge edge) {
496: List ticks = null;
497: if (RectangleEdge.isTopOrBottom(edge)) {
498: ticks = refreshTicksHorizontal(g2, dataArea, edge);
499: }
500: else if (RectangleEdge.isLeftOrRight(edge)) {
501: ticks = refreshTicksVertical(g2, dataArea, edge);
502: }
503: return ticks;
504: }
505:
506:
516: protected List refreshTicksHorizontal(Graphics2D g2,
517: Rectangle2D dataArea,
518: RectangleEdge edge) {
519:
520: List ticks = new java.util.ArrayList();
521:
522: Font tickLabelFont = getTickLabelFont();
523: g2.setFont(tickLabelFont);
524:
525: double size = getTickUnit().getSize();
526: int count = calculateVisibleTickCount();
527: double lowestTickValue = calculateLowestVisibleTickValue();
528:
529: double previousDrawnTickLabelPos = 0.0;
530: double previousDrawnTickLabelLength = 0.0;
531:
532: if (count <= ValueAxis.MAXIMUM_TICK_COUNT) {
533: for (int i = 0; i < count; i++) {
534: double currentTickValue = lowestTickValue + (i * size);
535: double xx = valueToJava2D(currentTickValue, dataArea, edge);
536: String tickLabel;
537: NumberFormat formatter = getNumberFormatOverride();
538: if (formatter != null) {
539: tickLabel = formatter.format(currentTickValue);
540: }
541: else {
542: tickLabel = valueToString(currentTickValue);
543: }
544:
545:
546: Rectangle2D bounds = TextUtilities.getTextBounds(tickLabel, g2,
547: g2.getFontMetrics());
548: double tickLabelLength = isVerticalTickLabels()
549: ? bounds.getHeight() : bounds.getWidth();
550: boolean tickLabelsOverlapping = false;
551: if (i > 0) {
552: double avgTickLabelLength = (previousDrawnTickLabelLength
553: + tickLabelLength) / 2.0;
554: if (Math.abs(xx - previousDrawnTickLabelPos)
555: < avgTickLabelLength) {
556: tickLabelsOverlapping = true;
557: }
558: }
559: if (tickLabelsOverlapping) {
560: tickLabel = "";
561: }
562: else {
563:
564: previousDrawnTickLabelPos = xx;
565: previousDrawnTickLabelLength = tickLabelLength;
566: }
567:
568: TextAnchor anchor = null;
569: TextAnchor rotationAnchor = null;
570: double angle = 0.0;
571: if (isVerticalTickLabels()) {
572: anchor = TextAnchor.CENTER_RIGHT;
573: rotationAnchor = TextAnchor.CENTER_RIGHT;
574: if (edge == RectangleEdge.TOP) {
575: angle = Math.PI / 2.0;
576: }
577: else {
578: angle = -Math.PI / 2.0;
579: }
580: }
581: else {
582: if (edge == RectangleEdge.TOP) {
583: anchor = TextAnchor.BOTTOM_CENTER;
584: rotationAnchor = TextAnchor.BOTTOM_CENTER;
585: }
586: else {
587: anchor = TextAnchor.TOP_CENTER;
588: rotationAnchor = TextAnchor.TOP_CENTER;
589: }
590: }
591: Tick tick = new NumberTick(new Double(currentTickValue),
592: tickLabel, anchor, rotationAnchor, angle);
593: ticks.add(tick);
594: }
595: }
596: return ticks;
597:
598: }
599:
600:
610: protected List refreshTicksVertical(Graphics2D g2,
611: Rectangle2D dataArea,
612: RectangleEdge edge) {
613:
614: List ticks = new java.util.ArrayList();
615:
616: Font tickLabelFont = getTickLabelFont();
617: g2.setFont(tickLabelFont);
618:
619: double size = getTickUnit().getSize();
620: int count = calculateVisibleTickCount();
621: double lowestTickValue = calculateLowestVisibleTickValue();
622:
623: double previousDrawnTickLabelPos = 0.0;
624: double previousDrawnTickLabelLength = 0.0;
625:
626: if (count <= ValueAxis.MAXIMUM_TICK_COUNT) {
627: for (int i = 0; i < count; i++) {
628: double currentTickValue = lowestTickValue + (i * size);
629: double yy = valueToJava2D(currentTickValue, dataArea, edge);
630: String tickLabel;
631: NumberFormat formatter = getNumberFormatOverride();
632: if (formatter != null) {
633: tickLabel = formatter.format(currentTickValue);
634: }
635: else {
636: tickLabel = valueToString(currentTickValue);
637: }
638:
639:
640: Rectangle2D bounds = TextUtilities.getTextBounds(tickLabel, g2,
641: g2.getFontMetrics());
642: double tickLabelLength = isVerticalTickLabels()
643: ? bounds.getWidth() : bounds.getHeight();
644: boolean tickLabelsOverlapping = false;
645: if (i > 0) {
646: double avgTickLabelLength = (previousDrawnTickLabelLength
647: + tickLabelLength) / 2.0;
648: if (Math.abs(yy - previousDrawnTickLabelPos)
649: < avgTickLabelLength) {
650: tickLabelsOverlapping = true;
651: }
652: }
653: if (tickLabelsOverlapping) {
654: tickLabel = "";
655: }
656: else {
657:
658: previousDrawnTickLabelPos = yy;
659: previousDrawnTickLabelLength = tickLabelLength;
660: }
661:
662: TextAnchor anchor = null;
663: TextAnchor rotationAnchor = null;
664: double angle = 0.0;
665: if (isVerticalTickLabels()) {
666: anchor = TextAnchor.BOTTOM_CENTER;
667: rotationAnchor = TextAnchor.BOTTOM_CENTER;
668: if (edge == RectangleEdge.LEFT) {
669: angle = -Math.PI / 2.0;
670: }
671: else {
672: angle = Math.PI / 2.0;
673: }
674: }
675: else {
676: if (edge == RectangleEdge.LEFT) {
677: anchor = TextAnchor.CENTER_RIGHT;
678: rotationAnchor = TextAnchor.CENTER_RIGHT;
679: }
680: else {
681: anchor = TextAnchor.CENTER_LEFT;
682: rotationAnchor = TextAnchor.CENTER_LEFT;
683: }
684: }
685: Tick tick = new NumberTick(new Double(currentTickValue),
686: tickLabel, anchor, rotationAnchor, angle);
687: ticks.add(tick);
688: }
689: }
690: return ticks;
691:
692: }
693:
694:
701: public String valueToString(double value) {
702: String strToReturn;
703: try {
704: strToReturn = (String) this.symbols.get((int) value);
705: }
706: catch (IndexOutOfBoundsException ex) {
707: strToReturn = "";
708: }
709: return strToReturn;
710: }
711:
712:
719: public boolean equals(Object obj) {
720: if (obj == this) {
721: return true;
722: }
723: if (!(obj instanceof SymbolAxis)) {
724: return false;
725: }
726: SymbolAxis that = (SymbolAxis) obj;
727: if (!this.symbols.equals(that.symbols)) {
728: return false;
729: }
730: if (this.gridBandsVisible != that.gridBandsVisible) {
731: return false;
732: }
733: if (!PaintUtilities.equal(this.gridBandPaint, that.gridBandPaint)) {
734: return false;
735: }
736: return super.equals(obj);
737: }
738:
739:
746: private void writeObject(ObjectOutputStream stream) throws IOException {
747: stream.defaultWriteObject();
748: SerialUtilities.writePaint(this.gridBandPaint, stream);
749: }
750:
751:
759: private void readObject(ObjectInputStream stream)
760: throws IOException, ClassNotFoundException {
761: stream.defaultReadObject();
762: this.gridBandPaint = SerialUtilities.readPaint(stream);
763: }
764:
765: }