Table of Contents
OpenOffice.org includes a powerful charting function. You may place a chart in a word processing document, a drawing, a presentation, or a spreadsheet.
Before proceeding to the actual XML, let’s define some of the terms that OpenOffice.org uses when talking about charts. Consider the spreadsheet section shown in Figure 8.1, “Chart Derived from Spreadsheet”. In this chart, there are three categories: the months of the first quarter of the year. Each of the items sold has a series of three data points or values. The values are the numbers in the spreadsheet, and they appear on the y-axis.
It is also possible to describe a chart where each row is a series of data points, and the columns are the categories. This produces a chart of sales by items sold, shown in Figure 8.2, “Chart with Series in Rows”. Here, each month has a series of four data points, one for each item.
In an x-y chart (such as the scatter chart in Figure 8.3, “Scatter Chart”), the x-axis is called the domain, as in the domain of an algebraic function.
Rather than being inserted directly into the content.xml, the chart is inserted as a <draw:object> element. This element will have an xlink:href attribute of the form #./Object 1. If you know your URLs, you see that this points to a subdirectory named Object 1. Inside that directory you will find another content.xml file that contains the chart data and style information.
The object’s directory also contains a styles.xml file, but it is just a placeholder; Its root element is a <office:document-styles> that contains an empty <office:styles> element. If you are creating a chart from scratch, you may omit the styles.xml file.
In addition to creating the link and the subdirectory, you must also add entries in the META-INF/manifest.xml file in order for OpenOffice.org to locate the chart. The entries must define the paths for the subdirectory and the content.xml and style.xml file in the subdirectory (if present).
No matter which type of document you insert a chart into, the chart’s <draw:object> element will contain these attributes:
The <draw:object> element for a chart embedded in a word processing document will automatically be given a draw:name attribute. You must explicitly name the charts in spreadsheets and drawings for them to get this attribute.
Since the object is considered to be a frame in a word processing document, the <draw:object> element will contain all the attributes described in the section called “Body Information for Frames” with one exception. Instead of an fo:min-height attribute, the object will have a svg:height attribute to specify the height of the chart. Example 8.1, “XML for a Chart in a Word Processing Document” shows the XML for a chart anchored as a paragraph.
Example 8.1. XML for a Chart in a Word Processing Document
<text:p text:style-name="Standard">
<draw:object draw:style-name="fr1" draw:name="Object1"
xlink:href="#./ObjBFFFE241" xlink:type="simple"
xlink:show="embed" xlink:actuate="onLoad"
text:anchor-type="paragraph"
svg:x="5.913cm" svg:y="2.23cm"
svg:width="7.997cm" svg:height="7.001cm"
draw:z-index="0"/>
</text:p>
When you insert a chart into a drawing, the <draw:object> gets these attributes:
Charts in spreadsheets are special; they display data that is within the rows and columns of the spreadsheet. Here are the <draw:object> attributes in question:
Example 8.2, “XML for Chart in Spreadsheet” shows the XML that embeds a chart shown in Figure 8.1, “Chart Derived from Spreadsheet” into a spreadsheet as.
Example 8.2. XML for Chart in Spreadsheet
<draw:object draw:name="spreadsheetchart1"
draw:notify-on-update-of-ranges="Sheet1.A1:Sheet1.E4"
table:end-cell-address="Sheet1.D22"
table:end-x="0.5921inch" table:end-y="0.0461inch"
svg:x="0.1094inch" svg:y="0.1362inch"
svg:width="3.1496inch" svg:height="2.7559inch"
draw:z-index="0"
xlink:href="#./Object 1" xlink:type="simple"
xlink:show="embed" xlink:actuate="onLoad"/>
You find the actual chart data and specifications in the content.xml file that is in the object subdirectory. This file follows the same general pattern that we have seen for content files of all the other document types. Its root <office:document-content> element will have an office:class attribute of chart. The first child of the <office:document> is an <office:automatic-styles> element that contains all the styles to control the chart’s presentation.
The styles are followed by the <office:body>, which contains a <chart:chart> element. This has child elements that specify:
Now let’s take a closer look at the chart:chart element and its attribute and children. The chart:class attribute tells what kind of chart to draw: line, area (stacked areas), circle (pie chart), ring, scatter, radar (called “net”) in OpenOffice.org, bar (vertical bars), stock, and add-in.
The <chart:chart> element has these children, in this order:
The <chart:title> and <chart:subtitle> elements have svg:x and svg:y attributes for positioning, and a chart:style-name for presentation, They contain a <text:p> element that gives the title (or subtitle) text, as shown in Example 8.3, “Example of Chart Title and Subtitle”
Example 8.3. Example of Chart Title and Subtitle
<chart:title svg:x="2.225cm" svg:y="0.28cm" chart:style-name="ch2">
<text:p>Sales Report</text:p>
</chart:title>
<chart:subtitle svg:x="4.716cm" svg:y="0.814cm" chart:style-name="ch3">
<text:p>First Quarter</text:p>
</chart:subtitle>
The <chart:legend> element has a chart:legend-position attribute that gives the relative location of the legend; top, left, bottom, or right, and an absolute svg:x and svg:y position. It also has a chart:style-name attribute to determine the presentation of the text in the legend.
The next element in line is a <chart:plot-area> element is where the action is. It establishes the location of the chart with the typical svg:x, svg:y, svg:width, and svg:height attributes.
If you are creating a chart from a spreadsheet, you will specify the source of the data in the table:cell-range-address attribute. Depending on whether this range of cells contains labels for the rows or columns, you must set chart:data-source-has-labels to none, row, column, or both. The <chart:table-number-list> is not used in the XML format, and should be set to 0.
You may be tempted to overlook the standard chart:style-name attribute, but that would be a mistake, because that style is just packed with information.
Example 8.4, “Plot Area and Style” shows the opening <chart:plot-area> element (and its associated style) for the bar chart in Figure 8.1, “Chart Derived from Spreadsheet”
Example 8.4. Plot Area and Style
<chart:plot-area chart:style-name="ch5"
table:cell-range-address="Sheet1.$A$1:.$E$4"
chart:data-source-has-labels="both"
chart:table-number-list="0"
svg:x="0.16cm" svg:y="1.69cm"
svg:width="5.997cm" svg:height="5.17cm">
<!-- the associated style -->
<style:style style:name="ch5" style:family="chart">
<style:properties
chart:series-source="columns"
chart:vertical="false"
chart:connect-bars="false"
chart:lines-used="0" chart:lines="false"
chart:splines="0" chart:symbol="-3"
chart:data-label-number="none"
chart:data-label-text="false"
chart:data-label-symbol="false"
chart:mean-value="false" chart:error-margin="0"
chart:error-lower-limit="0" chart:error-upper-limit="0"
chart:error-category="none" chart:error-percentage="0"
chart:regression-type="none"
chart:stock-updown-bars="false"
chart:stock-with-volume="false"
chart:three-dimensional="false"
chart:deep="false" />
</style:style>
Within the <chart:plot-area> element are two <chart:axis> elements; the first for the x-axis and the second for the y-axis. For pie charts, there is only one axis; the y-axis.
Each <chart:axis> has a chart:name attribute, which is either primary-x or primary-y. The chart:class attribute tells whether the axis represents a category, value, or domain. (This last is for the x-axis of a scatter chart.) Of course, there’s a chart:style-name, and the style it refers to also contains oodles of information about how to display the axis:
Don’t set this to false unless you have a compelling reason to do so. Graphs without labels are confusing at best and misleading or useless at worst.
If your axis has a title, then the <chart:axis> element will have a <chart:title> child element, formatted exactly like the chart’s main title.
The last child of the <chart:axis> element is the optional <chart:grid> element. Its <chart:class> attribute tells whether you want grid lines at major intervals only (major), or at both major and minor intervals (minor). For no grid lines, omit the element.
We still haven’t finished the <chart:plot-area> element yet; after specifying the axes and grid, we must now define what data series are in the chart.
The XML will continue with one <chart:series> element for each data series in the chart. It has a chart:style-name that refers to a style for that data series.
For line charts, this style needs to specify only chart:symbols (set it to -1), draw:stroke (solid is a good choice for value) and svg:stroke-color. For bar and pie charts, you need to specify only draw:fill-color.
For line and bar charts, each <chart:series> element contains a <chart:data-point> element; its chart:repeated attribute tells how many data points are in the series. A pie chart has only one chart:series element that contains multiple chart:data-point elements; one for each pie slice, and each will have its own chart:style-name attribute.
The chart wall is the area bounded by the axes (as opposed to the plot area, which is the chart entire). The empty <chart:wall> element has a chart:style-name attribute, used primarily to set the background color. The chart floor is applicable only to three-dimensional charts, and will be covered in that section.
This has been an immense amount of explanation, and we need to see how this all fits together. Example 8.5, “Styles and Content for a Bar Chart” shows the XML (so far) for the chart shown in Figure 8.1, “Chart Derived from Spreadsheet”,
Example 8.5. Styles and Content for a Bar Chart
<chart:chart chart:class="bar" chart:style-name="ch1"
svg:width="8cm" svg:height="7cm" >
<chart:title svg:x="2.564cm" svg:y="0.14cm" chart:style-name="ch2">
<text:p>Sales Report</text:p>
</chart:title>
<chart:subtitle svg:x="2.741cm" svg:y="0.953cm" chart:style-name="ch3">
<text:p>First Quarter</text:p>
</chart:subtitle>
<chart:legend chart:legend-position="right"
svg:x="6.476cm" svg:y="2.833cm" chart:style-name="ch4"/>
<chart:plot-area chart:style-name="ch5"
table:cell-range-address="Sheet1.$A$1:.$E$4"
chart:data-source-has-labels="both" chart:table-number-list="0"
svg:x="0.16cm" svg:y="1.69cm"
svg:width="5.997cm" svg:height="5.17cm">
<chart:axis chart:class="category"
chart:name="primary-x" chart:style-name="ch6">
<chart:title svg:x="3.345cm" svg:y="6.484cm"
chart:style-name="ch7">
<text:p>Month</text:p>
</chart:title>
</chart:axis>
<chart:axis chart:class="value"
chart:name="primary-y" chart:style-name="ch8">
<chart:title svg:x="0.16cm" svg:y="4.52cm"
chart:style-name="ch7">
<text:p>Units Sold</text:p>
</chart:title>
<chart:grid chart:class="major"/>
</chart:axis>
<chart:series chart:style-name="ch9">
<chart:data-point chart:repeated="3"/>
</chart:series>
<chart:series chart:style-name="ch10">
<chart:data-point chart:repeated="3"/>
</chart:series>
<chart:series chart:style-name="ch11">
<chart:data-point chart:repeated="3"/>
</chart:series>
<chart:series chart:style-name="ch12">
<chart:data-point chart:repeated="3"/>
</chart:series>
<chart:wall chart:style-name="ch13"/>
<chart:floor chart:style-name="ch14"/>
</chart:plot-area>
<!-- data table follows -->
</chart:chart>
Example 8.6, “Styles for Bar Chart Excerpt” shows the corresponding styles, cut down to the absolute minimum necessary. (For example, in style ch9, the bars have no labels or statistics, so we have been able to dispense with attributes such as chart:data-label-number and fo:font-family and chart:error-margin. For variety, we have used fo:font-family on some styles to explicitly specify a font, and in others we have used style:font-family-generic to specify the font. Comments have been added to indicate which styles apply to which parts of the chart.
Example 8.6. Styles for Bar Chart Excerpt
<!-- style for <chart:chart> element -->
<style:style style:name="ch1" style:family="chart">
<style:properties
draw:stroke="solid" draw:fill="solid" draw:fill-color="#ffffff" />
</style:style>
<!-- style for <chart:title> element -->
<style:style style:name="ch2" style:family="chart">
<style:properties fo:font-family="'Bitstream Vera Sans'"
style:font-family-generic="swiss"
fo:font-size="13pt" />
</style:style>
<!-- style for <chart:subtitle> element -->
<style:style style:name="ch3" style:family="chart">
<style:properties fo:font-family="'Bitstream Vera Sans'"/>
</style:style>
<!-- style for <chart:legend> element -->
<style:style style:name="ch4" style:family="chart">
<style:properties style:font-family-generic="swiss"
fo:font-size="6pt"/>
</style:style>
<!-- style for <chart:plot-area> element -->
<style:style style:name="ch5" style:family="chart">
<style:properties chart:vertical="false" chart:lines-used="0"
chart:connect-bars="false" chart:series-source="columns"/>
</style:style>
<!-- style for first <chart:axis> (x-axis) -->
<style:style style:name="ch6" style:family="chart"
style:data-style-name="N0">
<style:properties chart:display-label="true"
chart:tick-marks-major-inner="false"
chart:tick-marks-major-outer="true"
draw:stroke="solid" svg:stroke-width="0cm"
style:font-family-generic="swiss" fo:font-size="7pt"/>
</style:style>
<!-- style for <chart:title> in both axes -->
<style:style style:name="ch7" style:family="chart">
<style:properties fo:font-family="'Bitstream Vera Sans'"
fo:font-size="9pt"/>
</style:style>
<!-- style for the second <chart:axis> element (y-axis) -->
<style:style style:name="ch8" style:family="chart"
style:data-style-name="N0">
<style:properties chart:display-label="true"
chart:tick-marks-major-inner="false"
chart:tick-marks-major-outer="true"
fo:font-family="'Bitstream Vera Sans'"
fo:font-size="7pt"/>
</style:style>
<!-- style for the first <chart:series> element -->
<style:style style:name="ch9" style:family="chart">
<style:properties draw:fill-color="#9999ff"/>
</style:style>
<!-- style for the second <chart:series> element -->
<style:style style:name="ch10" style:family="chart">
<style:properties draw:fill-color="#993366"/>
</style:style>
<!-- style for the third <chart:series> element -->
<style:style style:name="ch11" style:family="chart">
<style:properties draw:fill-color="#ffffcc"/>
</style:style>
<!-- style for the fourth <chart:series> element -->
<style:style style:name="ch12" style:family="chart">
<style:properties draw:fill-color="#ccffff"/>
</style:style>
<!-- style for the <chart:wall> element -->
<style:style style:name="ch13" style:family="chart">
<style:properties draw:fill="solid" draw:fill-color="#ffffff"/>
</style:style>
<!-- style for the <chart:floor> element -->
<style:style style:name="ch14" style:family="chart">
<style:properties draw:fill-color="#999999"/>
</style:style>
Following the plot area is a table containing the data to be displayed. Even if you are creating a chart from a spreadsheet, OpenOffice.org does not look at the spreadsheet cells for the data—it looks at the internal table in the chart object’s content.xml file.
Compared to the chart and plot area definitions, the data table is positively anticlimactic. The <table:table> element has a table:name attribute which is set to local-table.
The first child of the <table:table> is a <table:table-header-columns> element that contains an empty <table:table-column> element. This is followed by a <table:table-header-rows> element that contains the first row of the table. Finally, a <table:table-rows> element contains the remaining data, one <table:table-row> at a time.
Example 8.7, “Table for Bar Chart” gives an excerpt of the table that was used in Figure 8.1, “Chart Derived from Spreadsheet”.
Example 8.7. Table for Bar Chart
<table:table table:name="local-table">
<table:table-header-columns>
<table:table-column/>
</table:table-header-columns>
<table:table-columns>
<table:table-column table:number-columns-repeated="4"/>
</table:table-columns>
<table:table-header-rows>
<table:table-row>
<table:table-cell>
<text:p/>
</table:table-cell>
<table:table-cell table:value-type="string">
<text:p>Widgets</text:p>
</table:table-cell>
<table:table-cell table:value-type="string">
<text:p>Thingies</text:p>
</table:table-cell>
<table:table-cell table:value-type="string">
<text:p>Doodads</text:p>
</table:table-cell>
<table:table-cell table:value-type="string">
<text:p>Whatzits</text:p>
</table:table-cell>
</table:table-row>
</table:table-header-rows>
<table:table-rows>
<table:table-row>
<table:table-cell table:value-type="string">
<text:p>Jan</text:p>
</table:table-cell>
<table:table-cell table:value-type="float" table:value="10">
<text:p>10</text:p>
</table:table-cell>
<table:table-cell table:value-type="float" table:value="20">
<text:p>20</text:p>
</table:table-cell>
<table:table-cell table:value-type="float" table:value="29">
<text:p>29</text:p>
</table:table-cell>
<table:table-cell table:value-type="float" table:value="15">
<text:p>15</text:p>
</table:table-cell>
</table:table-row>
<!-- February row, similar to January above -->
<table:table-row>
<table:table-cell table:value-type="string">
<text:p>Mar</text:p>
</table:table-cell>
<table:table-cell table:value-type="float" table:value="22">
<text:p>22</text:p>
</table:table-cell>
<table:table-cell table:value-type="float" table:value="27">
<text:p>27</text:p>
</table:table-cell>
<table:table-cell table:value-type="float" table:value="31">
<text:p>31</text:p>
</table:table-cell>
<table:table-cell table:value-type="float" table:value="29">
<text:p>29</text:p>
</table:table-cell>
</table:table-row>
</table:table-rows>
</table:table>
We are now prepared to do a rather complex case study. We will begin with an OpenOffice.org spreadsheet that contains the results of a survey[15], as shown in Figure 8.5, “Spreadsheet with Survey Responses”. Our goal is to create a word processing document. Each question will be displayed in a two-column section. The left column will contain the question and the results in text form; the right column will contain a pie chart of the responses to the question. The result will look like Figure 8.6, “Text Document with Survey Responses”
While we did write the OOoTransform.java program to create .zip format files, writing a program to create zip files with subdirectories seems a bit too much like work. Instead, we will write a shell script that creates a temporary directory, runs an Perl program to create the individual files in that directory, and then zips those files into our finished word processing document. Example 8.8, “Shell Script for Chart Document Creation” shows the script for the bash shell. It takes two parameters: the input file name (without the .sxc extension) and the name of the temporary directory where the XML files are stored.
Example 8.8. Shell Script for Chart Document Creation
#!/bin/bash # remember current directory curr_dir=$PWD # create temporary directory # (and all its intervening levels) mkdir --parents $2 --mode=0755 # run the perl program perl chartmaker.pl $1.sxc $2 # remove the word processing file in the # current directory (it may not exist) rm $curr_dir/$1.sxw # switch to the temporary directory cd $2 # create the word processing document # by zipping all the files in the temporary dir zip -r $curr_dir/$1.sxw * # return to the original directory cd $curr_dir # and remove temporary directory rm -rf $2
If you don’t have a way to make a directory with all its intervening levels, use the utility program in the section called “Creating Multiple Directory Levels”.
The Perl code is fairly lengthy, though most of it is just “boilerplate.” We have broken it into sections. for ease of analysis. We will use the XML::DOM module to parse the input file for use with the Document Object Model. We won’t use it to create the output file; we’ll just create raw XML text and put it into files. Let’s begin with the variable declarations and some utility routines.
#!/usr/bin/perl
use Archive::Zip;
use XML::DOM;
use warnings;
use strict;
#
# Command line arguments:
# input file
# output directory
my $doc; # the DOM document
my $rows; # all the <table:table-row> elements
my $n_rows; # number of rows
my $row; # current row number
my $col; # current column number
my @data; # contents of current row
my $sum; # sum of the row items
my @legends; # legends for the graph
my $percent; # string holding nicely formatted percent value
#
# Extract the content.xml file from the given
# filename, parse it, and return a DOM object.
#
sub makeDOM
{
my ($filename) = shift;
my $zip = Archive::Zip->new( $filename );
my $parser = new XML::DOM::Parser;
my $doc;
$zip->extractMember( "content.xml", "$ARGV[1]/workfile" );
$doc = $parser->parsefile( "$ARGV[1]/workfile" );
unlink( "$ARGV[1]/workfile" );
return $doc;
}
#
# $node - starting node
# $name - name of desired child element
# returns the node's first child with the given name
#
sub getFirstChildElement
{
my ($node, $name) = @_;
for my $child ($node->getChildNodes)
{
if ($child->getNodeName eq $name)
{
return $child;
}
}
return undef;
}
#
# $node - starting node
# $name - name of desired sibling element
# returns the node's next sibling with the given name
#
sub getNextSiblingElement
{
my ($node, $name) = @_;
while (($node = $node->getNextSibling) &&
$node->getNodeName ne $name)
{
# do nothing
;
}
return $node;
}
#
# $itemref - Reference to an array to hold the row contents
# $rowNode - a table row
#
sub getRowContents
{
my ($itemRef, $rowNode) = @_;
my $cell; # a cell node
my $value;
my $n_repeat;
my $i;
my $para; # <text:p> node
@{$itemRef} = ();
$cell = getFirstChildElement( $rowNode, "table:table-cell" );
while ($cell)
{
$n_repeat = $cell->getAttribute("table:number-columns-repeated");
$n_repeat = 1 if (!$n_repeat);
$value = "";
$para = getFirstChildElement( $cell, "text:p" );
while ($para)
{
$value .= $para->getFirstChild->getNodeValue . " ";
$para = getNextSiblingElement( $para, "text:p" );
}
chop $value;
for ($i=0; $i < $n_repeat; $i++)
{
push @{$itemRef}, $value;
}
$cell = getNextSiblingElement( $cell, "table:table-cell" );
}
}
We start the main program by parsing the input file and emitting boilerplate for the styles.xml file, which is devoted to setting up the page dimensions.
print "Processing $ARGV[0]\n";
$doc = makeDOM( $ARGV[0] );
open STYLEFILE, ">$ARGV[1]/styles.xml";
print STYLEFILE <<"STYLEINFO";
<!DOCTYPE office:document-styles
PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "office.dtd">
<office:document-styles xmlns:office="http://openoffice.org/2000/office"
xmlns:style="http://openoffice.org/2000/style"
xmlns:text="http://openoffice.org/2000/text"
xmlns:table="http://openoffice.org/2000/table"
xmlns:draw="http://openoffice.org/2000/drawing"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:number="http://openoffice.org/2000/datastyle"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:chart="http://openoffice.org/2000/chart"
xmlns:dr3d="http://openoffice.org/2000/dr3d"
xmlns:math="http://www.w3.org/1998/Math/MathML"
xmlns:form="http://openoffice.org/2000/form"
xmlns:script="http://openoffice.org/2000/script"
office:version="1.0">
<office:automatic-styles>
<style:page-master style:name="pm1">
<style:properties
fo:page-width="21.59cm" fo:page-height="27.94cm"
style:num-format="1" style:print-orientation="portrait"
fo:margin-top="1.27cm" fo:margin-bottom="1.27cm"
fo:margin-left="1.27cm" fo:margin-right="1.27cm"
style:writing-mode="lr-tb" style:footnote-max-height="0cm">
<style:columns fo:column-count="0" fo:column-gap="0cm"/>
</style:properties>
</style:page-master>
</office:automatic-styles>
<office:master-styles>
<style:master-page style:name="Standard"
style:page-master-name="pm1"/>
</office:master-styles>
</office:document-styles>
STYLEINFO
close STYLEFILE;
Up until now, we haven’t had any objects inserted into our documents, so we haven’t had to create a manifest file. Now we have to do so, as that is how OpenOffice.org locates the chart. This is the boilerplate for the main directory files; as we create the charts, we will append elements to the manifest file.
#
# Create the directory for the manifest file
# and the header of the manifest file
#
mkdir ( "$ARGV[1]/META-INF", 0755 );
open MANIFESTFILE, ">$ARGV[1]/META-INF/manifest.xml";
print MANIFESTFILE <<"MANIFEST_HEADER";
<!DOCTYPE manifest:manifest
PUBLIC "-//OpenOffice.org//DTD Manifest 1.0//EN" "Manifest.dtd">
<manifest:manifest xmlns:manifest="http://openoffice.org/2001/manifest">
<manifest:file-entry
manifest:media-type="application/vnd.sun.xml.writer"
manifest:full-path="/"/>
<manifest:file-entry
manifest:media-type="text/xml" manifest:full-path="content.xml"/>
<manifest:file-entry
manifest:media-type="text/xml" manifest:full-path="styles.xml"/>
MANIFEST_HEADER
Because we are not creating a settings.xml file, OpenOffice.org will think your document has not been saved when you first load it.
And now, the main event: the content.xml file. First, the boilerplate for the styles that we will need for the text and the chart itself:
#
# Create the main content.xml file and its
# header information
#
open CONTENTFILE, ">$ARGV[1]/content.xml";
print CONTENTFILE <<"CONTENT_HEADER";
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE office:document-content
PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN"
"office.dtd">
<office:document-content
xmlns:office="http://openoffice.org/2000/office"
xmlns:style="http://openoffice.org/2000/style"
xmlns:text="http://openoffice.org/2000/text"
xmlns:table="http://openoffice.org/2000/table"
xmlns:draw="http://openoffice.org/2000/drawing"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:number="http://openoffice.org/2000/datastyle"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:chart="http://openoffice.org/2000/chart"
xmlns:dr3d="http://openoffice.org/2000/dr3d"
xmlns:math="http://www.w3.org/1998/Math/MathML"
xmlns:form="http://openoffice.org/2000/form"
xmlns:script="http://openoffice.org/2000/script"
office:class="text"
office:version="1.0">
<office:script/>
<office:automatic-styles>
<!-- style for question title -->
<style:style style:name="hdr1" style:family="paragraph">
<style:properties
fo:font-family="Bitstream Charter"
style:font-family-generic="roman"
style:font-pitch="variable"
fo:font-size="14pt"
fo:font-style="italic"/>
</style:style>
<!-- style for text summary of results -->
<style:style style:name="info" style:family="paragraph">
<style:properties
fo:font-family="Bitstream Charter"
style:font-family-generic="roman"
style:font-pitch="variable"
fo:font-size="10pt">
<style:tab-stops>
<style:tab-stop style:position="3.5cm"
style:type="right"/>
<style:tab-stop style:position="5cm"
style:type="char"
style:char="."/>
</style:tab-stops>
</style:properties>
</style:style>
<!-- style to force a move to column two -->
<style:style style:name="colBreak" style:family="paragraph">
<style:properties fo:break-before="column"/>
</style:style>
<!-- set column widths -->
<style:style style:name="Sect1" style:family="section">
<style:properties
text:dont-balance-text-columns="true">
<style:columns fo:column-count="2">
<style:column style:rel-width="3968*"
fo:margin-left="0cm" fo:margin-right="0cm"/>
<style:column style:rel-width="7370*"
fo:margin-left="0cm" fo:margin-right="0cm"/>
</style:columns>
</style:properties>
</style:style>
<!-- style for chart frame -->
<style:style style:name="fr1" style:family="graphics">
<style:properties style:wrap="run-through"
style:vertical-pos="middle"
style:horizontal-pos="from-left"/>
</style:style>
</office:automatic-styles>
<office:body>
CONTENT_HEADER
That finishes the static portion of the content file. We now grab all the rows, and, for each row in the table:
After processing all the rows, we close the remaining tags in the content.xml and manifest.xml files, and close them. This finishes the main program.
$rows = $doc->getElementsByTagName( "table:table-row" );
getRowContents( \@legends, $rows->item(0));
$n_rows = $rows->getLength;
for ($row=1; $row<$n_rows; $row++)
{
getRowContents( \@data, $rows->item($row));
next if (!$data[0]); # skip rows without a question
# calculate total number of responses
$sum = 0;
for ($col=1; $col < scalar(@data); $col++)
{
$sum += $data[$col];
}
print CONTENTFILE qq!<text:section text:style-name="Sect1"!;
print CONTENTFILE qq! text:name="Section$row">!;
print CONTENTFILE qq!<text:h text:style-name="hdr1" text:level="1">!;
print CONTENTFILE qq!$row. $data[0]</text:h>\n!;
for ($col=1; $col < scalar(@data); $col++)
{
$percent = sprintf(" (%.2f%%)", 100*$data[$col]/$sum);
print CONTENTFILE qq!<text:p text:style-name="info">!;
print CONTENTFILE qq!$legends[$col]<text:tab-stop/>$data[$col]!;
print CONTENTFILE qq!<text:tab-stop/>$percent</text:p>\n!;
}
# now insert the reference to the graph
print CONTENTFILE qq!<text:p text:style-name="colBreak">!;
print CONTENTFILE qq!<draw:object draw:style-name="fr1"
draw:name="Object$row"
svg:x="7cm" svg:width="8cm" svg:height="7cm"
xlink:href="#./Object$row" xlink:type="simple"
xlink:show="embed" xlink:actuate="onLoad"/></text:p>\n!;
print CONTENTFILE qq!</text:section>\n!;
# Create a directory for the chart
mkdir ( "$ARGV[1]/Object$row", 0755 );
construct_chart( \@legends, \@data, $row );
append_manifest( $row );
}
print CONTENTFILE <<"CONTENT_FOOTER";
</office:body>
</office:document-content>
CONTENT_FOOTER
close CONTENTFILE;
print MANIFESTFILE "</manifest:manifest>\n";
close MANIFESTFILE;
Let’s handle the easy subroutine first—adding the path information to the manifest file. The append_manifest subroutine takes one parameter: the chart number.
#
# Append data to the manifest file;
# the parameter is the chart number
#
sub append_manifest
{
my $number = shift;
print MANIFESTFILE <<"ADD_MANIFEST";
<manifest:file-entry
manifest:media-type="application/vnd.sun.xml.chart"
manifest:full-path="Object$number/"/>
<manifest:file-entry
manifest:media-type="text/xml"
manifest:full-path="Object$number/content.xml"/>
<manifest:file-entry
manifest:media-type="text/xml"
manifest:full-path="Object$number/styles.xml"/>
ADD_MANIFEST
}
Finally, the subroutine to construct the chart. Again, we start with an immense amount of boilerplate, with styles for the chart title, legend, plot area, the data series, and the individual pie slices.
#
# Construct the chart file, given:
# reference to the @legends array
# reference to the @data array
# chart number
#
sub construct_chart
{
my $legendref = shift;
my $dataref = shift;
my $chart_num = shift;
my $cell; # current cell number being processed
open CHARTFILE, ">$ARGV[1]/Object$chart_num/content.xml";
print CHARTFILE <<"CHART_HEADER";
<!DOCTYPE office:document-content
PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "office.dtd">
<office:document-content xmlns:office="http://openoffice.org/2000/office"
xmlns:style="http://openoffice.org/2000/style"
xmlns:text="http://openoffice.org/2000/text"
xmlns:table="http://openoffice.org/2000/table"
xmlns:draw="http://openoffice.org/2000/drawing"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:number="http://openoffice.org/2000/datastyle"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:chart="http://openoffice.org/2000/chart"
xmlns:dr3d="http://openoffice.org/2000/dr3d"
xmlns:math="http://www.w3.org/1998/Math/MathML"
xmlns:form="http://openoffice.org/2000/form"
office:class="chart" office:version="1.0">
<office:automatic-styles>
<style:style style:name="title" style:family="chart">
<style:properties
style:font-family-generic="swiss"
fo:font-size="12pt"/>
</style:style>
<style:style style:name="legend" style:family="chart">
<style:properties
style:font-family-generic="swiss"
fo:font-size="8pt" />
</style:style>
<style:style style:name="plot" style:family="chart">
<style:properties
chart:lines="false"
chart:series-source="columns"/>
</style:style>
<style:style style:name="series" style:family="chart">
<style:properties draw:fill-color="#ffffff"/>
</style:style>
<style:style style:name="slice1" style:family="chart">
<style:properties draw:fill-color="#ff6060"/>
</style:style>
<style:style style:name="slice2" style:family="chart">
<style:properties draw:fill-color="#ffa560"/>
</style:style>
<style:style style:name="slice3" style:family="chart">
<style:properties draw:fill-color="#ffff60"/>
</style:style>
<style:style style:name="slice4" style:family="chart">
<style:properties draw:fill-color="#60ff60"/>
</style:style>
<style:style style:name="slice5" style:family="chart">
<style:properties draw:fill-color="#6060ff"/>
</style:style>
<style:style style:name="slice6" style:family="chart">
<style:properties draw:fill-color="#606080"/>
</style:style>
</office:automatic-styles>
The “here” document continues with the static part of the <office:body>, setting up the chart, title, legend, plot area, and table headings. There is only one series of data per chart, and each series has six data points. The first row of the table is a dummy header row, with the letter N (number of responses) as its content.
<office:body>
<chart:chart chart:class="circle" svg:width="9cm" svg:height="9cm">
<chart:title chart:style-name="title" svg:x="1cm"
svg:y="0.25cm">
<text:p>${$dataref}[0]</text:p>
</chart:title>
<chart:legend chart:legend-position="right" svg:x="8cm" svg:y="3cm"
chart:style-name="legend"/>
<chart:plot-area svg:x="0.5cm" svg:y="1.5cm"
svg:width="6cm" svg:height="6cm" chart:style-name="plot">
<chart:axis
chart:display-label="false"
chart:class="value"
chart:name="primary-y"/>
<chart:series chart:style="series">
<chart:data-point chart:style-name="slice1"/>
<chart:data-point chart:style-name="slice2"/>
<chart:data-point chart:style-name="slice3"/>
<chart:data-point chart:style-name="slice4"/>
<chart:data-point chart:style-name="slice5"/>
<chart:data-point chart:style-name="slice6"/>
</chart:series>
</chart:plot-area>
<table:table table:name="local-table">
<table:table-header-columns>
<table:table-column/>
</table:table-header-columns>
<table:table-columns>
<table:table-column table:number-columns-repeated="2"/>
</table:table-columns>
<table:table-header-rows>
<table:table-row>
<table:table-cell><text:p/></table:table-cell>
<table:table-cell table:value-type="string">
<text:p>N</text:p>
</table:table-cell>
</table:table-row>
</table:table-header-rows>
<table:table-rows>
CHART_HEADER
Now we create the dynamic portion of the table contents; each category (Strongly Agree/Agree/etc.) is in the first column, and the number of responses in the second column. The subroutine finishes by closing off all the open tags.
for ($cell=1; $cell < scalar(@{$dataref}); $cell++)
{
print CHARTFILE qq!<table:table-row>\n!;
print CHARTFILE qq!<table:table-cell table:value-type="string">!;
print CHARTFILE qq!<text:p>!, ${$legendref}[$cell], qq!</text:p>!;
print CHARTFILE qq!</table:table-cell>!;
print CHARTFILE qq!<table:table-cell table:value-type="float" !;
print CHARTFILE qq!table:value="!, ${$dataref}[$cell], qq!">!;
print CHARTFILE qq!<text:p>!, ${$dataref}[$cell], qq!</text:p>!;
print CHARTFILE qq!</table:table-cell></table:table-row>\n!;
}
print CHARTFILE <<"CHART_FOOTER";
</table:table-rows>
</table:table>
</chart:chart>
</office:body>
</office:document-content>
CHART_FOOTER
}
To make a three-dimensional chart, you must add the chart:three-dimensional attribute to the style that controls the <chart:plot-area>, and you must give it a value of true. If you want extra depth on the chart, you may set the chart:deep attribute to true as well. In a perfect world, that would be all that you would need to do. Unfortunately, if you leave it at that, your three-d bar charts will come out looking like Figure 8.7, “Insufficient Three-Dimensional Information”, which is not what you want.[16]
In order to get a reasonable-looking chart, you must add the following attributes to your <chart:plot-area> element. You can get by with just the first of these, <dr3d:distance>, but the results will still be significantly distorted.
You may also add any of the attributes that you would add to a <dr3d:scene> element, as described in the section called “The dr3d:scene element”. If you want to set the lighting, add <dr3d:light> elements as children of the <chart:plot-area> element. This element is described in the section called “Lighting”.
[15] This survey uses what is called a six-point Likert scale. If you are setting up a survey, always make sure you have an even number of choices. If you have an odd number of choices with “Neutral” in the middle, people will head for the center like moths to a flame. Using an even number of choices forces respondents to make a decision.
[16] As modern art, this is actually quite nice. The results for a pie chart look almost obscene.
Content licensed under a
Creative Commons
License.