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 Links
|
- Error when executed...
2007-11-29 03:49:37 qwertbhunji - Need help
2007-11-26 21:54:56 qwertbhunji - Another SVG tree drawer
2004-12-15 17:49:06 Weston Ruter - Example trouble
2004-09-21 08:06:25 Motivus - Example trouble
2004-09-21 09:29:02 Jirka Kosek - Example trouble
2004-09-21 10:14:17 Motivus - Example trouble
2004-09-21 14:52:02 Jirka Kosek - Example trouble
2004-09-22 11:16:57 Motivus - Example trouble
2004-10-20 08:48:40 Jirka Kosek - Example trouble
2005-05-31 11:28:05 GrosMinet - Example trouble
2004-11-02 05:00:24 Teflon - Example trouble
2004-11-02 05:05:57 Teflon - Example trouble
2004-11-02 05:08:37 Jirka Kosek - Example trouble
2004-11-02 05:03:53 Jirka Kosek - Example trouble
2004-11-02 05:07:47 Teflon - Example trouble
2004-11-02 05:12:15 Jirka Kosek - Example trouble
2004-11-02 05:27:19 Teflon
Figure 4. Rendered DocBook document with hedges and trees.