Templates

While expression based attribute values obviate the need for script to generate base SVG elements, defining non-SVG namespaced elements to instantiate these objects is still handy. In CSVG one can use templates to define the content that is to be rendered in place of the custom element. The RCC-XSLT syntax proposed in the SVG 1.2 working draft is used.

Why use XSLT?

There are a couple of reasons why XSL templates should be used as the prototyping mechanism for CSVG. Firstly, it is a widely used language for specifying transformations from one XML grammar to another. Rendering custom content is exactly this problem. XSLT, being XML, is easily integrated into an SVG document. Its declarative nature is more easily processed by a user agent than free form script.

The issue of shadow tree regeneration is an important one. With script based RCC, the author must explicitly control when and how the shadow tree is to be regenerated. This will mostly be by attaching DOM mutation event handlers to the custom element. Because the initial shadow tree generation is done using script it is difficult for the user agent to analyse it to determine the minimum changes necessary, automatically. When XSLT and constraints are used, the constraints can be set up to depend on the custom element's attributes, so that changes to the custom element will automatically result in an update in the shadow tree.

Animation could also be specified on custom elements, something which cannot be done with current RCC. Elements in the shadow tree could have attributes which depend on animated attributes of the custom elements, resulting in much more flexibility in animation over what is current possible.

Template examples

Example 5 — Graph

Here is an example for specifying nodes, arcs and labels in an undirected graph.

graph example

This example, graph.svg, will have a list of ex:node elements which specify the coordinates of the node and also the label which will be displayed on the node. Corresponding ex:arc elements will be used to specify lines to be drawn between particular nodes. These arcs also have labels.

<ex:graph>
<ex:node name="n1" cx="70" cy="200" label="Start"/>
<ex:node name="n2" cx="200" cy="330" label="1"/>
<ex:node name="n3" cx="300" cy="100" label="Final" final="true"/>
<ex:arc from="n1" to="n2" label="a*b*"/>
<ex:arc from="n2" to="n3" label="Λ + c"/>
</ex:graph>

The ex:arc elements use a string to refer to the ID of the nodes the arc is to connect.

<xsl:template match="ex:graph">
<xsl:copy-of select="ex:arc"/>
<xsl:copy-of select="ex:node"/>
</xsl:template>

The ex:graph element here is just a container for the ex:node and ex:arc elements. It makes sure though that the arcs are rendered before the nodes, so that the lines do not appear over the top of the circles.

<xsl:template match="ex:node">
<c:variable name="cx" value="c:Length(c:instance()/@cx)"/>
<c:variable name="cy" value="c:Length(c:instance()/@cy)"/>
<c:variable name="final" value="string(c:instance()/@final) = 'true'"/>
<c:variable name="b" value="c:bbox(following-sibling::svg:text)"/>
 
<circle r="0" fill="white" stroke-width="2" stroke="black">
<c:constraint attributeName="cx" value="$cx"/>
<c:constraint attributeName="cy" value="$cy"/>
<c:constraint attributeName="r" value="c:max(c:width($b) div 2 + 10, 30) + 5"/>
<c:constraint attributeName="display" value="c:if($final, 'inline', 'none')"/>
</circle>
<circle r="0" fill="#eee" stroke-width="2" stroke="black">
<c:constraint attributeName="cx" value="$cx"/>
<c:constraint attributeName="cy" value="$cy"/>
<c:constraint attributeName="r" value="c:max(c:width($b) div 2 + 10, 30)"/>
</circle>
<text text-anchor="middle">
<c:constraint attributeName="x" value="$cx"/>
<c:constraint attributeName="y" value="$cy + c:height($b) div 2"/>
<xsl:value-of select="@label"/>
</text>
</xsl:template>

The shadow tree for the ex:node element is reasonably simple. First, it defines a few CSVG variables to hold information given in the ex:node's attributes. Defined also is b which holds the bounding box of the node's label. If the label would extend past the edge of the default 30 radius circle, the size of the circle is increased. This is done using the c:max function in the r attribute of the two circles in the template.

The first circle is an outer ring for those nodes which are marked as "final". It has a constraint on the display attribute which will prevent it from beind rendered if the node is not a final node. Finally there is the text element into which is copied the label specified on the ex:node element.

