0001: package com.xoetrope.swing;
0002:
0003: import java.awt.BasicStroke;
0004: import java.awt.BorderLayout;
0005: import java.awt.geom.Arc2D;
0006: import java.util.Vector;
0007:
0008: import java.awt.Color;
0009: import java.awt.Dimension;
0010: import java.awt.Font;
0011: import java.awt.FontMetrics;
0012: import java.awt.GradientPaint;
0013: import java.awt.Graphics;
0014: import java.awt.Graphics2D;
0015: import java.awt.Image;
0016: import java.awt.Point;
0017: import java.awt.RenderingHints;
0018: import java.awt.Stroke;
0019: import java.awt.event.ActionEvent;
0020: import java.awt.event.ActionListener;
0021: import java.awt.event.MouseEvent;
0022: import java.awt.event.MouseMotionListener;
0023: import java.awt.font.TextAttribute;
0024: import java.awt.geom.AffineTransform;
0025: import java.awt.geom.Area;
0026: import java.awt.geom.Ellipse2D;
0027: import java.awt.geom.GeneralPath;
0028: import java.awt.geom.Rectangle2D;
0029: import java.text.AttributedString;
0030: import java.text.DecimalFormat;
0031: import java.util.ArrayList;
0032: import java.util.Enumeration;
0033: import java.util.Hashtable;
0034: import javax.swing.JComponent;
0035: import javax.swing.JLayeredPane;
0036: import javax.swing.Timer;
0037: import net.xoetrope.xui.XAttributedComponent;
0038: import net.xoetrope.xui.XModelHolder;
0039: import net.xoetrope.xui.XProject;
0040:
0041: import net.xoetrope.xui.data.XModel;
0042: import net.xoetrope.xui.helper.XTranslator;
0043: import net.xoetrope.xui.XProjectManager;
0044:
0045: /**
0046: * @todo render the entire chart (other than the background) as an image and
0047: * then draw it as an alpha composite for smooter animation. For scaling the
0048: * same couple be applicaed with a scale transformation, instead of redrawing
0049: * the entire image each time.
0050: */
0051:
0052: /**
0053: * An component that displays data as charts/graphs. The display can be Pie,
0054: * Scatter, Line, X-Y or Bar charts
0055: *
0056: * <p> Copyright (c) Xoetrope Ltd., 2001-2006, This software is licensed under
0057: * the GNU Public License (GPL), please see license.txt for more details. If
0058: * you make commercial use of this software you must purchase a commercial
0059: * license from Xoetrope.</p>
0060: * <p> $Revision: 1.29 $</p>
0061: */
0062: public class XGraph extends JComponent implements ActionListener,
0063: XAttributedComponent, XModelHolder {
0064: /**
0065: * Bar chart - with one bar per value in each series
0066: */
0067: public final static int BARCHART = 0;
0068:
0069: /**
0070: * Bar chart with the bars for each value in the series stacked upon one another so that there is one bar for each x value
0071: */
0072: public final static int STACKEDBARCHART = 1;
0073:
0074: /**
0075: * A simple line chart with connected points
0076: */
0077: public final static int LINECHART = 2;
0078:
0079: /**
0080: * A pie chart showing the first series
0081: */
0082: public final static int PIECHART = 3;
0083:
0084: /**
0085: * A plot with points for each x-y value pair
0086: */
0087: public final static int SCATTERCHART = 4;
0088:
0089: /**
0090: * Linear axis
0091: */
0092: public static final int LINEAR = 0;
0093:
0094: /**
0095: * A Log10 axis
0096: */
0097: public static final int LOGARITHMIC = 1;
0098:
0099: /**
0100: * The series column/field in the source data table
0101: */
0102: public static final int SERIES = 0;
0103:
0104: /**
0105: * The labels column/field in the source data table
0106: */
0107: public static final int LABELS = 1;
0108:
0109: /**
0110: * The values column/field in the source data table
0111: */
0112: public static final int VALUES = 2;
0113:
0114: /**
0115: * The owner project and the context in which this object operates.
0116: */
0117: protected XProject currentProject = XProjectManager
0118: .getCurrentProject();
0119:
0120: //----------------------------------------------------------------------------
0121: private FontMetrics fm;
0122: private String title, xTitle, yTitle;
0123: private int xAxisType, yAxisType;
0124:
0125: private double top;
0126: private double bottom;
0127: private double left;
0128: private double right;
0129: private double titleHeight;
0130: private double labelWidth, labelHeight;
0131: private double padding;
0132: private double minX;
0133: private double maxX;
0134: private double minY;
0135: private double maxY;
0136: private double intervalX, intervalY;
0137: private double minYAllowable;
0138:
0139: private int numSeries, newSeries;
0140: private double[] points, newPoints;
0141: private double[] pairTotals;
0142: private String[] seriesNames;
0143:
0144: private Timer animationTimer;
0145: private double animationScale = 1.0;
0146: private double animationScaleInc = 0.1;
0147: private JComponent chartPanel;
0148: private JLayeredPane layeredPane;
0149: private DecimalFormat xFormatter, yFormatter;
0150: private boolean animateYAxis = true/*false*/;
0151:
0152: private Image backgroundImage;
0153:
0154: private double min;
0155: private double max;
0156: private int numCols;
0157: private int numRecords;
0158: private int currentViewId;
0159: private XModel model;
0160: private int mode = 0;
0161:
0162: private double position = 0;
0163: private double increment = 2;
0164: private Vector cols;
0165:
0166: private boolean drawBorder;
0167: private boolean drawPoints;
0168: private boolean labelPoints;
0169: private int legendPos;
0170:
0171: private double legendWidth;
0172: private double legendHeight;
0173: private double scale;
0174:
0175: private String maxSeriesName;
0176: private String maxXLabelName;
0177: private String maxYLabelName;
0178:
0179: /**
0180: * An aray of areas that can be highlighted and selected
0181: */
0182: private ArrayList selectableAreas;
0183: private SelectableArea highlightedArea;
0184: private boolean highlightSelection;
0185:
0186: /**
0187: * The labels for the data-axis
0188: */
0189: private String[] xLabels;
0190:
0191: /**
0192: * The offset of the model field contain the labels if any
0193: */
0194: private int labelFields;
0195:
0196: /**
0197: * The offset of the model field containing the series values
0198: */
0199: private int seriesFields;
0200:
0201: /**
0202: * The offset of the field containing the value.
0203: */
0204: private int valueFields;
0205:
0206: //----------------------------------------------------------------------------
0207:
0208: /**
0209: * Construct a new graph/chart
0210: */
0211: public XGraph() {
0212: padding = 10;
0213: minYAllowable = Double.NEGATIVE_INFINITY;
0214: drawBorder = false;
0215: drawPoints = false;
0216: labelPoints = false;
0217: labelFields = -1;
0218: seriesFields = -1;
0219: valueFields = 1;
0220:
0221: legendPos = 0;
0222: legendWidth = 130;
0223: legendHeight = 85;
0224: animationTimer = new Timer(1, this );
0225: animationTimer.setCoalesce(true);
0226: animationTimer.setRepeats(true);
0227:
0228: layeredPane = new JLayeredPane();
0229: chartPanel = new ChartPanel();
0230: chartPanel.setOpaque(false);
0231: layeredPane.add(chartPanel, new Integer(0));
0232:
0233: setLayout(new BorderLayout());
0234: add(layeredPane, BorderLayout.CENTER);
0235: layeredPane.setVisible(true);
0236: xFormatter = new DecimalFormat();
0237: yFormatter = new DecimalFormat();
0238: yFormatter.setMaximumFractionDigits(0);
0239:
0240: selectableAreas = new ArrayList();
0241: highlightSelection = false;
0242: }
0243:
0244: /**
0245: * Define the location of the data in the model
0246: * @param attribute the attribute being defined (LABELS, SERIES, VALUES)
0247: * @param value the column of field where that data can be found
0248: */
0249: public void setModelStructure(int attribute, int value) {
0250: if (attribute == LABELS)
0251: labelFields = value;
0252: else if (attribute == SERIES)
0253: seriesFields = value;
0254: else if (attribute == VALUES)
0255: valueFields = value;
0256: }
0257:
0258: /**
0259: * Get the selectable area
0260: * @return the selected area or null if there is no selection
0261: */
0262: public SelectableArea getSelectableArea() {
0263: return highlightedArea;
0264: }
0265:
0266: /**
0267: * An action handler for the animation timer
0268: * @param e the event
0269: */
0270: public void actionPerformed(ActionEvent e) {
0271: chartPanel.repaint();
0272: if (animationScale > 0.0)
0273: animationScale -= animationScaleInc;
0274: }
0275:
0276: /**
0277: * Set the bounds for this component and layout the children
0278: * @param x the x coordinate
0279: * @param y the y coordinate
0280: * @param w the width
0281: * @param h the height
0282: */
0283: public void setBounds(int x, int y, int w, int h) {
0284: super .setBounds(x, y, w, h);
0285: chartPanel.setBounds(0, 0, w, h);
0286: }
0287:
0288: /**
0289: * Set the XScale options
0290: * @param min the min value
0291: * @param max the max value
0292: * @param type the type of axis
0293: * @param title the axis title
0294: * @param labels the labels for the markers in the series
0295: */
0296: public void setXScale(double min, double max, int type,
0297: String title, String[] labels) {
0298: minX = min;
0299: maxX = max;
0300: xTitle = title;
0301: xAxisType = type;
0302: xLabels = labels;
0303: }
0304:
0305: /**
0306: * Set the XScale options
0307: * @param type the type of axis
0308: * @param title the axis title
0309: */
0310: public void setYScale(int type, String title) {
0311: yTitle = title;
0312: yAxisType = type;
0313: }
0314:
0315: /**
0316: * Force a repaint
0317: */
0318: public void update() {
0319: setModel(model);
0320: }
0321:
0322: /**
0323: * Set the XModel which we will be generating the table from
0324: * @param xmodel The XModel of data
0325: */
0326: public void setModel(XModel xmodel) {
0327: model = xmodel;
0328: XTranslator translator = currentProject.getTranslator();
0329: if (model != null) {
0330: model.get();
0331: int numRows = model.getNumChildren();
0332:
0333: // Lookup the labels
0334: int idx = 0;
0335: Hashtable labels = new Hashtable();
0336: Hashtable series = new Hashtable();
0337: int labelCount = 0;
0338: int seriesCount = 0;
0339: for (int i = 0; i < numRows; i++) {
0340: XModel rowModel = (XModel) model.get(i);
0341: if (labelFields >= 0) {
0342: String labelValue = (String) rowModel
0343: .getAttribValue(labelFields);
0344: labelValue = translator.translate(labelValue);
0345: if (labels.get(labelValue) == null)
0346: labels.put(labelValue,
0347: new Integer(labelCount++));
0348: }
0349: if (seriesFields >= 0) {
0350: String seriesValue = (String) rowModel
0351: .getAttribValue(seriesFields);
0352: seriesValue = translator.translate(seriesValue);
0353: if (series.get(seriesValue) == null)
0354: series.put(seriesValue, new Integer(
0355: seriesCount++));
0356: }
0357: }
0358: xLabels = labelCount > 0 ? new String[labelCount] : null;
0359: Enumeration labelEnum = labels.keys();
0360: while (labelEnum.hasMoreElements()) {
0361: String text = (String) labelEnum.nextElement();
0362: xLabels[((Integer) labels.get(text)).intValue()] = text;
0363: }
0364:
0365: if (seriesCount > 0)
0366: numSeries = seriesCount;
0367: else
0368: numSeries = model.getNumAttributes() - 1
0369: - (labelCount > 0 ? 1 : 0);
0370:
0371: String names[] = new String[numSeries];
0372: Enumeration seriesEnum = series.keys();
0373: while (seriesEnum.hasMoreElements()) {
0374: String text = (String) seriesEnum.nextElement();
0375: names[((Integer) series.get(text)).intValue()] = text;
0376: }
0377:
0378: int seriesPts = (seriesCount > 0) ? (numSeries * 2 * labelCount)
0379: : (numRows * 2 * numSeries);
0380: double pts[] = new double[seriesPts];
0381: for (int i = 0; i < seriesPts; i++)
0382: pts[i] = Double.NaN;
0383:
0384: int seriesIdx, xIdx;
0385: String fieldValue;
0386: if (seriesFields >= 0) {
0387: // The series names/identifiers are contained in a singel column
0388: for (int i = 0; i < numRows; i++) {
0389: XModel rowModel = (XModel) model.get(i);
0390:
0391: fieldValue = (String) rowModel
0392: .getAttribValue(seriesFields);
0393: fieldValue = translator.translate(fieldValue);
0394: seriesIdx = ((Integer) series.get(fieldValue))
0395: .intValue();
0396:
0397: fieldValue = (String) rowModel
0398: .getAttribValue(labelFields);
0399: fieldValue = translator.translate(fieldValue);
0400: xIdx = (labelFields >= 0 ? ((Integer) labels
0401: .get(fieldValue)).intValue()
0402: : (i % numSeries));
0403:
0404: String valueStr = (String) rowModel
0405: .getAttribValue(valueFields);
0406:
0407: pts[(xIdx * 2) + (seriesIdx * labelCount * 2)] = (labelFields >= 0 ? xIdx
0408: : Double.parseDouble(fieldValue));
0409: pts[(xIdx * 2) + (seriesIdx * labelCount * 2) + 1] = Double
0410: .parseDouble(valueStr);
0411: }
0412: } else {
0413: // The series are contained in individual fields
0414: for (int j = 0; j < numSeries; j++) {
0415: names[j] = model.getAttribName(j + 1);
0416: for (int i = 0; i < numRows; i++) {
0417: XModel rowModel = (XModel) model.get(i);
0418:
0419: String s = rowModel.getAttribName(0);
0420: if (labelCount > 0)
0421: pts[idx++] = s.length() > 0 ? Double
0422: .parseDouble(s) : Double.NaN;
0423: else
0424: pts[idx++] = i;
0425: s = (String) rowModel.getAttribValue(rowModel
0426: .getAttribute(names[j]));
0427: pts[idx++] = s.length() > 0 ? Double
0428: .parseDouble(s) : Double.NaN;
0429: }
0430: }
0431: }
0432: setData(pts, numSeries, names, true);
0433: }
0434: }
0435:
0436: /**
0437: * Set the chart data
0438: * @param nSeries the number of series
0439: * @param pts the data points in x, y pairs, a complete series at a time e.g. ax1, ay1, ax2, ay2, .... xn, yn, bx1, by1, bx2, by2
0440: * @param names the names of the series
0441: * @param animate true to animate new data
0442: */
0443: public void setData(double[] pts, int nSeries, String[] names,
0444: boolean animate) {
0445: backgroundImage = null;
0446: boolean showNow = !animate || (newPoints == null);
0447: newSeries = nSeries;
0448: newPoints = pts;
0449: seriesNames = names;
0450: if (showNow)
0451: showNewData();
0452: else {
0453: animationScale = 0.1;
0454: animationScaleInc = -0.1;
0455: animationTimer.start();
0456: }
0457: }
0458:
0459: /**
0460: * Turn on/off the animation of new data
0461: */
0462: public void toggleYAxisAnimation() {
0463: animateYAxis = !animateYAxis;
0464: }
0465:
0466: private void showNewData() {
0467: minX = Double.MAX_VALUE;
0468: maxX = Double.MIN_VALUE;
0469: minY = Double.MAX_VALUE;
0470: maxY = Double.MIN_VALUE;
0471:
0472: numSeries = newSeries;
0473:
0474: int numPoints = newPoints.length;
0475: int numPairs = newPoints.length / (numSeries * 2);
0476: if (mode == STACKEDBARCHART)
0477: pairTotals = new double[numPairs];
0478:
0479: for (int i = 0; i < numPoints; i += 2) {
0480: double xValue = newPoints[i];
0481: double yValue = newPoints[i + 1];
0482: if (Double.isNaN(xValue) || Double.isNaN(yValue))
0483: continue;
0484: minX = Math.min(minX, xValue);
0485: maxX = Math.max(maxX, xValue);
0486: if (yValue > 0.0) {
0487: minY = Math.min(minY, yValue);
0488: maxY = Math.max(maxY, yValue);
0489: }
0490: if (mode == STACKEDBARCHART) {
0491: int idx = ((i / 2) % numPairs);
0492: pairTotals[idx] += yValue;
0493: maxY = Math.max(maxY, pairTotals[idx]);
0494: }
0495: }
0496:
0497: // X Interval --------------------------------------------------------------
0498: double range = maxX - minX;
0499: if (xLabels == null) {
0500: intervalX = 1000.0;
0501: if (range < intervalX) {
0502: intervalX = 100.0;
0503: if (range < intervalX)
0504: intervalX = 10.0;
0505: if (range < intervalX)
0506: intervalX = 1.0;
0507: if (range < intervalX)
0508: intervalX = 0.1;
0509: }
0510: range += intervalX;
0511: range += range % intervalX;
0512:
0513: if (range < 1.0) {
0514: xFormatter.setMinimumFractionDigits(2);
0515: xFormatter.setMaximumFractionDigits(2);
0516: } else if (range < 10.0) {
0517: xFormatter.setMinimumFractionDigits(1);
0518: xFormatter.setMaximumFractionDigits(1);
0519: } else {
0520: xFormatter.setMinimumFractionDigits(0);
0521: xFormatter.setMaximumFractionDigits(0);
0522: }
0523:
0524: minX -= Math.abs(minX) % intervalX;
0525: maxX = maxX + intervalX - Math.abs(maxX % intervalX);
0526: } else {
0527: intervalX = range / xLabels.length;
0528: maxX++;
0529: }
0530: // -------------------------------------------------------------------------
0531:
0532: // Y Interval --------------------------------------------------------------
0533: range = maxY - minY;
0534: intervalY = 1000.0;
0535: if (range < intervalY) {
0536: intervalY = 100.0;
0537: if (range < intervalY)
0538: intervalY = 10.0;
0539: if (range < intervalY)
0540: intervalY = 1.0;
0541: if (range < intervalY)
0542: intervalY = 0.1;
0543: }
0544: range += intervalY;
0545: range -= range % intervalY;
0546: if (range < 1.0) {
0547: yFormatter.setMinimumFractionDigits(2);
0548: yFormatter.setMaximumFractionDigits(2);
0549: } else if (range < 10.0) {
0550: yFormatter.setMinimumFractionDigits(1);
0551: yFormatter.setMaximumFractionDigits(1);
0552: } else {
0553: yFormatter.setMinimumFractionDigits(0);
0554: yFormatter.setMaximumFractionDigits(0);
0555: }
0556: minY -= Math.abs(minY) % intervalY;
0557: maxY = maxY + intervalY - Math.abs(maxY % intervalY);
0558: // -------------------------------------------------------------------------
0559:
0560: points = (double[]) newPoints.clone();
0561:
0562: // - Size the legens and axis labels ---------------------------------------
0563: maxSeriesName = "SERIES FOO";
0564: if (seriesNames != null) {
0565: for (int i = 0; i < numSeries; i++) {
0566: if (seriesNames[i].length() > maxSeriesName.length())
0567: maxSeriesName = seriesNames[i];
0568: }
0569: } else
0570: legendWidth = 130;
0571: // -------------------------------------------------------------------------
0572: maxXLabelName = xFormatter.format(maxX);
0573: if (xLabels != null) {
0574: for (int i = 0; i < xLabels.length; i++)
0575: if (xLabels[i].length() > maxXLabelName.length())
0576: maxXLabelName = xLabels[i];
0577: }
0578: maxYLabelName = yFormatter.format(maxY);
0579:
0580: animationScale = 1.0;
0581: animationScaleInc = 0.1;
0582: animationTimer.start();
0583: }
0584:
0585: /**
0586: * Setup the envelope component for printing
0587: * @param g the graphics context
0588: */
0589: public void print(Graphics g) {
0590: animationScale = 0.0;
0591: super .print(g);
0592: }
0593:
0594: /**
0595: * Renders the chart image.
0596: * @param g the graphics context
0597: */
0598: public void paintComponent(Graphics g) {
0599: Dimension size = getSize();
0600: int width = size.width;
0601: int height = size.height;
0602:
0603: if ((backgroundImage == null) && (maxSeriesName != null)) {
0604: Graphics2D g2d = (Graphics2D) g;
0605: backgroundImage = createImage(width, height);
0606: if (backgroundImage != null)
0607: g2d = (Graphics2D) backgroundImage.getGraphics();
0608:
0609: g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
0610: RenderingHints.VALUE_RENDER_QUALITY);
0611:
0612: g2d.setColor(getBackground());
0613: g2d.fillRect(0, 0, getSize().width, getSize().height);
0614: g2d.setColor(getForeground());
0615:
0616: // draw the title
0617: if (fm == null)
0618: fm = getFontMetrics(getFont());
0619:
0620: if (maxSeriesName == null)
0621: legendWidth = 100;
0622: else
0623: legendWidth = 40.0
0624: + padding
0625: + fm.getStringBounds(maxSeriesName, g)
0626: .getWidth();
0627: legendHeight = padding + numSeries * fm.getHeight();
0628:
0629: labelHeight = fm.getHeight();
0630: labelWidth = Math.max(fm
0631: .stringWidth(new Integer((int) maxX).toString()),
0632: fm.stringWidth(new Integer((int) maxY).toString())) + 2;
0633: titleHeight = labelHeight * 2;
0634: top = padding + titleHeight;
0635: bottom = getSize().height - padding - getXLabelHeight(g)
0636: - getLegendSpace(true);
0637: if (xTitle != null)
0638: bottom -= padding + labelHeight;
0639: left = padding + labelWidth;
0640: if (yTitle != null)
0641: left += padding + labelWidth;
0642: right = getSize().width - padding - getLegendSpace(false);
0643:
0644: Font fnt = g.getFont();
0645: Font tFont = new Font("Helv", Font.BOLD, 24);
0646: FontMetrics fmt = getFontMetrics(tFont);
0647: g2d.setFont(tFont);
0648: if ((title != null) && (title.length() > 0)) {
0649: drawString(g2d, title,
0650: (width - fmt.stringWidth(title)) / 2, top);
0651: top += padding;
0652: }
0653: g2d.setFont(fnt);
0654:
0655: switch (mode) {
0656: case PIECHART:
0657: break;
0658:
0659: case LINECHART:
0660: default:
0661: drawScale(g2d, false);
0662: drawScale(g2d, true);
0663: break;
0664: }
0665:
0666: drawLegend(g2d, getLegendX(left, right), getLegendY(top,
0667: bottom), (legendPos == 5 ? right - left
0668: : legendWidth),
0669: (legendPos == 5 ? getLegendSpace(true)
0670: : legendHeight));
0671:
0672: if (backgroundImage != null)
0673: g2d.dispose();
0674: }
0675:
0676: if (backgroundImage != null)
0677: g.drawImage(backgroundImage, 0, 0, width, height, null);
0678:
0679: if (drawBorder) {
0680: g.setColor(Color.gray);
0681: g.drawRect(0, 0, getSize().width - 1, getSize().height - 1);
0682: }
0683: }
0684:
0685: private double scaleX(double x) {
0686: return left + (((x - minX) * (right - left)) / (maxX - minX));
0687: }
0688:
0689: private double scaleY(double y) {
0690: return Math
0691: .min(
0692: bottom,
0693: bottom
0694: - ((scale * (y - minY) * (bottom - top)) / (maxY - minY)));
0695: }
0696:
0697: /**
0698: * Draw a scale
0699: * @param g2d the graphics context
0700: * @param isVertical true to draw a vertical axis, otherwise the horizontal axis is drawn
0701: */
0702: public void drawScale(Graphics2D g2d, boolean isVertical) {
0703: DecimalFormat formatter = isVertical ? yFormatter : xFormatter;
0704:
0705: AffineTransform identityTransform = g2d.getTransform();
0706: Color frgd = getForeground();
0707:
0708: //double rightExtent = right - ( legendPos == 5 ? getLegendSpace( true ) : legendHeight );
0709: double min = isVertical ? minY : minX;
0710: double max = isVertical ? maxY : maxX;
0711: double count = (int) ((max - min) / (isVertical ? intervalY
0712: : intervalX));
0713: if (!isVertical && (xLabels != null))
0714: count = xLabels.length;
0715: double separation = (max - min) / count;
0716: double xLabelSpace = getXLabelHeight(g2d);
0717: double deltaY = (bottom - top) / count;
0718: double deltaX = (right - left) / count;
0719:
0720: g2d.setColor(frgd);
0721: g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
0722: RenderingHints.VALUE_ANTIALIAS_OFF);
0723: g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
0724: RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
0725: AffineTransform at = ((AffineTransform) identityTransform
0726: .clone());
0727: if (isVertical) {
0728: at.translate(
0729: left
0730: - padding
0731: / 2
0732: - fm.stringWidth(formatter
0733: .format(new Double(max))), top);
0734: } else {
0735: at.translate(
0736: left - ((xLabels == null) ? 0.0 : xLabelSpace),
0737: bottom + xLabelSpace);
0738: at.rotate(-Math.PI / 4);
0739: }
0740:
0741: float xInc = (float) (isVertical ? 0.0 : deltaX);
0742: float yInc = (float) (isVertical ? deltaY : 0.0);
0743: g2d.setTransform(at);
0744: if (isVertical || (xLabels == null))
0745: g2d.drawString(formatter.format(new Double(isVertical ? max
0746: : min)), 0.0F, 0.0F);
0747: for (int i = 0; i < count; i++) {
0748: if (isVertical) {
0749: at.translate(xInc, yInc);
0750: } else {
0751: at.rotate(Math.PI / 4);
0752: at.translate(xInc, yInc);
0753: at.rotate(-Math.PI / 4);
0754: }
0755:
0756: g2d.setTransform(at);
0757: double labelValue;
0758: if (i == count)
0759: labelValue = isVertical ? min : max;
0760: else if (isVertical)
0761: labelValue = (max - (i + 1) * separation);
0762: else
0763: labelValue = (min + (i + 1) * separation);
0764:
0765: String label = "";
0766: if (!isVertical && (xLabels != null))
0767: label = xLabels[i];
0768: else
0769: label = formatter.format(new Double(labelValue));
0770: g2d.drawString(label,
0771: isVertical ? 0.0F : (float) (xLabelSpace
0772: - fm.stringWidth(label) + 0.6 * fm
0773: .getHeight()), 0.0F);
0774: }
0775:
0776: g2d.setTransform(identityTransform);
0777:
0778: // draw the vertical and horizontal lines
0779: if (isVertical)
0780: g2d.drawLine((int) left, (int) top, (int) left,
0781: (int) bottom);
0782: else
0783: g2d.drawLine((int) left, (int) bottom, (int) right,
0784: (int) bottom);
0785:
0786: g2d.setColor(frgd);
0787: g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
0788: RenderingHints.VALUE_ANTIALIAS_ON);
0789: g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
0790: RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
0791: g2d.setColor(Color.lightGray);
0792: Stroke oldStroke = g2d.getStroke();
0793: g2d.setStroke(new BasicStroke(0.5F, BasicStroke.CAP_ROUND,
0794: BasicStroke.JOIN_ROUND, 2.0F, new float[] { 2.0F, 2.0F,
0795: 4.0F, 2.0F }, 0.0F));
0796: for (int i = 0; i <= count; i++) {
0797: if (isVertical)
0798: g2d.drawLine((int) left, (int) (top + deltaY * i),
0799: (int) right, (int) (top + deltaY * i));
0800: else if (i == count)
0801: g2d.drawLine((int) (right - 1), (int) top,
0802: (int) (right - 1), (int) bottom);
0803: else
0804: g2d.drawLine((int) (left + (i + 1) * deltaX),
0805: (int) top, (int) (left + (i + 1) * deltaX),
0806: (int) bottom);
0807: }
0808: g2d.setColor(frgd);
0809: g2d.setStroke(oldStroke);
0810:
0811: // Draw the labels
0812: g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
0813: RenderingHints.VALUE_ANTIALIAS_OFF);
0814: g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
0815: RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
0816:
0817: if ((isVertical) && (yTitle != null)) {
0818: AttributedString as = new AttributedString(yTitle);
0819: as.addAttribute(TextAttribute.FAMILY, "arial");
0820: as.addAttribute(TextAttribute.SIZE, new Float(
0821: (float) (10.0F)));
0822: as.addAttribute(TextAttribute.FOREGROUND, frgd);
0823:
0824: at = ((AffineTransform) identityTransform.clone());
0825: at.translate(labelHeight + padding, top
0826: + (bottom - top + fm.stringWidth(yTitle)) / 2);
0827: at.rotate(-Math.PI / 2);
0828: g2d.setTransform(at);
0829:
0830: g2d.drawString(as.getIterator(), 0.0F, 0.0F);
0831: } else if (xTitle != null) {
0832: AttributedString as = new AttributedString(xTitle);
0833: as.addAttribute(TextAttribute.FAMILY, "arial");
0834: as.addAttribute(TextAttribute.SIZE, new Float(
0835: (float) (10.0F)));
0836: as.addAttribute(TextAttribute.FOREGROUND, frgd);
0837: g2d.setTransform(identityTransform);
0838: g2d.drawString(as.getIterator(), (float) (left + (right
0839: - left - fm.stringWidth(xTitle)) / 2),
0840: (float) (bottom + xLabelSpace + padding + fm
0841: .getHeight()));
0842: }
0843: g2d.setTransform(identityTransform);
0844: }
0845:
0846: /**
0847: * Draw a simple bar chart
0848: * @param g2d the graphics context
0849: */
0850: public void drawBarChart(Graphics2D g2d) {
0851: g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
0852: RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
0853: g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
0854: RenderingHints.VALUE_ANTIALIAS_ON);
0855:
0856: int numPairs = points.length / (numSeries * 2);
0857: double increment = (scaleX(maxX) - scaleX(minX))
0858: / (2.0 * numPairs * numSeries);
0859: for (int i = 0; i < numSeries; i++) {
0860: Color seriesColor = getColor(i);
0861: g2d.setColor(seriesColor);
0862:
0863: double seriesInc = i * increment * 2.0;
0864: double yStart = points[i * numPairs * 2 + 1];
0865: yStart = scaleY(yStart);
0866: boolean curveStarted = false;
0867: for (int j = 0; j < numPairs; j++) {
0868: int offset = i * numPairs * 2 + j * 2;
0869:
0870: double xValue = points[offset];
0871: double yValue = points[offset + 1];
0872:
0873: double xScaled = scaleX(xValue);
0874: double yScaled = scaleY(yValue);
0875:
0876: if (!Double.isNaN(yValue) && (yValue > minYAllowable)) {
0877: double deltaX = 0.0;
0878: if (xLabels != null)
0879: deltaX = -increment;
0880:
0881: fill3DRect(g2d, xScaled + deltaX + seriesInc,
0882: yScaled, 2 * increment, bottom - yScaled,
0883: true);
0884: if (scale > 0.99)
0885: selectableAreas.add(new SelectableArea(
0886: xLabels[j], seriesNames[i], yValue,
0887: new Area(
0888: new Rectangle2D.Double(xScaled
0889: + deltaX + seriesInc,
0890: yScaled, 2 * increment,
0891: bottom - yScaled)),
0892: seriesColor));
0893: }
0894:
0895: if (labelPoints) {
0896: String valueStr = yFormatter.format(yValue);
0897: drawString(g2d, valueStr, xScaled
0898: - fm.stringWidth(valueStr), yScaled + 4);
0899: }
0900: }
0901: }
0902: }
0903:
0904: /**
0905: * Draw a stacked bar chart
0906: * @param g2d the graphics context
0907: */
0908: public void drawStackedBarChart(Graphics2D g2d) {
0909: g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
0910: RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
0911: g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
0912: RenderingHints.VALUE_ANTIALIAS_ON);
0913:
0914: int numPairs = points.length / (numSeries * 2);
0915: double increment = (scaleX(maxX) - scaleX(minX))
0916: / (2.0 * numPairs);
0917: for (int j = 0; j < numPairs; j++) {
0918: double yStart = 0.0;
0919: boolean curveStarted = false;
0920: for (int i = 0; i < numSeries; i++) {
0921: int offset = i * numPairs * 2 + j * 2;
0922: double xValue = points[offset];
0923: double yValue = points[offset + 1];
0924: if (Double.isNaN(yValue) || (yValue < minYAllowable))
0925: continue;
0926:
0927: Color seriesColor = getGeneratedColor(i);
0928: g2d.setColor(seriesColor);
0929: double xScaled = scaleX(xValue);
0930: double yScaled = bottom - scaleY(yValue);
0931:
0932: fill3DRect(g2d, xScaled + 2.0, bottom - yStart
0933: - yScaled, 2.0 * increment - 4.0, yScaled, true);
0934:
0935: if (scale > 0.99)
0936: selectableAreas.add(new SelectableArea(xLabels[j],
0937: seriesNames[i], yValue, new Area(
0938: new Rectangle2D.Double(
0939: xScaled + 2.0, bottom
0940: - yStart - yScaled,
0941: 2.0 * increment - 4.0,
0942: yScaled)), seriesColor));
0943:
0944: if (labelPoints) {
0945: String valueStr = yFormatter.format(yValue);
0946: drawString(g2d, valueStr, xScaled
0947: - fm.stringWidth(valueStr), yScaled + 4);
0948: }
0949: yStart += yScaled;
0950: }
0951: }
0952: }
0953:
0954: /**
0955: * Draw a line chart
0956: * @param g2d the graphics context
0957: */
0958: public void drawLineChart(Graphics2D g2d) {
0959: g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
0960: RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
0961: g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
0962: RenderingHints.VALUE_ANTIALIAS_ON);
0963:
0964: int numPairs = points.length / (numSeries * 2);
0965: double previousX = 0.0;
0966: double previousY = 0.0;
0967: for (int i = 0; i < numSeries; i++) {
0968: Color seriesColor = getColor(i);
0969: g2d.setColor(seriesColor);
0970:
0971: double yStart = points[i * numPairs * 2 + 1];
0972: yStart = scaleY(yStart);
0973: GeneralPath gp = new GeneralPath();
0974: gp.moveTo((float) left, (float) yStart);
0975: boolean curveStarted = false;
0976: for (int j = 0; j < numPairs; j++) {
0977: int offset = i * numPairs * 2 + j * 2;
0978:
0979: double xValue = points[offset];
0980: double yValue = points[offset + 1];
0981:
0982: double xScaled = scaleX(xValue);
0983: double yScaled = scaleY(yValue);
0984:
0985: if (curveStarted) {
0986: if (!Double.isNaN(yValue)
0987: && (yValue > minYAllowable))
0988: gp.lineTo((float) xScaled, (float) yScaled);
0989: } else {
0990: previousY = yScaled;
0991: gp.moveTo((float) xScaled, (float) yScaled);
0992: if (!Double.isNaN(yValue)
0993: && (yValue > minYAllowable))
0994: curveStarted = true;
0995: }
0996: previousX = xScaled;
0997:
0998: if (labelPoints) {
0999: String valueStr = yFormatter.format(yValue);
1000: drawString(g2d, valueStr, xScaled
1001: - fm.stringWidth(valueStr), yScaled + 4);
1002: }
1003: if (drawPoints)
1004: fillOval(g2d, xScaled - 2, yScaled - 2, 4, 4);
1005: if ((scale > 0.99) && !Double.isNaN(yValue)
1006: && (yValue > minYAllowable)) {
1007: selectableAreas.add(new SelectableArea(xLabels[j],
1008: seriesNames[i], yValue, new Area(
1009: new Ellipse2D.Double(xScaled - 3,
1010: yScaled - 3, 6, 6)),
1011: seriesColor));
1012: previousY = yScaled;
1013: }
1014: }
1015: if (mode == LINECHART)
1016: g2d.draw(gp);
1017: }
1018: }
1019:
1020: /**
1021: * A helper method to take care of type conversions
1022: */
1023: private void drawLine(Graphics2D g2d, double x, double y, double w,
1024: double h) {
1025: g2d.drawLine((int) x, (int) y, (int) w, (int) h);
1026: }
1027:
1028: /**
1029: * A helper method to take care of type conversions
1030: */
1031: private void fillRect(Graphics2D g2d, double x, double y, double w,
1032: double h) {
1033: g2d.fillRect((int) x, (int) y, (int) w, (int) h);
1034: }
1035:
1036: /**
1037: * A helper method to take care of type conversions
1038: */
1039: private void fill3DRect(Graphics2D g2d, double x, double y,
1040: double w, double h, boolean raised) {
1041: g2d.fill3DRect((int) (x + 0.5), (int) (y + 0.5),
1042: (int) (w + 0.5), (int) (h + 0.5), raised);
1043: }
1044:
1045: /**
1046: * A helper method to take care of type conversions
1047: */
1048: private void drawRect(Graphics2D g2d, double x, double y, double w,
1049: double h) {
1050: g2d.drawRect((int) x, (int) y, (int) w, (int) h);
1051: }
1052:
1053: /**
1054: * A helper method to take care of type conversions
1055: */
1056: private void fillOval(Graphics2D g2d, double x, double y,
1057: double r1, double r2) {
1058: g2d.fillOval((int) x, (int) y, (int) r1, (int) r2);
1059: }
1060:
1061: /**
1062: * A helper method to take care of type conversions
1063: */
1064: private void drawString(Graphics2D g2d, String s, double x, double y) {
1065: g2d.drawString(s, (int) x, (int) y);
1066: }
1067:
1068: /**
1069: * A helper method to take care of type conversions
1070: */
1071: private void fillArc(Graphics2D g2d, double arcX, double arcY,
1072: double sizeX, double sizeY, double startAngle,
1073: double endAngle) {
1074: g2d.fillArc((int) arcX, (int) arcY, (int) sizeX, (int) sizeY,
1075: (int) startAngle, (int) endAngle);
1076: }
1077:
1078: /**
1079: * Get the height of the X axis labels
1080: * @return the label space height
1081: * @param g the graphics context
1082: */
1083: protected double getXLabelHeight(Graphics g) {
1084: return fm.getStringBounds(maxXLabelName, g).getWidth() * 0.6;
1085: }
1086:
1087: /**
1088: * Get the height of the X axis labels
1089: * @return the label space height
1090: * @param g the graphics context
1091: */
1092: protected double getYLabelWidth(Graphics g) {
1093: return fm.getStringBounds(maxYLabelName, g).getWidth();
1094: }
1095:
1096: /**
1097: * Get the space consumed by the legend
1098: * @param verticalSpace true if the legend is shown horizontally below the chart
1099: * @return the size the legend consumes if it does not overlap the chart area
1100: */
1101: protected int getLegendSpace(boolean verticalSpace) {
1102: if (legendPos < 4)
1103: return 0;
1104: else if (legendPos == 4)
1105: return (verticalSpace ? 0 : (int) (legendWidth + padding));
1106: else
1107: return (verticalSpace ? (int) (fm.getHeight() + padding)
1108: : 0);
1109: }
1110:
1111: /**
1112: * Get the legend x coordinate
1113: * @param left the left edge of the chart
1114: * @param right the right edge of the chart
1115: * @return the starting x coordinate of the legend
1116: */
1117: protected int getLegendX(double left, double right) {
1118: double pos = left + 10.0;
1119: switch (legendPos) {
1120: case 0:
1121: break;
1122: case 1:
1123: pos = right - 10 - legendWidth;
1124: break;
1125: case 2:
1126: break;
1127: case 3:
1128: pos = right - 10 - legendWidth;
1129: break;
1130: case 4:
1131: pos = right + 10;
1132: break;
1133: case 5:
1134: pos = left;
1135: default:
1136: break;
1137: }
1138:
1139: return (int) pos;
1140: }
1141:
1142: /**
1143: * Get the legend y coordinate
1144: * @param top the top edge of the chart
1145: * @param bottom the bottom edge of the chart
1146: * @return the starting y coordinate of the legend
1147: */
1148: protected int getLegendY(double top, double bottom) {
1149: double pos = top + 10.0;
1150: switch (legendPos) {
1151: case 0:
1152: break;
1153: case 1:
1154: break;
1155: case 2:
1156: pos = bottom - 10 - legendHeight;
1157: break;
1158: case 3:
1159: pos = bottom - 10 - legendHeight;
1160: break;
1161: case 4:
1162: pos = top;
1163: break;
1164: case 5:
1165: pos = bottom + 2 * padding + fm.getHeight();
1166: break;
1167: default:
1168: break;
1169: }
1170:
1171: return (int) pos;
1172: }
1173:
1174: /**
1175: * Draw the chart legend
1176: * @param g2d the graphics context
1177: * @param x the x/left position
1178: * @param y the y/top position
1179: * @param w the width
1180: * @param h the height
1181: */
1182: protected void drawLegend(Graphics2D g2d, double x, double y,
1183: double w, double h) {
1184: g2d.setColor(getBackground());
1185: fillRect(g2d, x, y, w, h);
1186: g2d.setColor(Color.lightGray);
1187: drawRect(g2d, x, y, w, h);
1188:
1189: Stroke oldStroke = g2d.getStroke();
1190: g2d.setStroke(new BasicStroke(0.5F));
1191: double xpos = x;
1192: double ypos = y;
1193: for (int i = 0; i < numSeries; i++) {
1194: if (seriesNames[i] == null)
1195: continue;
1196: g2d.setColor((mode == STACKEDBARCHART) ? getGeneratedColor(
1197: i, 255) : getColor(i, 255));
1198: drawLine(g2d, xpos + 10, ypos + 10, xpos + 30, ypos + 10);
1199: g2d.setColor(getForeground());
1200: drawString(g2d, seriesNames[i], xpos + 40, ypos + 15);
1201: if (legendPos != 5)
1202: ypos += fm.getHeight();
1203: else
1204: xpos += 100;
1205: }
1206: g2d.setStroke(oldStroke);
1207: }
1208:
1209: /**
1210: * Get the drawBorder flag value
1211: * @return true if the border is drawn
1212: */
1213: public boolean getDrawBorder() {
1214: return drawBorder;
1215: }
1216:
1217: /**
1218: * Set the drawBorder flag value
1219: * @param b true if the border is drawn
1220: */
1221: public void setDrawBorder(boolean b) {
1222: drawBorder = b;
1223: }
1224:
1225: /**
1226: * Get the labels flag value
1227: * @return true if the border is drawn
1228: */
1229: public boolean getLabels() {
1230: return labelPoints;
1231: }
1232:
1233: /**
1234: * Set the labels flag value
1235: * @param b true if the lables are drawn on the data points
1236: */
1237: public void setLabels(boolean b) {
1238: labelPoints = b;
1239: }
1240:
1241: /**
1242: * Get the highlight flag value
1243: * @return true if the current value is highlighted as the mouse is moved
1244: */
1245: public boolean getHighlight() {
1246: return highlightSelection;
1247: }
1248:
1249: /**
1250: * Set the highlight flag value
1251: * @param b true if the current value is to be highlighted as the mouse is moved
1252: */
1253: public void setHighlight(boolean b) {
1254: highlightSelection = b;
1255: }
1256:
1257: /**
1258: * Get the markers flag value
1259: * @return true if the markers are drwan for each point
1260: */
1261: public boolean getMarkers() {
1262: return drawPoints;
1263: }
1264:
1265: /**
1266: * Set the markers flag value
1267: * @param b true if the markers are drwan for each point
1268: */
1269: public void setMarkers(boolean b) {
1270: drawPoints = b;
1271: }
1272:
1273: /**
1274: * Get the minimum Y value
1275: * @return the minimum value
1276: */
1277: public double getMinY() {
1278: return minY;
1279: }
1280:
1281: /**
1282: * Set the minimum Y value
1283: * @param y the minimum value
1284: */
1285: public void setMinY(double y) {
1286: minY = y;
1287: }
1288:
1289: /**
1290: * Get the maximum Y value
1291: * @return the maximum value
1292: */
1293: public double getMaxY() {
1294: return maxY;
1295: }
1296:
1297: /**
1298: * Set the maximum Y value
1299: * @param y the maximum value
1300: */
1301: public void setMaxY(double y) {
1302: maxY = y;
1303: }
1304:
1305: /**
1306: * Get the legend position
1307: * @return the position value, 0=top left, 1=top right, 2=bottom left, 3=bottom right, 4=right margin, 5=bottom margin
1308: */
1309: public int getLegendPos() {
1310: return legendPos;
1311: }
1312:
1313: /**
1314: * Set the legend position
1315: * @param pos value=the legend position, 0=top left, 1=top right, 2=bottom left, 3=bottom right, 4=right margin, 5=bottom margin
1316: */
1317: public void setLegendPos(int y) {
1318: legendPos = y;
1319: }
1320:
1321: /**
1322: * Draw a pie chart with the first data series
1323: * @param g2d the graphics context
1324: */
1325: public void drawPieChart(Graphics2D g2d) {
1326: g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
1327: RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
1328: g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1329: RenderingHints.VALUE_ANTIALIAS_ON);
1330:
1331: double height3d = (bottom - top - 2 * padding) / 10.0;
1332: double size = Math.min(bottom - top, right - left) - padding
1333: - 30;
1334: double sizeX = scale * (right - left) - padding - 30;
1335: double sizeY = scale * (bottom - top) - padding - 30 - height3d;
1336:
1337: double arcX = left + (right - left - sizeX) / 2;
1338: double arcY = top + (bottom - top - sizeY - height3d) / 2;
1339: Color temp = g2d.getColor();
1340:
1341: double sumTot = 0;
1342: Point.Double center = new Point.Double((right - left) / 2,
1343: (bottom - top - height3d) / 2);
1344:
1345: int numPairs = points.length / (numSeries * 2);
1346: double totalValue = 0.0;
1347: for (int i = 0; i < 1/*numSeries*/; i++) {
1348: for (int j = 0; j < numPairs; j++) {
1349: int offset = i * numPairs * 2 + j * 2;
1350: double yValue = points[offset + 1];
1351: if (!Double.isNaN(yValue) && (yValue > minYAllowable))
1352: totalValue += yValue;
1353: }
1354: }
1355:
1356: int i = 0; // Only take the first series
1357: double degrees = 0.0;
1358: Area topCircle = new Area(new Arc2D.Double(arcX, arcY, sizeX,
1359: sizeY, 0.0, 360.0, Arc2D.PIE));
1360: Area bottomCircle = new Area(new Arc2D.Double(arcX, arcY
1361: + height3d, sizeX, sizeY, 0.0, 360.0, Arc2D.PIE));
1362: Area spacer = new Area(new Rectangle2D.Double(arcX, arcY
1363: + sizeY / 2.0, sizeX, height3d));
1364: spacer.subtract(topCircle);
1365: for (int j = 0; j < numPairs; j++) {
1366: int offset = i * numPairs * 2 + j * 2;
1367: double yValue = points[offset + 1];
1368: if (!Double.isNaN(yValue) && (yValue > minYAllowable)) {
1369: double adjustedValue = yValue * 360.0 / totalValue;
1370: Color pieColor = getColor(j);
1371: g2d.setColor(pieColor);
1372: Arc2D.Double arc = new Arc2D.Double(arcX, arcY, sizeX,
1373: sizeY, degrees, adjustedValue, Arc2D.PIE);
1374: g2d.fill(arc);
1375:
1376: if (scale > 0.99)
1377: selectableAreas.add(new SelectableArea(xLabels[j],
1378: seriesNames[0], yValue, new Area(arc),
1379: pieColor));
1380:
1381: Arc2D.Double arc2 = new Arc2D.Double(arcX, arcY, sizeX,
1382: sizeY, degrees, adjustedValue, Arc2D.OPEN);
1383: Rectangle2D rect = arc2.getBounds2D();
1384: double sideX = rect.getX();
1385: double sideW = rect.getWidth();
1386: Rectangle2D.Double rectangle = new Rectangle2D.Double(
1387: sideX, arcY + sizeY / 2.0, sideW, sizeY / 2.0
1388: + height3d);
1389: Area area = new Area(rectangle);
1390:
1391: // The spacer has a left and a right wedge, so we need to find the
1392: // intersection. We must also do this before subtracting the top and
1393: // bottom circles.
1394: Area spacerArea = new Area(spacer);
1395: spacerArea.intersect(area);
1396:
1397: area.subtract(topCircle);
1398: area.intersect(bottomCircle);
1399: area.add(spacerArea);
1400:
1401: g2d.setColor(darken(pieColor, degrees));
1402:
1403: GradientPaint gradient = new GradientPaint(0.0F, 0.0F,
1404: darken(pieColor, degrees),
1405: (float) sideW * 2.0F, 0.0F, darken(pieColor,
1406: degrees + adjustedValue), true);
1407: g2d.setPaint(gradient);
1408: g2d.fill(area);
1409:
1410: Point.Double titlePosition = new Point.Double(0, 0);
1411: titlePosition.x = (Math.cos(degrees * Math.PI / 180.0
1412: + adjustedValue * Math.PI / 360.0)
1413: * sizeX / 2.0)
1414: + center.x + left;
1415: titlePosition.y = center.y
1416: - (Math.sin(degrees * Math.PI / 180.0
1417: + adjustedValue * Math.PI / 360.0)
1418: * sizeY / 2.0) + top;
1419: String name = "";//df.getFieldName( ((Integer)cols.elementAt( i )).intValue());
1420: if (titlePosition.x <= center.x)
1421: titlePosition.x = titlePosition.x
1422: - fm.stringWidth(name) - 1;
1423: else if (titlePosition.x > center.x)
1424: titlePosition.x += 1;
1425:
1426: if (titlePosition.y > center.y)
1427: titlePosition.y = titlePosition.y + fm.getHeight()
1428: + 1;
1429: else if (titlePosition.y <= center.y)
1430: titlePosition.y -= 1;
1431:
1432: g2d.setColor(temp);
1433: drawString(g2d, name, titlePosition.x, titlePosition.y);
1434: degrees += adjustedValue;
1435: }
1436: }
1437: }
1438:
1439: /**
1440: * Highlight the selected area - if any
1441: * @param g2d the graphics context
1442: */
1443: public void drawSelectedArea(Graphics2D g2d) {
1444: if (highlightSelection && (highlightedArea != null)) {
1445: Stroke oldStroke = g2d.getStroke();
1446: g2d.setStroke(new BasicStroke(2.0F));
1447: g2d.setColor(highlightedArea.color.brighter());
1448: g2d.draw(highlightedArea.area);
1449: g2d.setStroke(oldStroke);
1450: }
1451: }
1452:
1453: /**
1454: * Get an index color with full opacity
1455: * @param idx the color index
1456: */
1457: private Color getColor(int idx) {
1458: int opacity = (int) (255 * (1.0 - Math.max(animationScale, 0.0)));
1459: opacity = Math.max(0, Math.min(255, opacity));
1460: return getColor(idx, opacity);
1461: }
1462:
1463: /**
1464: * Get an index color with partial opacity
1465: * @param idx the color index
1466: * @param opacity the opacity value in the range 0-255
1467: */
1468: private Color getColor(int idx, int opacity) {
1469: opacity = Math.min(255, Math.max(opacity, 0));
1470: switch (idx % 18) {
1471: case 0:
1472: return new Color(255, 0, 0, opacity);
1473: case 1:
1474: return new Color(0, 0, 228, opacity);
1475: case 2:
1476: return new Color(0, 228, 0, opacity);
1477: case 3:
1478: return new Color(168, 0, 168, opacity);
1479: case 4:
1480: return new Color(255, 208, 41, opacity);
1481: case 5:
1482: return new Color(51, 102, 255, opacity); // Mid blue
1483: case 6:
1484: return new Color(255, 255, 0, opacity); // yellow
1485: case 7:
1486: return new Color(153, 153, 255, opacity); // Sun purple
1487: case 8:
1488: return new Color(228, 204, 228, opacity); // Pink
1489: case 9:
1490: return new Color(51, 153, 204, opacity); // Mid blue-green
1491: case 10:
1492: return new Color(102, 255, 153, opacity); // lime
1493: case 11:
1494: return new Color(51, 0, 204, opacity); // purple
1495: case 12:
1496: return new Color(0, 204, 102, opacity); // emerald
1497: case 13:
1498: return new Color(0, 153, 255, opacity); // bright blue-green
1499: case 14:
1500: return new Color(255, 204, 0, opacity); // Orange
1501: case 15:
1502: return new Color(0, 204, 255, opacity); // bright blue
1503: case 16:
1504: return new Color(153, 00, 51, opacity); // ruby
1505: case 17:
1506: return new Color(153, 51, 0, opacity); // brown
1507: }
1508: return Color.gray;
1509: }
1510:
1511: /**
1512: * Darken the selected color for the sides of the pie chart
1513: * @param c the selected color
1514: * @param degrees the angle/position at which the color is used
1515: * @return the new color
1516: */
1517: protected Color darken(Color c, double degrees) {
1518: degrees = Math.min(360.0, Math.max(degrees, 0.0));
1519: double multiplier = 1.0 - (degrees % 180.0) / 360.0;
1520: return new Color((int) (c.getRed() * multiplier), (int) (c
1521: .getGreen() * multiplier),
1522: (int) (c.getBlue() * multiplier), c.getAlpha());
1523: }
1524:
1525: /**
1526: * Generate an HSB color, using the index as an offset int the H color range
1527: * @param idx the index of the color
1528: * @return the new color
1529: */
1530: protected Color getGeneratedColor(int idx) {
1531: int opacity = (int) (255 * (1.0 - Math.max(animationScale, 0.0)));
1532: opacity = Math.max(0, Math.min(255, opacity));
1533: return getGeneratedColor(idx, opacity);
1534: }
1535:
1536: /**
1537: * Get a generated color with partial opacity
1538: * @param i the color index
1539: * @param opacity the opacity in the range 0-255
1540: * @return the new color
1541: */
1542: protected Color getGeneratedColor(int i, int opacity) {
1543: opacity = Math.min(255, Math.max(opacity, 0));
1544: Color c = Color.getHSBColor((0.9F * i / numSeries), 0.9F, 0.9F);
1545: return new Color(c.getRed(), c.getGreen(), c.getBlue(), opacity);
1546: }
1547:
1548: /**
1549: * Set the display mode of the chart
1550: * @param newMode the new mode: 0=BAR, 1=STACKEDBAR, 2=LINE, 3=PIE, 4=SCATTER
1551: */
1552: public void setMode(int newMode) {
1553: mode = newMode;
1554: backgroundImage = null;
1555: highlightedArea = null;
1556:
1557: if (newPoints != null)
1558: showNewData();
1559: }
1560:
1561: /**
1562: * Get the current display mode of the chart
1563: * @return the mode: 0=BAR, 1=STACKEDBAR, 2=LINE, 3=PIE, 4=SCATTER
1564: */
1565: public int getMode() {
1566: return mode;
1567: }
1568:
1569: /**
1570: * Set the text of the form to the specified language string,
1571: * the setLangString(...) function is the preferred method of setting
1572: * the caption.
1573: * @param newStr the new title
1574: */
1575: public void setTitle(String newStr) {
1576: title = newStr;
1577: repaint();
1578: }
1579:
1580: /**
1581: * Get the graph title
1582: * @return the title text
1583: */
1584: public String getTitle() {
1585: return title;
1586: }
1587:
1588: /**
1589: * Set one or more attributes of the component. Currently this handles the
1590: * attributes
1591: * <OL>
1592: * <LI>mode, value=the type of chart i.e. 0=BAR, 1=STACKEDBAR, 2=LINE, 3=PIE, 4=SCATTER</LI>
1593: * <LI>border, value=1 or true to surround with a border</LI>
1594: * <LI>labels, value=1 or true to label the data points</LI>
1595: * <LI>markers, value=1 or true to draw markers for each data point</LI>
1596: * <LI>minY, value=the minimum allowable y value, any value less than this will not be drawn </LI>
1597: * <LI>legendPos, value=the legend position, 0=top left, 1=top right, 2=bottom left, 3=bottom right, 4=right margin, 5=bottom margin</LI>
1598: * <LI>highlight, value=1 or true to highlight the selection under the mouse</LI>
1599: * </OL>
1600: * @param attribName the attribute name
1601: * @param attribValue the attribute value
1602: * @return 0 for success, non zero otherwise
1603: */
1604: public int setAttribute(String attribName, Object attribValue) {
1605: String attribNameLwr = attribName.toLowerCase();
1606: String attribValueStr = (String) attribValue;
1607: String attribValueLwr = attribValueStr.toLowerCase();
1608: if (attribNameLwr.equals("mode"))
1609: mode = new Integer(attribValueStr).intValue();
1610: else if (attribNameLwr.equals("border"))
1611: drawBorder = (attribValueLwr.equals("true") || attribValueLwr
1612: .equals("1"));
1613: else if (attribNameLwr.equals("labels"))
1614: labelPoints = (attribValueLwr.equals("true") || attribValueLwr
1615: .equals("1"));
1616: else if (attribNameLwr.equals("markers"))
1617: drawPoints = (attribValueLwr.equals("true") || attribValueLwr
1618: .equals("1"));
1619: else if (attribNameLwr.equals("minY"))
1620: minYAllowable = Double.parseDouble(attribValueStr);
1621: else if (attribNameLwr.equals("legendpos"))
1622: legendPos = Integer.parseInt(attribValueStr);
1623: else if (attribNameLwr.equals("highlight"))
1624: highlightSelection = (attribValueLwr.equals("true") || attribValueLwr
1625: .equals("1"));
1626:
1627: repaint(100);
1628:
1629: return 0;
1630: }
1631:
1632: //----------------------------------------------------------------------------
1633:
1634: private class ChartPanel extends JComponent implements
1635: MouseMotionListener {
1636: public ChartPanel() {
1637: addMouseMotionListener(this );
1638: }
1639:
1640: /**
1641: * Renders the chart.
1642: */
1643: public void paintComponent(Graphics g) {
1644: Graphics2D g2d = (Graphics2D) g;
1645:
1646: g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
1647: RenderingHints.VALUE_RENDER_QUALITY);
1648:
1649: // draw the title
1650: if (fm == null)
1651: fm = getFontMetrics(getFont());
1652: if (labelHeight == 0) {
1653: labelHeight = fm.getHeight();
1654: labelWidth = Math.max(fm.stringWidth(new Integer(
1655: (int) maxX).toString()),
1656: fm.stringWidth(new Integer((int) maxY)
1657: .toString())) + 2;
1658: titleHeight = labelHeight * 2;
1659: top = padding + titleHeight;
1660: bottom = getSize().height - padding - labelHeight;
1661: if (xTitle != null)
1662: bottom -= padding + labelHeight;
1663: left = padding + labelWidth;
1664: if (yTitle != null)
1665: left += padding + labelWidth;
1666: right = getSize().width - padding
1667: - getLegendSpace(false);
1668: }
1669:
1670: if ((points != null) && (points.length > 0)) {
1671: scale = 1.0 - Math.max(Math.min(animationScale, 1.0),
1672: 0.0);
1673: scale *= scale;
1674: if (!animateYAxis)
1675: scale = 1.0;
1676:
1677: if ((scale < 1.0) || (animationScale > 0.0))
1678: selectableAreas.clear();
1679:
1680: switch (mode) {
1681: case PIECHART:
1682: drawPieChart(g2d);
1683: break;
1684:
1685: case STACKEDBARCHART:
1686: drawStackedBarChart(g2d);
1687: break;
1688:
1689: case BARCHART:
1690: drawBarChart(g2d);
1691: break;
1692:
1693: case SCATTERCHART:
1694: case LINECHART:
1695: default:
1696: drawLineChart(g2d);
1697: break;
1698: }
1699: drawSelectedArea(g2d);
1700: }
1701:
1702: if ((animationScale <= 0.0) || (animationScale > 1.0)) {
1703: animationTimer.stop();
1704: if (animationScaleInc < 0.0)
1705: showNewData();
1706: }
1707: }
1708:
1709: /**
1710: * Invoked when a mouse button is pressed on a component and then
1711: * dragged. <code>MOUSE_DRAGGED</code> events will continue to be
1712: * delivered to the component where the drag originated until the
1713: * mouse button is released (regardless of whether the mouse position
1714: * is within the bounds of the component).
1715: * <p>
1716: * Due to platform-dependent Drag&Drop implementations,
1717: * <code>MOUSE_DRAGGED</code> events may not be delivered during a native
1718: * Drag&Drop operation.
1719: */
1720: public void mouseDragged(MouseEvent e) {
1721: }
1722:
1723: /**
1724: * Invoked when the mouse cursor has been moved onto a component
1725: * but no buttons have been pushed.
1726: */
1727: public void mouseMoved(MouseEvent e) {
1728: int numAreas = selectableAreas.size();
1729: for (int i = 0; i < numAreas; i++) {
1730: SelectableArea area = (SelectableArea) selectableAreas
1731: .get(i);
1732: Point pt = e.getPoint();
1733: if (area.area.contains(pt.x, pt.y)) {
1734: highlightedArea = area;
1735: setToolTipText(area.series + ", " + area.label
1736: + ": " + area.value);
1737: repaint();
1738: return;
1739: }
1740: }
1741: highlightedArea = null;
1742: setToolTipText("");
1743: repaint();
1744: }
1745: }
1746:
1747: /**
1748: * An area of the chart that can be selected, with information about that area
1749: */
1750: public class SelectableArea {
1751: /**
1752: * the value of this area
1753: */
1754: public double value;
1755:
1756: /**
1757: * the label (x-value) of this area
1758: */
1759: public String label;
1760:
1761: /**
1762: * the series name
1763: */
1764: public String series;
1765:
1766: /**
1767: * The selected area
1768: */
1769: public Area area;
1770:
1771: /**
1772: * The displayed color
1773: */
1774: public Color color;
1775:
1776: /**
1777: * Create a new selectable area
1778: * @param l the label
1779: * @param s the series name
1780: * @param v the value
1781: * @param a the area
1782: * @param clr the color
1783: */
1784: public SelectableArea(String l, String s, double v, Area a,
1785: Color clr) {
1786: label = l;
1787: series = s;
1788: value = v;
1789: area = a;
1790: color = clr;
1791: }
1792: }
1793: }
|