LibreOffice Developer's Guide: Chapter 10 - Charts

From The Document Foundation Wiki
Jump to navigation Jump to search

Chart documents produce graphical representations of numeric data. They are always embedded objects inside other LibreOffice documents. The chart document is a document model similar to Writer, Calc and Draw document models, and it can be edited using this document model.

Handling Chart Documents

Creating Charts

The <idl>com.sun.star.table.XTableChartsSupplier</idl> interface of the <idl>com.sun.star.sheet.Spreadsheet</idl> service is used to create and insert a new chart into a Calc document. This creates a chart that uses data from the <idl>com.sun.star.chart.XChartDataArray</idl> interface of the underlying cell range. A generic way to create charts is to insert an OLE-Shape into a draw page and transform it into a chart setting a class-id.

Creating and Adding a Chart to a Spreadsheet

Charts are used in spreadsheet documents to visualize the data that they contain. A spreadsheet is one single sheet in a spreadsheet document and offers a <idl>com.sun.star.table.XTableChartsSupplier</idl> interface, that is required by the service <idl>com.sun.star.sheet.Spreadsheet</idl>. With this interface, a collection of table charts that are a container for the actual charts can be accessed. To retrieve the chart document model from a table chart object, use the method getEmbeddedObject().

The following example shows how to insert a chart into a spreadsheet document and retrieve its chart document model. The example assumes that there is a <idl>com.sun.star.sheet.XSpreadsheet</idl> to insert the chart and an array of cell range addresses that contain the regions in which the data for the chart can be found. Refer to Spreadsheet Documents for more information about how to get or create these objects. The following snippet shows how to insert a chart into a Calc document.

import com.sun.star.chart.*;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.container.XNameAccess;
import com.sun.star.document.XEmbeddedObjectSupplier;

final String sChartName = "MyChart";

com.sun.star.table.XTableChartsSupplier aSheet;
com.sun.star.table.CellRangeAddress[] aAddresses;

// get the sheet in which you want to insert a chart
// and query it for XTableChartsSupplier and store it in aSheet
//
// also get an array of CellRangeAddresses containing
// the data you want to visualize and store them in aAddresses
//
// for details see documentation of Spreadsheets

// ...

XChartDocument aChartDocument = null;

com.sun.star.table.XTableCharts aChartCollection = aSheet.getCharts();
XNameAccess aChartCollectionNA = (XNameAccess) UnoRuntime.queryInterface(
    XNameAccess.class, aChartCollection );

// only insert the chart if it does not already exist
if (aChartCollectionNA != null && !aChartCollectionNA.hasByName(sChartName)) {
    // following rectangle parameters are measured in 1/100 mm
    com.sun.star.awt.Rectangle aRect = new com.sun.star.awt.Rectangle(1000, 1000, 15000, 9271);

    // first bool: ColumnHeaders
    // second bool: RowHeaders
    aChartCollection.addNewByName(sChartName, aRect, aAddresses, true, false);

    try {
        com.sun.star.table.XTableChart aTableChart = (com.sun.star.table.XTableChart)
            UnoRuntime.queryInterface(
                com.sun.star.table.XTableChart.class,
                aChartCollectionNA.getByName(sChartName));

        // the table chart is an embedded object which contains the chart document
        aChartDocument = (XChartDocument) UnoRuntime.queryInterface(
            XChartDocument.class,
            ((XEmbeddedObjectSupplier) UnoRuntime.queryInterface(
                XEmbeddedObjectSupplier.class,
                aTableChart )).getEmbeddedObject());

    } catch (com.sun.star.container.NoSuchElementException ex) {
        System.out.println("Couldn't find chart with name " + sChartName + ": " + ex);
    }
}

// now aChartDocument should contain an XChartDocument representing the newly inserted chart

Creating a Chart OLE Object in Draw and Impress

The alternative is to create an OLE shape and insert it into a draw page. Writer, Spreadsheet documents and Draw/Impress documents have access to a draw page. The shape is told to be a chart by setting the unique class-id.

Note pin.svg

Note:
The unique Class-Id string for charts is "12dcae26-281f-416f-a234-c3086127382e".

A draw page collection is obtained from the <idl>com.sun.star.drawing.XDrawPagesSupplier</idl> of a draw or presentation document. To retrieve a single draw page, use <idl>com.sun.star.container.XIndexAccess</idl>.

A spreadsheet document is also a <idl>com.sun.star.drawing.XDrawPagesSupplier</idl> that provides draw pages for all sheets, that is, the draw page for the third sheet is obtained by calling getByIndex(2) on the interface <idl>com.sun.star.container.XIndexAccess</idl> of the container, returned by <idlm>com.sun.star.drawing.XDrawPagesSupplier:getDrawPages</idlm>().

A spreadsheet draw page can be acquired directly at the corresponding sheet object. A single sheet supports the service <idl>com.sun.star.sheet.Spreadsheet</idl> that supplies a single page, <idl>com.sun.star.drawing.XDrawPageSupplier</idl>, where the page is acquired using the method getDrawPage().

The LibreOffice Writer currently does not support the creation of OLE Charts or Charts based on a text table in a Writer document using the API.

The document model is required once a chart is inserted. In charts inserted as OLE2Shape, the document model is available at the property Model of the OLE2Shape after setting the CLSID.

Note pin.svg

Note:
Note, that the mechanism described for OLE objects seems to work in Writer, that is, you can see the OLE-Chart inside the Text document after executing the API calls described, but it is not treated as a real OLE object by the Writer. Thus, you can not activate it by double-clicking, because it puts the document into an inconsistent state.

The following example assumes a valid drawing document in the variable aDrawDoc and creates a chart in a Draw document. See Drawing Documents and Presentation Documents for an example of how to create a drawing document.

...
final String msChartClassID = "12dcae26-281f-416f-a234-c3086127382e";
com.sun.star.frame.XModel aDrawDoc;

// get a draw document into aDrawDoc
// ...

// this will become the resulting chart
XChartDocument aChartDoc;

