Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

svgdx svg svgdx input.xml diagram.svg

The Goal

In my computing life, my happy place is the terminal. I enjoy typing - pressing each of those little buttons on the keyboard in front of me, the steady ‘duh-duh-duh-duh’ as I type, the appearance of glyphs on the screen in response. I might use a keyboard and read from a monitor, but it can feel my mind is connected to the machine.

Typing is about words, and words are about language. They convey feelings and ideas, but though I love words, sometimes they are the wrong tool. If I need to convey or absorb information both accurately and quickly, I turn to a different tool - diagrams.

But I don’t want to leave my happy place of pressing those oh-so-satisfying buttons in front of me and seeing things appear in response. Maybe I can keep that and still create diagrams? Maybe rather than drawing diagrams, I can type them?

The Motivation

Direct manipulation

Let’s start with the negatives. Most diagrams are created using the direct manipulation paradigm - a mouse cursor is used to select a rectangle tool, then drag and resize a rectangle at the appropriate place within some canvas. Graphical objects are ‘directly manipulated’ as they appear, as though the diagramming tool provides virtual shapes and lines which can be conjured out of the air, then squashed, pushed, tweaked, duplicated and destroyed until the diagram satisfies its creator. There will be hundreds of tiny actions - mostly unconscious - which contribute to the final diagram, as the diagrammer gets the final output ‘just right’. The history of how the diagram is created is incidental, and only becomes conscious when mistakes are recognised and the magical ‘undo’ spell is invoked.

Is there an alternative to this WYSIWYG tweak-it-until-you-make-it approach? And is there anything wrong with it anyway?

To answer the second point first, no - there’s nothing wrong with it. But it does have strengths and weaknesses, and considering the alternatives will help us see how sometimes other approaches may be better.

“You probably don’t need a static site generator”

  • pandoc + Makefile; raw HTML; …

SVG

  • just write raw SVG.

HTMX

C++ & Cppfront

Cppfront

  • same semantics as C++, new syntax.

Comparison with alternatives

idea thinking most DaClanguages automationmagic SVG output Most DaC languageslive close to the domain idea thinking,pencil & paper,trial-and-error svgdx automation SVG output svgdx providesmore control

Delta 0 - SVG

SVG is the foundation for svgdx, and is not hidden away

Overview

The svgdx format is a superset of SVG. In that sense, any valid SVG is (in theory) already valid svgdx input, and being able to intersperse SVG with enhanced svgdx avoids providing too many limitations to svgdx. In theory, svgdx is at least as powerful as SVG - anything you can do in SVG, you can also do in svgdx. In practice there are various constraints1 which limit what can be done, though even these are more about being able to use the enhancements svgdx provides together with some SVG features - it’s always possible to drop down to the lower level more widely if required.

SVG is XML

If the benefit of having svgdx be a superset of SVG is that it provides enormous flexibility, the biggest downside is that SVG is an XML document format. Personally I quite like XML, or at least find its model of a hierarchy of tagged elements, each of which may have arbitrary attributes - and it’s support for comments - compelling advantages over (for example) JSON. However, it is tedious to type, and the need to use XML entities for common characters such as &, <, > and quotes is frustrating.

For now svgdx is XML, though in future a non-XML syntax retaining equivalent semantics is a definite possibility.

Elements and Attributes

SVG is built on a number of element types, each of which is parameterized through element-specific attributes.

svgdx provides new element types as well as additional attributes and semantics for existing SVG elements.



  1. for example the assumption that user-coordinates are used throughout.

Delta 1 - Shortcuts

svgdx inherits its semantics from SVG, but aims to reduce verbosity and boilerplate

Attribute shortcuts

A rectangle in SVG may be represented as follows:

<rect x="2" y="2" width="20" height="10"/>

Here there are four attributes - x and y specify where the rectangle is placed, while width and height specify the size of the rectangle.

svgdx allows combining these attributes into xy and wh respectively, so the same rectangle may be expressed in the shorter:

<rect xy="2" wh="20 10"/>

Note two things about this example:

  • if the same value is present for both ‘target’ attributes, it may be specified just once; xy="2" means that both x and y attributes are given the value "2".
  • if multiple values are given, they may be separated by ‘comma-whitespace’ - either a comma surrounded by optional whitespace, or whitespace alone.

Based on the above points, "20", "20,20", "20, 20" and "20 20" are all equivalent.

The attributes xy and wh are sufficient for rectangles, but other basic shapes have other ways to specify their layout, and other shortcuts are available.

An SVG <circle> is positioned using the attributes cx, cy and r, which define the x and y positions of the center coordinate, and the circle’s radius respectively. Analogous to the use of xy for the <rect> element, a circle may be positioned using the cxy shortcut attribute.

The set of attribute shortcuts are as follows:

Attribute nameMeaningApplies to
xyTop-left coordinate1rect, circle, ellipse
cxyCenter coordinaterect, circle, ellipse
xy1First coordinate of a lineline
xy2Second coordinate of a lineline

Root SVG Element Shortcuts

One of the frustrating things about hand-coding SVG documents is the set of attributes needed on the root <svg> element. As a minimum, these need to include the SVG version and namespace; a minimal SVG root element looks like:

<svg version="1.1" xmlns="http://www.w3.org/2000/svg">

Unfortunately even providing these two attributes isn’t enough in most cases; there are more values applied at the root element level.

Consider the following image and corresponding SVG code:

<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
  <rect x="0" y="0" width="120" height="50" style="fill:red" />
  <rect x="120" y="0" width="120" height="50" style="fill:green" />
  <rect x="240" y="0" width="120" height="50" style="fill:blue" />
</svg>

Each of the three rectangles has the same size (120 x 100 ‘user units’), but the displayed image (probably!) has the blue rectangle cut off and plenty of whitespace underneath the three coloured rectangles. Most browser user agents display SVG images with a size of 300 x 150 pixels if not otherwise specified.

As a convenience, svgdx translates an <svg> root element as follows:

  • Any existing attributes are preserved.
  • Default attributes for version="1.1" and xmlns="http://www.w3.org/2000/svg" are provided.
  • The bounding box of all elements2 is computed, and width, height and viewBox attributes are set as appropriate to contain the full set of elements (plus a configurable border) regardless of their position in the coordinate space3.

When the above SVG is passed through svgdx, it outputs the following4. Note how the addition of width, height and viewBox attributes cause the entire image - and only the image - to be rendered.

Using the shortcuts introduced so far, this SVG file can be created from the following input to svgdx:

<svg>
  <config auto-style-mode="none"/>
  <rect xy="0" wh="120 50" style="fill:red"/>
  <rect xy="120 0" wh="120 50" style="fill:green"/>
  <rect xy="240 0" wh="120 50" style="fill:blue"/>
</svg>

Summary

We’ve seen how svgdx allows less boilerplate to be written through the use of shortcut attributes which can expand to multiple attributes, and how the tedious job of determining the root SVG element is eliminated entirely in many cases.

One outcome of having shortcuts available is there may be several ways to express the same concept. The most concise is not always the most understandable, and if both a shortcut and a more explicit instruction are present, the more explicit instruction should always take priority.

The theme of being able to express more with less will return as we continue looking at svgdx, and we’ll see even more concise (and - hopefully - clear) ways that the given image can be authored.



  1. While xy normally refers to the top-left coordinate, this can be modified using the xy-loc attribute - see the positioning chapter for more.

  2. Bounding box calculation may not be evaluated perfectly for all SVG elements - e.g. svgdx cannot determine the exact size of rendered text elements, and the extension of certain arcs in <path> elements is not considered.

  3. As with all geometry processing in svgdx, the assumption is that user coordinates are used, i.e. without suffixes such as em, pt, mm etc.

  4. Whitespace has been added for clarity.

Delta 2 - Shape Text

Associating text with shapes is a fundamental diagramming technique

Overview

SVG supports a number of different ‘basic shapes’, as well as support for complex ‘path’ elements comprising line and curve segments. Text elements are also supported, but are entirely independent of other elements. Since associating text with shape elements is such a common part of making diagrams, svgdx has various tools to make this easier.

The text attribute

Consider the following (not very interesting!) image and source:

I am a rectangle! I am a circle! I am a square!
<svg>
  <rect xy="0" wh="40 10" text="I am a rectangle!"/>
  <circle cxy="20 30" r="15" text="I am a circle!"/>
  <rect xy="50 0" wh="40" text="I am a square!"/>
</svg>

The value of any text attribute is extracted and a new <text> element is created placed so it appears within the shape - centered by default.

The first (rectangle) element above is converted by svgdx into the following SVG fragment - the base element is immediately followed by a new text element, with content generated from the text attribute and positioned appropriately.

<rect x="0" y="0" width="40" height="10"/>
<text x="20" y="5" class="d-text">I am a rectangle!</text>

Note the use of the d-text class; CSS is used to anchor text as required. For centered text, the anchor is set to the center of the text itself, rather than the bottom-left default.

Multi-line text

The text attribute works well where there is a single short text value - perhaps a label - attached to another element. If the text is longer, squeezing into a single attribute could be messy. There are several ways of dealing with this:

