/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2008 - 2009 Larry Ogrodnek, Pentaho Corporation and Contributors. All rights reserved.
*/
package org.pentaho.reporting.libraries.libsparklines;
import org.pentaho.reporting.libraries.libsparklines.util.GraphUtils;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
/**
* A very fast and very simple line-graph drawable. This code is based on the LineGraph class writen by Larry Ogrodnek
* but instead of producing a low-resolution image, this class writes the content into a Graphics2D context.
*
* @author Thomas Morgner
*/
public class LineGraphDrawable {
private static final int DEFAULT_SPACING = 2;
private static final Number[] EMPTY = new Number[ 0 ];
private static final float LAST_POINT_RADIUS = 2.5f;
private static final float LAST_POINT_DIAMETER = LAST_POINT_RADIUS * 2;
private int spacing;
private Color color;
private Color background;
private Color lastColor;
private Number[] data;
/**
* Creates a default bargraph drawable with some sensible default colors and spacings.
*/
public LineGraphDrawable() {
this.color = Color.black;
this.spacing = DEFAULT_SPACING;
this.data = EMPTY;
}
/**
* Returns the numeric data for the drawable or null, if the drawable has no data.
*
* @return the data.
*/
public Number[] getData() {
return data.clone();
}
/**
* Defines the numeric data for the drawable or null, if the drawable has no data.
*
* @param data the data (can be null).
*/
public void setData( final Number[] data ) {
this.data = data.clone();
}
/**
* Returns the main color for the bars.
*
* @return the main color for the bars, never null.
*/
public Color getColor() {
return color;
}
/**
* Defines the main color for the bars.
*
* @param color the main color for the bars, never null.
*/
public void setColor( final Color color ) {
if ( color == null ) {
throw new NullPointerException();
}
this.color = color;
}
/**
* Returns the color for the background of the graph. This property can be null, in which case the bar will have a
* transparent background.
*
* @return color for the background or null, if the graph has a transparent background color.
*/
public Color getBackground() {
return background;
}
/**
* Defines the color for the background of the graph. This property can be null, in which case the bar will have a
* transparent background.
*
* @param background the background or null, if the graph has a transparent background color.
*/
public void setBackground( final Color background ) {
this.background = background;
}
/**
* Returns the spacing between the bars.
*
* @return the spacing between the bars.
*/
public int getSpacing() {
return spacing;
}
/**
* Defines the spacing between the bars.
*
* @param spacing the spacing between the bars.
*/
public void setSpacing( final int spacing ) {
this.spacing = spacing;
}
public Color getLastColor() {
return lastColor;
}
public void setLastColor( final Color lastColor ) {
this.lastColor = lastColor;
}
/**
* Draws the bar-graph into the given Graphics2D context in the given area. This method will not draw a graph if the
* data given is null or empty.
*
* @param graphics the graphics context on which the bargraph should be rendered.
* @param drawArea the area on which the bargraph should be drawn.
*/
public void draw( final Graphics2D graphics, final Rectangle2D drawArea ) {
if ( graphics == null ) {
throw new NullPointerException();
}
if ( drawArea == null ) {
throw new NullPointerException();
}
final float lastPointDiameter;
final float lastPointRadius;
if ( lastColor == null ) {
lastPointDiameter = 0;
lastPointRadius = 0;
} else {
lastPointDiameter = LAST_POINT_DIAMETER;
lastPointRadius = LAST_POINT_RADIUS;
}
final int height = (int) ( drawArea.getHeight() - lastPointDiameter );
if ( height <= 0 ) {
return;
}
final Graphics2D g2 = (Graphics2D) graphics.create();
if ( background != null ) {
g2.setPaint( background );
g2.draw( drawArea );
}
if ( data.length == 0 ) {
g2.dispose();
return;
}
g2.translate( drawArea.getX(), drawArea.getY() + lastPointRadius );
float scale = GraphUtils.getDivisor( data, height );
final int spacing = getSpacing();
final float usableWidth = (float) ( drawArea.getWidth() - lastPointRadius );
final float width = ( usableWidth - spacing * ( data.length - 1 ) ) / ( data.length - 1 );
float min = computeMin();
int x = 0;
int y = -1;
if ( scale == 0.0 ) {
//special case -- a horizontal straight line
scale = 1.0f;
y = -height / 2;
}
double lastX = 0;
double lastY = 0;
final Line2D.Double line = new Line2D.Double();
g2.setPaint( color );
for ( int i = 0; i < data.length - 1; i++ ) {
final int px1 = x;
x += ( width + spacing );
final int px2 = x;
final Number number = data[ i ];
final Number nextNumber = data[ i + 1 ];
if ( number == null && nextNumber == null ) {
final float delta = height - ( ( 0 - min ) / scale );
line.setLine( px1, y + delta, px2, y + delta );
} else if ( number == null ) {
line.setLine( px1, y + ( height - ( ( 0 - min ) / scale ) ),
px2, y + ( height - ( ( nextNumber.floatValue() - min ) / scale ) ) );
} else if ( nextNumber == null ) {
line.setLine( px1, y + ( height - ( ( number.floatValue() - min ) / scale ) ),
px2, y + ( height - ( ( 0 - min ) / scale ) ) );
} else {
line.setLine( px1, y + ( height - ( ( number.floatValue() - min ) / scale ) ),
px2, y + ( height - ( ( nextNumber.floatValue() - min ) / scale ) ) );
}
lastX = line.getX2();
lastY = line.getY2();
g2.draw( line );
}
if ( lastColor != null ) {
g2.setColor( lastColor );
g2.fill( new Ellipse2D.Double
( lastX - LAST_POINT_RADIUS, lastY - LAST_POINT_RADIUS, LAST_POINT_DIAMETER, LAST_POINT_DIAMETER ) );
}
g2.dispose();
}
private float computeMin() {
float min = Float.MAX_VALUE;
for ( int index = 0; index < data.length; index++ ) {
final Number i = data[ index ];
if ( i == null ) {
continue;
}
final float value = i.floatValue();
if ( value < min ) {
min = value;
}
}
return min;
}
}