<xsl:template match="ex:arc">
<c:variable name="from" value="c:instance()/@from"/>
<c:variable name="to" value="c:instance()/@to"/>
<c:variable name="x1" value="c:Length(c:instance()/../ex:node[@name=$from]/@cx)"/>
<c:variable name="y1" value="c:Length(c:instance()/../ex:node[@name=$from]/@cy)"/>
<c:variable name="x2" value="c:Length(c:instance()/../ex:node[@name=$to]/@cx)"/>
<c:variable name="y2" value="c:Length(c:instance()/../ex:node[@name=$to]/@cy)"/>
<line x1="0" y1="0" x2="0" y2="0" stroke="black" stroke-width="1">
<c:constraint attributeName="x1" value="$x1"/>
<c:constraint attributeName="y1" value="$y1"/>
<c:constraint attributeName="x2" value="$x2"/>
<c:constraint attributeName="y2" value="$y2"/>
</line>
<c:variable name="midx" value="($x1 + $x2) div 2"/>
<c:variable name="midy" value="($y1 + $y2) div 2"/>
<c:variable name="b" value="c:bbox(following-sibling::svg:text)"/>
<rect width="0" height="0" fill="white">
<c:constraint attributeName="x" value="c:x($b) - 1"/>
<c:constraint attributeName="y" value="c:y($b) - 1"/>
<c:constraint attributeName="width" value="c:width($b) + 2"/>
<c:constraint attributeName="height" value="c:height($b) + 2"/>
</rect>
<text text-anchor="middle">
<xsl:value-of select="@label"/>
<c:constraint attributeName="x" value="$midx"/>
<c:constraint attributeName="y" value="$midy"/>
</text>
</xsl:template>

The template for the ex:arc element is similar. Note that the x1, y1, x2 and y2 variables (defining the start and end coordinates of the arc) use the c:instance function to access the custom element which instantiated this shadow tree. XPath is then used to select the sibling ex:node element so the strat and end coordinates can be found.

After the CSVG variable definitions the line element which is the arc is copied to the shadow tree. Finally, a text element is used to display the arc label. A white rect is drawn just before this to make sure the label is legible over the line.

Example 6 — Flowchart

The next example is a translation of the RCC flowchart example from the SVG 1.2 working draft.

flowchart example
<ex:flowChart x="10" y="100" width="380" height="100">
<ex:terminalNode>
Start
</ex:terminalNode>
<ex:processNode>
Step 1
</ex:processNode>
<ex:processNode>
Step 2
</ex:processNode>
<ex:terminalNode>
End
</ex:terminalNode>
</ex:flowChart>

flowchart.svg has an ex:flowchart element which contains a series of ex:processNode and ex:terminalNodes.

<xsl:template match="ex:flowChart">
<c:variable name="x" value="c:Length(c:instance()/@x)"/>
<c:variable name="y" value="c:Length(c:instance()/@y)"/>
<c:variable name="width" value="c:Length(c:instance()/@width)"/>
<c:variable name="height" value="c:Length(c:instance()/@height)"/>
<rect width="0" height="0" fill="none" stroke="blue" stroke-width="1">
<c:constraint attributeName="x" value="$x"/>
<c:constraint attributeName="y" value="$y"/>
<c:constraint attributeName="width" value="$width"/>
<c:constraint attributeName="height" value="$height"/>
</rect>
<xsl:copy-of select="ex:*"/>
</xsl:template>

The template first declares a few CSVG variables for the attributes given on the outer ex:flowchart element which specify the dimensions of the object. A rect is then drawn to show the outline of the flowchart. As in the graph example, the child custom elements (the ex:processNode and ex:terminalNodes) are copied into the ex:flowchart's shadow tree, upon which these node elements will have their own shadow trees generated.

<xsl:template match="ex:terminalNode">
<c:variable name="pos" value="count(c:instance()/preceding-sibling::ex:*)"/>
<c:variable name="num" value="count(c:instance()/../ex:*)"/>
<c:variable name="step" value="($width - 80) div ($num - 1)"/>
<c:variable name="cx" value="$x + 40 + $pos * $step"/>
<c:variable name="cy" value="$y + $height div 2"/>
<circle r="30" stroke-width="2" stroke="navy" fill="none">
<c:constraint attributeName="cx" value="$cx"/>
<c:constraint attributeName="cy" value="$cy"/>
</circle>
<text text-anchor="middle" font-size="14">
<c:constraint attributeName="x" value="$cx"/>
<c:constraint attributeName="y" value="$cy + c:height(c:bbox(.)) div 2"/>
<xsl:value-of select="text()"/>
</text>
<line x1="0" y1="0" x2="0" y2="0" stroke-width="2" stroke="black" marker-end="url(#Triangle)">
<c:constraint attributeName="x1" value="$cx + 30"/>
<c:constraint attributeName="y1" value="$cy"/>
<c:constraint attributeName="x2" value="$cx + $step - 40"/>
<c:constraint attributeName="y2" value="$cy"/>
<c:constraint attributeName="display" value="c:if($pos + 1 != $num, 'inline', 'none')"/>
</line>
</xsl:template>