<svg>
  <rect id="r" xy="0" wh="50 20" text="One way to implement multiple
line text - by splitting the `text`
attribute over several lines."/>

  <rect xy="^|h 5" wh="50 20" text="Or use '\\n'\nto separate\nlines"/>

  <rect xy="#r|v 5" wh="50 20">
    And you can just
    put the text as
    the element content.
  </rect>

  <rect xy="^|h 5" wh="50 20">
<![CDATA[The content can be a CDATA
  block, allowing things such
  as "i < &j" without the need
  for escaping.
]]>
  </rect>
</svg>
One way to implement multipleline text - by splitting the `text`attribute over several lines. Or use '\n'to separatelines And you can just put the text as the element content. The content can be a CDATA block, allowing things such as "i < &j" without the need for escaping.

Text positioning

Text may be positioned within a shape using the text-loc attribute.

<svg>
  <rect id="tl" wh="25 15" text-loc="tl" text="top-left"/>
  <rect xy="^|h 2" wh="^" text-loc="t" text="top"/>
  <rect xy="^|h 2" wh="^" text-loc="tr" text="top-right"/>
  <rect xy="#tl|v 2" wh="^" text-loc="bl" text="bottom-left"/>
  <rect xy="^|h 2" wh="^" text-loc="b" text="bottom"/>
  <rect xy="^|h 2" wh="^" text-loc="br" text="bottom-right"/>
</svg>
top-left top top-right bottom-left bottom bottom-right

Multi-line text is aligned as appropriate based on its position; text positioned at the right will be right-aligned etc.

<svg>
<rect wh="20" text-loc="br" text="several\nlines of\ntext"/>
</svg>
severallines oftext

As shown above in all the examples above, by default text is positioned within the associated shape. It may also be placed outside, and this is the default for text associated with lines (where ‘inside’ doesn’t make a lot of sense), but can also be triggered using the d-text-outside class:

<svg>
  <line x1="0" y1="0" width="20" text-loc="t" text="above"/>
  <line x1="0" y1="10" width="20" text-loc="b" text="below"/>
</svg>
above below
<svg>
  <rect wh="10" text-loc="r"
        text="right" class="d-text-outside"/>
  <box wh="30 10" _="avoid text clipping"/>
</svg>
right

The example above shows a current limitation of svgdx: it does not compute a bounding box for text objects. (Without a rendering context or font-handling, it can’t know exactly how big any text will end up being.)

This can result in text outside any other object being clipped in the generated root SVG viewBox calculation; use of the <box> element to force a larger canvas without generating any further SVG elements is an effective workaround.

Text styling

When a text attribute is provided on a shape element, an additional <text> element is created. Many of the classes and attributes of the source element are copied across, but not the style attribute. This is because with common style presentation attributes such as fill or stroke, the text should generally have a different style to the containing element. Having red-filled text within a red-filled rectangle would be unhelpful.

For this reason a separate text-style attribute is available, which is injected into the newly created text element.

<svg>
  <rect wh="20" style="fill: green"
        text-style="fill: yellow; stroke: red; stroke-width: 1"
        text="very\nstylish..."/>
</svg>
verystylish...

There are other attributes and classes which affect text styling. In general, standard SVG attributes which apply as presentation attributes to <text> elements (and not shapes in general) may be provided on the source element and will be transferred to the new <text> element. Examples of such attributes include font-weight, font-family and letter-spacing.

A variety of use-cases are covered by text-specific auto styles.

Font size

d-text-smallest / -smaller / -small / -medium / -large / -larger / -largest

These styles control the size of text. The default text size is d-text-medium, but providing this style as an option allows the various relative size styles to be used if global font-size is overriden.

<svg>
<rect wh="30 4" text="smallest" class="d-text-smallest"/>
<rect xy="^|v" wh="^" dh="125%" text="smaller" class="d-text-smaller"/>
<rect xy="^|v" wh="^" dh="125%" text="small" class="d-text-small"/>
<rect xy="^|v" wh="^" dh="125%" text="default"/>
<rect xy="^|v" wh="^" dh="125%" text="large" class="d-text-large"/>
<rect xy="^|v" wh="^" dh="125%" text="larger" class="d-text-larger"/>
<rect xy="^|v" wh="^" dh="125%" text="largest" class="d-text-largest"/>
</svg>
smallest smaller small default large larger largest

Font styles

Monospace, italic and bold text faces are available through the classes d-text-monospace, d-text-italic and d-text-bold respectively. These may be combined as required.

<svg>
<rect wh="15" text="normal"/>
<rect xy="^|h" wh="^" text="bold" class="d-text-bold"/>
<rect xy="^|h" wh="^" text="italic" class="d-text-italic"/>
<rect xy="^|h" wh="^" text="bold\nitalic" class="d-text-bold d-text-italic"/>
<rect xy="^|h" wh="^" text="mono"
    class="d-text-monospace"/>
<rect xy="^|h" wh="^" text="mono\nbold"
    class="d-text-monospace d-text-bold"/>
<rect xy="^|h" wh="^" text="mono\nitalic"
    class="d-text-monospace d-text-italic"/>
<rect xy="^|h" wh="^" text="mono\nbold\nitalic"
    class="d-text-monospace d-text-bold d-text-italic"/>
</svg>
normal bold italic bolditalic mono monobold monoitalic monobolditalic

Pre-formatted text

Pre-formatted text is useful for code listings, or other cases where whitespace is significant and should be preserved.

This style is similar to d-text-monospace, but in addition the text element has spaces replaced with non-breaking spaces. This prevents the usual XML whitespace collapse which replaces multiple contiguous spaces with a single space.

The NBSP replacement approach may change in future, as SVG2 has better support for preserving whitespace.

<svg>
<rect wh="40 15" class="d-text-pre"
  text="def square(x):\n    return x * x"/>
</svg>
def square(x):    return x * x

Vertical text

Text may be oriented vertically using the d-text-vertical class.

<svg>
  <rect wh="6 15" text="Hello" class="d-text-vertical"/>
</svg>
Hello

Standalone text elements

While text within elements can be useful, there is still a place for the <text> element on its own.

An example use is to provide multiple text objects associated with one shape.

<svg>
  <box wh="40 30"/>
  <rect id="r" cxy="^" wh="15"/>
  <text xy="#r@t" text="Top"/>
  <text xy="#r@r" text="Right"/>
  <text xy="#r|v" text="Bottom"/>
  <text xy="#r|H" text="Left"/>
</svg>
Top Right Bottom Left

Note that both ‘dirspec’ and ‘locspec’ formats can be provided, and text anchors are computed appropriately.

In the case of separate <text> elements, the default associated position is outside the referenced object; analogous to the d-text-outside class, there is a d-text-inside class to override this default:

<svg>
  <defaults>
    <text class="d-text-inside"/>
  </defaults>
  <rect id="r" wh="25"/>
  <text xy="#r@t" text="Top"/>
  <text xy="#r@r" text="Right"/>
  <text xy="#r|v" text="Bottom"/>
  <text xy="#r|H" text="Left"/>
</svg>
Top Right Bottom Left

Delta 3 - Auto-styles

svgdx provides a range of CSS classes which control style and behaviour

Auto-style classes

Built-in svgdx class names all have a d- prefix. To avoid conflicts with your own styles, avoid defining class or id values beginning d-.

Note that inclusion of the relevant CSS definitions is automatic based on which classes are defined on properties; while this makes changing classes in the generated SVG document less convenient, but avoids including large chunks of mostly-unused style definitions in the output.

Adding these auto-style classes can be used to both affect presentation style and control layout and positioning.

Presentation style

Colour - stroke and fill

Three class definition formats affect colour:

  • d-<colour> sets a ‘default’ colour for shape outlines and text
  • d-fill-<colour> sets the colour for shape fills, and sets a default text colour to an appropriate contrast colour, if not overridden by d-fill-<colour> or d-text-<colour>
  • d-text-<colour> sets the colour for text elements, which overrides any implicit text colour set by d-colour or d-fill-colour.

Note that the approach to colour in auto-styles assumes that text will not have a stroke outline; if text stroke needs to be specified, use custom classes and styles.

d-<colour>

Sets the stroke of this element to the given colour, which must be a colour name as given in the SVG ‘Color’ type or the value none to disable stroke.

By default any text associated with this element will also have its colour changed, though text colour is applied via the fill attribute rather than stroke. Text colour can be overridden by use of d-text-<colour>.

An exception is that d-none (typically used to prevent an outline being rendered) does not apply an equivalent to text, since this would leave the text invisible.

Applies to: Basic Shapes

Examples:

<rect xy="0" wh="10" class="d-red" />

d-fill-<colour>

Sets the fill of this element to the given colour, which must be a colour name as given by the SVG ‘Color’ keywords or the value none to disable fill (note fill: none; is the default style).

If the fill colour matches an internal list of (subjectively) darker colours, any text associated with this element is changed to render in white rather than black, unless overridden by d-<colour> or d-text-colour.

Applies to: Basic shapes

Examples:

<rect xy="0" wh="10" text="Hello!" class="d-fill-deeppink" />

d-text-<colour>

