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