com.sun.star.drawing.XDrawPagesSupplier aSupplier = (com.sun.star.drawing.XDrawPagesSupplier)
    UnoRuntime.queryInterface(com.sun.star.drawing.XDrawPagesSupplier.class, aDrawDoc);

com.sun.star.drawing.XShapes aPage = null;
try {
    // get first page
    aPage = (com.sun.star.drawing.XShapes) aSupplier.getDrawPages().getByIndex(0);
} catch (com.sun.star.lang.IndexOutOfBoundsException ex) {
    System.out.println("Document has no pages: " + ex);
}

if (aPage != null) {
    // the document should be a factory that can create shapes
    XMultiServiceFactory aFact = (XMultiServiceFactory) UnoRuntime.queryInterface(
        XMultiServiceFactory.class, aDrawDoc);

    if (aFact != null) {
        try {
            // create an OLE 2 shape
            com.sun.star.drawing.XShape aShape = (com.sun.star.drawing.XShape)
                UnoRuntime.queryInterface(
                    com.sun.star.drawing.XShape.class,
                        aFact.createInstance("com.sun.star.drawing.OLE2Shape"));

            // insert the shape into the page
            aPage.add(aShape);
            aShape.setPosition(new com.sun.star.awt.Point(1000, 1000));
            aShape.setSize(new com.sun.star.awt.Size(15000, 9271));

            // make the OLE shape a chart
            XPropertySet aShapeProp = (XPropertySet) UnoRuntime.queryInterface(
                XPropertySet.class, aShape );

            // set the class id for charts
            aShapeProp.setPropertyValue("CLSID", msChartClassID);

            // retrieve the chart document as model of the OLE shape
            aChartDoc = (XChartDocument) UnoRuntime.queryInterface(
                XChartDocument.class,
                aShapeProp.getPropertyValue("Model"));
        } catch(com.sun.star.uno.Exception ex) {
            System.out.println("Couldn't change the OLE shape into a chart: " + ex);
        }
    }
}

Setting the Chart Type

The default when creating a chart is a bar diagram with vertical bars. If a different chart type is required, apply a different diagram type to this initial chart. For example, to create a pie chart, insert the default bar chart and change it to a pie chart.

To change the type of a chart, create an instance of the required diagram service by its service name using the service factory provided by the <idl>com.sun.star.chart.XChartDocument</idl>. This interface is available at the chart document model. After this service instance is created, set it using the setDiagram() method of the chart document.

In the following example, we change the chart type to a <idl>com.sun.star.chart.XYDiagram</idl>, also known as a scatter chart. We have assumed that there is a chart document in the variable aChartDoc already. The previous sections described how to create a document.

// let aChartDoc be a valid XChartDocument

// get the factory that can create diagrams
XMultiServiceFactory aFact = (XMultiServiceFactory) UnoRuntime.queryInterface(
    XMultiServiceFactory.class, aChartDoc);

XDiagram aDiagram = (XDiagram) UnoRuntime.queryInterface(
    XDiagram.class, aFact.createInstance("com.sun.star.chart.XYDiagram"));

aChartDoc.setDiagram(aDiagram);

// now we have an xy-chart

Diagram Service Names
com.sun.star.chart.BarDiagram
com.sun.star.chart.AreaDiagram
com.sun.star.chart.LineDiagram
com.sun.star.chart.PieDiagram
com.sun.star.chart.DonutDiagram
com.sun.star.chart.NetDiagram
com.sun.star.chart.XYDiagram
com.sun.star.chart.StockDiagram
com.sun.star.chart.BubbleDiagram since OOo 3.2
com.sun.star.chart.FilledNetDiagram since OOo 3.2

Accessing Existing Chart Documents

To get a container of all charts contained in a spreadsheet document, use the <idl>com.sun.star.table.XTableChartsSupplier</idl> of the service <idl>com.sun.star.sheet.Spreadsheet</idl>, which is available at single spreadsheets.

To get all OLE-shapes of a draw page, use the interface <idl>com.sun.star.drawing.XDrawPage</idl>, that is based on <idl>com.sun.star.container.XIndexAccess</idl>. You can iterate over all shapes on the draw page and check their CLSID property to find out, whether the found object is a chart.

Working with Charts

Document Structure

The important service for charts is <idl>com.sun.star.chart.ChartDocument</idl>. The chart document contains all the top-level graphic objects, such as a legend, up to two titles called Title and Subtitle, an axis title object for each primary axis if the chart supports axis. The <idl>com.sun.star.chart.ChartArea</idl> always exists. This is the rectangular region covering the complete chart documents background. The important graphical object is the diagram that actually contains the visualized data.

Apart from the graphical objects, the chart document is linked to some data. The required service for the data component is <idl>com.sun.star.chart.ChartData</idl>. It is used for attaching a data change listener and querying general properties of the data source, such as the number to be interpreted as NaN ("not a number"), that is, a missing value. The derived class <idl>com.sun.star.chart.ChartDataArray</idl> allows access to the actual values. Every component providing the ChartData service should also support ChartDataArray.

The following diagram shows the services contained in a chart and their relationships.

ChartDocument

The name spaces in the diagram have been omitted to improve readability. The services are all in the name space <idlmodule>com.sun.star.chart</idlmodule>. The interfaces in this diagram are <idl>com.sun.star.chart.XChartDocument</idl>, <idl>com.sun.star.drawing.XShape</idl>, <idl>com.sun.star.lang.XComponent</idl>, and <idl>com.sun.star.beans.XPropertySet</idl>.

The chart document model passes its elements through the interface <idl>com.sun.star.chart.XChartDocument</idl>. This interface consists of the following methods:

com::sun::star::chart::XChartData getData()
void attachData( [in] com::sun::star::chart::XChartData xData)
com::sun::star::drawing::XShape getTitle()
com::sun::star::drawing::XShape getSubTitle()
com::sun::star::drawing::XShape getLegend()
com::sun::star::beans::XPropertySet getArea()
com::sun::star::chart::XDiagram getDiagram()
void setDiagram( [in] com::sun::star::chart::XDiagram xDiagram)

