Welcome!

Blog Feed Post

BIRT Charting – Scripting Overview

The BIRT Chart Engine currently supports building and running charts within the BIRT designer or externally. The engine is fully extensible using the Eclipse extension mechanisms and supports client side interactivity and server side event scripting. Fourteen main chart types, such as bar charts, line charts, and scatter charts are supported. Additionally most chart types support many subtypes such as stacked bar charts, 2D with depth, or 3D charts. When using the chart engine within BIRT the charts can be rendered as PNG, JPG, BMP, or SVG.

The chart engine also supports a sophisticated server side event model that allows the developer to customize the charts dynamically. This post is an overview of most of the chart scripting events available.

If you are interested in learning more about client side chart scripting, see these posts:
ClientSide Script:
Calling Client Side JavaScript from a BIRT Chart
More on Chart Interactivity

General BIRT Server Side Scripting
BIRT Chart event scripting is based on the standard BIRT report scripting model. A primer for report scripting is available here. When the BIRT report engine processes reports they are executed in two phases (Generation and Presentation). The generation phase connects to data sources collects and processes the values and creates the report items. The presentation phase renders the content to the particular output (HTML, paginated HTML, PDF, PPT, XLS, DOC, or PS). These phases can be executed in one or two processes, depending on how you call the engine.

If you use the Report Engine API (RE API), the RunAndRenderTask is one process. To run it in two processes you would use a RunTask and then a RenderTask. If you are using the BIRT Viewer, the /Run and /Preview mappings use one process and the /frameset mapping uses two processes. This concept is very important to understand when scripting as it affects the order in which the events are triggered. When using one process the event order for a data item would look something like:

1st instance
onPrepare
onCreate
onRender
2nd instance
onCreate
onRender.

The same data item with two processes would look like:

1st instance
onPrepare
onCreate
2nd instance
onCreate

onRender first instance
onRender second instance

Using two processes offers many advantages. First a binary file called the report document (.rptdocument) is created which can be rendered many times at a later date without re-executing all the queries. It is also required to support TOC bookmarks and paginated HTML.

Chart Server Side Scripting
When using charts in a BIRT report, the engine supports over thirty events. These events can be used to change the data, interactivity, or presentation of the chart. All of these events are fired in the presentation phase (at render time) of the report and can be written in Java or JavaScript.

It is possible to change chart properties at generation time, but these changes must be made in a report event that happens prior to the chart being created, like the beforeFactory event. This approach is described here and here.

It is vital to understand when chart events are fired, if you plan on making script changes to your chart. Take for instance the following chart.



This is a standard Bar chart with a marker line and curve fitting line. If two processes are used to generate the report that contains this chart, the following event firing order will be used.




This diagram shows most of the available events, but not all events are fired for every chart type. For example before and afterDrawMarker events are only fired for chart types that support makers and these events are usually fired in between the before and afterDrawDataPoint scripts. The beforeDrawSeriesTitle event only fires for Pie charts.

Notice the chart events do not start until after the beforeRender report level event.

Chart Script Context
Before getting into each of the events, a good understanding of the chart script context object will be helpful. All BIRT Chart events are passed a chart script context object. This object will be labeled icsc in the function definition. You can use the chart script context object to get the current instance of the chart model by calling:


icsc.getChartInstance();



You can also use it to get the reportContext that is associated with all other report scripting events. See the scripting primer referenced above for more details. This object can be used to get parameter values, resource values, locales, the current http request, global variables, page variables, etc. To access these functions with a JavaScript event use the following:


rpCtx = icsc.getExternalContext().getScriptable();


This is equivalent to the reportContext variable. So to get a report parameter value, use:


rpCtx.getParameterValue(“MyParameter”);


or to access the http request use (this only works in the Viewer unless you put the http request object in the report engines App Context):


rptCtx.getHttpServletRequest().getRequestURL();


If you are using a Java Event handler use:


IReportContext rpCtx = (IReportContext)icsc.getExternalContext().getObject();


Data Set Filled Events
The first set of chart events that fire are the data set filled events. These events are fired when data organizing and generation of runtime series is occurring for the chart. In These events you can change many properties of the chart model, such as series expressions, grouping, labels, formatting, and series visibility. These events are fired once for the category series and once for each runtime series that is generated based on how the chart is designed. Generally you will get one runtime series per design time series and one additional one for the category series. So if you define a bar chart with product code on the x-axis and quantity ordered on the y-axis, this would produce two runtime series. Each series would contain an array of values. For example ProdA, ProdB, and ProdC may be the category series and 55, 98, and 32 may be the value series. If the chart also contained a line series then this would constitute on additional runtime series containing its values. If you use the optional grouping feature this could generate many runtime series, depending on the number of groups the optional grouping expression produced. In the above example, if the following expression was entered for the optional grouping.


if( row["QUANTITYORDERED"] > 50 ){
"high";
}else{
"low";
}


This would produce a total of three runtime series. 1 (ProdA,ProdB,ProdC), 2 ( 55, 98, null) and 3 (null ,null, 32).

Note that all series have to have the same number of points. In the afterDataSetFilled script these values can be check and or modified.

beforeGeneration Event
The beforeGeneration event is executed before chart building begins and after all series data points have been created. You can make changes to the chart model here and can even alter the datasets that the chart uses. In the data set filled events, many runtime series may have been generated. Every series in a chart will have a unique series identifier. You can specify this on the third tab of the chart wizard. If you use optional grouping and many runtime series are generated for each design time series, a new series identifier will be created for each runtime series. To interrogate these series use code similar to the following (Chart With Axis Example):



var xAxis = chart.getAxes().get(0);
var yAxis = xAxis.getAssociatedAxes().get(0);
var xSeriesDef = xAxis.getSeriesDefinitions().get(0);
var ySeriesDef = yAxis.getSeriesDefinitions().get(0);

var ySeries = ySeriesDef.getRunTimeSeries();
var numberofrunseries = ySeries.size();
//get the first runtime series identifier
ySeries.get(0).getSeriesIdentifier();



Computation Events
The before and afterComputations events are called next. These events are called just before and right after all the calculations for the chart’s rendering properties are processed. These computations include all bounds objects for the chart, the series rendering hints, and data point hints, which store the plot location and values for each series. Using these scripts these values can be modified before the chart is actually rendered.

afterGeneration/beforeRender Event
These events are fired after the chart is built but before it renders each part of the chart. When the chart engine is building it stores all information in the GeneratedChartState object. This object is passed to these methods and can be used to make changes before the chart is rendered. You can use the following code to get the plotComputations object that was populated in the computations event:


compu =gcs.getComputations();


You can even change how the chart engine processes interactivity triggers. For example assume you have a class that implements the IActionRenderer interface.



package my.action.handler;
import java.io.Serializable;
import org.eclipse.birt.chart.computation.DataPointHints;
import org.eclipse.birt.chart.event.StructureSource;
import org.eclipse.birt.chart.event.StructureType;
import org.eclipse.birt.chart.model.attribute.ActionType;
import org.eclipse.birt.chart.model.attribute.TooltipValue;
import org.eclipse.birt.chart.model.data.Action;
import org.eclipse.birt.chart.render.ActionRendererAdapter;
public class MyActionHandler extends ActionRendererAdapter implements Serializable{

private static final long serialVersionUID = 1169308658661902989L;

public void processAction( Action action, StructureSource source )
{
if ( ActionType.SHOW_TOOLTIP_LITERAL.equals( action.getType( ) ) )
{
TooltipValue tv = (TooltipValue) action.getValue( );
if ( StructureType.SERIES_DATA_POINT.equals( source.getType( ) ) )
{
final DataPointHints dph = (DataPointHints) source.getSource( );
String MyToolTip = "My Value is " + dph.getDisplayValue() + "--" +dph.getBaseDisplayValue();
tv.setText( MyToolTip );
}
}
}
}


You could instantiate an instance of this class in the beforeFactory event of the report:


importPackage(Packages.my.action.handler);
MyActionRenderer = new MyActionHandler();
reportContext.setPersistentGlobalVariable("myactionrenderer", MyActionRenderer);


And then set it in the beforeRender event like:


mar = icsc.getExternalContext().getScriptable().getPersistentGlobalVariable("myactionrenderer");
gcs.getRunTimeContext().setActionRenderer(mar);



DrawBlock Events
Charts in BIRT are made up of rendering areas called blocks. Each block will have properties such as rendering bounds, anchor points, outline properties, background properties, and child blocks. The whole chart is the main chart block and within this block is the title block, the plot block and the legend block.



The before and afterDraw block events are called multiple times, once for each chart part that constitutes a block.

The before and afterDraw block events are first called for the whole chart, the title block, plot block, and finally for the legend block. Nested within the plot block and legend block other events will fire as illustrated in the event diagram. The plot block events are fired once for each runtime series as well. So if you have 3 runtime series, the before and afterDrawBlock event will be called three times for the plot block.

To know which block the event is firing for you can use a script similar to:


if ( block.isLegend( ) )
{
block.getOutline( ).setVisible( true );
block.getOutline( ).getColor( ).set( 21,244,231 );
block.setBackground( ColorDefinitionImpl.YELLOW( ) );
block.setAnchor( Anchor.NORTH_LITERAL );
}
else if ( block.isPlot( ) )
{
}
else if ( block.isTitle( ) )
{
}
else if ( block.isCustom( ) ) //Main Block
{
}


You can use these events to modify the calculated bounds of any block, but this may result in unattractive charts. To change the location of the legend the following script could be used.


if (block.isLegend())
{
var wid = block.getBounds().getWidth();
var lft = block.getBounds().getLeft();

block.getBounds().setLeft(lft-20.00);
block.getBounds().setWidth(wid+20.00);
}


Marker Range and Line Events
The before and afterDrawMarker range and line events are called before any of the series are drawn. These events are only called for charts with axis that actually contain marker ranges (or lines). Using these events the marker ranges (or lines) can be modified. By default the location of the markers are set in the chart builder, but you can set the location using an index like:


markerLine.setValue(NumberDataElementImpl.create(intcountformaker));


The index is associated with the runtime series data point index and not the data point value. If the index is set to 0 the marker will be set to the first data point. You can use this in combination with other events like the afterDataSetFilled event to make the marker dynamic.

Draw Series Events
For each runtime series that is generated, the before and afterDraw series events will be fired. You can use the series identifier to determine which series is being processed. You can also check the specific type that of series that is being rendered.


if( series.getClass() == LineSeriesImpl ){
series.getLineAttributes().setThickness(5);
}
if( series.getClass() == BarSeriesImpl ){
if( series.getSeriesIdentifier() == “Series 1”){
}
}


Also note that this event is called for the category series.

Within these events you can modify the series before it is rendered. For example if you want to change a drill through hyperlink to point to a different report you could use the following script.


triglen = series.getTriggers().size();
if( triglen >0 ){
mytarget = series.getTriggers().get(0).getAction().getValue().getURLValues( ).get(0).getBaseUrl();
newtarget = mytarget.replace("reporttarget1.rptdesign", "reporttarget2.rptdesign");
series.getTriggers().get(0).getAction().getValue().getURLValues( ).get(0).setBaseUrl(newtarget);

}


You can also get all the data points for the series by using script like the following:


if( series.getClass() == BarSeriesImpl ){
var pts = isr.getSeriesRenderingHints().getDataPoints();
for( i=0; i< pts.length; i++ ){
var mydp = pts[i];
if( mydp.getOrthogonalValue() > 35 ){
//do something
}
}
}



DataPoint and Data Point Label Events
For each runtime series that is not a category series, the before and afterDrawDataPoint and before and afterDrawDataPointLabel events are fired. These occur between the before and afterDrawSeries events. These events can be used to modify how the data point and label are rendered.

The fill object passed to the before and afterDrawDataPoint events will be an instance of one of the following classes depending on how you have setup the palette.

GradientImpl
ColorDefinitionImpl
ImageImpl (EmbeddedImageImpl and PatternImageImpl extend ImageImpl)



It is important to know which type you are dealing with if you plan on making changes to the fill object. You can check the fill type with script similar to this:



if( fill.getClass().isAssignableFrom(GradientImpl)){
fill.setStartColor(ColorDefinitionImpl.create( 0, 0, 255 ));
fill.setEndColor(ColorDefinitionImpl.create(255,255,225 ));
}



You can set the fill object based on the data point value by using a script like (Assumes Color Fill):


val = dph.getOrthogonalValue();
if (val == 10){
fill.set(255, 0, 0);
}


Using the data point label scripts you can modify the label format and value.


var newlabel = label.getCaption().getValue().replace(",", "'");
label.getCaption().setValue(newlabel);

if( dph.getBaseDisplayValue() == 50 ){
label.setVisible(true);

label.getCaption().getFont().setBold(true);
label.getCaption().getFont().setSize(14);
label.getCaption().getFont().setName("Arial");
}else{
label.setVisible(false);
}



Curve Fitting Events
If the series contains a visible curve fitting line, the before and afterDrawFittingCurve events will be trigged after all the data point and data point label scripts have fired. You can use this event to modify the line attributes and label properties for the fitting curve.

Axis Title and Axis Label Events
After all series have been rendered the before and afterDrawAxisLabel events are fired for every label that appears on the axis. The before and afterDrawAxisTitle events are then fired. This process starts with the category axis and then proceeds for each value axis. You can use these events to modify the labels that are rendered. The axis type can be checked to determine which axis fired the event.


//LINEAR_LITERAL
//LOGARITHMIC_LITERAL
//TEXT_LITERAL
//DATE_TIME_LITERAL
if( context.getExternalContext().getScriptable().getParameterValue("NewParameter") == "date"){
if (axis.getType() == AxisType.TEXT_LITERAL)
{
value = label.getCaption().getValue();
var dtf = new SimpleDateFormat("MM/dd/yy");
var dt = new Date(value);
var fn = dtf.format(dt);
label.getCaption().setValue(fn);
}
}
You can also check the axis title to determine which axis fired the event.
if( axis.getTitle( ).getCaption( ).getValue( ) ==
"myYaxisTitle" )
{
label.getCaption( ).setColor(
ColorDefinitionImpl.BLUE( ) );
}
if ( axis.getType( ) == AxisType.DATE_TIME_LITERAL )
{
label.getCaption( ).setColor(
ColorDefinitionImpl.RED( ) );
}



Legend Item Events
The final set of events that are fired that can affect the chart are the before and afterDrawLegendItem events. These events can be used to customize the look of the chart legend.

This event is passed the LegendEntryRenderingHints (lerh) object and the bounds for the specific entry. The bounds object is for legend item graphic and not the text. Using these objects you can customize the legend. You can hide an entry using the bounds object.


if( lerh.getLabel().getCaption().getValue().compareToIgnoreCase("series3") == 0 ){
bounds.setHeight(0);
bounds.setWidth(0);
lerh.getLabel().setVisible(false);
}


You can modify the fill object using the lerh.getFill() function and modify the label using the lerh.getLabel() function.


importPackage( Packages.org.eclipse.birt.chart.model.attribute.impl );
label = lerh.getLabel();
labelString = label.getCaption().getValue();
if( labelString == "series2" ){
label.getCaption( ).getColor( ).set( 32, 168, 255 );
label.getCaption().getFont().setItalic(true);
label.getCaption().getFont().setRotation(5);
label.getCaption().getFont().setStrikethrough(true);
label.getCaption().getFont().setSize(12);
label.getCaption().getFont().setName("Arial");
label.getOutline().setVisible(true);
label.getOutline().setThickness(3);

var mycolor = ColorDefinitionImpl.BLUE();
r = mycolor.getRed();
g = mycolor.getGreen();
b = mycolor.getBlue();
lerh.getFill().set(r, g, b);

}



You can check the fill object type before modifying it as described in the DataPoint and Data Point Label Events described earlier.

Chart Scripting Examples
Here are some example reports/posts that illustrate using the chart scripting model.
BIRT Resizing Charts
BIRT - Reversing a Chart Scale
Resize Pie Chart based on optional grouping count
Add spacing to stacked bar optional grouped chart
Turning a Chart series off in script
Set Chart Axis Label Interval with Script
Using Chart Script to Define a specific series color
Chart Script to Alter Palette - Gradient Pie
Adjust BAR Location in Bar Chart

Read the original blog entry...

More Stories By Jason Weathersby

Jason Weathersby is a member of the extended BIRT development team at Actuate Corporation and has backgrounds in both computer science and technical writing. He has many years experience in technical consulting, training, writing, and publishing about reporting, business intelligence tools, and database technologies.

Latest Stories
DX World EXPO, LLC, a Lighthouse Point, Florida-based startup trade show producer and the creator of "DXWorldEXPO® - Digital Transformation Conference & Expo" has announced its executive management team. The team is headed by Levent Selamoglu, who has been named CEO. "Now is the time for a truly global DX event, to bring together the leading minds from the technology world in a conversation about Digital Transformation," he said in making the announcement.
"Space Monkey by Vivent Smart Home is a product that is a distributed cloud-based edge storage network. Vivent Smart Home, our parent company, is a smart home provider that places a lot of hard drives across homes in North America," explained JT Olds, Director of Engineering, and Brandon Crowfeather, Product Manager, at Vivint Smart Home, in this SYS-CON.tv interview at @ThingsExpo, held Oct 31 – Nov 2, 2017, at the Santa Clara Convention Center in Santa Clara, CA.
SYS-CON Events announced today that Conference Guru has been named “Media Sponsor” of the 22nd International Cloud Expo, which will take place on June 5-7, 2018, at the Javits Center in New York, NY. A valuable conference experience generates new contacts, sales leads, potential strategic partners and potential investors; helps gather competitive intelligence and even provides inspiration for new products and services. Conference Guru works with conference organizers to pass great deals to gre...
DevOps is under attack because developers don’t want to mess with infrastructure. They will happily own their code into production, but want to use platforms instead of raw automation. That’s changing the landscape that we understand as DevOps with both architecture concepts (CloudNative) and process redefinition (SRE). Rob Hirschfeld’s recent work in Kubernetes operations has led to the conclusion that containers and related platforms have changed the way we should be thinking about DevOps and...
The Internet of Things will challenge the status quo of how IT and development organizations operate. Or will it? Certainly the fog layer of IoT requires special insights about data ontology, security and transactional integrity. But the developmental challenges are the same: People, Process and Platform. In his session at @ThingsExpo, Craig Sproule, CEO of Metavine, demonstrated how to move beyond today's coding paradigm and shared the must-have mindsets for removing complexity from the develop...
In his Opening Keynote at 21st Cloud Expo, John Considine, General Manager of IBM Cloud Infrastructure, led attendees through the exciting evolution of the cloud. He looked at this major disruption from the perspective of technology, business models, and what this means for enterprises of all sizes. John Considine is General Manager of Cloud Infrastructure Services at IBM. In that role he is responsible for leading IBM’s public cloud infrastructure including strategy, development, and offering m...
The next XaaS is CICDaaS. Why? Because CICD saves developers a huge amount of time. CD is an especially great option for projects that require multiple and frequent contributions to be integrated. But… securing CICD best practices is an emerging, essential, yet little understood practice for DevOps teams and their Cloud Service Providers. The only way to get CICD to work in a highly secure environment takes collaboration, patience and persistence. Building CICD in the cloud requires rigorous ar...
Companies are harnessing data in ways we once associated with science fiction. Analysts have access to a plethora of visualization and reporting tools, but considering the vast amount of data businesses collect and limitations of CPUs, end users are forced to design their structures and systems with limitations. Until now. As the cloud toolkit to analyze data has evolved, GPUs have stepped in to massively parallel SQL, visualization and machine learning.
"Evatronix provides design services to companies that need to integrate the IoT technology in their products but they don't necessarily have the expertise, knowledge and design team to do so," explained Adam Morawiec, VP of Business Development at Evatronix, in this SYS-CON.tv interview at @ThingsExpo, held Oct 31 – Nov 2, 2017, at the Santa Clara Convention Center in Santa Clara, CA.
To get the most out of their data, successful companies are not focusing on queries and data lakes, they are actively integrating analytics into their operations with a data-first application development approach. Real-time adjustments to improve revenues, reduce costs, or mitigate risk rely on applications that minimize latency on a variety of data sources. In his session at @BigDataExpo, Jack Norris, Senior Vice President, Data and Applications at MapR Technologies, reviewed best practices to ...
Widespread fragmentation is stalling the growth of the IIoT and making it difficult for partners to work together. The number of software platforms, apps, hardware and connectivity standards is creating paralysis among businesses that are afraid of being locked into a solution. EdgeX Foundry is unifying the community around a common IoT edge framework and an ecosystem of interoperable components.
"ZeroStack is a startup in Silicon Valley. We're solving a very interesting problem around bringing public cloud convenience with private cloud control for enterprises and mid-size companies," explained Kamesh Pemmaraju, VP of Product Management at ZeroStack, in this SYS-CON.tv interview at 21st Cloud Expo, held Oct 31 – Nov 2, 2017, at the Santa Clara Convention Center in Santa Clara, CA.
Large industrial manufacturing organizations are adopting the agile principles of cloud software companies. The industrial manufacturing development process has not scaled over time. Now that design CAD teams are geographically distributed, centralizing their work is key. With large multi-gigabyte projects, outdated tools have stifled industrial team agility, time-to-market milestones, and impacted P&L stakeholders.
"Akvelon is a software development company and we also provide consultancy services to folks who are looking to scale or accelerate their engineering roadmaps," explained Jeremiah Mothersell, Marketing Manager at Akvelon, in this SYS-CON.tv interview at 21st Cloud Expo, held Oct 31 – Nov 2, 2017, at the Santa Clara Convention Center in Santa Clara, CA.
Enterprises are adopting Kubernetes to accelerate the development and the delivery of cloud-native applications. However, sharing a Kubernetes cluster between members of the same team can be challenging. And, sharing clusters across multiple teams is even harder. Kubernetes offers several constructs to help implement segmentation and isolation. However, these primitives can be complex to understand and apply. As a result, it’s becoming common for enterprises to end up with several clusters. Thi...