BIRT/FAQ/Charts

From Eclipsepedia

< BIRT‎ | FAQ
Jump to: navigation, search

Back to BIRT FAQ Index

Contents

General

Q: Can I use BIRT's chart engine in my own application?

Yes. A primary goal of the Eclipse Chart Engine project is to provide a package that can be used both with the BIRT reporting components as well as standalone.

Chart Engine - Extension

Q: How can the chart engine be extended to support my custom requirements?

The Chart Engine has a number of extension points that can be used to extend the basic charting fraemwork. The following document provides a description of this process. A basic knowledge of plug-in development, the GEF, and the EMF will be helpful when reviewing this document.

Chart Engine Extension

Output Formats

Q: What output formats does the chart engine support?

Internally, the charting engine writes to device independent primitives. This means that any output format may be plugged in by writing a device extension. A device refers to an implementation that translates rendering primitives into a specific file format. Release 1.0 provides the following:

  • 24-bit images
  • 8-bit images
  • SWT graphics (GC)
  • SWING graphics (Graphics2D)
  • SVG

In addition, custom extensions may be written to support additional file formats.

The device rendering framework is tailored to suit the needs of rendering a comprehensive set of graphics primitives, elaborate text layout (focused on typical usage of text rendering in charts), and event handling. The framework is optimized for performance.

Q: This sounds a lot like Draw2d in the GEF toolkit. Why is the chart project creating "yet another API"?

An important goal for the charting engine is that it needs to work in a pure J2EE environment and that it be portable. Creating a dependency on Draw2D would in turn create a dependency on SWT and OS dependent libraries which doesn't work well for users who would like to deploy their reports on an application server. In addition, Draw2D API is very restrictive in that it uses ?integers? in its co-ordinate system which causes rounding off errors and erroneous output. The chart primitive rendering layer of abstraction provides a device-independent floating point co-ordinate system for which primitive instructions are written to individual devices (of which, SWT is one). Since SWT (and draw2D) does not support floating point math in rendering arguments, approximations would have to be performed at several stages in chart generation and rendering resulting in inaccurate output.

For your reference, here's how the 'drawLine' API has been implemented using Draw2D's org.eclipse.draw2d.ScaledGraphics' implementation:

public void drawLine(int x1, int y1, int x2, int y2)
{
   graphics.drawLine((int)Math.floor((double)x1 * zoom + 
fractionalX), (int)Math.floor((double)y1 * zoom + fractionalY), 
       (int)Math.floor((double)x2 * zoom + fractionalX), 
       (int)Math.floor((double)y2 * zoom + fractionalY));
}

where fractionalX and fractionalY define linear translation and 'zoom' defines a scaling factor but as you can see, resulting co-ordinates are truncated before they are plotted.

Another drawback with using pure Draw2D/SWT API is that it is limited in its feature set and doesn't generically support certain advanced features such as:

  • Text anti-aliasing
  • Fractional font-metrics
  • Primitive support for text rotation
  • Defining custom shapes using a combination of polygons and curves

So, to summarize, the design objectives for the rendering API are twofold:

  • Create a clear abstraction layer between the core charting engine and the rendering API such that charts may be generated offscreen using high precision metrics and transformations and the device extensions (for specific output types) could translate the primitive instructions as appropriate.
  • Implement device/OS specific rendering toolkits as pluggable/removable extensions that implement our extension points allowing a great deal of flexibility in deployment configurations.

This allows the charting engine to write out SWT charts within the eclipse environment and non-SWT charts (possibly SWING or completely bypassing a graphics toolkit and writing to a vector based file format e.g. SVG) outside of the eclipse environment.

Q: Does the chart package output PDF?

Not in release 1. However, you could easily write your own output format PDF. Refer to the extension points documentation for details.

Q: What chart output formats are supported within reports?

For release 1, charts will be embedded as a scalar bitmapped image. This will be enhanced in a subsequent release to embed vector charts (SVG, PDF, etc).

Chart Programming

Q: How can I create a line chart programmatically?

Here's how you could create a sample line chart that relies on the chart library model (either as source or as JARs in your environment):

/**
 * Creates a line chart model as a reference implementation
 * 
 * @return An instance of the simulated runtime chart model (containing filled datasets)
 */