void dispose()
void addEventListener( [in] com::sun::star::lang::XEventListener xListener)
void removeEventListener( [in] com::sun::star::lang::XEventListener aListener)
boolean attachResource( [in] string aURL,
[in] sequence <com::sun::star::beans::PropertyValue aArgs)
string getURL()
sequence <com::sun::star::beans::PropertyValue > getArgs()

void connectController( [in] com::sun::star::frame::XController xController)
void disconnectController( [in] com::sun::star::frame::XController xController)
void lockControllers()
void unlockControllers()
boolean hasControllersLocked()
com::sun::star::frame::XController getCurrentController()
void setCurrentController( [in] com::sun::star::frame::XController xController)
com::sun::star::uno::XInterface getCurrentSelection()

Data Access

Data can only be accessed for reading when a chart resides inside a spreadsheet document and was inserted as a table chart, that is, the table chart obtains its data from cell ranges of spreadsheets. To change the underlying data, modify the content of the spreadsheet cells. For OLE charts, that is, charts that were inserted as OLE2Shape objects, modify the data.

The data of a chart is acquired from the <idl>com.sun.star.chart.XChartDocument</idl> interface of the chart document model using the method <idlm>com.sun.star.chart.XChartDocument:getData</idlm>(). The current implementation of LibreOffice charts provides a <idl>com.sun.star.chart.XChartDataArray</idl> interface, derived from <idl>com.sun.star.chart.XChartData</idl> and supports the service <idl>com.sun.star.chart.ChartDataArray</idl>.

Note pin.svg

Note:
Note that the interface definition of <idl>com.sun.star.chart.XChartDocument</idl> does not require XChartDocument.getData() to return an XChartDataArray, but XChartData, so there may be implementations that do not offer access to the values.

The methods of XChartDataArray are:

sequence <sequence < double > > getData()
void setData( [in] sequence <sequence < double > > aData)

sequence < string > getRowDescriptions ()
void setRowDescriptions(sequence < string aRowDescriptions >)

sequence < string > getColumnDescriptions()
void setColumnDescriptions(sequence < string aColumnDescriptions >)

Included are the following methods from XChartData:

void addChartDataChangeEventListener(
    [in] com::sun::star::chart::XChartDataChangeEventListener aListener)
void removeChartDataChangeEventListener (
    [in] com::sun::star::chart::XChartDataChangeEventListener aListener)

double getNotANumber()
boolean isNotANumber( [in] double nNumber)

The <idl>com.sun.star.chart.XChartDataArray</idl> interface offers several methods to access data. A sequence of sequences is obtained containing double values by calling getData(). With setData(), such an array of values can be applied to the XChartDataArray.

The arrays are a nested array, not two-dimensional. Java has only nested arrays, but in Basic a nested array must be used instead of a multidimensional array. The following example shows how to apply values to a chart in Basic:

' create data with dimensions 2 x 3
' 2 is called outer dimension and 3 inner dimension

' assume that oChartDocument contains a valid
' com.sun.star.chart.XChartDocument

Dim oData As Object
Dim oDataArray( 0 To 1 ) As Object
Dim oSeries1( 0 To 2 ) As Double
Dim oSeries2( 0 To 2 ) As Double

oSeries1( 0 ) = 3.141
oSeries1( 1 ) = 2.718
oSeries1( 2 ) = 1.0

oSeries2( 0 ) = 17.0
oSeries2( 1 ) = 23.0
oSeries2( 2 ) = 42.0

oDataArray( 0 ) = oSeries1()
oDataArray( 1 ) = oSeries2()

' call getData() method of XChartDocument to get the XChartDataArray
oData = oChartDocument.Data

' call setData() method of XChartDataArray to apply the data
oData.Data = oDataArray()

' Note: to use the series arrays here as values for the series in the chart
'       you have to set the DataRowSource of the XDiagram object to
'       com.sun.star.chart.ChartDataRowSource.ROWS (default is COLUMNS)

Note pin.svg

Note:
The Data obtained is a sequence that contains one sequence for each row. If you want to interpret the inner sequences as data for the series, set the DataRowSource of your XDiagram to <idl>com.sun.star.chart.ChartDataRowSource</idl>.ROWS, otherwise, the data for the nth series is in the nth element of each inner series.

With the methods of the XChartData interface, check if a number from the chart has to be interpreted as non-existent, that is, the number is not a number (NaN).

Warning.svg

Warning:
In the current implementation of LibreOffice Chart, the value of NaN is not the standard ISO value for NaN of the C++ double type, but instead DBL_MIN which is 2.2250738585072014-308.


Additionally, the XChartData interface is used to connect a component as a listener on data changes. For example, to use a spreadsheet as the source of the data of a chart that resides inside a presentation. It can also be used in the opposite direction: the spreadsheet could display the data from a chart that resides in a presentation document. To achieve this, the <idl>com.sun.star.table.CellRange</idl> service also points to the XChartData interface, so that a listener can be attached to update the chart OLE object.

The following class ListenAtCalcRangeInDraw demonstrates how to synchronize the data of a spreadsheet range with a chart residing in another document. Here the chart is placed into a drawing document.

The class ListenAtCalcRangeInDraw in the example below implements a <idl>com.sun.star.lang.XEventListener</idl> to get notified when the spreadsheet document or drawing document are closed.

It also implements a <idl>com.sun.star.chart.XChartDataChangeEventListener</idl> that listens for changes in the underlying XchartData. In this case, it is the range in the spreadsheet.

import com.sun.star.uno.UnoRuntime;
import com.sun.star.lang.XEventListener;
import com.sun.star.beans.XPropertySet;
import com.sun.star.lang.XComponent;

import com.sun.star.chart.*;
import com.sun.star.sheet.XSpreadsheetDocument;

// implement an XEventListener for listening to the disposing
// of documents. Also implement XChartDataChangeEventListener
// to get informed about changes of data in a chart

