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 }