Creating SVG Pie Charts through XSLT via a Web Service

An Experience Report

Keywords: Pie Chart, SVG, Web Service, Xalan, XSLT

Paul Asman
Technical Specialist, Advanced Technology Center
Federal Reserve Bank of New York
New York
New York
USA
paul.asman@ny.frb.org
http://www.newyorkfed.org

Biography

Paul Asman has been employed by the Automation Group of the Federal Reserve Bank of New York for over 20 years. He works in the Advanced Technology Center, which is charged to find and introduce new technologies with the potential to improve the Fed’s work. For the past few years, he has focused on XML and its related technologies. Paul’s education occurred before computer use was widespread, and aimed towards a different career. He holds a BA in philosophy and classics from Muhlenberg College, Allentown, Pennsylvania, and a PhD in philosophy from Cornell University, Ithaca, New York. His dissertation was on Aristotle.


Abstract


Departments at the Federal Reserve Bank of New York are under pressure to cut costs and show value. Information Technology at the Fed, like other departments, produces plenty of reports with graphical content, but content providers generally use different proprietary tools in generating the graphics.

While SVG avoids the cost of proprietary tools, even within IT we were not prepared to ask our content providers to master SVG. Most of the graphs we produce, though, are simply new instances of familiar charts. We thought that we’d be able to build the graphic types in advance, and then ask content providers only for the particular data from which the instances would be drawn. We would create a Web service that takes XML data, transforms it through XSLT, and creates SVG graphics. We’ve now completed this for one type of graphical object, a pie chart.

This report on that experience begins with examples of the XML that a content provider supplies. While XML can intimidate some at first, its creation does not require any technical skills, and we provide a simple schema to follow. The report continues with the set of XSLT transformations that create pie charts in response to these validly constructed XML files. The transformations incorporate best practices for pie charts, such as having the largest meaningful slice begin at the positive vertical axis of the pie, moving counterclockwise, and aggregating small contributors into an ‘other’ slice. I provide and describe the XSLT code that implements these practices and creates the pie charts.

Even with the XSLT set, we could not ask our non-technical users to install Xalan and run the transformations themselves. We decided to offer them as a Web Service. We could have offered them in other ways, but chose Web Services both to gain experience in their use and to prepare for the future we expect. Since we anticipated that this Web Service would be called mostly through human interaction, we also created a Java servlet that invokes the service.

Moving the Xalan invocation from command lines to Java code actually simplified the process. For programming ease, I had decided that the transformation should be done in three separate steps, with two interim files. I stored these interim files on disk when using Xalan from a command line. When the steps were called from a Java routine, though, I stored the interim files in memory. I provide and describe the code for this and for the Web Service clients.

All the processes in this effort rely on open source software and specifications. Other than Java, the software is from the Apache foundation — Xalan, Soap, and Jakarta Tomcat. The specifications are from the W3C — XML, XSLT, and SVG. For the pie chart, I started with examples others offered on the Web.


Table of Contents


Rationale
The XML
     An Example
     The Schema
The XSLT
     Best Practices
     Transformations
         First Transformation: Pulling Out the 'other's
         Second Transformation: Aggregation and Sorting
         Third Transformation: Creating the Pie Chart
              Assigning Colors
              Applying the Templates
              Making the Calculations
              Creating the Path
              Creating the Text
              Creating the Legend
              The Result
Web Services
     Validation
         The Process
         What Can Go Wrong
     Creating the Pie Chart
     Invoking the Web Service through a Servlet
Conclusion
Footnotes
Acknowledgements
Bibliography

Rationale

Like many Information Technology departments, the Federal Reserve Bank of New York’s is under pressure to cut costs. IT also needs to show the value it provides — to justify the costs that are not cut. This report describes an effort that combined both these dimensions, an effort to cut costs in demonstrating the value we provide.

IT at the Fed, like other Fed departments, produces plenty of reports with graphical content. Our graphics show, for example, how many people from different Bank areas use various services we provide, and what elements the costs of those services comprise. Content providers generally use different proprietary tools in generating these graphics, and tend to start from scratch each time.

We realized that using SVG would avoid the cost of proprietary tools — not only the costs of acquisition and maintenance, but also the costs of learning tools that are certain to be abandoned. On the other hand, we were not prepared to ask our content providers to master SVG. While our content providers have different degrees of technical adeptness, proprietary tools attract them, in part, through their ease of use. Writing SVG in a text editor does not provide ease of use.

Most of the graphs we produce, though, are not new types, but simply new instances of familiar charts. We thought that we’d be able to build the graphic types in advance, and then ask content providers only for the particular data from which the instances would be drawn. We would create a service that takes XML data, transforms it through XSLT, and creates an SVG graphic. Since we want to allow automatic invocation of this service, we made it a Web service. We’ve now completed the process for one type of graphical object, a pie chart.

The XML

An Example

Pie charts are reasonably simple objects. They include slices, some colors to fill those slices, and, in the service we offer, labels indicating what the slices represent and how much of the pie they encompass. While there are some trigonometric functions necessary to create pie charts on the fly, they are part of the service. A content provider simply needs to supply the numbers and names for what they represent.

Sometimes the content resides on a data repository, and the pie charts are created as part of an automated process. But the content can also come through a human interface. Although its creation does not require any technical skills, XML can intimidate content providers, even those comfortable with HTML. Accordingly, it might be best to offer a Web form to collect values, or an interface to a spreadsheet. As an alternative, a content provider can take a sample XML document and simply change its values. That is what I have asked content providers to do, in part because I wish them to become more at ease with XML.

The model I provided calls its top level element 'components'. The only child element is 'component'; each component represents a potential slice. (The transformation aggregates some slices, following one of the Best Practices described below.) 'component' has one attribute, 'name', used for the label of each slice, and a text node representing quantity.

