Part 2: Transforming XML into SVG
Doug Tidwell
Cyber Evangelist, developerWorks XML Team
Published: January 2000
Updated: March 2001
The first section of our tutorial showed you how to transform
XML documents into HTML. We used a variety of XML source documents (technical
manuals, spreadsheet data, a business letter, etc.) and converted them into
HTML. Along the way, we demonstrated the various things you can do with
the XSLT and XPath standards. In this section, we'll use the World Wide Web
Consortium's emerging Scalable Vector Graphics format (SVG) to convert a
couple of our original documents into graphics.
Our original documents For
our transformations, we’ll use two of our original six source documents:
some spreadsheet data and a Shakespearean sonnet. The other documents from
our original set aren't easily converted to SVG; we'll discuss why later.
Before we continue, let's look at the two documents and how we intend to
transform them.
Sales figures
Here is our sales figures document, rendered as a column chart in SVG:

Everything you see here was created with an XSLT stylesheet and our original
XML file. If you change the figures, titles, or region names in the XML
source, you can regenerate the column chart by applying the stylesheet to
the updated XML document. If you'd like to see the XML source from which
this column chart was generated, see Appendix A - Sample XML documents.
A Shakespearean sonnet This document is rendered line-by-line,
with an indication of the rhyme scheme at the first of each line. Our SVG
file uses a variety of SVG elements (more on these later) to recreate the look and feel of our original HTML transformation:

