Difference between revisions of "RAP/BIRT Integration"

From Eclipsepedia

< RAP
Jump to: navigation, search
(Be aware of TSD)
(Reports)
Line 279: Line 279:
  
 
=== HTML reports ===
 
=== HTML reports ===
 +
 +
==References==
 +
* [http://eclipse.org/birt/phoenix/ BIRT Homepage]
 +
* [http://www.eclipse.org/articles/article.php?file=Article-BIRTChartEngine/index.html Using the BIRT Chart Engine in Your Plug-in]
  
 
== Examples ==
 
== Examples ==
 
[[Category:RAP]]
 
[[Category:RAP]]

Revision as of 09:20, 2 July 2009

| RAP wiki home | RAP project home |

Contents

THIS ARTICLE IS STILL IN PROGRESS - PLEASE SEND ANY FEEDBACK TO bmuskalla (at) eclipsesource.com

Introduction

Besides a rich user interaction many applications need to display a big amount of data sets as diagrams or reports as part of their applications. In order to bridge the gap the BIRT project was created as part of the eclipse ecosystem. BIRT is an open source Eclipse-based reporting system that integrates with your Java/J2EE application to produce compelling reports. That BIRT integrates well with classic RCP applications is a well known fact. But the need for rich internet applications is still growing. And here the RAP comes into play. As a platform for developing Web 2.0 applications with the same patterns as for RCP it paves the way for single sourcing applications running on both platforms. In this talk we will show how to integrate diagrams and reports known from BIRT into RAP applications. Topics covered include how to setup the environment to let BIRT and RAP play well together. In addition we will give advices how to use the reports inside RAP applications and which problems may arise. As a final outcome of we will know everything to bring reporting capabilities into RAP applications.

Target

The first thing we need to do is to mix up a target which contains both runtimes: RAP and BIRT. We need to download both runtimes first:

When merging the two runtimes together, we should be sure that no bundle is available twice as this results in same strange errors during runtime. So first step is to remove all the duplicate bundles.

Fixing ICU

RAP provides them in version 4, the BIRT runtime needs them in version 3. So be sure that you include version 3 of the ICU bundles in order to get the constraints resolved for both runtimes.

Target.png

Charts

Under normal circumstances you create your chart object, fill it with some data and draw it on some surface. As RAP does not support any drawing capabilities we need to adjust the last step a little bit.

Create the chart object

Creating a new chart is done exactly as you would do it in the SWT/RCP case. For more information how to use the Chart API of BIRT, please refer to one of the BIRT tutorials (eg. Using the BIRT Chart Engine in Your Plug-in We only included an example here for the sake of completeness.

private Chart createBarChart() {
    ChartWithAxes chart = ChartWithAxesImpl.create();
    chart.setDimension( ChartDimension.TWO_DIMENSIONAL_WITH_DEPTH_LITERAL );
    Plot plot = chart.getPlot();
    plot.setBackground( ColorDefinitionImpl.WHITE() );
    plot.getClientArea().setBackground( ColorDefinitionImpl.WHITE() );
    Legend legend = chart.getLegend();
    legend.setItemType( LegendItemType.CATEGORIES_LITERAL );
    legend.setVisible( true );
    Text caption = chart.getTitle().getLabel().getCaption();
    caption.setValue( "Distribution of Chart Column Heights" );
    Axis xAxis = ( ( ChartWithAxes )chart ).getPrimaryBaseAxes()[ 0 ];
    xAxis.getTitle().setVisible( true );
    xAxis.getTitle().getCaption().setValue( "" );
    Axis yAxis = ( ( ChartWithAxes )chart ).getPrimaryOrthogonalAxis( xAxis );
    yAxis.getTitle().setVisible( true );
    yAxis.getTitle().getCaption().setValue( "" );
    yAxis.getScale().setStep( 1.0 );
    TextDataSet categoryValues = TextDataSetImpl.create( new String[]{
      "short", "medium", "tall"
    } );
    Series seCategory = SeriesImpl.create();
    seCategory.setDataSet( categoryValues );
    SeriesDefinition sdX = SeriesDefinitionImpl.create();
    sdX.getSeriesPalette().shift( 1 );
    xAxis.getSeriesDefinitions().add( sdX );
    sdX.getSeries().add( seCategory );
    NumberDataSet orthoValuesDataSet1 = NumberDataSetImpl.create( new double[]{
      1, 2, 3
    } );
    BarSeries bs1 = ( BarSeries )BarSeriesImpl.create();
    bs1.setDataSet( orthoValuesDataSet1 );
    SeriesDefinition sdY = SeriesDefinitionImpl.create();
    yAxis.getSeriesDefinitions().add( sdY );
    sdY.getSeries().add( bs1 );
    return chart;
  }

Use the right renderer

Instead of using the SWT renderer, we just use a simple file renderer (eg. PNG) to draw the chart. In RAP enviroments we should never use the org.eclipse.birt.chart.device.swt. Instead we can use the simple image renderes found in org.eclipse.birt.chart.device.extension.

Here you can see the dependencies of one of the example projects:

Birt dependencies.png

The most important items are:

  • org.eclipse.birt.chart.engine - The chart engine itself
  • org.eclipse.birt.chart.engine.extension - All chart types
  • org.eclipse.birt.chart.device.extension - The image file renderers

Display the chart

As mentioned above we need a way to display the chart. Here are the steps we need to do:

  • Generate the chart with a image renderer
  • Create a SWT image object from the file
  • Display the image in the UI

To encapsulate the problem of displaying charts in the UI we create a ChartCanvas to handle all this stuff.

/*******************************************************************************
 * Copyright (c) 2009 EclipseSource and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     EclipseSource - initial API and implementation
 ******************************************************************************/
package org.eclipse.rap.birt.charts;
 
import java.io.File;
import java.io.FileInputStream;
 
import org.eclipse.birt.chart.device.IDeviceRenderer;
import org.eclipse.birt.chart.factory.GeneratedChartState;
import org.eclipse.birt.chart.factory.Generator;
import org.eclipse.birt.chart.model.Chart;
import org.eclipse.birt.chart.model.attribute.Bounds;
import org.eclipse.birt.chart.model.attribute.impl.BoundsImpl;
import org.eclipse.birt.chart.util.PluginSettings;
import org.eclipse.rwt.graphics.Graphics;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
 
public class ChartCanvas extends Canvas {
 
  private Label chartLabel;
  private Chart chart;
 
  public Chart getChart() {
    return chart;
  }
 
  public void setChart( Chart chart ) {
    this.chart = chart;
  }
 
  public ChartCanvas( Composite parent, int style ) {
    super( parent, style );
    setLayout( new FillLayout() );
    chartLabel = new Label( this, SWT.NONE );
    chartLabel.setToolTipText( "Chart" );
    addControlListener( new SmartControlAdapter() {
 
      protected void handleControlResized( ControlEvent event ) {
        try {
          updateChart();
        } catch( Exception e ) {
          e.printStackTrace();
        }
      }
    } );
  }
 
  private void updateChart() {
    try {
      drawChart( chart );
    } catch( Exception e ) {
      e.printStackTrace();
    }
  }
 
  private void drawChart( Chart chart ) throws Exception {
    Point size = getSize();
    if( !isCached( chart, size ) ) {
      IDeviceRenderer render = null;
      PluginSettings ps = PluginSettings.instance();
      render = ps.getDevice( "dv.PNG" );
      Bounds bounds = BoundsImpl.create( 0, 0, size.x, size.y );
      int resolution = render.getDisplayServer().getDpiResolution();
      bounds.scale( 72d / resolution );
      GeneratedChartState state;
      Generator gr = Generator.instance();
      state = gr.build( render.getDisplayServer(),
                        chart,
                        bounds,
                        null,
                        null,
                        null );
      File tmpFile = null;
      tmpFile = File.createTempFile( "birt" + chart.hashCode(), "_"
                                                                + size.x
                                                                + "_"
                                                                + size.y );
      render.setProperty( IDeviceRenderer.FILE_IDENTIFIER, tmpFile );
      gr.render( render, state );
      Image img;
      FileInputStream inputStream = null;
      try {
        inputStream = new FileInputStream( tmpFile );
        img = Graphics.getImage( tmpFile.getName(), inputStream );
         tmpFile.delete();
      } finally {
        if( inputStream != null ) {
          inputStream.close();
        }
      }
      if( img != null ) {
        chartLabel.setImage( img );
      } else {
        chartLabel.setText( "Chart generation failed!" );
      }
    }
  }
 
  private boolean isCached( Chart chart, Point size ) {
    // should be implemented by using a useful cache implementation
    // depending on the use case it may be enough to just cache images
    // based on the chart object, if the chart is dynamic you need to
    // implement another strategy
    return false;
  }
}

The drawChart method is the most interesting part of the while story. It builds the chart to display trough the BIRT chart engine first and uses the dv.PNG renderer to render the chart into a .png file. Afterwards we create an instance of Image by utilzing the data stream of the image file. Finally the image is set as image of a SWT label. This approach to use a label is that the layout managers can determinate the right sizes for layouting the rest of the UI correctly.

Caching the images

As you may have noticed already, RAP caches all images once initialized. Be aware of this fact as calling

Graphics.getImage( tmpFile.getName(), inputStream );

with the same name will not update the image with the contents of the InputStream.

As rendering charts can be a quite resource-intensive operation you should think about strategies for caching the generated charts. Depending on your use case it may be suffice to only generate each chart instance once. If you're charts are changing dynamically you should also think about a way to regenerate the chart.

Be aware of TSD

The text size determination, short TSD, of RWT is normally nothing you need to care about. In the case of generating images on the fly depending on the size of the surround control we need to be a little carefull. In order to relayout the whole UI correctly after determinating the real text sizes, RWT fires two resize events with a temporary size. As we do no want to generate images for this case we need work around this by using a specialized version of a ControlAdapter as you can see here:

package org.eclipse.rap.birt.charts;
 
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
 
/**
 * This is an extended version of the regular ControlAdapter.
 * It is used to get only the real resize events and excludes all resize
 * events of the text size determination.
 * 
 * See http://wiki.eclipse.org/WidgetToolkit#Challenges
 */
public abstract class SmartControlAdapter extends ControlAdapter {
 
  public void controlResized( ControlEvent e ) {
    Shell shell = ( ( Control )e.widget ).getShell();
    Point shellSize = shell.getSize();
    Point previousSize = ( Point )e.widget.getData( "previousShellSize"
                                                    + this.hashCode() );
    e.widget.setData( "previousShellSize" + this.hashCode(), shellSize );
    if( previousSize != null ) {
      int dx = Math.abs( Math.abs( shellSize.x - previousSize.x ) - 1000 );
      int dy = Math.abs( Math.abs( shellSize.y - previousSize.y ) - 1000 );
      if( ( dx <= 2 || dy <= 2 ) ) {
        // This came from the TextSizeDetermination
        return;
      }
    }
    handleControlResized( e );
  }
 
  protected abstract void handleControlResized( ControlEvent e );
}

Reports

PDF reports

HTML reports

References

Examples