The following sample is the XML for the number of calls placed by different subgroups of the Automation Group at the Federal Reserve Bank of New York for a certain period of time. Each component represents a subgroup, named by 'name', and the text node contains the number of calls that group placed.

<?xml version="1.0" standalone="yes" encoding="UTF-8"?>
<!--The top level element contains the schema location. However, the validation service overrides this.-->
<components xmlns:xsi="http://www.w3c.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="pie.xsd">
     <component name="Information Technology">5712</component>
     <component name="NADCO">5033</component>
     <component name="Electronic Security">2104</component>
     <component name="SATT">1608</component>
     <component name="E-Business Office">291</component>
     <component name="Systems Development">158</component>
     <component name="Other">694</component>
</components>

To keep this simple, I have not included content for conversion into headers or footers, leaving these to the pie chart users, who might place the charts in HTML pages or Word documents, for example. If this information were to be included in the SVG, one might include attributes (of 'components') or elements to represent a title and a period of time for headers, while for a footer one might include an attribute or element for source. The XSLT could then transform these appropriately.

The XSLT creates the pie chart shown as Figure 1 . [1] I include it here to focus the discussion.

pie.svg

Figure 1: The Result

The Schema

Since there is no header or footer information, the schema is simple:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
     <xs:complexType name="componentType">
          <xs:simpleContent>
               <xs:extension base="xs:nonNegativeInteger">
                    <xs:attribute name="name" type="xs:string" use="required"/>
               </xs:extension>
          </xs:simpleContent>
     </xs:complexType>
     <xs:element name="components">
          <xs:complexType>
               <xs:sequence>
                    <xs:element name="component" type="componentType" maxOccurs="unbounded"/>
               </xs:sequence>
          </xs:complexType>
     </xs:element>
</xs:schema>

The schema follows the W3C [XML Schema] standard, and is therefore ill suited for investigation by our normal content providers. We instead offer Validation as a Web service, a process described below.

The XSLT

Best Practices

Pie charts can be drawn in a number of different ways. The slices can appear in ascending or descending order based on size, or can be placed at random. If the slices are ordered by size, the largest slice can appear in several different locations, and the other slices can follow clockwise or counterclockwise. Labels can be placed in slices, or near them. Legends can accompany the charts, or not. There can be a lower limit to the percentage that a slice may represent.

These decisions are built into the service through which the charts are offered. Since this service is internal to the Federal Reserve Bank of New York, this has the advantage of enforcing stylistic consistency. But it also creates the burden of making correct, or at least reasonable, choices. In this section, I describe the practices I have followed. Following industry usage, I call these "best practices," although I am aware that some might find this concept a stretch. It's not clear that [Tufte] , who writes, "the only worse design than a pie chart is several of them," would even entertain the possibility of best practices for pie charts.

Nevertheless, pie charts are common, in my organization and elsewhere, and we must make layout decisions. Here are the layout recommendations from the [SAP Design Guild] , which based its findings on a review of the literature:

I followed the first three of these recommendations, and added two that are related. The first addition calls for sorting the slices by size, except the one representing 'other', and presenting them in order starting with the largest. The second addition calls for placing the slice representing 'other' at the end, whatever its size. This serves to emphasize the most important slices, and to minimize that representing 'other'. To add to the bullet list, then:

Following the third recommendation has implications that may make pie charts unsuitable for certain representations. Consider this example. The Federal Reserve System has 12 regional Federal Reserve Banks. For certain services, one Bank will be responsible for the entire System. When the responsible Bank reports on its performance, it normally shows how much of the service is used by each Bank. With 12 Reserve Banks of varying size, it is almost inevitable that some Banks will represent less than 5% of the service. Yet no Bank wants to see itself represented by 'other'. A pie chart is inappropriate in this situation.

I'm not sure wheter I follow the fourth and fifth recommendations, for I'm not sure whether a legend on the side qualifies as labels placed outside segments. As shown in Figure 1 , I have labeled each segment, but not placed the labels near the segments they qualify. This decision stems in part from a graphic I produced in which two adjacent pie charts displayed the same information for two different times, the present time and a year previously. I wanted the same legend to serve for both charts, and placed it in between them. To preserve this option - a pie chart normally represents a moment or period of time, and moments and time periods are frequently compared - I kept the legend apart from the chart itself. This allowed me to put the values within the segments, rather than beside them, as the SAP Design Guild recommends.

I have also introduced a best practice concerning use of color. As you will see in Assigning Colors , I use seven colors for pie chart generation, but reuse only the last five. If the first color were reused for an eighth and final slice, two slices with the same color would appear adjacently, at the top. If the pie contained nine slices, the four segments at the top would have only two colors. Not reusing the first two colors avoids these visual confusions.

One other best practice is worthy of mention. The [SAP Design Guild] recommends against perspective, saying that "perspective makes it impossible to reliably compare the sections of a pie; futhermore, 3D borders may add to the area of segments. Also angles are much harder to estimate than distances." These statements strike me as true, so I follow this recommendation. These two practices can therefore be added to the bullet list:

Table 1 contains a reordered and reworded list of the best practices I followed.

  1. Begin the first segment at the upper vertical - 12 o'clock
  2. Draw segments clockwise
  3. Order segments by size, starting with the largest, except for 'other'
  4. Place the slice representing 'other' last
  5. Segment values total 100%
  6. Do not use a segment to represent less than 5% of the total
  7. Aggregate any values less than 5% into 'other'
  8. Do not reuse the first two colors
  9. Include a label to identify each segment
  10. Include values showing the percentages that the segments represent
  11. Do not use perspective or 3D effects