You can find the XML source from which this document was generated in Appendix A - Sample XML documents.
A pie chart For our grand finale, we'll discuss a stylesheet
that converts our sales figures document to a pie chart in SVG. This is
more difficult because we need to use trigonometric functions (the venerable
sine and cosine) that aren't available in XSLT and XPath. That means we
have to write extension functions that add new capabilities to the XSLT processor.
The fruits of our labors look like this:
Scalable Vector Graphics Before
we begin building our stylesheets, let's talk about our target markup language.
The SVG specification we'll use here is the 2 November 2000 candidate recommendation,
available at the W3C Web site. To view the examples, we'll use the Adobe
SVG browser plug-in, available at the Adobe site.
SVG is a language for describing two-dimensional graphics in XML. You
use SVG elements to describe text, paths (sets of lines and curves), and
images. Once you've defined those images, you can clip them, transform them,
and manipulate them in a variety of interesting ways. In addition, you can
define interactive and dynamic features by assigning event handlers, and
you can use the Document Object Model (DOM) to modify the elements, attributes,
and properties of the document. Finally, because SVG describes graphics
in terms of lines, curves, text, and other primitives, SVG images can be
scaled to any arbitrary degree of precision.
Here is a scaled example of our earlier column chart:
As you can see, scaling the document does not affect the quality of the
image. If you were to magnify a traditional graphic (a GIF or JPEG file)
to this size, the image would be grainy and illegible. An additional benefit
of SVG is that you can easily create SVG files from scratch.
We'll discuss some of the basic elements of SVG here, but we won't go
into great detail. The SVG specification is bursting at the seams with samples
that illustrate the capabilities of the language.
<path> elements
To define a shape in SVG, we'll use a <path>
element. A path contains some number of commands that tell the SVG renderer
to move to a particular point, to draw lines or curves to a certain point,
and to close and fill the path. Most SVG elements, <path> included, have an optional style attribute that uses CSS properties to define how the element should be rendered.
Here's an SVG document that contains a sample <path> element:
<svg height="50" width="100">
<path style="stroke:black; stroke-width:2; fill:red"
d="M 10 10 L 90 10 L 90 40 L 10 40 Z"/>
</svg>
|
The style attribute specifies that the path should be
stroked with a black line 2 pixels wide, and that the path should be filled
with red. The meaning of the d attribute is explained in the following table:
| SVG Data | Meaning |
| M 10 10 | Moves the current point to (10, 10). M stands for the move command. |
| L 90 10 | Draws a line from the current point to (90, 10). L stands for the lineto command. The lineto command draws a line and moves the current point. |
| L 90 40 | Draws a line from the current point to (90, 40). |
| L 10 40 | Draws a line from the current point to (10, 40). |
| Z | Closes the path and fills it according to the value of the style attribute. |
In our example here, the style attribute causes the box to be filled with the color red. Z, strangely enough, stands for the closepath command.
You should be aware that the x- and y-coordinates of SVG graphics begin
in the upper left corner. The point (10, 10) is 10 pixels down and 10 pixels
to the right, measured from the upper left corner. See the SVG specification
for complete definitions of all drawing commands.
When rendered, our SVG document creates this breathtaking image:
<text> elements
To draw text in an SVG image, we use the SVG <text> element. We use the style attribute to define the properties of the text, and we supply x and y attributes to tell the SVG rendering engine where to begin drawing.
Here's an SVG document that contains a sample <text> element:
<svg height="50" width="100">
<text style="font-size:36; font-family:Times Roman; fill:blue"
x="10" y="40">
The quick brown fox
</text>
</svg>
|
The style attribute tells the SVG renderer to use the Times
Roman font, 36-point type, and to fill the shapes of the letters with blue.
When rendered, the document looks like this:
<g> elements
The final element we'll use in our SVG transformations is the <g> element. The <g>
element is used to group collections of drawing elements. Because SVG elements
inherit properties from their ancestors, you can use a <g> element to define a set of properties for a group of elements.
Here's an SVG document that contains three <path> elements. The first two <path> elements inherit all their properties from the <g> element, while the third overrides one of them.
<svg height="50" width="100">
<g style="stroke:black; stroke-width:2; fill:blue">
<path d="M 10 10 L 30 10 L 30 40 L 10 40 Z"/>
<path d="M 40 10 L 60 10 L 60 40 L 40 40 Z"/>
<path style="fill:green"
d="M 70 10 L 90 10 L 90 40 L 70 40 Z"/>
</g>
</svg>
|
When rendered, this SVG image looks like this:
All three boxes are drawn with a black, 2-pixel-wide line, as specified by the style attribute of the <g> element. The first two boxes inherit the property fill:blue from the <g> element, while the third box uses fill:green, coded on the <path> tag itself.
Arcs
There’s one final wrinkle we’ll cover when we draw our pie chart: arcs. An arc is drawn as part of a <path>, using the A command. Here’s a sample that draws an arc:
<svg height="200" width="200">
<path style="fill:blue; stroke:black; stroke-width:2;
fillrule:evenodd; stroke-linejoin:miter;"
transform="translate(100, 100)
rotate(-72.24046757584189)"
d="M 80 0 A 80 80 0 0 0 25.26622959787925
-75.90532024770893 L 0 0 Z"/>
</svg>
|
When rendered, this SVG image looks like this:
In this sample, we used the transform attribute to move (translate)
the origin (the point 0, 0) to 100, 100, and to rotate the slice by roughly
-72 degrees. We’ll use this technique in our pie chart so that all of the
slices of the pie are centered, and to simplify the trigonometry we have
to do. The arguments of the d attribute are explained in the following table:
| SVG Data | Meaning |
| M 80 0 | Moves the current point to (80, 0). |
| A 80 80 0 0 0 25.266... -75.905... | Draws
an elliptical arc from the current point to some other point. The first
two numbers (80 80) are the x- and y-radius of the arc (if the two are the
same, the ellipse is a circle). The next three numbers (0 0 0) deal with
the rotation of the x-axis and two other parameters (referred to as the large-arc-flag
and the sweep-flag in the SVG spec); we’re not using any of these here.
The final two numbers (25.266... -75.905...) are the x- and y-coordinates
of the endpoint of the arc. |
| L 0 0 | Draws a line from the current point (the end of the arc we just drew) to the origin. |
| Z | Closes the path and fills it according to the value of the style attribute. |
XML into SVG The first installment
of this series converted XML into HTML documents. For our exercises here,
we'll convert our XML source into SVG documents. Transformation #1 - A column chart To create our column
chart, we'll need to draw the various lines and features of the chart, then
fill in the chart with columns whose size is based on data from the XML document.
We'll look at the end result we want, then we'll figure out how to get there.
There are several parts to our chart:
- A title
- A subtitle
- An x-axis, a y-axis, and some grid lines
- A scale
- A series of columns, each representing the sales of a particular region of the company
- A number above each column, indicating the actual sales for each region
- A legend, indicating which region is represented by each column
To write our stylesheet, we'll write transformation rules for each part
of the SVG document. When we transformed XML into HTML, we had to write
the parts of the HTML document in a certain order (<head> before <body>,
for example). Although our SVG elements can appear in any order, we'll want
to draw certain items first so that they'll appear in the background. If
we draw the grid lines on top of the columns, for example, the grid lines
will be in the foreground (drawing operations in SVG are opaque by default).
We'll create the elements for our SVG document in the order we listed
them above. The following sections discuss how we build each of the parts
of the column chart.
Our main template will process the <sales> element, the root element of our XML document. Before we begin building the column chart, we'll output the <svg> tag to begin the document:
<xsl:template match="sales">
<svg width="400" height="300">
|
Drawing the title
The title of the chart comes from the <heading> element inside the <caption> element. We'll use the style text-anchor:middle to center the text around our starting point.
<text style="font-size:18; text-anchor:middle" x="120" y="20">
<xsl:value-of select="caption/heading"/>
</text>
|
Notice that we've hard-coded the x and y attributes.
A more sophisticated stylesheet would figure out how wide the entire graph
should be, then calculate the starting position based on that information.
For our purposes here, we'll hardcode a variety of details like this.
Drawing the subtitle We've hardcoded the subtitle as well.
For the foreseeable future, all of our reports will be in millions of dollars.
(If our company's sales grow tenfold, we probably won't mind changing our
stylesheet.)
<text style="font-size:12; text-anchor:middle"
y="33" x="120">(in millions of dollars)</text>
|
Drawing the x-axis, y-axis, and grid lines Again, we hardcode
the x-axis, y-axis, and the grid lines. The x- and y-axes are drawn with
a wide black line, and the grid lines are drawn with a dashed gray line:
<g style="stroke-width:2; stroke:black">
<path d="M 40 220 L 40 30 L 40 220 L 200 220 Z"/>
</g>
<g style="fill:none; stroke:#B0B0B0; stroke-width:1;
stroke-dasharray:2 4">
<path d="M 42 200 L 198 200 Z"/>
<path d="M 42 180 L 198 180 Z"/>
<path d="M 42 160 L 198 160 Z"/>
<path d="M 42 140 L 198 140 Z"/>
<path d="M 42 120 L 198 120 Z"/>
<path d="M 42 100 L 198 100 Z"/>
<path d="M 42 80 L 198 80 Z"/>
<path d="M 42 60 L 198 60 Z"/>
<path d="M 42 40 L 198 40 Z"/>
</g>
|
Drawing the scale
Our latest hardcoded elements draw the scale along the y-axis. We draw the various strings, hardcoding the x and y attributes of each <text> element. Notice that we use the style text-anchor:end to right-align the entries in the scale. SVG uses text-anchor:end to right-justify text, text-anchor:middle to center it, and text-anchor:start to left-justify the text. The default property is text-anchor:start.
<g style="text-anchor:end; font-size:9">
<text x="36" y="203">20</text>
<text x="36" y="183">40</text>
<text x="36" y="163">60</text>
<text x="36" y="143">80</text>
<text x="36" y="123">100</text>
<text x="36" y="103">120</text>
<text x="36" y="83">140</text>
<text x="36" y="63">160</text>
<text x="36" y="43">$180</text>
</g>
|
Drawing the columns
Now we get down to the real work of our stylesheet. For each <region>
in our XML source, we'll select a color, stroke a path of the appropriate
height, then fill it with our chosen color. We’ll use the <xsl:for-each> and <xsl:apply-templates> elements to do this; within the <xsl:apply-templates> element, we’ll calculate some parameters and pass them to the appropriate template.
To choose a color, we cycle through five different colors. We use the XPath mod operator and the position() function to do this:
<xsl:with-param name="color">
<xsl:choose>
<xsl:when test="(position() mod 5) = 1">
<xsl:text>red</xsl:text>
</xsl:when>
<xsl:when test="(position() mod 5) = 2">
<xsl:text>yellow</xsl:text>
</xsl:when>
<xsl:when test="(position() mod 5) = 3">
<xsl:text>purple</xsl:text>
</xsl:when>
<xsl:when test="(position() mod 5) = 4">
<xsl:text>blue</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>green</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
|
The position() function gives us the position of the <region> tag. The first <region> has a position of 1, the second has a position of 2, etc. The <xsl:choose> statement above cycles through green, red, yellow, purple, and blue. The mod
operator is very useful for cycling through a limited series of options as
we’re doing here. The elements above calculate the value of the color parameter
each time a <region> is processed.
We also need to calculate the dimensions of the column. The y-coordinate
of the legend and the x-coordinate of the column begin at a position based
on the region. The first region begins at 60, the second begins at 90, the
third begins at 120, etc. We calculate the parameters x-offset and y-legend-offset
as follows:
<xsl:with-param name="y-legend-offset">
<xsl:value-of select="60 + (position() * 30)"/>
</xsl:with-param>
<xsl:with-param name="x-offset">
<xsl:value-of select="30 + (position() * 30)"/>
</xsl:with-param>
|
The final parameter to our template, y-offset, is simply hardcoded:
<xsl:with-param name=”y-offset” select=”’220’”/>
|
A quick syntax note: You might have noticed that the select attribute above contains single quotes inside double quotes. That tells the XSLT processor that the value of the y-offset
parameter is the literal string “220”. The single quotes inside double quotes
aren’t required in this case, because the processor always interprets “220”
as a literal string. If we wanted to hardcode the value of the color parameter,
this wouldn’t work:
<xsl:with-param name=”color” select=”blue”/>
|
This <xsl:with-param> element sets the value of the parameter color to the node-set of all <blue>
elements in the current context, which is definitely not what we want. The
reason for this confusion is that the XML spec states that XML element names
can’t begin with a number. If the element you’re asking for is “220,” the
XSLT processor is clever enough to figure out that you meant the literal
value 220. To avoid this confusion, it’s a good idea to type literal values
with single quotes inside double quotes, whether the literal value is a number
or not.
Now that we've calculated the values of all of the parameters, our <xsl:apply-templates> element takes effect. Remember that the select attribute of <xsl:apply-templates> simply processes the current context. Because the <xsl:apply-templates> element occurs inside the <xsl:for-each select="region"> element, the current context (".") is the <region> element we're currently processing.
Let's look at the template that processes each <region> element. First of all, we have to declare the parameters used by the template:
<xsl:param name="x-offset" select="'60'"/>
<xsl:param name="y-offset" select="'220'"/>
<xsl:param name="color" select="'red'"/>
<xsl:param name="y-legend-offset" select="'90'"/>
|
Each parameter has a name and a default value. If a given parameter isn't
passed in when this template is invoked, the default value is used instead.
Notice that we followed our guideline of using single quotes inside double
quotes for all literal values.
Once we've set up our parameters, we define the height of the box. To
keep things simple, we're using the total amount of sales as the height in
pixels of the box. We'll need this value several times, so we'll store it
in a variable so we don't have to recalculate it each time we use it:
<xsl:variable name="y">
<xsl:value-of select="$y-offset - sum(product)"/>
</xsl:variable>
|
The XPath function sum(product) converts the values of all <product> elements inside the current <region> tag to numbers, then totals them. We used this same function in our XML to HTML transformation.
Now we're ready to build our SVG <path> element. We need to generate something like this:
<path style="fill:red" d="M 50 220 L 50 75.800 L 70 75.800 L 70 220 Z"/>
|
Our job here is to find the data that fills in the attributes of the <path> element. Here's a short table that maps the SVG attributes to the data available to our template:
| SVG Data | Source |
| "fill:red" | The parameter $color |
| M 50 220 | The parameter $x-offset minus 10 is 50, and 220 is the parameter $y-offset (we want to center the column on $x-offset, so we draw the column from $x-offset - 10 to $x-offset + 10) |
| L 50 75.800 | The parameter $x-offset minus 10 is 50, and the local variable $y (which we calculated earlier) is 75.800 |
| L 70 75.800 | The parameter $x-offset plus 10 is 70, and $y is 75.800 |
| L 70 220 | The parameter $x-offset plus 10 is 70, and the parameter $y-offset is 220 |
| Z | Closes the path and fills it with the appropriate color |
Because we need to create the style and path attributes dynamically, we'll use <xsl:attribute> to build them on the fly:
<path>
<xsl:attribute name="style">
<xsl:text>fill:</xsl:text>
<xsl:value-of select="$color"/>
</xsl:attribute>
<xsl:attribute name="d">
<xsl:text>M </xsl:text>
<xsl:value-of select="$x-offset - 10"/>
<xsl:text> </xsl:text>
<xsl:value-of select="$y-offset"/>
<xsl:text> L </xsl:text>
<xsl:value-of select="$x-offset - 10"/>
<xsl:text> </xsl:text>
<xsl:value-of select="$y"/>
<xsl:text> L </xsl:text>
<xsl:value-of select="$x-offset + 10"/>
<xsl:text> </xsl:text>
<xsl:value-of select="$y"/>
<xsl:text> L </xsl:text>
<xsl:value-of select="$x-offset + 10"/>
<xsl:text> </xsl:text>
<xsl:value-of select="$y-offset"/>
<xsl:text> Z</xsl:text>
</xsl:attribute>
</path>
|
Whew! The listing here is pretty tedious, but it generates what we need. Each of the many <xsl:text> and <xsl:value-of> elements here generates one or two of the characters we need. Together, though, they get the job done.
Drawing the totals Now that we've drawn the column, we want
to print the actual total sales. Compared to drawing the column itself,
this is easy. We want to create an SVG <text> element that is centered just above the column we just drew. Here's a sample SVG element:
<text style="font-size:10; text-anchor:middle" x="60" y="70.8">144.2</text>
|
Here's where the parts of our <text> element came from:
| SVG Data | Source |
| "text-anchor:middle" | We want the text to be centered |
| x="60" | The parameter $x-offset |
| y="70.8" | The local variable $y, calculated earlier, minus 5 |
| 144.2 | The XPath function sum(product), which converts the values of all the <product> elements to numbers, then totals them |
This element is pretty straightforward, especially when compared to the <path> element we generated earlier. Here's the part of the stylesheet that generates this element:
<text style="font-size:10; text-anchor:middle">
<xsl:attribute name="x">
<xsl:value-of select="$x-offset"/>
</xsl:attribute>
<xsl:attribute name="y">
<xsl:value-of select="$y - 5"/>
</xsl:attribute>
<xsl:value-of select="sum(product)"/>
</xsl:element>
|
Drawing the legend
Our final task for each <region>
is to add an entry to the legend. We'll draw a small box filled with the
same color as the column, with the name of the region written to its right.
Here's the legend from our completed column chart:

The x-coordinate at which the legend starts is hardcoded (a more sophisticated stylesheet would have to figure out how many <region>
elements existed, then start drawing the legend at the appropriate spot).
The y-coordinate of each legend entry is passed in as the $y-legend-offset parameter.
A given legend entry looks like this:
<text x="240" style="font-size:14;
text-anchor:start" y="90">Southeast</text>
<path style="stroke:black; stroke-width:2; fill:red"
d="M 220 80 L 220 90 L 230 90 L 230 80 Z"/>
|
In the <text> element, the x attribute is hardcoded; we begin all legend entries at the same place. The y attribute is the parameter $y-legend-offset. The text of the <text> element is the text of the <name> element inside the current <region> element. Again, we'll use <xsl:attribute> to create an attribute. Here's the portion of the stylesheet that draws the text of the legend entry:
<text style="font-size:14; text-anchor:start" x="240">
<xsl:attribute name="y">
<xsl:value-of select="$y-legend-offset"/>
</xsl:attribute>
<xsl:value-of select="name"/>
</text>
|
For the <path> element, the color is the $color parameter. For the move (M) and lineto (L) commands, the x-coordinates are hardcoded, and the y-coordinates are based on the parameter $y-legend-offset. Here's the portion of the stylesheet that creates the <path> element:
<path>
<xsl:attribute name="style">
<xsl:text>stroke:black; stroke-width:2; fill:</xsl:text>
<xsl:value-of select="$color"/>
</xsl:attribute>
<xsl:attribute name="d">
<xsl:text>M 220 </xsl:text>
<xsl:value-of select="$y-legend-offset - 10"/>
<xsl:text> L 220 </xsl:text>
<xsl:value-of select="$y-legend-offset"/>
<xsl:text> L 230 </xsl:text>
<xsl:value-of select="$y-legend-offset"/>
<xsl:text> L 230 </xsl:text>
<xsl:value-of select="$y-legend-offset - 10"/>
<xsl:text> Z</xsl:text>
</xsl:attribute>
</path>
|
Transformation #2 - A Shakespearean sonnet
Compared to the column chart, an SVG version of our sonnet is easy to create. Let's look at the output we want:
Looking at our result document, there are several components:
- The title of the sonnet
- Information about the author: the author's name, nationality, year of birth, and year of death
- The lines of the sonnet, each of which is color-coded and preceded by a letter to indicate the rhyme scheme
We'll look at each of these components in detail.
Drawing the title
To draw the title, we'll need the text of the <title>
element from our source document. Because all sonnets are 14 lines long,
we can simplify things by hardcoding most of the x- and y-coordinates of
the objects in our SVG document. Here's the part of the stylesheet that
draws the title:
<text style="font-size:24" x="20" y="20">
<xsl:value-of select="title"/>
</text>
|
That's it! If we had needed to calculate the x and y attributes of the <text> element, this would have been more complicated.
Drawing the author information Now we'll draw the line of text
that contains the author information. This line contains five parts: the
text "Author:", and the author's name, nationality, year of birth, and year
of death. The author's name comes from the <first-name> and <last-name> elements inside the <author> element of our source document. Similarly, the nationality, year of birth, and year of death come from the <nationality>, <year-of-birth>, and <year-of-death>
elements inside the <author> element. The complication here is that
we want the author's nationality, year of birth, and year of death to be
italicized. To do this, we'll use the SVG <tspan> element. Here's what the SVG elements look like:
<text y="40" x="20" style="font-size:18">Author: William Shakespeare (
<tspan style="font-style:italic">British, 1564-1616</tspan>)
</text>
|
The <tspan> element inherits all the properties of the <text> element that contains it. The advantage of using the <tspan> element here is that we don't have to calculate where the text should appear. We could have created a new <text> element to draw the italicized text, but we would have to determine the x- and y-coordinates of the italicized text. Using <tspan> means the attributes of the text change, but the SVG rendering engine handles the details of drawing the <tspan> element's text inline with the rest of the <text> element.
Here's the section of the stylesheet that generates the <text> and <tspan> elements:
<text style="font-size:18" x="20" y="40">
<xsl:text>Author: </xsl:text>
<xsl:value-of select="author/first-name"/>
<xsl:text> </xsl:text>
<xsl:value-of select="author/last-name"/>
<xsl:text> (</xsl:text>
<tspan style="font-style:italic">
<xsl:value-of select="author/nationality"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="author/year-of-birth"/>
<xsl:text>-</xsl:text>
<xsl:value-of select="author/year-of-death"/>
</tspan>
<xsl:text>)</xsl:text>
</text>
|
Notice that we hardcoded the x and y attributes here, just as we did when we drew the title.
Drawing the lines of the sonnet Our final task is to draw the
lines of the sonnet. Because of the constraints of the sonnet form, we can
create a separate template for each line of the sonnet. When we draw each
line of text, we'll cheat again by hardcoding the x- and y-coordinates.
We can also hardcode the rhyme scheme and the color of each line. Here are
the templates for the first and second lines of the sonnet:
<xsl:template match="line[1]">
<text x="20" y="60"
style="font-weight:bold; font-style:italic; fill:green">
<xsl:text>A</xsl:text>
</text>
<text x="40" y="60" style="fill:green">
<xsl:value-of select="." />
</text>
</xsl:template>
<xsl:template match="line[2]">
<text x="20" y="78"
style="font-weight:bold; font-style:italic; fill:purple">
<xsl:text>B</xsl:text>
</text>
<text x="40" y="78" style="fill:purple">
<xsl:value-of select="." />
</text>
</xsl:template>
|
The other twelve templates are similar. Converting a sonnet to SVG is
a pretty simple exercise, thanks to the strict format of sonnets. Creating
a stylesheet to convert any arbitrary poem to SVG would be more difficult,
particularly if you needed to calculate where lines of text should break.
Transformation #3 - A pie chart For our final performance,
we’ll take the sales data we transformed into a column chart and convert
it into a pie chart. To do this, we need to use trigonometry to calculate
the sizes and positions of the various slices of pie. XSLT and XPath have
very basic math functions, so we’ll have to use extension functions for the
trigonometric functions we need. Be aware that the extension mechanism in
XSLT 1.0 isn’t portable from one XSLT processor to another. Our examples
here are written for the Java version of the Apache XML Project’s Xalan processor.
Doing the math There are several things we’ll have to calculate
for each slice of the pie. The size of each pie in degrees is the percentage
of total sales contained in the current region, multiplied by 360 degrees.
In other words, if the current region contains 20% of the total sales for
the entire company, the current slice of pie will be 72 degrees (20% of 360).
We can calculate the angle of the slice of pie using the existing math functions
of XPath and XSLT; calculating the endpoint of the arc requires trigonometry
functions that aren’t available. We’ll use extension functions to access
those trigonometry functions; before we do, we’ll discuss the math briefly.
Here’s a sample slice of pie:
In this example, we know that point A is at (80,0), and point C is the
origin (0, 0). We know the location of point A because our pie has a radius
of 80. We’ll always rotate the x- and y-axis so that the y-coordinate of
point A is always 0; that simplifies the math we’ll have to do.
The other things we know are the length of the line between point C and
point B (it’s 80, the radius of our pie) and the size of angle C in degrees
(it’s calculated by dividing the total sales of the company by the current
region’s). Our challenge here is to figure out the x- and y- coordinates
of point B; SVG’s arc drawing command (A) requires us to provide those coordinates.
To calculate the x- and y- coordinates, it helps to make a right triangle
by drawing a line from point B to point D. Now we can calculate the x- and
y-coordinates of point B; the x-coordinate is the length of the line between
point C and point D, and the y-coordinate is the length of the line between
point B and point D. To put this in trigonometric terms, the x-coordinate
is the cosine of angle C multiplied by 80 (the radius of the circle), and
the y-coordinate is the sine of angle C multiplied by -80. (We use -80 because
the slices of the pie are drawn counterclockwise.)
Using extension functions To get the sine and cosine functions,
we’ll need to write an extension function and access it from within our stylesheet.
Fortunately for us, the java.lang.Math
class provides static methods for sine and cosine. Even more fortunately,
Xalan makes it easy for us to access static methods from a given Java class.
To make this magic happen, we’ll add some declarations to the <xsl:stylesheet> element:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:javaMath="http://xml.apache.org/xslt/java"
xmlns:lxslt="http://xml.apache.org/xslt"
extension-element-prefixes="javaMath"
exclude-result-prefixes="lxslt">
|
This defines a couple of new namespaces, javaMath and lxslt, and uses the standard XSLT attributes extension-element-prefixes and exclude-result-prefixes to tell the XSLT processor that any elements with these namespace prefixes should not be sent to the output stream.
Our next step is a Xalan-specific element that defines the functions we’ll be using in our stylesheet:
<lxslt:component prefix="javaMath" functions="cos sin toRadians">
<lxslt:script lang="javaclass" src="class:java.lang.Math"/>
</lxslt:component>
|
This tells Xalan that the javaMath namespace prefix should be associated with the Java class java.lang.Math. It defines three functions, cos, sin, and toRadians. (We have to use the toRadians method because cos and sin require their arguments to be in radians, not degrees.) This means that invoking a function named javaMath:cos() tells Xalan to invoke the cos method of the java.lang.Math class. One final detail: because the src attribute of the <lxslt:script>
element begins with class:, Xalan understands that these are static methods.
That means that Xalan doesn’t have to create a new class object each time
the functions are invoked.
Now that we’ve defined everything, we’re ready to invoke our extension
functions. Here’s a sample that illustrates how these work:
<xsl:attribute name="d">
<xsl:text>M 80 0 A 80 80 0 0 0 </xsl:text>
<xsl:value-of select="javaMath:cos($currentAngle) * 80"/>
<xsl:text> </xsl:text>
<xsl:value-of select="javaMath:sin($currentAngle) * -80"/>
<xsl:text> L 0 0 L 80 0 </xsl:text>
</xsl:attribute>
|
Yes, this is somewhat tedious, but we were able to add new capabilities
to our XSLT processor without having to write a line of Java code. We simply
found some static methods that did what we wanted, then we linked to them
as needed.
The stylesheet itself The stylesheet that uses these extension
functions to build the pie chart isn’t that different from the stylesheet
that built a column chart. There are a couple of differences worth noting,
however.
First of all, we use the mode attribute of the <xsl:template> and <xsl:apply-templates>
elements. This allows us to define different templates for the same content,
and invoke them at different times. Here are the two <xsl:apply-templates> elements:
<xsl:apply-templates select=".">
<xsl:with-param name="color" select="$color"/>
<xsl:with-param name="regionSales" select="$regionSales"/>
<xsl:with-param name="totalSales" select="$totalSales"/>
<xsl:with-param name="runningTotal"
select="sum(preceding-sibling::region/product)"/>
</xsl:apply-templates>
<xsl:apply-templates select="." mode="legend">
<xsl:with-param name="color" select="$color"/>
<xsl:with-param name="regionSales" select="$regionSales"/>
<xsl:with-param name="y-legend-offset"
select="90 + (position() * 20)"/>
<xsl:with-param name="position" select="position()"/>
</xsl:apply-templates>
|
These two <xsl:apply-templates> elements process each <region> element in turn; the first draws the pie slice, and the second draws the legend entry. The mode attribute is a useful technique for organizing the templates in your stylesheets.
Another thing we’re doing differently is we’re using the preceding-sibling
axis of XSLT. To know how many degrees counterclockwise to rotate the x-
and y- axes of our pie chart, we need to know how much of the total sales
of the company we’ve seen so far. This element calculates the value runningTotal based on all of the sales totals for all of the regions up to the current one:
<xsl:with-param name="runningTotal"
select="sum(preceding-sibling::region/product)"/>
|
In the template that processes the <region> elements, we use this value to rotate the axes. Here’s the complete template:
<xsl:template match="region">
<xsl:param name="color" select="'red'"/>
<xsl:param name="runningTotal" select="'0'"/>
<xsl:param name="totalSales" select="'0'"/>
<xsl:param name="regionSales" select="'0'"/>
<xsl:variable name="currentAngle"
select="javaMath:toRadians(($regionSales div $totalSales)
* 360.0)"/>
<path style="fill:{$color}; stroke:black; stroke-width:2;
fillrule:evenodd; stroke-linejoin:miter;">
<xsl:attribute name="transform">
<xsl:text>translate(100,140) rotate(</xsl:text>
<xsl:value-of select="-1 * (($runningTotal div $totalSales)
* 360.0)"/>
<xsl:text>)</xsl:text>
</xsl:attribute>
<xsl:attribute name="d">
<xsl:text>M 80 0 A 80 80 0 0 0 </xsl:text>
<xsl:value-of select="javaMath:cos($currentAngle) * 80"/>
<xsl:text> </xsl:text>
<xsl:value-of select="javaMath:sin($currentAngle) * -80"/>
<xsl:text> L 0 0 L 80 0 </xsl:text>
</xsl:attribute>
</path>
</xsl:template>
|
This template first calculates the variable currentAngle; this is the angle of the current pie slice in radians. We’re using the first of our three functions here, the toRadians method of the java.lang.Math
class. Notice that the only thing different about this function call is
the namespace prefix in front of the function name. Other than that, it’s
exactly the same as a function call to one of the XSLT or XPath functions.
The second task for each <region> element is to draw
the appropriate entry in the legend. This is done similarly to the legend
in our column chart example. The only noteworthy thing here is that we’ve
added the mode attribute to the <xsl:template>:
<xsl:template match="region" mode="legend">
|
Invoking the stylesheet processor
Now that we've created our stylesheets, how do we invoke a stylesheet processor
to transform our XML documents? The simplest way to do this is with the
command line facility of the Xalan stylesheet processor. Assuming our XML
file is sonnet.xml, our stylesheet is sonnet-rhyme-scheme.xsl, and our output
is sonnet.svg, here’s how to invoke Xalan (the command should be typed as
a single line):
java org.apache.xalan.xslt.Process -in sonnet.xml -xsl
sonnet-rhyme-scheme.xsl -out sonnet.svg
|
This transforms the XML document sonnet.xml according to the templates
in the file sonnet-rhyme-scheme.xsl, writing the output to sonnet.svg.
Where are the other files? You
may be wondering where the other example files are, and why we didn't transform
them into SVG documents. We didn't transform the other files because all
of them require some basic text formatting. We were able to convert our
sonnet to SVG because each <line>
element in our XML source becomes a single line of output. If we wanted
to format our glossary in SVG, for example, we would need to calculate the
width and height of each and every line of text, inserting line breaks and
other formatting touches at the right places. While that's certainly possible,
we wouldn't be able to do it in a stylesheet; we'd have to write the Java
code to do everything by hand.
Summary We've taken a couple
of our documents and transformed them into SVG. The column and pie charts
are really useful examples that demonstrate what SVG can do, and our transformed
sonnet displays the sonnet and its rhyme scheme clearly.
These transformations used several important concepts in stylesheets.
We used parameters and variables, we added extension functions when we needed
them, and we used the mode
attribute to control how templates were invoked. All of these were necessary
because of the kind of documents we were creating. Despite this, our approach
to writing stylesheets remains the same:
- Determine the kind of document you want to create.
- Look at the contents of that target document, and determine what information you need to complete it.
- Build a stylesheet that creates the elements of the target document,
and either retrieve or calculate the information you need for each part of
the target document.
The more text-intensive documents demonstrate what SVG doesn't do very
well. Anything that contains text that needs to be broken into lines and
paragraphs is difficult to do with SVG. You have to calculate the line breaks
yourself, and you have to figure out how tall each line of text should be.
Furthermore, if you wanted to use rich text features in your SVG document
(display certain words in other fonts, different type sizes, different colors,
etc.), your job would be even more difficult.
If there are other transformations you’d like to see, let us know. As
always, we’d love to hear your comments, questions, complaints, and suggestions
about this tutorial.
Resources
About the author
MC
Dug-T is developerWorks’ Minister of Science, droppin’ the XML, Java, and
Web services 411 on the public. In his travels, he gets mad props from his
peeps worldwide for the stone-cold, stoopid-fresh stylesheets he leaves behind.
All his mad-phat nollidge will soon be published by O’Reilly and Associates
in the Strictly Non-Fiction book XSLT, which will then start slayin’ soft-sellin’
suckas at tha local booksella. Discussing the book in a recent dW interview,
he boasted, “I’m gonna empty mah dome into one supa-fly tome.”
For relaxation, he likes to put his hands up in the air,
and in his words, “wave ‘em around like I just don’t care.” When not chillin’
with his worldwide XML krew, he maxes at the crib in Raleigh with his wife,
cooking teacher CT-ONE, and their five-year-old shortie, Lily the Flayva
Princess. You can send him a shout-out at dtidwell@us.ibm.com.
|