Shape Sets
A ShapeSet
is a set of named shapes that a Shape Library can use to render SVG into vertex elements or a palette. The Toolkit currently ships with two sets of shapes - FLOWCHART_SHAPES
and BASIC_SHAPES
, and it is a straightforward process to make your own set.
Structure
This is the basic structure - here we show the header and a few shapes from the flowchart shapes that ship with the Toolkit:
export const FLOWCHART_SHAPES = {
id:"flowchart",
name:"Flowchart",
shapes:{
"process": {
type: "process",
label: "Process",
template: '<svg:rect x="0" y="0" width="{{width}}" height="{{height}}"/>'
},
"decision": {
type: 'decision',
label: "Decision",
template: '<svg:path d="M {{width/2}} 0 L {{width}} {{height/2}} L {{width/2}} {{height}} L 0 {{height/2}} Z"/>'
},
"collate": {
type: "collate",
template: `<svg:g><svg:path d="M 0 0 L {{width}} 0 L {{width/2}} {{height/2}} Z"/>
<svg:path d="M 0 {{height}} L {{width}} {{height}} L {{width/2}} {{height/2}} Z"/>
</svg:g>`
}
}
}
Each shape must have at least a type
and a template
value; label
is optional, and description
is also supported.
Template format
The template
must be in the format supported by the Toolkit's default templating engine, ie. XHTML, with every tag properly closed, and using a namespace prefix for SVG elements. Additionally, the template must have a single root element. Notice how in the collate
template we use a group element as the root, and put the other SVG elements inside of that.
Variable interpolation
When a shape library renders a shape, it presents two variables to the template engine:
width
When rendering a vertex this value will have been sourced from the vertex's backing data. When rendering a shape in a palette the palette sets a fixed value for this.height
When rendering a vertex this value will have been sourced from the vertex's backing data. When rendering a shape in a palette the palette sets a fixed value for this.
The template
you provide is inserted as a child element of an SVG element, whose viewBox
is set to 0 0 width height
. With this arrangement, your SVG will scale seamlessly if the size of a node changes.
Defs
From 6.8.0 shape sets support the concept of defs
, which is a wrapper around the defs element in SVG. With defs, you can declare snippets of code in one place and reuse them across your vertices without duplicating the markup. This keeps the DOM size down and is also good when you export to an SVG - you get a smaller file size and better readability.
Configuring defs
defs
are declared in the top level of your shape sets, and then referenced in the standard svg way - with the use
element.
const shapes = {
id: "networkTopology",
name: "NetworkTopology",
defs:{
"jtk-switch":`<svg:svg viewBox="0 0 1024 1024">
<svg:path d="..."/>
</svg:svg>`,
"jtk-cloud":`<svg:svg viewBox="0 0 100 100">
<svg:path d="..."/>
</svg:svg>`,
"jtk-terminal":`<svg:svg viewBox="0 0 32 32">
<svg:path d="..." />
</svg:svg>`
},
shapes: {
"cloud": {
type: "cloud",
template: `<svg:use xlink:href="#jtk-cloud" x="0" y="0" width="60" height="60"/>`
},
"switch": {
type: "switch",
template: `<svg:use xlink:href="#jtk-switch" x="0" y="0" width="60" height="60"/>`
},
"terminal": {
type: "terminal",
template: `
<svg:g>
<svg:use xlink:href="#jtk-terminal" x="0" y="0" width="60" height="60"/>
<svg:text x="30" y="25" text-anchor="middle" dominant-baseline="middle" stroke-width="0.25px">{{label}}</svg:text>
</svg:g>
`
}
}
}
I've truncated the actual SVG elements for readability's sake, but these are the things to note:
- Each
def
has ansvg
element at its root, with aviewBox
. This is not essential, but in this case our icons came from various different places and had different base sizes. TheviewBox
allows us to map them to the size we want. - Each def has a single root element. This is mandatory. Use an
svg:g
element as the root if you want to group a few elements. - The markup for each def is parsed by the Toolkit's default template engine and therefore must include namespaces and be XHTML, ie. no unclosed tags.
- The IDs of the various defs must be unique in the window, so choose carefully. We typically prefix with a namespace (
jtk-
here).
Available sets
The Toolkit currently ships with two shape sets:
Flowchart
import { FLOWCHART_SHAPES, ShapeLibraryImpl } from "@jsplumbtoolkit/browser-ui"
const myLibrary = new ShapeLibraryImpl(FLOWCHART_SHAPES)
Basic
import { BASIC_SHAPES, ShapeLibraryImpl } from "@jsplumbtoolkit/browser-ui"
const myLibrary = new ShapeLibraryImpl(BASIC_SHAPES)
Custom shape sets
It's straightforward to make your own shape set. Here we have made a set of faces:
const shapes = {
id:"faces",
shapes:{
impassive:{
type:"impassive",
template:`<svg:g>
<svg:circle cx="{{width/2}}" cy="{{height/2}}" r="{{(width/2)}}"/>
<svg:path d="M {{width/4}} {{height*3/4}} L {{width*3/4}} {{height*3/4}}"/>
<svg:circle cx="{{width/4}}" cy="{{height/4}}" r="10"/>
<svg:circle cx="{{width*3/4}}" cy="{{height/4}}" r="10"/>
<svg:circle cx="{{width/2}}" cy="{{height/2}}" r="10"/>
</svg:g>`
},
pleased:{
type:"pleased",
template:`<svg:g>
<svg:circle cx="{{width/2}}" cy="{{height/2}}" r="{{(width/2)}}"/>
<svg:circle cx="{{width/4}}" cy="{{height/4}}" r="10"/>
<svg:circle cx="{{width*3/4}}" cy="{{height/4}}" r="10"/>
<svg:circle cx="{{width/2}}" cy="{{height/2}}" r="10"/>
<svg:path d="M {{width/4}} {{height*3/4}} C {{width/4}} {{height*7/8}}, {{width*3/4}} {{height*7/8}} {{width*3/4}} {{height*3/4}}"/>
</svg:g>`
},
notpleased:{
type:"notpleased",
template:`<svg:g>
<svg:circle cx="{{width/2}}" cy="{{height/2}}" r="{{(width/2)}}"/>
<svg:circle cx="{{width/4}}" cy="{{height/4}}" r="10"/>
<svg:circle cx="{{width*3/4}}" cy="{{height/4}}" r="10"/>
<svg:circle cx="{{width/2}}" cy="{{height/2}}" r="10"/>
<svg:path d="M {{width/4}} {{height*3/4}} C {{width/4}} {{height*5/8}}, {{width*3/4}} {{height*5/8}} {{width*3/4}} {{height*3/4}}"/>
</svg:g>`
}
}
}