The templates for the ex:processNode and ex:terminalNodes are reasonably similar. Shown here is the code for the ex:processNodes. The differ only in that for process nodes a circle is drawn, for terminal nodes, a circle. Both of these are positioned to be layed out horizontally, one after the other. Their x coordinates depend on the cx CSVG variable, which calculates the horizontal position using the current position number $pos (just the count of how many node elements precede the current one in the document tree), and the step variable, which is the amount of horizontal space to leave between each node. Next, a text is placed in the middle of the node. Its content is selected from the character content of the ex:terminalNode or ex:processNode that is currently being processed. Finally, a line is added to the shadow tree. Its display property is constrained to be displayed only if this node isn't the last in the flowchart.

The script in the RCC example does essentially the same thing as this template. It loops over the node elements to calculate the the total width and the positioning of the nodes and then copies the SVG objects into the shadow tree for them to be rendered. The RCC example cannot handle updates to the custom elements, though. This type of functionality is currently not built in to RCC, so the author must explicitly attach DOM event handlers so that script can be run when the subtree is modified. This could could simply re-run the function which generated the shadow tree in the first place, but this will probably be inefficient if only a small change was made. If the author wants to make only the necessary changes, they must keep a track of the dependencies in their code manually. In the CSVG solution, this dependency tracking and update propagation is handled automatically.

Example 7 — Simple buttons

Say that we want to make a simple button widget. This button should just be a rect with some text centered on it. The button also should be able to run some user specified code when it is clicked.

button example

To generate the buttons in this example, button.svg, we need three things — the template definition, the template instantiation elements and the script to handle the behaviour of the buttons.

<script type="text/ecmascript">
<![CDATA[ function f() { window.alert("Button click handler"); } ]]>
</script>
 
<ex:button x="100" y="100" onbuttonclick="f()">
My button label
</ex:button>
 
<ex:button x="100" y="200" onbuttonclick="f()">
Second button
</ex:button>

Here we have two custom elements to insert buttons into the document. The ex namespace is declared on the root svg element earlier in the document. The ex:button elements have attributes for the coordinates at which to place the button and an event handler to have some script run when the button is clicked. The text inside the element will be used as the button's label.

<xsl:template match="ex:button">
<c:variable name="gap" value="8"/>
<c:variable name="x" value="c:Length(c:instance()/@x)"/>
<c:variable name="y" value="c:Length(c:instance()/@y)"/>
<c:variable name="b" value="c:bbox(following-sibling::svg:text)"/>
<c:variable name="w" value="c:width($b)"/>
<c:variable name="h" value="c:height($b)"/>
<rect width="0" height="0" stroke="black" stroke-width="1" fill="#ccc" onclick="{@onbuttonclick}" onmouseover="testButtonMouseOver(evt)" onmouseout="testButtonMouseOut(evt)" onmousedown="testButtonMouseDown(evt)" onmouseup="testButtonMouseOut(evt)">
<c:constraint attributeName="x" value="$x"/>
<c:constraint attributeName="y" value="$y"/>
<c:constraint attributeName="width" value="$w + $gap * 2"/>
<c:constraint attributeName="height" value="$h + $gap * 2"/>
</rect>
<line stroke-width="1" stroke="white" pointer-events="none">
<c:constraint attributeName="x1" value="$x + 1"/>
<c:constraint attributeName="y1" value="$y + 1"/>
<c:constraint attributeName="x2" value="$x + $w + $gap * 2 - 1"/>
<c:constraint attributeName="y2" value="$y + 1"/>
</line>
<line stroke-width="1" stroke="white" pointer-events="none">
<c:constraint attributeName="x1" value="$x + 1"/>
<c:constraint attributeName="y1" value="$y + 1"/>
<c:constraint attributeName="x2" value="$x + 1"/>
<c:constraint attributeName="y2" value="$y + $h + $gap * 2 - 1"/>
</line>
<text font-size="12" pointer-events="none">
<c:constraint attributeName="x" value="$x + $gap"/>
<c:constraint attributeName="y" value="$y + $h + $gap"/>
<xsl:value-of select="text()"/>
</text>
</xsl:template>