Sets the colour of rendered text to the given colour, which must be a colour name as given by the SVG ‘Color’ keywords. Note this overrides any colour applied by the other colour specifiers above.

Example:

<rect xy="0" wh="10" text="Hello!" class="d-fill-grey d-text-darkblue d-green" />

This will render a grey square with green outline and dark blue text.

Text styles

d-text-smallest / -smaller / -small / -medium / -large / -larger / -largest

These styles control the size of text. The default text size is d-text-medium, but providing this style as an option allows the various relative size styles to be used if global font-size is overriden.

d-text-monospace / d-text-italic / d-text-bold

These styles provide basic styling of text elements, and may be combined as required.

d-text-pre

This style is similar to d-text-monospace, but in addition the text element has spaces replaced with non-breaking spaces. This prevents the usual XML whitespace collapse which replaces multiple contiguous spaces with a single space.

The NBSP replacement approach may change in future, as SVG2 has better support for preserving whitespace.

This is useful for including code listings, ASCII art, and other whitespace-sensitive text in an SVG document.

Line styles - dots, dashes, and arrows

d-dot / d-dash

Renders an element outline (stroke) with a ‘dotted’ or ‘dashed’ line style respectively. Implemented with stroke-dasharray.

d-thin / d-thick

These respectively reduce or increase the stroke width from the default by a factor of 2.

d-arrow

Renders an arrowhead at the ‘end’ of a line or polyline element. When used on a connector element (line or polyline with start and end attributes) the arrowhead appears at the point pointing toward the end point.

d-flow

Animates (using CSS) the stroke-dashoffset property, to provide the appearance of flowing lines. The simple d-flow property adds the equivalent of d-dash by default, but providing the d-dot property will override this.

Different speeds can be provided by using the suffixes slower, slow, fast or faster, and the direction can be reversed by providing the additional class d-flow-rev. For a ‘dotted fast reverse flow’, use class="d-dot d-flow-fast d-flow-rev".

This style provides interesting effects beyond lines - try on circles with radius a multiple of pi.

Shadows and gradients

d-softshadow / d-hardshadow

Renders a “shadow” filter effect behind the element. d-softshadow renders a softer shadow with a blurred boundary; d-hardshadow has more defined boundaries.

Note shadows will extend beyond the bounding-box of an element, and unwanted clipping of the shadow can be observed in some cases as a result.

Patterns

d-grid / d-grid-N

These classes define a fill for the associated object which draw thin grid lines at gaps of 1, or N (1-100) respectively. This can be useful when debugging a diagram.

d-stipple / d-hatch / d-crosshatch

These classes provide various fill patterns.

TODO: gradients

Delta 4 - Positioning

svgdx provides alternatives to the absolute positioning of elements provided by SVG

Overview

Most SVG elements are placed on a coordinate grid using absolute values within a defined coordinate system. An exception to this is the <tspan> element, which naturally “follows on” in terms of position from the previous <tspan> element. Being able to do this (and more) would be useful for other SVG elements, and is provided by svgdx.

Two important notes should be considered when planning positioning in svgdx:

  • User units should be used throughout; absolute units (e.g. those with some measurement suffix, such as px or mm) will prevent svgdx understanding the positions of elements.
  • svgdx diagrams are ‘expected’ to be between approximately 10 and 1000 units in each dimension. While there are no hard limits on size, various aspects make assumptions about appropriate absolute values - such as default text or arrow-head size - which won’t be valid with very small or very large drawings. SVG is by nature scalable, and scaling the largest dimension to fit in this range should generally be feasible.

Simple relative positioning

The simple cases of ‘after the previous element’ and ‘below the previous element’ which <tspan> handles automatically for text are dealt with generically in svgdx through special cases of the xy attribute.

xy attribute valuemeaning
“^|h”to the right of (‘horizontally after’) the previous element
“^|H”to the left of (‘horizontally before’) the previous element
“^|v”below (‘vertically after’) the previous element
“^|V”above (‘vertically before’) the previous element

For each of these, a further numeric value can be given which provides the ‘margin’ before the next element starts.

So we can have:

a b c d
<svg>
 <rect xy="0" wh="20" text="a"/>
 <rect xy="^|h" wh="20" text="b"/>
 <rect xy="^|v" wh="20" text="c"/>
 <rect xy="^|h" wh="20" text="d"/>
</svg>

or:

A B C D
<svg>
 <rect xy="0" wh="20" text="A"/>
 <rect xy="^|h 10" wh="20" text="B"/>
 <rect xy="^|V 5" wh="20" text="C"/>
 <rect xy="^|H 10" wh="20" text="D"/>
</svg>

Layout

The most important concept for positioning is the element bounding box. This is an axis-aligned rectangle which is the minimum size required to cover a shape. For (non-rotated) <rect> elements, the bounding box is identical with the element’s own layout; for other shapes it will there will usually be some area inside the bounding box that is not within the shape itself.

The diagram below shows the bounding box (blue dashed line) of several shapes (in red).

Each bounding box has nine ‘locations’ which can be used as relative positioning points, as shown here:

tl t tr r br b bl l c

A mnemonic to remember these positions is “TRBL”, so stay out of ‘trouble’ by remembering these! A further point to note is that for the corner positions, the Top/Bottom indicator is always before the Left/Right indicator, so it’s always br - not rb - for the bottom-right corner.

Scalarspec

The following diagram shows the set of scalar values which may obtained from any bounding box in svgdx.

This is closely related to ‘uniform positioning’ - the idea that regardless of the native attributes for a shape (e.g. x/y/width/height for a <rect>, x1/y1/x2/y2 for a <line> and so on), shapes in svgdx can be positioned with any meaningful and sufficient combination of these attributes.

For example, a horizontal line in svgdx can be defined with a start point (x1 & y1, or using compound attributes xy1) and a width. Similarly if xy2 is given together with a width, that is the right most point of the horizontal line, which stretches out for width units up to that point.

y1 cy y2 x1 cx x2 w h

For an <ellipse> shape, additional rx and ry values are available, as in the next diagram. <circle> elements have a single r value for radius.

y1 cy y2 x1 cx x2 w h rx ry

There are some basic aliases for these ‘scalarspec’ values:

  • x == x1
  • y == y1
  • w == width
  • h == height

Delta 5 - Connectors

Lines between shapes provide valuable information in many diagrams

Overview

Connections between elements are a key part of diagrams, where they can represent data flow, dependencies, or other associations. In svgdx connectors link together a start and end element, and use auto-styles to provide visual information such as directionality.

Simple Connectors

Given two elements with unique id attributes, a connection between them may be created using the <line> or <polyline> element with start and end attributes of the relevant id references:

input output
<svg>
  <rect id="a" wh="20 10" text="input" />
  <rect id="b" xy="^|h 10" wh="^" text="output" />

  <line start="#a" end="#b"/>
</svg>