public class ListenAtCalcRangeInDraw implements XChartDataChangeEventListener {
    public ListenAtCalcRangeInDraw(String args[]) {
        // create a spreadsheet document in maSheetDoc
        // create a drawing document in maDrawDoc
        // put a chart into the drawing document
        // and store it in maChartDocument
        // ...

        com.sun.star.table.XCellRange aRange;
        // assign a range from the spreadsheet to aRange
        // ...

        // attach the data coming from the cell range to the chart
        maChartData = (XChartData) UnoRuntime.queryInterface(XChartData.class, aRange);
        maChartDocument.attachData(maChartData);
    }

    public void run() {
        try {
            // show a sub title to inform about last update
            ((XPropertySet) UnoRuntime.queryInterface(
                XPropertySet.class, maChartDocument)).setPropertyValue(
                    "HasSubTitle", new Boolean(true));

            // start listening for death of spreadsheet
            ((XComponent) UnoRuntime.queryInterface(XComponent.class,
                maSheetDoc)).addEventListener(this);

            // start listening for death of chart
            ((XComponent) UnoRuntime.queryInterface(XComponent.class,
                maChartDocument)).addEventListener(this);

            //start listening for change of data
            maChartData.addChartDataChangeEventListener(this);
        } catch (com.sun.star.uno.Exception ex) {
            System.out.println("Oops: " + ex);
        }

        // call listener once for initialization
        ChartDataChangeEvent aEvent = new ChartDataChangeEvent();
        aEvent.Type = ChartDataChangeType.ALL;
        chartDataChanged(aEvent);
    }

    // XEventListener (base interface of XChartDataChangeEventListener)
    public void disposing(com.sun.star.lang.EventObject aSourceObj)
    {
        // test if the Source object is a chart document
        if( UnoRuntime.queryInterface(XChartDocument.class, aSourceObj.Source) != null)
            System.out.println("Disconnecting Listener because Chart was shut down");

        // test if the Source object is a spreadsheet document
        if (UnoRuntime.queryInterface(XSpreadsheetDocument.class, aSourceObj.Source) != null)
            System.out.println("Disconnecting Listener because Spreadsheet was shut down");

        // remove data change listener
        maChartData.removeChartDataChangeEventListener(this);

        // remove dispose listeners
        ((XComponent) UnoRuntime.queryInterface(XComponent.class,
            maSheetDoc)).removeEventListener(this);
        ((XComponent) UnoRuntime.queryInterface(XComponent.class,
            maChartDocument)).removeEventListener(this);
        // this program cannot do anything any more
        System.exit(0);
    }

    // XChartDataChangeEventListener
    public void chartDataChanged(ChartDataChangeEvent aEvent)
    {
        // update subtitle with current date
        String aTitle = new String("Last Update: " + new java.util.Date(System.currentTimeMillis()));

        try {
            ((XPropertySet) UnoRuntime.queryInterface(XPropertySet.class,
                maChartDocument.getSubTitle())).setPropertyValue(
                    "String", aTitle);

            maChartDocument.attachData(maChartData);
        } catch(Exception ex) {
            System.out.println("Oops: " + ex);
        }

        System.out.println("Data has changed");
    }

    // private
    private com.sun.star.sheet.XSpreadsheetDocument maSheetDoc;
    private com.sun.star.frame.XModel maDrawDoc;
    private com.sun.star.chart.XChartDocument maChartDocument;
    private com.sun.star.chart.XChartData maChartData;
}

Chart Document Parts

In this section, the parts that most diagram types have in common are discussed. Specific chart types are discussed later.

Common Parts of all Chart Types

Diagram

The diagram object is an important object of a chart document. The diagram represents the visualization of the underlying data. The diagram object is a graphic object group that lies on the same level as the titles and the legend. From the diagram, data rows and data points are obtain that are also graphic objects that represent the respective data. Several properties can be set at a diagram to influence its appearance. Note that the term data series is used instead of data rows.

Some diagrams support the service <idl>com.sun.star.chart.Dim3DDiagram</idl> that contains the property Dim3D. If this is set to true, you get a three-dimensional view of the chart, which in LibreOffice is usually rendered in OpenGL. In 3-D charts, you can access the z-axis, which is not available in 2-D.

The service <idl>com.sun.star.chart.StackableDiagram</idl> offers the properties Percent and Stacked. With these properties, accumulated values can be stacked for viewing. When setting Percent to true, all slices through the series are summed up to 100 percent, so that for an AreaDiagram the whole diagram space would be filled with the series. Note that setting the Percent property also sets the Stacked property, because Percent is an addition to Stacked.

There is a third service that extends a base diagram type for displaying statistical elements called <idl>com.sun.star.chart.ChartStatistics</idl>. With this service, error indicators or a mean value line are added. The mean value line represents the mean of all values of a series. The regression curve is only available for the XYDiagram, because a numeric x-axis, is required.

Diagram

The illustration above shows that there are eight base types of diagrams. The three services, StackableDiagram, Dim3DDiagram and ChartStatistics are also supported for several diagram types and allows extensions of the base types as discussed. For instance, a three-dimensional pie chart can be created, because the <idl>com.sun.star.chart.PieDiagram</idl> service points to the <idl>com.sun.star.chart.Dim3DDiagram</idl> service.

The services <idl>com.sun.star.chart.AreaDiagram</idl>, <idl>com.sun.star.chart.LineDiagram</idl>, and <idl>com.sun.star.chart.BarDiagram</idl> support all three feature services.

Axis

All charts can have one or more axis, except for pie charts. A typical two-dimensional chart has two axis, an x- and y-axis. Secondary x- or y-axis can be added to have up to four axis. In a three-dimensional chart, there are typically three axis, x-, y- and z-axis. There are no secondary axis in 3-dimensional charts.

