Understanding when chart events fire
The chart engine generator processes charts in the following four phases in the BIRT reporting environment:
*Prepare
*Data binding
*Build
*Render
These phases correspond to the following methods called when the BIRT chart engine generates a chart:
*Generator.prepare( )
*Generator.bindData( )
*Generator.build( )
*Generator.render( )
The following sections describe these phases and provide more information about the context in which chart events fire.
Prepare phase
The prepare phase sets up the chart script context. All chart event handlers use this script context object. This phase sets the class loader for the scripting environment to the same class loader that the BIRT scripting environment uses. This phase does not trigger any event handlers.
Data binding phase
The data binding phase prepares all data sets associated with a series, which is a set of values plotted in a chart. This preparation performs the following two tasks:
*Retrieving the data values provided by a dynamic data set
*Creating one or more run-time series from a series definition object
The chart engine supports both static and dynamic data. Static data does not need a data binding phase because the data is already prepared in the necessary format. The chart engine also provides extension points to support custom data set types.
An object implementing the org.eclipse.birt.chart.model.data.DataSet interface provides static data. For each data type, an implementation class having a static create method initializes the run-time series data values. For example, the following code creates a static NumberDataSet for a series:
NumberDataSet seriesOneValues =
NumberDataSetImpl.create( new double[ ]{ 15, 23, 55, 76 } )
Sources of dynamic data include BIRT data sets and java.sqlResultSet objects. BIRT uses a series definition object to define the dynamic data a series contains. For example, row["month"] populates a series with the month column from a BIRT data set. The data binding phase uses the series definition object to create a run-time series bound to a static, typed chart data set. BIRT supports defining groups and sorting in a series definition using dynamic data. When using these features, BIRT creates additional run‑time series as necessary, each with its own chart data set.
To get the run-time series for a series, call the getRunTimeSeries( ) method for the series definition object. Listing 39‑4 gets a run-time series and sets the first bar series to have a riser type of triangle.
Listing 39‑4 Getting a run-time series
function beforeGeneration( chart, icsc )
{
importPackage(
Packages.org.eclipse.birt.chart.model.attribute );
 
var xAxis = chart.getBaseAxes( )[0];
var yAxis = chart.getOrthogonalAxes( xAxis, true )[0]
var seriesDef = yAxis.getSeriesDefinitions( ).get( 0 )
var runSeries = seriesDef.getRunTimeSeries( );
var firstRunSeries = runSeries.get( 0 );
firstRunSeries.setRiser( RiserType.TRIANGLE_LITERAL );
}
The binding phase triggers the following event types:
*beforeDataSetFilled
*afterDataSetFilled
beforeDataSetFilled event
The chart engine calls the beforeDataSetFilled event handler before creating and populating the run-time series chart data set. This event handler receives two parameters: an IDataSetProcessor implementation and the chart script context.
The IDataSetProcessor implementation must contain a populate method that creates one of the static chart data set types. The populate method is called just after this event fires. The DataSetProcessorImpl class, which contains empty methods, is the default implementation of IDataSetProcessor. Extend this interface using the chart extension points to provide your own data set processor to add calls to the beforeDataSet event.
afterDataSetFilled event
The chart engine calls the afterDataSetFilled event handler after creating and populating the run-time series chart data set. This event handler receives three parameters: the chart script context, the run-time series, and the populated static, typed chart data set.
The series object is an implementation of the run-time series type being created. This object is a SeriesImpl object for the category series and a chart‑specific type for other series. For example, in a bar chart, the series instance is a BarSeriesImpl object. Get the class type to determine the current series type, as shown in Listing 39‑5. In a JavaScript event handler, casting to a specific type is not necessary. In a Java event handler, cast to a specific type if the method to call in the series object is not defined by the Series interface.
This event fires for each run-time series. To identify the triggering series, use the series identifier, as shown in the following code:
if ( series.getSeriesIdentifier( ) == "series one" ) {
...
}
By default, the series identifier value is undefined. Set the identifier in the Chart Builder wizard under series title. If a series definition creates more than one run‑time series, this identifier is the same for all these generated run‑time series.
Listing 39‑5 Checking the class type to determine the type of series
function afterDataSetFilled( series, dataSet, icsc )
{
importPackage( Packages.java.io );
importPackage(
Packages.org.eclipse.birt.chart.model.type.impl );
if ( series.getClass( ) == LineSeriesImpl ) {
series.getLineAttributes( ).setThickness( 5 );
}
if ( series.getClass( ) == BarSeriesImpl ) {
...
}
}
The chart data set passed to the method is the chart data set object for the current series. The chart data set values are available for manipulation at this point. For example, Listing 39‑6 replaces null values and values below 24,000 in the data set with 0.
Listing 39‑6 Replacing null values in a data set
function afterDataSetFilled( series, dataSet, icsc )
{
var list = dataSet.getValues( );
for ( i=0; i<list.length; i=i+1 ) {
if ( ( list[i] == null ) || ( list[i] < 24000 ) ) {
list[i]= 0;
}
}
}
Building phase
Do not confuse the chart building phase with the BIRT report generation phase, which occurs during rendering. The chart building phase starts when a chart model binds to a data set to produce a GeneratedChartState object. This object contains most of the information required to render the chart. A chart consists of blocks, which are the rectangular sub-regions of the chart that act as containers for specific chart information. The chart model contains the following blocks:
*Outermost block that contains all other blocks
*Title block for the chart title
*Plot block to render the series and any axes
*Legend block to display the legend
The building phase calculates the following values:
*Bounds for all blocks
*Minimum, maximum, and scale values for any axes
The building phase performs the following tasks:
*Initializes all series renderers
*Creates rendering hints for the legend, each run-time series, and the data points in each series
*Stores all calculations in the chart model in a GeneratedChartState object
The building phase triggers the following script events:
*beforeGeneration
*afterGeneration
beforeGeneration event
The chart engine calls the beforeGeneration event handler after completing the data binding phase. This event handler receives two parameters: the chart model bound to data from the binding phase and the chart script context.
The chart model is an instance of a subclass of either ChartWithAxesImpl or ChartWithoutAxesImpl. To implement a Java event handler, it may be necessary to cast to the specific implementation of the chart interface. You can get the chart’s class to determine the chart type. For example, to write a beforeGeneration event handler for a pie chart to explode slices that contain large values, use script similar to the example in Listing 39‑7. The example shows the values series for a pie chart nesting under a second-level series definition. The category series nests below the top-level series definition.
Listing 39‑7 Modifying pie slice properties
function beforeGeneration( chart, icsc )
{
importPackage( Packages.org.eclipse.birt.chart.model.impl );
importPackage(
Packages.org.eclipse.birt.chart.model.type.impl );
if ( chart.getClass( ) == ChartWithAxesImpl ) {
//Do something if a line chart...
}
 
if ( chart.getClass( ) == ChartWithoutAxesImpl ) {
seriesDef = chart.getSeriesDefinitions( ).get( 0 );
catRunSeries = seriesDef.getRunTimeSeries( );
// Pie Charts use nested series definitions for value series
valSeriesDef = seriesDef.getSeriesDefinitions( ).get( 0 );
valRunSeries = valSeriesDef.getRunTimeSeries( ).get( 0 );
if ( valRunSeries.getClass( ) == PieSeriesImpl ) {
valRunSeries.setExplosion( 10 );
valRunSeries.setExplosionExpression("valueData > 15000");
}
}
}
afterGeneration event
The chart engine calls the afterGeneration event handler after processing all calculations, just prior to entering the rendering phase. This event handler receives two parameters: the GeneratedChartState object and the chart script context. Modifications to the chart can be made by using the GeneratedChartState object to retrieve the chart model. For example, Listing 39‑8 adds a blue border around the plot area.
Listing 39‑8 Setting plot area properties
function afterGeneration( gcs, icsc )
{
importPackage(
Packages.org.eclipse.birt.chart.model.attribute );
importPackage(
Packages.org.eclipse.birt.chart.model.attribute.impl );
 
var outline = gcs.getChartModel( ).getPlot( ).getOutline( );
outline.setColor( ColorDefinitionImpl.BLUE( ) );
outline.setStyle( LineStyle.SOLID_LITERAL );
outline.setVisible( true );
}
Rendering phase
The rendering phase takes the GeneratedChartState object and renders the chart. This phase also sets up chart interactivity. The rendering phase uses the following main object types to render a chart:
*Device renderer
*Display server
*Model renderers
The device renderer object implements the IDeviceRenderer interface, which provides methods for drawing primitives such as drawPolygon, drawRectangle, drawText, and fillArc. The chart engine provides device renderers for Standard Widget Toolkit (SWT) and Swing. The device renderers for BMP, JPEG, PDF, PNG, and SVG formats extend the Swing renderer. The chart engine provides an extension point to extend this list.
The display server provides generic services to the device renderer for such operations as getting the dots-per-inch (DPI) resolution and providing text metrics. A display server implements the IDisplayServer interface. The chart engine provides implementations for SWT and Swing.
The display server links to a device renderer based on an Eclipse extension point entry for the renderer. All Swing-related renderers use the Swing display server and the SWT device renderer uses the SWT display server. The chart engine provides an extension point to extend the list of display servers.
Model renderers render a specific series and use the device renderer to render the actual graphic primitives that make up the chart series data points. All chart series renderers extend from the BaseRenderer or AxesRenderer abstract classes and implement the ISeriesRenderer interface, which provides the methods listed in Table 39-6.
Table 39-6 ISeriesRenderer methods
Name
Called
Performs
compute( )
Before the end of the building phase
Prerendering calculations
renderLegendGraphic( )
During the render phase
Drawing the graphic markers in the legend
renderSeries( )
During the render phase
Drawing the series using the provided device renderer and series rendering hints
Table 39-7 lists the series-specific renderers. The chart engine provides an extension point to extend this list.
Table 39-7 Series-specific renderers
AxesRenderer
BaseRenderer
Bar
Dial
Bubble
Pie
Scatter
 
