Automated Tree Drawing: XSLT and SVG
by Jirka Kosek
|
Pages: 1, 2, 3
Generating SVG
It is surprisingly easy to create SVG graphics from the nodes with added layout information. The depth of a node directly corresponds to the Y-coordinate. The X-coordinate can be calculated from the width of a node — it is the sum of widths of the preceding nodes with the same depth or with the lower depth if there is no preceding node on the same level. This looks like a complicated calculation but in fact it can be expressed by one XPath expression. The following XSLT code takes as input the layout from the previous stage and creates an SVG image from it.
<!-- Magnifying factor -->
<xsl:param name="hedge.scale" select="10"/>
<!-- Convert layout to SVG -->
<xsl:template name="layout2svg">
<xsl:param name="layout"/>
<!-- Find depth of the tree -->
<xsl:variable name = "maxDepth">
<xsl:for-each select = "$layout//node">
<xsl:sort select = "@depth" data-type = "number" order = "descending"/>
<xsl:if test = "position() = 1">
<xsl:value-of select = "@depth"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!-- Create SVG wrapper -->
<svg:svg viewBox = "0 0 {sum($layout/node/@width) * 2 * $hedge.scale} {$maxDepth * 2 * $hedge.scale}">
<!-- Note that some SVG implementations work better when you set explicit width and height also.
In that case add following attributes to svg element:
width="{sum($layout/node/@width)*5}mm"
depth = "{$maxDepth*5}mm"
preserveAspectRatio="xMidYMid meet" -->
<svg:g transform = "translate(0,-{$hedge.scale div 2}) scale({$hedge.scale})">
<xsl:apply-templates select = "$layout/node" mode = "layout2svg"/>
</svg:g>
</svg:svg>
</xsl:template>
<!-- Draw one node -->
<xsl:template match = "node" mode = "layout2svg">
<!-- Calculate X coordinate -->
<xsl:variable name="x" select = "(sum(preceding::node[@depth = current()/@depth or (not(node) and @depth <= current()/@depth)]/@width) + (@width div 2)) * 2"/>
<!-- Calculate Y coordinate -->
<xsl:variable name = "y" select = "@depth * 2"/>
<!-- Draw label of node -->
<svg:text x = "{$x}"
y = "{$y - 0.2}"
style = "text-anchor: middle; font-size: 0.9;">
<xsl:value-of select="@label"/>
</svg:text>
<!-- Draw rounded rectangle around label -->
<svg:rect x = "{$x - 0.9}" y="{$y - 1}" width = "1.8" height="1"
rx = "0.4" ry = "0.4"
style = "fill: none; stroke: black; stroke-width: 0.1;"/>
<!-- Draw connector lines to all sub-nodes -->
<xsl:for-each select="node">
<svg:line x1 = "{$x}"
y1 = "{$y}"
x2 = "{(sum(preceding::node[@depth = current()/@depth or (not(node) and @depth < = current()/@depth)]/@width) + (@width div 2)) * 2}"
y2 = "{@depth * 2 - 1}"
style = "stroke-width: 0.1; stroke: black;"/>
</xsl:for-each>
<!-- Draw sub-nodes -->
<xsl:apply-templates select = "node" mode = "layout2svg"/>
</xsl:template>
Putting the Pieces Together
I have so far described and implemented the process of getting an SVG image from the compact syntax as a series of steps. Now it's time to combine these steps together. The following templates create an SVG image of tree (or hedge) passed as a parameter.
<xsl:template name="hedge2svg">
<!-- Hedge is passed as parameter -->
<xsl:param name="hedge"/>
<!-- Parse text hedge into XML nodes -->
<xsl:variable name="tree">
<xsl:call-template name="hedge2xml">
<xsl:with-param name="hedge" select="$hedge"/>
</xsl:call-template>
</xsl:variable>
<!-- Add layout information to XML nodes -->
<xsl:variable name="layoutTree">
<xsl:apply-templates select="exsl:node-set($tree)/node" mode="xml2layout"/>
</xsl:variable>
<!-- Turn XML nodes into SVG image -->
<xsl:call-template name="layout2svg">
<xsl:with-param name="layout" select="exsl:node-set($layoutTree)"/>
</xsl:call-template>
</xsl:template>
We use node-set since several passes over the data are needed.
Integration with Existing Stylesheets
Drawing trees is an interesting exercise but my initial motivation was more practical. I plan to use trees as an integral part of a document. For example, I might want to use trees as part of a DocBook document:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE article PUBLIC '-//OASIS//DTD DocBook XML V4.3//EN'
'http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd'>
<article lang="en">
<title>DocBook article with hedges and trees</title>
<para>Inline hedges:
<phrase role="hedge">a(ϵ)</phrase>,
<phrase role="hedge">a(x)</phrase> and
<phrase role="hedge">a(ϵ)b(c(ϵ)x)</phrase>.</para>
<para>Sample tree: <phrase role="hedge">a(b(pqr)c(xyz)d(mno))</phrase></para>
<para>Sample tree: <phrase role="hedge">a(bcd(ef))</phrase></para>
</article>
In order to draw the content of the phrase element as a hedge, we must create a customization layer that combines stock DocBook stylesheets with our hedge-to-SVG code. We use an instream-foreign-object formatting object to place SVG directly into the flow of formatting objects.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
version="1.0">
<xsl:import href = "http://docbook.sourceforge.net/release/xsl/current/fo/docbook.xsl"/>
<xsl:include href="hedge2svg.xsl"/>
<xsl:template match="phrase[@role='hedge']">
<fo:instream-foreign-object alignment-baseline="middle">
<xsl:call-template name="hedge2svg">
<xsl:with-param name="hedge" select="."/>
</xsl:call-template>
</fo:instream-foreign-object>
</xsl:template>
<xsl:param name="draft.watermark.image" select="''"/>
</xsl:stylesheet>
The result of applying this stylesheet to the sample document is shown in Figure 4.
Figure 4. Rendered DocBook document with hedges and trees. |
Conclusion
The techniques described in this article are useful because compact text syntaxes are much easier to type by hand than XML syntaxes. The examples presented in the article show how to create SVG representations of trees from such compact syntaxes.
|
Related Reading Learning XSLT |
Related Links
|
Share your experience in our forums.
(* You must be a member of XML.com to use this feature.)
Comment on this Article
| Titles Only | Titles Only | Newest First |
- Error when executed...
2007-11-29 03:49:37 qwertbhunji [Reply]
New to XSLT.. Looking something to do as per the article, but not able to run..
The character '>' was expected. Error processing resource 'http://www.oasis-open.org/docbook/xml/4.2/dbpoolx.mod'. Line 17...
Any help would be very much appreciated
- Another SVG tree drawer
2004-12-15 17:49:06 Weston Ruter [Reply]
I created something similar to this a year or so ago, although I used a different approach which I think is much simpler and more practical for the user. Instead of requiring depth and width layout information embedded into the XML source document for the transformation into a static SVG image, my solution transforms an XML document into an SVG image with a specific document structure of nested g, line, and text elements that are dynamically positioned into tree with a recursive algorithm upon loading. The algorithm takes advantage of SVG's getComputedTextLength method to correctly position the nodes. Batik is able to rasterize the dynamic SVG image. The source code is released under the GPL. Instructions are available on the website: http://www.linguiste.org/syntax/tree/drawer/
- Example trouble
2004-09-21 08:06:25 Motivus [Reply]
Hi. I'm having some difficulty testing the downloaded example files. When I try to open dbhedge.xml in IE6 under WinXP I get the following error message:
<Error>
Namespace 'http://example.com/my' does not contain any functions.
</Error>
<Note>
I had to add a line to dbhedge.xml so that it would associate the correct XSL file; i.e.,
<?xml-stylesheet type="text/xsl" href="fo-hedge.xsl"?>
</Note>
I'm not perfectly familiar with namespaces, but as far as I can tell the missing function -- my:closingParenPos -- is located in the file hedge2svg.xsl. I know enough about namespaces to not take the error message literally: i.e., it's a URI and not a link to a real resource. So, my question is: how do I get hedge2svg.xsl to recognize the function??
Regards,
Brian
- Example trouble
2004-09-21 09:29:02 Jirka Kosek [Reply]
To process examples you must use XSLT processor that supports EXSLT extensions. Unfortunately XSLT processor bundled with IE doesn't support EXSLT and thus you are getting these odd error messages. You must use Saxon or xsltproc to try out examples.
- Example trouble
2004-09-21 10:14:17 Motivus [Reply]
Thanks Jirka. Does Microsoft have any announced plans to come out with an EXSLT-compliant XSLT processor?
Brian
- Example trouble
2004-09-21 14:52:02 Jirka Kosek [Reply]
AFAIK no. But there are other intiatives that integrate EXSLT into MSXML. For example http://sourceforge.net/projects/fxsl. Unfortunately such solutions make EXSLT available only to standalone applications that are using XSLT, not to XSLT stylesheets run inside IE.
- Example trouble
2004-09-22 11:16:57 Motivus [Reply]
You wrote in the article that you "decided to implement (my:closingParenPos) as an EXSLT function because a regularly named template would be too verbose". I am not perfectly XML-literate, but this would seem to imply that one can still accomplish the objective of your article without using EXSLT. As I am most interested in using XSLT and SVG to dynamically draw tree diagrams, can you tell us what changes to make in order to run the example straight-up in IE6?
- Example trouble
2004-10-20 08:48:40 Jirka Kosek [Reply]
Sorry for a long delay, but I didn't received notification e-mail about new post under my article.
I prepared demo for IE6 (MSXML) + Adobe SVG Viewer. Just download
http://www.kosek.cz/temp/hedge2svg-msxml.zip
and open page.xml in IE. Included XSLT stylesheet uses named templates instead of EXSLT extension function.
- Example trouble
2005-05-31 11:28:05 GrosMinet [Reply]
Hello every one,
The link to hedge2svg-msxml.zip is dead, can someone give me a new link or send me the file by email (bilel@linuxmail.org) please ? I need to draw flow diagram trees from dynamic xml data and I need this exemple
Thank in advance!
- Example trouble
2004-11-02 05:00:24 Teflon [Reply]
>>Error during XSLT transformation: An unknown XPath extension function was called.
Still not working....
Have you been able to get this to work for anyone, Im going to give it a try with server-side transforms but would be nice to do client-side....
- Example trouble
2004-11-02 05:05:57 Teflon [Reply]
PHP didnt transform it either...php5 using xsltprocessor...just as I normally do.
Warning: xmlXPathCompOpEval: function node-set not found in c:\Inetpub\wwwroot\hedge2svg-msxml\convertxsl.php on line 19
Warning: XPath error Unregistered function in exsl:node-set($tree)/node in c:\Inetpub\wwwroot\hedge2svg-msxml\convertxsl.php on line 19
line 19: fwrite($file_handle, $proc->transformToXML($xml));
- Example trouble
2004-11-02 05:08:37 Jirka Kosek [Reply]
Example posted in discussion is customized for use with MSXML 3.0 (part of IE) as someone requested.
PHP5 uses xsltproc, original code posted in article should work with xsltproc.
- Example trouble
- Example trouble
2004-11-02 05:03:53 Jirka Kosek [Reply]
And what XSLT processor you are using? MSXML 3.0 or other?
- Example trouble
2004-11-02 05:07:47 Teflon [Reply]
It should be using MSXML 4.0 SP2 Parser and SDK, as this is the only one installed.
- Example trouble
2004-11-02 05:12:15 Jirka Kosek [Reply]
Well, on my machine it works both in MSXML 3.0 and 4.0, I tested it using msxsl utility from MS.
What happens if you open page.xml in Internet Explorer?
- Example trouble
2004-11-02 05:27:19 Teflon [Reply]
Sorry for being a pain in the butt, I thought I opened it in IE but in fact was opening it in Firefox, it does work well in IE.
Again sorry for the confusion and my idiocy!
- Example trouble
- Example trouble
- Example trouble
- Example trouble
- Example trouble
- Example trouble
- Example trouble
- Example trouble
- Example trouble
- Example trouble

Figure 4. Rendered DocBook document with hedges and trees.