Table 1

Transformations

As indicated in Best Practices , I need to sort the components and aggregate the smaller ones into 'other'. Rather than attempt to pull out the smaller elements, aggregate, and sort in the same transformation that creates the pie chart and its legend, I separate the tasks into three distinct [XSLT] transformations. The first and second transformations store their results in memory, and the second and third transform these stored files. The code for this is shown in the discussion of Creating the Pie Chart .

First Transformation: Pulling Out the 'other's

The first transformation takes an XML input file, tests each <component> to determine if it represents less than 5% of the total, and replaces it with a new element, <other>, if it does. <other> receives the text node from <component> as its value. The transformation also takes any <component> with the name attribute of either 'Other' or 'other' and replaces it with this new element in the same manner. If the test condition is not met - that is, if the <component> represents 5% or more of the total and does not have the value 'Other' or 'other' for its name attribute - it is simply copied over. Here is the code:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>

<xsl:template match="/">
     <xsl:apply-templates select="components"/>
</xsl:template>

<xsl:template match="components">
     <xsl:copy>
          <xsl:variable name="total" select="sum(component)"/>
          <xsl:for-each select="component">
               <xsl:variable name="number" select="."/>
               <xsl:choose>
                    <xsl:when test="(($number div $total) &lt; .05) or @name = 'Other' or @name = 'other'">
                         <xsl:element name="other">
                              <xsl:value-of select="."/>
                         </xsl:element>
                    </xsl:when>
                    <xsl:otherwise>
                         <xsl:copy-of select="."/>
                    </xsl:otherwise>
               </xsl:choose>
          </xsl:for-each>
     </xsl:copy>
</xsl:template>

</xsl:stylesheet>

In the example presented here, three <component> elements have become <other> elements: the components for E-Business Office and Systems Development, since each reprsents less than 5% of the total, and the component for Other. Here is the resulting XML file:

<?xml version="1.0" encoding="UTF-8"?>
<components xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
     <component name="Information Technology">5712</component>
     <component name="NADCO">5033</component>
     <component name="Electronic Security">2104</component>
     <component name="SATT">1608</component>
     <other>291</other>
     <other>158</other>
     <other>694</other>
</components>

The only result that may be unexpected is the addition of the XSI namespace in the <components> element, which reflects a harmless [Xalan] default.

Second Transformation: Aggregation and Sorting

The second transformation sorts the <component>s representing 5% or more of the total, aggregates the text nodes (which are quantities) for the <other> elements, and places the result of that aggregation into a new <component> with the value 'Other' for the attribute name. This 'Other' element is the last component, following the Best Practices , even if its value is greater than that of other components. Here is the code:

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>

<xsl:template match="/">
     <xsl:apply-templates select="components"/>
</xsl:template>

<xsl:template match="components">
     <xsl:copy>
          <xsl:for-each select="component">
               <xsl:sort select="." order="descending" data-type="number"/>
               <xsl:copy-of select="."/>
          </xsl:for-each>
          <xsl:variable name="total" select="sum(other)"/>
          <xsl:if test="$total &gt; 0">
               <xsl:element name="component">
                    <xsl:attribute name="name">
                         <xsl:text>Other</xsl:text>
                    </xsl:attribute>
                    <xsl:value-of select="$total"/>
               </xsl:element>
          </xsl:if>
     </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Are here is the result:

<?xml version="1.0" encoding="UTF-8"?>
<components xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
     <component name="Information Technology">5712</component>
     <component name="NADCO">5033</component>
     <component name="Electronic Security">2104</component>
     <component name="SATT">1608</component>
     <component name="Other">1143</component>
</components>

There are no surprises. We are ready to create the pie chart and its legend.

Third Transformation: Creating the Pie Chart

The final transformation takes this XML file and creates the SVG. It is more complex than what has come before, and I shall go through the code in sequence. All the code is included, though, so that it can be copied into a file and run (just as I did with the code in the [Tidwell Tutorial] ).

Assigning Colors

I have chosen to use 7 different colors for slices, and the first part of the XSLT sets these colors. While there will normally be fewer, there can be no more than 20 slices, since each slice must represent at least 5% of the total. Instead of writing a more sophisticated algorithm, I take advantage of this small number and simply test for positions 1 through 20.

The code begins with the application of the template for the top-level element <components>. This sets the dimensions for the SVG space, sets a variable for the sum of all the child components, and then specifies an xsl:for-each for each <component>. The first part of the for-each uses the XPath [XPath] position() function to set a color for each slice; the <xsl:otherwise> sets the color for positions 7, 12, and 17. The following code takes the XSLT through the assignment of a color to the color variable for each component.

<xsl:stylesheet version="1.0" 
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
     xmlns:java="http://xml.apache.org/xslt/java"
     xmlns:xlink="http://www.w3.org/1999/xlink">
<xsl:output method="svg"/>

<xsl:template match="/">
     <xsl:apply-templates select="components"/>
</xsl:template>

<xsl:template match="components">
     <svg width="500" height="500" viewBox="0 0 500 500">
     <xsl:variable name="total" select="sum(component)"/>
     <xsl:for-each select="component">
          <xsl:variable name="color">
               <xsl:choose>
                    <xsl:when test="(position() = 1)">
                         <xsl:text>lightSalmon</xsl:text>
                    </xsl:when>
                    <xsl:when test="(position() = 2)">
                         <xsl:text>lightCyan</xsl:text>
                    </xsl:when>
                    <xsl:when test="(position() = 3 or position() = 8 or position() = 13 or position() = 18)">
                         <xsl:text>mistyRose</xsl:text>
                    </xsl:when>
                    <xsl:when test="(position() = 4 or position() = 9 or position() = 14 or position() = 19)">
                         <xsl:text>gold</xsl:text>
                    </xsl:when>
                    <xsl:when test="(position() = 5 or position() = 10 or position() = 15 or position() = 20)">
                         <xsl:text>tan</xsl:text>
                    </xsl:when>
                    <xsl:when test="(position() = 6 or position() = 11 or position() = 16)">
                         <xsl:text>lightGreen</xsl:text>
                    </xsl:when>
                    <xsl:otherwise>
                         <xsl:text>lightSteelBlue</xsl:text>
                    </xsl:otherwise>
               </xsl:choose>
          </xsl:variable>

