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    }