Line
 
Difference
 
Gantt
 
Stock
 
The rendering phase loops through and renders all the run-time series for a chart. In Listing 39‑9, pseudo-code shows the order of the rendering events. The axis rendering code applies only to chart types containing axes.
Listing 39‑9 Pseudo-code illustrating the order of the rendering operations
Loop for all renderers
Render Main block - Only on first series
Render Title block - Only on first series
Render Plot block
Render Background - Only on first series
Render Axis structure - Only on first series
Render Series (Series-specific, such as Line or Bar)
Render Axis labels - Only on last series
Render Legend block (call to specific series renderer for graphic) - Only on last series
End Loop
Rendering phase script events
Most chart events fire during the rendering phase. Some events are specific to a particular chart type and are called for that series renderer only. In Listing 39‑10, pseudo-code expands the code in Listing 39‑9 to show the triggering order of most events. The order varies slightly depending on the series renderer. This section does not describe every event in detail. The common objects used in each script are presented in the following sections.
Listing 39‑10 Pseudo-code showing the triggering order of rendering events
beforeRendering
Loop all run-time series
If first series
beforeDrawBlock - Main block
afterDrawBlock - Main block
beforeDrawBlock - Title block
afterDrawBlock - Title block
beforeDrawBlock - Plot block
 
Loop all marker ranges
beforeDrawMarkerRange
afterDrawMarkerRange
End Loop
 