Reuse of only the last five colors follows one of the Best Practices described above.

Applying the Templates

From here, the work continues to proceed slice by slice. After assigning a color, and still within the xsl:for-each context (for each component), I assign a variable to keep track of the position of the component that the slice will represent. I then call a template that creates the slice, sending that template the color to use, the total that the pie represents, and how far along the pie creation has gotten - the incremental total. (The total will be used in calculating the area that the slice represents, and the incremental total will be used in calculating where the slice begins.) Finally, I call another template that creates the entry in the legend for that slice, sending that template the color to which the entry corresponds and an offset to indicate where in the legend (essentially a table of keys) this particular entry goes. Here is the code for this, which takes the XSLT to the end of the template for <components>.

          <xsl:variable name="position" select="position()"/>
          
          <xsl:apply-templates select=".">
               <xsl:with-param name="color" select="$color"/>
               <xsl:with-param name="total" select="$total"/>
               <xsl:with-param name="runningTotal" select = "sum(preceding-sibling::component)"/>
          </xsl:apply-templates>

          <xsl:apply-templates select="." mode="legend">
               <xsl:with-param name="color" select="$color"/>
               <xsl:with-param name="offset" select="90 + ($position * 20)"/>
          </xsl:apply-templates>

     </xsl:for-each>
     </svg>
</xsl:template>
Making the Calculations

The next section of the stylesheet starts the template match for <component> in its default mode. (<component> is matched in another mode to create the legend.) It first initializes the parameters it receives and puts the number held in the text node into a variable. It then makes the calculations necessary to draw each slice and place the percentage figure, using the Java math [Xalan Extensions] through the namespace that xmlns:java="http://xml.apache.org/xslt/java" references. It puts the results of these calculations into variables. The path uses the variables currentAngle and rotation. The placement of the percentage uses more variables, because the figure needs not only to be located but also to be aligned to the horizontal. These calculations reuse rotation to calculate cosTheta and sinTheta, and create halfAngle for use in calculating x1 and y1. Here is the code:

<xsl:template match="component">
     <xsl:param name="color" select="'indianRed'"/>
     <xsl:param name="total" select="'0'"/>
     <xsl:param name="runningTotal" select="'0'"/>
     <xsl:variable name="number" select="."/>
     <xsl:variable name="currentAngle"
          select="java:java.lang.Math.toRadians(($number div $total) * 360.0)"/>
     <xsl:variable name="halfAngle"
          select="java:java.lang.Math.toRadians((($number div 2) div $total) * 360.0)"/>
     <xsl:variable name="rotation" select="270 + (360.0 * ($runningTotal div $total))"/>
     <xsl:variable name="x1" select="java:java.lang.Math.cos($halfAngle) * 70"/>
     <xsl:variable name="y1" select="java:java.lang.Math.sin($halfAngle) * 70"/>
     <xsl:variable name="cosTheta" 
          select="java:java.lang.Math.cos(java:java.lang.Math.toRadians($rotation))"/>
     <xsl:variable name="sinTheta" 
          select="java:java.lang.Math.sin(java:java.lang.Math.toRadians($rotation))"/>

This section makes three changes to the calculations in the [Tidwell Tutorial] to bring the charts into compliance with the Best Practices , which require beginning at the top vertical. First, it changes the rotation from negative to positive. Then it adds 270 to the rotation to bring it around to the top vertical, as every slice is calculated from the right horizontal and rotated. Finally, it makes the multiplier applied to the sine positive rather than negative.

Creating the Path

The next section of the XSLT takes the results of the calculations and uses them to create a path statement. The sweep flag (the fifth parameter of the arc) is 1, so that the segments are laid out in a clockwise direction, in compliance with another of the Best Practices . Here is the code:

     <path style="fill:{$color};stroke:black;stroke-width:1;
          fillrule:evenodd;stroke-linejoin:bevel;">
          <xsl:attribute name="transform">
               <xsl:text>translate(150,150)</xsl:text> <!--centering the circle in the non-legend space-->
               <xsl:text>rotate(</xsl:text>
               <xsl:value-of select="$rotation"/>
               <xsl:text>)</xsl:text>
          </xsl:attribute>
          <xsl:attribute name="d">
               <xsl:text>M 100 0 A 100 100 0 </xsl:text> <!-- to change size, change all instances of 100-->
                    <xsl:choose>
                         <xsl:when test="$currentAngle > 3.14">
                              <xsl:text>1 </xsl:text>
                         </xsl:when>
                         <xsl:otherwise>
                              <xsl:text>0 </xsl:text>
                         </xsl:otherwise>
                    </xsl:choose>
                    <xsl:text>1 </xsl:text>
                    <xsl:value-of select="java:java.lang.Math.cos($currentAngle) * 100"/>
                    <xsl:text> </xsl:text>
                    <xsl:value-of select="java:java.lang.Math.sin($currentAngle) * 100"/>
                    <xsl:text> L 0 0 Z</xsl:text>
               </xsl:attribute>
          </path>

