Custom Visualization Component for Jaspersoft Studio v5.6.x

The Custom Visualization component for Jaspersoft Studio v5.6.x

This page describes the Custom Visualization Component included in Jaspersoft Studio v5.6.x.  The Jaspersoft Studio v6.0.x version of the component is significantly different and has its own page.

The Custom Visualization component  allows JasperReports to include dynamic, data driven visualizations in the SVG image format and display them across the output formats JasperReports can deliver – HTML (browser), PDF, Excel and more. The engine behind the component uses a Velocity “template” to generate the SVG. Templates can be configured to leverage Javascript libraries. The PhantomJS engine is used to execute the Javascript generated by the template and produce the final SVG image, and also to generate PNG images for output formats that do not use SVG

Pre-requisites

  1. Install PhantomJS

    Download PhantomJS from http://phantomjs.org/ and include the PhantomJS binary in the PATH, so JasperReports will be able to find it easily. If you don't want the binary to be in the path, you can specify its exact location by setting the property:

    com.jaspersoft.jasperreports.components .customvisualization.phantomjs.executable.path
  2. Configure Jaspersoft Studio

    There are some properties to configure which in JSS can be done from Preferences -> Jaspersoft Studio -> Properties

    (Click to Enlarge)
    Click to Enlarge

    There are two sets of properties, the first ones are used to run the reports "off line", which is not inside an interactive viewer, the second ones are used to let the interactive viewer servlet to properly access the JavaScript component provided by the Bridge component, and all the scripts that will be loaded dynamically from the class path by JasperReports.

    Script locations are configured by specifying where each script is located. These properties will be used to generate temporary html files passed to PhantomJS

com.jaspersoft.jasperreports.components.customvisualization.script.path.d3=file:///path/to/my/JaspersoftWorkspace/myProject/scripts/scripts/d3/d3.v3.min.js
com.jaspersoft.jasperreports.components.customvisualization.script.path.d3-layout=file:///path/to/my/JaspersoftWorkspace/myProject/scripts/scripts/d3/d3.layout.min.js
com.jaspersoft.jasperreports.components.customvisualization.script.path.jquery=file:///path/to/my/JaspersoftWorkspace/myProject/scripts/scripts/jquery/jquery.min.js
com.jaspersoft.jasperreports.components.customvisualization.script.path.highcharts=file:///path/to/my/JaspersoftWorkspace/myProject/scripts/scripts/highcharts/highcharts-3.0.7.src.js
com.jaspersoft.jasperreports.components.customvisualization.script.path.raphael=file:///path/to/my/JaspersoftWorkspace/myProject/scripts/scripts/graphael/raphael.js
com.jaspersoft.jasperreports.components.customvisualization.script.path.graphael=file:///path/to/my/JaspersoftWorkspace/myProject/scripts/scripts/graphael/g.raphael.js
com.jaspersoft.jasperreports.components.customvisualization.script.path.gdot=file:///path/to/my/JaspersoftWorkspace/myProject/scripts/scripts/graphael/g.dot.js
com.jaspersoft.jasperreports.components.customvisualization.script.path.qrcode=file:///path/to/my/JaspersoftWorkspace/myProject/scripts/scripts/qrcode/qrcode.min.js

Replace

/path/to/my/JaspersoftWorkspace/myProject

with the path to your project.

On Windows, a bug of PhantomJS requires you to remove the file://. I suggest you to test with it first, in case of problems, remove the prefix.