The template element defines what content will be used as the shadow tree for the ex:button element. The template starts with a few CSVG variable elements. The x and y variables use the c:instance function to get the x and y attribute values from the ex:button element which instantiated the template. The w and h variables will store the width and height of the text label of the button.

Next is the rect which makes up the base of the button. Its x, y, width and height attributes are written in terms of the variables defined above it. Event handlers are assigned to control the behaviour of the button. Following the rect is a couple of lines to give the button a three dimensional look.

Finally in the template there is the text element which is used for the button label. The content of the text element is generated using the XSLT value-of element to select the text nodes which are children of the ex:button element.

Notice that the rect's onclick event attribute gets its content from the onbuttonclick of the custom element. In the example here, this is all that is needed for a custom event to be supported. In more complex situations, it may be necessary to synthesize an event in script.

<script type="text/ecmascript">
<![CDATA[ function testButtonMouseOver(evt) { System.out.println("mouse over"); evt.target.setAttributeNS(null, "fill", "#ddd"); } function testButtonMouseOut(evt) { System.out.println("mouse out"); evt.target.setAttributeNS(null, "fill", "#ccc"); } function testButtonMouseDown(evt) { evt.target.setAttributeNS(null, "fill", "#aaa"); } ]]>
</script>

The script here handles the behaviour for the button widget. When the mouse is moved over or clicked on the button, the rect in the shadow tree is modified to make its colour change.

Constraint propagation will occur if the attributes of the custom elements are modified. If an element in the shadow tree uses the c:instance() function to refer to a value on the custom element, this value will be updated in the shadow tree automatically, just like for non-templated elements.

Example 8 — Complex buttons

The button example can be extended to allow arbitrary SVG content as the button's label.

complex button example
<ex:complexButton x="100" y="80" onbuttonclick="f()">
<circle cx="20" cy="20" r="20" fill="yellow" stroke="black" stroke-width="1"/>
<rect x="20" y="20" width="50" height="30" fill="lime" stroke="black" stroke-width="1"/>
</ex:complexButton>

This example, complexButton.svg, uses a circle and a rect as the button's label. Only a small modification to the template used in the previous example is needed to handle this.

<xsl:template match="ex:complexButton">
<c:variable name="gap" value="4"/>
<c:variable name="x" value="c:Length(c:instance()/@x)"/>
<c:variable name="y" value="c:Length(c:instance()/@y)"/>
<c:variable name="b" value="c:bbox(following-sibling::svg:g)"/>
<c:variable name="w" value="c:width($b)"/>
<c:variable name="h" value="c:height($b)"/>
<rect width="0" height="0" stroke="black" stroke-width="1" fill="#ccc" onclick="{@onbuttonclick}" onmouseover="testButtonMouseOver(evt)" onmouseout="testButtonMouseOut(evt)" onmousedown="testButtonMouseDown(evt)" onmouseup="testButtonMouseOut(evt)">
<c:constraint attributeName="x" value="$x"/>
<c:constraint attributeName="y" value="$y"/>
<c:constraint attributeName="width" value="$w + $gap * 2"/>
<c:constraint attributeName="height" value="$h + $gap * 2"/>
</rect>
<line stroke-width="1" stroke="white" pointer-events="none">
<c:constraint attributeName="x1" value="$x + 1"/>
<c:constraint attributeName="y1" value="$y + 1"/>
<c:constraint attributeName="x2" value="$x + $w + $gap * 2 - 1"/>
<c:constraint attributeName="y2" value="$y + 1"/>
</line>
<line stroke-width="1" stroke="white" pointer-events="none">
<c:constraint attributeName="x1" value="$x + 1"/>
<c:constraint attributeName="y1" value="$y + 1"/>
<c:constraint attributeName="x2" value="$x + 1"/>
<c:constraint attributeName="y2" value="$y + $h + $gap * 2 - 1"/>
</line>
<g pointer-events="none">
<c:constraint attributeName="transform" value="concat('translate(', $x + $gap, ',', $y + $gap, ')')"/>
<xsl:copy-of select="*"/>
</g>
</xsl:template>

In place of the text element there is an g element. This is used to position the content over the button. The transform attribute is used to specify a translation in terms of the $x and $y variables. The xsl:copy-of element is used to copy the children of the ex:complexButton template instantiation elements to become children of this g element.

Example 9 — Checkboxes

Another simple widget example is given here in checkbox.svg.

checkbox example

Example 10 — Vertical layout container

Templates can be written to arrange SVG objects so that common layout problems don't have to be handled explicitly. The first layout example here is a vertical layout element, which arranges its child elements in a column.