Loop all marker lines
beforeDrawMarkerLine
afterDrawMarkerLine
End Loop
 
beforeDrawSeries - Category series
afterDrawSeries - Category series
afterDrawBlock - Plot Block
End If
 
beforeDrawBlock - Plot Block
beforeDrawSeries - For specific series
 
Loop all data points - For specific series
beforeDrawDataPoint
afterDrawDataPoint
End Loop
Loop all data point labels - For specific series
beforeDrawDataPointLabel
afterDrawDataPointLabel
End Loop
beforeDrawFittingCurve
afterDrawFittingCurve
afterDrawSeries
 
If last series and chart contains axes
Loop for each axis
beforeDrawAxisLabel
afterDrawAxisLabel
End Loop
beforeDrawAxisTitle
afterDrawAxisTitle
End If
 
afterDrawBlock - Plot Block
 
If last series
beforeDrawBlock - Legend
Loop all legend entries
beforeDrawLegendItem
afterDrawLegendItem
End Loop
afterDrawBlock - Legend
End If
End Loop
afterRender
Rendering blocks
Charts are composed of blocks. These blocks are rectangular sub-regions that contain sub-components of the chart. For example, the plot block contains the rendering of data points and the title block contains the chart title. The beforeDrawBlock event fires before rendering each block. Use this event handler to modify a block’s properties before the chart renders.
The beforeDrawBlock and afterDrawBlock events fire once for the main, title, and legend blocks and multiple times for the plot block. These two events both receive two parameters: a BlockImpl object and the chart script context.
The block class provides methods for determining which block triggered the event. Both event handlers support manipulating the block, for example setting the anchor points, background fill, or outline, by using the block’s methods and properties. Listing 39‑11 shows how to set the anchor point for the legend in a legend block.
Listing 39‑11 Setting the anchor point for the legend in a legend block
function beforeDrawBlock( block, icsc )
{
importPackage(
Packages.org.eclipse.birt.chart.model.attribute.impl );
importPackage(
Packages.org.eclipse.birt.chart.model.attribute );
 
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
...
}
}
Rendering series
The beforeDrawSeries and afterDrawSeries events fire prior once for every run‑time series in a chart. The event handlers for these events receive a Series object, an ISeriesRenderer object, and the chart script context as parameters.
To modify a series, use an event handler for the beforeDrawSeries event. In Listing 39‑12, the event handler sets the visibility for the series identifier, SalesBalance, to false, so the series does not render in the chart.
Listing 39‑12 Using the beforeDrawSeries event to set visibility in a chart
function beforeDrawSeries( series, seriesRenderer, context )
{
if ( series.getSeriesIdentifier() == "SalesBalance" )
{
series.setVisible( false );
}
}
Rendering data points and data point labels
The beforeDrawDataPoint, afterDrawDataPoint, beforeDrawDataPointLabel, and afterDrawDataPointLabel events each fire once for every run-time series value.
The event handlers for all of these events receive a DataPointHints object and the chart script context as parameters. The building phase creates a DataPointHints object for every run-time series value. The series renderer renders this object using the object’s data. All these event handlers support accessing and, for some properties, manipulating the DataPointHints object using the object’s methods. Use DataPointHints property values to examine or modify the chart before rendering a data point. For example, the script in Listing 39‑13 prints the x‑ and y‑locations of a data point in the plot bounds, the category and series values, and the size of the bar for each value in a run‑time series of a two-dimensional bar chart.
Listing 39‑13 Displaying x‑ and y‑locations in a two-dimensional bar chart
function beforeDrawDataPoint( dph, fill, icsc )
{
importPackage( Packages.java.io );
 
out = new PrintWriter( new FileWriter(
"C:/data/datapoints.txt", true ) );
out.println(
"BaseValue X-Axis Value " + dph.getBaseValue( ) );
out.println(
"Orthogonal Y-Axis Value " + dph.getOrthogonalValue( ) );
out.println( " X location " + dph.getLocation( ).getX( ) );
out.println( " Y Location " + dph.getLocation( ).getY( ) );
out.println( " Size " + dph.getSize( ) );
out.close( );
}
The beforeDrawDatapoint and afterDrawDatapoint event handlers receive two further parameters: a Fill object and the chart script context. Subclasses of Fill provide support for a solid color, a gradient color, a pattern, or an image to display for the data point. For example, using an instance of the ColorDefinitionImpl class, which implements the Fill interface, supports setting a solid color for a data point.
These events fire before and after rendering a data point in a chart. Use these events to modify how the data point is rendered. In Listing 39‑14, the event handler renders a bar chart with positive and negative bars and colors the negative bars red. The beforeDrawDataPoint event handler retrieves the value and determines if the bar is negative. If the bar is negative, the event handler saves the current fill color. The afterDrawDataPoint uses these settings to restore the color, setting the bar color back to red.
Listing 39‑14 Rendering a bar chart with positive and negative bars
previousFill = null;
function beforeDrawDataPoint( dph, fill, icsc )
{
val = dph.getOrthogonalValue( );
if ( val < 0 ){
previousFill = new Object( );
previousFill.r = fill.getRed( );
previousFill.g = fill.getGreen( );
previousFill.b = fill.getBlue( );
fill.set( 255, 0, 0 );
}
else{
previousFill = null;
}
}
 
