Friday, August 20, 2010

BIRT Flash and Gadget Scripting

In my last post I blogged about how Chart Scripting works in BIRT. If you are using Actuate BIRT, you also will have access to Flash Charts and Gadgets. Scripting for these two report items is very similar to the standard Chart scripting model.



Flash Charts
Currently Flash charts support 14 script event triggers that can be hooked to make chart modifications.



The event firing order is shown below.



As you can see the order is very similar to that of normal BIRT charts. The before and after dataset filled events are fired first for each dataset (category and value). Next the beforeRendering event is fired and then the before and after draw series events are fired for each series. Between the before and after draw series events the before and after draw data point events are fired for each point in the series. The before and after draw series events (and the events contained in them) are fired for each series in the chart. After all series are processed, the before and after draw effect events are fired for each effect in the chart. Effects can be used to alter the chart at runtime, allowing font manipulation, blurring, glowing, shadowing, beveling, and animation of different objects of the chart. Effects are added to the chart using the effects editor.



Using the beforeDrawEffect event script you can alter any of the effects added to the chart. For example to turn off the shadow effect in the above chart you could use the following script.



function beforeDrawEffect( effect, fcsc )
{
var effectName = "MyTitleEffect";
if ( effectName.equals( effect.getAction().getName() ) ){
if( effect.getAction().getShadow() != null ){
effect.getAction().getShadow().setEnabled(false);
effect.getAction().getFont().setEnabled(false);
}
}
}



The getEffect().getAction().getName() call returns the name of the effect as defined in the effect editor. This can be used to determine which effect definition your script is operating on. The getAction allows access to the different effect features. Note that if the effect is not enabled the getter method for the effect will return null. In the above script we first check to see if the getShadow method returns null before turning it off.

Finally the afterRendering event is triggered.

These events function much the same way as described in my earlier post. You will notice that event function prototypes are just a little different. For example instead of a chart and icsc parameter you will get a flashChart and fcsc parameter in certain events. For practical purposes these are just extended chart and icsc objects and still provide most of the standard object features. For example to set the chart title to the value of a report parameter using the reportContext, you could use a script like the following.



function beforeRendering( flashChart, fcsc )
{
var parmChartTitle = fcsc.getExternalContext().getScriptable().getParameterValue("MyChartTitle");
flashChart.getChart().getTitle().getLabel().getCaption().setValue(parmChartTitle)
}



Flash Gadgets
BIRT Flash Gadget report items are used to display data in a unique dynamic graphic. Currently Actuate BIRT supports thermometer, cylinder, bullet, spark line, meter gauge or linear gauge gadgets. These gadgets also support server side scripting.



Twelve event triggers are supported and can be used to alter how the gadget is displayed. The events that are triggered will depend on the gadget and which features the gadget is using. For example this meter gadget:



Produces the following event order.



The beforeRendering event is called first and can be used to modify the flash gadget before it is rendered. Next before and after draw region events are trigger for each of the three regions in the meter gadget. This gadget contains two thresholds, so the before and after threshold events are triggered next for each threshold. The gadget also contains two needles so the before and after draw needle events are fired. The threshold label contains a font effect and the threshold areas are animated. This produces before and after draw effect triggers for each. Finally the afterRendering event is triggered.

Retrieving Gadget Values
BIRT Flash Gadgets use value definitions to link the gadget to a BIRT dataset. The definitions are assigned in the Gadget Editor. The runtime values for these definitions can be retrieved in script using the gadget object. For example, in the beforeRendering you could use the following script:


function beforeRendering( gadget, fgsc )
{
var vals=gadget.getResultValues();
for( i = 0; i < vals.size(); i++ ){
if( vals.get(i).getValue() > 20 ){
//do something
}
}
}


The number of result values will depend on the gadget. For example, In a Spark Line gadget there will be multiple values. In a meter gadget, there will be a result value for each needle value.

Region Events
You can customize the colors, transparency, labels, and range values of any region using the beforeRendering or beforeDrawRegion scripts. See the example below.


function beforeDrawRegion( region, fgsc )
{
if( region.getLabel().equals("A") ){
region.setColor("00ff00");
}
//Display value range of region.
region.setLabel( "(" + region.getStartValue() + ", " + region.getEndValue() + ")" );

}