vertical layout example

This example, vlayout.svg, uses a template to place each of the child elements of the custom element at differing y coordinates.

<ex:vertical-layout id="one" x="20" y="20">
<circle cx="10" cy="10" r="10" fill="red" stroke="black" stroke-width="1"/>
<rect x="0" y="0" width="50" height="20" fill="white" stroke="black" stroke-width="1">
<c:constraint attributeName="height" value="$rectHeight"/>
</rect>
<circle cx="10" cy="10" r="10" fill="yellow" stroke="black" stroke-width="1"/>
<text x="0" font-size="24">
<c:constraint attributeName="y" value="c:height(c:bbox(.))"/>
Some text
</text>
<circle cx="10" cy="10" r="10" fill="lime" stroke="black" stroke-width="1"/>
<g>
<rect width="220" height="50" fill="none" stroke="blue" stroke-width="1"/>
<text x="50" y="30" text-align="middle">
Text with padding
</text>
</g>
<circle cx="10" cy="10" r="10" fill="#c08" stroke="black" stroke-width="1"/>
<path d="M 0 20 L 20 0 L 40 80 L 90 0 z" fill="#fc0" stroke="#c80" stroke-width="3"/>
</ex:vertical-layout>

The t:vertical-layout element just contains the SVG elements which will be layed out. Since each element will be placed in a g element which has been translated to the current position in the column, each of the SVG elements here are placed close to (0, 0). The text element, for example, has its y attribute set so that the top of the text is aligned with y=0.

<xsl:template match="ex:vertical-layout">
<c:variable name="x" value="c:Length(c:instance()/@x)"/>
<c:variable name="y" value="c:Length(c:instance()/@y)"/>
<c:variable name="origy" value="$y"/>
<c:variable name="maxw" value="0"/>
<rect width="0" height="0" fill="#eee" stroke="#ccc" stroke-width="1">
<c:constraint attributeName="x" value="$x - 1"/>
<c:constraint attributeName="y" value="$origy - 1"/>
<c:constraint attributeName="width" value="$finalmaxw + 3"/>
<c:constraint attributeName="height" value="$finaly - $origy"/>
</rect>
<xsl:for-each select="*">
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x + 1, ',', $y + 1, ')')"/>
<xsl:copy-of select="."/>
</g>
<c:variable name="y" value="$y + c:height(c:bbox(preceding-sibling::svg:g[1])) + 3"/>
<c:variable name="maxw" value="c:max($maxw, c:width(c:bbox(preceding-sibling::svg:g[1])))"/>
</xsl:for-each>
<c:variable name="finaly" value="$y"/>
<c:variable name="finalmaxw" value="$maxw"/>
</xsl:template>

The template works by copying each of the child elements into the shadow tree one by one. The xsl:for-each element will iterate over each element. The current y coordinate and the maximum width of the layout in each iteration are kept track of by generating CSVG variable elements that build upon previous variable elements with the same name. After the iteration, two variables are defined for the final y and maximum width values, which are used earlier in the template for the dimensions of the rect that acts as the background colour for the layout widget.

The shadow tree for the t:vertical-layout element is shown below.

