We have come a long way since we started this book. We have seen all the different aspects of the BIRT Report Development environment. We have seen how to drag-and-drop reports with the Palette and the Report Designer, how to work with the outline and the navigator, and how to set properties or each of the report components.
We have seen how BIRT can link to data sources through JDBC drivers and how to build dataset through those connections. We have seen how to format reports using properties and styles.
Now we are going to look at the aspect of BIRT that makes it really powerful—scripting. Scripting is a complex topic, but once we understand it, we will be able to make BIRT do some really amazing things such as:
BIRT utilizes the Mozilla Rhino Engine to handle its scripting capabilities. What this means is that inside of BIRT reports, Report designers have full access to all primitive types, object, methods, and libraries accessible to JavaScript. As an added bonus, report developers even have full access to all Java classes that are in the classpath. In the BIRT Report Designer, this means one have full access to all the Java objects in the Eclipse environment. One can even use custom Java objects in his/her report to handle certain aspects of processing.
In the following sections, we are going to look at the two different types of scripting that BIRT has to offer—expressions and event handling. We are going to look at how to access different types of BIRT properties using expressions. We will then look at how to handle report generation events using BIRT's implementation of the Rhino engine and how to handle those same events using Java objects. Although a prior knowledge of Java or JavaScript is not necessary, it will greatly enhance the understanding of the topics covered in this chapter. But to follow along effectively, the reader should understand:
In BIRT, there are two different types of scripting—expressions and event handling. Expressions are simple bits of script, usually ranging from 1 to less than 10 lines of code, returning a single value for use in a BIRT report. Event Handling scripts are usually a bit larger than expressions, and are meant to accomplish some sort of task such as retrieve a row from a dataset, manipulate some data before it is sent to output, or handle preparations for rendering. It is recommended that the reader learn more about JavaScript to assist with their report development.
Expressions make up a bulk of the scripting used in BIRT. In fact, we have used Expressions multiple times already in this book. Expressions are usually single line statements that return a single result. Tasks such as retrieving a value from a row and outputing it in a data element, retrieving parameter values, and the Highlight expressions used in the last chapter are all examples of Expressions. Any time we use the Expression editor in BIRT, we are working with Expressions.
Let's take a look at the most commonly used Expression—the Data element. Open up the Customer Orders report that we've used in the last chapter. Here we have a finished report with numerous Expressions used throughout. Let's take a look at what one of these Expressions look like.
This preceding screenshot shows us a very simple expression that retrieves the value of the CUSTOMERNUMBER
column from the current row. It is taken from the Data element in the Orders detail table. dataSetRow
is an array that represents the current row in the detail band. The screenshot illustrates that a single value is returned with a simple line of code.
Let's take a look at another more complicated example. Look at step 14 of the Bar Chart example from last chapter. There, we have an expression that looks like:
(row["ORDERDATE"].getYear() + 1900).toString() + '-' + (row["ORDERDATE"].getMonth() + 1).toString()
This Expression retrieves the ORDERDATE
value from the current row using the row array, and in two separate parts—one part will retrieve the year and another will retrieve the month using the standard Javascript functions getYear
and getMonth
. In BIRT, getYear
will return the number of years since the epoch, which is 1900. So, in order to get the actual number of years, we need to add 1900 to the result. Therefore, for the year 2007, the result from getYear
will return 107, so adding 1900 will return 2007. The situation arises with getMonth
; it will return the month from a zero offset. So, January will return 0, February will return 1, and so on, requiring us to add 1 to get the correct month.
As we want to return a string, and the results returned by getYear()
and getMonth()
are integer or number values, we use the Javascript toString()
method to convert these numbers to strings. Finally, we need to concatenate the converted strings and the separating character to get the final result. This Expression, although doing many different things such as retrieving portions of a date value, adding offsets, converting numbers to strings, and then concatenating strings together, returns only a single result.
So, let's get our hands on this. In the following example, we are going to add a line number to the order detail table.
OrderNumber
column.row.__rownum + 1
There is now a column with the current line number for the report. While not exactly the most attractive addition, the expression works. We have seen expressions such as these throughout the book. In the Customer Orders report, the Report Title is pulled from the rprmReportTitle
parameter using the params["rprmReportTitle"]
expression. We use the same row number expression in the highlight example in the Customer Orders report as well.
As we saw with the Bar Chart example, expressions are simply JavaScript. Expressions also have access to JavaScript methods, operators, and objects. In the line number example we just saw, we used the addition operator, and in the Bar Chart example, we used the toString(), getMonth()
, and getYear()
methods. All assignment, math, comparison, and logical operators available in JavaScript are included. Let's look at the following example that shows the JavaScript string methods being used to return the length of a string:
String Length.rptDesign
."This is a test".length
A simple report with the number 14 should be displayed. The above expression can also be replaced with the following expression.
var testString = "This is a test"; testString.length;
In the example, we are breaking from the one line expression and using multiple lines and a variable. The key is that the last line is returning a single value. All sorts of computations can take place, as long a single value is returned. Now consider we change the expression to the following:
var testString = "This is a test"; var splitString = testString.split(" "); splitString[3].toUpperCase();
The report will now return only the word TEST. Again, while multiple things are being done in this expression such as the assignment of the string, splitting the string, and converting the element 3
to upper case, only a single result is returned.
At the end of Chapter 6, Report Parameters, we went through an exercise where we set a default parameter, using expressions in BIRT's new expression-based default parameter value mechanism, to set a range of current date minus 20 days as a start date and current date as the end date. This exercise is an example of using more complex expressions.
//bring in the java.util package into the scripting scope importPackage( java.util ); //create a new GregorianCalendar object var cal = new java.util.GregorianCalendar(); //set the date to now cal.setTime(new Date()); //substract 20 days cal.add(Calendar.DAY_OF_MONTH, -20); //return the start date cal.getTime();
In this example, we utilized some Java objects that were in the execution environment's classpath. Any Java object in a classpath can be utilized in BIRT expressions, as long as the importPackage
method is called or it is referenced with the Packages.path
syntax. For example, we used importPackage( java.util )
to import the java.util
package. But we could have omitted that line and just as easily declared the GregorianCalendar
as:
var cal = new java.util.GregorianCalendar();
The reason for this is that as the Eclipse environment already has the java.util
package loaded, so there is no need to explicitly load it with the importPackage
call. The key to complex expressions are that a single value gets returned, as in the result of the last line of our expression, cal.getTime()
, which returned a single Date value.
Now if Expressions are simple lines of code that return a single value, how do Event Handlers fit into the scripting world of BIRT? The answer is simple—they do exactly what they claim they do, handle events. What does that mean exactly?
Well, to understand that we need to look at how BIRT reports are generated. BIRT Reports are generated in phases. As explained in the BIRT Report Object Model Scripting Specification at http://www.eclipse.org/birt/phoenix/ref/ROM_Scripting_SPEC.pdf, there are five phases in the report generation lifecycle.
In each one of these phases, particular events are triggered that allow the report developer to override the default behavior of the report generation. This allows us to do all sorts of advanced things with BIRT such as custom filtering of data, dynamically adding or removing BIRT objects to the report, or even building an entire BIRT report through script.
Let's simplify things just slightly. If we look at the dataset element, we can see five different events are associated with it:
Consider we have two things we need to do with the dataset. First, we need to initialize a counter variable to 0
for the number of rows that are going to be processed. Next, we need to add to the counter each time a row is retrieved. So, these things are important. The order in which these events take place is beforeOpen, afterOpen, onFetch, beforeClose
, and afterClose
. Therefore, in either beforeOpen
or afterOpen
, we need to set our variable to 0
. Then, in the onFetch
event, we need to add 1
to our counter. Let's look at an example of how to do that:
Customer Orders.rptDesign
. Customer Orders.rptDesign
. initialize
. Be aware that initialize can be called twice in certain circumstances. If we are doing heavy processing, we can also consider using the beforeFactory
method. But for our example, we are using initialize.var globalCount;
getCustomerOrders
dataset from the Outline tab. beforeOpen.
globalCount
variable: onFetch.
globalCount++;
globalCount
Understanding the order of execution is important. The report's initialize
method will get executed before any of the other methods and declare globalCount
. Next, the dataset gets generated in the data transform phase calling BeforeOpen
and initializing globalCount
to 0
. During each row request, the globalCount
gets incremented by one. And finally, the value of globalCount
gets inserted into the data element we dropped into the report design in the factory phase.
From any of the report generation phases, we have access to various contexts. For the most part, each of the five phases has its own context. In addition, there are also a few extra contexts—one for the data row and the other for displaying an element.
In BIRT, contexts are used to access objects within the scripting environment. At a high level, one has access to the Report context. The Report Context is a mapping in the BIRT scripting environment to the Java object IReportContext
, inside the org.eclipse.birt.report.engine.api.script
package. This object allows script developers access to the design object, report parameters, and various other aspects of the report. Many of these objects are shorthanded by other, easier-to-use references such as using the params array to access report parameters. Let's take a look at how to access the report context.
stringLength.rptDesign.
accessMe
as a string and assign it the default value of Test Parameter. valueFromScript
. StringLength.rptDesign
. initialize
event, put in the following code:valueFromScript = params["accessMe"];
reportContext
under the Context
folder, it will automatically put that into the script editor. Also, when we type, we may notice a drop-down box that allows to see all of the objects and properties associated with the reportContext
object. initialize
method as follows:valueFromScript = reportContext.getParameterValue("accessMe");
In the last modification, we didn't use a local context. Instead, we used the global reportContext
to access the reports parameters and used the report context's getParameterValue()method
to retrieve the value entered in accessMe
.
Let's now look at using the reportContext
to add a new element to the report. In order to do this, we need to change only the initialize
method from the last example with the following code:
valueFromScript = reportContext.getParameterValue("accessMe") + " modified"; //import the needed packages from the Java API importPackage(Packages.org.eclipse.birt.report.model.api); importPackage(Packages.org.eclipse.birt.report.model.api.elements); //using the report context, get the design handle design = reportContext.getReportRunnable().designHandle; //get the element factory to create the new label elementFactory = design.getElementFactory(); dataElement = elementFactory.newLabel("testElement"); //set the text from the valueFromScript variable and add //to the report designs body dataElement.setText(valueFromScript); design.getDesignHandle().getBody().add(dataElement);
So, let's take a look at this example. The valueFromScript
variable is assigned from the report parameter, retrieved from the reportContext
, similar to the previous example. The next two lines are not really necessary, but illustrate that we will be using objects retrieved from the BIRT API packages. Any time we reference Java objects in code, we should use the importPackage
method to reference the package containing those objects. For example, if we were to use the java.util.GregorianCalendar
object, we should use importPackage(Packages.java.util)
.
The next step is to retrieve the Design Handle from the report context. First, we use the getReportRunnable
, which is a reference to the open report design. Then we use the designHandle
reference to get the report design handle.
The next two lines retrieve the report element factory, which is a convenient way to create report design elements from the design handle. It then uses that element factory to create a new Label element called testElement
. We then set the text of that label to valueFromScript
.
The last part of the script needs to reference the getBody
method in order to get a slot in the report to add a new element to. We then add the new element to the report.
Now, where we add elements is important. Recollect the phases from earlier? Well, one cannot add new elements after the factory phase. This means if we look at the Report Root, we can use the earlier code snippet only in the initialize, beforeRender
, and the beforeFactory
events. The afterFactory
and afterRender
events will not work. If we move the code to those elements, then the element will not get added.
If one looks in the beforeFactory
event, he/she will notice that the context that points to the IReportDesign
object is available, but it is not available in any of the other events. When working with reports through scripting, remember that objects are not always going to be available, depending on what point of the report creation cycle we are at.
The easiest way to drop elements from reports is to find the element using the Design Handle's findElement
method. This method will return a reference to a DesignElementHandle
, referencing the items name used. Let's say we wanted to change the name of the Data element to dataElementToRemove
, with the test string expression used earlier in this chapter. The following code will search the report design, find the matching element, and remove it from the report. It is run from the report's initialize
method.
reportContext.getReportRunnable().designHandle.getDesignHandle().findElement("dataElementToRemove").drop();
In addition to adding report elements, it is also possible to add conditions such as Highlights, Maps, and Sorting conditions. The following example will show how to dynamically add sorting conditions to a report, based on the value of a report parameter checkbox:
customerPaymentDynamicSort.rptdesign
. dsClassicCars
data source from the library.select * from CUSTOMERS, PAYMENTS where CUSTOMERS.CUSTOMERNUMBER = PAYMENTS.CUSTOMERNUMBER and customers.customernumber = ?
dsprmCustomerID
. rptprmCustomerID
. Set it as a textbox entry. rptprmSortOrder
. Set Hidden option checked. OnPrepare
event of the table. There are two versions of this script.One for BIRT 2.3 and above:
importPackage(Packages.org.eclipse.birt.report.model.api.simpleapi); if ( params["rptprmSortOrder"].value != null ) if ( params["rptprmSortOrder"].value.length > 0 ) { var sortCondition = SimpleElementFactory.getInstance().createSortCondition(); switch (params["rptprmSortOrder"].value) { case "date" : sortCondition.setKey("row["PAYMENTDATE"]"); break; case "price" : sortCondition.setKey("row["AMOUNT"]"); break; } sortCondition.setDirection("asc"); this.addSortCondition(sortCondition); }
The other for BIRT versions prior to 2.3:
//We only want to add this into our code when the value is not null for the //parameter sort if ( params["rptprmSortOrder"].value != null ) { //Bring in the BIRT Report Model API and for CONSTANTS importPackage( Packages.org.eclipse.birt.report.engine.api.script.element ); //Create a dynamic sort condition var sortCondition = StructureScriptAPIFactory.createSortCondition(); //Based on the value of the sort parameter, set the appropriate key value for sorting switch (params["rptprmSortOrder"].value) { //Remember that for the key, we need to use the fully qualified row and field name as a string, not as a value case "date" : sortCondition.setKey("row["PAYMENTDATE"]"); break; case "price" : sortCondition.setKey("row["AMOUNT"]"); break; } //set condition to ascending order sortCondition.setDirection("asc"); //Add to the table this.addSortCondition(sortCondition); }
PaymentDate
column header. customerPayment.rptdesign
file. rptprmCustomerID
field and set the value to params["rptprmCustomerID"]
. rptprmSortOrder
parameter from the Report Parameters list and set the value to "date
" with the quotation marks. Amount
column, except set the value of rptprmSortorder
to "price
".With this done, we can now reorder the report in view time when the user clicks on the date or the amount columns. With a little more logic developed in, we can have the report do both ascending and descending sorts, and even have it refresh the report without having to refresh the viewing page.
Sometimes it is preferable to have a single report behave in a particular way based on some environmental factor. Parameters such as Environment Variables, Output Format, and the time of day may affect the way we want a report rendered. In the following example, we are going to use a real life scenario that I encountered when a client wanted a single report to not enable hyperlinks when they rendered to PDF.
PDFhyperlink.rptDesign
. onRender
. onRender
script:if (reportContext.getOutputFormat().equalsIgnoreCase("PDF")) { this.setAction(null); }
If we go to the Run menu, and choose View as HTML, we will see that the report has a hyperlink which, when clicked, opens the BIRT homepage. If we view as a PDF, that hyperlink will not exist and it will just be a label.
In the example that we just saw, we are affecting only the onRender
event for output format. This is affecting report generation at the presentation phase. The example uses the report context to determine the output format. If the output format is nothing, the above script will take the labels action property, which is of type org.eclipse.birt.report.engine.api.iaction
, and set it to nothing or null. This will essentially cancel any actions for this component when the output format is equal to PDF.
One of the other things we can do with BIRT scripting is create a data source. In the following example, we will create a simple report that will return the numbers 1 through 10 using a scripted data source:
countOneToTen.rptDesign
. dsCount
. cnt
of the type Integer
. Open
event, add the following code:reportContext.setGlobalVariable("currentCount", 0);
fetch
method, use the following code:var currentCount = reportContext.getGlobalVariable("currentCount"); if (currentCount < 10) { currentCount++; row["cnt"] = currentCount; reportContext.setGlobalVariable("currentCount", currentCount); return true; } return false;
dsCount
data source to the Report Designer.In the example that we just saw, we did a few things in a different but interesting way. First, we created the Scripted Data source. The way this works is that the fetch
method needs to return true
when data is returned and false
when data is not returned. To get data into the returned row into the cnt
column, we then use the reportContexts
global variable to keep track of the running count.
The last thing we are going to look at in this chapter is using Java objects as Event Handlers instead of JavaScript in the script editor. In order to implement Java-based Event handlers, the designer needs to extend the appropriate Event Handler object. For example, if we are going to implement the last example as a Java object, we would need to extend the org.eclipse.birt.report.engine.api.script.eventadapter.ScriptedDataSetEventAdapter
class.
Therefore, to do this, let's create a separate Java project in Eclipse. Let's create a class like the following:
package com.birtbook.eventHandler; Java objectsJava objectsusing, as event handlersimport org.eclipse.birt.report.engine.api.script.IUpdatableDataSetRow; import org.eclipse.birt.report.engine.api.script.ScriptException; import org.eclipse.birt.report.engine.api.script.eventadapter.ScriptedDataSetEventAdapter; import org.eclipse.birt.report.engine.api.script.instance.IDataSetInstance; public class ScriptedDataSetHandler extends ScriptedDataSetEventAdapter { private int currentCount; @Override public boolean fetch(IDataSetInstance dataSet, IUpdatableDataSetRow row) { //increment the counter currentCount++; if (currentCount < 11) { //set the rows value try { row.setColumnValue("cnt", currentCount); } catch (ScriptException e) { e.printStackTrace(); } return true; } return false; } @Override public void open(IDataSetInstance dataSet) { super.open(dataSet); //initialize the count currentCount = 0; } }
Now, we need to restart Eclipse in order for it to recognize the class in our report design. Then, once we restart Eclipse, we are able to use our class and debug in Eclipse when we run the report. Next, we go into the report design, select my dsCount
as the data source, clear all of the script out of the events. Then, under the Event Handler tab in the Property Editor, we click on the Browse… button and select my class. When we run the report, it will use our new class as the event handler.
The benefit to this approach is that it is much easier to debug during development. Plus, we have full access to the Eclipse IDE, code completion, and a much cleaner IDE to develop event handlers with. The drawback is that we would need to deploy the classes with the reports and make sure they are visible in the classpath for our runtime environment.
While Event Handling can be a tricky concept in BIRT, there is something that complicates things even further—charts. Charts use a completely different Event handling mechanism as they are extended item types in BIRT. What this means is that charts are not intrinsic BIRT components. When BIRT executes a Chart, there is a marker saying "Hey, I'm an external component, I need to use the Chart Engine API to execute and Render". Because Charts do not get created until Render time, charts have only an onRender
event, with several subevents that internal to the Chart Engine API. This is a little confusing at first. So, let's go through an exercise to illustrate what this means. The following will use BIRT's Chart Event Handler to override the action for a Pie Slice. Pie slices will jump to a corresponding table bookmark for all values except the number 5.
ChartEventOverride.rptDesign
.count = 0; column = 0;
fetch
method, use the following code: id
field. For the value series, drag over the count
field. row["column"]
.So right now, we have a basic interactive chart. When we click on any of the pie slices, it executes the hyperlink and jumps to the correct section. Let's now override that behavior through the script:
onRender
event:var oldHandler; /** * Called before drawing each datapoint graphical representation or marker. * * @param dph * DataPointHints * @param fill * Fill * @param icsc * IChartScriptContext */ function beforeDrawDataPoint( dph, fill, icsc ) { //get the value of our currently rendering series //dph is of type DataPointHandler in the Chart //Engine API value = dph.getBaseValue(); //we need to use reflection to get to the data point implementation //normally this isn't necessary, but these values are //not exposed. This is complex stuff you don't need //to know under normal circumstances. But we need to //get access to our current data point so we can //override its action behavior dphClass = dph.getClass(); dataPointInstance = dphClass.getDeclaredField("dp"); dataPointInstance.setAccessible(true); implementation = dataPointInstance.get(dph); pieInstance = implementation.eContainer(); //make sure the object we are working with is a pie series impl object if (pieInstance.getClass().getName().contains("PieSeriesImpl")) { //ok, if our value for the current category equals X, then we want to do our action if ( value.equals("5")) { //save the old action. if we dont, any further actions that dont meet the criteria won't //execute. then clear the action so it wont execute oldHandler = pieInstance.getTriggers().get(0); pieInstance.getTriggers().clear(); } else { //If we cleared the triggers, we need to re-initialize the action if (pieInstance.getTriggers().size() < 1) { if (oldHandler != null) { pieInstance.getTriggers().add(oldHandler); } } } } }
As we can see, chart event handling is much more complex than regular event handling. In the example that we just saw, we had to use Java's reflection mechanism to get access to some properties that are not normally exposed to the end developer in order to change the action of an individual slice. We can also note that there is a submethod called beforeDrawDataPoint
that we are implementing. This is an internal Chart Engine API event.
In the following exercise, we are going to look at building a very complex chart interactivity example using a combination of chart interactivities, which get rendered as client side JavaScript, text elements containing client side JavaScript, and BIRT Event Handlers to replicate a chart several times for an old fashioned image swap. It will be important that we use either PNG or JPEG charts as SVG already has its own mechanisms for doing this.
interactiveChartExample.rptDesign
. ID
and an other called COUNT
.count = 0; id = 0;
fetch
event, use the following code:if (id < 20) { id++; count++; row["id"] = id; row["count"] = count; return true; } return false;
row["id"]
.<script type="text/javascript"> /** Javascript for setting up the image swap from all the chart instances **/ //Define a new array, which will contain all references to chart images //with exploded slices imageArray = new Array(); //the mainImageSrc will be a reference to a chart with no slices exploded, and //will be the default image used when no mouse over exists mainImageSrc = document.getElementById("Chart-Main").src; //we are expecting 20 chart instances, so iterate through all for (x = 0; x < 20; x++) { //current image will be prefixed by "Chart-", plus the number currentImage = document.getElementById("Chart-" + (x + 1)); //get the current image, and hide it using standard HTML style properties imageArray[x] = currentImage; currentImage.style.display = "none"; } //This function will swap an image, and gets called from a chart Interactivity function swapImage(num) { //get the main image reference. We will be swapping it out. don't worry, //the actual image is stored in mainImageSrc mainImage = document.getElementById("Chart-Main"); //swap images with the array mainImage.src = imageArray[num - 1].src; } //set back the main, unexploded pie image function showMain() { document.getElementById("Image-Main").src = mainImageSrc; } </script>
COUNT
, insert another text element, set the type to HTML, and use the following JavaScript. Again, this is client side JavaScript. It creates an HTML tag called tableAnchor
. The code will find this div
tag and will go up the parent tree to find the TBODY
tag, which is inserted when a BIRT table is rendered. This script will highlight a table row when a user hover over an area of the chart.<!-- Dummy tag to get parent nodes --> <div id="tableAnchor"></div> <script> // Get the table object itself var o = document.getElementById("tableAnchor"); while(o != null){ if (o.tagName == "TBODY") break; o = o.parentNode; } // Add the mouseover event to each of the table rows for (var i = 1; i < o.children.length; i++) { var ro = o.children[i]; ro.onmouseover = function(){highlight(this.id);swapImage(this.id);}; ro.onmouseout = function(){unhighlight(this.id);}; } // Highlight function is called from mouseove events (see above) // and also from chart mouseover events. var g_previousHighlight = ""; function highlight(category) { // Remove previous highlight (if any) if (g_previousHighlight > "") { var o = document.getElementById(g_previousHighlight); o.style.backgroundColor=""; } // Apply new highlight var o = document.getElementById(category); o.style.backgroundColor="#8080ff"; g_previousHighlight = category; } function unhighlight(category) { // Remove previous highlight (if any) if (g_previousHighlight > "") { var o = document.getElementById(g_previousHighlight); o.style.backgroundColor=""; } // Apply new highlight var o = document.getElementById(category); o.style.backgroundColor="#FFFFFF"; g_previousHighlight = category; } </script>
id
as the category definition and count
as the slice. MouseOver
. Set Action to Invoke Script
. Use the below script. Again, it is important to note that we are not using BIRT server side scripts here. These are client side, executed in the user's browser at run time. What this does is add in chart interactivity so that when we mouse over a pie chart slice, it will call the swap image and row highlighting code we defined in the two text elements.swapImage(categoryData); highlight(categoryData);
"Chart-Main"
NewChart
as the name of the Chart . initialize
event, use the following code. It is important to note that this code executes on server side, before the browser ever sees the final BIRT report. What this code does is find the single chart instance in our report and duplicate it 20 times. For each duplicate, it will explode the pie slice for the matching category name, and name it so that our swap script can find it. Remember, this code executes and is completely unaware of any of the code in either of the two text elements or in the chart interactivity.var reportDesignHandle = reportContext.getReportRunnable().getDesignHandle(); var re = reportDesignHandle.findElement("NewChart"); var pieChart = re.getReportItem().getProperty("chart.instance"); for (var x = 1; x <= 20; x++) { importPackage(Packages.org.eclipse.emf.ecore.util); var chartCopy = EcoreUtil.copy(pieChart); var outerSeries = chartCopy.getSeriesDefinitions().get(0); var innerSeries = outerSeries.getSeriesDefinitions().get(0); var pieSeries = innerSeries.getSeries().get(0); pieSeries.setExplosionExpression("valueData == " + x); pieSeries.setExplosion(5); var eih = reportDesignHandle.getElementFactory().newExtendedItem("chart-" + x, "Chart"); eih.getReportItem().setProperty("chart.instance", chartCopy); eih.setBookmark(""Chart-" + x + """); re.getContainerSlotHandle().add(eih); }