Skip to main content

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 an svg element at its root, with a viewBox. This is not essential, but in this case our icons came from various different places and had different base sizes. The viewBox 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>`
}
}
}
Custom shapes