<c:variable name="x" value="c:Length(c:instance()/@x)"/>
<c:variable name="y" value="c:Length(c:instance()/@y)"/>
<c:variable name="origy" value="$y"/>
<c:variable name="maxw" value="0"/>
<rect width="0" height="0" fill="#eee" stroke="#ccc" stroke-width="1">
<c:constraint attributeName="x" value="$x - 1"/>
<c:constraint attributeName="y" value="$origy - 1"/>
<c:constraint attributeName="width" value="$finalmaxw + 3"/>
<c:constraint attributeName="height" value="$finaly - $origy"/>
</rect>
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x + 1, ',', $y + 1, ')')"/>
<circle cx="10" cy="10" r="10" fill="red" stroke="black" stroke-width="1"/>
</g>
<c:variable name="y" value="$y + c:height(c:bbox(preceding-sibling::svg:g[1])) + 3"/>
<c:variable name="maxw" value="c:max($maxw, c:width(c:bbox(preceding-sibling::svg:g[1])))"/>
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x + 1, ',', $y + 1, ')')"/>
<rect id="a" x="0" y="0" width="50" height="20" fill="white" stroke="black" stroke-width="1"/>
</g>
<c:variable name="y" value="$y + c:height(c:bbox(preceding-sibling::svg:g[1])) + 3"/>
<c:variable name="maxw" value="c:max($maxw, c:width(c:bbox(preceding-sibling::svg:g[1])))"/>
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x + 1, ',', $y + 1, ')')"/>
<circle cx="10" cy="10" r="10" fill="yellow" stroke="black" stroke-width="1"/>
</g>
<c:variable name="y" value="$y + c:height(c:bbox(preceding-sibling::svg:g[1])) + 3"/>
<c:variable name="maxw" value="c:max($maxw, c:width(c:bbox(preceding-sibling::svg:g[1])))"/>
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x + 1, ',', $y + 1, ')')"/>
<text x="0" y="c:height(c:bbox(.))" font-size="24">
Some text
</text>
</g>
<c:variable name="y" value="$y + c:height(c:bbox(preceding-sibling::svg:g[1])) + 3"/>
<c:variable name="maxw" value="c:max($maxw, c:width(c:bbox(preceding-sibling::svg:g[1])))"/>
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x + 1, ',', $y + 1, ')')"/>
<circle cx="10" cy="10" r="10" fill="lime" stroke="black" stroke-width="1"/>
</g>
<c:variable name="y" value="$y + c:height(c:bbox(preceding-sibling::svg:g[1])) + 3"/>
<c:variable name="maxw" value="c:max($maxw, c:width(c:bbox(preceding-sibling::svg:g[1])))"/>
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x + 1, ',', $y + 1, ')')"/>
<g>
<rect width="220" height="50" fill="none" stroke="blue" stroke-width="1"/>
<text x="50" y="30" text-align="middle">
Text with padding
</text>
</g>
</g>
<c:variable name="y" value="$y + c:height(c:bbox(preceding-sibling::svg:g[1])) + 3"/>
<c:variable name="maxw" value="c:max($maxw, c:width(c:bbox(preceding-sibling::svg:g[1])))"/>
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x + 1, ',', $y + 1, ')')"/>
<circle cx="10" cy="10" r="10" fill="#c08" stroke="black" stroke-width="1"/>
</g>
<c:variable name="y" value="$y + c:height(c:bbox(preceding-sibling::svg:g[1])) + 3"/>
<c:variable name="maxw" value="c:max($maxw, c:width(c:bbox(preceding-sibling::svg:g[1])))"/>
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x + 1, ',', $y + 1, ')')"/>
<path d="M 0 20 L 20 0 L 40 80 L 90 0 z" fill="#fc0" stroke="#c80" stroke-width="3"/>
</g>
<c:variable name="y" value="$y + c:height(c:bbox(preceding-sibling::svg:g[1])) + 3"/>
<c:variable name="maxw" value="c:max($maxw, c:width(c:bbox(preceding-sibling::svg:g[1])))"/>
<c:variable name="finaly" value="$y"/>
<c:variable name="finalmaxw" value="$maxw"/>

In this example clicking on the black rectangle in the upper right corner will increase the height attribute of the white rect. The objects below this rect will be shifted down to accommodate the rect's new height.

vertiucal layout example 2

Example 11 — Grid layout container

To lay out SVG objects in a grid is more complex. Many variables must be set up to ensure the x coordinate of all cells in the one column are equal, and the y coordinates of all cells in the one row also.

grid example

In this templated grid example, gridt.svg, we want to be able to lay out objects in a grid with an arbitrary number of rows and columns. We'll use a specification similar to HTML tables, which uses one element to enclose the whole table, one to enclose a row of elements and one to enclose one cell in a row.

<ex:grid x="20" y="20">
<ex:row>
<ex:cell>
<circle cx="10" cy="10" r="10" fill="red"/>
</ex:cell>
<ex:cell>
<circle cx="10" cy="10" r="10" fill="lime"/>
</ex:cell>
<ex:cell>
<circle cx="10" cy="10" r="10" fill="blue"/>
</ex:cell>
</ex:row>
<ex:row>
<ex:cell>
<circle cx="10" cy="10" r="10" fill="yellow"/>
</ex:cell>
<ex:cell>
<rect id="r" x="0" y="0" width="40" height="70" fill="purple" onclick="resizePurpleRect(evt)"/>
</ex:cell>
<ex:cell>
<circle cx="10" cy="10" r="10" fill="brown"/>
</ex:cell>
</ex:row>
<ex:row>
<ex:cell>
<circle cx="10" cy="10" r="10" fill="black"/>
</ex:cell>
<ex:cell>
<circle cx="10" cy="10" r="10" fill="gray"/>
</ex:cell>
<ex:cell>
<circle cx="10" cy="10" r="10" fill="orange"/>
</ex:cell>
</ex:row>
</ex:grid>

