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.

23 comments:

Fred said...

Hi Jason,
thank you for this interesting post, I'm in needing of something like this but with a scripted data set (instead of queries).
I tried to understand how to add data in a different manner than this:
//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)

but cannot find any informations on the net about... any hint?
Thanks a lot

Fred

Jason Weathersby said...

Fred,

It should not matter if it is a scriped dataset or a normal query. The QueryImpl.create function is looking for a bound column name. Look at the binding tab in the property editor for the chart.

Jason

Anonymous said...

It's well and fine, but for exemple, how do you do this , if you want to apply one methode on the row values for the Y-Series like this :

Query query = QueryImpl.create("row[\"product\"].length");

that will not work. In the Designer you will note, that the value of the Y-Series is row["product"] , but the length function is ignored...

How can you resolve that ?

Thanks,
JBill.

Jason Weathersby said...

JBill,

Did you get this issue worked out?

Did you try adding a new bound column with its value set to
dataSetRow["PRODUCT"].length

Jason

Anonymous said...

When you export the plugin project,
please do not use Export --> Java --> JAR file
Instead, export to this: "Deployable plug-ins and fragments."

Then it will work.

And the new library added one more arguments to the execute method:
public Object execute( Object[] args, IScriptFunctionContext arg1)

rajesh said...

when i published a similar dynamic series in MAXIMO it gave the error "SeriesDefinitionImpl is not defined".
Please help me to solve it.

Jason Weathersby said...

Did you do the imports?

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);

rajesh said...

Jason ..I added all those imports.. I guess the org.eclipse.birt.chart.engine.jar is not recognized in MAXIMO... It didnt show any error for the imports, but it said "SeriesDefinitionImpl is not defined" ... any clue about the problem ?? my beforefactory method is almost same like your example except for the looping structure...

rajesh said...
This comment has been removed by the author.
Jason Weathersby said...

Do you know what BIRT version is being used?

rajesh said...

Hi Jason ,

am using birt 2.3.2 .. its IBM's birt... i googled the error text "reference error SeriesDefinitionImpl is not defined" but couldnt find anything useful... i guess no one has got this error before .. thanks for quick reply ... please help me if have got any idea about this error..
Thanks ,
Rajesh

Anonymous said...

Hi Jason,
LineSeriesImpl.create() method returns SereisImpl which is why LineChart is not getting created.
I'm using Birt 2.3

Jason Weathersby said...

I am not 100% on what you are saying. Can you email me a report that uses the sample db that shows the issue?

Jason

Anonymous said...

hi Jason,
Thanks for this post very interesting,
i tried to do the same, but it doesn't work :( i got an exeption caused by :
org.mozilla.javascript.EcmaError: TypeError: Cannot call method "getReportItem" of null
How can i resolve that ?

Jason Weathersby said...

Did you name the chart? The error you are getting indicates that you either did not name it our you used the wrong name here:
cht = reportContext.getDesignHandle().findElement(“MyChart”);

Anonymous said...

i see, i corrected the probleme, now i'm getting in preview an empty image.
any idea ?

Jason Weathersby said...

if you switch to the xml view does it show any red icons in the margin?

Anonymous said...

yes exactly, that's what it shows ...

Jason Weathersby said...

if you mouse over the red icon what error pops up?

Anonymous said...

there is no problem in the xml, but in the preview i got an empty image with an X icon on the top.
i don't know if the problem is linked to the data set or the data source ?

Jason Weathersby said...

can you email me the report? jasonweathersby at windstream.net

Anonymous said...

i sent it to jasonweathersby@windstream.net

plz tell me if u didn't get it.

Thank you.

Jason Weathersby said...

I replied to you.