krysalis-jcharts/src/java/org/krysalis/jcharts/legend Legend.java,NONE,1.1
"Nathaniel G. Auvil" <[email protected]> Sat, 23 Oct 2004 17:44:50 +0000
| Newsgroups | gmane.comp.krysalis.jcharts.cvs |
|---|---|
| Message-ID | <[email protected]> |
Update of /cvsroot/jcharts/krysalis-jcharts/src/java/org/krysalis/jcharts/legend
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv4384/src/java/org/krysalis/jcharts/legend
Added Files:
Legend.java
Log Message:
needs a lot more work
--- NEW FILE: Legend.java ---
/***********************************************************************************************
* 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 [email protected]
*
* 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.legend;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.BasicStroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import org.krysalis.jcharts.Chart;
import org.krysalis.jcharts.chartData.interfaces.IAxisDataSeries;
import org.krysalis.jcharts.chartData.interfaces.IAxisPlotDataSet;
import org.krysalis.jcharts.chartData.interfaces.IData;
import org.krysalis.jcharts.chartData.interfaces.IPieChartDataSet;
import org.krysalis.jcharts.chartData.processors.TextProcessor;
import org.krysalis.jcharts.properties.LegendAreaProperties;
import org.krysalis.jcharts.properties.LegendProperties;
import org.krysalis.jcharts.properties.LineChartProperties;
import org.krysalis.jcharts.properties.PointChartProperties;
import org.krysalis.jcharts.test.HTMLGenerator;
import org.krysalis.jcharts.test.HTMLTestable;
import org.krysalis.jcharts.types.ChartType;
/*****************************************************************************************
*
* @author Nathaniel Auvil, Sandor Dornbush, Sundar Balasubramanian
* @version $Id: Legend.java,v 1.1 2004/10/23 17:44:48 nathaniel_auvil Exp $
****************************************************************************************/
public class Legend implements HTMLTestable, Serializable
{
private Chart chart;
private LegendProperties legendProperties;
private ArrayList items;
private float iconSide;
//---derived values
private float widestLabelAndColumnPadding;
private int numColumns;
private int numRows;
private TextProcessor textProcessor;
private float x;
private float y;
private float width = 0;
private float height = 0;
// for Bar chart legend shape - size is automatically calculated later on
private Rectangle2D barShape = new Rectangle2D.Double( 0, 0, 0, 0 );
private Stroke barStroke = new BasicStroke();
ChartType chartType;
ArrayList shapes, fillPointsFlags, strokes, pointOutlinePaints, labels, paints;
/***************************************************************************************
*
* @param chart
* @param legendProperties
**************************************************************************************/
public Legend( Chart chart, LegendProperties legendProperties )
{
this.chart = chart;
this.legendProperties = legendProperties;
this.items= new ArrayList();
}
/**************************************************************************************
*
* @param x
*************************************************************************************/
private void setX( float x )
{
this.x = x;
}
/**************************************************************************************
*
* @param y
*************************************************************************************/
private void setY( float y )
{
this.y = y;
}
/***************************************************************************************
*
* @param iAxisDataSeries
* @param chartTitleHeight
**************************************************************************************/
public void computeLegendXY( IAxisDataSeries iAxisDataSeries, float chartTitleHeight )
{
//---PROCESS the size needed for drawing the legend.
this.calculateDrawingValues( iAxisDataSeries );
if( (this.getLegendProperties().getPlacement() == LegendAreaProperties.RIGHT)
|| (this.getLegendProperties().getPlacement() == LegendAreaProperties.LEFT) )
{
if( this.getHeight() > this.chart.getImageHeight() - this.chart.getChartProperties().getEdgePadding() * 2 )
{
this.setY( this.chart.getChartProperties().getEdgePadding() );
}
else
{
this.setY( (this.chart.getImageHeight() / 2) - (this.getHeight() / 2) );
}
if( this.getLegendProperties().getPlacement() == LegendAreaProperties.RIGHT )
{
this.setX( this.chart.getImageWidth() - this.getWidth() - this.chart.getChartProperties().getEdgePadding() );
}
else
//---else, LegendAreaProperties.LEFT
{
this.setX( this.chart.getChartProperties().getEdgePadding() );
}
}
else
//---LegendAreaProperties.BOTTOM, OR LegendAreaProperties.TOP
{
if( this.getWidth() + this.chart.getChartProperties().getEdgePadding() * 2 > this.chart.getImageWidth() )
{
this.setX( this.chart.getChartProperties().getEdgePadding() );
}
else
{
this.setX( (this.chart.getImageWidth() / 2) - (this.getWidth() / 2) );
}
if( this.getLegendProperties().getPlacement() == LegendAreaProperties.BOTTOM )
{
this.setY( this.chart.getImageHeight() - this.getHeight() - this.chart.getChartProperties().getEdgePadding() );
}
else
//---else, LegendAreaProperties.TOP
{
this.setY( this.chart.getChartProperties().getEdgePadding() + chartTitleHeight );
}
}
}
/***************************************************************************************
* Central method for processing data; try to minimize looping. 1) calculate the maximum
* height of labels 2) find the maximum label width
*
* @param iAxisDataSeries
**************************************************************************************/
private void processData( IAxisDataSeries iAxisDataSeries )
{
this.textProcessor = new TextProcessor();
Iterator iterator = iAxisDataSeries.getIAxisPlotDataSetIterator();
//LOOP
while( iterator.hasNext() )
{
this.processLegendLabels( (IAxisPlotDataSet) iterator.next() );
}
}
/***************************************************************************************
* Central method for processing data; try to minimize looping. 1) calculate the maximum
* height of labels 2) find the maximum label width
*
* @param iPieChartDataSet
**************************************************************************************/
private void processData( IPieChartDataSet iPieChartDataSet )
{
this.textProcessor = new TextProcessor();
this.processLegendLabels( iPieChartDataSet );
}
/***************************************************************************************
* Method for processing data for AxisPlot datasets; try to minimize looping.
* 1) calculate the maximum height of labels
* 2) find the maximum label width
*
* @param iAxisPlotDataSet
**************************************************************************************/
private void processLegendLabels( IAxisPlotDataSet iAxisPlotDataSet )
{
for( int i = 0; i < iAxisPlotDataSet.getNumberOfLegendLabels(); i++ )
{
//---StockChartDataSets could have NULLs depending on the data
if( iAxisPlotDataSet.getLegendLabel( i ) != null )
{
//TODO this probably should be moved into the Item Class.
this.textProcessor.addLabel( iAxisPlotDataSet.getLegendLabel( i ),
this.legendProperties.getChartFont().getFont(),
this.chart.getGraphics2D().getFontRenderContext() );
if( iAxisPlotDataSet.getChartType().equals( ChartType.POINT )
|| iAxisPlotDataSet.getChartType().equals( ChartType.POINT_RHS ) )
{
PointChartProperties pointChartProperties = (PointChartProperties) iAxisPlotDataSet.getChartTypeProperties();
Symbol symbol= new Symbol( pointChartProperties.getShape( i ),
iAxisPlotDataSet.getPaint( i ),
pointChartProperties.getPointOutlinePaints( i ) );
this.items.add( new Item( symbol, iAxisPlotDataSet.getLegendLabel( i ) ) );
}
else if( iAxisPlotDataSet.getChartType().equals( ChartType.LINE )
|| iAxisPlotDataSet.getChartType().equals( ChartType.LINE_RHS ) )
{
LineChartProperties lineChartProperties = (LineChartProperties) iAxisPlotDataSet.getChartTypeProperties();
Shape shape;
if( lineChartProperties.getShapes() != null )
{
shape= lineChartProperties.getShapes()[ i ];
}
else
{
shape= null;
}
LineSymbol lineSymbol= new LineSymbol( shape, iAxisPlotDataSet.getPaint( i ), lineChartProperties.getLineStrokes()[ i ] );
this.items.add( new Item( lineSymbol, iAxisPlotDataSet.getLegendLabel( i ) ) );
}
else
{
Symbol symbol= new Symbol( PointChartProperties.SHAPE_SQUARE,
iAxisPlotDataSet.getPaint( i ),
this.legendProperties.getIconBorderPaint() );
this.items.add( new Item( symbol, iAxisPlotDataSet.getLegendLabel( i ) ) );
}
}
}
}
/***************************************************************************************
* Method for processing data for PieCharts; try to minimize looping. 1) calculate the
* maximum height of labels 2) find the maximum label width
*
* @param iPieChartDataSet
**************************************************************************************/
private void processLegendLabels( IPieChartDataSet iPieChartDataSet )
{
for( int i = 0; i < iPieChartDataSet.getNumberOfLegendLabels(); i++ )
{
//---StockChartDataSets could have NULLs depending on the data
if( iPieChartDataSet.getLegendLabel( i ) != null )
{
//TODO this probably should be moved into the Item Class.
this.textProcessor.addLabel( iPieChartDataSet.getLegendLabel( i ),
this.legendProperties.getChartFont().getFont(),
this.chart.getGraphics2D().getFontRenderContext() );
Symbol symbol= new Symbol( PointChartProperties.SHAPE_SQUARE,
iPieChartDataSet.getPaint( i ),
this.legendProperties.getIconBorderPaint() );
this.items.add( new Item( symbol, iPieChartDataSet.getLegendLabel( i ) ) );
}
}
}
/***************************************************************************************
*
**************************************************************************************/
public LegendProperties getLegendProperties()
{
return this.legendProperties;
}
/***************************************************************************************
* Calculates the width and height needed to display the Legend. Use the getWidth() and
* getHeight() methods to extract this information.
*
* @param iData can pass either the IPieChartDataSet or the IChartDataSeries to this.
**************************************************************************************/
public void calculateDrawingValues( IData iData )
{
int numberOfLabels;
if( iData instanceof IAxisDataSeries )
{
IAxisDataSeries iAxisDataSeries = (IAxisDataSeries) iData;
this.processData( iAxisDataSeries );
numberOfLabels = iAxisDataSeries.getTotalNumberOfDataSets();
}
else
{
IPieChartDataSet iPieChartDataSet = (IPieChartDataSet) iData;
this.processData( iPieChartDataSet );
numberOfLabels = iPieChartDataSet.getNumberOfLegendLabels();
}
//---make the icon proportional to the Font being used.
this.iconSide = (float) .50 * this.textProcessor.getTallestLabel();
//---for POINT and LINE charts, set iconSide to max width of legend shapes
if( (chartType == ChartType.POINT) || (chartType == ChartType.LINE) )
{
for( int i = 0; i < this.shapes.size(); i++ )
{
Shape shape;
//---get the bounds of the shape
if( this.shapes.get( i ) != null )
{
shape = (Shape) this.shapes.get( i );
}
else
{
shape = PointChartProperties.SHAPE_SQUARE;
}
this.iconSide = Math.max( this.iconSide, (float) shape.getBounds2D().getWidth() );
/*
//---get the bounds of the shape
try
{
Double shapeWidthDouble = new Double( ( ( (Shape) this.shapes.get( i ) ).getBounds2D().getWidth() ) );
float shapeWidth = shapeWidthDouble.floatValue();
this.iconSide = Math.max(this.iconSide, shapeWidth);
}
catch (NullPointerException npe) {
// Looks like in 0.74 it was quite acceptable to make shape = null
// we should probably catch all these and render a "null" shape to the legend
System.err.println("Warning: legend shape is null");
npe.printStackTrace();
}
*/
}
}
this.determineWidthAndHeight( numberOfLabels );
}
/***************************************************************************************
*
**************************************************************************************/
public float getWidth()
{
return this.width;
}
/***************************************************************************************
*
**************************************************************************************/
public int getHeight()
{
//why not return a float here?
return ((int) Math.ceil( this.height ));
}
/***************************************************************************************
* Determines the dimensions needed for the Legend and creates the image for it.
*
**************************************************************************************/
private void determineWidthAndHeight( int numberOfLabels )
{
//---start with the padding no matter how many columns or specified width
width = this.legendProperties.getEdgePadding() * 2;
height = width;
//---if don't care how many columns or the number of labels is less than num columns specified, all in one row.
if( this.legendProperties.getNumColumns() == LegendAreaProperties.COLUMNS_AS_MANY_AS_NEEDED
|| this.legendProperties.getNumColumns() >= numberOfLabels )
{
this.numColumns = numberOfLabels;
width += this.textProcessor.getTotalLabelWidths();
this.numRows = 1;
}
//---else, more than one row
else
{
//---one less addition to do when looping.
this.widestLabelAndColumnPadding = this.textProcessor.getWidestLabel() + this.legendProperties.getColumnPadding();
if( legendProperties.getNumColumns() == LegendAreaProperties.COLUMNS_FIT_TO_IMAGE )
{
// calculate that the columns match exactly
float actualWidth = legendProperties.getSize().width;
float widestLabelColumnAndIcon = widestLabelAndColumnPadding + iconSide +
legendProperties.getIconPadding() + legendProperties.getColumnPadding();
numColumns = (int) (actualWidth / widestLabelColumnAndIcon);
numColumns = Math.min( numColumns, numberOfLabels );
}
else
{
numColumns = this.legendProperties.getNumColumns();
}
width += this.textProcessor.getWidestLabel() * this.numColumns;
this.numRows = (int) Math.ceil( (double) numberOfLabels / (double) this.numColumns );
}
//---account for icons
width += (this.iconSide + this.legendProperties.getIconPadding()) * this.numColumns;
//---account for space between each column
width += this.legendProperties.getColumnPadding() * (this.numColumns - 1);
//---account for lineStrokes for LINE charts
//TODO this is NOT correct. What about a combo chart?
if( chartType == ChartType.LINE )
{
//width += this.legendProperties.getIconLineStrokeLength() * 2 * this.numColumns;
width += this.legendProperties.getIconLineStrokeLength() * this.numColumns;
}
//---account for each row
height += (this.textProcessor.getTallestLabel() * this.numRows);
//---account for each row padding
height += (this.legendProperties.getRowPadding() * (this.numRows - 1));
}
/***************************************************************************************
* Renders the legend.
*
**************************************************************************************/
public void render()
{
Graphics2D g2d = this.chart.getGraphics2D();
//---get the bounds of the image
Rectangle2D.Float rectangle = new Rectangle2D.Float( this.x, this.y, width - 1, this.height - 1 );
//---fill the background of the Legend with the specified Paint
if( this.legendProperties.getBackgroundPaint() != null )
{
g2d.setPaint( this.legendProperties.getBackgroundPaint() );
g2d.fill( rectangle );
}
//---draw Legend border
if( this.legendProperties.getBorderStroke() != null )
{
this.legendProperties.getBorderStroke().draw( g2d, rectangle );
}
//---dont think we want this so text will be clean but leave commented out.
//g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );
//---set the font and text color.
g2d.setFont( this.legendProperties.getChartFont().getFont() );
//---icon coordinates
rectangle.y += this.legendProperties.getEdgePadding() + (this.textProcessor.getTallestLabel() / 2) - (this.iconSide / 2);
rectangle.width = this.iconSide;
rectangle.height = this.iconSide;
float posX = this.x + this.legendProperties.getEdgePadding();
float fontY = rectangle.y + rectangle.height;
//---pre calculate utility values
float yIncrement = this.textProcessor.getTallestLabel() + this.legendProperties.getRowPadding();
float iconAndPaddingWidth = this.iconSide + this.legendProperties.getIconPadding();
int labelIndex = 0;
Shape shape = (Shape) this.shapes.get( labelIndex );
//---get the original transform so can reset it
AffineTransform affineTransform = g2d.getTransform();
//LOOP
for( int j = 0; j < this.numRows; j++ )
{
//LOOP
for( int i = 0; i < this.numColumns; i++ )
{
rectangle.x = posX;
shape = (Shape) this.shapes.get( labelIndex );
//---display icon
g2d.setPaint( (Paint) this.paints.get( labelIndex ) );
//TODO still need to draw a line for Line Charts
if( shape != null )
{
//TODO what about scatter plots?
//TODO what about NULL shapes on a Line Chart?
if( this.shapes.size() > 0 && this.shapes.size() > labelIndex && !shape.equals( barShape ) )
{
//---translate the Shape into position
g2d.translate( rectangle.x, rectangle.y );
if( this.fillPointsFlags.size() > 0 )
{
if( ((Boolean) this.fillPointsFlags.get( labelIndex )).booleanValue() )
{
g2d.fill( shape );
//---if we are filling the points, see if we should outline the Shape
//---applicable only to POINt charts
if( this.pointOutlinePaints.get( labelIndex ) != null )
{
g2d.setPaint( (Paint) this.pointOutlinePaints.get( labelIndex ) );
g2d.draw( shape );
}
}
}
else
{
//---get the bounds of the shape
Rectangle2D shapeBounds = shape.getBounds2D();
double xOffset = shapeBounds.getWidth() / 2;
double yOffset = shapeBounds.getHeight() / 2;
g2d.setStroke( (Stroke) this.strokes.get( labelIndex ) );
g2d.draw( new Line2D.Double( 0, yOffset, this.legendProperties.getIconLineStrokeLength(), yOffset ) );
// move posX to account for the lineStroke before the shape. for example: ---o
//posX += this.legendProperties.getIconLineStrokeLength();
//---translate the Shape to adjust for the IconLineStrokeLength
//g2d.translate( this.legendProperties.getIconLineStrokeLength() - xOffset, 0 );
g2d.translate( xOffset, 0 );
//line.x1 = xOffset;
//g2d.draw( line );
//---Line Charts fill with the same Paint as the lines are drawn with
g2d.fill( (Shape) this.shapes.get( labelIndex ) );
//---border around icon
if( this.legendProperties.getIconBorderStroke() != null && this.pointOutlinePaints.size() != 0 )
{
if( this.pointOutlinePaints != null )
{
g2d.setStroke( this.legendProperties.getIconBorderStroke() );
g2d.setPaint( (Paint) this.pointOutlinePaints.get( labelIndex ) );
g2d.draw( shape );
}
}
//---move posX to account for the lineStroke after the shape. for example: o---
posX += this.legendProperties.getIconLineStrokeLength();
}
//---reset original transform
g2d.setTransform( affineTransform );
}
// for other charts, just draw a rectangle
else
{
g2d.fill( rectangle );
//---border around icon
if( this.legendProperties.getIconBorderStroke() != null )
{
g2d.setStroke( this.legendProperties.getIconBorderStroke() );
g2d.setPaint( this.legendProperties.getIconBorderPaint() );
g2d.draw( rectangle );
}
posX += iconAndPaddingWidth;
}
}
//---draw the label
g2d.setPaint( this.legendProperties.getChartFont().getPaint() );
//posX += iconAndPaddingWidth;
g2d.drawString( (String) this.labels.get( labelIndex ), posX, fontY );
if( this.legendProperties.getNumColumns() == LegendAreaProperties.COLUMNS_AS_MANY_AS_NEEDED
|| this.legendProperties.getNumColumns() >= this.labels.size() )
{
//---each column is as wide as it needs to be
posX += this.textProcessor.getTextTag( labelIndex ).getWidth() + this.legendProperties.getColumnPadding();
}
else
{
//---all columns have the same width
posX += this.widestLabelAndColumnPadding;
}
labelIndex++;
//---if no more labels, we are done.
if( labelIndex == this.labels.size() ) break;
}
posX = this.x + this.legendProperties.getEdgePadding();
fontY += yIncrement;
rectangle.y += yIncrement;
}
}
/***************************************************************************************
* Enables the testing routines to display the contents of this Object.
*
* @param htmlGenerator
**************************************************************************************/
public void toHTML( HTMLGenerator htmlGenerator )
{
htmlGenerator.legendTableStart();
htmlGenerator.addTableRow( "X", Float.toString( this.x ) );
htmlGenerator.addTableRow( "Y", Float.toString( this.y ) );
htmlGenerator.addTableRow( "numColumns", Integer.toString( this.numColumns ) );
htmlGenerator.addTableRow( "numRows", Integer.toString( this.numRows ) );
htmlGenerator.addTableRow( "Width", Float.toString( this.width ) );
htmlGenerator.addTableRow( "Height", Float.toString( this.height ) );
htmlGenerator.addTableRow( "Icon Side", Float.toString( this.iconSide ) );
htmlGenerator.innerTableRowStart();
this.legendProperties.toHTML( htmlGenerator );
htmlGenerator.innerTableRowEnd();
htmlGenerator.legendTableEnd();
}
}
-------------------------------------------------------
This SF.net email is sponsored by: IT Product Guide on ITManagersJournal
Use IT products in your business? Tell us what you think of them. Give us
Your Opinions, Get Free ThinkGeek Gift Certificates! Click to find out more
http://productguide.itmanagersjournal.com/guidepromo.tmpl