LibreOffice Developer's Guide: Chapter 10 - Charts
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 = 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 = UnoRuntime.queryInterface(
XChartDocument.class,
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.
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
.
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 = 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 = 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 = 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 = UnoRuntime.queryInterface(
XMultiServiceFactory.class, aChartDoc);
XDiagram aDiagram = 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.
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.
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)
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).
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 = UnoRuntime.queryInterface(XChartData.class, aRange);
maChartDocument.attachData(maChartData);
}
public void run() {
try {
// show a sub title to inform about last update
UnoRuntime.queryInterface(
XPropertySet.class, maChartDocument).setPropertyValue(
"HasSubTitle", Boolean.TRUE);
// start listening for death of spreadsheet
UnoRuntime.queryInterface(XComponent.class,
maSheetDoc).addEventListener(this);
// start listening for death of chart
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
UnoRuntime.queryInterface(XComponent.class,
maSheetDoc).removeEventListener(this);
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 = "Last Update: " + new java.util.Date();
try {
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.
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.
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 = 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", Integer.valueOf(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", Integer.valueOf(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", Integer.valueOf(ChartDataCaption.VALUE));
aPointProp.setPropertyValue("CharHeight", new Float(14.0));
aPointProp.setPropertyValue("CharColor", Integer.valueOf(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", 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 = UnoRuntime.queryInterface(XPropertySet.class, maDiagram);
Boolean aTrue = Boolean.TRUE;
aDiaProp.setPropertyValue("Dim3D", aTrue);
aDiaProp.setPropertyValue("Deep", aTrue);
// from service Chart3DBarProperties:
aDiaProp.setPropertyValue("SolidType", Integer.valueOf(
com.sun.star.chart.ChartSolidType.CYLINDER));
// change floor color to Magenta6
XPropertySet aFloor = UnoRuntime.queryInterface(
X3DDisplay.class, maDiagram).getFloor();
aFloor.setPropertyValue("FillColor", Integer.valueOf(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", Integer.valueOf(0xff3333));
aDiaProp.setPropertyValue("D3DSceneLightOn1", 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.
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:
- com.sun.star.lang.XInitialization
- com.sun.star.util.XRefreshable
- com.sun.star.lang.XServiceName
- com.sun.star.lang.XServiceInfo
- com.sun.star.lang.XTypeProvider 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 com.sun.star.lang.XServiceInfo):
- com.sun.star.chart.Diagram
- A unique service name that identifies the component. This service name has to be returned in the
getServiceName()
method of com.sun.star.lang.XServiceName.
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 = UnoRuntime.queryInterface(
XChartDocument.class, aArguments[0]);
XPropertySet aDocProp = 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 = UnoRuntime.queryInterface(
XDrawPageSupplier.class, maChartDocument);
if( aPageSupp != null )
maDrawPage = 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 = UnoRuntime.queryInterface(
XShape.class, maShapeFactory.createInstance("com.sun.star.drawing.LineShape"));
maDrawPage.add(maTopLine);
// make line red and thicker
XPropertySet aShapeProp = UnoRuntime.queryInterface(
XPropertySet.class, maTopLine);
aShapeProp.setPropertyValue("LineColor", Integer.valueOf(0xe01010));
aShapeProp.setPropertyValue("LineWidth", Integer.valueOf(50));
// create bottom line
maBottomLine = UnoRuntime.queryInterface(
XShape.class, maShapeFactory.createInstance("com.sun.star.drawing.LineShape"));
maDrawPage.add(maBottomLine);
// make line green and thicker
aShapeProp = UnoRuntime.queryInterface(
XPropertySet.class, maBottomLine);
aShapeProp.setPropertyValue("LineColor", Integer.valueOf(0x10e010));
aShapeProp.setPropertyValue("LineWidth", Integer.valueOf(50));
}
}
// XRefreshable
public void refresh() throws RuntimeException {
// position lines
// --------------
// get data
XChartDataArray aDataArray = UnoRuntime.queryInterface(
XChartDataArray.class, maChartDocument.getData());
double aData[][] = aDataArray.getData();
// get axes
XDiagram aDiagram = maChartDocument.getDiagram();
XShape aXAxis = UnoRuntime.queryInterface(
XShape.class, UnoRuntime.queryInterface(
XAxisXSupplier.class, aDiagram ).getXAxis() );
XShape aYAxis = UnoRuntime.queryInterface(
XShape.class, 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 = UnoRuntime.queryInterface(
XPropertySet.class, maTopLine );
aShapeProp.setPropertyValue( "PolyPolygon", aMaxPtSeq );
aShapeProp = 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 = 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 )
{
nResult = aAxis.getPosition().Y +
(int)((aAxis.getSize().Height) *
(1.0 - (( fValue - fMin ) / fRange )));
}
else
{
nResult = aAxis.getPosition().X +
(int)((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