    001    //$$Header: $$
    002    /*----------------------------------------------------------------------------
    003     This file is part of deegree, http://deegree.org/
    004     Copyright (C) 2001-2009 by:
    005       Department of Geography, University of Bonn
    006     and
    007       lat/lon GmbH
    008
    009     This library is free software; you can redistribute it and/or modify it under
    010     the terms of the GNU Lesser General Public License as published by the Free
    011     Software Foundation; either version 2.1 of the License, or (at your option)
    012     any later version.
    013     This library is distributed in the hope that it will be useful, but WITHOUT
    014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    016     details.
    017     You should have received a copy of the GNU Lesser General Public License
    018     along with this library; if not, write to the Free Software Foundation, Inc.,
    019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    020
    021     Contact information:
    022
    023     lat/lon GmbH
    024     Aennchenstr. 19, 53177 Bonn
    025     Germany
    026     http://lat-lon.de/
    027
    028     Department of Geography, University of Bonn
    029     Prof. Dr. Klaus Greve
    030     Postfach 1147, 53001 Bonn
    031     Germany
    032     http://www.geographie.uni-bonn.de/deegree/
    033
    034     e-mail: info@deegree.org
    035    ----------------------------------------------------------------------------*/
    036
    037    package org.deegree.graphics.charts;
    038
    039    import java.awt.Dimension;
    040    import java.awt.Font;
    041    import java.awt.Graphics2D;
    042    import java.awt.Rectangle;
    043    import java.awt.image.BufferedImage;
    044    import java.util.Iterator;
    045
    046    import org.deegree.framework.log.ILogger;
    047    import org.deegree.framework.log.LoggerFactory;
    048    import org.deegree.framework.util.StringTools;
    049    import org.jfree.chart.ChartFactory;
    050    import org.jfree.chart.JFreeChart;
    051    import org.jfree.chart.plot.PiePlot;
    052    import org.jfree.chart.plot.PlotOrientation;
    053    import org.jfree.chart.plot.XYPlot;
    054    import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
    055    import org.jfree.chart.renderer.xy.XYSplineRenderer;
    056    import org.jfree.data.category.CategoryDataset;
    057    import org.jfree.data.category.DefaultCategoryDataset;
    058    import org.jfree.data.general.DefaultPieDataset;
    059    import org.jfree.data.xy.XYDataset;
    060    import org.jfree.data.xy.XYSeries;
    061    import org.jfree.data.xy.XYSeriesCollection;
    062    import org.jfree.ui.RectangleInsets;
    063
    064    /**
    065     * A Charts class with static methods. Its used to create de.latlon.charts from the given inputs.
    066     *
    067     * @author <a href="mailto:elmasry@lat-lon.de">Moataz Elmasry</a>
    068     * @author last edited by: $Author: elmasri$
    069     *
    070     * @version $Revision: $, $Date: 27 Mar 2008 11:43:00$
    071     */
    072    public class ChartsBuilder {
    073
    074        private static final ILogger LOG = LoggerFactory.getLogger( ChartsBuilder.class );
    075
    076        /**
    077         * To indicate a horizontal chart type
    078         */
    079        public static final int ORIENTATION_HORIZONTAL = 1001;
    080
    081        /**
    082         * To indicate a vertical chart type
    083         */
    084        public static final int ORIENTATION_VERTICAL = 1002;
    085
    086        /**
    087         * &CDU=34&SPD=36&Gruene=11
    088         */
    089        protected final static int VALUE_FORMAT_SIMPLE = 1101;
    090
    091        /**
    092         * &CDU=23,25,21,26&SPD=42 23 33 36
    093         */
    094        protected final static int VALUE_FORMAT_SERIES = 1102;
    095
    096        /**
    097         * &CDU=1970 23;1974,44;1978,43&SPD=1970,23;1974,44;1978,43
    098         */
    099        protected final static int VALUE_FORMAT_XYSERIES = 1103;
    100
    101        /**
    102         * Unknown format
    103         */
    104        protected final static int VALUE_FORMAT_UNKNOWN = 1104;
    105
    106        private ChartConfig chartConfigs = null;
    107
    108        /**
    109         * @param chartConfigs
    110         */
    111        public ChartsBuilder( ChartConfig chartConfigs ) {
    112            this.chartConfigs = chartConfigs;
    113        }
    114
    115        /**
    116         * Create a pie 2D/3D Pie Chart
    117         *
    118         * @param title
    119         *
    120         * @param keyedValues
    121         *            The key/value pairs used for the pie chart
    122         * @param width
    123         *            of the output image
    124         * @param height
    125         *            height of the output image
    126         * @param is3D
    127         *            is a 3D Chart
    128         * @param legend
    129         *            for the output chart
    130         * @param tooltips
    131         *            for the output chart
    132         * @param lblType
    133         *            Possible types are <i>Key</i>, <i>Value</i>, <i>KeyValue</i>
    134         * @param imageType
    135         *            of the output image
    136         * @param chartConfigs
    137         *            to configure the output chart, or null to use the default ChartConfig
    138         * @return BufferedImage representing the generated chart
    139         */
    140        public BufferedImage createPieChart( String title, QueuedMap<String, Double> keyedValues, int width, int height,
    141                                             boolean is3D, boolean legend, boolean tooltips, String lblType,
    142                                             String imageType, ChartConfig chartConfigs ) {
    143
    144            DefaultPieDataset dataset = new DefaultPieDataset();
    145            Iterator<String> it = keyedValues.keySet().iterator();
    146            while ( it.hasNext() ) {
    147                String key = it.next();
    148                if ( "KeyValue".equals( lblType ) ) {
    149                    dataset.setValue( StringTools.concat( 20, key, " ", keyedValues.get( key ) ), keyedValues.get( key ) );
    150                } else if ( "Value".equals( lblType ) ) {
    151                    dataset.setValue( keyedValues.get( key ), keyedValues.get( key ) );
    152                } else {
    153                    dataset.setValue( key, keyedValues.get( key ) );
    154                }
    155            }
    156            JFreeChart chart = null;
    157            if ( is3D ) {
    158                chart = ChartFactory.createPieChart3D( title, dataset, legend, tooltips, false );
    159            } else {
    160                chart = ChartFactory.createPieChart( title, dataset, legend, tooltips, true );
    161            }
    162            if ( chartConfigs == null ) {
    163                chartConfigs = this.chartConfigs;
    164            }
    165            return createBufferedImage( configPieChart( chart, chartConfigs ), width, height, imageType );
    166        }
    167
    168        /**
    169         * Creates a Bar chart
    170         *
    171         * @param title
    172         * @param keyedValues
    173         *            key is the category name, value is a series tupels as follows for instance key1 = (arg1,4);(arg2,6)
    174         *            key2 = (arg1,8); (arg2,11)
    175         * @param width
    176         *            of the output image
    177         * @param height
    178         *            height of the output image
    179         * @param is3D
    180         *            is a 3D Chart
    181         * @param legend
    182         *            for the output chart
    183         * @param tooltips
    184         *            for the output de.latlon.charts
    185         * @param orientation
    186         *            Horiyontal or vertical chart
    187         * @param imageType
    188         *            of the output image
    189         * @param horizontalAxisName
    190         *            Name of the Horizontal Axis
    191         * @param verticalAxisName
    192         *            Name of the vertical Axis
    193         * @param chartConfigs
    194         *            to configure the output chart, or null to use the default ChartConfig
    195         * @return BufferedImage representing the generated chart
    196         * @throws IncorrectFormatException
    197         */
    198        public BufferedImage createBarChart( String title, QueuedMap<String, String> keyedValues, int width, int height,
    199                                             boolean is3D, boolean legend, boolean tooltips, int orientation,
    200                                             String imageType, String horizontalAxisName, String verticalAxisName,
    201                                             ChartConfig chartConfigs )
    202                                throws IncorrectFormatException {
    203
    204            CategoryDataset dataset = convertMapToCategoryDataSet( keyedValues );
    205            JFreeChart chart = null;
    206            if ( is3D ) {
    207                chart = ChartFactory.createBarChart3D( title, horizontalAxisName, verticalAxisName, dataset,
    208                                                       translateToPlotOrientation( orientation ), legend, tooltips, false );
    209            } else {
    210                chart = ChartFactory.createBarChart( title, horizontalAxisName, verticalAxisName, dataset,
    211                                                     translateToPlotOrientation( orientation ), legend, tooltips, false );
    212            }
    213            if ( chartConfigs == null ) {
    214                chartConfigs = this.chartConfigs;
    215            }
    216            return createBufferedImage( configChart( chart, chartConfigs ), width, height, imageType );
    217        }
    218
    219        /**
    220         * Creates a Line chart
    221         *
    222         * @param title
    223         * @param keyedValues
    224         *            key is the category name, value is a series tupels as follows for instance key1 = (arg1,4);(arg2,6)
    225         *            key2 = (arg1,8); (arg2,11)
    226         * @param width
    227         *            of the output image
    228         * @param height
    229         *            height of the output image
    230         * @param is3D
    231         *            is a 3D Chart
    232         * @param legend
    233         *            for the output chart
    234         * @param tooltips
    235         *            for the output de.latlon.charts
    236         * @param orientation
    237         *            Horiyontal or vertical chart
    238         * @param imageType
    239         *            of the output image
    240         * @param horizontalAxisName
    241         *            Name of the Horizontal Axis
    242         * @param verticalAxisName
    243         *            Name of the vertical Axis
    244         * @param chartConfigs
    245         *            to configure the output chart, or null to use the default ChartConfig
    246         * @return BufferedImage representing the generated chart
    247         * @throws IncorrectFormatException
    248         */
    249        public BufferedImage createLineChart( String title, QueuedMap<String, String> keyedValues, int width, int height,
    250                                              boolean is3D, boolean legend, boolean tooltips, int orientation,
    251                                              String imageType, String horizontalAxisName, String verticalAxisName,
    252                                              ChartConfig chartConfigs )
    253                                throws IncorrectFormatException {
    254
    255            CategoryDataset dataset = convertMapToCategoryDataSet( keyedValues );
    256
    257            JFreeChart chart = null;
    258            if ( is3D ) {
    259                chart = ChartFactory.createLineChart3D( title, horizontalAxisName, verticalAxisName, dataset,
    260                                                        translateToPlotOrientation( orientation ), legend, tooltips, false );
    261            } else {
    262                chart = ChartFactory.createLineChart( title, horizontalAxisName, verticalAxisName, dataset,
    263                                                      translateToPlotOrientation( orientation ), legend, tooltips, false );
    264            }
    265            if ( chartConfigs == null ) {
    266                chartConfigs = this.chartConfigs;
    267            }
    268            return createBufferedImage( configChart( chart, chartConfigs ), width, height, imageType );
    269        }
    270
    271        /**
    272         * Creates an XY Line chart
    273         *
    274         * @param title
    275         * @param keyedValues
    276         *            key is the category name, value is a series tupels Format: key = x1,y1;x2,y2;x3,y3 Example row1 =
    277         *            2,3;4,10 Note that x and y have to be numbers
    278         * @param width
    279         *            of the output image
    280         * @param height
    281         *            height of the output image
    282         * @param legend
    283         *            for the output chart
    284         * @param tooltips
    285         *            for the output de.latlon.charts
    286         * @param orientation
    287         *            Horiyontal or vertical chart
    288         * @param imageType
    289         *            of the output image
    290         * @param horizontalAxisName
    291         *            Name of the Horizontal Axis
    292         * @param verticalAxisName
    293         *            Name of the vertical Axis
    294         * @param chartConfigs
    295         *            to configure the output chart, or null to use the default ChartConfig
    296         * @return BufferedImage representing the generated chart
    297         * @throws IncorrectFormatException
    298         */
    299        public BufferedImage createXYLineChart( String title, QueuedMap<String, String> keyedValues, int width, int height,
    300                                                boolean legend, boolean tooltips, int orientation, String imageType,
    301                                                String horizontalAxisName, String verticalAxisName, ChartConfig chartConfigs )
    302                                throws IncorrectFormatException {
    303            XYDataset dataset = convertMapToXYSeriesDataSet( keyedValues );
    304
    305            JFreeChart chart = null;
    306            chart = ChartFactory.createXYLineChart( title, horizontalAxisName, verticalAxisName, dataset,
    307                                                    translateToPlotOrientation( orientation ), legend, tooltips, false );
    308
    309            XYSplineRenderer renderer = new XYSplineRenderer();
    310            XYPlot plot = (XYPlot) chart.getPlot();
    311            plot.setRenderer( renderer );
    312            if ( chartConfigs == null ) {
    313                chartConfigs = this.chartConfigs;
    314            }
    315            return createBufferedImage( configLineChart( chart, chartConfigs ), width, height, imageType );
    316        }
    317
    318        /**
    319         * It takes in a map a QueuedMap and converts it to a XYDataSet. Format: key =
    320         * RowName,Value;RowName,Value;RowName,Value Example row1 = col1,3;col2,10
    321         *
    322         * @param keyedValues
    323         * @return CategoryDataSet
    324         * @throws IncorrectFormatException
    325         */
    326        protected CategoryDataset convertMapToCategoryDataSet( QueuedMap<String, String> keyedValues )
    327                                throws IncorrectFormatException {
    328
    329            DefaultCategoryDataset dataset = new DefaultCategoryDataset();
    330
    331            for ( String key : keyedValues.keySet() ) {
    332                String value = keyedValues.get( key );
    333                ValueFormatsParser parser = new ValueFormatsParser( value );
    334                if ( parser.isFormatUnknown() ) {
    335                    continue;
    336                }
    337                if ( !parser.isFormatSeries() && !parser.isFormatSeriesXY() ) {
    338                    throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_BAD_FORMAT_SERIES", value ) );
    339                }
    340                int counter = 0;
    341                while ( parser.hasNext() ) {
    342                    String tupel = parser.nextTupel();
    343
    344                    String colName = "Col" + ++counter;
    345                    String colValue = tupel;
    346                    dataset.addValue( Double.parseDouble( colValue ), key, colName );
    347                }
    348            }
    349
    350            return dataset;
    351        }
    352
    353        /**
    354         * It takes in a map a QueuedMap and converts it to a XYDataSet.The two tokens of each tupel have to be numbers
    355         * Format: key = x1,y1;x2,y2;x3,y3; Example row1 = 2,3;4,10;
    356         *
    357         * @param keyedValues
    358         * @return CategoryDataSet
    359         * @throws IncorrectFormatException
    360         */
    361        protected XYDataset convertMapToXYSeriesDataSet( QueuedMap<String, String> keyedValues )
    362                                throws IncorrectFormatException {
    363
    364            XYSeriesCollection dataset = new XYSeriesCollection();
    365
    366            for ( String key : keyedValues.keySet() ) {
    367                String value = keyedValues.get( key );
    368                ValueFormatsParser parser = new ValueFormatsParser( value );
    369
    370                if ( !parser.isFormatSeriesXY() && !parser.isFormatUnknown() ) {
    371                    throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_BAD_FORMAT_KEY", key ) );
    372                }
    373                if ( parser.isFormatUnknown() ) {
    374                    continue;
    375                } else if ( !parser.isFormatSeriesXY() ) {
    376                    throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_BAD_FORMAT_SERIESXY", value ) );
    377                }
    378
    379                XYSeries series = new XYSeries( key );
    380                while ( parser.hasNext() ) {
    381                    String tupel = parser.getNext();
    382                    int separatorIndex = tupel.indexOf( "," );
    383                    if ( separatorIndex == -1 ) {
    384                        separatorIndex = tupel.indexOf( " " );
    385                    }
    386                    if ( separatorIndex == -1 ) {
    387                        throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_MISSING_SEPARATOR", tupel,
    388                                                                                 value ) );
    389                    }
    390
    391                    String xValue = tupel.substring( 0, separatorIndex );
    392                    String yValue = tupel.substring( separatorIndex + 1, tupel.length() );
    393                    try {
    394                        series.add( Double.parseDouble( xValue ), Double.parseDouble( yValue ) );
    395                    } catch ( Exception e ) {
    396                        throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_INVALID_TUPEL", tupel ) );
    397                    }
    398                }
    399                dataset.addSeries( series );
    400            }
    401            return dataset;
    402        }
    403
    404        /**
    405         * Translates an integer that represents the chart orientation to a plot orientation instance
    406         *
    407         * @param orientation
    408         * @return Horizontal plot orientation if orientation is Horizontal else Vertical
    409         */
    410        protected PlotOrientation translateToPlotOrientation( int orientation ) {
    411
    412            if ( orientation == ORIENTATION_HORIZONTAL ) {
    413                return PlotOrientation.HORIZONTAL;
    414            }
    415            return PlotOrientation.VERTICAL;
    416        }
    417
    418        /**
    419         * Creates a BufferedImage instance from a given chart, according to the given additional parameters
    420         *
    421         * @param chart
    422         * @param width
    423         *            of the generated image
    424         * @param height
    425         *            of the generated image
    426         * @param imageType
    427         *            ex image/png, image/jpg
    428         * @return BufferedImage
    429         */
    430        protected BufferedImage createBufferedImage( JFreeChart chart, int width, int height, String imageType ) {
    431
    432            chart.setTextAntiAlias( true );
    433            chart.setAntiAlias( true );
    434            BufferedImage image = new BufferedImage( width, height, mapImageformat( imageType ) );
    435            Graphics2D g2 = image.createGraphics();
    436            chart.draw( g2, new Rectangle( new Dimension( width, height ) ) );
    437            return image;
    438        }
    439
    440        /**
    441         * Configures the pie chart according to the stored configurations file
    442         *
    443         * @param chart
    444         * @param chartConfigs
    445         *            to configure the output chart
    446         * @return configured JFreeChart
    447         */
    448        protected JFreeChart configPieChart( JFreeChart chart, ChartConfig chartConfigs ) {
    449
    450            chart = configChart( chart, chartConfigs );
    451
    452            ( (PiePlot) chart.getPlot() ).setLabelFont( new Font( chartConfigs.getGenFontFamily(),
    453                                                                  findFontType( chartConfigs.getGenFontType() ),
    454                                                                  (int) chartConfigs.getGenFontSize() ) );
    455            ( (PiePlot) chart.getPlot() ).setInteriorGap( chartConfigs.getPieInteriorGap() );
    456            ( (PiePlot) chart.getPlot() ).setLabelGap( chartConfigs.getPieLabelGap() );
    457            ( (PiePlot) chart.getPlot() ).setCircular( chartConfigs.isPieCircular() );
    458            ( (PiePlot) chart.getPlot() ).setBaseSectionPaint( chartConfigs.getPieBaseSectionColor() );
    459            ( (PiePlot) chart.getPlot() ).setShadowPaint( chartConfigs.getPieShadowColor() );
    460            return chart;
    461        }
    462
    463        /**
    464         * Configures the pie chart according to the stored configurations file
    465         *
    466         * @param chart
    467         * @param chartConfigs
    468         *            to configure the output chart
    469         * @return configured JFreeChart
    470         */
    471        protected JFreeChart configLineChart( JFreeChart chart, ChartConfig chartConfigs ) {
    472
    473            XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer( chartConfigs.isLineRenderLines(),
    474                                                                          chartConfigs.isLineRenderShapes() );
    475
    476            XYPlot plot = (XYPlot) chart.getPlot();
    477            plot.setRenderer( renderer );
    478            return chart;
    479        }
    480
    481        /**
    482         * Initializes the chart with the values from the properties file config.properties
    483         *
    484         * @param chart
    485         * @param chartConfigs
    486         *            to configure the output chart
    487         * @return initialized chart
    488         */
    489        protected JFreeChart configChart( JFreeChart chart, ChartConfig chartConfigs ) {
    490
    491            chart.setAntiAlias( chartConfigs.isGenAntiAliasing() );
    492
    493            chart.setBorderVisible( chartConfigs.isGenBorderVisible() );
    494
    495            String rectanglkeInsets = chartConfigs.getGenRectangleInsets();
    496            if ( !rectanglkeInsets.startsWith( "!" ) ) {
    497                String[] insets = rectanglkeInsets.split( "," );
    498                if ( insets.length == 4 ) {
    499                    try {
    500                        double top = Double.parseDouble( insets[0] );
    501                        double left = Double.parseDouble( insets[1] );
    502                        double buttom = Double.parseDouble( insets[2] );
    503                        double right = Double.parseDouble( insets[3] );
    504                        RectangleInsets rectInsets = new RectangleInsets( top, left, buttom, right );
    505                        chart.setPadding( rectInsets );
    506                    } catch ( Exception e ) {
    507                        LOG.logError( Messages.getMessage( "GRA_CHART_BAD_FORMAT_INSETS" ) );
    508                    }
    509                } else {
    510                    LOG.logError( Messages.getMessage( "GRA_CHART_BAD_FORMAT_INSETS" ) );
    511                }
    512            }
    513
    514            chart.setTextAntiAlias( chartConfigs.isGenTextAntiAlias() );
    515            chart.setBackgroundPaint( chartConfigs.getGenBackgroundColor() );
    516
    517            chart.getPlot().setOutlineVisible( chartConfigs.isPlotOutlineVisible() );
    518            chart.getPlot().setForegroundAlpha( (float) chartConfigs.getPlotForegroundOpacity() );
    519            chart.getPlot().setBackgroundPaint( chartConfigs.getPlotBackgroundColor() );
    520            return chart;
    521        }
    522
    523        /**
    524         * Maps the image format to an appropriate type, either RGB or RGBA (allow opacity). There are image types that
    525         * allow opacity like png, while others don't, like jpg
    526         *
    527         * @param imgFormat
    528         * @return BufferedImage Type INT_ARGB if the mime type is image/png or image/gif INT_RGB else.
    529         */
    530        protected int mapImageformat( String imgFormat ) {
    531            if ( ( "image/png" ).equals( imgFormat ) || ( "image/gif" ).equals( imgFormat ) ) {
    532                return BufferedImage.TYPE_INT_ARGB;
    533            }
    534            return BufferedImage.TYPE_INT_RGB;
    535        }
    536
    537        /**
    538         * Finds the appropriate integer that represents either one of the following "PLAIN","BOLD" or "ITALIC"
    539         *
    540         * @param fontType
    541         * @return font type
    542         */
    543        private int findFontType( String fontType ) {
    544
    545            if ( fontType.toUpperCase().equals( "ITALIC" ) ) {
    546                return Font.ITALIC;
    547            } else if ( fontType.toUpperCase().equals( "BOLD" ) ) {
    548                return Font.BOLD;
    549            }
    550            return Font.PLAIN;
    551        }
    552    }