An axis combines two types of properties:

  • Scaling properties that affect other objects in the chart. A minimum and maximum values are set that spans the visible area for the displayed data. A step value can also be set that determines the distance between two tick-marks, and the distance between two grid-lines if grids are switched on for the corresponding axis.
  • Graphical properties that influence the visual impression. These are character properties (see <idl>com.sun.star.style.CharacterProperties</idl>) affecting the labels and line properties (see <idl>com.sun.star.drawing.LineProperties</idl>) that are applied to the axis line and the tick-marks.
Axis

Different diagram types support a different number of axis. In the above illustration, a <idl>com.sun.star.chart.XYDiagram</idl>, also known as a scatter diagram, is shown. The scatter diagram supports x- and y-axis, but not a z-axis as there is no 3-dimensional mode. The <idl>com.sun.star.chart.PieDiagram</idl> supports no axis at all. The <idl>com.sun.star.chart.BarDiagram</idl> supports all kinds of axis. Note that the z-Axis is only supported in a three-dimensional chart. Note that there is a <idl>com.sun.star.chart.ChartTwoAxisXSupplier</idl> that includes the <idl>com.sun.star.chart.ChartAxisXSupplier</idl> and is supported by all diagrams in LibreOffice required to support the service ChartAxisXSupplier.

The following example shows how to obtain an axis and how to change the number format.

import com.sun.star.chart.*;
import com.sun.star.beans.XPropertySet;
import com.sun.star.util.XNumberFormatsSupplier;

...

// class members
XChartDocument aChartDocument;
XDiagram aDiagram;

...

// get an XChartDocument and assign it to aChartDocument
// get the diagram from the document and assign it to aDiagram
// ...

// check whether the current chart supports a y-axis
XAxisYSupplier aYAxisSupplier = (XAxisYSupplier) UnoRuntime.queryInterface(
    XAxisYSupplier.class, aDiagram);

if (aYAxisSupplier != null) {
    XPropertySet aAxisProp = aYAxisSupplier.getYAxis();

    // initialize new format with value for standard
    int nNewNumberFormat = 0;

    XNumberFormatsSupplier aNumFmtSupp = (XNumberFormatsSupplier)
        UnoRuntime.queryInterface(XNumberFormatsSupplier.class,
            aChartDocument);

    if (aNumFmtSupp != null) {
        com.sun.star.util.XNumberFormats aFormats = aNumFmtSupp.getNumberFormats();

        Locale aLocale = new Locale("de", "DE", "de");

        String aFormatStr = aFormats.generateFormat(
            0,        // base key
            aLocale,  // locale
            true,     // thousands separator on
            true,     // negative values in red
            (short)3, // number of decimal places
            (short)1  // number of leading ciphers
        );

        nNewNumberFormat = aFormats.addNew(aFormatStr, aLocale);
    }

    aAxisProp.setPropertyValue("NumberFormat", new Integer(nNewNumberFormat));
}

Data Series and Data Points

The objects that visualize the actual data are data series. The API calls them data rows that are not rows in a two-dimensional spreadsheet table, but as sets of data, because the data for a data row can reside in a column of a spreadsheet table.

The data rows contain data points. The following two methods at the <idl>com.sun.star.chart.XDiagram</idl> interface allow changes to the properties of a whole series or single data point:

com::sun::star::beans::XPropertySet getDataRowProperties( [in] long nRow)
com::sun::star::beans::XPropertySet getDataPointProperties( [in] long nCol,
                                                            [in] long nRow)

The index provided in these methods is 0-based, that is, 0 is the first series. In XYDiagrams, the first series has an index 1, because the first array of values contains the x-values of the diagram that is not visualized. This behavior exists for historical reasons.

In a spreadsheet context, the indexes for getDataPointProperties() are called nCol and nRow and are misleading. The nRow parameter gives the data row, that is, the series index. The nCol gives the index of the data point inside the series, regardless if the series is taken from rows or columns in the underlying table. To get the sixth point of the third series, write getDataPointProperties(5, 2).

Data rows and data points have <idl>com.sun.star.drawing.LineProperties</idl> and <idl>com.sun.star.drawing.FillProperties</idl>. They also support <idl>com.sun.star.style.CharacterProperties</idl> for text descriptions that can be displayed next to data points.

Properties can be set for symbols and the type of descriptive text desired. With the SymbolType property, one of several predefined symbols can be set. With SymbolBitmapURL, a URL that points to a graphic in a format known by LibreOffice can be set that is then used as a symbol in a <idl>com.sun.star.chart.LineDiagram</idl> or <idl>com.sun.star.chart.XYDiagram</idl>.

The following example demonstrates how to set properties of a data point. Before implementing this example, create a chart document and diagram of the type XYDiagram.

com.sun.star.chart.XChartDocument aChartDocument;
com.sun.star.chart.XDiagram aXYDiagram;

// get a chart document and assign it to aChartDocument
// set an "XYDiagram" and remember the diagram in aXYDiagram
// ...

// get the properties of the fifth data point of the first series
// note that index 1 is the first series only in XYDiagrams
try {
    com.sun.star.beans.XPropertySet aPointProp = maDiagram.getDataPointProperties(4, 1);
} catch (com.sun.star.lang.IndexOutOfBoundsException ex) {
    System.out.println("Index is out of bounds: " + ex);
    System.exit(0);
}

try {
    // set a bitmap via URL as symbol for the first series
    aPointProp.setPropertyValue("SymbolType", new Integer(ChartSymbolType.BITMAPURL));
    aPointProp.setPropertyValue("SymbolBitmapURL",
        "http://graphics.openoffice.org/chart/bullet1.gif");

    // add a label text with bold font, bordeaux red 14pt
    aPointProp.setPropertyValue("DataCaption", new Integer(ChartDataCaption.VALUE));
    aPointProp.setPropertyValue("CharHeight", new Float(14.0));
    aPointProp.setPropertyValue("CharColor", new Integer(0x993366));
    aPointProp.setPropertyValue("CharWeight", new Float(FontWeight.BOLD));
} catch (com.sun.star.uno.Exception ex) {
    System.out.println("Exception caught: " + ex);
}