I've retained two comments in the XSLT. The first notes that the transform translate value of 150,150 centers the pie chart in the space defined for it - that is, the part of the viewbox that does not hold the legend. The second indicates the code that must be changed to change the size of the pie chart. The other parts of the arc follow the [elliptical arc curve commands] of the [SVG] specification.

Creating the Text

The next section of the stylesheet creates and places horizontally aligned text for the percentage figure in each slice, and then ends the template:

     <text style="text-anchor:middle">
          <xsl:attribute name="transform">
               <xsl:text>translate(150,150) </xsl:text> 
          </xsl:attribute>
          <xsl:attribute name="x">
               <xsl:value-of select="($x1 * $cosTheta) - ($y1 * $sinTheta)"/>
          </xsl:attribute>
          <xsl:attribute name="y">
               <xsl:value-of select="($x1 * $sinTheta) + ($y1 * $cosTheta)"/>
          </xsl:attribute>
          <xsl:value-of select="round(100 * ($number div $total))"/>
          <xsl:text>%</xsl:text>
     </text>
</xsl:template>
Creating the Legend

The final section is another template to match <component>; this mode creates the legend. The first part of the template creates text holding the value of the name attribute. For placement, it uses the offset parameter, which is given a value incremented by position in Applying the Templates . The second part uses the offset to draw a path that creates a small box with the color of the corresponding segment.

<xsl:template match="component" mode="legend">
     <xsl:param name="color" select="'indianRed'"/>
     <xsl:param name="offset" select="'0'"/>
     <text>
          <xsl:attribute name="style">
               <xsl:text>font-size:12; text-anchor:start</xsl:text>
          </xsl:attribute>
          <xsl:attribute name="x">
               <xsl:text>320</xsl:text>
          </xsl:attribute>
          <xsl:attribute name="y">
               <xsl:value-of select="$offset"/>
          </xsl:attribute>
          <xsl:value-of select="@name"/>
          <xsl:text> (</xsl:text>
          <xsl:value-of select="."/>
          <xsl:text>) </xsl:text>
     </text>
     <path>
          <xsl:attribute name="style">
               <xsl:text>stroke:black; stroke-width:1; fill:</xsl:text>
               <xsl:value-of select="$color"/>
          </xsl:attribute>
          <xsl:attribute name="d">
               <xsl:text>M 290 </xsl:text>
               <xsl:value-of select="$offset - 10"/>
               <xsl:text> L 290 </xsl:text>
               <xsl:value-of select="$offset"/>
               <xsl:text> L 300 </xsl:text>
               <xsl:value-of select="$offset"/>
               <xsl:text> L 300 </xsl:text>
               <xsl:value-of select="$offset - 10"/>
               <xsl:text> Z</xsl:text>
          </xsl:attribute>
     </path>
</xsl:template>

</xsl:stylesheet>
The Result

This process creates the pie chart given as Figure 1 , where 'view source' will reveal the generated SVG code. A content provider can modify this code, and can embed the SVG into an HTML document, or into any other context in which SVG can be read.

Web Services

The transformations just described could be done from a command line. We wanted to offer these transformations through Web services, so that they could be invoked automatically as well as through human interaction. This section describes two Web services, one for validating the XML and the other for generating pie charts.

Validation

In addition to the Web service that builds pie charts, we created a Web service to validate the XML. This is invoked first. I present some of What Can Go Wrong below, but validation has intuitive appeal in any case. By validating the XML first, one is not forced to handle invalid XML in the pie chart service. Making validation a separate Web service preserves modularity, and as a separate service, validation is available to other processing sequences.

The Process

I've included the code below; it is mostly the result of working through the [Chase] tutorial and choosing what was needed. Although the code runs on a bit, in the way that code can, it's actually very simple. The validation is done by a [SAX] parser from [Xerces] , and the line that does the work is in the validator class: parser.parse(xml). To have the parser validate against the schema I specify, I set two [Xerces Features] , /validation and /validation/schema, and one [Xerces Properties] , /schema/external-noNamespaceSchemaLocation, which uses an external schema that I identify. I want to be sure that the validation is against the schema I specify, so I include it explicitly. If the parser were invoked with a different schema passed as a parameter, the property would override the parameter.

package validation;

import java.io.IOException;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.apache.xerces.parsers.SAXParser;

public class ValidationService extends DefaultHandler {

     static SAXParser parser = null;
     static String messages;

     public void warning(SAXParseException exception) throws SAXException {
           messages = messages + "WARNING: " + exception.getMessage();
     }

     public void error(SAXParseException exception) throws SAXException {
           messages = messages + "ERROR: " + exception.getMessage();
     }

     public void fatalError(SAXParseException exception) throws SAXException {
		messages = messages + "FATAL ERROR: " + exception.getMessage();
     }

     private static void setFeature(String feature, boolean setting){
          try {
               parser.setFeature(feature,setting);
          }
          catch (SAXNotRecognizedException exception) {
               System.out.print("Feature unrecognized by SAX Parser: ");
               System.out.println(exception.getMessage());
          }
          catch (SAXNotSupportedException exception) {
               System.out.print("Feature unsupported by SAX Parser: ");
               System.out.println(exception.getMessage());
          }
     }
     
     private static void setProperty(String property, Object setting){
          try {
               parser.setProperty(property,setting);
          }
          catch (SAXNotRecognizedException exception) {
               System.out.print("Feature unrecognized by SAX Parser: ");
               System.out.println(exception.getMessage());
          }
          catch (SAXNotSupportedException exception) {
               System.out.print("Feature unsupported by SAX Parser: ");
               System.out.println(exception.getMessage());
          }
     }
     
     synchronized public String validator (String xml) {
          parser = new SAXParser();
          DefaultHandler handler = new ValidationService();
          messages = "";
          parser.setErrorHandler(handler);
          setFeature("http://xml.org/sax/features/validation",true);
          setFeature("http://apache.org/xml/features/validation/schema",true);
          setProperty("http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation",
               "http://ats.it.ny.frb.org/validation/pie.xsd");
          try {
               parser.parse(xml);
          }
          catch(IOException exception) {
               System.out.print("Cannot read the file: ");
               System.out.println(exception.getMessage());
          }
          catch(SAXException exception) {
               System.out.print("Error parsing file: ");
               System.out.println(exception.getMessage());
          }
          return(messages);
     }
}

Since this is a Web service running over [SOAP] , it needs to be described and deployed. I do not explain deployment descriptors in this pager (see [SOAP Deploy] ), but this is its deployment descriptor:

<?xml version="1.0" encoding="UTF-8"?>
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment" id="urn:xmlValidator">
     <isd:provider type="java" scope="Application" methods="validator">
          <isd:java class="validation.ValidationService"/>
     </isd:provider>
     <isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener>
</isd:service>

I also do not explain how to perform a remote procedure call over SOAP in this paper; see either [SOAP RPC] or [Goodwill] , from whom I've taken the code, altering little but the object references. (I've also omitted the comments, but you can see them in the original.) This is the client piece for the validation Web service:

package validation;

import java.io.*;
import java.net.*;
import java.util.*;
import org.apache.soap.*;
import org.apache.soap.rpc.*;

public class ValidationClient {
     public static void main(String[] args) throws Exception {
          String result = new ValidationClient().runSoap(args);
          System.out.println (result);
     }
       
     public String runSoap (String[] args) throws Exception {
          URL url = new URL ("http://ats.it.ny.frb.org:8080/soap/servlet/rpcrouter");
          String xml = new String (args[0]);
          Call call = new Call();
          call.setTargetObjectURI("urn:xmlValidator");
          call.setMethodName("validator");
          call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
          Vector params = new Vector();
          params.addElement(new Parameter("xml", String.class, xml, null));
          call.setParams (params);
          Response resp = call.invoke(url, "" );
          if ( resp.generatedFault() ) {
               Fault fault = resp.getFault ();
               System.out.println("The call failed: ");
               System.out.println("Fault Code from ValidationClient: " + fault.getFaultCode());
               System.out.println("Fault String from ValidationClient: " + fault.getFaultString());
               return null;
          }
           else {
               Parameter result = resp.getReturnValue();
               return result.getValue().toString();
          }
     }
}

What Can Go Wrong

In introducing Validation , I said that there is probably no need to convince anyone to validate an input file before attempting to build a pie chart. Just in case, though, I'll show what happened when I tried to generate pie charts from invalid XML input files.

In one trial, I included an extra attribute in the <components> top element, so that the line read <components year="2003" xmlns:xsi="http://www.w3c.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="pie.xsd">. (I had had an earlier version of the pie chart generator which put out a label for the year for which the chart was drawn, but found this too limiting.) The extra attribute caused no problem, and the chart was drawn correctly. In this case, the absence of validation was arguably preferable, for with validity checking the chart was not drawn. Instead, an error message was sent: ERROR: cvc-complex-type.3.2.2: Attribute 'year' is not allowed to appear in element 'components'. On the other hand, someone including the extra attribute would likely assume that it would have an effect, and this effect would be missing.

Validity checking was unambiguously preferable in other trials. For one, I omitted the end bracket for one of the components, so that a line of the input file read <component name="NADCO">5033</component. Validation of this input file returned the error FATAL ERROR: The end-tag for element type 'component' must end with a '>' delimiter Without validation, no error message was returned, but no pie chart was drawn.

Two other conditions resulted in charts being drawn, but incorrectly. One condition simply resulted in the legend missing a name; this occurred when the 'name' attribute was omitted for one component, in <component>5033</component>. Validation returned this error message: ERROR: cvc-complex-type.4: Attribute 'name' must appear on element 'component'.. Without validation, the legend is incorrectly drawn, as shown in Figure 2 :

pieMissingName.svg

Figure 2: Invalid Input File: Missing Name

The result was more seriously wrong when a number (the text node of a <component>) was missing, in <component name="Other"></component>. Validation returned two error messages: ERROR: cvc-datatype-valid.1.2.1: '' is not a valid 'decimal' value and ERROR: cvc-complex-type.2.2: Element 'component' must have no element [children], and the value must be valid.. Without validation, a badly formed chart was returned, as shown in Figure 3 :

pieMissingNumber.svg

Figure 3: Invalid Input File: Missing Number

With the text node (number) missing, the first stylesheet, shown in First Transformation: Pulling Out the 'other's , could not get a result for sum(component); the value of the variable total was therefore NaN, not a number. The check for each component being less than 5% of the total could not be made. Given how the test is written in the stylesheet, the elements that would have been cast as 'other' were simply copied and passed through. The second stylesheet, shown in Second Transformation: Aggregation and Sorting , could sort, but had nothing to aggregate, and the final stylesheet, shown in Third Transformation: Creating the Pie Chart , wrote the small segments and their overlaying percentage figures.

Things got even worse when both a name and a number are missing, but I think the case for validation is made.

Creating the Pie Chart

With the XML validated, a Web service can use The XSLT to create a pie chart. In describing the XSLT, I presented stylesheets for three separate XSLT transformations:

The code that executes the transformations uses the [Xalan] XSLT processor. Following the Xalan [Basic Usage Patterns] (from which the code was largely taken), I first create a TransformerFactory and cast it as a SAXTransformerFactory, which provides access to TransformerHandler. I then create three instances of TransformerHandler, each of which gets one of the three stylesheets as a StreamSource.