The template to position these objects correctly will need to iterate over each t:cell and copy its contents into the shadow tree, wrapped by an g element so it can be repositioned.

<xsl:template match="ex:grid">
<xsl:for-each select="ex:row">
<xsl:variable name="rp" select="position()"/>
<xsl:for-each select="ex:cell">
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x{position()}, ', ', $y{$rp}, ')')"/>
<xsl:copy-of select="*"/>
</g>
<c:variable name="c{$rp}{position()}w" value="c:width(c:bbox(preceding-sibling::svg:g[1]))"/>
<c:variable name="c{$rp}{position()}h" value="c:height(c:bbox(preceding-sibling::svg:g[1]))"/>
</xsl:for-each>
</xsl:for-each>

The xsl:for-each element is used to iterate first over each t:row in the grid. Each iteration, an XSLT variable is defined to store the current row number. Note that this is not a CSVG variable; this variable exists while processing the template and is not copied into the shadow tree like CSVG variables are. Inside this loop is another xsl:for-each to iterate over each t:cell element in the current row. For each of these cells, an g element is inserted into the shadow tree containing the contents of the t:cell.

See that the the x and y attributes of the svg element use braces to evaluate an expression at template processing time. This is used here to construct the name of the variable being referred to. So, the first cell in the grid will have its g element with transform attribute transform="concat('translate(', $x1, ', ', $y1, ')')" and the last cell will have transform="concat('translate(', $x1, ', ', $y1, ')')". Following that, two CSVG variables are defined which will hold the width and height of the current cell. Again, braces are used to construct the variable anmes.

<xsl:for-each select="ex:row[position()=1]/ex:cell">
<xsl:variable name="cp" select="position()"/>
<xsl:variable name="value">
<xsl:text>
c:max(
</xsl:text>
<xsl:for-each select="../../ex:row">
<xsl:if test="position() != 1">
,
</xsl:if>
<xsl:text>
$c
</xsl:text>
<xsl:value-of select="$cp"/>
<xsl:value-of select="position()"/>
<xsl:text>
w
</xsl:text>
</xsl:for-each>
<xsl:text>
)
</xsl:text>
</xsl:variable>
<c:variable name="min{position()}w" value="{$value}"/>
</xsl:for-each>
 
<xsl:for-each select="ex:row">
<xsl:variable name="rp" select="position()"/>
<xsl:variable name="value">
<xsl:text>
c:max(
</xsl:text>
<xsl:for-each select="ex:cell">
<xsl:if test="position() != 1">
,
</xsl:if>
<xsl:text>
$c
</xsl:text>
<xsl:value-of select="position()"/>
<xsl:value-of select="$rp"/>
<xsl:text>
h
</xsl:text>
</xsl:for-each>
<xsl:text>
)
</xsl:text>
</xsl:variable>
<c:variable name="r{position()}h" value="{$value}"/>
</xsl:for-each>

The next task for the template is to generate variables which will store the maximum width of the cells in each column. These variables will be called min1w, min2w, etc. — one for each column. This begins by iterating over each of the cells in the first row of the grid. For each of these cells, a CSVG variable is created. This variable should be equal to a c:max function call of the width of the cell in each row. The $value XSL variable is used here to construct the complex value attribute for the c:variable. It will generate an attribute with value max($c11w,$c12w,$c13w) for the first column. Similarly for the other two columns.

The same thing is done for each row of the grid. Variables called r1h, r2h, etc. are generated which hold the height of each row.

<xsl:for-each select="ex:row[position()=1]/ex:cell">
<xsl:choose>
<xsl:when test="position()=1">
<c:variable name="x1" value="c:Length(c:instance()/@x)"/>
</xsl:when>
<xsl:otherwise>
<c:variable name="x{position()}" value="$x{position()-1} + $min{position()-1}w"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
 
<xsl:for-each select="ex:row">
<xsl:choose>
<xsl:when test="position()=1">
<c:variable name="y1" value="c:Length(c:instance()/@y)"/>
</xsl:when>
<xsl:otherwise>
<c:variable name="y{position()}" value="$y{position()-1} + $r{position()-1}h"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>

Finally the template creates CSVG variables which store the x coordinates of each column and the y coordinates of each row. xsl:choose is used to create the variable x1 equal to the x attribute given on the t:grid element, and other xn variables which are equal to the previous x coordinate plus the width of the previous column. It is these variables which are used on the g's transform attribute at the start of the template.

