Package org.krysalis.jcharts

Source Code of org.krysalis.jcharts.Legend

/***********************************************************************************************
* 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;


import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
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.10 2004/05/31 16:26:13 nathaniel_auvil Exp $
************************************************************************************/
final public class Legend implements HTMLTestable, Serializable
{
  private Chart chart;
  private LegendProperties legendProperties;

  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;


  //---used to extract the legendLabels and paints from the data set and make them easy to loop through
  private ArrayList labels;
  private ArrayList paints;
  private ArrayList shapes = new ArrayList();
  private ArrayList fillPointsFlags = new ArrayList();
  private ArrayList pointOutlinePaints = new ArrayList();
  private ChartType chartType;
  private PointChartProperties pointChartProperties;
  private LineChartProperties lineChartProperties;


  /*********************************************************************************************
   *
   * @param chart
   * @deprecated
   **********************************************************************************************/
  public Legend( Chart chart )
  {
    this.chart = chart;
  }


  /*********************************************************************************************
   *
   * @param chart
   * @param legendProperties
   **********************************************************************************************/
  public Legend( Chart chart, LegendProperties legendProperties )
  {
    this.chart = chart;
    this.legendProperties = legendProperties;
  }


  public void setX( float x )
  {
    this.x = x;
  }


  public 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 )
      {
        this.textProcessor.addLabel( iAxisPlotDataSet.getLegendLabel( i ), this.legendProperties.getChartFont().getFont(), this.chart.getGraphics2D().getFontRenderContext() );

        //---pair labels with paints to get around ugly piechart vs axischart data structure mess
        this.labels.add( iAxisPlotDataSet.getLegendLabel( i ) );
        this.paints.add( iAxisPlotDataSet.getPaint( i ) );

        if( iAxisPlotDataSet.getChartType().equals( ChartType.POINT ) )
        {
          this.chartType = ChartType.POINT;
          this.pointChartProperties = (PointChartProperties) iAxisPlotDataSet.getChartTypeProperties();
          this.shapes.add( pointChartProperties.getShape( i ) );
          this.fillPointsFlags.add( new Boolean( pointChartProperties.getFillPointsFlag( i ) ) );
          this.pointOutlinePaints.add( pointChartProperties.getPointOutlinePaints( i ) );
        }
        if( iAxisPlotDataSet.getChartType().equals( ChartType.LINE ) )
        {
          this.chartType = ChartType.LINE;
          this.lineChartProperties = (LineChartProperties) iAxisPlotDataSet.getChartTypeProperties();
          if( lineChartProperties.getShapes() != null )
          {
            this.shapes.add( lineChartProperties.getShapes()[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 )
      {
        this.textProcessor.addLabel( iPieChartDataSet.getLegendLabel( i ), this.legendProperties.getChartFont().getFont(), this.chart.getGraphics2D().getFontRenderContext() );

        //---pair labels with paints to get around ugly piechart vs axischart data structure mess
        this.labels.add( iPieChartDataSet.getLegendLabel( i ) );
        this.paints.add( iPieChartDataSet.getPaint( 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;
    this.labels = new ArrayList();
    this.paints = new ArrayList();


    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 < numberOfLabels; i++ )
      for( int i = 0; i < this.shapes.size(); i++ )
      {
        //---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
    if( chartType == ChartType.LINE)
    {
      width += this.legendProperties.getIconLineStrokeLength() * 2 * 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;

    //LOOP
    for( int j = 0; j < this.numRows; j++ )
    {
      //LOOP
      for( int i = 0; i < this.numColumns; i++ )
      {
        rectangle.x = posX;

        //---display icon
        g2d.setPaint( (Paint) this.paints.get( labelIndex ) );

        // only Point and Line Charts will have shapes drawn

        if( this.shapes.size() > 0 && this.shapes.size() > labelIndex )
        {
          //Shape shape = (Shape)this.shapes.get( labelIndex);

          //---get the original transform so can reset it
          AffineTransform affineTransform = g2d.getTransform();

          //---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) this.shapes.get( labelIndex ) );

              //---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) this.shapes.get( labelIndex ) );
              }
            }
          }
          else
          {
            // for Point Charts, only draw shape
            if( chartType == ChartType.POINT)
            {
              g2d.draw( (Shape) this.shapes.get( labelIndex ) );
            } else
            // chartType == ChartType.LINE
            // for Line Charts, fill the shape
            {
              //---get the bounds of the shape
              Rectangle2D shapeBounds = ( (Shape) this.shapes.get( labelIndex ) ).getBounds2D();
              double XOffset = shapeBounds.getWidth() / 2;
              double YOffset = shapeBounds.getHeight() / 2;

              g2d.setStroke(this.lineChartProperties.getLineStrokes()[ labelIndex]);

              Line2D.Double line = new Line2D.Double(0, YOffset, this.legendProperties.getIconLineStrokeLength(), YOffset);
              g2d.draw( line );
              // 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 );


              line.x1 = XOffset;
              g2d.draw( line );


              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) this.shapes.get( labelIndex ) );
                }
              }


              // 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 );
          }
        }


        //---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( "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();
  }

}
TOP

Related Classes of org.krysalis.jcharts.Legend

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.