random technical thoughts from the Nominet technical team

Adding charts to Spring web applications with JFreeChart

1 Star2 Stars3 Stars4 Stars5 Stars (5 votes, average: 5 out of 5)
Loading ... Loading ...
Posted by tom on Aug 20th, 2008

I’m currently in the process of adding some reports based on data stored in a database. With annotated configuration in Spring 2.5 and the flexibility of JFreeChart, I was surprised at what can be achieved with such little effort.

The new features added to spring-mvc provide a nice way to avoid having to manage large configuration files and meant that I could concentrate on writing the code for my charts. I’ve added a quick example after the jump

The intention of my first chart was to read data from our database and present the information in a bar chart which will be available on an internal site.

category barchart

As I was just adding to an existing Spring web application, all I had to do was configure a new spring-mvc DispatcherServlet in web.xml

    <servlet>
            <servlet-name>chart</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
            <servlet-name>chart</servlet-name>
            <url-pattern>*.png</url-pattern>
    </servlet-mapping>       

The ‘chart’ servlet now needs a corresponding chart-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                                 http://www.springframework.org/schema/beans/spring-beans.xsd
                                 http://www.springframework.org/schema/context
                                 http://www.springframework.org/schema/context/spring-context.xsd">

  <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
  <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
  <context:annotation-config />
  <context:component-scan base-package="uk.nominet.demo" />

</beans>

This configuration just instructs spring to search the classpath for all classes that are annotated with @Controller. Normally there would also be a ViewResolver configured too (for rendering the JSP, Freemarker, etc) but in this case the images will be streaming directly from the controller.

Now all that is left to do is write the code.

/**
* Example Spring Controller for creating JFreeChart charts
*/
@Controller
public class DemoChartController {
 
  @RequestMapping("/demo-chart.png")
  public void renderChart(String variation, OutputStream stream) throws Exception {
    boolean rotate = "rotate".equals(variation); // add ?variation=rotate to the URL to rotate the chart
    JFreeChart chart = generateChart(rotate);
    ChartUtilities.writeChartAsPNG(stream, chart, 750, 400);
  }
 
  private JFreeChart generateChart(boolean rotate){
    DefaultCategoryDataset data = getDataset();
    return ChartFactory.createBarChart( "example graph", // title
        "x-axis"// x-axis label
        "y-axis"// y-axis label
        data,
        rotate ? PlotOrientation.HORIZONTAL : PlotOrientation.VERTICAL,
        true,      // legend displayed
        true,      // tooltips displayed
        false );   // no URLs*/
  }
 
}

The above code handles the request and creates the chart. The @RequestMapping(”/demo-chart.png”) tells the DispatcherServlet what URL we are interested in whilst Spring is able to work out what to do with the parameters in the renderChart() method - pretty clever.

    private static final SimpleDateFormat dateFormatter = new SimpleDateFormat("dd MMM");
    private DataSource dataSource;
 
    private DefaultCategoryDataset getDataset(){
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        // Run the SQL query and add the results to the JFreeChart dataset
 
        for (Object o : getQueryResults()){
 
            Map result = (Map) o;
            Number count = (Number) result.get("count");
            String category = (String) result.get("categoryName");
            String day = dateFormatter.format((Timestamp) result.get("day"));
            dataset.addValue(count, category, day);
        }
        return dataset;
    }
 
    private List getQueryResults(){
        // Use Spring's JdbcTemplate to run the SQL query
        // Also see JFreeChart's JDBCCategoryDataset
        return (new JdbcTemplate(dataSource)).queryForList(
                "select count(id) count, categoryName, trunc(mydate) as day\n" +
                        "from myTable\n" +
                        "where mydate &gt; sysdate - 14\n" +
                        "group by categoryName, trunc(mydate)"
        );
    }
 
    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

This last bit of code just executes the SQL query and populates a DataSet to use in the chart. There are of course better ways of doing this. The @Autowired dataSource is set from the main spring context in our web application. If you are creating an application from scratch you could add the dataSource definition to the chart-servlet.xml

Making the charts look good requires a little more effort and it’s best to create custom chart factories. Here I’ve used the same data but create a stacked bar chart which makes the weekends stand out…

stacked barchart

Here is the basic factory method for this chart…

    private static JFreeChart createChart(DefaultCategoryDataset dataset){
        ValueAxis valueAxis = new NumberAxis("Value");
 
        CategoryAxis categoryAxis = new CategoryAxis("Day");
        categoryAxis.setLowerMargin(0.01d); // indent first bar
        categoryAxis.setUpperMargin(0.01d); // indent last bar
        categoryAxis.setCategoryMargin(0.1d); // space between categories
 
        BarRenderer renderer = new StackedBarRenderer();
        renderer.setItemMargin(0d); // remove space between items
        CategoryPlot categoryPlot = new CategoryPlot(dataset, categoryAxis, valueAxis, renderer);
 
        // change the colours of the bars
        GradientPaint gradient1 =
                new GradientPaint( 0.0f, 0.0f, new Color(51, 104, 155, 196),
                        0.0f, 0.0f, new Color(51, 104, 155, 64) );
        GradientPaint gradient2 =
                new GradientPaint( 0.0f, 0.0f, new Color(0, 140, 208, 196),
                        0.0f, 0.0f, new Color(0, 140, 208, 64) );
        categoryPlot.getRenderer(0).setSeriesPaint(0, gradient1);
        categoryPlot.getRenderer(0).setSeriesPaint(1, gradient2);
 
        // bring it all together
        return new JFreeChart("Bar Chart Demo",
                new Font("Helvetica", Font.BOLD, 12),
                categoryPlot,
                false);
    }

More about annotated controllers
More examples on JFreeChart. I used one of the ChartFactory examples in the code above.

9 Responses

  1. Marco Says:

    For what it’s worth: Google has some nice charting to offer too:

    http://code.google.com/apis/chart/

  2. tom Says:

    I will probably try the Google chart API too to see which is simpler to use. It looks fantastic for ad-hoc charts but it does require you hand over your data.
    Some of the charts we produce may be confidential so I’ll be using Eastwood (Jfreechart wrapper) that clones the Google API.

    http://www.jfree.org/eastwood/

  3. andrew Says:

    Hi!
    Very very good solution… I like it! But I have a question..

    Is the file PNG stored somewhere?
    Is the file deleted after the user close the browser?

    thanks..
    Bye

  4. tom Says:

    Andrew,

    The PNG images are created for each request. They are not stored but are written straight to the http stream.

    Just by having an OutputStream as a parameter in the renderChart() method, the Spring DispatcherServlet knows that we want the ServletResponse stream.
    In a completely standard J2EE servlet, we would have used ServletResponse.getOutputStream().

    The Javadoc for @RequestMapping lists all of the supported parameter types - http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/web/bind/annotation/RequestMapping.html

  5. andrew Says:

    Hi Tom,
    The tooltip is not working…
    Is because the images are written directly in the http stream?
    Thanks!

    Bye

  6. ashok Says:

    How to create hyperlinks to the BARS in BARCHART and PIECHARTS?

  7. Svetlana Says:

    Hi! I need a javascript method for updating charts. I tried to change image.src param, but controller’s method wasn’t called the second time. Do you have any advices for me? Thanks!

  8. marcelo Says:

    Great sample!!

  9. Raj Says:

    The article is superb,how to add a horizantal scroll bar for the bar chart generated

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.

Recent Posts

Highest Rated

Categories

Archives

Meta: