Showing posts with label Charts. Show all posts
Showing posts with label Charts. Show all posts

Thursday, August 12, 2010

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

Thursday, May 06, 2010

More on Chart Interactivity

In an earlier post we examined chart interactivity with a focus on the Invoke Script Action, which allowed client side scripts to be called. This post is available here.

At the end of the post we mentioned predefined variables that are passed to the script that is invoked. These variables are:


evt – The Event Object.
categoryData – The Chart Category Value.
valueData – The Chart Orthogonal Value.
valueSeriesName – The specific Series Name.
legendItemText – The specific legend entry Text.
legendItemValue – The value of the specific legend entry.
axisLabel – The value of the specific axis label selected.


What these predefined variables contain depends on the chart type, how the chart is configured and what event is being fired. For example if you define a Bar chart with legend interactivity and do not show the legend value, all the variables will be null with the exception of the evt Object and the legendItemText variable. Generally these values will be populated like (evt object is always populated):

Series Interactivity Invoke Script – categoryData, valueData, valueSeriesName populated, the rest are null.
Legend Interactivity Invoke Script – legendItemText and legendItemValue populated, the rest are null.
Axis Interactivity Invoke Script – axisLabel populated, the rest are null.
All other Interactivity Invoke Script – All are null.

To see the values you can call an invoke script function to alert them. For example, select a specific series on a chart within the third tab of the chart wizard and select interactivity. For the event enter Mouse Click. For the Action choose invoke script and enter the following script.





ShowEventData(evt, categoryData, valueData, valueSeriesName, legendItemText, legendItemValue, axisLabel);





Next add a text element to the report that has the following value.

<script language="JavaScript">

function ShowEventData(evt, categoryData, valueData, valueSeriesName, legendItemText, legendItemValue, axisLabel) {

alert( "categoryData=" + categoryData + 
"\nvalueData=" + valueData + 
"\nvalueSeriesName=" + valueSeriesName + 
"\nlegendItemText=" + legendItemText + 
"\nlegendItemValue=" + legendItemValue + 
"\naxisLabel=" + axisLabel);
 
}
</script>

Make sure you set the type to HTML for the text element. The output when clicking on the series should look similar to:



An example report that contains this event on all interactivity elements is available at Birt-Exchange.

An example that uses the predefined variables with client side script to toggle the visibly of a chart series is also available at Birt-Exchange.

Thursday, April 22, 2010

Multi-View Report Items

BIRT currently supports the concept of a multi-view report item. What this means is that two report items share the same data bindings and physical space, but only one is displayed. In the current version of BIRT only tables and crosstabs support this multi-view feature and the second view must be a chart. Generally the view that is displayed is chosen at design time, but this can be altered using some script and a report parameter.

To create a multi-view report item, first create a table or crosstab. Next right click on the report item and select Create Chart View.



This will launch t he Chart Builder. Construct the chart as usual. After creating the chart a second tab will appear at the bottom of the report item.



The tab selected currently determines which view will be displayed. The selected view can also be changed with script and a report parameter. First select the table tab and name the table.



In this case I have named the table “mytable”. Next add a report parameter to use in the script. The parameter should allow two choices, show as chart or show as table. In this example I have named the report parameter ShowAs and the parameter has chartview or tableview as possible values.



Finally enter the following script in the beforeFactory event.



var th = reportContext.getDesignHandle().findElement("mytable");
var mvh = th.getProperty("multiViews");

if( params["ShowAs"].value == "chartview" ){
mvh.setProperty("index", "0" );
}else{
mvh.setProperty("index", "-1" );
}




The first line retrieves a handle to the table and the second retrieves a handle to the multiviews property for the table. The parameter is then used to set the index for the multiview item.

The output will look similar to:



And




This example is available at Birt-Exchange.

Friday, December 11, 2009

Calling Client Side JavaScript from a BIRT Chart

A couple of months ago I detailed a new feature for BIRT charts that allows multiple hyperlinks to be attached to one the supported events. That post is available here.

In this post I will discuss using a BIRT Text element that contains script which executes within the client browser and contains functions that are called from rendered charts.

General Information
BIRT currently supports interactivity on many chart components like chart series, title, axis, and the legend. The components that support interactivity will depend on the type of chart being used. The events are client based events like mouse click, mouse over, key down, etc. Multiple events can be hooked and each is associated with an action. Actions define the behavior that should occur when a specific event happens. The actions available depend on what component the action is defined for and what chart output type is being used. Currently BIRT supports the following Actions.

Hyperlink – Supports multiple hyperlinks, drill through, and linking to bookmarks.
Show Toolip – Supports displaying tooltips. Data available for use in the tooltip will depend on the component.
Highlight – Highlights the selected component. Most often used to highlight specific series or data point.
Toggle Visibility – Toggles the visibility of the selected component. Most often used to change the visibility of a series or data point.
Toggle DataPoint Visibility – Toggles the data point label visibility.
Invoke Script – Invokes client side script.

Additionally if you are using the Chart engine within an SWT, SVG, or Swing based application the engine supports adding a callback action that your code can use to interpret chart events.



Highlight, Toggle Visibility, and Toggle DataPoint Visibility actions are only available when using an SVG output setting.



If you use SVG, and you wish to test the report in the designer remember to set the Enable SVG chart in the Preview preferences for the Report Design Preference entry.



More information on Chart Interactivity is available here. Also see the Chart FAQ for more details.

InvokeScript Example
The follow section details an example of using the invokeScript action on multiple Chart events.

Assume that you have a Chart that displays a set of customers with a series value for each equal to the customer's credit limit.



Suppose you want to display the customer details right below the chart when the mouse is moved over a specific data point. This can be done using the invokeScript action on the mouse over event for the chart series. To do this first create a table below the chart that is bound to the same dataset that the chart is using. You can then nest a table that calls another data set to retrieve customer information. In the attached example this will be the ChartData and CustomerInformation datasets respectively.



By default this will generate a inner table that contains customer information for every customer represented in the chart. Obviously you do not want to display all of these at once, so create a new style in the style editor with only one property overridden – Text Block:Display set to no Display. Apply this new style to both the inner and outer tables.



If you run the report after completing this step all the tables will be generated in the output but none will be displayed. This is different than using the visibility property which will not put the tables in the output. Enter the following bookmarks for the outer and inner tables.

Outer table bookmark expression: "myoutertable";
Inner table bookmark expression: "mytable"+row["CUSTOMERNUMBER"]

This will assign the outer table id to myoutertable and a unique id for each inner table starting with mytable and the customer number for the given inner table appended to it. We can now use these with some client script to turn them on or off.

Add a Text element below the two tables with the following value.


<script>
function clearSel(){
var ot=document.getElementById("myoutertable");
ot.style.display="none";
var intbls=ot.getElementsByTagName("TABLE");
for( jj=0; jj<intbls.length; jj++ ){
intbls[jj].style.display = "none";
}
}
function DisplayCustomer(cat) {
clearSel();
//alert(cat);
document.getElementById("myoutertable").style.display="block"
document.getElementById("mytable"+cat).style.display="block"

}
</script>



Make sure to set the Text type to HTML.

The clearSel function first finds the outer table and then locates every nested table and sets the display style for each to none. The outer table’s display style is also set to none. This will effectively hide both tables.

The DisplayCustomer function first clears all current displayed tables. Next it uses a category passed in from the Chart to find the specific customer inner table and sets its display style to block. It also sets the outer table to be visible.

To link these two functions to the Chart use the Interactivity button on the Value (Y) Series as shown in the picture below. Note that we use a mouse over event to call the DisplayCustomer function and a mouse click function to clear the tables.



This produces the following output.



As the mouse is moved over the different tubes the table below the chart will update. To clear the tables click on one of the tubes. You will notice that we passed categoryData to the DisplayCustomer JavaScript function. This is a predefined variable available to the invokeScript function that contains the category value for a specific datapoint. The example report lists the predefined variables and in what action they are available using label elements at the top of the report. These labels are hidden at run time.

The example is available at BIRT Exchange.

Tuesday, October 20, 2009

Multiple Hyperlinks on BIRT Charts

BIRT 2.5.1 was released a couple of weeks ago and with its release BIRT Charts now support multiple hyperlinks. To use this feature select any of the interactivity locations within the chart builder. Next select the add button under the hyperlinks list box.



This will launch the hyperlink editor.



Enter the name you wish to appear in the context menu. Next select the edit base URL button, which will display the Hyperlink Options editor.



Here you have the option to link to a URI, a bookmark within the same report or to a bookmark in another report. Values from the current chart can be passed through to the target. For example the value of a slice could be passed to a google query. To do this select the URI radial and enter an expression similar to the one shown in the image below.



When running the report containing the chart, the multiple hyperlinks menu will popup on whatever event you defined in the editor. In the above example this was defined on a mouse click event. So clicking on a slice of the pie will cause the multiple hyperlinks context menu to appear.



This sample report is available at Birt-Exchange.

Wednesday, August 26, 2009

Using Script to Modify a BIRT Chart

When designing BIRT reports that contain charts, the developer has many ways a chart can be customized. One way is to implement a script event handler either in JavaScript or Java. The chart engine currently supports over thirty hooks for implementing an event handler. These events are fired during the generation and rendering of the chart. Many items can be customized. For example, the data for a chart can be changed in the afterDatasetFilled script, or the entire chart model can be changed in the beforeGeneration script.


In addition to changing the chart using the chart scripting hooks, the chart model can also be changed using the primary BIRT report script hooks. When making changes to the report, most developers will use the beforeFactory event script. This event is fired prior to running the report and offers a location to make simple or complex changes to the report. This includes modifying any charts that exist within the report. Simple chart properties can be changed using the Chart Simple API. An example of this approach is described here.

For more complex changes, the Chart API can be called within the script. As an example, changing a specific chart series type is possible. BIRT 2.5 provides study charts which allow multiple Y-axes to be stacked, each with a different series type. Turning this feature on or off can also be done using script. These two changes can be done with the following script:

First import the API packages that will be needed.

importPackage(Packages.org.eclipse.birt.chart.model.data.impl);
importPackage(Packages.org.eclipse.birt.chart.model.type.impl);
importPackage(Packages.org.eclipse.birt.chart.model.attribute.impl);


Retrieve the report design handle and find the chart. Note that the chart needs to be named. This can be done in the general properties for the chart.

//Chart must be named
cht = reportContext.getDesignHandle().findElement("MyChart");
//get the chart model;
mychart = cht.getReportItem().getProperty( "chart.instance" );



Next retrieve the axes. In this example the chart has two Y-Axes. The second one contains the series to be modified.

xAxis =mychart.getAxes().get(0);
yAxis2 = xAxis.getAssociatedAxes().get(1);

Clear all series definitions defined for the second Y-axis.

yAxis2.getSeriesDefinitions().clear();

The example report contains a combo parameter that allows the user to select the series type for the second Y-Axis. The parameter is checked and an appropriate series is created. Note that if an area series is created, the background is set to translucent.

if( params["SeriesType"].value == "Line"){
var ns = LineSeriesImpl.create();
}else if( params["SeriesType"].value == "Bar"){
var ns = BarSeriesImpl.create();
}else if( params["SeriesType"].value == "Area"){
var ns = AreaSeriesImpl.create();
ns.setTranslucent(true);
ns.getLineAttributes().setColor(ColorDefinitionImpl.TRANSPARENT())


}

Create a new series definition and set the appropriate query and series type.

var sdNew = SeriesDefinitionImpl.create();
sdNew.getSeriesPalette( ).shift( 0 );
ns.getLabel().setVisible(true);
var qry = QueryImpl.create("row[\"QUANTITYORDERED\"]" );
ns.getDataDefinition().add(qry)
sdNew.getSeries().add( ns );

Assign the new series definition to the second Y-Axis.

yAxis2.getSeriesDefinitions().add( sdNew );

The example report also has a Boolean parameter that determines if the chart should be displayed as a study chart.


if( params["StudyChart"].value == true ){
mychart.setStudyLayout(true);
}else{
mychart.setStudyLayout(false);
}

Some of the output options for this example report are displayed below.


This report is available at Birt-Exchange.

Thursday, December 11, 2008

BIRT - Reversing a Chart Scale.

The Chart Engine that is delivered as part of the BIRT project offers a lot of functionality. Not only can you extend most of its features, the chart engine also offers hooks to do scripting (either in Java or JavaScript). Scripting can be very useful for modifying the chart based on parameters or data. As an example assume you have a chart that is displayed as follows:



Assume these numbers represent percent of sales returns and the lower the bar the better. In some cases larger bars graphically represent better values, so instead of displaying the chart as above the user may wish to see the chart as follows.



One way to achieve this effect is to modify the values and labels in chart script. To modify or add scripts to a chart select the chart and then the script tab within the designer. Bear in mind that although this example illustrates building the event handler in JavaScript it can also be done with Java.

The first thing to setup is the scale for the chart, which we want to tie to a report parameter. So you can enter a script like:


//Global scale
gblscale = 100.00;


//this is the first event called for charting
//So set scale here
function beforeDataSetFilled( series, idsp, icsc )
{
gblscale = icsc.getExternalContext().getScriptable().getParameterValue("Scale");
}

We set the scale for the chart to the parameter value in the beforeDataSetFilled event because this is the first event called for chart scripts. This variable will be used in other scripts for calculating labels.

The next script we need to override is the afterDataSetFilled event handler. This script can be used to modify the values used in a chart.

function afterDataSetFilled(series, dataSet, icsc)
{
if( series.getSeriesIdentifier() == "series1"){
var list = dataSet.getValues();
for ( i=0; i < list.length; i=i+1)
{
list[i] = gblscale - list[i];
}
}
}



This script first checks the series identifier as this call is made for every series, but we only want to change the bar series. The series identifier should be entered in the chart wizard. This can be done in the third tab of the chart wizard.



Once we know that we are working with the correct series, we can modify the values by calling the dataSet.getValues. In the script we are just subtracting the values from the global scale and resetting the value. So if the global scale is 100, a 5 value becomes 95.

We can then set the scale for the entire chart in the beforeGeneration event as follows.


function beforeGeneration(chart, icsc)
{
importPackage( Packages.org.eclipse.birt.chart.model.data.impl );
xAxis = chart.getBaseAxes()[0];
yAxis = chart.getOrthogonalAxes( xAxis, true)[0]
yscale = yAxis.getScale();
yscale.setStep (10);
yscale.setMin( NumberDataElementImpl.create(0) )
yscale.setMax( NumberDataElementImpl.create(gblscale) )
yAxis.setScale(yscale);

}

By default this script sets the Y axis to range from 0 to whatever the parameter value is set to. We now can reverse the labels by adding a beforeDrawAxisLabel script.

function beforeDrawAxisLabel(axis, label, icsc)
{
importPackage(Packages.org.eclipse.birt.chart.model.attribute);
if (axis.getType() == AxisType.LINEAR_LITERAL){
var val = parseFloat(label.getCaption().getValue());
var newval = gblscale - val;
label.getCaption().setValue(newval);
}
}

This script only changes the labels. Internally the chart is still scaled from 0 to the parameter value. So if the scale is 100 and the original value is 5, its new value is 95 and the bar will be drawn from 100 to 5. Internally this is still 0 – 95, but we reversed the labels. The last script we may want to add can change the data point labels. This script is called beforeDrawDataPointLabel and is called whenever the chart renders labels on the bars.

function beforeDrawDataPointLabel(dph, label, icsc)
{
var val = parseFloat(label.getCaption().getValue());
var newval = gblscale - val;
newval = newval.toFixed(2);
label.getCaption().setValue(newval);
}

In this script we just reverse the value label. This example can be downloaded at
BIRT Exchange.

Monday, October 20, 2008

Dynamically adding a series to a BIRT Chart

Often it is a requirement to customize report output for specific users. Generally this is accomplished with parameters and some form of conditional logic within the report itself. This approach can be used with BIRT as well. BIRT supports JavaScript expressions and has an event model that allows report customization with handlers written in either Java or JavaScript. For more information on the event model see the scripting primer.

To illustrate modifying a BIRT chart at runtime, assume we have a bar chart that we wish to add additional line series to, based on some report parameter. This can be accomplished by creating an event handler for the beforeFactory event and modifying the report prior to it running.


cht = reportContext.getDesignHandle().findElement(“MyChart”);

This line of code requires that the chart be named MyChart in the General tab of the Properties View. This line of code gets a handle to the Chart report item. Once the chart report item is located we then can retrieve the actual chart model. The chart model is de-serialized from the xml within the report design and stored in a property called chart.instance. So to get the model, you can use code like the following:

mychart = cht.getReportItem().getProperty(“chart.instance”);

Now that we have the actual chart model, we can use standard chart engine API calls to modify it prior to the report running. Remember to import the CE API packages prior to using them. See the final example for more details.

Since we have a bar chart, the chart model will be an instance of the ChartWithAxesImpl class. This class has methods for getting the chart axes and adding series definitions. If we define a static report parameter that allows multi-selection, with values that correspond to the row values we want to map with line series, the following code will add the appropriate series.



//Get the x axis
xAxis =mychart.getAxes().get(0);
//Get the first y axis
yAxis1 = xAxis.getAssociatedAxes().get(0);
alternate_label_position = false;

for( i=0; i < params["SelectSeriesToAdd"].value.length; i++){
var sdNew = SeriesDefinitionImpl.create();
var ls = LineSeriesImpl.create();
ls.getLabel().setVisible(true);
if( alternate_label_position ){
ls.setLabelPosition( Position.BELOW_LITERAL );
alternate_label_position = false;
}else{
ls.setLabelPosition( Position.ABOVE_LITERAL );
alternate_label_position = true;
}
var qry = QueryImpl.create("row[\"" + params["SelectSeriesToAdd"].value[i] + "\"]" );
ls.getDataDefinition().add(qry)
sdNew.getSeries().add( ls );
yAxis1.getSeriesDefinitions().add( sdNew );


The for-loop iterates over the selected values within the parameter and creates a Line series for each value. The value of each parameter is used as the query for the new series. Finally the new series definition is added to the first y axis.

The final code the beforeFactory event handler looks like:

importPackage(Packages.org.eclipse.birt.chart.model.data.impl);
importPackage(Packages.org.eclipse.birt.chart.model.component.impl);
importPackage(Packages.org.eclipse.birt.chart.model.type.impl);
importPackage(Packages.org.eclipse.birt.chart.model.attribute);

//Chart must be named
cht = reportContext.getDesignHandle().findElement("MyChart");
mychart = cht.getReportItem().getProperty( "chart.instance" );


//Get the x axis
xAxis =mychart.getAxes().get(0);

//Get the first y axis
yAxis1 = xAxis.getAssociatedAxes().get(0);

//alternate the series label position of each new series
alternate_label_position = false;

for( i=0; i < params["SelectSeriesToAdd"].value.length; i++){

//create a new series definition for each new series
var sdNew = SeriesDefinitionImpl.create();
//create a new series for each parameter selection
var ls = LineSeriesImpl.create();
ls.getLabel().setVisible(true);
if( alternate_label_position ){
ls.setLabelPosition( Position.BELOW_LITERAL );
alternate_label_position = false;
}else{
ls.setLabelPosition( Position.ABOVE_LITERAL );
alternate_label_position = true;
}
//Create the query. The row value must correlate with the data set row value
var qry = QueryImpl.create("row[\"" + params["SelectSeriesToAdd"].value[i] + "\"]" );
ls.getDataDefinition().add(qry)
sdNew.getSeries().add( ls );
//Add the new series definition to the first y axis
yAxis1.getSeriesDefinitions().add( sdNew );
}




The complete example is located here.

For an example of resizing a chart dynamically see
this.

Wednesday, April 23, 2008

BIRT Resizing Charts

Note: I have added the reports that are used in this post to the subversion repository http://longlake.minnovent.com/repos/birt_example
the project is birt_api_example
and the reports can be found in /Reports/charts
Alternatively you can just go to:
http://longlake.minnovent.com/repos/birt_example/birt_api_example/Reports/charts/
navigate into the charts and cut and paste into your report designs

When creating charts in BIRT the size is set automatically. The chart can be resized with the mouse after the chart is in the report as well. This is fine if you know before hand the amount of data your chart is going to display. Currently there are several bugzilla entries tracking chart resize features, but I thought I would offer a work around until those entries are completed. Presented below are two options for resizing the chart. Using the simple chart API, and using the afterDataSetFilled chart event to modify the size of the chart based on the data retrieved.

One way of resizing the chart is to use the chart simple API within the beforeFactory event. The simple api allows simple properties of the chart to be changed before the report is executed.

Some examples include setting titles, chart dimensions, and output formats:


var chart1 = this.getReportElement( "Chart1" )
var chart2 = this.getReportElement( "Chart2" )
var chart3 = this.getReportElement( "Chart3" )
var chart4 = this.getReportElement( "Chart4" )

var color1 = chart1.getTitle().getCaption().getColor()

chart1.setColorByCategory( true );
chart1.getTitle().getCaption().setValue( "Color by Category" );
chart1.setColorByCategory( true );
color1.setRed( 255 );
color1.setGreen( 0 );
color1.setBlue( 0 );
chart1.getTitle().getCaption().setColor( color1 );

chart2.setDimension( "ThreeDimensional" );
chart3.getCategory().setSorting( "Descending" );
chart4.setOutputType("PNG");


In this example the charts must be named. Select the general properties for the chart and in the name field enter a value.

The size of the chart can be set using the following code in the before factory.


var mychart = this.getReportElement( "mychart1" );
if( reportContext.getOutputFormat() == "pdf" ){
mychart.setWidth("3in");
mychart.setHeight("3in");

}



In this example we resize the chart based on output format.

If you want the chart to resize based on the data populated in the chart data sets it requires a little bit of a work around. First, name the chart as with the simple chart API and enter the following code in your beforeFactory event handler.



var mychart = this.getReportElement( "mychart" );
mychart.setWidth("");
mychart.setHeight("");



After you have done that, enter code similar to the following in your afterDataSetFilled chart event handler.


function afterDataSetFilled(series, dataSet, icsc)
{
if( series.getSeriesIdentifier() == "seriesone" ){
if( dataSet.getValues().length > 4 ){
icsc.getChartInstance().getBlock().getBounds().setWidth(800);
icsc.getChartInstance().getBlock().getBounds().setHeight(600);
}else{
icsc.getChartInstance().getBlock().getBounds().setWidth(400);
icsc.getChartInstance().getBlock().getBounds().setHeight(300);

}
}
}


In this example we have named our series “seriesone” on the third tab of the chart wizard.

If the category values contain more than four entries we double the size of the chart. The sizing could be more dynamic, for example we could use the list size to increment a delta per category as well.

This example works with BIRT 2.2.2, but will not work with the chart output type set to SVG, when rendering to HTML.

For Birt 2.3, everything can be done in the beforeGeneration script and it does work with SVG. No other scripts are needed.


function beforeGeneration( chart, icsc )
{
xAxis = chart.getBaseAxes()[0];
yAxis = chart.getOrthogonalAxes( xAxis, true )[0];
seriesDef = yAxis.getSeriesDefinitions().get(0);
runSeries = seriesDef.getRunTimeSeries()[0];
//Retrieve list of data values
list = runSeries.getDataSet().getValues();
if( list.length > 3){
chart.getBlock().getBounds().setHeight(250);
chart.getBlock().getBounds().setWidth(600);
}else{
chart.getBlock().getBounds().setHeight(100);
chart.getBlock().getBounds().setWidth(600);
}
}



Tuesday, August 29, 2006

Building a Combination Chart with BIRT

A new screen cast has been added to the BIRT web site that demonstrates building a Combination Chart. This example uses a bar chart with a line chart superimposed. It also illustrates how drill to details can be implemented within a chart.

It can be viewed here.

Additionally the Charting FAQ is now available in the Eclipse Wiki.
The Charting FAQ explains many details on how the Charting Engine can be deployed, customized and manipulated.