function beforeRendering( gadget, fgsc )
{
//First Region
gadget.getRegion().get(0).setEndValue(25);
//Second Region
gadget.getRegion().get(1).setStartValue(25);




Threshold Events
Thresholds, like regions can also be customized using script. Virtually anything that is set in the designer can be changed in an event trigger. For example:


function beforeDrawThreshold( threshold, fgsc )
{
if( threshold.getLabel().equals("MyThreshold") ){
threshold.setShowValueInside(true);
threshold.setShowValueOnTop(false);
threshold.setStartValue(10);
threshold.setColor("0000ee");
// Display the value range of threshold.
threshold.setLabel( "(" + threshold.getStartValue() + ", " + threshold.getEndValue() + ")" );
}
}

function beforeRendering( gadget, fgsc )
{
gadget.getThresholds().get(0).setMarkerColor("ff0000");
}


In these scripts, we change the starting value, color, label and label location in the beforeDrawThreshold. The marker color is also changed in the beforeRendering script.

AddOn Events
BIRT Gadgets support the concept of an AddOn. An AddOn allows the designer to place images, text, and various shapes on top of the gadget. If the gadget contains an AddOn the before and afterDrawAddOn events will be triggered, allowing you to customize the AddOn at runtime. For example, the following script customizes two AddOns.


function beforeDrawAddOn( addOn, fgsc )
{
// Change add-on styles.
if ( addOn.getName().equals("TitleAddOn") )
{
addOn.setLabel( "Meter Gauge" );
addOn.setFontColor( "ff0000" );
addOn.setFontSize( 28 );
addOn.setY( 35 );
}
else if ( addOn.getName().equals( "ArcAddOn" ) )
{
addOn.setFillColor( "ffff00" );
}
}


AddOns can even be created and placed on the gadget at runtime. For example the following script adds a text AddOn to the gadget using the beforeRendering event.


function beforeRendering( gadget, fgsc )
{
importPackage( Packages.com.actuate.birt.flash.gadgets );
importPackage( Packages.com.actuate.birt.flash.gadgets.impl );

// Add a text add-on.
var textAddOn = GadgetsFactory.eINSTANCE.createTextAddOn();
textAddOn.setAddOnType( AddOnType.TEXT );
textAddOn.setName("DescAddOn");
textAddOn.setShowAddOn(true);
gadget.getAddOns().add( textAddOn );
gadget.setZOrderPosition(1);

textAddOn.setLabel("This sample demonstrates how to add an add-on by scripting.");
textAddOn.setX(2);
textAddOn.setY(20);
textAddOn.setFontSize( 12 );
textAddOn.setHorizontalAlignment( HorizontalAlignmentType.LEFT );
textAddOn.setVerticalAlignment( VerticalAlignmentType.TOP );
textAddOn.setWrap( true );
textAddOn.setTextBoxBorderColor("000000"); //Black
textAddOn.setTextBoxBackgroundColor("c0c0c0");
textAddOn.setWrapMaxWidth( 30 );
textAddOn.setWrapMaxHeight( 50 );

}


Needle Events
If your gadget contains a needle, the before and afterDrawNeedle events will be triggered. Using script the needle can be customized. The size, color, shape, and various other properties of the needle can be customized. These events are passed a NeedleWrapper instance, which can be used to get the specific needle type. For example the following two scripts differentiate between a normal gadget needle and a meter gadget needle.


function beforeDrawNeedle( needleWrapper, fgsc )
{
importPackage( Packages.com.actuate.birt.flash.gadgets );
importPackage( Packages.com.actuate.birt.flash.gadgets.impl );
// Change needle styles.
needleWrapper.getNeedle().setShape( Shape.DIAMOND );
needleWrapper.getNeedle().setFillColor("2cf83a");
needleWrapper.getNeedle().setBorderColor("007f00");
}

function beforeDrawNeedle( needleWrapper, fgsc )
{
//Set Needle size to 25%
needleWrapper.getMeterNeedle().setSize(25);
needleWrapper.getMeterNeedle().setBackgroundColor("ff0000");
}



Effect Events
The effect events were described above in the flash charting section.

Several examples used in this post are available at BIRT-Exchange.

17 comments:

Anonymous said...

Hi Jason,

i would be very happy if you could give me some advice for my problem on which i have already worked for several days without any success.

my problem is as following:
i use Birt 2.2 with own resource locator to add images in the reports and it works fine in the past.

Recently i upgrade to Birt 2.6, after the update images could not been shown in the report, there isnt any errors in the log.

The Images come from shared resources and at runtime they will be catched by my own resource locator from a xxx.jar file in /WEB-INF/lib.

As already said, it works fine with Birt 2.2 in the past. With Birt 2.6 the properties and library file in xxx.jar can also be catched, only the images file cause problem. In log file i can see that the resource locator has already localized the asked image with output like "zipC:/.../xxx.jar!/.../image.gif".

i have also try with "file:///C:/.../xxx.jar!/.../image.gif", but it doesnt help.

thanks in advance.
Peng

hier is code-fragment from my resource locator:

public URL findResource(ModuleHandle moduleHandle, String filename, int type) {
...
case IMAGE :
return ClassLoader.getResource(filename);
...
}

public URL findResource( ModuleHandle moduleHandle, String fileName,int type , Map appContext) {
return findResource(moduleHandle, fileName, type);
}

Jason Weathersby said...

If you leave the default resource locator, you can select the image type to be a image file in shared resource and then enter the jar file like:
jar:file:/C:/myresources/myimages.jar!/myimage.jpg

If you want this to be dynamic you can enter an expression (make sure to use the fx button) and enter an expression like:

var jarfile = reportContext.getResource("myimages.jar");
myfulljarimage = "jar:"+jarfile.toString()+"!/myimage.jpg";
myfulljarimage;

This assume the myimages.jar file is in your resource folder.

Anonymous said...

Hi Jason,

that works, thanks very much.

Peng

toksib said...

Hi Jason,

I want to set color order for the flas pie chart but I'm a bit confused...
I created the follow script but it doesn't work:

function beforeDrawDataPoint( fcdph, fill, fcsc )
{
var sValue = fcdph.getValueAsString();
if( sValue == 'Incomplete PPMs' )
{
fill.set( 242, 88, 106, 255 );
}
else if( sValue == "Complete PPMs" )
{
fill.set( 128, 255, 128, 255 );
}
}

Did I use the wrong function?

Thanks
Peter

toksib said...

I'm also not sure how could I change the legend color to the same as I want it in the script. For standard piechart there is a function called "beforeDrawLegendItem" but it is not exist for flash object.
Or don't I need to change it as it linked with chart color and it will change automaticaly?

Jason Weathersby said...

If you change the color in the beforeDrawDataPoint the legend will change automatically. My guess as to why the script did not work is because you are getting the value of the orthogonal value not the category series. For example if you design a pie chart that uses the sample db with the query select * from orderdetails where ordernumber == 10101 and set the product code on the category and qty ordered on the slice you can check for a category like:

function beforeDrawDataPoint( fcdph, fill, fcsc )
{
var sValue = fcdph.getDataPointHints().getBaseDisplayValue();
if( sValue == 'S18_2325' )
{
fill.set( 0, 0, 255, 255 );
}
}

toksib said...

Hi Jason,

Thanks for this it works nice solution.

I've tried to do it with bar chart as well but I think I should use soemthing else instead of getBaseDisplayValue() function as it doesn't work.


function beforeDrawDataPoint( fcdph, fill, fcsc )
{
var sValue = fcdph.getDataPointHints().getBaseDisplayValue();
// set( R, G, B, alpha )
if( sValue == "Incomplete PPMs" )
{
fill.set( 242, 88, 106, 255 );
}
/* else if( sValue == "2-Mid" )
{
fill.set( 232, 172, 57, 255 );
}*/
else if( sValue == "Complete PPMs" )
{
fill.set( 128, 255, 128, 255 );
}
}

Could you give me any idea for this also please?

toksib said...

I think something is wrong because this is a Stacked Bar Chart. The color should depends on the optional Y series grouping value. I tried tu use the getStackOrthogonalValue() function as well but still not working.

Tanks
Peter

Jason Weathersby said...

Can you try
fcdph.getDataPointHints().getSeriesValue()

this is used for optional grouping.

BTW you may want to surround the code with a
if( fcdph != null ){
}

toksib said...

Hi Jason,

Thanks for your quick response. I tried my code like this but it is not working:

function beforeDrawDataPoint( fcdph, fill, fcsc )
{
if (fcdph != null){
var sValue = fcdph.getDataPointHints().getSeriesValue();

if( sValue == 'Cancelled' ){
fill.set( 242, 88, 106, 255 );}
else
if( sValue == 'Resolved' ){
fill.set( 232, 172, 57, 255 );}
else
if( sValue == 'Shipped' ){
fill.set( 128, 255, 128, 255 );}
}
}

I also tried with the getSeriesDisplayValue(); but it is same.
This code is for the sample database like:

select CLASSICMODELS.ORDERS.ORDERNUMBER,
CLASSICMODELS.ORDERS.ORDERDATE,
CLASSICMODELS.ORDERS.STATUS
from CLASSICMODELS.ORDERS

and then I created a barchart where Y is counted ordernumnber X is orderdate and optional Y is status. I tested your code on this but nothing happend.
Did I put the if section the right place?

Thanks in advance
Peter

Jason Weathersby said...

Change the chart to color by categories. You can do this in the chart editor in the third tab. Select Series tree node and there will be a Color By: drop down box. Select categories. Then you script should work.

Jason

toksib said...

Hi Jason,

That works, thank you very much once again. One thing. In case of pie chart the legend color is changing with the scilces color however in case of barchart it isn't. Do I need to change the legend color separately in case of bar chart? I couldn't find any function for legend in case of flash chart. How can I do that?

Thanks Peter

Jason Weathersby said...

Yea, I think that script is to late. Set the colorby setting back to value series and use the following beforeRendering script


function beforeRendering( flashChart, fcsc )
{

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

chart = flashChart.getChart();
xAxis = chart.getBaseAxes()[0];
yAxis = chart.getOrthogonalAxes( xAxis, true)[0]
ySerieDef = yAxis.getSeriesDefinitions().get(0)
runSeries = ySerieDef.getRunTimeSeries();
ySerieDef.getSeriesPalette().getEntries().clear( );
//grouping will add additional runtime series
firstRunSeries = runSeries.get(0);
for( cnt=0;cnt<runSeries.size();cnt++){
var series = runSeries.get(cnt);
if( series.getSeriesIdentifier() == "Shipped" ){
ySerieDef.getSeriesPalette().getEntries().add( ColorDefinitionImpl.create(0,0,255,255) );
}
if( series.getSeriesIdentifier() == "Cancelled" ){
ySerieDef.getSeriesPalette().getEntries().add( ColorDefinitionImpl.ORANGE() );
}
if( series.getSeriesIdentifier() == "Resolved" ){
ySerieDef.getSeriesPalette().getEntries().add( ColorDefinitionImpl.RED() );
}
if( series.getSeriesIdentifier() == "In Process" ){
ySerieDef.getSeriesPalette().getEntries().add( ColorDefinitionImpl.YELLOW() );
}
if( series.getSeriesIdentifier() == "On Hold" ){
ySerieDef.getSeriesPalette().getEntries().add( ColorDefinitionImpl.GREY() );
}
if( series.getSeriesIdentifier() == "Disputed" ){
ySerieDef.getSeriesPalette().getEntries().add( ColorDefinitionImpl.GREEN() );
}
}
}

toksib said...

Thanks Jason that works fine.
Peter

Anonymous said...

that was very helpful to me in building a report with a flash chart where the ranges are based on parameters.
thanks!!

Sri Krishna said...

Hi Jason,

Greetings For The Day. I am using birt eclipse. I have a requirement with flash reports. In above u said it is possible with birt actuate but i want in birt eclipse. Is there any solution for that?

Thanks in advance,

Srikrishna.

Jason Weathersby said...

Currently those features are only in Actuate BIRT. You can include flash content in open source BIRT, using a text element, but the engine will not generate flash content.