Features of Special Chart Types

Examples of some of the services that are not available for all chart types are discussed in this section. Only examples that can be changed in specific chart types only are discussed.

Statistics

Statistical properties like error indicators or regression curves can be applied. The regression curves can only be used for xy-diagrams that have tuples of values for each data point. The following example shows how to add a linear regression curve to an xy-diagram.

Additionally, the mean value line is displayed and error indicators for the standard deviation are applied.

// get the diagram
// ...

// get the properties of a single series
XPropertySet aProp = maDiagram.getDataRowProperties(1)

// set a linear regression
aProp.setPropertyValue("RegressionCurves", ChartRegressionCurveType.LINEAR);

// show a line at y = mean of the series' values
aProp.setPropertyValue("MeanValue", new Boolean(true));

// add error indicators in both directions
// with the length of the standard deviation
aProp.setPropertyValue("ErrorCategory", ChartErrorCategory.STANDARD_DEVIATION);
aProp.setPropertyValue("ErrorIndicator", ChartErrorIndicatorType.TOP_AND_BOTTOM);

3-D Charts

Some chart types can display a 3-dimensional representation. To switch a chart to 3-dimensional, set the boolean property Dim3D of the service <idl>com.sun.star.chart.Dim3DDiagram</idl>.

In addition to this property, bar charts support a property called Deep (see service <idl>com.sun.star.chart.BarDiagram</idl>) that arranges the series of a bar chart along the z-axis, which in a chart, points away from the spectator. The service <idl>com.sun.star.chart.Chart3DBarProperties</idl> offers a property SolidType to set the style of the data point solids. The solid styles can be selected from cuboids, cylinders, cones, and pyramids with a square base (see constants in <idl>com.sun.star.chart.ChartSolidType</idl>).

The XDiagram of a 3-dimensional chart is also a scene object that supports modification of the rotation and light sources. The example below shows how to rotate the scene object and add another light source.

// prerequisite: maDiagram contains a valid bar diagram
// ...

import com.sun.star.drawing.HomogenMatrix;
import com.sun.star.drawing.HomogenMatrixLine;
import com.sun.star.chart.X3DDisplay;
import com.sun.star.beans.XPropertySet;

XPropertySet aDiaProp = (XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, maDiagram);
Boolean aTrue = new Boolean(true);

aDiaProp.setPropertyValue("Dim3D", aTrue);
aDiaProp.setPropertyValue("Deep", aTrue);

// from service Chart3DBarProperties:
aDiaProp.setPropertyValue("SolidType", new Integer(
    com.sun.star.chart.ChartSolidType.CYLINDER));

// change floor color to Magenta6
XPropertySet aFloor = ((X3DDisplay) UnoRuntime.queryInterface(
    X3DDisplay.class, maDiagram)).getFloor();
aFloor.setPropertyValue("FillColor", new Integer(0x6b2394));

// rotate the scene using a homogen 4x4 matrix
// -------------------------------------------
HomogenMatrix aMatrix = new HomogenMatrix();
// initialize matrix with identity
HomogenMatrixLine aLines[] = new HomogenMatrixLine[] {
        new HomogenMatrixLine(1.0, 0.0, 0.0, 0.0),
        new HomogenMatrixLine(0.0, 1.0, 0.0, 0.0),
        new HomogenMatrixLine(0.0, 0.0, 1.0, 0.0),
        new HomogenMatrixLine(0.0, 0.0, 0.0, 1.0)
    };

aMatrix.Line1 = aLines[0];
aMatrix.Line2 = aLines[1];
aMatrix.Line3 = aLines[2];
aMatrix.Line4 = aLines[3];

// rotate 10 degrees along the x axis
double fAngle = 10.0;
double fCosX = Math.cos(Math.toRadians(fAngle));
double fSinX = Math.sin(Math.toRadians(fAngle));

// rotate -20 degrees along the y axis
fAngle = -20.0;
double fCosY = Math.cos(Math.toRadians(fAngle));
double fSinY = Math.sin(Math.toRadians(fAngle));

// rotate -5 degrees along the z axis
fAngle = -5.0;
double fCosZ = Math.cos(Math.toRadians(fAngle));
double fSinZ = Math.sin(Math.toRadians(fAngle));

// set the matrix such that it represents all three rotations in the order
// rotate around x axis then around y axis and finally around the z axis
aMatrix.Line1.Column1 = fCosY * fCosZ;
aMatrix.Line1.Column2 = fCosY * -fSinZ;
aMatrix.Line1.Column3 = fSinY;

aMatrix.Line2.Column1 = fSinX * fSinY * fCosZ + fCosX * fSinZ;
aMatrix.Line2.Column2 = -fSinX * fSinY * fSinZ + fCosX * fCosZ;
aMatrix.Line2.Column3 = -fSinX * fCosY;

aMatrix.Line3.Column1 = -fCosX * fSinY * fCosZ + fSinX * fSinZ;
aMatrix.Line3.Column2 = fCosX * fSinY * fSinZ + fSinX * fCosZ;
aMatrix.Line3.Column3 = fCosX * fCosY;

aDiaProp.setPropertyValue("D3DTransformMatrix", aMatrix);

// add a red light source
// ----------------------

// in a chart by default only the second (non-specular) light source is switched on
// light source 1 is the only specular light source that is used here

// set direction
com.sun.star.drawing.Direction3D aDirection = new com.sun.star.drawing.Direction3D();

aDirection.DirectionX = -0.75;
aDirection.DirectionY = 0.5;
aDirection.DirectionZ = 0.5;

aDiaProp.setPropertyValue("D3DSceneLightDirection1", aDirection);
aDiaProp.setPropertyValue("D3DSceneLightColor1", new Integer(0xff3333));
aDiaProp.setPropertyValue("D3DSceneLightOn1", new Boolean(true));

Refer to Drawing Documents and Presentation Documents for additional details about three-dimensional properties.

Pie Charts

