/*
* Copyright 2002 - 2013 Pentaho Corporation. All rights reserved.
*
* This software was developed by Pentaho Corporation and is provided under the terms
* of the Mozilla Public License, Version 1.1, or any later version. You may not use
* this file except in compliance with the license. If you need a copy of the license,
* please go to http://www.mozilla.org/MPL/MPL-1.1.txt. TThe Initial Developer is Pentaho Corporation.
*
* Software distributed under the Mozilla Public License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. Please refer to
* the license for the specific language governing your rights and limitations.
*/
package org.pentaho.plugin.jfreereport.reportcharts;
import org.jfree.chart.ChartFactory;
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.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLine3DRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.general.Dataset;
import org.jfree.data.time.Day;
import org.jfree.data.time.Hour;
import org.jfree.data.time.Minute;
import org.jfree.data.time.Month;
import org.jfree.data.time.Second;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.Year;
import org.jfree.data.xy.XYDataset;
import org.pentaho.plugin.jfreereport.reportcharts.backport.ExtTimeTableXYDataset;
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 XYAreaLineChartExpression extends XYAreaChartExpression implements MultiPlotChartExpression {
private static final long serialVersionUID = 7082583397390897215L;
private String secondValueAxisLabel; //$NON-NLS-1$
private ArrayList lineSeriesColor;
private Font linesLabelFont; //$NON-NLS-1$
private Font linesTickLabelFont; //$NON-NLS-1$
private String lineTicksLabelFormat; //$NON-NLS-1$
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 String secondaryDataSet;
private boolean lineAxisIncludesZero;
private boolean lineAxisStickyZero;
//constructor
public XYAreaLineChartExpression() {
lineSeriesColor = new ArrayList();
secondValueAxisLabel = "";
linePeriodCount = 0;
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 XYAreaLineChartExpression chartExpression = (XYAreaLineChartExpression) super.getInstance();
chartExpression.lineSeriesColor = (ArrayList) lineSeriesColor.clone();
return chartExpression;
}
public String getSecondaryDataSet() {
return secondaryDataSet;
}
public void setSecondaryDataSet( final String dataset ) {
secondaryDataSet = dataset;
}
public String getSecondValueAxisLabel() {
return secondValueAxisLabel;
}
public void setSecondValueAxisLabel( final String secondValueAxisLabel ) {
this.secondValueAxisLabel = secondValueAxisLabel;
}
public Font getLinesLabelFont() {
return linesLabelFont;
}
public void setLinesLabelFont( final Font linesLabelFont ) {
this.linesLabelFont = linesLabelFont;
}
public Font getLinesTickLabelFont() {
return linesTickLabelFont;
}
public void setLinesTickLabelFont( final Font linesTickLabelFont ) {
this.linesTickLabelFont = linesTickLabelFont;
}
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 ) );
}
private XYDataset createLinesDataset() {
final Object maybeCollector = getDataRow().get( getSecondaryDataSet() );
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 XYDataset linesDataset;
if ( dataset instanceof XYDataset ) {
linesDataset = (XYDataset) dataset;
} else {
linesDataset = null;
}
return linesDataset;
}
protected JFreeChart computeXYChart( final XYDataset xyDataset ) {
final JFreeChart chart;
if ( xyDataset instanceof TimeSeriesCollection ) {
if ( isStacked() ) {
final ExtTimeTableXYDataset tableXYDataset = convertToTable( xyDataset );
chart = createTimeSeriesChart( computeTitle(), getDomainTitle(), getRangeTitle(), tableXYDataset,
isShowLegend(), false, false, isStacked() );
} else {
chart = createTimeSeriesChart( computeTitle(), getDomainTitle(), getRangeTitle(), xyDataset,
isShowLegend(), false, false, isStacked() );
}
} else {
final PlotOrientation orientation = computePlotOrientation();
if ( isStacked() ) {
chart = createStackedXYAreaChart( computeTitle(), getDomainTitle(), getRangeTitle(),
xyDataset, orientation, isShowLegend(), false, false );
} else {
chart = ChartFactory.createXYAreaChart( computeTitle(), getDomainTitle(), getRangeTitle(),
xyDataset, orientation, isShowLegend(), false, false );
}
}
configureLogarithmicAxis( chart.getXYPlot() );
configureLineChart( chart.getXYPlot() );
return chart;
}
protected void configureLineChart( final XYPlot plot ) {
final XYDataset linesDataset = createLinesDataset();
if ( linesDataset == null || linesDataset.getSeriesCount() == 0 ) {
return;
}
//Create Axis Objects
final ValueAxis linesAxis;
if ( isSharedRangeAxis() ) {
linesAxis = plot.getRangeAxis();
} else if ( isThreeD() ) {
linesAxis = new NumberAxis3D( getSecondValueAxisLabel() );
} else {
linesAxis = new NumberAxis( getSecondValueAxisLabel() );
}
final XYItemRenderer lineRenderer;
if ( isThreeD() ) {
lineRenderer = new XYLine3DRenderer();
} else {
lineRenderer = new XYLineAndShapeRenderer();
}
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 );
}
protected void configureChart( final JFreeChart chart ) {
super.configureChart( chart );
final XYPlot plot = chart.getXYPlot();
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 ) ) {
final double lineRangeMinimumVal = lineRangeMinimum == null ? 0 : lineRangeMinimum;
final double lineRangeMaximumVal = lineRangeMaximum == null ? 0 : lineRangeMaximum;
if ( lineRangeMinimum != null ) {
linesAxis.setLowerBound( getLineRangeMinimum() );
}
if ( lineRangeMaximum != null ) {
linesAxis.setUpperBound( getRangeMaximum() );
}
if ( lineRangeMinimumVal == 0 && lineRangeMaximumVal == 1 ) {
linesAxis.setLowerBound( 0 );
linesAxis.setUpperBound( 1 );
linesAxis.setAutoRange( true );
}
} else {
if ( lineRangeMinimum != null ) {
linesAxis.setLowerBound( lineRangeMinimum );
}
if ( lineRangeMaximum != null ) {
linesAxis.setUpperBound( lineRangeMaximum );
}
linesAxis.setAutoRange( isLineAxisAutoRange() );
}
}
}
final XYLineAndShapeRenderer linesRenderer = (XYLineAndShapeRenderer) plot.getRenderer( 1 );
if ( linesRenderer != null ) {
//set stroke with line width
linesRenderer.setStroke( translateLineStyle( getLineWidth(), getLineStyle() ) );
//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 ) );
}
}
}
public boolean isLineAxisAutoRange() {
return lineAxisAutoRange;
}
public void setLineAxisAutoRange( final boolean lineAxisAutoRange ) {
this.lineAxisAutoRange = lineAxisAutoRange;
}
protected int getDateUnitAsInt( final Class domainTimePeriod ) {
if ( Second.class.equals( domainTimePeriod ) ) {
return DateTickUnit.SECOND;
}
if ( Minute.class.equals( domainTimePeriod ) ) {
return DateTickUnit.MINUTE;
}
if ( Hour.class.equals( domainTimePeriod ) ) {
return DateTickUnit.HOUR;
}
if ( Day.class.equals( domainTimePeriod ) ) {
return DateTickUnit.DAY;
}
if ( Month.class.equals( domainTimePeriod ) ) {
return DateTickUnit.MONTH;
}
if ( Year.class.equals( domainTimePeriod ) ) {
return DateTickUnit.YEAR;
}
if ( Second.class.equals( domainTimePeriod ) ) {
return DateTickUnit.MILLISECOND;
}
return DateTickUnit.DAY;
}
}