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