In this simple form, a straight line between the two connected shapes is always drawn. If only base element references (i.e. #abc) are given for the start and end points, the location the line is drawn from is calculated automatically based on the shortest connection:

input output
<svg>
  <rect id="a" wh="20 10" text="input" />
  <rect id="b" xy="^ 25" wh="^" text="output" />

  <line start="#a" end="#b"/>
</svg>

This can result in connectors which don’t look great, e.g. the following is not particularly pleasing:

input output
<svg>
  <rect id="a" wh="20 10" text="input" />
  <rect id="b" xy="^ 20" wh="^" text="output" />

  <line start="#a" end="#b"/>
</svg>

To counter this, provide more explicit start and(/or) end references, e.g. using explicit locations or edge-specs.

input output
<svg>
  <rect id="a" wh="20 10" text="input" />
  <rect id="b" xy="^ 20" wh="^" text="output" />

  <line start="#a@r" end="#b@t"/>
</svg>

Providing an “edgespec”, which consists of one of the edges (t for top, r right, b bottom, or l for left) followed by a colon and a percentage or offset - for example #abc@r:25% - can be particularly useful when multiple connections would otherwise target the same point:

a b c d z
<svg>
  <rect id="a" wh="10" text="a" />
  <rect id="b" xy="^|h 5" wh="10" text="b" />
  <rect id="c" xy="^|h 5" wh="10" text="c" />
  <rect id="d" xy="^|v 5" wh="10" text="d" />
  <rect id="z" xy="#b|v 20" wh="10" text="z" />

  <line start="#a" end="#z@t:20%"/>
  <line start="#b" end="#z@t:40%"/>
  <line start="#c" end="#z@t:60%"/>
  <line start="#d" end="#z@t:80%"/>
</svg>

Connector styles

Classes can be applied to connector elements which control the style. Note these are not restricted to connectors, but this is their primary use-case. As with classes in general, these can be combined as appropriate.

start end d-arrow start end d-biarrowd-red start end d-dashd-arrow

Polyline connectors

Connectors can be defined by the <polyline> SVG element in addition to <line>. With polylines, connectors are restricted to horizontal and vertical segments, with corners at appropriate places.

Currently svgdx only supports polyline connectors with one or two corners.

#a@r #b@l a b line #c #d #e #f c d e f polyline polyline

Note that with the polyline case, the start and end specs don’t need the full locspec (i.e. @r, @l), as corner locations are not included when evaluating default join points on elements.

By default, corners happen 50% along the path between the connected elements, but this can be overwritten with the corner-offset attribute.

#a #b a b polylinecorner-offset="25%" 25% 75%

For elbow connectors such as the above, the corner-offset can be given as either a percentage or an absolute value. If the connector is joining things facing the same direction, it requires an absolute value, which is 3 units by default.

#a@t #b@t a b polylinecorner-offset="12" 12

Delta 6 - Variables and Expressions

Variables

Variables are defined in svgdx in several ways, but primarily through the <vars> element.

Variable names begin with an alphabetic or underscore character, optionally followed by alphanumeric or underscore characters. The variable value _ should be avoided, as it has meaning as a ‘comment attribute’, and cannot be defined via the <vars> element. Note that variable names are case sensitive.

Variables are referenced using the ‘$’ symbol, followed by the variable name. If a variable reference is followed by alphanumeric characters which could

Examples

Defining and referencing variables:

<svg>
  <var abc="123"/>
  <rect wh="20 10" text="$abc"/>
  <!-- Note use of ${...} to delimit the var name -->
  <rect xy="^|v 2" wh="20 10" text="${abc}4"/>
</svg>
123 1234

Multiple variables can be defined in a single <vars> element; each variable is assigned the attribute’s value.

<svg>
  <var size="20 10" col="red"/>
  <rect wh="$size" text="Hello!" class="d-text-$col"/>
</svg>
Hello!

Variable values can be updated after being set, and can the new value can include variable references, including the value itself.

<svg>
  <var size="20 10" msg="Hello "/>
  <rect xy="0" wh="$size" text="$msg"/>
  <var msg="${msg}World"/>
  <rect xy="0" wh="$size" text="$msg"/>
</svg>
Hello Hello World

Variables defined in a <var> element are updated simultaneously in parallel, allowing variables to be swapped in a single var element:

<svg>
  <var a="1" b="2"/>
  <rect xy="0" wh="20 10" text="a = $a; b = $b"/>
  <var a="$b" b="$a"/>
  <rect xy="^|v 2" wh="20 10" text="a = $a; b = $b"/>
</svg>
a = 1; b = 2 a = 2; b = 1

Expressions

All the examples above treat the variables as simple string substitution. When included in an expression block, delimited with double-braces ({{...}}), expressions including variables can be evaluated.

<svg>
  <var a="1" b="2"/>
  <!-- text substitution by default -->
  <rect xy="0" wh="20 10" text="$a + $b"/>
  <!-- evaluated as expression inside '{{...}}' -->
  <rect xy="^|v 2" wh="20 10" text="{{$a + $b}}"/>
</svg>
1 + 2 3

The standard operators +, -, * and / are supported, as well as % for modulo. Standard arithmetic operator precedence takes effect, including support for parenthesized expressions.

<svg>
  <var a="3" b="5"/>
  <rect id="a" xy="0" wh="15 10" text="a = $a"/>
  <rect xy="^|v 2" wh="15 10" text="a+b = {{$a + $b}}"/>
  <rect xy="^|v 2" wh="15 10" text="a-b = {{$a - $b}}"/>
  <rect xy="#a|h 2" wh="15 10" text="b = $b"/>
  <rect xy="^|v 2" wh="15 10" text="a*b = {{$a * $b}}"/>
  <rect xy="^|v 2" wh="15 10" text="a/b = {{$a / $b}}"/>
</svg>
a = 3 a+b = 8 a-b = -2 b = 5 a*b = 15 a/b = 0.6

Built-in functions

A range of functions are provided by svgdx, which are called using fn(arg1, arg2, ...) syntax. The result of the function is substituted into the expression as-is. Note variable expansion happens first when evaluating expressions, so the function to be used can be provided by a variable, for example:

<svg>
  <var x="45" fn="sin"/>
  <circle r="10" text="{{$fn($x)}}"/>
</svg>
0.707

There is currently no support for user-defined functions in svgdx; this may be added in future.

Functions include the following:

  • Trigonometric functions sin, cos, tan, asin, acos, atan; note these are all based on degrees, not the radians which most programming languages use.
  • Polar / Rectangular conversion r2p, p2r - convert between a pair of values in rectangular and polar coordinates. Note that ‘0 degrees’ points horizontally to the right, and as the angle increases it turns clockwise, such that p2r(1, 90) is 0, -1.
  • Random number generation random() produces a floating point value in the range 0..1. randint(a, b) produces an integer in the inclusive range [a..b].
  • Exponential and logarithmic pow(x, y) (xy), exp(x) (ex), log(x) (natural log of x)
  • TODO: many more!

Types in expressions

Values in expressions can be numbers, strings, or lists of these types.

Numbers are internally stored as 32 bit IEEE754 floating point values, with integers up to several million stored exactly. Note that while final attribute numeric values are aggressively rounded for human comprehension, within an expression (and in variable values) full precision is maintained.

There is no specific boolean type, with zero being considered ‘false’ and any other value ‘true’.

There is no explicit syntax for lists; rather comma-separated values are considered a list. Therefore a function’s arguments (if more than one) are a list, and a function may return a list, which may be substituted into another function, or used as the value of an attribute which takes a comma separated list.

<svg>
  <var x="45" fn="sin"/>
  <circle r="10" text="{{p2r(10, -30)}}"/>
  <circle r="2" cxy="{{p2r(10, -30)}}"/>
  <box wh="12 4" xy="^|h" text="{{r2p(p2r(10, -30))}}"/>
</svg>
8.66, -5 10, -30

Delta 7 - Loops and Conditions

svgdx allows elements to be included multiple times, or conditionally included.

Loop elements

svgdx provides three forms of the <loop> element: count, while, and until. Each of these acts as a container, and all the elements nested within it are repeated based on the attributes defined on the loop element.

Every <loop> element must have exactly one of the attributes count, while, or until.

Count-based loops

Count loops use the count attribute to define a fixed number of repeat counts. Note the number of repeats is evaluated before any repeats are created, so while an expression (possibly including variables) can be provided to this attribute, it will only be evaluated once rather than each iteration.

For each iteration, all elements within the <loop> block are processed and appended to the output document.

Example:

<svg>
  <var i="0"/>
  <loop count="4">
    <circle cxy="{{$i * 10}} 0" r="5"/>
    <var i="{{$i + 1}}"/>
  </loop>
</svg>

The count variant of <loop> has three optional attributes:

  • var - variable name set on each loop iteration
  • start - the initial value of the loop variable (default 0)
  • step - the delta added to the variable on each iteration (default 1)

These provide shortcuts replacing a combination of var and while-based <loop>s.

Note that start and step are only meaningful if var is defined, and the number of iterations is always exactly the count value. While count must always be a positive integer, start and step (and therefore the loop variable value) may be (possibly negative) floating point values.

NOTE: If expressions are given as var, start or step values, these are evaluated once before the first loop iteration.

The above example may be re-written using these attributes as follows:

<svg>
  <loop count="4" var="i" step="10">
    <circle cxy="$i 0" r="5"/>
  </loop>
</svg>

A fuller example, showing nested loops:

<svg>
  <rect xy="0" wh="120 60"/>
  <loop count="2" var="i" step="30">
    <loop count="4" var="j" step = "30">
      <loop count="3" var="k" start="5" step="-1.5">
        <rect wh="20" xy="{{$j + $k}} {{$i + $k}}" class="d-softshadow"/>
      </loop>
    </loop>
  </loop>
</svg>

While loops

while - this is given an expression as a condition, and iterations repeat while the condition is “true”, which is defined as non-zero (as with the C language).

Example:

<svg>
  <var x="0" y="0"/>
  <loop while="{{le($x, 90)}}">
    <var oldx="$x" oldy="$y" x="{{$x + 1}}" y="{{10 * sin($x * 10)}}"/>
    <line xy1="$oldx $oldy" xy2="$x $y"/>
  </loop>
</svg>

Until loops

until - similar to while, but the expression is evaluated at the end of each loop rather the start as with while, so will always be present at least once in the output.

Example:

<svg>
  <var size="30"/>
  <loop until="{{lt($size, 1)}}">
    <rect cxy="0" wh="$size" class="d-thinner"/>
    <var size="{{$size * 0.9}}"/>
  </loop>
</svg>

Note that for while and until, the expression is evaluated each iteration, whereas it is only evaluated once for the count form.

NOTE: It is easy to generate very large documents using loops, and potentially take a long time to evaluate.

To mitigate this, a separate loop-limit config value (default: 1000) is defined to detect excessive loop counts. If the number of loops exceeds this at any point, document processing is abandoned with an error.

Note that loop-limit does not ‘clamp’ the number of loops, but is a limit which if exceeded rejects the input entirely. It is intended to detect and avoid infinite loops, which are easy to generate accidentally with malformed while and until conditions.

As with other config elements, loop-limit can be set using the <config> element.

Conditions

All conditions in svgdx are equivalent to a single check: is the value of a conditional expression zero (false) or non-zero (true). This is analogous to the C programming language, and related languages. Various functions such as eq (equals), lt (less than) or ge (greater than or equals) return a value of either 0 for false, or 1 (by convention) for true. Logical operation functions (and, or, not etc) operate similarly: each input is considered a condition and checked to see if they are non-zero, the logical operation applied, and either a one (‘true’) or zero (‘false’) returned.

The if element

The loop elements above - specifically while and until loops - show a use of conditions, but a simple <if> container element is also provided by svgdx. This has a single mandatory attribute ‘test’, which is evaluated as a conditional expression. If true, everything contained in the <if> element is processed as normal; if the test condition is false, everything inside the if element is ignored.

<svg>
  <config border="10"/>
  <var n="7"/>
  <text class="d-text">
    <tspan>7 is</tspan>
    <if test="eq($n % 2, 0)"><tspan>even!</tspan></if>
    <if test="eq($n % 2, 1)"><tspan>odd!</tspan></if>
  </text>
</svg>
7 is odd!

Delta 8 - Reuse, defaults, and custom elements

svgdx fragments may be instantiated - with variation - later in a document

Overview

This page discusses how content in your svgdx document may define templates which may later be instantiated.

It starts by discussing SVG’s <use> element, provided as part of standard SVG, before moving on to discuss the <reuse> element provided by svgdx allowing parameterised re-use, syntax sugar allowing custom elements to be defined, and the <defaults> element allowing a level of attribute inheritance.

The <use> element

SVG supports re-use of elements through the <use> element. Its href attribute references another element, which is instanced at the point of the <use> element. Instance position can be provided through x and y attributes; in svgdx the standard universal positioning and compound attributes can be provided to position instances relative to one-another.

Typically the href target of a <use> element is not a visual part of the document, but referenced from an element within the <defs> container - which contains ‘definitions’ which aren’t directly rendered. In addition, the <symbol> element acts in the same way as the <g> element, but is not itself rendered. Typically <symbol> elements will also be inside a <defs> element, though this does not affect the document rendering.

Typical use of <defs>, <symbol> and <use> will include several elements all defined at the origin, and then instanced at particular positions through the <use> element.

The following example shows <use> elements in action.

<svg>
 <defs>
  <symbol id="a">
   <rect wh="10" class="d-fill-grey"/>
   <circle r="3" cxy="^" class="d-fill-red"/>
  </symbol>
 </defs>
<use href="#a"/>
<use href="#a" xy="^|h 3"/>
<use href="#a" xy="^|v 3"/>
<use href="#a" xy="^|H 3"/>
</svg>

A limitation of the <use> element is that each instance is an exact copy of the original; while some styles and transforms can be applied to the element, the overall structure is identical.

Where similarity rather than exact instancing is the order of the day, the svgdx extension element <reuse> is available.

The <reuse> element

The <reuse> element is modelled on <use>, and in most cases any <use> element in an svgdx document could be changed to <reuse> without a change in the document appearance.

  • <use> is a standard SVG element - it is efficient in document size. It is up to the client application (e.g. image viewer, browser etc) to render the <use> element appropriately.
  • <reuse> replicates the referenced element as-is at the instance site1. This is usually less efficient, especially if the referenced element is complex, but allows structural changes to be made.
<svg>
 <defs>
  <symbol id="a">
   <rect wh="10" class="d-fill-grey"/>
   <circle r="3" cxy="^" class="d-fill-red"/>
  </symbol>
 </defs>
<reuse href="#a"/>
<reuse href="#a" xy="^|h 3"/>
<reuse href="#a" xy="^|v 3"/>
<reuse href="#a" xy="^|H 3"/>
</svg>

The <specs> container element

With <reuse> duplicating the entire element into the rendered document, there is no benefit to keeping the source definition in the rendered document: a new svgdx element named <specs> is introduced that acts equivalently to SVG’s <defs> element but doesn’t appear in the rendered document.

One of the key ways structural changes can be made is through the use of context variables. From the perspective of the referenced element, these are normal variables, used with the $name syntax as part of attribute values. The source of these variables is not a <vars> or <loop> element, but additional attributes on the <reuse> element itself.

When using injected context variables, the template may not be valid at the point it appears in the input document (i.e. the variables might not be defined at the point the template appears). In a <defs> container svgdx would still attempt (and fail) to evaluate variables, while elements within a <specs> element are deliberately not evaluated in any way until referenced from a <reuse> element.

<svg>
 <specs>
  <symbol id="a">
   <rect wh="15" class="d-fill-$colour"/>
   <circle r="5" cxy="^" text="$colour"/>
  </symbol>
 </specs>
<reuse href="#a" colour="blue"/>
<reuse href="#a" x="20" colour="red"/>
</svg>
blue red

Custom elements

Custom elements may be defined within an svgdx input document using reuse semantics. This feature is implemented as syntax sugar over specs and reuse.

Rather than using <specs> purely as a container for elements referenced via <reuse>, a <specs> element with an element attribute defines a custom element.

<specs>
  <symbol id="name">
    ...
  </symbol>
</specs>
<reuse href="#name" ...>

can be rewritten as the more semantic

<specs element="name">
  ...
</specs>
<name .../>

Note that not all values of name are valid as custom element names - at least the following are reserved by svgdx:

  • config
  • reuse
  • specs
  • defaults
  • var
  • if
  • loop
  • for

NOTE: Avoid defining custom elements that conflict with standard SVG elements. The list of reserved elements may expand in future.

<svg>
  <specs element="document">
    <!--
      name: document
      variables: width, height, text
    -->
    <var fold="{{min($width, $height) / 3}}"/>
    <path d="M 0 0 H {{$width - $fold}} L $width $fold V $height H 0 Z
             M {{$width - $fold}} 0 V $fold H $width" style="fill: whitesmoke"/>
    <text xy="^@c" text="$text"/>
  </specs>
  <document xy="0" width="15" height="20" text="ABC"/>
  <document xy="^|h 10" width="20" height="10" text="DOC"/>
</svg>
ABC DOC

The <defaults> element

The <defaults> element provided by svgdx is normally used as a container element - surrounding another group of elements which act as ‘blueprints’ for setting the attributes of matching elements within that scope.

Suppose we want all rectangles to have rounded corners, and a default size of 30x10. We can encode these as default attributes for all rectangles:

<svg>
  <defaults>
    <rect rx="2" wh="30 10"/>
  </defaults>
  <rect text="hello!"/>
  <rect xy="^|v 5" rx="5" text="rounder"/>
  <rect xy="^|v 5" height="15" text="height\noverride!"/>
</svg>
hello! rounder heightoverride!

There are several concepts to be aware of when using the defaults element:

  • Defaults are scoped to the current nesting level and below. For each attribute, a lookup for the default value to use starts at the inner-most nesting level and bubbles up to the root element until a match is found or no default is specified.

  • Defaults apply to matching elements: the simplest case is the element name being the same (as in the earlier example using rect), but the <_ .../> element (i.e. element ‘_’) may be used to match all element types, with the match attribute in elements applying further restrictions on either class or element type. The syntax roughly matches very basic CSS selectors: comma separated ‘alternate’ matching, with element.class1.class2 or .class3 type selectors in each of the comma-separated parts. Only element type and class-based matching are implemented.

  • Certain attributes are augmented rather than set if absent. In the example above, the rx and wh attributes are set on the target when not already present, but for the following attributes any defaults are appended to existing values:

    • class
    • style
    • text-style
    • transform

    NOTE: this doesn’t always work as expected, as no concept of ‘related’ classes or styles exists. Setting a default colour through a default class and later attempting to override it with a locally defined colour class will result in both colour classes or styles defined on the target.

  • While <defaults> is typically used as a container, any attributes defined directly on this element are equivalent to those on a contained <_ .../> element, so is also useful asas an empty element. <defaults .../> is equivalent to <defaults><_ .../></defaults>, i.e. it applies to every element subject to any provided match attribute.

The following fuller example shows these in practice.

<svg>
  <defaults rx="1" xy="^|h 2" wh="5"/>
  <rect xy="0"/>
  <g>
    <defaults wh="12" class="d-thick">
      <rect class="d-text-italic"/>
      <circle class="d-dot"/>
      <_ match=".error" class="d-fill-red"/>
      <_ match=".warn" class="d-fill-orange"/>
      <_ match=".ok" class="d-fill-green"/>
    </defaults>
    <rect text="stop" class="error"/>
    <circle text="ready" class="warn"/>
    <rect text="Go!" width="20" rx="3" class="ok"/>
  </g>
  <circle />
</svg>
stop ready Go!


  1. With some caveats; in particular a referenced <symbol> element is converted into an equivalent <g> element.

Elements

SVG Elements

The primary element type in the source document is likely to be SVG element types, though many support additional attributes to provide easier specification or other functionality.

See the SVG spec for further details on these elements.

Custom Elements

config

This element allows a document to provide it’s own configuration settings which would otherwise be provided on the command line. Normally this element should be provided at the start of a document.

Values are given as key="value" attribute pairs; multiple key-value config pairs can be provided in a single <config> element.

The following configuration settings can be applied using this element. These correspond to equivalent command line options.

NameTypeExampleNotes
debugbooldebug="true"
backgroundcolour namebackground="lightgrey"
scalefloatscale="2.5"
borderintegerborder="20"
font-sizefloatfont-size="5"
font-familystringfont-family="Ubuntu Mono"
loop-limitintegerloop-limit="9999"
var-limitintegervar-limit="4096"
depth-limitintegerdepth-limit="10000"
svg-stylestringmax-width: 100%; height: auto;

defaults

The defaults element is a container for providing element defaults. Elements within the defaults block do not directly contribute to the final output, but provide default attributes and classes for matched elements.

Note that default value substitution happens early in the transformation process, and no attribute processing (e.g. variable lookup, compound attribute expansion) is performed prior to populating a matching element.

Matches are controlled from two sources:

  • The element name, or the element name ‘_’ to match on any element type
  • The match attribute, which is the sole attribute excluded from being a ‘default’. This attribute is first split on comma-whitespace, and then used as selectors similar to (basic) CSS selectors, e.g. rect to match <rect> elements, .my-class to match elements with my-class as a class, and circle.small to match <circle> elements which also have the small class. No other selector types are supported at this point.

Any attributes on the matched element have priority over defaults.

If multiple matches occur, later matches override (in the case of attributes) or augment (for classes) earlier matches.

Two flag values can be provided in the match attribute:

  • init causes any previous match information to be ignored, and this to be the starting point
  • final prevents any further matching

Note both these flags only apply once a match has otherwise been made.

Note that defaults are scoped, typically through the use of the <g> element. More local scopes will take priority over outer scopes, but do not replace them (though <_ match="init"/> at the start of a local <defaults> container would do this).

Note that the ‘id’ attribute cannot be defaulted.

Attributes which are effectively ‘lists’ are ‘augmented’, i.e. a local value or later matched element attribute is appended to earlier ones, rather than replacing them. Augmented attributes include “class”, “transform”, “style”, and “text-style”. There is no attempt at de-duplication except in the case of “class”, which is special-cased throughout svgdx.

var

This element allows one or more variables to be set. These values can be referenced later in expressions.

Variables are set using a varname="value" attribute pair, and multiple variables can be set in a single <var> element.

Note the value is considered to be an expressions, so variables can be set based on the value of other (or the same) variable.

Be careful when updating variable values; an element such as <var thing="$thing + 1"/> may appear to do the right thing in a document, but internally if this is in a loop it will expand to a string of “… + 1 + 1 + 1 + 1 + 1 …”, which may work, but probably isn’t the intended effect, and will slow down document processing. (The likely correct approach here is to use <var thing="{{$thing + 1}}"/>.) In order to help detect this string expansion, the config value var-limit (default 1024) limits the maximum length of string values being assigned to variables.

specs

This is a container element; the contents of it are not transferred to the rendered output, but may be referenced by other elements, in particular the reuse element.

The element is analogous to SVG’s <defs> element, in that “The ‘[specs]’ element is a container element for referenced elements … Elements that are descendants of a ‘[specs]’ are not rendered directly” ref. The difference is that <defs> remain in the document (and therefore DOM) at render time; <specs> do not.

Elements within a <specs> section should generally have an id attribute so they can be referenced, otherwise they will have no effect on the rendered document.

Note that <specs> elements may not be nested.

reuse

The <reuse> element is analogous to SVG’s <use> element, in that it takes an href attribute referring to another element. The difference is that where a <use> element will remain as-is in the rendered output, the <reuse> element is replaced by the referenced element.

Typically this is used to refer to elements defined in the <specs> section of the document, using a href attribute analogous to the <use> element. (Note there should not be an xlink: namespace prefix on the href attribute of <reuse> elements).

The style attribute of the <reuse> element is applied to the rendered output, as are any classes defined for the element. Any id attribute of the <reuse> element is also applied to the rendered output element, and the target id becomes a new class entry.

For example:

<specs>
  <rect id="square" x="$x" y="$y" width="$size" height="$size"/>
</specs>
<reuse id="base" href="#square" x="0" y="0" size="10" class="thing"/>

will result in the following rendered output:

<rect id="base" x="0" y="0" width="10" height="10" class="thing square"/>

Any additional attributes on the <reuse> element are available in the target element’s context as local attribute variables.

point

The point element is used to define a position, via the xy - or separate x and y - attributes. It does not appear in the rendered output, and is simply used to define a point which may later be referred to by other refspec attributes.

In general <point> elements will only be useful if they are given an id value.

Note that <point> elements differ from alternatives - such as a zero-width <rect> or zero-radius <circle> - by being ignored when composite bounding boxes are being established, including the top-level SVG viewBox.

box

The point element is used to define a rectangular region, via the xy - or separate x and y - attributes. It does not appear in the rendered output, and is simply used to define a region which may later be referred to by other refspec attributes.

In general <box> elements will only be useful if they are given an id value.

Unlike the analogous <point> elements, <box> elements do contribute to any surrounding bounding box, and one use case is to define a surrounding borderless region which other elements then sit within.

if

The <if> element allows conditional inclusion of blocks of elements. A single attribute - test - provides the condition. If the condition expression evaluates to non-zero, the contained block is processed as usual; if the condition evalates to zero then it is omitted.

Note the test expression is always evaluated in a numeric context - there is no need to surround the conditional expression with {{..}}.

Example:

<if test="eq($n, 7)">
  <text>Seven</text>
</if>

loop

The <loop> element allows blocks of elements to be repeated. The repetition happens at the ‘input’ stage to processing, so side-effects such as variable updates take effect in each repetition.

There are three forms of the loop element depending on given attribute:

  • count - a fixed number of repeat counts. The count variant of <loop> has three optional attributes: var, start, and step.
  • while - this is given an expression as a condition, and iterations repeat while the condition is “true”, which is defined as non-zero (as with the C language).
  • until - similar to while, but the expression is evaluated at the end of each loop rather the start as with while, so will always be present at least once in the output.

Attributes

General attributes

id

This has the same meaning as in normal SVG (and XML); it should be unique within the document, and will be transferred as-is to the output.

class

This has the same meaning as in normal SVG, but a set of built-in auto-styles may have side-effects which affect conversion to SVG.

_, __

These attributes are used to attach a comment to an input element, which will be converted into an XML Comment prior to the generated element(s).

Expressions and variables in the _ attribute will be evaluated, while __ is a ‘raw’ comment, with no special processing.

Example

<rect id="base" wh="10" _="All other elements are positioned relative to this"/>

Position and size

xy

Determines the top-left point of the given shape.

Note that the top-left point is calculated via the bounding box, and may not actually be part of the shape itself, e.g. in the case of a <circle>.

Type: Expression pair; Relative specifier

Applies to: Basic shapes

Example

<rect xy="10 5" width="5" height="5" />

cxy

Determines the center point of the given shape.

Note that the center point is calculated via a bounding box on the shape, unless the SVG shape itself natively supports cx, cy (i.e. <circle>, <ellipse>)

Type: Expression pair; Relative specifier

Applies to: Basic shapes

xy-loc

Overrides the behaviour of xy to indicate another point on the bounding box of the given shape.

Ignored if xy is not given.

Type: Location

For example, xy-loc="c" (using the c or ‘center’ location) makes an xy attribute behave the same as if just cxy was provided.

May be used for relative alignment, e.g. in the following the second rectangle is positioned with it’s left (l) point equal to the right (r) location of the first rectangle.

<rect id="a" xy="0" wh="10" />
<rect xy="#a@r" xy-loc="l" wh="10" />

Applies to: Basic shapes

dx, dy, dxy

TODO

wh

Determines the width and height of the given shape.

Type: Expression pair; Relative specifier

Applies to: <rect>, <circle>, <ellipse>

dw, dh, dwh

TODO

surround, inside

As an alternative to specifying position and size (e.g. xy and wh), the surround or inside attributes can be given a list of element references, causing it to be positioned at either the union (surround) or intersection (inside) of the bounding boxes to those elements. May be used together with the margin attribute to visually group a set of related elements.

Type: List of Element ref items.

Applies to: <rect>, <circle>, <ellipse>

Example:

<rect id="a" xy="0" wh="2" />
<rect id="b" xy="5 0" wh="2" />
<rect id="c" xy="0 5" wh="2" />
<rect surround="#a #b" margin="1" class="d-dash" />

margin

Note: The behaviour of margin is context-dependent and has no meaning in isolation.

Typically it represents added space / size (see also dw / dh / dwh) between or around elements.

When used with inside, the margin is a decrease in the target element size relative to the intersection box; when used with surround, margin is an increase in the target element size.

Separate margins may be defined for each of the ‘TRBL’ (top, right, bottom, left) edges analogous to CSS padding and margin values.

  • If a single value is given it is used for all 4 edges.
  • If 2 values are given they correspond to top/bottom and left/right edges respectively.
  • If 3 values are given, they correspond to top, left/right, bottom edges respectively.
  • If 4 values are given, they correspond to top, right, bottom, left edges (i.e. clockwise from top) respectively.

Each entry in a margin attribute may be either a number (in user coordinates) or a percentage length.

Lines and connectors

xy1

Determines the starting point of a <line> element.

Type: Expression pair; Relative specifier

Applies to: <line> elements.

Example:

<line xy1="0" xy2="10 20" />

xy2

Determines the ending point of a <line> element.

Type: Expression pair; Relative specifier

Applies to: <line> elements.

Example:

<line xy1="0" xy2="10 20" />

start

Determines the ending point of a connector. This may be a simple expression pair, (in which case it acts identically to xy1) but is typically relative to another shape element.

Type: Expression pair; Relative specifier

Applies to: <line>, <polyline> elements.

Example:

<line start="#abc" end="#pqr" />

end

Determines the ending point of a connector. This may be a simple expression pair, (in which case it acts identically to xy2) but is typically relative to another shape element.

Type: Expression pair; Relative specifier

Applies to: <line>, <polyline> elements.

Example:

<line start="#abc" end="#pqr" />

corner-offset

TODO

Text attributes

text

Provides a text string to associate with and display on the given element.

TODO: expand

text-loc

Determines the location of element text. Behaviour depends on the element this applies to: For shapes enclosing an area (i.e. not simple lines) the text is assumed to live ‘inside’ the shape, and the location determines ‘text justification’ in both horizontal and vertical aspects.

Type: Location

Applies to: Basic shapes

text-offset

When text-loc is used to place text at the corner or edge of a shape, it can become unreadable if pushed all the way to the edge. The text-offset attribute - which defaults to ‘1’ if omitted - controls how much the text string is ‘pulled in’ from the edge or corner.

For centered text this has no effect.

Types

Lists

List attributes correspond to SVG 1.1’s <list-of-Ts> datatype:

“A list consists of a separated sequence of values. Unless explicitly described differently, lists within SVG’s XML attributes can be either comma-separated, with optional white space before or after the comma, or white space-separated.”

source

Expression pair

Location

Expressions

Attribute values and text content

Variables

Variable naming

Variable names are alpha-numeric plus underscore. They may not start with a digit.

Variable names are case-sensitive; abc and Abc are two different variables.

Variable references

Variable references are introduced with the $ symbol.

$abc in an attribute value will be replaced with the content of the variable abc, assuming it exists. An alternative format using braces may be used to avoid ambiguous variable references, e.g. if var is defined as 1, ${var}0 will expand to 10 (in non-arithmetic contexts), whereas $var0 would be a reference to the (perhaps non-existent) var0 variable.

While there is only a single global namespaces for variables, lookups are first done on the attributes of any parent element’s attributes. For example if a <g> element defines a radius="3" attribute, this may be referenced in attributes of the element’s children, e.g. <rect rx="$radius" .../>. Note there is no way to refer to attributes of the current element, as that would allow circular references - an element such as <g width="4"><rect width="$width" ...></g> would not work as expected, and starting attribute lookup at the parent element avoids the need to be ‘creative’ with variable names.

These “attribute locals” shadow global variables, but do not modify them.

Note that in the context of the reuse element, the attributes of the <reuse> element itself provide the local attribute values, rather than the target element.

Note that as an exception, the var element does not provide access to its attributes as locals, as that would cause infinite recursion when redefining a variable in terms of itself.

Variable definition

Variables are defined in a custom <var> element, where each attribute names and sets a variable. For example <var key="1"/> sets the variable key to be the value 1.

Variables are untyped; there is a single global namespace, and modifying variable values is performed by overriding the value.

Example - increment var1

<var var1="{{${var1} + 1}}" />

Arithmetic

Arithmetic expressions are specified in double-brace pairs, for example {{ 1 + $var }}.

Expressions may include the following. Note these are listed in order of precedence.

  • Numbers, including floating point and negative numbers. Internally numbers are stored with at least IEEE 754 single-precision floats, but exact precision and range are not part of this spec.
  • Variable references of the form $var or ${var}
  • Element references, of the form #id~v where id indicates the target element and v is the value of that element to retrieve.
  • function calls, of the form function(args)
  • (, ) - parenthesis, for increasing precedence.
  • *, /, % - multiply, divide, remainder. Precedence is left-to-right among these.
  • +, - - addition and subtraction. Precedence is left-to-right among these.
  • , - expression separator.

Multiple expressions

Note that multiple expressions may be provided within a single {{...}} pair, and must be comma-separated. This allows attributes such as wh="{{$t + 3, $t + 2}}" rather than the (slightly) more verbose wh="{{$t + 3}} {{$t + 2}}".

Note that input expressions must be comma separated, and the resulting list of expression results are separated with ", ". Most SVG attributes which take multiple values use the comma-wsp format, where commas are optional and whitespace may be used to separate values, but allowing whitespace-only separation for multiple expressions makes errors more likely.

Built-in functions

A selection of built-in functions are provided, as follows:

functiondescription
abs(x)absolute value of x
ceil(x)ceiling of x
floor(x)floor of x
fract(x)fractional part of x
sign(x)-1 for x < 0, 0 for x == 0, 1 for x > 0
sqrt(x)square root of x
log(x)(natural) log of x
exp(x)raise e to the power of x
pow(x, y)raise x to the power of y
sin(x)sine of x (x in degrees)
cos(x)cosine of x (x in degrees)
tan(x)tangent of x (x in degrees)
asin(x)arcsine of x degrees
acos(x)arccosine of x in degrees
atan(x)arctangent of x in degrees
random()generate uniform random number in range 0..1
randint(min, max)generate uniform random integer in range [min, max] inclusive
min(a, b)minimum of two values
max(a, b)maximum of two values
clamp(x, min, max)return x, clamped between min and max
mix(start, end, amount)linear interpolation between start and end
eq(a, b)1 if a == b, 0 otherwise
ne(a, b)1 if a != b, 0 otherwise
lt(a, b)1 if a < b, 0 otherwise
le(a, b)1 if a <= b, 0 otherwise
gt(a, b)1 if a > b, 0 otherwise
ge(a, b)1 if a >= b, 0 otherwise
if(cond, a, b)a if cond is non-zero, else b
not(a)1 if a is zero, 0 otherwise
and(a, b)1 if both a and b are non-zero, 0 otherwise
or(a, b)1 if either a or b are non-zero, 0 otherwise
xor(a, b)1 if either a or b are non-zero but not both, 0 otherwise

Note these functions (e.g. the order of arguments in mix and clamp) are influenced by GLSL.

Unlike most programming languages, degrees are the unit used for trigonometric functions.

Element references

The following scalar values may be referred to from an element reference:

  • x, x1 - the x coordinate of the left-hand-side of the given element
  • y, y1 - the y coordinate of the top of the given element
  • x2 - the x coordinate of the right-hand-side of the given element
  • y2 - the y coordinate of the bottom of the given element
  • w, width - the width of the given element
  • h, height - the height of the given element
  • cx - the x coordinate of the centre of the given element
  • cy - the y coordinate of the centre of the given element
  • r - the radius of the given element (assuming a circle!)
  • rx - the x-radius of the given element
  • ry - the y-radius of the given element

These are accessed by providing an element reference (e.g. #abc) followed by a tilde (~), followed by the appropriate entry from the list above.

Note these are different to the relative locations which may be derived from an element.

NOTE: this currently has two overlapping use-cases:

  • get scalar geometric values from an element
  • get the (numeric) value from an attribute of an element

In many cases these are equivalent, but in some cases they can have different meanings. For example: rx on a <rect> vs an <ellipse>, or x2 to mean the right-hand side of an element - when > a <line> may have an x2 attribute less than its x1 value.

The cleanest way to resolve this is likely splitting up the ScalarSpec type.

Layout

Most SVG elements are placed on the canvas using absolute coordinates; easy for SVG-generating tools and GUI applications to handle, but difficult to manage by hand for anything but the most simple diagrams.

svgdx provides various mechanisms to help with laying out diagrams.

NOTE: svgdx assumes that ‘User Coordinates’ are used for all positioning, i.e. without units.

NOTE: Changes to the coordinate system (e.g. using the transform attribute) are currently ignored when svgdx calculates layout.

NOTE: Bounding boxes calculations for <path> elements are incomplete, (in particular arcs and curves are not handled) so these may not position effectively.

Uniform Attributes

SVG requires different approaches to specifying position and size depending on the shape being used; svgdx makes this uniform by determining axis-aligned bounding boxes around every element, and placing objects appropriately.

Every object has a width and height (the wh attribute), and is located at a particular point denoting the top-left of the bounding box (the xy attribute). Alternatively, the center of an object can be given (via the cxy attribute) along with the width and height.

As usual, mixing and matching with standard SVG attributes is possible, so an rx, ry pair may be given alongside an xy attribute to define the position and size of an <ellipse> element, for example.

Relative Positioning

Rather than requiring absolute positions for elements, svgdx allows elements to be placed relative to other elements. Since xy defaults to “0” if not specified, many diagrams will not need any absolute positions to be specified.

The following concepts are defined, and can be combined to make a ‘relative specifier’, or ‘relspec’.

Element Reference - (‘elref’) may be either ‘the previous element’ (denoted with ^) or an element referenced by its id, as #<id>, for example #abc.

Location Spec - (‘locspec’) a specific point on a given element, for example ‘top-left’, ‘center’, or ‘75% along the top edge’. These are given by one of the following abbreviations:

  • tl - top-left
  • t - top, i.e. center of the top edge of the bounding box
  • tr - top-right
  • r - right, i.e. center of the right-hand edge of the bounding box
  • br - bottom-right
  • b - bottom, i.e. center of the bottom edge of the bounding box
  • bl - bottom-left
  • l - left, i.e. center of the left-hand edge of the bounding box
  • c - center of the bounding box

Together an ‘elref’ and a ‘locspec’ denote a point in 2D user coordinates.

Edge-based LocSpec - as a special case of ‘locspec’, those locations which define the edge of an element (i.e. t,r,b,l) may be followed by an offset to vary the resulting point position along the edge. The offset is separated from the locspec by a colon (:), and may be either a number or a percentage.

Each edge starts at the ‘left’ (t / b edges) or ‘top’ (l / r edges) and ends at the right/bottom of the edge respectively.

If a percentage is given (e.g. :30%), this represents that percentage along the edge from the start. This implies that #abc@t:0% is equivalent to #abc@tl, and #abc@t:100% is equivalent to #abc@tr. Note that the value given is not restricted to 0%..100%, but can exceed this range.

If the value is a number rather than a percentage, it is treated differently. A positive value is an offset from the start of the edge, while a negative offset moves backwards from the end of the edge. This is analogous to slice indexing in the Python language, where a[-1] represents the last item in the sequence a.

Edge offsets can be useful where many connector lines are joining an element and it would be clearer to keep them separate; rather than having four connectors all join an element at @b for example, consider joining them at @b:20%, @b:40%, @b:60% and @b:80%.

Direction Spec - (‘dirspec’) denotes a directional relation between two objects. The following dirspec values are supported:

  • h - place horizontally to the right of the associated elref
  • H - place horizontally to the left of the associated elref
  • v - place vertically below the associated elref
  • V - place horizontally above the associated elref

Relspec

The above pieces fit together according to the following grammar.

dirspec    := : [h|H|v|V]
locspec    := @ [tl|t|tr|r|br|b|bl|l|c] | ([t|r|b|l] : length)
length     := number | (number %)

elref      := prevspec | ref
prevspec   := ^
ref        := # ident
ident      := alphanumeric

relspec  := elref [dirspec | locspec]

Following the relspec as defined above, additional values may be given to define margins or deltas.

When used as part of a dirspec (e.g. #abc|H), a single value defines the ‘gap’ between the referenced element and the one being positioned.

When used as part of a locspec (e.g. @tl), a pair of values may be provided which define the dx and dy offsets to apply.

Some simple examples:

  • xy="#abc|h 5" - position this element 5 units to the right of the element with id="abc".
  • cxy="^" - position this element to have its center on the center of the previous element.
  • xy="^|V" - position this element directly above the previous element.
  • xy="^@br" - position this element at the bottom-right of the previous element.
  • xy="#thing@tr 5 10" - position this element at the top-right of element with id="thing", offset by (5, 10).

Styles

The following provides a description and examples of the various auto-styles svgdx provides.

Note: all svgdx auto-styles being d-, and classes starting with this prefix are reserved by svgdx, in that they may be defined with arbitrary behaviour in future.

Most auto-styles simply cause CSS rule(s) to be included matching that class, but some have functional changes to the transformation process.

Presentation style

Colour - stroke and fill

Three class definition formats affect colour:

  • d-<colour> sets a ‘default’ colour for shape outlines and text
  • d-fill-<colour> sets the colour for shape fills, and sets a default text colour to an appropriate contrast colour, if not overridden by d-fill-<colour> or d-text-<colour>
  • d-text-<colour> sets the colour for text elements, which overrides any implicit text colour set by d-colour or d-fill-colour.

Note that the approach to colour in auto-styles assumes that text will not have a stroke outline; if text stroke needs to be specified, use custom classes and styles.

d-<colour>

Sets the stroke of this element to the given colour, which must be a colour name as given in the SVG ‘Color’ type or the value none to disable stroke.

By default any text associated with this element will also have its colour changed, though text colour is applied via the fill attribute rather than stroke. Text colour can be overridden by use of d-text-<colour>.

An exception is that d-none (typically used to prevent an outline being rendered) does not apply an equivalent to text, since this would leave the text invisible.

Applies to: Basic Shapes

Examples:

<svg>
  <rect xy="0" wh="20" class="d-red" />
</svg>

d-fill-<colour>

Sets the fill of this element to the given colour, which must be a colour name as given by the SVG ‘Color’ keywords or the value none to disable fill (note fill: none; is the default style).

If the fill colour matches an internal list of (subjectively) darker colours, any text associated with this element is changed to render in white rather than black, unless overridden by d-<colour> or d-text-colour.

Applies to: Basic shapes

Examples:

<svg>
  <rect xy="0" wh="20" text="Hello!"
        class="d-fill-deeppink" />
</svg>
Hello!

d-text-<colour>

Sets the colour of rendered text to the given colour, which must be a colour name as given by the SVG ‘Color’ keywords. Note this overrides any colour applied by the other colour specifiers above.

Example:

<svg>
  <rect xy="0" wh="20" text="Hello!"
        class="d-fill-lightgrey d-text-darkblue d-green" />
</svg>
Hello!

This will render a grey square with green outline and dark blue text.

Text styles

d-text-smallest / -smaller / -small / -medium / -large / -larger / -largest

These styles control the size of text. The default text size is d-text-medium, but providing this style as an option allows the various relative size styles to be used if global font-size is overriden.

d-text-monospace / d-text-italic / d-text-bold

These styles provide basic styling of text elements, and may be combined as required.

d-text-pre

This style is similar to d-text-monospace, but in addition the text element has spaces replaced with non-breaking spaces. This prevents the usual XML whitespace collapse which replaces multiple contiguous spaces with a single space.

The NBSP replacement approach may change in future, as SVG2 has better support for preserving whitespace.

This is useful for including code listings, ASCII art, and other whitespace-sensitive text in an SVG document.

Line styles - dots, dashes, and arrows

d-dot / d-dash

Renders an element outline (stroke) with a ‘dotted’ or ‘dashed’ line style respectively. Implemented with stroke-dasharray.

<svg>
  <rect wh="20" class="d-dot"/>
</svg>
<svg>
  <rect wh="20" class="d-dash"/>
</svg>

Line thickness

Styles for five stroke thicknesses (including the default) are supported, with d-thin and d-thinner being half or one quarter thickness, and d-thick and d-thicker being twice and four times as thick as the default respectively.

The width of the default stroke is determined by the selected theme config setting.

<svg>
  <line xy1="0" xy2="5 15" class="d-thinner"/>
  <line xy1="^" xy2="^" dx="2" class="d-thin"/>
  <line xy1="^" xy2="^" dx="2" />
  <line xy1="^" xy2="^" dx="3" class="d-thick"/>
  <line xy1="^" xy2="^" dx="4" class="d-thicker"/>
</svg>

d-arrow

Renders an arrowhead at the ‘end’ of a line or polyline element. When used on a connector element (line or polyline with start and end attributes) the arrowhead appears at the point pointing toward the end point.

d-flow

Animates (using CSS) the stroke-dashoffset property, to provide the appearance of flowing lines. The simple d-flow property adds the equivalent of d-dash by default, but providing the d-dot property will override this.

Different speeds can be provided by using the suffixes slower, slow, fast or faster, and the direction can be reversed by providing the additional class d-flow-rev. For a ‘dotted fast reverse flow’, use class="d-dot d-flow-fast d-flow-rev".

This style provides interesting effects beyond lines - try on circles with radius a multiple of pi.

Shadows and gradients

d-softshadow / d-hardshadow

Renders a “shadow” filter effect behind the element. d-softshadow renders a softer shadow with a blurred boundary; d-hardshadow has more defined boundaries.

<svg>
  <rect wh="20" class="d-fill-darkred d-softshadow"/>
  <rect xy="^|h 10" wh="20" class="d-fill-darkred d-hardshadow"/>
</svg>

Note shadows will extend beyond the bounding-box of an element, and unwanted clipping of the shadow can be observed in some cases as a result.

Patterns

d-grid / d-grid-N

These classes define a fill for the associated object which draw thin grid lines at gaps of 1, or N (1-100) respectively. This can be useful when debugging a diagram.

<svg>
  <rect wh="20" class="d-grid"/>
  <rect xy="^|h 10" wh="20" class="d-grid-2"/>
</svg>

d-stipple / d-hatch / d-crosshatch

These classes provide various fill patterns. As with the grid patterns above, the repeat frequency can be specified with a trailing integer.

<svg>
  <defaults><rect class="d-text-outside" text-loc="b"/></defaults>
  <rect wh="20" class="d-stipple" text="d-stipple"/>
  <rect xy="^|h 10" wh="20" class="d-hatch-2" text="d-hatch-2"/>
  <rect xy="^|h 10" wh="20" class="d-crosshatch-4" text="d-crosshatch-4"/>
</svg>
d-stipple d-hatch-2 d-crosshatch-4