LibreOffice Developer's Guide: Chapter 10 - Charts

    From The Document Foundation Wiki

    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 com.sun.star.table.XTableChartsSupplier interface of the com.sun.star.sheet.Spreadsheet service is used to create and insert a new chart into a Calc document. This creates a chart that uses data from the com.sun.star.chart.XChartDataArray 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 com.sun.star.table.XTableChartsSupplier interface, that is required by the service com.sun.star.sheet.Spreadsheet. 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 com.sun.star.sheet.XSpreadsheet 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 com.sun.star.drawing.XDrawPagesSupplier of a draw or presentation document. To retrieve a single draw page, use com.sun.star.container.XIndexAccess.

    A spreadsheet document is also a com.sun.star.drawing.XDrawPagesSupplier 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 com.sun.star.container.XIndexAccess of the container, returned by getDrawPages().

    A spreadsheet draw page can be acquired directly at the corresponding sheet object. A single sheet supports the service com.sun.star.sheet.Spreadsheet that supplies a single page, com.sun.star.drawing.XDrawPageSupplier, 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 com.sun.star.chart.XChartDocument. 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 com.sun.star.chart.XYDiagram, 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 com.sun.star.table.XTableChartsSupplier of the service com.sun.star.sheet.Spreadsheet, which is available at single spreadsheets.

    To get all OLE-shapes of a draw page, use the interface com.sun.star.drawing.XDrawPage, that is based on com.sun.star.container.XIndexAccess. 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 com.sun.star.chart.ChartDocument. 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 com.sun.star.chart.ChartArea 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 com.sun.star.chart.ChartData. 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 com.sun.star.chart.ChartDataArray 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 com.sun.star.chart. The interfaces in this diagram are com.sun.star.chart.XChartDocument, com.sun.star.drawing.XShape, com.sun.star.lang.XComponent, and com.sun.star.beans.XPropertySet.

    The chart document model passes its elements through the interface com.sun.star.chart.XChartDocument. 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 com.sun.star.chart.XChartDocument interface of the chart document model using the method getData. The current implementation of LibreOffice charts provides a com.sun.star.chart.XChartDataArray interface, derived from com.sun.star.chart.XChartData and supports the service com.sun.star.chart.ChartDataArray.

    Note pin.svg

    Note:
    Note that the interface definition of com.sun.star.chart.XChartDocument 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 com.sun.star.chart.XChartDataArray 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 com.sun.star.chart.ChartDataRowSource.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 com.sun.star.table.CellRange 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 com.sun.star.lang.XEventListener to get notified when the spreadsheet document or drawing document are closed.

    It also implements a com.sun.star.chart.XChartDataChangeEventListener 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 com.sun.star.chart.Dim3DDiagram 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 com.sun.star.chart.StackableDiagram 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 com.sun.star.chart.ChartStatistics. 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 com.sun.star.chart.PieDiagram service points to the com.sun.star.chart.Dim3DDiagram service.

    The services com.sun.star.chart.AreaDiagram, com.sun.star.chart.LineDiagram, and com.sun.star.chart.BarDiagram 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 com.sun.star.style.CharacterProperties) affecting the labels and line properties (see com.sun.star.drawing.LineProperties) 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 com.sun.star.chart.XYDiagram, 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 com.sun.star.chart.PieDiagram supports no axis at all. The com.sun.star.chart.BarDiagram supports all kinds of axis. Note that the z-Axis is only supported in a three-dimensional chart. Note that there is a com.sun.star.chart.ChartTwoAxisXSupplier that includes the com.sun.star.chart.ChartAxisXSupplier 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 com.sun.star.chart.XDiagram 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 com.sun.star.drawing.LineProperties and com.sun.star.drawing.FillProperties. They also support com.sun.star.style.CharacterProperties 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 com.sun.star.chart.LineDiagram or com.sun.star.chart.XYDiagram.

    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 com.sun.star.chart.Dim3DDiagram.

    In addition to this property, bar charts support a property called Deep (see service com.sun.star.chart.BarDiagram) that arranges the series of a bar chart along the z-axis, which in a chart, points away from the spectator. The service com.sun.star.chart.Chart3DBarProperties 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 com.sun.star.chart.ChartSolidType).

    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 com.sun.star.chart.ChartPieSegmentProperties 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 com.sun.star.chart.StockDiagram 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 com.sun.star.frame.XModel. 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:

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

    How Add-Ins Work

    The method initialize() from the com.sun.star.lang.XInitialization interface is the first method that is called for an add-in. It is called directly after it is created by the com.sun.star.lang.XMultiServiceFactory 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 com.sun.star.util.XRefreshable. 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).