An XMLReader sets the first TransformerHandler as the ContentHandler that parses the input. The result gets passed to the next handler, and the result of that transformation gets passed to the third and final handler. This process, which follows the usage pattern called "Using transformation output as input for another transformation," then returns the SVG pie chart. With error handling omitted, here is the code:

package pieChart;

import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamSource;

import org.xml.sax.SAXException;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.XMLReader;

import org.apache.xalan.serialize.Serializer;
import org.apache.xalan.serialize.SerializerFactory;
import org.apache.xalan.templates.OutputProperties;

import java.io.IOException;
import java.io.FileOutputStream;


public class PieService {
     public String pieMaker (String xml) throws
          TransformerException, TransformerConfigurationException, SAXException, IOException {
          TransformerFactory tFactory = TransformerFactory.newInstance();
          if (tFactory.getFeature(SAXSource.FEATURE) && tFactory.getFeature(SAXResult.FEATURE)) {
               SAXTransformerFactory saxTFactory = ((SAXTransformerFactory) tFactory);
               TransformerHandler tHandler1 = 
                    saxTFactory.newTransformerHandler(new StreamSource
                    ("http://ats.it.ny.frb.org/pieChart/intermediatePie1.xsl"));
               TransformerHandler tHandler2 = 
                    saxTFactory.newTransformerHandler(new StreamSource
                    ("http://ats.it.ny.frb.org/pieChart/intermediatePie2.xsl"));
               TransformerHandler tHandler3 = 
                    saxTFactory.newTransformerHandler(new StreamSource
                    ("http://ats.it.ny.frb.org/pieChart/pieLabel.xsl"));
               tHandler1.setResult(new SAXResult(tHandler2));
               tHandler2.setResult(new SAXResult(tHandler3));
               Serializer serializer = SerializerFactory.getSerializer(OutputProperties.getDefaultMethodProperties("xml"));
               serializer.setOutputStream(new FileOutputStream("e:/Netscape/webserver/docs/temp/pieResult.svg"));
               tHandler3.setResult(new SAXResult(serializer.asContentHandler()));
               
               XMLReader reader = XMLReaderFactory.createXMLReader();
               reader.setContentHandler(tHandler1);
               reader.setProperty("http://xml.org/sax/properties/lexical-handler", tHandler1);
               reader.parse(xml);
          }
          return("http://ats.it.ny.frb.org/temp/pieResult.svg");
     }
}

As with the Web service for validation, this needs to be deployed and describer. Here is the deployment descriptor:

<?xml version="1.0" encoding="UTF-8"?>
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment" id="urn:pieChartServer">
     <isd:provider type="java" scope="Application" methods="pieMaker">
          <isd:java class="pieChart.PieService"/>
     </isd:provider>
     <isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener>
</isd:service>

Except for the names of the objects, the client code is the same as for the ValidationClient:

package pieChart;

import java.io.*;
import java.net.*;
import java.util.*;
import org.apache.soap.*;
import org.apache.soap.rpc.*;

public class PieClient {

     public static void main(String[] args) throws Exception {
          String result = new PieClient().runSoap(args);
          System.out.println (result);
     }
     
     public String runSoap (String[] args) throws Exception {
          URL url = new URL ("http://ats.it.ny.frb.org:8080/soap/servlet/rpcrouter");
          String xml = new String (args[0]);
          Call call = new Call();
          call.setTargetObjectURI("urn:pieChartServer");
          call.setMethodName("pieMaker");
          call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
          Vector params = new Vector();
          params.addElement(new Parameter("xml", String.class, xml, null));
          call.setParams (params);
          Response resp = call.invoke(url, "" );
          if ( resp.generatedFault() ) {
               Fault fault = resp.getFault ();
               System.out.println("The call failed: ");
               System.out.println("Fault Code from PieClient: " + fault.getFaultCode());
               System.out.println("Fault String from PieClient: " + fault.getFaultString());
               return null;
          }
          else {
               Parameter result = resp.getReturnValue();
               return result.getValue().toString();
          }
     }
}

Invoking the Web Service through a Servlet

While we may look forward to the time when Web services make up a large part of Web traffic, we are not there yet. To accommodate direct human use of the Web services, I constructed a rudimentary Java servlet that that accepts a URI and calls them. [2] A more robust servlet would accept the XML in other forms, such as text and as a file present in a local directory. If the Web services generate the SVG pie chart, the servlet embeds that chart into an HTML page. A more robust servlet would also format the page nicely, perhaps offering some text explaining how the SVG might be modified. If validation fails, a different HTML page is returned, with the error messages concatenated and written out. Again, a more robust servlet would separate the fatal and simple errors, and put out user-friendly text guiding content providers in how to correct their XML. This servlet simply calls the validation service, then either calls the pie chart service and puts out the chart if there are no errors, or writes out the errors otherwise.

package validation;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import pieChart.*;

public class ValidationAndPieServlet extends HttpServlet {

     public void doGet (HttpServletRequest  request, HttpServletResponse response) throws IOException, ServletException {
          response.setContentType("text/html");
          PrintWriter out = response.getWriter();
          String args[] = new String[1];
          args[0]= request.getParameter("xml");
          try {
               String result = new ValidationClient().runSoap(args);
               if (result.equals("")) {
                    String result2 = new PieClient().runSoap(args);
                    out.println("<html><body><embed src='" + result2 + "' type='image/svg+xml'" + 
                         "width=\"500\" height=\"300\"></body></html>");
               } else {
                    out.println("<html><body>" + result + "</body></html>");
               }
          }
          catch (Exception exc){
               out.println("error in SOAP call: exception " + exc);
          }
     }
}

Conclusion