public static final Chart createSimpleLineChart()
{
   ChartWithAxes cwaBar = ChartWithAxesImpl.create();
   cwaBar.getBlock().setBackground(ColorDefinitionImpl.WHITE());
   Plot p = cwaBar.getPlot();
   p.getClientArea().setBackground(
   	ColorDefinitionImpl.create(255, 255, 225));
   cwaBar.getTitle().getLabel().getCaption().
   	setValue("Sample Line Chart");

   Legend lg = cwaBar.getLegend();
   LineAttributes lia = lg.getOutline();
   lg.getText().getFont().setSize(16);
   lia.setStyle(LineStyle.SOLID_LITERAL);
   lg.getInsets().set(10, 5, 0, 0);
   lg.getOutline().setVisible(false);
   lg.setAnchor(Anchor.NORTH_LITERAL);

   Axis xAxisPrimary = cwaBar.getPrimaryBaseAxes()[0];
   xAxisPrimary.setType(AxisType.TEXT_LITERAL);
   xAxisPrimary.getMajorGrid().setTickStyle(TickStyle.BELOW_LITERAL);
   xAxisPrimary.getOrigin().setType(IntersectionType.VALUE_LITERAL);
   xAxisPrimary.getTitle().setVisible(false);

   Axis yAxisPrimary = cwaBar.getPrimaryOrthogonalAxis(xAxisPrimary);
   yAxisPrimary.getMajorGrid().setTickStyle(TickStyle.LEFT_LITERAL);
   yAxisPrimary.setPercent(true);

   Vector vs = new Vector();
   vs.add("one");
   vs.add("two");
   vs.add("three");

   ArrayList vn1 = new ArrayList();
   vn1.add(new Double(25));
   vn1.add(new Double(35));
   vn1.add(new Double(-45));

   TextDataSet categoryValues = TextDataSetImpl.create(vs);
   NumberDataSet orthoValues1 = NumberDataSetImpl.create(vn1);

   // CREATE THE CATEGORY SERIES
   Series seCategory = SeriesImpl.create();
   seCategory.setDataSet(categoryValues);

   // CREATE THE PRIMARY DATASET
   LineSeries ls = (LineSeries) LineSeriesImpl.create();
   ls.setSeriesIdentifier("My Line Series");
   ls.setDataSet(orthoValues1);
   ls.getLineAttributes().setColor(ColorDefinitionImpl.CREAM());
   ls.getMarker().setType(MarkerType.TRIANGLE_LITERAL);
   ls.getLabel().setVisible(true);

   SeriesDefinition sdX = SeriesDefinitionImpl.create();
   sdX.getSeriesPalette().update(0); // SET THE COLORS IN THE PALETTE
   xAxisPrimary.getSeriesDefinitions().add(sdX);

   SeriesDefinition sdY = SeriesDefinitionImpl.create();
   sdY.getSeriesPalette().update(1); // SET THE COLORS IN THE PALETTE
   yAxisPrimary.getSeriesDefinitions().add(sdY);

   sdX.getSeries().add(seCategory);
   sdY.getSeries().add(ls);

   return cwaBar;
}

Q: Can I add call backs when the user clicks or hovers over nodes in the chart?

Yes ... you may add 'triggers' for various 'conditions' in the design-time chart model associated with 'series' or 'blocks' as follows:

PieSeries sePie = ...;

sePie.getTriggers().add(
  TriggerImpl.create(
     TriggerCondition.MOUSE_CLICK_LITERAL,
     ActionImpl.create(
        ActionType.URL_REDIRECT_LITERAL,
        URLValueImpl.create(
           "http://www.actuate.com", null, "city", "population", null
        )
     )
  )
);

This trigger causes a URL redirect notification when a user clicks on a slice in a pie chart. However, this has only been implemented for the swing device renderer.

A reference implementation of handling user interaction has been provided in org.eclipse.birt.chart.device.swing.SwingRendererImpl in the following parts of the class:

variables: _iun, _eh, _lhmAllTriggers
method: enableInteraction

This method creates a low-level 'ShapedAction' for a polygon, an arc or an oval and places it into the linked hashmap of all triggers.

Later, when rendered, the SwingEventHandler class listens for AWT events on these shaped objects and notifies the class defined that implements IUpdateNotifier to update itself due to user interaction at view time. The update notifier (depending on the context) should be capable of:

  • Regenerating the chart (for the design-time model)
  • Repainting the chart (for the existing generated instance)

This hasn't yet been implemented for SWT but should be easily doable.

Q: How do I put the x-axis on the bottom of the chart instead of the middle?

