/***********************************************************************************************
* Copyright 2002 (C) Nathaniel G. Auvil. All Rights Reserved.
*
* Redistribution and use of this software and associated documentation ("Software"), with or
* without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright statements and notices.
* Redistributions must also contain a copy of this document.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. The name "jCharts" or "Nathaniel G. Auvil" must not be used to endorse or promote
* products derived from this Software without prior written permission of Nathaniel G.
* Auvil. For written permission, please contact nathaniel_auvil@users.sourceforge.net
*
* 4. Products derived from this Software may not be called "jCharts" nor may "jCharts" appear
* in their names without prior written permission of Nathaniel G. Auvil. jCharts is a
* registered trademark of Nathaniel G. Auvil.
*
* 5. Due credit should be given to the jCharts Project (http://jcharts.sourceforge.net/).
*
* THIS SOFTWARE IS PROVIDED BY Nathaniel G. Auvil AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* jCharts OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
************************************************************************************************/
package org.krysalis.jcharts.nonAxisChart;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import org.krysalis.jcharts.Chart;
import org.krysalis.jcharts.chartData.interfaces.IRadarChartDataSet;
import org.krysalis.jcharts.chartData.processors.RadarChartDataProcessor;
import org.krysalis.jcharts.properties.ChartProperties;
import org.krysalis.jcharts.properties.LegendProperties;
import org.krysalis.jcharts.properties.RadarChartProperties;
import org.krysalis.jcharts.properties.util.ChartFont;
/*************************************************************************************
* Represents a radar chart a.k.a. spider chart
*
* @author Rami Hansenne
* @version $Id: RadarChart.java,v 1.3 2003/08/28 14:36:28 nicolaken Exp $
* @since 1.0.0
************************************************************************************/
public class RadarChart
extends Chart
{
private IRadarChartDataSet iRadarChartDataSet;
private RadarChartDataProcessor radarChartDataProcessor;
private RadarChartProperties props;
private double radius, step;
private Point2D center;
// scale max value and increment
private double scaleMax, scaleIncrement;
// default number of scale increments if no user defined increment
private static final int DEFAULT_NR_OF_INCREMENTS = 10;
/************************************************************************************************
* Constructor
*
* @param iRadarChartDataSet
* @param legendProperties
* @param chartProperties general chart properties
* @param pixelWidth
* @param pixelHeight
************************************************************************************************/
public RadarChart( IRadarChartDataSet iRadarChartDataSet,
LegendProperties legendProperties,
ChartProperties chartProperties,
int pixelWidth,
int pixelHeight )
{
super( legendProperties, chartProperties, pixelWidth, pixelHeight );
this.iRadarChartDataSet = iRadarChartDataSet;
props = (RadarChartProperties) iRadarChartDataSet.getChartTypeProperties();
if( props == null )
{
props = new RadarChartProperties();
}
}
/************************************************************************************************
* Draws the chart
*
************************************************************************************************/
protected void renderChart()
{
Graphics2D g2d = getGraphics2D();
// turn anti-aliasing on
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
//todo : render legend, use scale processor classes,...
FontRenderContext fontRenderContext = super.getGraphics2D().
getFontRenderContext();
//---render the TITLE. If no title, this will return zero.
float chartTitleHeight = super.renderChartTitle( this.iRadarChartDataSet.
getChartTitle(), fontRenderContext );
this.radarChartDataProcessor = new RadarChartDataProcessor( this.iRadarChartDataSet );
this.radarChartDataProcessor.processData();
// set scale max value
if( Double.isNaN( props.getScaleMaxValue() ) )
this.scaleMax = radarChartDataProcessor.getMaxValue();
else
scaleMax = props.getScaleMaxValue();
// set scale increment
if( Double.isNaN( props.getScaleIncrement() ) )
this.scaleIncrement = scaleMax / DEFAULT_NR_OF_INCREMENTS;
else
scaleIncrement = props.getScaleIncrement();
//todo : adjust chart space in order to correctly render title & legend
Rectangle chartSpace = new Rectangle( getImageWidth(), getImageHeight() );
center = new Point2D.Double(
chartSpace.getWidth() / 2 - chartSpace.getX(),
chartSpace.getHeight() / 2 - chartSpace.getY() );
radius = Math.min( chartSpace.getWidth(), chartSpace.getHeight() );
radius = ( radius - ( radius * 10 ) / 100 ) / 2;
step = 2 * Math.PI / iRadarChartDataSet.getNumberOfDataItems();
if( props.getShowGridLines() )
{
drawGridLines( g2d );
}
drawAxis( g2d );
drawRadar( g2d );
}
private void drawRadar(
Graphics2D g )
{
for( int dataset = 0; dataset < iRadarChartDataSet.getNumberOfDataSets();
dataset++ )
{
double currentValue;
double previousValue = scaleValue( iRadarChartDataSet.getValue( dataset,
iRadarChartDataSet.getNumberOfDataItems() -
1 ) );
Paint paint = iRadarChartDataSet.getPaint( dataset );
for( int di = 0; di < iRadarChartDataSet.getNumberOfDataItems(); di++ )
{
currentValue = previousValue;
previousValue = scaleValue( iRadarChartDataSet.getValue( dataset, di ) );
g.setPaint( paint );
int c0 = (int) Math.round( center.getX() );
int c1 = (int) Math.round( center.getY() );
int x0 = getX( di + 1, previousValue );
int y0 = getY( di + 1, previousValue );
int x1 = getX( di, currentValue );
int y1 = getY( di, currentValue );
if( props.getFillRadar() )
{
Polygon p = new Polygon();
p.addPoint( c0, c1 );
p.addPoint( x0, y0 );
p.addPoint( x1, y1 );
// make color translucent
if( paint instanceof Color )
{
Color color = (Color) paint;
g.setPaint( new Color( color.getRed(), color.getGreen(),
color.getBlue(), 100 ) );
}
g.fillPolygon( p );
g.setPaint( paint );
}
g.drawLine( x0, y0, x1, y1 );
}
}
}
private void drawGridLines( Graphics2D g )
{
g.setColor( Color.lightGray );
for( int di = 0; di < iRadarChartDataSet.getNumberOfDataItems();
di++ )
{
for( double i = scaleIncrement; i <= scaleMax; i += scaleIncrement )
{
double pos = scaleValue( i );
g.drawLine(
getX( di + 1, pos ),
getY( di + 1, pos ),
getX( di, pos ),
getY( di, pos ) );
}
}
}
private void drawAxis( Graphics2D g )
{
g.setColor( Color.darkGray );
for( int index = 0; index < iRadarChartDataSet.getNumberOfDataItems();
index++ )
{
String label = iRadarChartDataSet.getAxisLabel( index );
g.drawLine(
(int) center.getX(),
(int) center.getY(),
getX( index, 1 ),
getY( index, 1 ) );
Rectangle2D bounds = g.getFontMetrics().getStringBounds( label, g );
// draw axis label
ChartFont cfont = props.getTitleChartFont();
if( cfont != null )
{
g.setFont( cfont.getFont() );
}
g.drawString(
label,
(int) ( getX( index + 1, 1 ) - bounds.getWidth() / 2 ),
getY( index + 1, 1 ) );
}
// draw gridline labels
g.setColor( Color.darkGray );
g.setFont( ChartFont.DEFAULT_AXIS_VALUE.getFont() );
int selectedLine = (int) iRadarChartDataSet.getNumberOfDataItems() / 2;
for( double i = scaleIncrement; i <= scaleMax; i += scaleIncrement )
{
double pos = scaleValue( i );
Rectangle2D bounds = g.getFont().getStringBounds( "1",
g.getFontRenderContext() );
g.drawString( props.getGridLabelFormat().format( i ),
getX( selectedLine, pos ),
(int) Math.round( getY( selectedLine, pos ) + bounds.getHeight() ) );
}
}
private int getX( int dataset, double factor )
{
return (int) Math.round( center.getX() + radius * factor * Math.sin( step * dataset ) );
}
private int getY( int dataset, double factor )
{
return (int) Math.round( center.getY() + radius * factor * Math.cos( step * dataset ) );
}
private double scaleValue( double value )
{
if( value > scaleMax )
value = scaleMax;
if( scaleMax == 0 )
return 0;
return value / scaleMax;
}
}