IBM Skip to main content
Search for:   within 
   Search help 
    IBM home  |  Products & services  |  Support & downloads   |  My account
IBM : developerWorks : XML education
 

e-mail it!

Part 2: Transforming XML into SVG

Doug Tidwell
Cyber Evangelist, developerWorks XML Team
Published: January 2000
Updated: March 2001

Contents:
 Original documents
 SVG
 XML into SVG
 Stylesheet processor
 The other files
 Summary
 Resources
 About the author
 Appendix A
 Appendix B
 Part 1: XML to HTML
 Part 3: XML to PDF

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 DataMeaning
M 10 10Moves 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 DataMeaning
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 220The 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:

  1. Determine the kind of document you want to create.
  2. Look at the contents of that target document, and determine what information you need to complete it.
  3. 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.


 
e-mail it!
What do you think of this article?

Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Comments?


Privacy Legal Contact