001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * ------------------------------- 028 * OfflineRenderingChartPanel.java 029 * ------------------------------- 030 * (C) Copyright 2000-present, by Yuri Blankenstein and Contributors. 031 * 032 * Original Author: Yuri Blankenstein; 033 */ 034 035package org.jfree.chart; 036 037import java.awt.AlphaComposite; 038import java.awt.Container; 039import java.awt.Cursor; 040import java.awt.Dimension; 041import java.awt.Graphics2D; 042import java.awt.GraphicsConfiguration; 043import java.awt.Rectangle; 044import java.awt.Transparency; 045import java.awt.geom.Point2D; 046import java.awt.image.BufferedImage; 047 048import javax.swing.SwingWorker; 049 050import org.jfree.chart.entity.EntityCollection; 051import org.jfree.chart.plot.PlotRenderingInfo; 052 053/** 054 * A {@link ChartPanel} that applies offline rendering, for better performance 055 * when navigating (i.e. panning / zooming) {@link JFreeChart charts} with lots 056 * of data. 057 * <P> 058 * This chart panel uses a {@link SwingWorker} to perform the actual 059 * {@link JFreeChart} rendering. While rendering, a {@link Cursor#WAIT_CURSOR 060 * wait cursor} is visible and the current buffered image of the chart will be 061 * scaled and drawn to the screen. When - while rendering - another 062 * {@link #setRefreshBuffer(boolean) refresh} is requested, this will be either 063 * postponed until the current rendering is done or ignored when another refresh 064 * is requested. 065 */ 066public class OfflineRenderingChartPanel extends ChartPanel { 067 private static final long serialVersionUID = -724633596883320084L; 068 069 /** 070 * Using enum state pattern to control the 'offline' rendering 071 */ 072 protected enum State { 073 IDLE { 074 @Override 075 protected State renderOffline(OfflineRenderingChartPanel panel, 076 OfflineChartRenderer renderer) { 077 // Start rendering offline 078 renderer.execute(); 079 panel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); 080 return RENDERING; 081 } 082 083 @Override 084 protected State offlineRenderingDone( 085 OfflineRenderingChartPanel panel, 086 OfflineChartRenderer renderer) { 087 throw new IllegalStateException( 088 "offlineRenderingDone not expected in IDLE state"); 089 } 090 }, 091 RENDERING { 092 @Override 093 protected State renderOffline(OfflineRenderingChartPanel panel, 094 OfflineChartRenderer renderer) { 095 // We're already rendering, we'll start this renderer when we're 096 // finished. If another rendering is requested, this one will be 097 // ignored, see RE_RENDERING_PENDING. This gains a lot of speed 098 // as not all requested (intermediate) renderings are executed 099 // for large plots. 100 panel.pendingOfflineRenderer = renderer; 101 return RE_RENDERING_PENDING; 102 } 103 104 @Override 105 protected State offlineRenderingDone( 106 OfflineRenderingChartPanel panel, 107 OfflineChartRenderer renderer) { 108 // Offline rendering done, prepare the buffer and info for the 109 // next repaint and request it. 110 panel.currentChartBuffer = renderer.buffer; 111 panel.currentChartRenderingInfo = renderer.info; 112 panel.repaint(); 113 panel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 114 return IDLE; 115 } 116 }, 117 RE_RENDERING_PENDING { 118 @Override 119 protected State renderOffline(OfflineRenderingChartPanel panel, 120 OfflineChartRenderer renderer) { 121 // We're already rendering, we'll start this renderer when we're 122 // finished. 123 panel.pendingOfflineRenderer = renderer; 124 return RE_RENDERING_PENDING; 125 } 126 127 @Override 128 protected State offlineRenderingDone( 129 OfflineRenderingChartPanel panel, 130 OfflineChartRenderer renderer) { 131 // Store the intermediate result, but do not actively repaint 132 // as this could trigger another RE_RENDERING_PENDING if i.e. 133 // the buffer-image-size of the pending renderer differs from 134 // the current buffer-image-size. 135 panel.currentChartBuffer = renderer.buffer; 136 panel.currentChartRenderingInfo = renderer.info; 137 // Immediately start rendering again to update the chart to the 138 // latest requested state. 139 panel.pendingOfflineRenderer.execute(); 140 panel.pendingOfflineRenderer = null; 141 return RENDERING; 142 } 143 }; 144 145 protected abstract State renderOffline( 146 final OfflineRenderingChartPanel panel, 147 final OfflineChartRenderer renderer); 148 149 protected abstract State offlineRenderingDone( 150 final OfflineRenderingChartPanel panel, 151 final OfflineChartRenderer renderer); 152 } 153 154 /** A buffer for the rendered chart. */ 155 private transient BufferedImage currentChartBuffer = null; 156 private transient ChartRenderingInfo currentChartRenderingInfo = null; 157 158 /** A pending rendering for the chart. */ 159 private transient OfflineChartRenderer pendingOfflineRenderer = null; 160 161 private State state = State.IDLE; 162 163 /** 164 * Constructs a double buffered JFreeChart panel that displays the specified 165 * chart. 166 * 167 * @param chart the chart. 168 */ 169 public OfflineRenderingChartPanel(JFreeChart chart) { 170 this(chart, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MINIMUM_DRAW_WIDTH, 171 DEFAULT_MINIMUM_DRAW_HEIGHT, DEFAULT_MAXIMUM_DRAW_WIDTH, 172 DEFAULT_MAXIMUM_DRAW_HEIGHT, true, // properties 173 true, // save 174 true, // print 175 true, // zoom 176 true // tooltips 177 ); 178 179 } 180 181 /** 182 * Constructs a double buffered JFreeChart panel. 183 * 184 * @param chart the chart. 185 * @param properties a flag indicating whether or not the chart property 186 * editor should be available via the popup menu. 187 * @param save a flag indicating whether or not save options should be 188 * available via the popup menu. 189 * @param print a flag indicating whether or not the print option 190 * should be available via the popup menu. 191 * @param zoom a flag indicating whether or not zoom options should be 192 * added to the popup menu. 193 * @param tooltips a flag indicating whether or not tooltips should be 194 * enabled for the chart. 195 */ 196 public OfflineRenderingChartPanel(JFreeChart chart, boolean properties, 197 boolean save, boolean print, boolean zoom, boolean tooltips) { 198 199 this(chart, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MINIMUM_DRAW_WIDTH, 200 DEFAULT_MINIMUM_DRAW_HEIGHT, DEFAULT_MAXIMUM_DRAW_WIDTH, 201 DEFAULT_MAXIMUM_DRAW_HEIGHT, properties, save, print, zoom, 202 tooltips); 203 204 } 205 206 /** 207 * Constructs a double buffered JFreeChart panel. 208 * 209 * @param chart the chart. 210 * @param width the preferred width of the panel. 211 * @param height the preferred height of the panel. 212 * @param minimumDrawWidth the minimum drawing width. 213 * @param minimumDrawHeight the minimum drawing height. 214 * @param maximumDrawWidth the maximum drawing width. 215 * @param maximumDrawHeight the maximum drawing height. 216 * @param properties a flag indicating whether or not the chart 217 * property editor should be available via the 218 * popup menu. 219 * @param save a flag indicating whether or not save options 220 * should be available via the popup menu. 221 * @param print a flag indicating whether or not the print 222 * option should be available via the popup menu. 223 * @param zoom a flag indicating whether or not zoom options 224 * should be added to the popup menu. 225 * @param tooltips a flag indicating whether or not tooltips should 226 * be enabled for the chart. 227 */ 228 public OfflineRenderingChartPanel(JFreeChart chart, int width, int height, 229 int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth, 230 int maximumDrawHeight, boolean properties, boolean save, 231 boolean print, boolean zoom, boolean tooltips) { 232 233 this(chart, width, height, minimumDrawWidth, minimumDrawHeight, 234 maximumDrawWidth, maximumDrawHeight, properties, true, save, 235 print, zoom, tooltips); 236 } 237 238 /** 239 * Constructs a double buffered JFreeChart panel. 240 * 241 * @param chart the chart. 242 * @param width the preferred width of the panel. 243 * @param height the preferred height of the panel. 244 * @param minimumDrawWidth the minimum drawing width. 245 * @param minimumDrawHeight the minimum drawing height. 246 * @param maximumDrawWidth the maximum drawing width. 247 * @param maximumDrawHeight the maximum drawing height. 248 * @param properties a flag indicating whether or not the chart 249 * property editor should be available via the 250 * popup menu. 251 * @param copy a flag indicating whether or not a copy option 252 * should be available via the popup menu. 253 * @param save a flag indicating whether or not save options 254 * should be available via the popup menu. 255 * @param print a flag indicating whether or not the print 256 * option should be available via the popup menu. 257 * @param zoom a flag indicating whether or not zoom options 258 * should be added to the popup menu. 259 * @param tooltips a flag indicating whether or not tooltips should 260 * be enabled for the chart. 261 */ 262 public OfflineRenderingChartPanel(JFreeChart chart, int width, int height, 263 int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth, 264 int maximumDrawHeight, boolean properties, boolean copy, 265 boolean save, boolean print, boolean zoom, boolean tooltips) { 266 super(chart, width, height, minimumDrawWidth, minimumDrawHeight, 267 maximumDrawWidth, maximumDrawHeight, true, properties, copy, 268 save, print, zoom, tooltips); 269 } 270 271 @Override 272 protected BufferedImage paintChartToBuffer(Graphics2D g2, 273 Dimension bufferSize, Dimension chartSize, Point2D anchor, 274 ChartRenderingInfo info) { 275 synchronized (state) { 276 if (this.currentChartBuffer == null) { 277 // Rendering the first time, prepare an empty buffer and 278 // start rendering, no need for an additional state 279 this.currentChartBuffer = createChartBuffer(g2, bufferSize); 280 clearChartBuffer(currentChartBuffer); 281 setRefreshBuffer(true); 282 } else if ((this.currentChartBuffer.getWidth() != bufferSize.width) 283 || (this.currentChartBuffer 284 .getHeight() != bufferSize.height)) { 285 setRefreshBuffer(true); 286 } 287 288 // do we need to redraw the buffer? 289 if (getRefreshBuffer()) { 290 setRefreshBuffer(false); // clear the flag 291 292 // Rendering is done offline, hence it requires a fresh buffer 293 // and rendering info 294 BufferedImage rendererBuffer = createChartBuffer(g2, 295 bufferSize); 296 ChartRenderingInfo rendererInfo = info; 297 if (rendererInfo != null) { 298 // As the chart will be re-rendered, the current chart 299 // entities cannot be trusted 300 final EntityCollection entityCollection = 301 rendererInfo.getEntityCollection(); 302 if (entityCollection != null) { 303 entityCollection.clear(); 304 } 305 306 // Offline rendering requires its own instance of 307 // ChartRenderingInfo, using clone if possible 308 try { 309 rendererInfo = rendererInfo.clone(); 310 } catch (CloneNotSupportedException e) { 311 // Not expected 312 e.printStackTrace(); 313 rendererInfo = new ChartRenderingInfo(); 314 } 315 } 316 317 OfflineChartRenderer offlineRenderer = new OfflineChartRenderer( 318 getChart(), rendererBuffer, chartSize, anchor, 319 rendererInfo); 320 state = state.renderOffline(this, offlineRenderer); 321 } 322 323 // Copy the rendered ChartRenderingInfo into the passed info 324 // argument and mark that we have done so. 325 copyChartRenderingInfo(this.currentChartRenderingInfo, info); 326 this.currentChartRenderingInfo = info; 327 328 return this.currentChartBuffer; 329 } 330 } 331 332 private class OfflineChartRenderer extends SwingWorker<Object, Object> { 333 private final JFreeChart chart; 334 private final BufferedImage buffer; 335 private final Dimension chartSize; 336 private final Point2D anchor; 337 private final ChartRenderingInfo info; 338 339 public OfflineChartRenderer(JFreeChart chart, BufferedImage image, 340 Dimension chartSize, Point2D anchor, ChartRenderingInfo info) { 341 this.chart = chart; 342 this.buffer = image; 343 this.chartSize = chartSize; 344 this.anchor = anchor; 345 this.info = info; 346 } 347 348 @Override 349 protected Object doInBackground() throws Exception { 350 clearChartBuffer(buffer); 351 352 Graphics2D bufferG2 = buffer.createGraphics(); 353 if ((this.buffer.getWidth() != this.chartSize.width) 354 || (this.buffer.getHeight() != this.chartSize.height)) { 355 // Scale the chart to fit the buffer 356 bufferG2.scale( 357 this.buffer.getWidth() / this.chartSize.getWidth(), 358 this.buffer.getHeight() / this.chartSize.getHeight()); 359 } 360 Rectangle chartArea = new Rectangle(this.chartSize); 361 362 this.chart.draw(bufferG2, chartArea, this.anchor, this.info); 363 bufferG2.dispose(); 364 365 // Return type is not used 366 return null; 367 } 368 369 @Override 370 protected void done() { 371 synchronized (state) { 372 state = state.offlineRenderingDone( 373 OfflineRenderingChartPanel.this, this); 374 } 375 } 376 } 377 378 @Override 379 public void setCursor(Cursor cursor) { 380 super.setCursor(cursor); 381 382 // Buggy mouse cursor: setting both behaves as expected 383 Container root = getTopLevelAncestor(); 384 if (null != root) { 385 root.setCursor(cursor); 386 } 387 } 388 389 private static void copyChartRenderingInfo(ChartRenderingInfo source, 390 ChartRenderingInfo target) { 391 if (source == null || target == null || source == target) { 392 // Nothing to do 393 return; 394 } 395 target.clear(); 396 target.setChartArea(source.getChartArea()); 397 target.setEntityCollection(source.getEntityCollection()); 398 copyPlotRenderingInfo(source.getPlotInfo(), target.getPlotInfo()); 399 } 400 401 private static void copyPlotRenderingInfo(PlotRenderingInfo source, 402 PlotRenderingInfo target) { 403 target.setDataArea(source.getDataArea()); 404 target.setPlotArea(source.getPlotArea()); 405 for (int i = 0; i < target.getSubplotCount(); i++) { 406 PlotRenderingInfo subSource = source.getSubplotInfo(i); 407 PlotRenderingInfo subTarget = new PlotRenderingInfo( 408 target.getOwner()); 409 copyPlotRenderingInfo(subSource, subTarget); 410 target.addSubplotInfo(subTarget); 411 } 412 } 413 414 private static BufferedImage createChartBuffer(Graphics2D g2, 415 Dimension bufferSize) { 416 GraphicsConfiguration gc = g2.getDeviceConfiguration(); 417 return gc.createCompatibleImage(bufferSize.width, bufferSize.height, 418 Transparency.TRANSLUCENT); 419 } 420 421 private static void clearChartBuffer(BufferedImage buffer) { 422 Graphics2D bufferG2 = buffer.createGraphics(); 423 // make the background of the buffer clear and transparent 424 bufferG2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f)); 425 bufferG2.fill(new Rectangle(buffer.getWidth(), buffer.getHeight())); 426 bufferG2.dispose(); 427 } 428}