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 }