001: /*
002: JOpenChart Java Charting Library and Toolkit
003: Copyright (C) 2001 Sebastian Müller
004: http://jopenchart.sourceforge.net
005:
006: This library is free software; you can redistribute it and/or
007: modify it under the terms of the GNU Lesser General Public
008: License as published by the Free Software Foundation; either
009: version 2.1 of the License, or (at your option) any later version.
010:
011: This library is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public
017: License along with this library; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019:
020: CoordSystem.java
021: Created on 26. Juni 2001, 22:49
022: */
023:
024: package de.progra.charting;
025:
026: import de.progra.charting.render.AbstractRenderer;
027: import de.progra.charting.render.ChartRenderingHints;
028: import java.awt.Rectangle;
029: import java.awt.geom.Point2D;
030: import java.awt.geom.Rectangle2D;
031: import java.awt.geom.Line2D;
032: import java.awt.geom.AffineTransform;
033: import java.awt.Graphics2D;
034: import java.awt.Dimension;
035: import java.awt.Color;
036: import java.awt.Font;
037: import java.awt.font.FontRenderContext;
038: import java.awt.font.TextLayout;
039: import java.text.DecimalFormat;
040: import de.progra.charting.model.ChartDataModel;
041: import de.progra.charting.model.ChartDataModelConstraints;
042:
043: /** This class defines a coordinate system. The CoordSystem class computes
044: * an AffineTransform for each y-axis, which translates the user space
045: * coordinates (ie. the data value coordinates) into pixel space coordinates.
046: * These AffineTransform classes make the PixelToPointTranslator obsolete,
047: * since it provides more flexibility. <code>getDefaultTransform</code> always
048: * computes the default transformation, whereas you can set another
049: * transformation via <code>setTransform</code>. This will be used to implement
050: * zooming and panning in the Swing classes.<p>
051: * All classes incl. this one, which render data will use the transformations
052: * to translate the coordinates. The transformations are not set up on
053: * instantiation of a CoordSystem, instead they're computed when setBounds
054: * is called, because they need this information of course. Afterwards you
055: * can set your own transformation or even better you can manipulate the
056: * existing ones by pre- or postconcatenating another AffineTransform.
057: */
058: public class CoordSystem extends AbstractRenderer {
059:
060: /** The x-axis caption string. */
061: protected String xaxis_unit = "x";
062: /** The y-axis caption string. */
063: protected String yaxis_unit = "y";
064:
065: /** The Font used in the CoordSystem. */
066: protected Font font = new Font("sans", Font.PLAIN, 9);
067:
068: /** FontRenderContext used througout the CoordSystem*/
069: protected final FontRenderContext frc = new FontRenderContext(null,
070: false, false);
071: /** DecimalFormat used throught on the Yaxis of the CoordSystem*/
072: protected DecimalFormat dfY;
073: /** DecimalFormat used throught on the Xaxis of the CoordSystem*/
074: protected DecimalFormat dfX;
075: /** if true, the arrows will be drawn at the end of the axi*/
076: protected boolean shouldDrawArrows = true;
077: /** if true, the increment will be painted at each tick mark*/
078: protected boolean shouldPaintAltTick = true;
079: /** if true only the tick will be painted on the yaxis. Alternately, if false, a
080: * light grey line will paint across the background of the chart.*/
081: protected boolean shouldPaintOnlyTick = true;
082:
083: /** If true, the labels will be painted. If false, only the ticks will display. */
084: protected boolean shouldPaintLabels = true;
085:
086: /** The left margin */
087: protected int leftmargin = 50;
088: /** The top margin. */
089: protected int topmargin = 20;
090:
091: /** The right margin. */
092: protected int rightmargin = 30;
093: /** The bottom margin. */
094: protected int bottommargin = 30;
095: /** The minimal margin constant. */
096: public final int MINIMALMARGIN = 20;
097: /** The arrow length constant. */
098: public final int ARROWLENGTH = 15;
099:
100: /** The ChartDataModel constraints of the first y-axis and the x-axis. */
101: protected ChartDataModelConstraints constraints;
102: /** The ChartDataModel constraints of the second y-axis and the x-axis. */
103: protected ChartDataModelConstraints constraints2;
104:
105: /** The DataModel class. */
106: protected ChartDataModel model;
107:
108: /** The utilities class, which contains all the rendering methods etc. */
109: protected CoordSystemUtilities c;
110:
111: /** The xaxis.*/
112: protected Axis xaxis;
113: /** The first y-axis. */
114: protected Axis yaxis;
115: /** The second y-axis. */
116: protected Axis yaxis2;
117:
118: /** The multiplication matrix for the first y-axis and the x-axis. */
119: protected AffineTransform y1transform;
120: /** The multiplication matrix for the second y-axis and the x-axis. */
121: protected AffineTransform y2transform;
122:
123: /** the axis binding constant for the first y-axis
124: */
125: public static final int FIRST_YAXIS = 0;
126: /** the axis binding constant for the second y-axis
127: */
128: public static final int SECOND_YAXIS = 1;
129:
130: /** Creates a new CoordSystem using the given model constraints.
131: * Also creates default linear x and y-axis. Note that the length
132: * of the axis are set on the first call to
133: * setBounds().
134: * @param c the ChartDataModel needed to compute the DataConstraints.
135: */
136: public CoordSystem(ChartDataModel cdm) {
137: this .constraints = cdm
138: .getChartDataModelConstraints(FIRST_YAXIS);
139: this .constraints2 = cdm
140: .getChartDataModelConstraints(SECOND_YAXIS);
141:
142: this .model = cdm;
143:
144: xaxis = new Axis(Axis.HORIZONTAL, constraints);
145: yaxis = new Axis(Axis.VERTICAL, constraints);
146:
147: c = new CoordSystemUtilities(this , constraints, constraints2,
148: model);
149:
150: dfY = new DecimalFormat();
151: dfX = new DecimalFormat();
152: }
153:
154: /** Creates a new CoordSystem using the given model constraints.
155: * Also creates default linear x and y-axis. Note that the length
156: * of the axis are set on the first call to
157: * setBounds().
158: * @param c the ChartDataModel needed to compute the DataConstraints.
159: * @param xtext the x-axis unit
160: * @param ytext the y-axis unit
161: */
162: public CoordSystem(ChartDataModel c, String xunit, String yunit) {
163: this (c);
164:
165: setXAxisUnit(xunit);
166: setYAxisUnit(yunit);
167: }
168:
169: /**
170: * Create a new CoordSystem with alternate painting parameters.
171: * @param c the ChartDataModel needed to compute the DataConstraints.
172: * @param drawArrows if true the arrows will be drawn at the end of the axis
173: * @param paintAltYTick if true the caption will paint on alternate ticks of the
174: * yaxis instead of on every one.
175: * @param paintOnlyYTick if true the horizontal lightgray line will <i>not</i>
176: * appear behind the chart at each yaxis tick mark.
177: */
178: public CoordSystem(ChartDataModel c, DecimalFormat yAxisFormat,
179: boolean drawArrows, boolean paintAltYTick,
180: boolean paintOnlyYTick) {
181: this (c);
182: dfY = yAxisFormat;
183: shouldDrawArrows = drawArrows;
184: shouldPaintAltTick = paintAltYTick;
185: shouldPaintOnlyTick = paintOnlyYTick;
186: }
187:
188: /** Sets the coordinate transformation for any y-coordinate.
189: * @param at the AffineTransform that transforms the coordinates into pixel
190: * space
191: * @axis defines for which y-axis the transform is computed
192: */
193: public void setTransform(AffineTransform at, int axis) {
194: switch (axis) {
195: case (FIRST_YAXIS):
196: y1transform = at;
197: break;
198: case (SECOND_YAXIS):
199: y2transform = at;
200: break;
201: }
202: }
203:
204: /** Returns the currently defined AffineTransform for any y-axis.
205: * @param axis the y-axis to be used.
206: */
207: public AffineTransform getTransform(int axis) {
208: switch (axis) {
209: case (FIRST_YAXIS):
210: return y1transform;
211: case (SECOND_YAXIS):
212: return y2transform;
213: }
214:
215: return null;
216: }
217:
218: /** This method computes the default transform which transforms the
219: * user space coordinates of this coordinate system to the pixel
220: * space coordinates used in the Graphics object.
221: * All rendering in the CoordinateSystem and the ChartRenderers
222: * will rely on this transform.
223: * @param axis defines which y-axis to use.
224: */
225: public AffineTransform getDefaultTransform(int axis) {
226: double x_pt2px = 0;
227: double y_pt2px = 0;
228: double xcoord0 = 0;
229: double ycoord0 = 0;
230:
231: x_pt2px = 1 / getXAxis().getPointToPixelRatio();
232: //System.out.println("** x_pt2px = "+getXAxis().getPointToPixelRatio());
233: xcoord0 = getBounds().getX() + getLeftMargin()
234: + getXAxis().getPixelForValue(0.0);
235:
236: switch (axis) {
237: case FIRST_YAXIS:
238: y_pt2px = 1 / getFirstYAxis().getPointToPixelRatio();
239: ycoord0 = getBounds().getY() + getBounds().getHeight()
240: - getBottomMargin()
241: - getFirstYAxis().getPixelForValue(0.0);
242: break;
243: case SECOND_YAXIS:
244: y_pt2px = 1 / getSecondYAxis().getPointToPixelRatio();
245: ycoord0 = getBounds().getY() + getBounds().getHeight()
246: - getBottomMargin()
247: - getSecondYAxis().getPixelForValue(0.0);
248: break;
249: }
250: return new AffineTransform(x_pt2px, 0f, 0f, -y_pt2px, xcoord0,
251: ycoord0);
252: }
253:
254: /** Sets the x-axis.
255: * @param a the x-axis
256: */
257: public void setXAxis(Axis a) {
258: xaxis = a;
259: }
260:
261: /** Returns the x axis.
262: * @return the x-axis
263: */
264: public Axis getXAxis() {
265: return xaxis;
266: }
267:
268: /** Sets the x-axis unit string.
269: * @param xtext the unit string
270: */
271: public void setXAxisUnit(String xunit) {
272: this .xaxis_unit = xunit;
273: }
274:
275: /** Gets the x-axis unit string.
276: * @return the label String
277: */
278: public String getXAxisUnit() {
279: return xaxis_unit;
280: }
281:
282: /** Sets the y-axis unit string.
283: * @param ytext the unit string
284: */
285: public void setYAxisUnit(String yunit) {
286: this .yaxis_unit = yunit;
287: }
288:
289: /** Gets the y-axis label.
290: * @return the label String
291: */
292: public String getYAxisUnit() {
293: return yaxis_unit;
294: }
295:
296: /** Sets the font for the axis labels.
297: * @param f the Font to be used
298: */
299: public void setFont(Font f) {
300: font = f;
301: }
302:
303: /** Returns the font used for the axis labels.
304: * @return the Font object
305: */
306: public Font getFont() {
307: return font;
308: }
309:
310: /** Sets the left y-axis and computes the matrix transformation.
311: * @param a the left y-axis
312: */
313: public void setFirstYAxis(Axis a) {
314: yaxis = a;
315: }
316:
317: /** Returns the first y-axis.
318: * @return the left y-axis
319: */
320: public Axis getFirstYAxis() {
321: return yaxis;
322: }
323:
324: /** Sets the second y-axis and computes the matrix transformation.
325: * @param a the right y-axis
326: */
327: public void setSecondYAxis(Axis a) {
328: yaxis2 = a;
329: }
330:
331: /** Returns the second y-axis.
332: * @return the right y-axis
333: */
334: public Axis getSecondYAxis() {
335: return yaxis2;
336: }
337:
338: /** Returns the inner margin, ie the bounds minus the margins.
339: * @return a Rectangle object defining the inner bounds.
340: */
341: public Rectangle getInnerBounds() {
342: Rectangle b = getBounds();
343: Rectangle i = new Rectangle((int) b.getX() + getLeftMargin()
344: - 1, (int) b.getY() + getTopMargin() - 1, (int) b
345: .getWidth()
346: - (getLeftMargin() + getRightMargin()) + 2, (int) b
347: .getHeight()
348: - (getTopMargin() + getBottomMargin()) + 2);
349: return i;
350: }
351:
352: /** Computes all margins, initializes the length of the Axis and
353: * calls <code>super.setBounds</code>. Additionally, it sets the
354: * default AffineTransforms for every y-axis.
355: * @param bounds <CODE>Rectangle</CODE> object defining the bounds
356: */
357: public void setBounds(Rectangle bounds) {
358: super .setBounds(bounds);
359:
360: setRightMargin(c.computeRightMargin());
361: setLeftMargin(c.computeLeftMargin());
362:
363: setTopMargin(c.computeTopMargin());
364: setBottomMargin(c.computeBottomMargin());
365:
366: xaxis.setLength((int) (bounds.getWidth()) - getLeftMargin()
367: - getRightMargin());
368: //System.out.println("** xaxis.length = "+xaxis.getLength());
369: yaxis.setLength((int) (bounds.getHeight()) - getTopMargin()
370: - getBottomMargin());
371: //System.out.println("** yaxis.length = "+yaxis.getLength());
372: setTransform(getDefaultTransform(FIRST_YAXIS), FIRST_YAXIS);
373: if (yaxis2 != null) {
374: yaxis2.setLength((int) (bounds.getHeight())
375: - getTopMargin() - getBottomMargin());
376: setTransform(getDefaultTransform(SECOND_YAXIS),
377: SECOND_YAXIS);
378: }
379: }
380:
381: /** Returns the preferred size needed for the renderer.
382: * @return a Dimension with the minimum Integer values.
383: */
384: public Dimension getPreferredSize() {
385: return new Dimension(Integer.MIN_VALUE, Integer.MIN_VALUE);
386: }
387:
388: /** Overrides the method to just call <code>paintDefault</code>.
389: * @param g the <CODE>Graphics2D</CODE> object to paint in
390: */
391: public void render(Graphics2D g) {
392: paintDefault(g);
393: }
394:
395: /** This method is called by the paint method to do the actual painting.
396: * The painting is supposed to start at point (0,0) and the size is
397: * always the same as the preferred size. The paint method performs
398: * the possible scaling.
399: * @param g the <CODE>Graphics2D</CODE> object to paint in
400: */
401: public void paintDefault(Graphics2D g) {
402: g.setColor(Color.black);
403:
404: Line2D x = c.getXAxisLine2D();
405: Line2D y = c.getYAxisLine2D();
406:
407: g.draw(x);
408: g.draw(y);
409:
410: // draw X-Axis Arrow
411: if (shouldDrawArrows) {
412: g.drawLine((int) x.getX2(), (int) x.getY2(), (int) x
413: .getX2()
414: + ARROWLENGTH, (int) x.getY2());
415: g.fillPolygon(new int[] {
416: (int) (x.getX2() + ARROWLENGTH / 3.0),
417: (int) (x.getX2() + ARROWLENGTH / 3.0),
418: (int) (x.getX2() + ARROWLENGTH) }, new int[] {
419: (int) x.getY2() - 3, (int) x.getY2() + 3,
420: (int) x.getY2() }, 3);
421: }
422:
423: // draw X-Axis label right below the Arrow ?!
424: g.setColor(Color.black);
425: TextLayout layoutX = new TextLayout(getXAxisUnit(), getFont(),
426: new FontRenderContext(null, true, false));
427: layoutX.draw(g, (float) x.getX2() + (float) ARROWLENGTH / 3,
428: (float) x.getY2()
429: + (float) layoutX.getBounds().getHeight() + 5);
430:
431: // draw Y-Axis Arrow
432: if (shouldDrawArrows) {
433: g.drawLine((int) y.getX1(), (int) y.getY1(), (int) y
434: .getX1(), (int) y.getY1() - ARROWLENGTH);
435: g.fillPolygon(new int[] { (int) (y.getX1() - 3),
436: (int) (y.getX1() + 3), (int) (y.getX1()) },
437: new int[] { (int) (y.getY1() - ARROWLENGTH / 3.0),
438: (int) (y.getY1() - ARROWLENGTH / 3.0),
439: (int) y.getY1() - ARROWLENGTH }, 3);
440: }
441:
442: // draw Y-Axis label right below the Arrow ?!
443: g.setColor(Color.black);
444: TextLayout layoutY = new TextLayout(getYAxisUnit(), getFont(),
445: new FontRenderContext(null, true, false));
446: layoutY.draw(g, (float) y.getX1() - 6
447: - (float) layoutY.getBounds().getWidth(), (float) y
448: .getY1()
449: - layoutX.getDescent() - 3);
450:
451: if (getSecondYAxis() != null) {
452: Line2D y2 = c.getSecondYAxisLine2D();
453: g.draw(y2);
454: }
455:
456: if (model.isColumnNumeric())
457: c.drawNumericalXAxisTicks(g);
458: else
459: c.drawXAxisTicks(g);
460:
461: c.drawYAxisTicks(g);
462: }
463:
464: /** Returns a new PointToPixelTranslator for the given axis.
465: * Please notice that this method is deprecated since release 0.92.
466: * The PointToPixelTranslator interface has been replaced with
467: * AffineTransforms.
468: * @param y the y-axis identifier used to choose the right Point / Pixel ratio
469: * @return a PointToPixelTranslator object or null if the resulting
470: * Point is not within the Bounds of the Coordinate System
471: * @deprecated
472: */
473: public PointToPixelTranslator getPointToPixelTranslator(int yaxis) {
474: final Axis x = this .getXAxis();
475: final Axis y;
476: if (yaxis == CoordSystem.FIRST_YAXIS)
477: y = this .getFirstYAxis();
478: else
479: y = this .getSecondYAxis();
480:
481: return new PointToPixelTranslator() {
482: public Point2D getPixelCoord(Point2D pt) {
483: double x0 = 0.0;
484: double y0 = 0.0;
485:
486: x0 = getBounds().getX() + getLeftMargin()
487: + x.getPixelForValue(pt.getX());
488:
489: y0 = getBounds().getY() + getBounds().getHeight()
490: - getBottomMargin()
491: - y.getPixelForValue(pt.getY());
492: Point2D p = new Point2D.Double(x0, y0);
493:
494: if (getInnerBounds().contains(p))
495: return p;
496: else
497: return null;
498: }
499: };
500: }
501:
502: /** Returns the left margin. */
503: protected int getLeftMargin() {
504: return leftmargin;
505: }
506:
507: /** Returns the right margin. */
508: protected int getRightMargin() {
509: return rightmargin;
510: }
511:
512: /** Returns the top margin. */
513: protected int getTopMargin() {
514: return topmargin;
515: }
516:
517: /** Returns the bottom margin. */
518: protected int getBottomMargin() {
519: return bottommargin;
520: }
521:
522: /** Sets the left margin.
523: * @param margin the new margin value
524: */
525: protected void setLeftMargin(int margin) {
526: leftmargin = margin;
527: }
528:
529: /** Sets the right margin.
530: * @param margin the new margin value
531: */
532: protected void setRightMargin(int margin) {
533: rightmargin = margin;
534: }
535:
536: /** Sets the top margin.
537: * @param margin the new margin value
538: */
539: protected void setTopMargin(int margin) {
540: topmargin = margin;
541: }
542:
543: /** Sets the bottom margin.
544: * @param margin the new margin value
545: */
546: public void setBottomMargin(int margin) {
547: bottommargin = margin;
548: }
549:
550: /** Returns the FontRenderContext used througout the CoordSystem*/
551: public FontRenderContext getFontRenderContext() {
552: return frc;
553: }
554:
555: /** Returns the DecimalFormat used throught on the Yaxis of the CoordSystem*/
556: public DecimalFormat getYDecimalFormat() {
557: return dfY;
558: }
559:
560: /** Returns the DecimalFormat used throught on the Xaxis of the CoordSystem*/
561: public DecimalFormat getXDecimalFormat() {
562: return dfX;
563: }
564:
565: /** if true, the arrows will be drawn at the end of the axis*/
566: public boolean isDrawArrows() {
567: return shouldDrawArrows;
568: }
569:
570: /** if true, the increment will be painted at each tick mark*/
571: public boolean isPaintAltTick() {
572: return shouldPaintAltTick;
573: }
574:
575: /** if true only the tick will be painted on the yaxis. Alternately a
576: * light grey line will paint across the background of the chart.*/
577: public boolean isPaintOnlyTick() {
578: return shouldPaintOnlyTick;
579: }
580:
581: public boolean isPaintLabels() {
582: return shouldPaintLabels;
583: }
584:
585: public void setPaintLabels(boolean label) {
586: shouldPaintLabels = label;
587: }
588:
589: /** Returns the used ChartDataModelConstraints. */
590: public ChartDataModelConstraints getChartDataModelConstraints(
591: int axis) {
592: if (axis == FIRST_YAXIS)
593: return constraints;
594: else if (axis == SECOND_YAXIS)
595: return constraints2;
596: else
597: return null;
598: }
599: }
|