(The PhantomJS bug is https://github.com/ariya/phantomjs/issues/10231)

The other two properties we need to set are used in interactive environments (Interactive Viewer and JasperReports Server), and tell to JasperReports that is safe to load resources from specific class path locations.

There are two location that we need to allow: the location of the JavaScript custom visualization component, which is inside the component JAR

net.sf.jasperreports.web.resource.pattern.customvisualization=com/jaspersoft/jasperreports/customvisualization/.*

and the location from which we will load all the scripts. Since we made the folder scripts as a class path root, all the scripts will be loaded with a class path uri like "script/d3/d3.v3.min.js". So we want to allow everything in the class path under the scripts directory:

net.sf.jasperreports.web.resource.pattern. customvisualization.scripts=scripts/. 
  • Configure JasperReports Server (Minimum: v5.6)

    1. Installing the custom component Jar (if required)

      Add the jasperreports-customvisualization.jar into the WEB-INF/lib directory of your JasperReports Server web application (it should or will added to 6.x versions of the server)

    2. Installing the templates

      Create a directory inside the classes directory to store the templates. We will store our sample templates there. Since the component configuration inside the jrxml will need to reference this path, we need to keep note of it. Technically, should be possible to also load templates from the Repo, but for simplicity (and for security), templates are for now loaded from the classpath. Templates inside the custom component configuration are referenced with the velocity.template property:

    3. Installing the scripts

      Scripts are used by the server-side rendering engine (PhantomJS) and by JasperReports Server when the report is executed in an interactive environment.
      In the first case, the location of the script is defined by a proper jasperreports property, in the second, the script can be defined either as requirejs module or provided inside the scripts directory to be loaded from a given path (i.e. customvisualization/d3.js).

      Create a directory inside the scripts directory in your webapp root, we will put here all the JavaScript used by our components (please note that from 6.0 scripts are loaded from the directory optimized-scripts, but still referenced as scripts/...). The location can be changed accordingly to your preferences.

      Copy here all your JavaScript files such as d3.js, d3-layout.js, raphael.js etc....

      Edit the file WEB-INF/classes/jasperreports.properties to configure the location of your JavaScript files by adding the following rows:

      net.sf.jasperreports.web.resource.pattern.customvisualization=com/jaspersoft/jasperreports/components/customvisualization/.*
      com.jaspersoft.jasperreports.components.customvisualization.script.path.d3=file:///Applications/jasperreports-server-6.0/apache-tomcat/webapps/jasperserver-pro/scripts/customvisualization/d3.v3.min.js
      com.jaspersoft.jasperreports.components.customvisualization.script.path.d3-layout=file:///Applications/jasperreports-server-6.0/apache-tomcat/webapps/jasperserver-pro/scripts/customvisualization/d3.layout.min.js
      com.jaspersoft.jasperreports.components.customvisualization.script.path.jquery=file:///Applications/jasperreports-server-6.0/apache-tomcat/webapps/jasperserver-pro/optimized-scripts/bower_components/jquery/dist/jquery.js

      We defined here just D3 and JQuery, but the properties can be defined also for other scripts used in the samples (paths may need to be adjusted accordingly):

      com.jaspersoft.jasperreports.components.bridge.customvisualization.path.gdot=file:///Applications/jasperreports-server-6.0/apache-tomcat/webapps/jasperserver-pro-trunk/scripts/customvisualization/g.dot.js
      com.jaspersoft.jasperreports.components.customvisualization.script.path.graphael=file:///Applications/jasperreports-server-6.0/apache-tomcat/webapps/jasperserver-pro-trunk/scripts/customvisualization/g.raphael.js
      com.jaspersoft.jasperreports.components.customvisualization.script.path.highcharts=file:///Applications/jasperreports-server-6.0/apache-tomcat/webapps/jasperserver-pro-trunk/scripts/bridge/highcharts-3.0.7.src.original.js
      com.jaspersoft.jasperreports.components.customvisualization.script.path.jquery=file:///Applications/jasperreports-server-6.0/apache-tomcat/webapps/jasperserver-pro-trunk/scripts/scripts/bower_components/highcharts-pack/highcharts/highcharts.js
      com.jaspersoft.jasperreports.components.customvisualization.script.path.raphael=file:///Applications/jasperreports-server-6.0/apache-tomcat/webapps/jasperserver-pro-trunk/scripts/customvisualization/raphael.js

      The first line will allow JasperReports to load and serve resources from this package (they are the core javascript files of the bridge component)

      All the others depend by the used components, in this case D3, GRaphael with Dot chart, Highcharts 3.0.7

      The last (optional and discouraged) step is to modify the file require.config.js inside the scripts directory, by changing this line from

      enforceDefine:true

      to

      enforceDefine:false

      This will allow to load non-module javascripts in RequireJS. This set is discouraged because this may break Visualize.js, and should be considered not correct. Instead, all the javascript files loaded by the components should be either modified to be presented as requirejs modules (only when not loaded for server side rendering) or specified inside the file jasperreports-server-6.0/apache-tomcat/webapps/jasperserver-pro/optimize-scripts/require.config.js

      Some modules (like jquery) are already defined. To define D3, add the paths and shim configuration (we assume here that the D3 scripts reside inside .../optimized-scripts/customvisualization directory):

      'd3': 'customvisualization/d3.v3.min',
      'd3.layout': 'customvisualization/d3.layout.min',

      Shim (please note that d3.v3 is already AMD compatible, and may be omitted):

      'd3': {
          deps: [],
          exports: 'd3'
      },
      'd3.layout': {
          deps: ['d3'],
          exports: 'd3'
      } 

Using the Custom Visualization component in Studio

The Custom Vizualization component appears on the Jaspersoft Studio pallete and can be added into the report layout like any other object from the pallete. The Custom Vizualization component can be included in any band in the report.

The Custom Vizualization object in the report will have a particular size and position in the band, like any other object/component in JasperReports. You can see this and other properties in the Appearance tab of the object properties.

The Appearance tab of the Custom Vizualization component properties

The Custom Vizualization specific properties are:

Evaluation Time

As per evaluationTime in JasperReports elements

Now
Report
Page
Band
Column
Auto

On Error Type

How to handle errors from the component

Null: show null (?)
Error: raise a Java programmatic exception - default
Blank: show a blank space
Icon: show an icon

Properties

A list of named values/properties that will be passed into the specified “template”.

The single required property is “velocity.template”. This has to refer to a template file that is on the Java classpath at report execution time.

Item Data configuration

A list of JasperReports data sets that will be passed into the template. These data sets are a link to data from the queries within the report.

Processing Class

Allows manipulation of data being passed into the template, adding/changing values in the “configuration” object (see below).

Class needs to implement the Java interface

com.jaspersoft.jasperreports.customvisualization.Processor

Single method:

public Map<String, Object> processConfiguration( Map<String, Object> configuration );

Default is

com.jaspersoft.jasperreports.customvisualization.DefaultProcessor

which does nothing.

These are set in the Custom Visualization tab.

The Custom Visualization tab of the Custom Visualization component properties

Item Data Configuration

You can define multiple data sets to flow into the template.

The names of the properties in the data set will be accessible within the template.

Custom Vizualization component templates

The template used by a Custom Visualization component defines the way SVG will generated into the report. Templates use a simple scripting engine, called Velocity, to take data and configuration information at report generation time, and blend that into the final SVG output.

There will be a template for each type of visualization you want to generate. By defining a template with a range of configuration options, you can reuse templates across Custom Visualization components.

Let’s start with a basic template.

<div id="element${configuration.element.hashCode()}"
     style="width: ${configuration.element.width}px; height: ${configuration.element.height}px;" >
 
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
         width='${configuration.element.width}px' height='${configuration.element.height}px'>
        <circle cx='120' cy='150' r='60' style='fill: gold;'>
            <animate attributeName='r' from='2' to='80' begin='0' dur='3' repeatCount='indefinite' />
        </circle>      
        <text x='60' y='250' fill='blue'>Hello, World!</text>
    </svg>
</div>
 
<script class='jasperreports' type='text/javascript'>
 
## Empty requireJS function. Used when no javascript is involved.
function renderComponentelement${configuration.element.hashCode()}(instanceData)
{
return [];
}
 
## The main function (renderComponentelementXXX) is invoked by requirejs and
## the component javascript module automatically. In case the environment is
## not using requirejs, which is what happens with a standard
## export outside jasperreports server or an interactive environment, we need
## to kick the rendering
## process manually by invoking renderComponentelementXXX 
 
#if (!${configuration.isInteractiveViewer})
   renderComponentelement${configuration.element.hashCode()}(
       ${configuration.instanceData});
#end
 
</script>

This script example has no explicit Javascript in it. It has a static SVG object defined in the DIV tag.

Velocity and variables

You can see in this sample template text looking like “${configuration.element.hashCode()}”. When the template runs, these placeholders are replaced with values from the Custom Visualization component definition and run time values. These are Java object references and method calls.

“configuration” is the name of the base object provided to the template at runtime.

configuration.element.XXX

The component parameters from the JRXML.

width, height: in pixels

hashCode(): the unique id of the component

configuration.instanceData

The data sets from the component. These are a JSON object that is easily accessed through Javascript.

Each data set can be access individually by “instanceData.series[X]” where X is 0, 1, 2,…

Each row and values on the row in the data set can be accessed as follows in Javascript:

var series0 = instanceData.series[0];
for (var index = 0; index < series0.length; ++index) {
   var record = series0[index];
   var value = record.<name>;
}

where <name> is as defined in the data set definition.

configuration.XXX

Named properties from the component

configuration.isInteractiveViewer

Is the report being run through the JasperReports/Jaspersoft Studio interactive viewer, or JasperReports Server.

There are Velocity directives that allow loops:

#foreach( $entry in $configuration )
	 ${entry.key}${entry.value}${entry.value.class}
#end

and conditional logic:

#if (!${configuration.isInteractiveViewer})
   renderComponentelement${configuration.element.hashCode()}(
       ${configuration.instanceData});
#end

Find out more about Velocity here: https://velocity.apache.org/

The DIV tag

The DIV tag in the template is the place the Custom Visualization engine expects to access SVG to render as the output to the component.

It has to be named

<div id="element${configuration.element.hashCode()}"> 

Any Javascript processing that is included in the template will need to update this DIV tag.

The DIV tag can be referenced in Javascript by:

d3.select("#element" + instanceData.key)

or

jQuery("#element" + instanceData.key)

(instanceData.key = configuration.element.hashCode())

Required Javascript methods

These functions allow you to update the DIV tag based on the Javascript in the function.

function renderComponentelement${configuration.element.hashCode()}(instanceData)

The Custom Visualization engine will explicitly call this Javascript function when running in the JasperReports/Jaspersoft studio Java interactive viewer.

#if (!${configuration.isInteractiveViewer})
   renderComponentelement${configuration.element.hashCode()}(
       ${configuration.instanceData});
#end

This section of the template is needed to allow the component to run when exporting to HTML and other output formats.

If you are not invoking Javascript from the generated source, you need to include this function:

## Empty requireJS function. Used when no javascript is involved.
function requireJSComponentelement${configuration.element.hashCode()}() {
    return [];
}

Including Javascript libraries

The ${configuration.scripts.require(…) function allows the inclusion of specific Javascript libraries that will be available when the Javascript generated by the template is executing.

${configuration.scripts.require(
{
    "path": "",
    "name": "jquery-1.10.2",
    "export": "jQuery",
    "key": "jquery"
},
{
    "path": "com/jaspersoft/jasperreports/highcharts/charts/render/scripts/highcharts-3.0.7.src.js",
    "name": "",
    "export": "",
    "key": "highcharts"
}
)}

This example includes jQuery and Highcharts Javascript libraries for use in the Javascript generated by the template. ie.

jQuery("#element" + instanceData.key ).highcharts({
                chart: {
                    type: 'pie'
                },
                title: {
                    text: 'Browser market share, April, 2011'
                },
...

The parameters to the function are sets of JSON definitions, one per required library.

Each definition contains:

path: URI to a Javascript file
name: when running in environments like JasperReports Server that use require.js, entering a value here will point a Javascript file in a require defined directory. In JRS, this is jasperserver-pro/optimized-scripts/.
key: name the library will be referred to in JasperReports properties. ie. JasperReports property com.jaspersoft.jasperreports.components.customvisualization.script.path.d3 will refer to a file for key=d3.
export: Javascript variable this library will make available to the template Javascript. Examples include “d3” for D3., “L” for Leaflet, ….”

Base entry objects within the libraries, like “Highcharts” and “d3”, will be instantiated as part of the Javascript loading process that require does.

In practice, the best practices for defining where the Javascript is in the template define statement is:

Set the “key” attribute for running in Jaspersoft Studio, and set the Jaspersoft Studio properties to point to those files.

Don’t prefix file names here with file://; use full path names not relative paths
The “path” attribute will be ignored

Set the “name” attribute for running in Jaspersoft Reports Server, and put Javascript files in jasperserver-pro.optimized-scripts. Path has to be set, but name or key values will overwrite the path values.

Path has to be set, but key and name attributes override path attributes.

So in Jaspersoft Studio, set the key.

In JasperReports Server, set the name, like:

${configuration.scripts.require(
{ 
    "path": "http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.js",
    "export": "L"
}
)}

Installing the Samples

Download the samples from this page: customvisualization-component_samples.zip

Unzip the archive and copy the scripts, reports and templates directories inside your project

The samples package contains three folders:

  1. reports, which is where all the jrxmls are

  2. templates, which contains the Velocity templates used by the samples

  3. scripts, which contains a nested scripts directory, used to make the scripts available to JasperReports class loader (in order to be able to serve that scripts by using the special resource servlet when the reports are executed in an interactive environment)

Right click the scripts directory, and select Build Path -> Use as Source Folder. This will add the scripts directory content to the classpath. Please note that the scripts directory contains another script directory with all the scripts being used by the samples.

You need to do the same for JasperReports Server (copy your templates and the most inner scripts directory in JasperReports Server WEB-INF/classes directory: right folders to be put in the classpath again are templates and scripts (not scripts/scripts).

Working with Custom Vizualization in Studio - Running the samples

We are ready to go!

Open a sample. All the samples use an empty data source or the Sample DB, which are both preconfigured in any Jaspersoft Studio environment.

Press Preview for the "Java" preview:

To test the same report in HTML click on HTML output format:

And you will be able to play with the zoomable circle packing used in this example:

The next step is to try this report inside the interactive viewer. Select the interactive viewer mode:

And run the report inside the interactive viewer:

Developing new templates

The Custom Visualization Component does not offer much without a component template. Templates provide the actual JavaScript that processes the data collected by the component and transforms it in a possibly interactive SVG image.

For the purpose of generating the SVG, it is usually convenient to adopt a JavaScript library like D3js (http://d3js.org/) or RaphaëlJs (http://raphaeljs.com/).

In this section, we will create from scratch a new template and we will use it in Jaspersoft Studio to build our report.

Tutorial material

All the code described in this tutorial is available here: wiki_attachments/custom_template_tutorial.zip

Overview of the Steps

In order to plan our work, it is useful to understand how the Custom Visualization component works. The component element, once placed inside a report, can be configured by means of a set of properties (key/value) and Item Data. Item Data are used to collect set of Items, which are also composed by properties.

The value of each property can be a static text or the result of an expression. The value of the property is converted to a string when the component is printed, so the use of complex objects as property value is discouraged, even if still possible when, for instance, the specific property is meant to be consumed by a custom processing class.

The component and Items properties required by a specific custom visualization template are defined by the template designer. For example, if we plan to create a visualization that shows points on a map, we may decide to have a component property to let the user specify the map background color, let's say backgroundColor, and three properties: label, latitude, longitude for the Items of the Data Item.

When the report is executed, the data collected is stored in a special object called Configuration, which will be serialized in a JSON structure and passed to the JavaScript function in charge to produce the visualization. In particular this JavaScript function, which must have a specific name (more about this later), must create new SVG tag. When exporting in HTML, this tag is automatically rendered by the browser. When the report is exported in other formats, the reporting engine uses PhantomJS to execute the JavaScript code and extract the SVG. The SVG image will then be included in formats like PDF, or converted in an high quality PNG image in formats like MS Excel, Words, etc.


Step 1: Prepare a first version of the JavaScript code

The first step, when creating a new template, is to create the JavaScript that generates visualization. For better productivity, this step should be done with a proper JavaScript editor and by utilizing a library like D3js. One of the good things of using D3js is support coming by a large community of users that provides a lot of example charts that can be used as starting point for our custom visualizations.

In our sample, we want to display data by reusing the code of the following D3js chart:

This chart is showcased in the examples page of D3js at this URL: http://bost.ocks.org/mike/nations/. It is an animated chart from Mike Bostock that shows the size of population of several countries and related average income over the time. We will have to adapt to our needs, since, as all the D3js based charts; it has been cut to size of the data it displays.

The main difference between this chart and a normal bubble chart is that this one can be animated across some kind of time line. In our implementation this extra information will be mapped to a category.

The idea is to produce a chart able to display values having a category value (i.e. Year), an X position, a Y position and a Z value (for the size of each ball).

We start by taking a look at the code of the chart; on the same page from where the chart is showcased there is a nice link (View Source) that points to https://github.com/mbostock/bost.ocks.org/blob/gh-pages/mike/nations/index.html

Around line 140 of the code there is this instruction:

// Load the data.
d3.json("nations.json", function(nations) { ....

This is the typical way to postpone the creation of the chart after loading the data with an ajax call (in this case from a json file). Since the Custom Visualization component provides the data to our script without having to load it with a different call, we need to get rid off of this approach, and refactor the code assuming to have all the data ready to use. Moreover, if we keep the Ajax call to load a data file, the code run locally would break, since browsers don't allow loading files from the disk (this works only on pages hosted on a web server).

We change the code by replacing the callback with a function that provides the data directly (see the file animated_time_series_step1.html in the project files for the complete code).

// Load the data.
var nations = getNations();

getNations() will return directly the data that usually is supposed to be loaded with an Ajax call.

Please note that we are just make changes to be able to run this chart without performing extra ajax call to simplify our development and also to get closer to the way the Custom Visualization component works.

The chart we have taken as starting point sets a predefined rage for the domain axes (years from 1800 to 2009, population range, life expectancy, and so on). We may automatically calculate this range (if numeric), or adapt the script in a way to automatically collect all the category values or even define different strategies to dynamically set using a property what should be the extents of the domain axis.

After some rework in step 2 (animated_time_series_step2.html) we reorganized the code to accept as data a much simpler data structure made up of simple records with the following fields: category, key, x, y, z.

Category is used for the time series, but could be actually be something else (it is just what differentiate from a step to the other). Anyway, in this implementation it must be a numeric value, and all the categories must be sequentials (0,1,2,3,....). In step 4 we will use a category index to remove this requirement and allow the use of arbitrary category text (which must be unique for each category).
Key is used to identify a ball across several categories (so it will maintain the same color and the position once animated).

In step 2 we also abandoned the original data set, and the data is produced randomly, so we can fill the structure we need for testing.

After some more rework in step 3 we restored the animation functionalities that are present in the original chart by rewriting the tween functions (animated_time_series_step3.html).

Finally in step 4 (animated_time_series_step4.html) we have our prototype ready to be converted in a custom visualization template. In particular in this step we removed as "magic" numbers as possible from the original chart, so the chart can adapt properly and dynamically to the data we provide. In particular we put all the code to setup the chart in the setupChart method which also defines a configuration object that we will simply our life to set properties that the final report designer will provided and configure from the UI.

function setupChart(chartData)
{
        var cfg = {};
 
        cfg.width = 960;
        cfg.height = 500;
        cfg.margin_top = 19.5;
        cfg.margin_right = 19.5;
        cfg.margin_bottom = 19.5;
        cfg.margin_left = 69.5;
        cfg.max_ball_size = 40;
        cfg.x_axis_ticks = 10;
        cfg.x_axis_ticks_format = ",d";
        cfg.x_axis_label = "X axis";
        cfg.y_axis_ticks = 10;
        cfg.y_axis_ticks_format = ",d";
        cfg.y_axis_label = "Y axis";

We are now ready to create our template....


Step 2: Prepare the template

The template we need to prepare is a mix of HTML, JavaScript and optionally css. The template engine used by the Custom Visualization component is called Velocity (http://velocity.apache.org/), an Apache project that allows a simple but complete template language.

The parts of a Custom Visualization component template are:

  • The DIV tag definition, which will work as container for our SVG (the DIV definition is pretty much always the same)

  • Javascript imports (which are realized by using an utility method provided to the template itself).

  • The Javascript function that will be invoked by the component engine (or from the web browser in case of HTML output) when the element is exported.

The template itself can be created with a text editor. The usual (but not required) file extension is .vm (which stands for Velocity Macro).

The DIV tag definition

The template starts defining the DIV attribute in this way:

<div id="element${configuration.element.hashCode()}"
     style="width: ${configuration.element.width}px; height: ${configuration.element.height}px;" >
...
</div>

The configuration object that we use in the first line of code includes all the information required to customize our template, in particular it contains:

Property Description
configuration.element The JRTemplateGenericPrintElement that subtends this component element. This object documented here: http://jasperreports.sourceforge.net/api/net/sf/jasperreports/engine/fill/JRTemplateGenericPrintElement.html
configuration.script An instance of ScriptsHandler, to generate the import tags for Javascripit. We will talk about this class in the next paragraph.
configuration.series An array of Item Data. If the user did not configure any Item Data at design time, this array will be empty.The records of each Item Data are available as array from series[n]. The first Item Data is series[0].;
configuration.height A shortcut to get the element height
configuration.width A shortcut to get the element width
configuration.isInteractiveViewer A boolean value that tells us if this report is being exported to be consumed inside an Interactive Viewer (which requires the use of requireJs for imports)
configuration.instanceData JSON representation of this configuration. All not JSON-serializable objects are not present in this object.
configuration.<property name> The value for the user defined properties

The expression, ${configuration.element.hashCode()}, is used to generate the unique element ID. This expression will be used in several places to refer the container.

The DIV does not define much: just the element ID and the size of the DIV.

Here is a sample output that will be generated by JasperReports:

<div id="element170560682" style="width: 555px; height: 590px;">
   ...
</div>
Element styling

If our custom visualization requires some styles to be defined, the developer may decide to add the style tag inside the template. You may decide to limit the style definition to this particular DIV by setting attributes to be specific to this id, with a syntax like:

<style>
   #element${configuration.element.hashCode()}
   svg text {
      font: 10px sans-serif;
   }
</style>

This works well when the report is exported in HTML, since the browser is able to properly associate the styles to the parts of your svg image. This, unfortunately, does not work when the image is exported in other formats, where the pure SVG data is taken in consideration, and no div is available. A possible solution would be set an unique class name to the SVG element itself when created, and set the style to it:

<style>
   .svg${configuration.element.hashCode()}
   text {
      font: 10px sans-serif;
   }
</style>

In D3js, the relevant code would be something like:

var svg = d3.select("#element" + instanceData.key).append("svg").attr("class", "svg" + instanceData.key)
....

Javascript Imports

Importing external javascript script is a little bit tricky. The temptation would be to just put in the template a simple script tag.

<script type="text/javascript" src="d3.v3.min.js"></script>

Unfortunately this approach prevents the report to correctly work in different environments at the same time, in example when viewed inside an Interactive Viewer (which may use requireJS) and then exported to PDF.

To solve the problem, the Custom Visualization component provides an utility class called ScriptHandler. This class has a convenient method called require to dynamically produce the javascript import suitable for the specific exporting environment.

${configuration.scripts.require( { "path": "scripts/d3.v3.min.js", "name": "", "export": "d3", "key": "d3"} )}
Attribute Description Used in Interactive Viewer Used in off-line/server side rendering
path This is the path of script to be used when this script is loaded from the classpath from JasperReports to be served trough a Resource servlet in an interactive environment Yes No
name The name (if any) of an existing pre-configured requireJS module that should be used in an Interactive Environment (if not empty it will override the path attribute) Yes No
export The name of a variable to export when this javascript is dynamically loaded trough requireJS a no module has been pre configured. It simulates the property shim of a requireJS configuration. Yes No
key The last part of the JasperReports property with prefix "com.jaspersoft.jasperreports.components.customvisualization.script.path.". These properties are used to tell to JasperReports where a specific script is located when the custom visualization component uses PhantomJS to execute the HTML snippet generated by the component itself. No Yes
Javascript and JasperReports Server

When a report is displayed in an Interactive Viewer, like the server does, the report is loaded asynchronously and the script tags have no effect. Also, to prevent conflicts, the server allows loading of scripts only in form of requireJS modules.

If a script is not pre-configured as module in the server configuration, it is still possible to load it dynamically. The Custom Visualization component javascript module is able to load all the required scripts you have set to be imported in your template one by one, by using the path as module key and by exporting the requested variable. This works only if the requireJS configuration in JasperReports Server does not enforce define (this setting is located in one of the first lines of the file scripts/require.config.js in JasperReports Server).

By setting the enforceDefine to false, you may experience problems when running reports in Visualize.js.

When possible, you should always provide a requireJS module for your javascript libraries, and pre-configure them on the server. This will also improve security a little bit, by defining which libraries the user can actually use, and will also simplify your template configuration, since variable exports and paths are automatically resolved by the server.

The Main Javascript function

The role of this function is to actually append to the container DIV an SVG tag representing the chart.

In most D3js samples, there is not a function that run the code to make a chart. The code is just executed by being inside a script tag. This would not work when the page is loaded asynchronously in an interactive viewer. We need to defined an entry point, or more simply, a function that can be invoked to perform the rendering of the visualization. This function takes as argument a JSON object called instanceData, very similar to the configuration object passed to the template. Our function should be able to use this object to fully configure the chart at run time.

The name of the function must be renderComponent<element ID>. In an interactive environment, this function will be invoked automatically as the page containing the Custom Visualization component is loaded. In case we are not in an interactive environment, we are in charge to invoke this function by our own. Here is the typical code to achieve this:

<script class='jasperreports' type='text/javascript'>
    function renderComponentelement${configuration.element.hashCode()}(instanceData) {
    //
        .... create the SVG ....
    } 
 
    #if (!${configuration.isInteractiveViewer})
        renderComponentelement${configuration.element.hashCode()}(${configuration.instanceData});
    #end
</script>

Please note how we use the isInteractiveViewer property to decide if we need to invoke our function or not. We also pass the instanceData object produced for us by the Custom Visualization component (${configuration.instanceData}).

Here is the complete template:
Expand source

The main function is at line 39. The instanceData object is used to configure dynamically some properties (the level of customization given to the final user is decided by the template developer). In this case (lines 45-57) we prepared this chart to be fairly customizable, but we actually set just width and height. Properties may be used to set other details like labels, number formats, etc...

At line 96 we create the svg tag and we append it to our div, locating it by using the element ID. In this case the element ID is not hard coded by the Velocity template, but is rather read from the dataInstance object. This will help to move this chart in its own javascript file (and eventually its requireJS module) without having dependencies from the configuration object of the template.

Chart interactivity

What makes the code listing for this template so long is the amount of code used to make the chart interactive: we want the chart to automatically start the animation and we also listen for events on the mouse move. Moreover, between a category item and the other, we calculate all the interpolated values to create a smooth animation of the balls moving from a point to another. Of course all this may not be required in many situations.

Currently the Custom Visualization component does not offer any facility for hyperlinks. Hyperlinks, as managed by JasperReports are not suitable to be embedded inside an SVG image. Anyway a javascript function could be used to listen to clicks on specific parts of the chart, which could be enriched with data objects useful to drive a specific behavior (similarly to how hyperlinks are handled in Visualize.js, with the difference that in that case we are in charge to compose the entire URL to invoke).

Using this template inside a Jrxml

Now that the template is ready, it is time to test it in a real jrxml. It is good practice to have tested this template before using it with JasperReports, this because debugging is a little more complicated. Anyway, if this is not possible, it is a good idea to start with an HTML preview so we can still open our HTML report inside a browser and introspect it with some developer tools (i.e. to catch console errors).

The configuration required to run the new report is similar to what has been explained in the chapter about setting up the examples. Summarizing, we need to put our javascripts (D3js in this case) inside the classpath, set the net.sf.jasperreports.customvisualization.path.d3 property to properly point to the file (in the form of file:///path/to/file.js) and finally put our template inside the classpath (i.e. by creating a new folder in a Jaspersoft Studio project and adding this folder to the build path).

We start by creating a new report and by dragging into the design a new Custom Visualization component.

In the Custom Visualization component properties we set the velocity.template property to point to our file (i.e. templates/animated_bubblechart.vm).

We can set here all the properties that the template developer decided to read to configure the chart. In our case we did not really define any special property to define things like max bubble size, colors, etc...

We proceed with configuring a new Item Data. This template uses a single Item Data containing records providing the following fields: category, key, x, y, z.

Field Type Description
category Mixes Any object (i.e. a String)
key String The unique identifier of a ball. This identifier will be repeated for each category
x Numeric The value of x axis
y Numeric The value of y axis
z Numeric The value used to calculate the size of the ball

To load this data from a dataset, we create a new Item Data by clicking Add (next to the list of Item Data). In the Item Data dialog we check "Use a dataset for the items" to generate an item for each record on the dataset.

In the Items tab we create a new Item to define the record (accordingly to the required fields described above:

Find a good dataset large and rich enough to populate this such of chart is difficult when using simple test datasets. To properly test the template we populate the report with an empty data source with 300 records and we set up the field expressions to dynamically create categories, balls and values. Here are the expressions used:

Property Expression
category
"Cat " + ($V{REPORT_COUNT}%20)
key
"Ball " + ((int)($V{REPORT_COUNT}/20))
x
(int)(Math.random()*100)
y
(int)(Math.random()*100)
z
(int)(Math.random()*100)

This was the last required setting, It is time to test the report with an empty data source of 200 records.

Feedback