Masoud Kalali has a software engineering degree and has been working on software development projects since 1998. He has experience with a variety of technologies (.NET, J2EE, CORBA, and COM+) on diverse platforms (Solaris, Linux, and Windows). His experience is in software architecture, design, and server-side development.

Masoud has published several articles at Java.net and Dzone. He has authored multiple refcards, published by Dzone, including but not limited to Using XML in Java, Java EE Security and GlassFish v3 refcardz. He is one of the founder members of NetBeans Dream Team and a GlassFish community spotlighted developer. Recently Masoud's new book, GlassFish Security has been published which covers GlassFish v3 security and Java EE 6 security.

Masoud's main area of research and interest includes service-oriented architecture and large scale systems' development and deployment and in his leisure time he enjoys photography, mountaineering and camping. Masoud's can be followed at his Twitter account.

Masoud has posted 82 posts at DZone. You can read more from them at their website. View Full User Profile

Using Dynamically Generated JFreeChart Charts and JasperReports

09.22.2009
| 12579 views |
  • submit to reddit

If you are reading this blog it means you want to use JasperReport with dynamic programmatically generated images. An example of such use cases in generating complex charts using JFreeChart and then including these charts into reports.

Each report in JasperReport uses a datasource to populate the fields, we need to use JRBeanCollectionDataSource which can be considered as a simple collection of JavaBeans. Each object in the JRBeanCollectionDataSource can be used to populate one row of the report or it can be used to prepare calculated values and so on.

Each Report in JasperReport is basically an XML file which describe the report. in our case the report description document is like:

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="DataSourceReport" pageWidth="595" pageHeight="842" columnWidth="515" leftMargin="40" rightMargin="40" topMargin="50" bottomMargin="50">
<style name="Sans_Normal" isDefault="true" fontName="DejaVu Sans" fontSize="12" isBold="false" isItalic="false" isUnderline="false" isStrikeThrough="false"/>
<style name="Sans_Bold" isDefault="false" fontName="DejaVu Sans" fontSize="12" isBold="true" isItalic="false" isUnderline="false" isStrikeThrough="false"/>
<style name="Sans_Italic" isDefault="false" fontName="DejaVu Sans" fontSize="12" isBold="false" isItalic="true" isUnderline="false" isStrikeThrough="false"/>
<field name="image" class="java.awt.image.BufferedImage"/>
<field name="description" class="java.lang.String"/>
<detail>
<band height="180">
<image scaleImage="RetainShape" hAlign="Center" isUsingCache="true" isLazy="true">
<reportElement x="67" y="17" width="344" height="140"/>
<imageExpression class="java.awt.Image"><![CDATA[$F{image}]]></imageExpression>
</image>
<textField>
<reportElement x="83" y="157" width="311" height="20"/>
<textElement/>
<textFieldExpression class="java.lang.String"><![CDATA[$F{description}]]></textFieldExpression>
</textField>
</band>
</detail>
</jasperReport>

As you can see I defined two fields named image and description which are two property of the ReportBean objects included in the data source. The report looks as follow in the IreportDesigner (I am using NetBeans plugin for developing reports).

report in the designer

Lets see what is the JavaBean class we want to use to carry the report fields from our Java code to the JasperReport engine.

public class ChartBean {

public ChartBean(BufferedImage image, String description) {
setImage(image);
setDescription(description);
}



private java.awt.image.BufferedImage image;
private String description;

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public BufferedImage getImage() {
return image;
}

public void setImage(BufferedImage image) {
this.image = image;
}


}

Now we need to prepare the datasource for the report, to prepare the datasource I wrote a method like the following snippet which uses another method to extract the BufferedImage from the JFreeChart items which we want to include in the report.

public JRBeanCollectionDataSource prepareDataSource() {


List charts = new ArrayList();

for (int i = 0; i < 5; i++) {
JFreeChart chart = createChart("This is chart number: " + i);
BufferedImage bi = extractImage(chart, 800, 600);
ChartBean chartBean = new ChartBean(bi, "This is description for Chart: " + i);
charts.add(chartBean);
}

return new JRBeanCollectionDataSource(charts);
}

The createChart method create some sample charts which we will include in the report. the method is simpley creating some dummy ring charts without any specific and meaningful data.


private JFreeChart createChart(String chartTitle) {


DefaultPieDataset piedataset = new DefaultPieDataset();
piedataset.setValue("One", new Double(43.200000000000003D));
piedataset.setValue("Two", new Double(10D));
piedataset.setValue("Three", new Double(27.5D));
piedataset.setValue("Four", new Double(17.5D));
piedataset.setValue("Five", new Double(11D));
piedataset.setValue("Six", new Double(19.399999999999999D));


JFreeChart jfreechart = ChartFactory.createRingChart(chartTitle, piedataset, false, true, false);

RingPlot ringplot = (RingPlot) jfreechart.getPlot();
ringplot.setLabelFont(new Font("SansSerif", 0, 12));
ringplot.setNoDataMessage("No data available");
ringplot.setSectionDepth(0.34999999999999998D);
ringplot.setCircular(false);
ringplot.setLabelGap(0.02D);
return jfreechart;
}

The other method which need to be explained is extractImage which simply create a BufferedImage from a given JfreeChart object.


 public BufferedImage extractImage(JFreeChart chart, int width, int height) {
BufferedImage img =
new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);

Graphics2D g2 = img.createGraphics();
chart.draw(g2, new Rectangle2D.Double(0, 0, width, height));

g2.dispose();
return img;

}