Pie charts support the offset of pie segments with the service <idl>com.sun.star.chart.ChartPieSegmentProperties</idl> that has a property SegmentOffset to drag pie slices radially from the center up to an amount equal to the radius of the pie. This property reflects a percentage, that is, values can go from 0 to 100.

// ...

// drag the fourth segment 50% out
aPointProp = maDiagram.getDataPointProperties(3, 0)
aPointProp.setPropertyValue("SegmentOffset", 50)

Note that the SegmentOffset property is not available for donut charts and three-dimensional pie charts.

Stock Charts

A special data structure must be provided when creating stock charts. When a <idl>com.sun.star.chart.StockDiagram</idl> is set as the current chart type, the data is interpreted in a specific manner depending on the properties Volume and UpDown. The following table shows what semantics are used for the data series.

Volume UpDown Series 1 Series 2 Series 3 Series 4 Series 5
false false Low High Close - -
true false Volume Low High Close -
false true Open Low High Close -
true true Volume Open Low High Close

For example, if the property Volume is set to false and UpDown to true, the first series is interpreted as the value of the stock when the stock exchange opened, and the fourth series represents the value when the stock exchange closed. The lowest and highest value during the day is represented in series two and three, respectively.

Chart Document Controller

The chart document model implements <idl>com.sun.star.frame.XModel</idl>. Therefore, controllers can be registered with the method connectController(). When one of the registered controllers is set as current one with setCurrentController(), this one is also returned in the method getCurrentController().

Apart from using the controller directly, there are three other useful methods:

void lockControllers()
void unlockControllers()
boolean hasControllersLocked()

With a call to lockControllers() all registered controllers will no longer be notified about changes in the model. If there are changes those are notified no earlier than after calling unlockControllers(). This is especially useful if you do many changes to the chart model at a time but do not need the view to be updated after every single change. The method hasControllersLocked() just gives you the state whether controllers are locked or not.

Note pin.svg

Note:
You can nest calls to lockControllers(). Every call to lockControllers() needs a corresponding unlockControllers() call. Only after the correct number of unlocking calls, notifications of the model will be sent again. So, you can safely lock and unlock the controllers in your code without having to check if they have already been locked or not. Just make sure that you call both methods and do not leave the controllers in a locked state after your code finishes.

Chart Add-Ins

Chart types can also be created by developing components that serve as chart types. Existing chart types can be extended by adding additional shapes or modifying the existing shapes. Alternatively, a chart can be created from scratch. If drawing from scratch, it is an empty canvas and all shapes would have to be drawn from scratch.

Chart Add-Ins are actually UNO components, thus, you should be familiar with the chapter Writing UNO Components.

Prerequisites

The following interfaces must be supported for a component to serve as a chart add-in:

  • <idl>com.sun.star.lang.XInitialization</idl>
  • <idl>com.sun.star.util.XRefreshable</idl>
  • <idl>com.sun.star.lang.XServiceName</idl>
  • <idl>com.sun.star.lang.XServiceInfo</idl>
  • <idl>com.sun.star.lang.XTypeProvider</idl> to access the add-in interfaces from LibreOffice Basic and other interpreted programming languages (optional).

In addition to these interfaces, the following services must be supported and returned in the getSupportedServiceNames() method of <idl>com.sun.star.lang.XServiceInfo</idl>):

  • <idl>com.sun.star.chart.Diagram</idl>
  • A unique service name that identifies the component. This service name has to be returned in the getServiceName() method of <idl>com.sun.star.lang.XServiceName</idl>.

How Add-Ins Work

The method initialize() from the <idl>com.sun.star.lang.XInitialization</idl> interface is the first method that is called for an add-in. It is called directly after it is created by the <idl>com.sun.star.lang.XMultiServiceFactory</idl> provided by the chart document. This method gets the XChartDocument object.

When initialize() is called, the argument returned is the chart document. Store this as a member to that it can be called later in the refresh() call to access all elements of the chart. The following is an example for the initialize() method of an add-in written in Java:

// XInitialization
public void initialize(Object[] aArguments) throws Exception, RuntimeException {
    if (aArguments.length > 0) {
        // maChartDocument is a member
        // which is set to the parent chart document
        // that is given as first argument
        maChartDocument = (XChartDocument) UnoRuntime.queryInterface(
            XChartDocument.class, aArguments[0]);

        XPropertySet aDocProp = (XPropertySet) UnoRuntime.queryInterface(
            XPropertySet.class, maChartDocument);
        if (aDocProp != null) {
            // set base diagram which will be extended in refresh()
            aDocProp.setPropertyValue("BaseDiagram", "com.sun.star.chart.XYDiagram");
        }

        // remember the draw page, as it is frequently used by refresh()
        // (this is not necessary but convenient)
        XDrawPageSupplier aPageSupp = (XDrawPageSupplier) UnoRuntime.queryInterface(
            XDrawPageSupplier.class, maChartDocument);
        if( aPageSupp != null )
            maDrawPage = (XDrawPage) UnoRuntime.queryInterface(
                XDrawPage.class, aPageSupp.getDrawPage());
    }
}

An important method of an add-in component is refresh() from the <idl>com.sun.star.util.XRefreshable</idl>. This method is called every time the chart is rebuilt. A change of data results in a refresh, but also a resizing or changing of a property that affects the layout calls the refresh() method. For example, the property HasLegend that switches the legend on and off.

To add shapes to the chart, create them once and modify them later during the refresh calls. In the following example, a line is created in initialize() and modified during refresh():

