ThreadChart.java
Code: Select all
/*
* Created on Jul 1, 2006
*
* Spawn a thread to do chart drawing.
*/
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.event.ChartChangeEvent;
import org.jfree.chart.event.ChartChangeEventType;
import org.jfree.chart.event.ChartChangeListener;
import org.jfree.chart.event.ChartProgressEvent;
import org.jfree.chart.event.ChartProgressListener;
import org.jfree.chart.plot.Plot;
import edu.emory.mathcs.backport.java.util.concurrent.Callable;
import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
import edu.emory.mathcs.backport.java.util.concurrent.Executors;
import edu.emory.mathcs.backport.java.util.concurrent.Future;
/**
* @author Charles Anderson
*
* This class creates a proxy around the main JFreeChart draw method. It creates
* a new BufferedImage that is passed to the superclass draw method in a new
* thread. When it receives a ChartProgress.DRAWING_FINISHED event it fires a
* ChartChanged back to the ChartPanel, which causes it to redraw. When we get
* the next call to draw we simply fill graphics with the imgBuffer that was
* drawn by JFreeChart.
*/
public class ThreadChart extends JFreeChart implements ChartProgressListener, ChartChangeListener {
private BufferedImage imgBuffer = null;
private ExecutorService exec = Executors.newSingleThreadExecutor();
private Future chartDrawing;
private Font font = new Font("Dialog", Font.PLAIN, 14);
private int width;
private int height;
/**
* @param title
* @param titleFont
* @param plot
* @param createLegend
*/
public ThreadChart(String title, Font titleFont, Plot plot, boolean createLegend) {
super(title, titleFont, plot, createLegend);
addListeners();
}
/**
* @param title
* @param plot
*/
public ThreadChart(String title, Plot plot) {
super(title, plot);
addListeners();
}
/**
* @param plot
*/
public ThreadChart(Plot plot) {
super(plot);
addListeners();
}
/**
*
*/
private void addListeners() {
super.addProgressListener(this);
super.addChangeListener(this);
}
public void superDraw(Graphics2D g2, final Rectangle2D chartArea, final Point2D anchor, final ChartRenderingInfo info) {
super.draw(g2, chartArea, anchor, info);
}
public void draw(Graphics2D g2, final Rectangle2D chartArea, final Point2D anchor, final ChartRenderingInfo info) {
Rectangle d = chartArea.getBounds();
int w = d.width;
int h = d.height;
if (imgBuffer != null && w == this.width && h == this.height) {
g2.drawImage(imgBuffer, 0, 0, null);
return;
}
else {
if (chartDrawing != null) {
chartDrawing.cancel(true);
}
this.width = d.width;
this.height = d.height;
imgBuffer = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_RGB);
chartDrawing = exec.submit(new DrawTask((Graphics2D)imgBuffer.getGraphics(), chartArea, anchor, info));
}
if (getBackgroundPaint() != null) {
g2.setPaint(getBackgroundPaint());
g2.fill(chartArea);
}
drawCenteredString(g2, chartArea, "Drawing Chart");
}
/**
* @author Charles Anderson
*
*/
private class DrawTask implements Callable {
private Graphics2D g2;
private Rectangle2D chartArea;
private Point2D anchor;
private ChartRenderingInfo info;
public DrawTask(Graphics2D g2, Rectangle2D chartArea, Point2D anchor, ChartRenderingInfo info) {
this.g2 = g2;
this.chartArea = chartArea;
this.anchor = anchor;
this.info = info;
}
public Object call() throws Exception {
superDraw(g2, chartArea, anchor, info);
if (!Thread.interrupted())
fireChange();
return null;
}
}
/**
* @param g2
* @param chartArea
* @param text
*/
private void drawCenteredString(Graphics2D g2, Rectangle2D chartArea, String text) {
Rectangle d = chartArea.getBounds();
int w = d.width;
int h = d.height;
g2.setColor(Color.BLACK);
FontRenderContext frc = g2.getFontRenderContext();
TextLayout tl = new TextLayout(text, font, frc);
float tx = w / 2 - tl.getAdvance() / 2;
float ty = h / 2 - tl.getAscent() / 2;
tl.draw(g2, tx, ty);
}
/*
* (non-Javadoc)
*
* @see org.jfree.chart.event.ChartProgressListener#chartProgress(org.jfree.chart.event.ChartProgressEvent)
*/
public void chartProgress(ChartProgressEvent event) {
if (event.getType() == ChartProgressEvent.DRAWING_FINISHED && chartDrawing != null && chartDrawing.isDone()
&& !chartDrawing.isCancelled()) {
fireChange();
}
}
private void fireChange() {
chartDrawing = null;
if (!EventQueue.isDispatchThread()) {
EventQueue.invokeLater(new Runnable() {
public void run() {
fireChartChanged();
}
});
}
else {
fireChartChanged();
}
}
/*
* (non-Javadoc)
*
* @see org.jfree.chart.event.ChartChangeListener#chartChanged(org.jfree.chart.event.ChartChangeEvent)
*/
public void chartChanged(ChartChangeEvent event) {
if (event.getType() == ChartChangeEventType.DATASET_UPDATED || event.getType() == ChartChangeEventType.NEW_DATASET
|| event.getSource() != this) {
imgBuffer = null;
}
}
}
TestThreadChart.java
Code: Select all
/*
* Created on May 16, 2006
*
* TODO To change the template for this generated file go to
* Window - Preferences - Java - Code Style - Code Templates
*/
import javax.swing.JPanel;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
/**
* A simple demo showing a dataset created using the {@link XYSeriesCollection}
* class.
*/
public class TestThreadChart extends ApplicationFrame {
public TestThreadChart(String title) {
super(title);
XYDataset dataset = createDataset();
JFreeChart chart = createChart(dataset);
ChartPanel chartPanel = new ChartPanel(chart, true);
chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
setContentPane(chartPanel);
}
private static JFreeChart createChart(XYDataset dataset) {
NumberAxis xAxis = new NumberAxis("X");
xAxis.setAutoRangeIncludesZero(false);
NumberAxis yAxis = new NumberAxis("Y");
XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false);
XYPlot plot = new XYPlot(dataset, xAxis, yAxis, renderer);
plot.setOrientation(PlotOrientation.VERTICAL);
renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator());
JFreeChart chart = new ThreadChart("XY Series Demo", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
return chart;
}
private static XYDataset createDataset() {
XYSeries series = new XYSeries("Random Data");
double t = 0.0;
double x = 0.0;
for (int i = 0; i < 100000; i++) {
t += Math.random();
double r = Math.random();
if (r < 0.33) {
x += Math.random();
}
else if (r < 0.66) {
x -= Math.random();
}
series.add(t, x);
}
return new XYSeriesCollection(series);
}
public static JPanel createDemoPanel() {
JFreeChart chart = createChart(createDataset());
return new ChartPanel(chart);
}
public static void main(String[] args) {
TestThreadChart demo = new TestThreadChart("XY Series Demo");
demo.pack();
RefineryUtilities.centerFrameOnScreen(demo);
demo.setVisible(true);
}
}
While it's doing that it puts a message saying "Drawing Chart" in the middle of the graphics2d that was passed in from the ChartPanel paintComponent method, which is returned immediately.
If you click on the chart while it's drawing you can actually see the partially drawn plot. I'm not sure if this is a good thing or not. There may need to be a check added to not copy the partially drawn image.
Swing remains responsive, and you can right click on the panel and the popup comes right up.
It does produce a little bit of an extra lag on small charts, so I don't think it would be something you want to use all the time.
I'm using the util-concurrent backport, because I'm relagated to java 1.4.
If you're using java 5 you can just change the import.
If you're stuck on 1.4 like me, the port is at http://www.mathcs.emory.edu/dcl/util/ba ... concurrent
I added the call to fireChange from DrawTask because I was getting the chartProgress event before the Thread was marked as done.
So really at this point the ChartProgress listener isn't really neccesary.