function afterDrawDataPoint( dph, fill, icsc )
{
if ( previousFill != null ){
fill.set( previousFill.r, previousFill.g, previousFill.b);
}
}
The beforeDrawDataPointLabel and afterDrawDataPointLabel event handlers receive the DataPointHints object and the chart script context and one further parameter: a Label object.
The Label object is an instance of the LabelImpl class and provides many methods for altering the label. For example, modify the label caption or color based on DataPointHints property values. Listing 39‑15 shows how to change the appearance and text of a data point label.
Listing 39‑15 Changing the values for a label on a data point
function beforeDrawDataPointLabel(dph, label, icsc)
{
value = dph.getOrthogonalValue( ) .doubleValue( );
if ( ( value >= 0 ) && ( value < 500 ) ) {
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( 22 );
label.getCaption( ).getFont( ).setName( "Arial" );
label.getOutline( ).setVisible( true );
label.getOutline( ).setThickness( 3 );
}
else if ( value >= 500 ) {
label.getCaption( ).getFont( ).setRotation( 5 );
label.getOutline( ).setVisible( true );
}
else if ( value < 0 ) {
label.getCaption( ).getColor( ).set( 0, 208, 32 );
}
}
Rendering axes
The beforeDrawAxisLabel, afterDrawAxisLabel, beforeDrawAxisTitle, afterDrawAxisTitle, beforeDrawMarkerLine, afterDrawMarkerLine, beforeDrawMarkerRange, and afterDrawMarkerRange events each fire at least once for every axis in a chart with axes.
The event handlers for all of these events receive two parameters: an axis object and the chart script context. The axis object is an instance of the AxisImpl class and provides many methods and properties for use in the scripting environment. These features support functionality such as retrieving the run-time series associated with the axis and setting min, max, and scale values.
To work with an axis object, first determine which axis it is by getting the axis type or the axis title caption, as shown in Listing 39‑16. The getType( ) method used in this example returns one of the following values:
*DATE_TIME_LITERAL
*LINEAR_LITERAL
*LOGARITHMIC_LITERAL
*TEXT_LITERAL
If a chart has multiple y-axes all of a specific type such as LINEAR_LITERAL, use the axis title to determine the current axis. The title need not be visible, but the value must exist.
Use the beforeDrawAxisTitle and beforeDrawAxisLabel events to modify the axis title and label elements respectively. Listing 39‑16 shows how to check the axis title caption and the axis type to modify the title colors.
Listing 39‑16 Checking the axis type to modify the title colors
function beforeDrawAxisTitle( axis, label, icsc )
{
importPackage(
Packages.org.eclipse.birt.chart.model.attribute.impl );
importPackage(
Packages.org.eclipse.birt.chart.model.attribute );
 
if ( axis.getTitle( ).getCaption( ).getValue( ) ==
"myYaxisTitle" ) {
label.getCaption( ).setColor( ColorDefinitionImpl.BLUE( ) );
}
 
if ( axis.getType( ) == AxisType.DATE_TIME_LITERAL ) {
label.getCaption( ).setColor( ColorDefinitionImpl.RED( ) );
}
}
The beforeDrawAxisLabel event fires for each label rendered on a chart axis. Provide an event handler for this event to modify the labels. In Listing 39‑17, the modified labels provide an abbreviated number format, making the chart more readable.
Listing 39‑17 Modifying labels in a beforeDrawAxisLabel event
function beforeDrawAxisLabel( axis, label, context )
{
value = label.getCaption( ).getValue( );
if ( value >= 1000 && value < 1000000 )
value = value/1000 + "k";
else if ( value >= 1000000 )
value = value/1000000 + "M";
 
label.getCaption( ).setValue( value );
}
When the beforeDrawAxisXXX events fire, the axis lines and grids are already rendered, so, to modify the basic properties of an axis, make these changes in an earlier event. Listing 39‑18 shows how to modify an axis using this approach. The code retrieves all the axes and checks that the x-axis is a date-and-time axis. After the verification, the code specifies the format for the axis labels.
Listing 39‑18 Retrieving the axis to modify axis properties
function beforeGeneration( chart, icsc )
{
importPackage(
Packages.org.eclipse.birt.chart.model.attribute.impl );
importPackage(
Packages.org.eclipse.birt.chart.model.attribute );
 
// The chart model supports only one base axis
xAxis = chart.getBaseAxes( )[ 0 ];
yAxis = chart.getOrthogonalAxes( xAxis, true )[ 0 ];
yAxisNumber2 = chart.getOrthogonalAxes( xAxis, true )[ 1 ];
if ( xAxis.getType( ) == AxisType.DATE_TIME_LITERAL ) {
xAxis.setFormatSpecifier(
JavaDateFormatSpecifierImpl.create( "MM/dd/yyyy" ) );
}
}
Rendering legend items
The beforeDrawLegendItem and afterDrawLegendItem events fire once for each legend entry while the legend is rendering. The event handlers receive three parameters: an instance of the LegendEntryRenderingHints class, an instance of the BoundsImpl class, and the chart script context.
The LegendEntryRenderingHints object contains the following items:
*A data index, the index for the entry being rendered
*A Fill object, defining the color, image, or pattern filling the legend entry graphic
*A Label object, an instance of the LabelImpl class, representing the label for the legend entry
*A value label, an instance of the LabelImpl class, used when coloring the legend by values and the show values check box is selected
The BoundsImpl object contains the top, left, width, and height values for the legend entry graphic. If you change these values, be careful to avoid making the legend entry bounds wider or taller than the legend block. Listing 39‑19 shows how to make modifications to the legend entry label and the graphic size. Making these changes in the afterDrawLegendItem function has no effect, as the entry is already rendered.
Listing 39‑19 Make modifications to the legend entry label
function beforeDrawLegendItem( lerh, bounds, icsc )
{
importPackage(
Packages.org.eclipse.birt.chart.model.attribute.impl );
 
label = lerh.getLabel( );
labelString = label.getCaption( ).getValue( );
 
if ( labelString == "true" ) {
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 );
 
var graphicwidth = bounds.getWidth( );
var graphicheight = bounds.getHeight( );
var chartModel = icsc.getChartInstance( );
var legendBounds = chartModel.getLegend( ).getBounds( );
var legendInsets = chartModel.getLegend( ).getInsets( );
 
// This test does not account for the label text
var availablewidth = legendBounds.getWidth( ) -
legendInsets.getLeft( ) - legendInsets.getRight( );
if ( availablewidth > ( graphicwidth + 15 ) ) {
bounds.setWidth( graphicwidth + 15 );
bounds.setHeight( graphicheight + 15 );
}
}
}