Skip to main content

Shape Libraries

JsPlumb allows you to use arbitrary HTML/SVG to render the elements in your UI - inside a view, you map some node/group type to a definition that contains either a template, when using JsPlumb Vanilla, some JSX with JsPlumb React, or a component, with JsPlumb Angular, Vue and Svelte.

In some applications - particularly diagramming applications - the only thing that is different between the representations of the vertices in your app is that they are drawn with different shapes. With this type of app, maintaining a separate mapping inside your view for every available shape is not desirable, and that's where a ShapeLibrary comes in.

A ShapeLibrary contains one or more sets of named shapes that you will use to render SVG inside the vertices in your application. A ShapeLibrary is a wrapper around one or more Shape sets - you can read about the available shapes (and how to create your own set) below. The type of each vertex is used by JsPlumb to look up an appropriate shape to render, and when you have more than one set of shapes, JsPlumb uses each vertex's category value to pick the set to use for a given shape.

Creating a shape library

You'll first need to create a ShapeLibraryImpl and pass in a set of shapes to render:

Vanilla JS
React
Angular
Vue
Svelte
import { FLOWCHART_SHAPES, ShapeLibraryImpl } from "@jsplumbtoolkit/browser-ui"

const shapeLibrary = new ShapeLibraryImpl(FLOWCHART_SHAPES)

You can use this shape library to do a couple of things:

Required data properties

In order for the shape library to be able to pick the appropriate shape for some vertex, the vertex data must contain:

  1. A type property that maps to the ID of one of the shapes in your library
  2. A category property that maps to some shape set ID.
  3. A width and height value for each node. The SVG shapes use path elements internally, which require absolute coordinates, and so they need to know the current size of your vertices:
{
"id": "1",
"name": "Some Node",
"type": "process",
"category": "flowchart",
"width": 200,
"height": 200
}
Flowchart shapes

In this example we have these two nodes:

[
{
"id":"1",
"left":50,
"top":50,
"type":"process",
"category":"flowchart",
"width":100,
"height":100,
"fill":"white",
"outline":"black",
"name": "Node 1"
},
{
"id":"2",
"left":150,
"top":250,
"type":"terminus",
"category":"flowchart",
"width":100,
"height":100,
"fill":"white",
"outline":"black",
"name": "Node 2"
}
]

Notice how each of the nodes has a type, width and height property in its data. Also note the fill and outline values - these are optional, and can be used to control the, er, fill and outline. If you do not provide values for these you'll get the default SVG behaviour, which is, in most browsers, to use a fill of "black". You can use CSS to apply a style to every node at once if individual fill and outline is not something you need to control:

.jtk-node svg {
fill:white;
stroke:black;
}

Rendering a shape

Vanilla JS
React
Angular
Vue
Svelte

In Vanilla JsPlumb, you need to do two things to wire up the shape library to a surface:

  1. Provide a shapes render option that contains a reference to the shape library
  2. Use a <jtk-shape/> tag in your vertex template. This is a custom tag that the surface adds when we provide the shape library in its render options.

import { newInstance, ShapeLibraryImpl } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance()
const shapeLibrary = new ShapeLibraryImpl(FLOWCHART_SHAPES)

const renderParams = {
shapes:{
library:shapeLibrary
},
view:{
nodes:{
default:{
template:`<div>
<jtk-shape/>
</div>`
}
}
},
consumeRightClick:false
}

toolkit.render(someElement, renderParams)

SVG shape tag

An alternative way to register the tag is manually, with a name you choose. This is only recommended if, for some reason, you don't want to use jtk-shape in your templates, or if you have more than one shape library. To do this you replace the shapes entry with tags:

const renderParams = {
tags:{
"jtk-shape":shapeLibrary.getShapeTagDefinition()
},
layout:{
type:AbsoluteLayout.type
},
etc

Displaying labels

You can request that the shape library render a label in the center of each of your nodes/groups:

Flowchart shapes
Vanilla JS
React
Angular
Vue

To add labels in Vanilla JsPlumb, set the showLabels property in your shapes config:

const renderParams = {
shapes:{
library:shapeLibrary,
showLabels:true
}
}

By default, the shape library will look for a data property label in your vertex data. We can override that and tell JsPlumb to get the label from some other property if we wish:

shapes:{
library:shapeLibrary,
showLabels:true,
labelAttribute:"name"
}

SVG Shape palettes

A common requirement in apps that use a library of shapes is the ability to drag new shapes onto the canvas. JsPlumb offers a simple yet powerful method for configuring this behaviour, in Vanilla JS and when using one of our integrations.

Vanilla JS
React
Angular
Vue
Svelte

import { newInstance, ShapeLibraryImpl } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance()
const shapeLibrary = new ShapeLibraryImpl(FLOWCHART_SHAPES)

const renderParams = {
shapes:{
library:shapeLibrary
},
view:{
nodes:{
default:{
template:`<div>
<jtk-shape/>
</div>`
}
}
},
consumeRightClick:false
}

const surface = toolkit.render(surfaceDOMElement, renderParams)

const palette = new ShapeLibraryPalette({
container:paletteDOMElement,
surface,
shapeLibrary
})
note

When you attach a shape library palette to a surface, the palette will automatically register the jtk-shape tag for you: you do not need to include the tags section in your render params as shown in the examples at the top of this page.

The full list of options for the ShapeLibraryPalette class are as follows:

Interface ShapeLibraryPaletteOptions

NameTypeDescription
allowClickToAdd?booleanWhen in tap mode, allow addition of new vertices simply by clicking, instead of requiring a shape be drawn. (When this is true, the drawing method also still works)
autoExitDrawMode?booleanDefaults to true: when in 'tap' mode and a new group/node has been drawn on the canvas, the surface is set back to pan mode.
canvasStrokeWidth?numberStroke width to use for shapes dropped on canvas. Defaults to 2.
containerHTMLElementThe element to draw the palette into.
dataGenerator?DataGeneratorFunctionOptional data generator to allow you to specify initial data for some element to be dragged. Note that you cannot override the object's type with this function. The palette will set the new object's type to match the type of the element that the user is dragging from the palette.
dragSize?SizeOptional size to use for dragged elements.
fill?stringOptional fill color to use for dragged elements. This should be in RGB format, _not_ a color like 'white' or 'cyan' etc.
iconSize?SizeOptional size to use for icons. Defaults to 150x100 pixels. If you provide this but not dragSize this size will also be used for an icon that is being dragged.
initialSet?stringOptional ID of the first set to show, hiding the others.
mode?DropManagerModeMode to operate in - 'drag' or 'tap'. Defaults to 'drag' (DROP_MANAGER_MODE_DRAG).
onVertexAdded?Optional callback to invoke when a new vertex has been added
outline?stringOptional color to use for outline of dragged elements. Should be in RGB format.
paletteStrokeWidth?numberStroke width to use for shapes in palette. Defaults to 1.
selectAfterDrop?booleanWhen true (which is the default), a newly dropped vertex will be set as the underlying Toolkit's selection.
shapeLibrary?ShapeLibraryThe shape library to render.
showAllMessage?stringMessage to use for the 'show all' option in the shape set drop down when there is more than one set of shapes. Defaults to Show all.
showLabels?booleanOptionally show each icon's label underneath it
surfaceSurfaceSurface to attach the drag/drop to.

Example - dragging SVG shapes

Here, elements can be dragged from the palette onto the canvas. Note that when you drag an element from the palette it is automatically scaled to match the zoom of the canvas at that time:


Getting notification of a new vertex

You might want to be notified when the user has dragged a new vertex onto the canvas.

Vanilla JS
React
Angular
Vue
Svelte

As with the SurfaceDropManager that the ShapeLibraryPalette wraps, you can provide an onVertexAdded callback, which will be invoked whenever a new shape was dragged onto the canvas:


const palette = new ShapeLibraryPalette({
container:paletteDOMElement,
surface,
shapeLibrary,
onVertexAdded(vertex, dropTarget) => {
alert(`New vertex ${vertex.id} added`)
}
})

vertex is the new vertex that was added. In the event that the new vertex was dropped on top of some existing node, dropTarget will be provided, containing information about the node onto which the new vertex was dropped, as well as its position and size. dropTarget is of this type:

Try dragging a new shape onto this canvas - we'll pop up an alert giving you its ID:


Displaying Labels

The SVG shape palette will not by default display labels for the shapes it contains, but you can switch that on if you wish:

Vanilla JS
React
Angular
Vue
Svelte

As with the SurfaceDropManager that the ShapeLibraryPalette wraps, you can provide an onVertexAdded callback, which will be invoked whenever a new shape was dragged onto the canvas:


const palette = new ShapeLibraryPalette({
container:paletteDOMElement,
surface,
shapeLibrary,
showLabels:true
})

Tap mode and vertex drawing

The shape library palette component can be used in conjunction with the Vertex drawing plugin. To do that, you'll need to create the shape library palette in "tap" mode:

Vanilla JS
React
Angular
Vue

import { DROP_MANAGER_MODE_TAP, ShapeLibraryPalette } from "@jsplumbtoolkit/browser-ui"

const palette = new ShapeLibraryPalette({
container:paletteDOMElement,
surface,
shapeLibrary,
mode:DROP_MANAGER_MODE_TAP
})

Tap on an element in the palette to select it, then drag in the canvas to draw a new vertex:


Export to SVG, PNG and JPG

You can export a UI that uses a shape library to SVG, PNG or JPG. See this page for more details.


CSS Classes


Shape sets

A ShapeSet is a set of named shapes that a ShapeLibrary can use to render SVG into vertex elements or a palette. JsPlumb 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 JsPlumb:

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.

Mapping to a shape

To map a vertex to a shape, the vertex's backing data needs to have a type value and a category, for instance:

{
"id":"myNode",
"type":"process",
"category":"flowchart"
}

category is mapped to the ID of some shape set - in the above example, flowchart. Then type identifies the specific shape inside that shape set.

Template format

The template must be in the format supported by JsPlumb'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, for instance, 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

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"/>`,
label:"Cloud"
},
"switch": {
type: "switch",
template: `<svg:use xlink:href="#jtk-switch" x="0" y="0" width="60" height="60"/>`,
label:"Switch"
},
"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>`,
label:"Terminal"
}
}
}

We'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 JsPlumb'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

JsPlumb 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>`,
label:"Impassive"
},
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>`,
label:"Pleased"
},
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>`,
label:"Not Pleased"
}
}
}
Custom shapes