Finally I have a method named showReport which uses the created datasource to prepare a report and then uses JRViewer to show the report. The code snippet is as follow:


  public void showReport(JRBeanCollectionDataSource dataSource) {

try {
Map parameters = new HashMap();
JasperDesign jasperDesign = JRXmlLoader.load(this.getClass().getResourceAsStream("/jasperdynamic/report.jrxml"));
JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign);
JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, dataSource);
JRViewer viewer = new JRViewer(jasperPrint);
viewer.setSize(850, 500);
viewer.setMinimumSize(new Dimension(850, 510));
viewer.setPreferredSize(new Dimension(850, 510));

this.setLayout(new BorderLayout(10, 10));

this.setSize(850, 500);

this.getContentPane().add(viewer);
this.setVisible(true);

} catch (Exception ex) {
ex.printStackTrace();
}
}

And the complete code for the application application class is as follow:


/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package jasperdynamic;

import javax.swing.JFrame;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.design.JasperDesign;
import net.sf.jasperreports.engine.xml.JRXmlLoader;
import net.sf.jasperreports.view.JRViewer;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.RingPlot;
import org.jfree.data.general.DefaultPieDataset;

/**
*
* @author masoud
*/
public class Main extends JFrame {

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
Main m = new Main();

m.showReport(m.prepareDataSource());

}

public JRBeanCollectionDataSource prepareDataSource() {


List charts = new ArrayList();

for (int i = 0; i < 5; i++) {
JFreeChart chart = createChart("This is chart number: " + i);
BufferedImage bi = extractImage(chart, 800, 600);
ChartBean chartBean = new ChartBean(bi, "This is description for Chart: " + i);
charts.add(chartBean);
}

return new JRBeanCollectionDataSource(charts);
}

public BufferedImage extractImage(JFreeChart chart, int width, int height) {
BufferedImage img =
new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);

Graphics2D g2 = img.createGraphics();
chart.draw(g2, new Rectangle2D.Double(0, 0, width, height));

g2.dispose();
return img;

}

public void showReport(JRBeanCollectionDataSource dataSource) {

try {
Map parameters = new HashMap();
JasperDesign jasperDesign = JRXmlLoader.load(this.getClass().getResourceAsStream("/jasperdynamic/report.jrxml"));
JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign);
JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, dataSource);
JRViewer viewer = new JRViewer(jasperPrint);
viewer.setSize(850, 500);
viewer.setMinimumSize(new Dimension(850, 510));
viewer.setPreferredSize(new Dimension(850, 510));

this.setLayout(new BorderLayout(10, 10));

this.setSize(850, 500);

this.getContentPane().add(viewer);
this.setVisible(true);

} catch (Exception ex) {
ex.printStackTrace();
}
}

private JFreeChart createChart(String chartTitle) {


DefaultPieDataset piedataset = new DefaultPieDataset();
piedataset.setValue("One", new Double(43.200000000000003D));
piedataset.setValue("Two", new Double(10D));
piedataset.setValue("Three", new Double(27.5D));
piedataset.setValue("Four", new Double(17.5D));
piedataset.setValue("Five", new Double(11D));
piedataset.setValue("Six", new Double(19.399999999999999D));


JFreeChart jfreechart = ChartFactory.createRingChart(chartTitle, piedataset, false, true, false);

RingPlot ringplot = (RingPlot) jfreechart.getPlot();
ringplot.setLabelFont(new Font("SansSerif", 0, 12));
ringplot.setNoDataMessage("No data available");
ringplot.setSectionDepth(0.34999999999999998D);
ringplot.setCircular(false);
ringplot.setLabelGap(0.02D);
return jfreechart;
}
}

Following image shows how the report will look like after we run the program. and the complete source code can be downloaded from Here

report in the designer
From http://weblogs.java.net/blog/kalali
Published at DZone with permission of its author, Masoud Kalali.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Tomas Ramirez replied on Wed, 2009/09/23 - 6:02pm

you should try windward reports. I think the code interface is a lot simpler. plus you can do your template design right in word, excel, or powerpoint, so you're already trained - that's why we created the Windward Report Engine. Companies need a powerful, secure enterprise-level Java/J2EE reporting engine to back their critical reporting and document generation systems. The Windward Engine installs easily, runs on one or more servers as part of your server-based application, and can easily produce hundreds of thousands of reports per day on a single server. The server is a pure Java product (and also is available as pure .NET). Even better news for you guys: When combined with the Windward report design tools AutoTag and AutoTag Max, users design, schedule and run reports from within Microsoft Office. That means you can turn over reporting to the business group and get back to more interesting work. You don't have to take my word for it. Check out this robust Java reporting engine for yourself.

David Gilbert replied on Thu, 2009/09/24 - 1:55pm in response to: Tomas Ramirez

Tomas,

I may be alone in this, but I think that promoting your own product in the comments section of a technical article about a competing product (JasperReports) crosses a line that shouldn't be crossed.  It just doesn't seem right (to me, at least).  It would be better for you to publish a technical article about some aspect of your reporting software (which I'm sure is very good) and get that published somewhere.

Best regards,

Dave

Tomas Ramirez replied on Fri, 2009/09/25 - 11:56am in response to: David Gilbert

Sorry, I didn't mean to offend. I think people hit articles like this one a lot when they're scrambling to find the answer to a difficult problem. And so if they're struggling with the tool they're using, then it's nice to see a different one that might make their lives a little easier.

stuart douglas replied on Wed, 2009/10/14 - 7:41pm

You really should be passing in an instance of DrawChartRenderer (which implements JRRenderable) into the report rather than doing the rasterisation in your own code. This way jasper reports decideds on the image size and you get a much niver looking chart.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.