Yes, you may place the X-axis at the bottom using the following API:

xAxis.getOrigin().setType(IntersectionType.MIN_LITERAL);

where MIN indicates 'the minimum vertical position of the plot (bottom)' when applied to the X axis or 'the minimal horizontal position of the plot (left)' when applied to the Y axis'.

Q: How do I remove the title from the legend?

Yes, set 'block' visibility for the 'TitleBlock' and the 'Legend'.

Q: How do I turn of the debug messages?

Running the Chart package may produce messages such as:

INFO]: SWT Display Server: win32 v3062
[INFO]: Using graphics context org.eclipse.swt.graphics.GC@GC {1023479146}
[INFO]: (STANDALONE-ENV) Creating series renderer
org.eclipse.birt.chart.render.Line

To turn these off, set the logging verbose level using:

org.eclipse.birt.chart.log.DefaultLoggerImpl.instance( )
	.setVerboseLevel( ILogger.ERROR | ILogger.WARNING | ILogger.FATAL );

This will suppress the detailed INFORMATION messages.

Eclipse, SWT and RCP

Q: How do I show a chart in my Eclipse RCP application?

You could create a plug-in with a ViewPart extension that creates an SWT canvas and notifies a PaintListener implementation where the chart rendering code may be written.

To render an SWT chart on a Canvas GC, see below.

Note: You have to remove the line "System.setProperty("STANDALONE", "true");" if you want to use the example below in Eclipse RCP.

Q: How can I use charts in SWT?

Here's the program François-Xavier Le Louarn wrote using the example code above (slightly modified, as the Axis interface does not contain the getSeriesDefinitions() method but AxisImpl does). He adapted the code from the org.eclipse.birt.chart.ui.swt.ChartModelAdapter object.

The Chart project lead comments: Here's a suggestion on optimizing the number of calls you make to re-build the chart:

Instead of building and rendering the chart in the paintControl(...) method, you could choose to build it off screen and render it any number of times without re-building it. There is no requirement that a UI component needs to be showing to be able to build a chart. Hence it is not mandatory to build the chart in the paintControl(…) method. However, if you choose to resize the client area, then you'd want to rebuild it for the new dimensions before you render the chart. So, in effect, you would want to compute the line chart off screen when the client area is resized ONLY rather than for every paint notification. Remember ... the bounds (specified in points) with which you render the chart must equate to the bounds used in building the chart.

public class TestLineChart
{
    private static final class MyPaintListener implements PaintListener
    {
        public void paintControl(PaintEvent e)
        {
            // let's get a SWT device renderer
            IDeviceRenderer deviceRenderer = null;
            try
            {
                System.setProperty("STANDALONE", "true");
                deviceRenderer = PluginSettings.instance()
			.getDevice("dv.SWT");
            }
            catch (PluginException ex)
            {
                System.err.println(
			"Oops, can't find the device renderer.");
                ex.printStackTrace();
                System.exit(1);
            }
            deviceRenderer
	    	.setProperty(IDeviceRenderer.GRAPHICS_CONTEXT, e.gc);

            // now let's make sure we stay in the client area's bounds
            Rectangle rect = ((Composite) e.widget).getClientArea();
            final Bounds bounds = BoundsImpl.create(rect.x + 2,
                    rect.y + 2,
                    rect.width - 4,
                    rect.height - 4);
            bounds.scale(72d /
	    	deviceRenderer.getDisplayServer().getDpiResolution());

            // create Rohit's chart
            Chart chart = createSimpleLineChart();

            // and finally, generate it...
            final Generator gr = Generator.instance();
            GeneratedChartState state;
            try
            {
                state = gr.build(deviceRenderer.getDisplayServer(),
                        chart,
                        (Scriptable) null,
                        bounds,
                        Locale.getDefault());
                gr.render(deviceRenderer, state);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
                System.exit(1);
            }
        }
    }

    public static void main(String[] args)
    {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setLayout(new FillLayout());
        shell.setText("TestLineChart");
        Canvas canvas = new Canvas(shell, SWT.NONE);
        canvas.addPaintListener(new MyPaintListener());
        shell.setSize(400, 400);

        shell.open();
        while (!shell.isDisposed())
        {
            if (!display.readAndDispatch()) display.sleep();
        }
        display.dispose();
    }