// XInitialization
public void initialize(Object[] aArguments) throws Exception, RuntimeException {
    // get document and page -- see above
    // ...

        // get a shape factory
        maShapeFactory = ...;

        // create top line
        maTopLine = (XShape) UnoRuntime.queryInterface(
            XShape.class, maShapeFactory.createInstance("com.sun.star.drawing.LineShape"));
        maDrawPage.add(maTopLine);

        // make line red and thicker
        XPropertySet aShapeProp = (XPropertySet)UnoRuntime.queryInterface(
            XPropertySet.class, maTopLine);
        aShapeProp.setPropertyValue("LineColor", new Integer(0xe01010));
        aShapeProp.setPropertyValue("LineWidth", new Integer(50));

        // create bottom line
        maBottomLine = (XShape) UnoRuntime.queryInterface(
            XShape.class, maShapeFactory.createInstance("com.sun.star.drawing.LineShape"));
        maDrawPage.add(maBottomLine);

        // make line green and thicker
        aShapeProp = (XPropertySet) UnoRuntime.queryInterface(
            XPropertySet.class, maBottomLine);
        aShapeProp.setPropertyValue("LineColor", new Integer(0x10e010));
        aShapeProp.setPropertyValue("LineWidth", new Integer(50));
    }

}

// XRefreshable
public void refresh() throws RuntimeException {
    // position lines
    // --------------

    // get data
    XChartDataArray aDataArray = (XChartDataArray) UnoRuntime.queryInterface(
        XChartDataArray.class, maChartDocument.getData());
    double aData[][] = aDataArray.getData();

    // get axes
    XDiagram aDiagram = maChartDocument.getDiagram();
    XShape aXAxis = (XShape) UnoRuntime.queryInterface(
        XShape.class, ((XAxisXSupplier) UnoRuntime.queryInterface(
            XAxisXSupplier.class, aDiagram)).getXAxis());
    XShape aYAxis = (XShape) UnoRuntime.queryInterface(
        XShape.class, ((XAxisYSupplier) UnoRuntime.queryInterface(
            XAxisYSupplier.class, aDiagram)).getYAxis());

    // calculate points for hull
    final int nLength = aData.length;
    int i, j;
    double fMax, fMin;

    Point aMaxPtSeq[][] = new Point[1][];
    aMaxPtSeq[0] = new Point[nLength];
    Point aMinPtSeq[][] = new Point[1][];
    aMinPtSeq[0] = new Point[nLength];

    for (i = 0; i < nLength; i++) {
        fMin = fMax = aData[i][1];
        for (j = 1; j < aData[i].length; j++) {
            if (aData[i][j] > fMax)
                fMax = aData[i][j];
            else if (aData[i][j] < fMin)
                fMin = aData[i][j];
        }
        aMaxPtSeq[0][i] = new Point(getAxisPosition(aXAxis, aData[i][0], false),
            getAxisPosition(aYAxis, fMax, true));
        aMinPtSeq[0][i] = new Point(getAxisPosition(aXAxis, aData[i][0], false),
            getAxisPosition(aYAxis, fMin, true));
    }

    // apply point sequences to lines
    try {
        XPropertySet aShapeProp = (XPropertySet) UnoRuntime.queryInterface(
            XPropertySet.class, maTopLine);
        aShapeProp.setPropertyValue("PolyPolygon", aMaxPtSeq);

        aShapeProp = (XPropertySet) UnoRuntime.queryInterface(
            XPropertySet.class, maBottomLine);
        aShapeProp.setPropertyValue("PolyPolygon", aMinPtSeq);
    } catch (Exception ex) {
    }
}

// determine the position of a value along an axis
// bVertical is true for the y-axis and false for the x-axis
private int getAxisPosition(XShape aAxis, double fValue, boolean bVertical) {
    int nResult = 0;

    if (aAxis != null) {
        XPropertySet aAxisProp = (XPropertySet) UnoRuntime.queryInterface(
            XPropertySet.class, aAxis);

        try {
            double fMin, fMax;
            fMin = ((Double) aAxisProp.getPropertyValue("Min")).doubleValue();
            fMax = ((Double) aAxisProp.getPropertyValue("Max")).doubleValue();
            double fRange = fMax - fMin;

            if (fMin <= fValue && fValue <= fMax && fRange != 0) {
                if (bVertical) {
                    // y==0 is at the top, thus take 1.0 - ...
                    nResult = aAxis.getPosition().Y +
                        (int)((double)(aAxis.getSize().Height) * (1.0 - ((fValue - fMin) / fRange)));
                } else {
                    nResult = aAxis.getPosition().X +
                        (int)((double)(aAxis.getSize().Width) * ((fValue - fMin) / fRange));
                }
            }
        } catch (Exception ex) {
        }
    }
return nResult;
}

The subroutine getAxisPosition() is a helper to determine the position of a point inside the diagram coordinates. This add-in calculates the maximum and minimum values for each slice of data points, and creates two polygons based on these points.

How to Apply an Add-In to a Chart Document

There is no method to set an add-in as a chart type for an existing chart in the graphical user interface. To set the chart type, use an API script, for instance, in LibreOffice Basic. The following example sets the add-in with service name “com.sun.star.comp.Chart.JavaSampleChartAddIn” at the current document. To avoid problems, it is advisable to create a chart that has the same type as the one that the add-in sets as BaseDiagram type.

Sub SetAddIn
Dim oDoc As Object
Dim oSheet As Object
Dim oTableChart As Object
Dim oChart As Object
Dim oAddIn As Object

    ' assume that the current document is a spreadsheet
    oDoc = ThisComponent
    oSheet = oDoc.Sheets( 0 )

    ' assume also that you already added a chart
    ' named "MyChart" on the first sheet
    oTableChart = oSheet.Charts.getByName( "MyChart" )

    If Not IsNull( oTableChart ) Then
        oChart = oTableChart.EmbeddedObject
        If Not IsNull( oChart ) Then
            oAddIn = oChart.createInstance( "com.sun.star.comp.Chart.JavaSampleChartAddIn" )
            If Not IsNull( oAddIn ) Then
                oChart.setDiagram( oAddIn )
            End If
        End If
    End If
End Sub

Tip.svg

Tip:
If you want to create an XML-File on your own and want to activate your add-in for a chart; set the attribute chart:class of the chart:chart element to “add-in” and the attribute chart:add-in-name to the service name that uniquely identifies your component.


Heckert GNU white.svg

Content on this page is licensed under the Public Documentation License (PDL).