The shadow tree that is finally generated in this example is shown below.

<g>
<c:constraint attributeName="transform" value="concat('translate(', $x1, ', ', $y1, ')')"/>
<circle cx="10" cy="10" r="10" fill="red"/>
</g>
<c:variable name="c11w" value="c:width(c:bbox(preceding-sibling::svg:g[1]))"/>
<c:variable name="c11h" value="c:height(c:bbox(preceding-sibling::svg:g[1]))"/>
 
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x2, ', ', $y1, ')')"/>
<circle cx="10" cy="10" r="10" fill="lime"/>
</g>
<c:variable name="c21w" value="c:width(c:bbox(preceding-sibling::svg:g[1]))"/>
<c:variable name="c21h" value="c:height(c:bbox(preceding-sibling::svg:g[1]))"/>
 
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x3, ', ', $y1, ')')"/>
<circle cx="10" cy="10" r="10" fill="blue"/>
</g>
<c:variable name="c31w" value="c:width(c:bbox(preceding-sibling::svg:g[1]))"/>
<c:variable name="c31h" value="c:height(c:bbox(preceding-sibling::svg:g[1]))"/>
 
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x1, ', ', $y2, ')')"/>
<circle cx="10" cy="10" r="10" fill="yellow"/>
</g>
<c:variable name="c12w" value="c:width(c:bbox(preceding-sibling::svg:g[1]))"/>
<c:variable name="c12h" value="c:height(c:bbox(preceding-sibling::svg:g[1]))"/>
 
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x2, ', ', $y2, ')')"/>
<rect x="0" y="0" width="40" height="70" fill="purple" onclick="resizePurpleRect(evt)"/>
</g>
<c:variable name="c22w" value="c:width(c:bbox(preceding-sibling::svg:g[1]))"/>
<c:variable name="c22h" value="c:height(c:bbox(preceding-sibling::svg:g[1]))"/>
 
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x3, ', ', $y2, ')')"/>
<circle cx="10" cy="10" r="10" fill="brown"/>
</g>
<c:variable name="c32w" value="c:width(c:bbox(preceding-sibling::svg:g[1]))"/>
<c:variable name="c32h" value="c:height(c:bbox(preceding-sibling::svg:g[1]))"/>
 
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x1, ', ', $y3, ')')"/>
<circle cx="10" cy="10" r="10" fill="black"/>
</g>
<c:variable name="c13w" value="c:width(c:bbox(preceding-sibling::svg:g[1]))"/>
<c:variable name="c13h" value="c:height(c:bbox(preceding-sibling::svg:g[1]))"/>
 
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x2, ', ', $y3, ')')"/>
<circle cx="10" cy="10" r="10" fill="gray"/>
</g>
<c:variable name="c23w" value="c:width(c:bbox(preceding-sibling::svg:g[1]))"/>
<c:variable name="c23h" value="c:height(c:bbox(preceding-sibling::svg:g[1]))"/>
 
<g>
<c:constraint attributeName="transform" value="concat('translate(', $x3, ', ', $y3, ')')"/>
<circle cx="10" cy="10" r="10" fill="gray"/>
</g>
<c:variable name="c33w" value="c:width(c:bbox(preceding-sibling::svg:g[1]))"/>
<c:variable name="c33h" value="c:height(c:bbox(preceding-sibling::svg:g[1]))"/>
 
<c:variable name="min1w" value="c:max($c11w,$c12w,$c13w)"/>
<c:variable name="min2w" value="c:max($c21w,$c22w,$c23w)"/>
<c:variable name="min3w" value="c:max($c31w,$c32w,$c33w)"/>
 
<c:variable name="r1h" value="c:max($c11h,$c21h,$c31h)"/>
<c:variable name="r2h" value="c:max($c12h,$c22h,$c32h)"/>
<c:variable name="r3h" value="c:max($c13h,$c23h,$c33h)"/>
 
<c:variable name="x1" value="c:Length(c:instance()/@x)"/>
<c:variable name="x2" value="$x1 + $min1w"/>
<c:variable name="x3" value="$x2 + $min2w"/>
 
<c:variable name="y1" value="c:Length(c:instance()/@y)"/>
<c:variable name="y2" value="$y1 + $r1h"/>
<c:variable name="y3" value="$y2 + $r2h"/>

In this example clicking on the purple rect in the centre of the grid will cause its width or height to be increased. The rest of the elements in the grid will move over appropriately as the constraints are updated.


Last updated: 26th February, 2004