001: /***********************************************************************************************
002: * Copyright 2002 (C) Nathaniel G. Auvil. All Rights Reserved.
003: *
004: * Redistribution and use of this software and associated documentation ("Software"), with or
005: * without modification, are permitted provided that the following conditions are met:
006: *
007: * 1. Redistributions of source code must retain copyright statements and notices.
008: * Redistributions must also contain a copy of this document.
009: *
010: * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
011: * conditions and the following disclaimer in the documentation and/or other materials
012: * provided with the distribution.
013: *
014: * 3. The name "jCharts" or "Nathaniel G. Auvil" must not be used to endorse or promote
015: * products derived from this Software without prior written permission of Nathaniel G.
016: * Auvil. For written permission, please contact nathaniel_auvil@users.sourceforge.net
017: *
018: * 4. Products derived from this Software may not be called "jCharts" nor may "jCharts" appear
019: * in their names without prior written permission of Nathaniel G. Auvil. jCharts is a
020: * registered trademark of Nathaniel G. Auvil.
021: *
022: * 5. Due credit should be given to the jCharts Project (http://jcharts.sourceforge.net/).
023: *
024: * THIS SOFTWARE IS PROVIDED BY Nathaniel G. Auvil AND CONTRIBUTORS ``AS IS'' AND ANY
025: * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
026: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
027: * jCharts OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
028: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
029: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
030: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,STRICT LIABILITY, OR TORT
031: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
032: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
033: ************************************************************************************************/package org.krysalis.jcharts.nonAxisChart;
034:
035: import java.awt.Color;
036: import java.awt.Graphics2D;
037: import java.awt.Paint;
038: import java.awt.Polygon;
039: import java.awt.Rectangle;
040: import java.awt.RenderingHints;
041: import java.awt.font.FontRenderContext;
042: import java.awt.geom.Point2D;
043: import java.awt.geom.Rectangle2D;
044:
045: import org.krysalis.jcharts.Chart;
046: import org.krysalis.jcharts.chartData.interfaces.IRadarChartDataSet;
047: import org.krysalis.jcharts.chartData.processors.RadarChartDataProcessor;
048: import org.krysalis.jcharts.properties.ChartProperties;
049: import org.krysalis.jcharts.properties.LegendProperties;
050: import org.krysalis.jcharts.properties.RadarChartProperties;
051: import org.krysalis.jcharts.properties.util.ChartFont;
052:
053: /*************************************************************************************
054: * Represents a radar chart a.k.a. spider chart
055: *
056: * @author Rami Hansenne
057: * @version $Id: RadarChart.java,v 1.3 2003/08/28 14:36:28 nicolaken Exp $
058: * @since 1.0.0
059: ************************************************************************************/
060:
061: public class RadarChart extends Chart {
062:
063: private IRadarChartDataSet iRadarChartDataSet;
064: private RadarChartDataProcessor radarChartDataProcessor;
065: private RadarChartProperties props;
066: private double radius, step;
067: private Point2D center;
068:
069: // scale max value and increment
070: private double scaleMax, scaleIncrement;
071:
072: // default number of scale increments if no user defined increment
073: private static final int DEFAULT_NR_OF_INCREMENTS = 10;
074:
075: /************************************************************************************************
076: * Constructor
077: *
078: * @param iRadarChartDataSet
079: * @param legendProperties
080: * @param chartProperties general chart properties
081: * @param pixelWidth
082: * @param pixelHeight
083: ************************************************************************************************/
084: public RadarChart(IRadarChartDataSet iRadarChartDataSet,
085: LegendProperties legendProperties,
086: ChartProperties chartProperties, int pixelWidth,
087: int pixelHeight) {
088: super (legendProperties, chartProperties, pixelWidth,
089: pixelHeight);
090: this .iRadarChartDataSet = iRadarChartDataSet;
091: props = (RadarChartProperties) iRadarChartDataSet
092: .getChartTypeProperties();
093: if (props == null) {
094: props = new RadarChartProperties();
095: }
096: }
097:
098: /************************************************************************************************
099: * Draws the chart
100: *
101: ************************************************************************************************/
102: protected void renderChart() {
103: Graphics2D g2d = getGraphics2D();
104:
105: // turn anti-aliasing on
106: g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
107: RenderingHints.VALUE_ANTIALIAS_ON);
108:
109: //todo : render legend, use scale processor classes,...
110:
111: FontRenderContext fontRenderContext = super .getGraphics2D()
112: .getFontRenderContext();
113:
114: //---render the TITLE. If no title, this will return zero.
115: float chartTitleHeight = super .renderChartTitle(
116: this .iRadarChartDataSet.getChartTitle(),
117: fontRenderContext);
118:
119: this .radarChartDataProcessor = new RadarChartDataProcessor(
120: this .iRadarChartDataSet);
121: this .radarChartDataProcessor.processData();
122:
123: // set scale max value
124: if (Double.isNaN(props.getScaleMaxValue()))
125: this .scaleMax = radarChartDataProcessor.getMaxValue();
126: else
127: scaleMax = props.getScaleMaxValue();
128:
129: // set scale increment
130: if (Double.isNaN(props.getScaleIncrement()))
131: this .scaleIncrement = scaleMax / DEFAULT_NR_OF_INCREMENTS;
132: else
133: scaleIncrement = props.getScaleIncrement();
134:
135: //todo : adjust chart space in order to correctly render title & legend
136: Rectangle chartSpace = new Rectangle(getImageWidth(),
137: getImageHeight());
138:
139: center = new Point2D.Double(chartSpace.getWidth() / 2
140: - chartSpace.getX(), chartSpace.getHeight() / 2
141: - chartSpace.getY());
142: radius = Math
143: .min(chartSpace.getWidth(), chartSpace.getHeight());
144: radius = (radius - (radius * 10) / 100) / 2;
145: step = 2 * Math.PI / iRadarChartDataSet.getNumberOfDataItems();
146:
147: if (props.getShowGridLines()) {
148: drawGridLines(g2d);
149: }
150: drawAxis(g2d);
151: drawRadar(g2d);
152: }
153:
154: private void drawRadar(Graphics2D g) {
155:
156: for (int dataset = 0; dataset < iRadarChartDataSet
157: .getNumberOfDataSets(); dataset++) {
158:
159: double currentValue;
160: double previousValue = scaleValue(iRadarChartDataSet
161: .getValue(dataset, iRadarChartDataSet
162: .getNumberOfDataItems() - 1));
163:
164: Paint paint = iRadarChartDataSet.getPaint(dataset);
165:
166: for (int di = 0; di < iRadarChartDataSet
167: .getNumberOfDataItems(); di++) {
168: currentValue = previousValue;
169: previousValue = scaleValue(iRadarChartDataSet.getValue(
170: dataset, di));
171: g.setPaint(paint);
172:
173: int c0 = (int) Math.round(center.getX());
174: int c1 = (int) Math.round(center.getY());
175: int x0 = getX(di + 1, previousValue);
176: int y0 = getY(di + 1, previousValue);
177: int x1 = getX(di, currentValue);
178: int y1 = getY(di, currentValue);
179:
180: if (props.getFillRadar()) {
181: Polygon p = new Polygon();
182: p.addPoint(c0, c1);
183: p.addPoint(x0, y0);
184: p.addPoint(x1, y1);
185: // make color translucent
186: if (paint instanceof Color) {
187: Color color = (Color) paint;
188: g.setPaint(new Color(color.getRed(), color
189: .getGreen(), color.getBlue(), 100));
190: }
191: g.fillPolygon(p);
192: g.setPaint(paint);
193: }
194: g.drawLine(x0, y0, x1, y1);
195: }
196: }
197: }
198:
199: private void drawGridLines(Graphics2D g) {
200: g.setColor(Color.lightGray);
201: for (int di = 0; di < iRadarChartDataSet.getNumberOfDataItems(); di++) {
202: for (double i = scaleIncrement; i <= scaleMax; i += scaleIncrement) {
203: double pos = scaleValue(i);
204: g.drawLine(getX(di + 1, pos), getY(di + 1, pos), getX(
205: di, pos), getY(di, pos));
206: }
207: }
208: }
209:
210: private void drawAxis(Graphics2D g) {
211: g.setColor(Color.darkGray);
212: for (int index = 0; index < iRadarChartDataSet
213: .getNumberOfDataItems(); index++) {
214: String label = iRadarChartDataSet.getAxisLabel(index);
215: g.drawLine((int) center.getX(), (int) center.getY(), getX(
216: index, 1), getY(index, 1));
217:
218: Rectangle2D bounds = g.getFontMetrics().getStringBounds(
219: label, g);
220:
221: // draw axis label
222: ChartFont cfont = props.getTitleChartFont();
223: if (cfont != null) {
224: g.setFont(cfont.getFont());
225:
226: }
227: g.drawString(label, (int) (getX(index + 1, 1) - bounds
228: .getWidth() / 2), getY(index + 1, 1));
229: }
230:
231: // draw gridline labels
232: g.setColor(Color.darkGray);
233: g.setFont(ChartFont.DEFAULT_AXIS_VALUE.getFont());
234: int selectedLine = (int) iRadarChartDataSet
235: .getNumberOfDataItems() / 2;
236: for (double i = scaleIncrement; i <= scaleMax; i += scaleIncrement) {
237: double pos = scaleValue(i);
238: Rectangle2D bounds = g.getFont().getStringBounds("1",
239: g.getFontRenderContext());
240: g.drawString(props.getGridLabelFormat().format(i), getX(
241: selectedLine, pos), (int) Math.round(getY(
242: selectedLine, pos)
243: + bounds.getHeight()));
244: }
245:
246: }
247:
248: private int getX(int dataset, double factor) {
249: return (int) Math.round(center.getX() + radius * factor
250: * Math.sin(step * dataset));
251: }
252:
253: private int getY(int dataset, double factor) {
254: return (int) Math.round(center.getY() + radius * factor
255: * Math.cos(step * dataset));
256: }
257:
258: private double scaleValue(double value) {
259: if (value > scaleMax)
260: value = scaleMax;
261: if (scaleMax == 0)
262: return 0;
263: return value / scaleMax;
264: }
265:
266: }
|