This paper presents the use of open source, standard software to create Web services that result in the creation of pie charts. The process requires little from those wishing to obtain pie charts, and represents a way to avoid the cost of proprietary tools. As such, it enables us to save money both in time and in software costs. I began with pie charts because they are simpler than other charts - there are few scaling issues with a circle, which cannot vary from 360°. With more work, though, we can offer Web services to create other charts, such as bar graphs. There are also other constrained graphics that we currently offer through SVG, such as a set of dials (based on automobile speedometers) showing how well service level agreements are being met. (The red, yellow, and green portions of the dials reflect the agreements, and needles show compliance.) These too can be offered as Web services. We will save money when more of our processes are automated, but the services must be there to be called.

Footnotes

  1. Actually, the XSLT creates a pie chart with a height of 500 pixels, which I then changed to 300 for this paper. 500 pixels allow sufficient height to accommodate the maximum length legend, but, as in this example, this height is not normally needed. I expect other users to change this attribute as well.

  2. To make things simple, I put the validation client in the directory holding the servlet, put the pie client in a sibling directory, and imported that directory.

Acknowledgements

Although I don't program as much as I once did, I retain my one programming strength: I use other people's code. For the work presented here, I acknowledge Doug Tidwell, whose code I used in my first attempts at creating SVG pie charts. I commend both his tutorial [Tidwell Tutorial] and his book [Tidwell Book] to your attention. The book does not have a CD, but the publisher maintains the code online: follow the link on the book's page (http://www.oreilly.com/catalog/xslt/) to 'examples'. For Web services over SOAP, I used the work of James Goodwill [Goodwill] , and for validation through Xerces I used the work of Nicholas Chase [Chase] .

I used the SAP Design Guild's "Recommendations for Charts and Graphics" [SAP Design Guild] in developing my take on Best Practices . The documents states that "the material in these recommendations are collected from various sources," and, in good Web fashion, I have relied on its distillation rather than redoing the work myself.

Christopher Kell, Automation Officer, Federal Reserve Bank of New York, solved my problem of how to apply the calculations for the slices. Pat Gleason, my colleague in the Advanced Technology Center, solved a number of other difficulties I encountered. Rohan Nishar, an intern with Information Technology, helped me bring the application from my desktop to a production server.

Bibliography

[SOAP Deploy]
Apache SOAP User's Guide, "Deployment Descriptors" (http://ws.apache.org/soap/docs/guide/deploy.html)
[SOAP RPC]
Apache SOAP User's Guide, "Writing RPC Clients" (http://ws.apache.org/soap/docs/guide/rpcclient.html)
[Xalan]
Apache XML Project, "Xalan-Java" (http://xml.apache.org/xalan-j/)
[Basic Usage Patterns]
Apache XML Project, "[Xalan-Java] Basic usage patterns " (http://xml.apache.org/xalan-j/usagepatterns.html)
[Xalan Extensions]
Apache XML Project, "[Xalan-Java] Extensions for XSLTC " (http://xml.apache.org/xalan-j/extensions_xsltc.html)
[Xerces]
Apache XML Project, "Xerces Java2 Parser" (http://xml.apache.org/xerces2-j/index.html)
[Xerces Features]
Apache XML Project, "Xerces Java2 Parser; Parser Features" (http://xml.apache.org/xerces2-j/features.html)
[Xerces Properties]
Apache XML Project, "Xerces Java2 Parser; Parser Properties" (http://xml.apache.org/xerces2-j/properties.html)
[Chase]
Chase, Nicholas, "XML Schema Validation in Xerces-Java 2," IBM developerWorks (http://www-106.ibm.com/developerworks/xml/edu/x-dw-xxschema-i.html)
[Goodwill]
Goodwill, James, "Using SOAP with Tomcat," February 27, 2002 (http://www.onjava.com/lpt/a/onjava/2002/02/27/tomcat.html)
[SAP Design Guild]
SAP Design Guild, "Recommendations for Charts and Graphics" (http://www.sapdesignguild.org/resources/diagram_guidelines/diagram_guidelines.pdf)
[SAX]
SAX (Simple API for XML) (http://www.saxproject.org/)
[SVG]
"Scalable Vector Graphics (SVG) 1.0 Specification," W3C Recommendation, 04 September 2001 (http://www.w3.org/TR/SVG/)
[elliptical arc curve commands]
"Scalable Vector Graphics (SVG) 1.0 Specification, 8.3.8 The elliptical arc curve commands," W3C Recommendation, 04 September 2001 (http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands)
[SOAP]
"Simple Object Access Protocol (SOAP) 1.1," W3C Note, 08 May 2000 (http://www.w3.org/TR/SOAP/)
[Tidwell Tutorial]
Tidwell, Doug, "Tutorial: Transforming XML Documents, Part 2: Transforming XML into SVG," January 2000, updated March 2001 (http://www-106.ibm.com/developerworks/education/transforming-xml/xmltosvg/)
[Tidwell Book]
Tidwell, Doug, XSLT, O'Reilly & Associates, Sebastopol, California, 2001
[Tufte]
Tufte, Edward R., The Visual Display of Quantitative Information, second edition, Graphics Press, Cheshire, Connecticut, 2001
[XPath]
"XML Path Language (XPath) Version 1.0," W3C Recommendation, 16 November 1999 (http://www.w3.org/TR/xpath)
[XML Schema]
"XML Schema," W3C Architecture domain (http://www.w3.org/XML/Schema)
[XSLT]
"XSL Transformations (XSLT) Version 1.0," W3C Recommendation, 16 November 1999 (http://www.w3.org/TR/xslt)

XHTML rendition created by gcapaper Web Publisher v2.0, © 2001-3 Schema Software Inc.