/*!
* 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.JFreeChart;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickUnit;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberAxis3D;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.DatasetRenderingOrder;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.renderer.category.LineRenderer3D;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.Dataset;
import org.pentaho.plugin.jfreereport.reportcharts.backport.FastNumberTickUnit;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.function.Expression;
import org.pentaho.reporting.libraries.formatting.FastDecimalFormat;
import java.awt.*;
import java.text.DateFormatSymbols;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
public class BarLineChartExpression extends BarChartExpression implements MultiPlotChartExpression {
private static final long serialVersionUID = 7082583397390897215L;
private String linesDataSource;
private String secondValueAxisLabel; //$NON-NLS-1$
private ArrayList<String> lineSeriesColor;
private String linesLabelFont; //$NON-NLS-1$
@Deprecated
private String linesTickLabelFont; //$NON-NLS-1$
private String lineTicksLabelFormat; //$NON-NLS-1$
private String lineStyle; //$NON-NLS-1$
private float lineWidth;
private boolean markersVisible;
private boolean sharedRangeAxis;
private double linePeriodCount;
private Class lineTimePeriod;
private Font lineTitleFont;
private Font lineTickFont;
private double lineRangeMinimum;
private double lineRangeMaximum;
private boolean lineAxisAutoRange;
private boolean lineAxisIncludesZero;
private boolean lineAxisStickyZero;
//constructor
public BarLineChartExpression() {
lineSeriesColor = new ArrayList<String>();
secondValueAxisLabel = "";
linesLabelFont = "SansSerif--8";
linesTickLabelFont = "SansSerif--8";
lineWidth = 1.0f;
markersVisible = false;
linePeriodCount = 0;
lineRangeMinimum = 0;
lineRangeMaximum = 1;
lineAxisAutoRange = true;
lineAxisIncludesZero = true;
lineAxisStickyZero = true;
}
public boolean isLineAxisIncludesZero() {
return lineAxisIncludesZero;
}
public void setLineAxisIncludesZero( final boolean lineAxisIncludesZero ) {
this.lineAxisIncludesZero = lineAxisIncludesZero;
}
public boolean isLineAxisStickyZero() {
return lineAxisStickyZero;
}
public void setLineAxisStickyZero( final boolean lineAxisStickyZero ) {
this.lineAxisStickyZero = lineAxisStickyZero;
}
public Font getLineTitleFont() {
return lineTitleFont;
}
public void setLineTitleFont( final Font lineTitleFont ) {
this.lineTitleFont = lineTitleFont;
}
public Font getLineTickFont() {
return lineTickFont;
}
public void setLineTickFont( final Font lineTickFont ) {
this.lineTickFont = lineTickFont;
}
public double getLineRangeMinimum() {
return lineRangeMinimum;
}
public void setLineRangeMinimum( final double lineRangeMinimum ) {
this.lineRangeMinimum = lineRangeMinimum;
}
public double getLineRangeMaximum() {
return lineRangeMaximum;
}
public void setLineRangeMaximum( final double lineRangeMaximum ) {
this.lineRangeMaximum = lineRangeMaximum;
}
public double getLinePeriodCount() {
return linePeriodCount;
}
public void setLinePeriodCount( final double linePeriodCount ) {
this.linePeriodCount = linePeriodCount;
}
public Class getLineTimePeriod() {
return lineTimePeriod;
}
public void setLineTimePeriod( final Class lineTimePeriod ) {
this.lineTimePeriod = lineTimePeriod;
}
public boolean isSharedRangeAxis() {
return sharedRangeAxis;
}
public void setSharedRangeAxis( final boolean sharedRangeAxis ) {
this.sharedRangeAxis = sharedRangeAxis;
}
/**
* Return a completly separated copy of this function. The copy does no longer share any changeable objects with the
* original function.
*
* @return a copy of this function.
*/
public Expression getInstance() {
final BarLineChartExpression chartExpression = (BarLineChartExpression) super.getInstance();
chartExpression.lineSeriesColor = (ArrayList<String>) lineSeriesColor.clone();
return chartExpression;
}
public String getLinesDataSource() {
return linesDataSource;
}
public void setLinesDataSource( final String linesDataSource ) {
this.linesDataSource = linesDataSource;
}
public String getSecondaryDataSet() {
return getLinesDataSource();
}
public void setSecondaryDataSet( final String dataset ) {
setLinesDataSource( dataset );
}
public String getLinesLabelFont() {
return linesLabelFont;
}
public void setLinesLabelFont( final String linesLabelFont ) {
this.linesLabelFont = linesLabelFont;
}
@Deprecated
public String getLinesTickLabelFont() {
return linesTickLabelFont;
}
@Deprecated
public void setLinesTickLabelFont( final String linesTickLabelFont ) {
this.linesTickLabelFont = linesTickLabelFont;
}
public String getSecondValueAxisLabel() {
return secondValueAxisLabel;
}
public void setSecondValueAxisLabel( final String secondValueAxisLabel ) {
this.secondValueAxisLabel = secondValueAxisLabel;
}
public String getLineTicksLabelFormat() {
return lineTicksLabelFormat;
}
public void setLineTicksLabelFormat( final String lineTicksLabelFormat ) {
this.lineTicksLabelFormat = lineTicksLabelFormat;
}
public void setLineSeriesColor( final int index, final String field ) {
if ( lineSeriesColor.size() == index ) {
lineSeriesColor.add( field );
} else {
lineSeriesColor.set( index, field );
}
}
public String getLineSeriesColor( final int index ) {
return (String) this.lineSeriesColor.get( index );
}
public int getLineSeriesColorCount() {
return this.lineSeriesColor.size();
}
public String[] getLineSeriesColor() {
final Object[] toArray = this.lineSeriesColor.toArray( new String[ this.lineSeriesColor.size() ] );
return (String[]) toArray;
}
public void setLineSeriesColor( final String[] fields ) {
this.lineSeriesColor.clear();
this.lineSeriesColor.addAll( Arrays.asList( fields ) );
}
/**
* @return returns the style set for the lines
*/
public String getLineStyle() {
return lineStyle;
}
/**
* @param value set the style for all line series
*/
public void setLineStyle( final String value ) {
lineStyle = value;
}
/**
* @return the width of all line series Valid values are float numbers zero or greater
*/
public float getLineWidth() {
return lineWidth;
}
/**
* @param value set the width of all line series Valid values are float numbers zero or greater
*/
public void setLineWidth( final float value ) {
lineWidth = value;
}
/**
* @return boolean whether the markers (data points) for all series are displayed
*/
public boolean isMarkersVisible() {
return markersVisible;
}
/**
* @param markersVisible set whether the markers (data points) for all series should be displayed
*/
public void setMarkersVisible( final boolean markersVisible ) {
this.markersVisible = markersVisible;
}
/**
* @deprecated
*/
public String getBarsTickLabelFont() {
return convertFontToString( getRangeTickFont() );
}
/**
* @deprecated
*/
public void setBarsTickLabelFont( final String barsTickLabelFont ) {
setRangeTickFont( Font.decode( barsTickLabelFont ) );
}
/**
* @deprecated
*/
public String getCategoryTickLabelFont() {
return getLabelFont();
}
/**
* @deprecated
*/
public void setCategoryTickLabelFont( final String categoryTickLabelFont ) {
this.setLabelFont( categoryTickLabelFont );
}
/**
* @return
* @deprecated duplicate property.
*/
public String getBarTicksLabelFormat() {
return getRangeTickFormatString();
}
/**
* @param lineTicksLabelDateFormat
* @deprecated duplicate property.
*/
public void setBarTicksLabelFormat( final String lineTicksLabelDateFormat ) {
setRangeTickFormatString( lineTicksLabelDateFormat );
}
/**
* @deprecated
*/
public String getBarsLabelFont() {
return convertFontToString( getRangeTitleFont() );
}
/**
* @deprecated
*/
public void setBarsLabelFont( final String barsLabelFont ) {
setRangeTitleFont( Font.decode( barsLabelFont ) );
}
public boolean isLineAxisAutoRange() {
return lineAxisAutoRange;
}
public void setLineAxisAutoRange( final boolean lineAxisAutoRange ) {
this.lineAxisAutoRange = lineAxisAutoRange;
}
public JFreeChart computeCategoryChart( final CategoryDataset barsDataset ) {
final JFreeChart chart = super.computeCategoryChart( barsDataset );
final CategoryDataset linesDataset = createLinesDataset();
//Create the renderer with the barchart, use a different bar renderer depending
//if 3D chart or not
final CategoryPlot plot = chart.getCategoryPlot();
final CategoryItemRenderer lineRenderer;
if ( isThreeD() ) {
lineRenderer = new LineRenderer3D();
} else {
lineRenderer = new LineAndShapeRenderer();
}
//add lines dataset and axis to plot
if ( linesDataset != null ) {
//Create Axis Objects
final ValueAxis linesAxis;
if ( isSharedRangeAxis() ) {
linesAxis = plot.getRangeAxis();
} else if ( isThreeD() ) {
linesAxis = new NumberAxis3D( getSecondValueAxisLabel() );
} else {
linesAxis = new NumberAxis( getSecondValueAxisLabel() );
}
plot.setRenderer( 1, lineRenderer );
plot.setDataset( 1, linesDataset );
plot.setRangeAxis( 1, linesAxis );
//map lines to second axis
plot.mapDatasetToRangeAxis( 1, 1 );
//set location of second axis
plot.setRangeAxisLocation( 1, AxisLocation.BOTTOM_OR_RIGHT );
}
//set rendering order
plot.setDatasetRenderingOrder( DatasetRenderingOrder.FORWARD );
return chart;
}
private CategoryDataset createLinesDataset() {
final Object maybeCollector = getDataRow().get( getLinesDataSource() );
final Dataset dataset;
if ( maybeCollector instanceof ICollectorFunction ) {
final ICollectorFunction collector = (ICollectorFunction) maybeCollector;
dataset = (Dataset) collector.getDatasourceValue();
} else if ( maybeCollector instanceof CollectorFunctionResult ) {
final CollectorFunctionResult collector = (CollectorFunctionResult) maybeCollector;
dataset = collector.getDataSet();
} else {
dataset = null;
}
final CategoryDataset linesDataset;
if ( dataset instanceof CategoryDataset ) {
linesDataset = (CategoryDataset) dataset;
} else {
linesDataset = null;
}
return linesDataset;
}
protected void configureChart( final JFreeChart chart ) {
super.configureChart( chart );
final CategoryPlot plot = chart.getCategoryPlot();
if ( isSharedRangeAxis() == false ) {
final ValueAxis linesAxis = plot.getRangeAxis( 1 );
if ( linesAxis instanceof NumberAxis ) {
final NumberAxis numberAxis = (NumberAxis) linesAxis;
numberAxis.setAutoRangeIncludesZero( isLineAxisIncludesZero() );
numberAxis.setAutoRangeStickyZero( isLineAxisStickyZero() );
if ( getLinePeriodCount() > 0 ) {
if ( getLineTicksLabelFormat() != null ) {
final FastDecimalFormat formatter = new FastDecimalFormat
( getLineTicksLabelFormat(), getResourceBundleFactory().getLocale() );
numberAxis.setTickUnit( new FastNumberTickUnit( getLinePeriodCount(), formatter ) );
} else {
numberAxis.setTickUnit( new FastNumberTickUnit( getLinePeriodCount() ) );
}
} else {
if ( getLineTicksLabelFormat() != null ) {
final DecimalFormat formatter = new DecimalFormat
( getLineTicksLabelFormat(), new DecimalFormatSymbols( getResourceBundleFactory().getLocale() ) );
numberAxis.setNumberFormatOverride( formatter );
}
}
} else if ( linesAxis instanceof DateAxis ) {
final DateAxis numberAxis = (DateAxis) linesAxis;
if ( getLinePeriodCount() > 0 && getLineTimePeriod() != null ) {
if ( getLineTicksLabelFormat() != null ) {
final SimpleDateFormat formatter = new SimpleDateFormat
( getLineTicksLabelFormat(), new DateFormatSymbols( getResourceBundleFactory().getLocale() ) );
numberAxis.setTickUnit
( new DateTickUnit( getDateUnitAsInt( getLineTimePeriod() ), (int) getLinePeriodCount(), formatter ) );
} else {
numberAxis.setTickUnit
( new DateTickUnit( getDateUnitAsInt( getLineTimePeriod() ), (int) getLinePeriodCount() ) );
}
} else if ( getRangeTickFormatString() != null ) {
final SimpleDateFormat formatter = new SimpleDateFormat
( getRangeTickFormatString(), new DateFormatSymbols( getResourceBundleFactory().getLocale() ) );
numberAxis.setDateFormatOverride( formatter );
}
}
if ( linesAxis != null ) {
final Font labelFont = Font.decode( getLabelFont() );
linesAxis.setLabelFont( labelFont );
linesAxis.setTickLabelFont( labelFont );
if ( getLineTitleFont() != null ) {
linesAxis.setLabelFont( getLineTitleFont() );
}
if ( getLineTickFont() != null ) {
linesAxis.setTickLabelFont( getLineTickFont() );
}
final int level = getRuntime().getProcessingContext().getCompatibilityLevel();
if ( ClassicEngineBoot.isEnforceCompatibilityFor( level, 3, 8 ) ) {
if ( getRangeMinimum() != 0 ) {
linesAxis.setLowerBound( getLineRangeMinimum() );
}
if ( getRangeMaximum() != 1 ) {
linesAxis.setUpperBound( getLineRangeMaximum() );
}
if ( getLineRangeMinimum() == 0 && getLineRangeMaximum() == 1 ) {
linesAxis.setLowerBound( 0 );
linesAxis.setUpperBound( 1 );
linesAxis.setAutoRange( true );
}
} else {
linesAxis.setLowerBound( getLineRangeMinimum() );
linesAxis.setUpperBound( getLineRangeMaximum() );
linesAxis.setAutoRange( isLineAxisAutoRange() );
}
}
}
final LineAndShapeRenderer linesRenderer = (LineAndShapeRenderer) plot.getRenderer( 1 );
if ( linesRenderer != null ) {
//set stroke with line width
linesRenderer.setStroke( translateLineStyle( lineWidth, lineStyle ) );
//hide shapes on line
linesRenderer.setShapesVisible( isMarkersVisible() );
linesRenderer.setBaseShapesFilled( isMarkersVisible() );
//set colors for each line
for ( int i = 0; i < lineSeriesColor.size(); i++ ) {
final String s = (String) lineSeriesColor.get( i );
linesRenderer.setSeriesPaint( i, parseColorFromString( s ) );
}
}
}
private String convertFontToString( final Font font ) {
if ( font == null ) {
return null;
}
final String fontName = font.getFamily();
final int fontSize = font.getSize();
final int fontStyle = font.getStyle();
final String fontStyleText;
if ( ( fontStyle & ( Font.BOLD | Font.ITALIC ) ) == ( Font.BOLD | Font.ITALIC ) ) {
fontStyleText = "BOLDITALIC";
} else if ( ( fontStyle & Font.BOLD ) == Font.BOLD ) {
fontStyleText = "BOLD";
} else if ( ( fontStyle & Font.ITALIC ) == Font.ITALIC ) {
fontStyleText = "ITALIC";
} else {
fontStyleText = "PLAIN";
}
return ( fontName + "-" + fontStyleText + "-" + fontSize );
}
public void reconfigureForCompatibility( final int versionTag ) {
super.reconfigureForCompatibility( versionTag );
if ( ClassicEngineBoot.isEnforceCompatibilityFor( versionTag, 3, 8 ) ) {
setLineAxisAutoRange( getLineRangeMinimum() == 0 && getLineRangeMaximum() == 1 );
}
}
}