/*!
* 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) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.plugin.jfreereport.reportcharts;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.entity.CategoryItemEntity;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.entity.PieSectionEntity;
import org.jfree.chart.entity.XYItemEntity;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.ResourceBundleFactory;
import org.pentaho.reporting.engine.classic.core.imagemap.AbstractImageMapEntry;
import org.pentaho.reporting.engine.classic.core.imagemap.CircleImageMapEntry;
import org.pentaho.reporting.engine.classic.core.imagemap.ImageMap;
import org.pentaho.reporting.engine.classic.core.imagemap.PolygonImageMapEntry;
import org.pentaho.reporting.engine.classic.core.imagemap.RectangleImageMapEntry;
import org.pentaho.reporting.engine.classic.core.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.util.FloatList;
import org.pentaho.reporting.engine.classic.core.util.ReportDrawable;
import org.pentaho.reporting.libraries.base.config.Configuration;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.xmlns.LibXmlInfo;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
public class JFreeChartReportDrawable implements ReportDrawable {
private boolean debugRendering;
private boolean buggyDrawArea;
private JFreeChart chart;
private ChartRenderingInfo chartRenderingInfo;
private Rectangle2D bounds;
public JFreeChartReportDrawable( final JFreeChart chart, final boolean collectRenderingInfo ) {
debugRendering = ClassicEngineBoot.getInstance().getExtendedConfig().
getBoolProperty( "org.pentaho.plugin.jfreereport.reportcharts.DebugChartEntities" );
buggyDrawArea = ClassicEngineBoot.getInstance().getExtendedConfig().
getBoolProperty( "org.pentaho.plugin.jfreereport.reportcharts.DrawAreaBug" );
this.chart = chart;
if ( collectRenderingInfo ) {
this.chartRenderingInfo = new ChartRenderingInfo();
}
}
public void draw( final Graphics2D graphics2D, final Rectangle2D bounds ) {
this.bounds = (Rectangle2D) bounds.clone();
if ( chartRenderingInfo != null ) {
this.chartRenderingInfo.clear();
}
final Graphics2D g2 = (Graphics2D) graphics2D.create();
this.chart.draw( g2, bounds, chartRenderingInfo );
g2.dispose();
if ( chartRenderingInfo == null || debugRendering == false ) {
return;
}
graphics2D.setColor( Color.RED );
final Rectangle2D dataArea = getDataAreaOffset();
final EntityCollection entityCollection = chartRenderingInfo.getEntityCollection();
for ( int i = 0; i < entityCollection.getEntityCount(); i++ ) {
final ChartEntity chartEntity = entityCollection.getEntity( i );
if ( chartEntity instanceof XYItemEntity ||
chartEntity instanceof CategoryItemEntity ||
chartEntity instanceof PieSectionEntity ) {
final Area a = new Area( chartEntity.getArea() );
if ( buggyDrawArea ) {
a.transform( AffineTransform.getTranslateInstance( dataArea.getX(), dataArea.getY() ) );
}
a.intersect( new Area( dataArea ) );
graphics2D.draw( a );
} else {
graphics2D.draw( chartEntity.getArea() );
}
}
}
private Rectangle2D getDataAreaOffset() {
return chartRenderingInfo.getPlotInfo().getDataArea();
}
/**
* Provides the current report configuration of the current report process to the drawable. The report configuration
* can be used to configure the drawing process through the report.
*
* @param config the report configuration.
*/
public void setConfiguration( final Configuration config ) {
}
/**
* Provides the computed stylesheet of the report element that contained this drawable. The stylesheet is immutable.
*
* @param style the stylesheet.
*/
public void setStyleSheet( final StyleSheet style ) {
}
/**
* Defines the resource-bundle factory that can be used to localize the drawing process.
*
* @param bundleFactory the resource-bundle factory.
*/
public void setResourceBundleFactory( final ResourceBundleFactory bundleFactory ) {
}
public JFreeChart getChart() {
return chart;
}
/**
* Returns an optional image-map for the entry.
*
* @param bounds the bounds for which the image map is computed.
* @return the computed image-map or null if there is no image-map available.
*/
public ImageMap getImageMap( final Rectangle2D bounds ) {
if ( chartRenderingInfo == null ) {
return null;
}
final Rectangle2D dataArea = getDataAreaOffset();
final Rectangle2D otherArea = new Rectangle2D.Double();
if ( ( ObjectUtilities.equal( bounds, this.bounds ) ) == false ) {
final BufferedImage image = new BufferedImage( 1, 1, BufferedImage.TYPE_4BYTE_ABGR );
final Graphics2D graphics = image.createGraphics();
draw( graphics, bounds );
graphics.dispose();
}
final ImageMap map = new ImageMap();
final EntityCollection entityCollection = chartRenderingInfo.getEntityCollection();
final int count = entityCollection.getEntityCount();
for ( int i = 0; i < count; i++ ) {
final ChartEntity chartEntity = entityCollection.getEntity( i );
final Shape area = chartEntity.getArea();
final String hrefValue = chartEntity.getURLText();
final String tooltipValue = chartEntity.getToolTipText();
if ( StringUtils.isEmpty( tooltipValue ) == false ||
StringUtils.isEmpty( hrefValue ) == false ) {
final AbstractImageMapEntry entry;
if ( chartEntity instanceof XYItemEntity ||
chartEntity instanceof CategoryItemEntity ||
chartEntity instanceof PieSectionEntity ) {
entry = createMapEntry( area, dataArea );
} else {
entry = createMapEntry( area, otherArea );
}
if ( entry == null ) {
continue;
}
if ( StringUtils.isEmpty( hrefValue ) == false ) {
entry.setAttribute( LibXmlInfo.XHTML_NAMESPACE, "href", hrefValue );
} else {
entry.setAttribute( LibXmlInfo.XHTML_NAMESPACE, "href", "#" );
}
if ( StringUtils.isEmpty( tooltipValue ) == false ) {
entry.setAttribute( LibXmlInfo.XHTML_NAMESPACE, "title", tooltipValue );
}
map.addMapEntry( entry );
}
}
return map;
}
private AbstractImageMapEntry createMapEntry( final Shape area,
final Rectangle2D dataArea ) {
if ( buggyDrawArea ) {
if ( area instanceof Ellipse2D ) {
final Ellipse2D ellipse2D = (Ellipse2D) area;
if ( ellipse2D.getWidth() == ellipse2D.getHeight() ) {
return new CircleImageMapEntry( (float) ( ellipse2D.getCenterX() + dataArea.getX() ),
(float) ( ellipse2D.getCenterY() + dataArea.getY() ), (float) ( ellipse2D.getWidth() / 2 ) );
}
} else if ( area instanceof Rectangle2D ) {
final Rectangle2D rect = (Rectangle2D) area;
return ( new RectangleImageMapEntry( (float) ( rect.getX() + dataArea.getX() ),
(float) ( rect.getY() + dataArea.getY() ),
(float) ( rect.getX() + rect.getWidth() ),
(float) ( rect.getY() + rect.getHeight() ) ) );
}
} else {
if ( area instanceof Ellipse2D ) {
final Ellipse2D ellipse2D = (Ellipse2D) area;
if ( ellipse2D.getWidth() == ellipse2D.getHeight() ) {
return new CircleImageMapEntry( (float) ( ellipse2D.getCenterX() ),
(float) ( ellipse2D.getCenterY() ), (float) ( ellipse2D.getWidth() / 2 ) );
}
} else if ( area instanceof Rectangle2D ) {
final Rectangle2D rect = (Rectangle2D) area;
return ( new RectangleImageMapEntry( (float) ( rect.getX() ),
(float) ( rect.getY() ),
(float) ( rect.getX() + rect.getWidth() ),
(float) ( rect.getY() + rect.getHeight() ) ) );
}
}
final Area a = new Area( area );
if ( buggyDrawArea ) {
a.transform( AffineTransform.getTranslateInstance( dataArea.getX(), dataArea.getY() ) );
}
if ( dataArea.isEmpty() == false ) {
a.intersect( new Area( dataArea ) );
}
final PathIterator pathIterator = a.getPathIterator( null, 2 );
final FloatList floats = new FloatList( 100 );
final float[] coords = new float[ 6 ];
while ( pathIterator.isDone() == false ) {
final int retval = pathIterator.currentSegment( coords );
if ( retval == PathIterator.SEG_MOVETO ||
retval == PathIterator.SEG_LINETO ) {
floats.add( coords[ 0 ] );
floats.add( coords[ 1 ] );
}
pathIterator.next();
}
if ( floats.size() == 0 ) {
return null;
}
return ( new PolygonImageMapEntry( floats.toArray() ) );
}
}