    /**
     * Creates a line chart model as a reference implementation *
     *
     * @return An instance of the simulated runtime chart model
     * (containing filled datasets)
     */
    public static final Chart createSimpleLineChart()
    {
        ChartWithAxes cwaBar = ChartWithAxesImpl.create();
        cwaBar.getBlock().setBackground(ColorDefinitionImpl.WHITE());
        Plot p = cwaBar.getPlot();
        p.getClientArea().setBackground(ColorDefinitionImpl.create(255,
                255,
                225));
        cwaBar.getTitle().getLabel().getCaption()
		.setValue("Sample Line Chart");

        Legend lg = cwaBar.getLegend();
        LineAttributes lia = lg.getOutline();
        lg.getText().getFont().setSize(16);
        lia.setStyle(LineStyle.SOLID_LITERAL);
        lg.getInsets().set(10, 5, 0, 0);
        lg.getOutline().setVisible(false);
        lg.setAnchor(Anchor.NORTH_LITERAL);

        AxisImpl xAxisPrimary =
		(AxisImpl) cwaBar.getPrimaryBaseAxes()[0];
        xAxisPrimary.setType(AxisType.TEXT_LITERAL);
        xAxisPrimary.getMajorGrid()
		.setTickStyle(TickStyle.BELOW_LITERAL);
        xAxisPrimary.getOrigin()
		.setType(IntersectionType.VALUE_LITERAL);
        xAxisPrimary.getTitle().setVisible(false);

        AxisImpl yAxisPrimary = (AxisImpl) cwaBar
                .getPrimaryOrthogonalAxis(xAxisPrimary);
        yAxisPrimary.getMajorGrid()
		.setTickStyle(TickStyle.LEFT_LITERAL);
        yAxisPrimary.setPercent(true);

        Vector vs = new Vector();
        vs.add("one");
        vs.add("two");
        vs.add("three");

        ArrayList vn1 = new ArrayList();
        vn1.add(new Double(25));
        vn1.add(new Double(35));
        vn1.add(new Double(-45));

        TextDataSet categoryValues = TextDataSetImpl.create(vs);
        NumberDataSet orthoValues1 = NumberDataSetImpl.create(vn1);

        // CREATE THE CATEGORY SERIES
        Series seCategory = SeriesImpl.create();
        seCategory.setDataSet(categoryValues);

        // CREATE THE PRIMARY DATASET
        LineSeries ls = (LineSeries) LineSeriesImpl.create();
        ls.setSeriesIdentifier("My Line Series");
        ls.setDataSet(orthoValues1);
        ls.getLineAttributes().setColor(ColorDefinitionImpl.CREAM());
        ls.getMarker().setType(MarkerType.TRIANGLE_LITERAL);
        ls.getLabel().setVisible(true);

        SeriesDefinition sdX = SeriesDefinitionImpl.create();
        sdX.getSeriesPalette().update(0);
        xAxisPrimary.getSeriesDefinitions().add(sdX);

        SeriesDefinition sdY = SeriesDefinitionImpl.create();
        sdY.getSeriesPalette().update(1);
        yAxisPrimary.getSeriesDefinitions().add(sdY);

        sdX.getSeries().add(seCategory);
        sdY.getSeries().add(ls);

        return cwaBar;
    }
}

Q: What are the angles of color gradient supported in SWT?

SWT does not support gradient angles other than 0, 90 and -90. This will be fixed when the underlying SWT API supports it.

Deployment

Q: What JARs are needed on the class path to deploy the BIRT charting package in an SWT/JFace application?

If you are using 1.0M2, the following JARs need to be included in your CLASSPATH at build/run time:

Chart engine libraries:

  • engine.jar OR chart-engine.jar (from org.eclipse.birt.chart.engine)
  • shared.jar OR chart-shared.jar (from org.eclipse.birt.chart.shared) (See below)
  • engine-extension.jar (from org.eclipse.birt.chart.engine.extension)
  • device-extension.jar (from org.eclipse.birt.chart.device.extension)
  • core.jar (from org.eclipse.birt.core)

3rd party dependencies:

  • js.jar (Rhino 1.5R5 from org.eclipse.birt.core/lib that allows scripting)
  • common.jar (from org.eclipse.emf.common/runtime)
  • common.resources.jar
  • ecore.jar (from org.eclipse.emf.ecore/runtime)
  • ecore.resources.jar
  • ecore.xmi.jar

Also, when you run your SWT application, remember to set a system property called 'STANDALONE'.

Note: chart-shared.jar OR shared.jar will not be needed (nor included) in the final release 1.0.