001/* ====================================================== 002 * JFreeChart : a chart library for the Java(tm) platform 003 * ====================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert and Contributors. 006 * 007 * Project Info: https://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 074 /** Idle state. */ 075 IDLE { 076 @Override 077 protected State renderOffline(OfflineRenderingChartPanel panel, 078 OfflineChartRenderer renderer) { 079 // Start rendering offline 080 renderer.execute(); 081 panel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); 082 return RENDERING; 083 } 084 085 @Override 086 protected State offlineRenderingDone( 087 OfflineRenderingChartPanel panel, 088 OfflineChartRenderer renderer) { 089 throw new IllegalStateException( 090 "offlineRenderingDone not expected in IDLE state"); 091 } 092 }, 093 094 /** Rendering state. */ 095 RENDERING { 096 @Override 097 protected State renderOffline(OfflineRenderingChartPanel panel, 098 OfflineChartRenderer renderer) { 099 // We're already rendering, we'll start this renderer when we're 100 // finished. If another rendering is requested, this one will be 101 // ignored, see RE_RENDERING_PENDING. This gains a lot of speed 102 // as not all requested (intermediate) renderings are executed 103 // for large plots. 104 panel.pendingOfflineRenderer = renderer; 105 return RE_RENDERING_PENDING; 106 } 107 108 /** 109 * Called when rendering is done. 110 * 111 * @param panel the panel. 112 * @param renderer the renderer. 113 * @return The state 114 */ 115 @Override 116 protected State offlineRenderingDone( 117 OfflineRenderingChartPanel panel, 118 OfflineChartRenderer renderer) { 119 // Offline rendering done, prepare the buffer and info for the 120 // next repaint and request it. 121 panel.currentChartBuffer = renderer.buffer; 122 panel.currentChartRenderingInfo = renderer.info; 123 panel.repaint(); 124 panel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 125 return IDLE; 126 } 127 }, 128 RE_RENDERING_PENDING { 129 @Override 130 protected State renderOffline(OfflineRenderingChartPanel panel, 131 OfflineChartRenderer renderer) { 132 // We're already rendering, we'll start this renderer when we're 133 // finished. 134 panel.pendingOfflineRenderer = renderer; 135 return RE_RENDERING_PENDING; 136 } 137 138 @Override 139 protected State offlineRenderingDone( 140 OfflineRenderingChartPanel panel, 141 OfflineChartRenderer renderer) { 142 // Store the intermediate result, but do not actively repaint 143 // as this could trigger another RE_RENDERING_PENDING if i.e. 144 // the buffer-image-size of the pending renderer differs from 145 // the current buffer-image-size. 146 panel.currentChartBuffer = renderer.buffer; 147 panel.currentChartRenderingInfo = renderer.info; 148 // Immediately start rendering again to update the chart to the 149 // latest requested state. 150 panel.pendingOfflineRenderer.execute(); 151 panel.pendingOfflineRenderer = null; 152 return RENDERING; 153 } 154 }; 155 156 /** 157 * Render the content offline. 158 * 159 * @param panel the panel. 160 * @param renderer the renderer. 161 * @return The state. 162 */ 163 protected abstract State renderOffline( 164 final OfflineRenderingChartPanel panel, 165 final OfflineChartRenderer renderer); 166 167 /** 168 * Called when rendering is done. 169 * 170 * @param panel the panel. 171 * @param renderer the renderer. 172 * @return The state. 173 */ 174 protected abstract State offlineRenderingDone( 175 final OfflineRenderingChartPanel panel, 176 final OfflineChartRenderer renderer); 177 } 178 179 /** A buffer for the rendered chart. */ 180 private transient BufferedImage currentChartBuffer = null; 181 private transient ChartRenderingInfo currentChartRenderingInfo = null; 182 183 /** A pending rendering for the chart. */ 184 private transient OfflineChartRenderer pendingOfflineRenderer = null; 185 186 /** The state. */ 187 private State state = State.IDLE; 188 189 /** 190 * Constructs a double buffered JFreeChart panel that displays the specified 191 * chart. 192 * 193 * @param chart the chart. 194 */ 195 public OfflineRenderingChartPanel(JFreeChart chart) { 196 this(chart, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MINIMUM_DRAW_WIDTH, 197 DEFAULT_MINIMUM_DRAW_HEIGHT, DEFAULT_MAXIMUM_DRAW_WIDTH, 198 DEFAULT_MAXIMUM_DRAW_HEIGHT, true, // properties 199 true, // save 200 true, // print 201 true, // zoom 202 true // tooltips 203 ); 204 205 } 206 207 /** 208 * Constructs a double buffered JFreeChart panel. 209 * 210 * @param chart the chart. 211 * @param properties a flag indicating whether the chart property 212 * editor should be available via the popup menu. 213 * @param save a flag indicating whether save options should be 214 * available via the popup menu. 215 * @param print a flag indicating whether the print option 216 * should be available via the popup menu. 217 * @param zoom a flag indicating whether zoom options should be 218 * added to the popup menu. 219 * @param tooltips a flag indicating whether tooltips should be 220 * enabled for the chart. 221 */ 222 public OfflineRenderingChartPanel(JFreeChart chart, boolean properties, 223 boolean save, boolean print, boolean zoom, boolean tooltips) { 224 225 this(chart, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MINIMUM_DRAW_WIDTH, 226 DEFAULT_MINIMUM_DRAW_HEIGHT, DEFAULT_MAXIMUM_DRAW_WIDTH, 227 DEFAULT_MAXIMUM_DRAW_HEIGHT, properties, save, print, zoom, 228 tooltips); 229 230 } 231 232 /** 233 * Constructs a double buffered JFreeChart panel. 234 * 235 * @param chart the chart. 236 * @param width the preferred width of the panel. 237 * @param height the preferred height of the panel. 238 * @param minimumDrawWidth the minimum drawing width. 239 * @param minimumDrawHeight the minimum drawing height. 240 * @param maximumDrawWidth the maximum drawing width. 241 * @param maximumDrawHeight the maximum drawing height. 242 * @param properties a flag indicating whether the chart 243 * property editor should be available via the 244 * popup menu. 245 * @param save a flag indicating whether save options 246 * should be available via the popup menu. 247 * @param print a flag indicating whether the print 248 * option should be available via the popup menu. 249 * @param zoom a flag indicating whether zoom options 250 * should be added to the popup menu. 251 * @param tooltips a flag indicating whether tooltips should 252 * be enabled for the chart. 253 */ 254 public OfflineRenderingChartPanel(JFreeChart chart, int width, int height, 255 int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth, 256 int maximumDrawHeight, boolean properties, boolean save, 257 boolean print, boolean zoom, boolean tooltips) { 258 259 this(chart, width, height, minimumDrawWidth, minimumDrawHeight, 260 maximumDrawWidth, maximumDrawHeight, properties, true, save, 261 print, zoom, tooltips); 262 } 263 264 /** 265 * Constructs a double buffered JFreeChart panel. 266 * 267 * @param chart the chart. 268 * @param width the preferred width of the panel. 269 * @param height the preferred height of the panel. 270 * @param minimumDrawWidth the minimum drawing width. 271 * @param minimumDrawHeight the minimum drawing height. 272 * @param maximumDrawWidth the maximum drawing width. 273 * @param maximumDrawHeight the maximum drawing height. 274 * @param properties a flag indicating whether the chart 275 * property editor should be available via the 276 * popup menu. 277 * @param copy a flag indicating whether a copy option 278 * should be available via the popup menu. 279 * @param save a flag indicating whether save options 280 * should be available via the popup menu. 281 * @param print a flag indicating whether the print 282 * option should be available via the popup menu. 283 * @param zoom a flag indicating whether zoom options 284 * should be added to the popup menu. 285 * @param tooltips a flag indicating whether tooltips should 286 * be enabled for the chart. 287 */ 288 public OfflineRenderingChartPanel(JFreeChart chart, int width, int height, 289 int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth, 290 int maximumDrawHeight, boolean properties, boolean copy, 291 boolean save, boolean print, boolean zoom, boolean tooltips) { 292 super(chart, width, height, minimumDrawWidth, minimumDrawHeight, 293 maximumDrawWidth, maximumDrawHeight, true, properties, copy, 294 save, print, zoom, tooltips); 295 } 296 297 @Override 298 protected BufferedImage paintChartToBuffer(Graphics2D g2, 299 Dimension bufferSize, Dimension chartSize, Point2D anchor, 300 ChartRenderingInfo info) { 301 synchronized (state) { 302 if (this.currentChartBuffer == null) { 303 // Rendering the first time, prepare an empty buffer and 304 // start rendering, no need for an additional state 305 this.currentChartBuffer = createChartBuffer(g2, bufferSize); 306 clearChartBuffer(currentChartBuffer); 307 setRefreshBuffer(true); 308 } else if ((this.currentChartBuffer.getWidth() != bufferSize.width) 309 || (this.currentChartBuffer 310 .getHeight() != bufferSize.height)) { 311 setRefreshBuffer(true); 312 } 313 314 // do we need to redraw the buffer? 315 if (getRefreshBuffer()) { 316 setRefreshBuffer(false); // clear the flag 317 318 // Rendering is done offline, hence it requires a fresh buffer 319 // and rendering info 320 BufferedImage rendererBuffer = createChartBuffer(g2, 321 bufferSize); 322 ChartRenderingInfo rendererInfo = info; 323 if (rendererInfo != null) { 324 // As the chart will be re-rendered, the current chart 325 // entities cannot be trusted 326 final EntityCollection entityCollection = 327 rendererInfo.getEntityCollection(); 328 if (entityCollection != null) { 329 entityCollection.clear(); 330 } 331 332 // Offline rendering requires its own instance of 333 // ChartRenderingInfo, using clone if possible 334 try { 335 rendererInfo = rendererInfo.clone(); 336 } catch (CloneNotSupportedException e) { 337 // Not expected 338 e.printStackTrace(); 339 rendererInfo = new ChartRenderingInfo(); 340 } 341 } 342 343 OfflineChartRenderer offlineRenderer = new OfflineChartRenderer( 344 getChart(), rendererBuffer, chartSize, anchor, 345 rendererInfo); 346 state = state.renderOffline(this, offlineRenderer); 347 } 348 349 // Copy the rendered ChartRenderingInfo into the passed info 350 // argument and mark that we have done so. 351 copyChartRenderingInfo(this.currentChartRenderingInfo, info); 352 this.currentChartRenderingInfo = info; 353 354 return this.currentChartBuffer; 355 } 356 } 357 358 private class OfflineChartRenderer extends SwingWorker<Object, Object> { 359 private final JFreeChart chart; 360 private final BufferedImage buffer; 361 private final Dimension chartSize; 362 private final Point2D anchor; 363 private final ChartRenderingInfo info; 364 365 public OfflineChartRenderer(JFreeChart chart, BufferedImage image, 366 Dimension chartSize, Point2D anchor, ChartRenderingInfo info) { 367 this.chart = chart; 368 this.buffer = image; 369 this.chartSize = chartSize; 370 this.anchor = anchor; 371 this.info = info; 372 } 373 374 @Override 375 protected Object doInBackground() throws Exception { 376 clearChartBuffer(buffer); 377 378 Graphics2D bufferG2 = buffer.createGraphics(); 379 if ((this.buffer.getWidth() != this.chartSize.width) 380 || (this.buffer.getHeight() != this.chartSize.height)) { 381 // Scale the chart to fit the buffer 382 bufferG2.scale( 383 this.buffer.getWidth() / this.chartSize.getWidth(), 384 this.buffer.getHeight() / this.chartSize.getHeight()); 385 } 386 Rectangle chartArea = new Rectangle(this.chartSize); 387 388 this.chart.draw(bufferG2, chartArea, this.anchor, this.info); 389 bufferG2.dispose(); 390 391 // Return type is not used 392 return null; 393 } 394 395 @Override 396 protected void done() { 397 synchronized (state) { 398 state = state.offlineRenderingDone( 399 OfflineRenderingChartPanel.this, this); 400 } 401 } 402 } 403 404 @Override 405 public void setCursor(Cursor cursor) { 406 super.setCursor(cursor); 407 408 // Buggy mouse cursor: setting both behaves as expected 409 Container root = getTopLevelAncestor(); 410 if (null != root) { 411 root.setCursor(cursor); 412 } 413 } 414 415 private static void copyChartRenderingInfo(ChartRenderingInfo source, 416 ChartRenderingInfo target) { 417 if (source == null || target == null || source == target) { 418 // Nothing to do 419 return; 420 } 421 target.clear(); 422 target.setChartArea(source.getChartArea()); 423 target.setEntityCollection(source.getEntityCollection()); 424 copyPlotRenderingInfo(source.getPlotInfo(), target.getPlotInfo()); 425 } 426 427 private static void copyPlotRenderingInfo(PlotRenderingInfo source, 428 PlotRenderingInfo target) { 429 target.setDataArea(source.getDataArea()); 430 target.setPlotArea(source.getPlotArea()); 431 for (int i = 0; i < target.getSubplotCount(); i++) { 432 PlotRenderingInfo subSource = source.getSubplotInfo(i); 433 PlotRenderingInfo subTarget = new PlotRenderingInfo( 434 target.getOwner()); 435 copyPlotRenderingInfo(subSource, subTarget); 436 target.addSubplotInfo(subTarget); 437 } 438 } 439 440 private static BufferedImage createChartBuffer(Graphics2D g2, 441 Dimension bufferSize) { 442 GraphicsConfiguration gc = g2.getDeviceConfiguration(); 443 return gc.createCompatibleImage(bufferSize.width, bufferSize.height, 444 Transparency.TRANSLUCENT); 445 } 446 447 private static void clearChartBuffer(BufferedImage buffer) { 448 Graphics2D bufferG2 = buffer.createGraphics(); 449 // make the background of the buffer clear and transparent 450 bufferG2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f)); 451 bufferG2.fill(new Rectangle(buffer.getWidth(), buffer.getHeight())); 452 bufferG2.dispose(); 453 } 454}