/*
* 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.Rectangle2D;
/**
* A very fast and very simple bar-graph drawable. This code is based on the BarGraph 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 BarGraphDrawable {
private static final int DEFAULT_SPACING = 2;
private static final Color DEFAULT_COLOR = Color.gray;
private static final Color DEFAULT_HIGH_COLOR = Color.black;
private static final Color DEFAULT_LAST_COLOR = Color.red;
private static final Number[] EMPTY = new Number[ 0 ];
private Number[] data;
private Color color;
private Color highColor;
private Color lastColor;
private Color background;
private int spacing;
/**
* Creates a default bargraph drawable with some sensible default colors and spacings.
*/
public BarGraphDrawable() {
this.highColor = BarGraphDrawable.DEFAULT_HIGH_COLOR;
this.lastColor = BarGraphDrawable.DEFAULT_LAST_COLOR;
this.color = BarGraphDrawable.DEFAULT_COLOR;
this.spacing = BarGraphDrawable.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 (Number[]) 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 = (Number[]) 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 highest bars. This property is optional and the high-color can be null.
*
* @return the color for the highest bars, or null if high bars should not be marked specially.
*/
public Color getHighColor() {
return highColor;
}
/**
* Defines the color for the highest bars. This property is optional and the high-color can be null.
*
* @param highColor the color for the highest bars, or null if high bars should not be marked specially.
*/
public void setHighColor( final Color highColor ) {
this.highColor = highColor;
}
/**
* Returns the color for the last bar. This property is optional and the last-bar-color can be null.
*
* @return the color for the last bar in the graph, or null if last bars should not be marked specially.
*/
public Color getLastColor() {
return lastColor;
}
/**
* Defines the color for the last bar. This property is optional and the last-bar-color can be null.
*
* @param lastColor the color for the last bar in the graph, or null if last bars should not be marked specially.
*/
public void setLastColor( final Color lastColor ) {
this.lastColor = lastColor;
}
/**
* 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;
}
/**
* 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 g2 the graphics context on which the bargraph should be rendered.
* @param drawArea the area on which the bargraph should be drawn.
*/
public void draw( Graphics2D g2, Rectangle2D drawArea ) {
if ( g2 == null ) {
throw new NullPointerException();
}
if ( drawArea == null ) {
throw new NullPointerException();
}
final int height = (int) drawArea.getHeight();
if ( height <= 0 ) {
return;
}
final Graphics2D g = (Graphics2D) g2.create();
g.translate( drawArea.getX(), drawArea.getY() );
if ( background != null ) {
g.setBackground( background );
g.clearRect( 0, 0, (int) drawArea.getWidth(), height );
}
if ( data == null || data.length == 0 ) {
g.dispose();
return;
}
final float scale = GraphUtils.getDivisor( data, height );
final float axe = GraphUtils.getAxe( data ) / scale;
final float avg = computeAverage( data );
final float spacing1 = getSpacing();
final float barWidth = (float) ( ( drawArea.getWidth() - ( spacing1 * data.length ) ) / (float) data.length );
float x = 0;
final double canvasHeight = drawArea.getHeight();
final Rectangle2D.Double bar = new Rectangle2D.Double();
for ( int index = 0; index < data.length; index++ ) {
final Number value = data[ index ];
if ( value == null ) {
x += ( barWidth + spacing1 );
continue;
}
final float h = (int) ( value.doubleValue() / scale );
final float intVal = value.floatValue();
if ( index == ( data.length - 1 ) && lastColor != null ) {
g.setPaint( lastColor );
} else if ( intVal < avg || ( highColor == null ) ) {
g.setPaint( color );
} else {
g.setPaint( highColor );
}
if ( axe == 0 ) {
// only positive values, bottom aligned
bar.setRect( x, ( canvasHeight - h ), barWidth, h );
} else if ( axe < 0 ) {
// only negative values, top aligned
bar.setRect( x, 0, barWidth, h < 0 ? -h : h );
} else {
// mixed values, middle aligned
//{-1|-2|-3|-4|0|1|2|3|4|5}
bar.setRect( x, h < 0 ? axe : axe - h, barWidth, h < 0 ? -h : h );
}
g.fill( bar );
x += ( barWidth + spacing1 );
}
g.dispose();
}
/**
* Computes the average for all numbers in the array.
*
* @param data the numbers for which the average should be computed.
* @return the average.
*/
private static float computeAverage( final Number[] data ) {
int total = 0;
int length = 0;
for ( int index = 0; index < data.length; index++ ) {
final Number i = data[ index ];
if ( i == null ) {
continue;
}
total += i.floatValue();
length += 1;
}
return ( total / length );
}
}