The jsPlumb Toolkit uses individual SVG elements to render edges and individual SVG/HTML elements to render nodes, which is a different approach to other libraries in this space. In this post we're going to discuss the reasons why we do this, give a little historical background, and also share our vision for the near future.
jsPlumb does not use a single SVG context element because we do not consider jsPlumb to be limited to being strictly a diagramming library. jsPlumb's approach to rendering - allowing you to use any HTML or SVG to render your nodes - means that it is capable of building a whole other class of applications that many other libraries in this space cannot, or at least not in a straightforward and maintainable way.
Where it started
The original Community edition of jsPlumb, before it was even known as the Community edition, was released in 2009, at a time when the browser landscape was markedly different from what it is now.
One lunchtime back in 2008, I was discussing the majestic Yahoo Pipes application, beloved of Ajaxians everywhere, with a colleague. At that time Yahoo Pipes was still relatively new, and for web devs it had that intoxicating scent of the future that makes something impossible to not investigate.
Chris and I, being web devs ourselves, were therefore curious. Where did they get those bezier curves? How did they reshape them as the elements moved around? What's going on?
The answer to all 3 questions turned out to be absolute positioned HTML canvas elements. You can draw all sorts of things into a canvas, bezier curves being one of them (check out our post on Image processing with HTML canvas for some fancy tricks), and that's what the YUI people were doing. They were drawing bezier curves into a canvas and moving that canvas around.
The next question was, of course, have they open sourced this? And the answer was no, they had not. The fact that Pipes was not open source is really the fundamental reason jsPlumb exists. We even got inspiration for our name from it: pipes, plumbing, Javascript...jsPlumb.
And so jsPlumb's very first release used an HTML canvas for each edge. We drew bezier curves into them and moved them around. But we wanted to support IE6, because it still had a large market share, and IE6 had no canvas support. What it did have, though, was VML. VML elements behave in a page like canvas elements do: there is no notion of a top level container to group them all, like you can have with SVG (if you're wondering why we didn't go straight to SVG, it was also very new at the time and only Chrome - which had just been released - supported it).
Since our renderer needed to be written in such a way that the knowledge of canvas vs VML vs SVG was the very last consideration, we standardised on the approach of a separate element for each edge, positioned individually.
Where it's at
Element dragging
In this little snippet we've painted an outline on each edge's SVG element. As you drag the nodes around you can see the way the SVG elements are resized and repositioned:
Do you notice how the node you are dragging turns green and is always positioned above the other nodes in the display? That's because of this CSS rule:
.jtk-show-outline-demo.jtk-drag {
background-color: forestgreen;
z-index:11;
}
We render all of our nodes with the .jtk-show-outline-demo
class. When a user starts to drag a node, the Toolkit assigns the .jtk-drag
class, and removes it on drag stop. For us it means that with a simple CSS rule we can implement a useful piece of UX.
This is not easily done when rendering to a single SVG context element. If you render all of your nodes and edges inside a single SVG element, z index is defined only implicitly - by the index of each child element inside the parent. The further down in the SVG's list of child nodes an element is, the greater the implicit z index. The true power of UIs inside the browser comes from a combination of markup, javascript and CSS, and a single SVG context removes some of that power.
Rich UIs
The Toolkit's approach excels in the complexity of the elements you can use to render your nodes. The Toolkit allows you to use any HTML or SVG you wish to - you are not restricted to using SVG - and it will automatically respond to the computed size of your nodes. This greatly expands the range of applications that can be built with jsPlumb when compared with other libraries in this space. jsPlumb is not just a diagramming library. jsPlumb is an application building library.
HTML vs SVG rendering
Every now and then we come across misunderstandings such as this, regarding "HTML" node rendering:
Furthermore, relying on HTML for node rendering has consequences for the types of shapes that can be represented. Whereas rectangles are supported out-of-the-box, anything more complicated (e.g. polygons, ellipses or custom paths) is much more difficult to support than in SVG-based libraries.
which is not the case. Consider this HTML:
<div class="someClass">
<h1>Title</h1>
<input placeholder="enter a value here"/>
<svg width="300px" height="300px">
<rect x="50" y="50" width="100" height="70" fill="orangered" stroke-width="1" stroke="yellow"/>
<path d="M 10 10 L 30 200 L 250 150 L 300 300" stroke-width="2" stroke="green" fill="none"></path>
</svg>
</div>
...it has SVG embedded in it:
There is no such thing as HTML versus SVG for node rendering. Choosing HTML means you also get SVG. There is, though, a difference between choosing a single SVG rendering context vs one element per node/edge - more on this below.
Of course, given that SVG is a subset of HTML from the browser's perspective, if you want to render a node that's a pure shape it is easily done. Here's a hexagon:
<svg viewBox="0 0 173.2 200" width="80" height="80">
<path fill="forestgreen"
d="M86.6 0L173.2 50L173.2 150L86.6 200L0 150L0 50Z"></path>
</svg>
In the Toolkit we can use just this SVG on its own as our node template:
Notice also in this demonstration that we implemented the same z-index trick as we did above - the hexagon we are dragging has a different fill and is raised above the others:
.demo-hexagon.jtk-drag {
z-index:11;
}
.demo-hexagon.jtk-drag path {
fill:orangered;
}
Dynamic sizing
We can also very easily - in a declarative way - add support for dynamic sizing to these hexagons:
<svg viewBox="0 0 173.2 200" width="{{size}}" height="{{size}}">
<path fill="forestgreen"
d="M86.6 0L173.2 50L173.2 150L86.6 200L0 150L0 50Z"></path>
</svg>
Here, size
is a field in our node data:
{
nodes:[
{ id:"1", left:50, top:50, size:80},
{ id:"2", left:450, top:150, size:120},
{ id:"3", left:250, top:200, size:150},
{ id:"4", left:380, top:350, size:40}
]
}
Notice how in these few examples just discussed there was no need for any programmatic manipulation of elements or of their sizing - we provided an HTML template, the browser figured out the layout using the markup in this template and the CSS in the page, and jsPlumb figured out the necessary placement of the edges afterwards.
HTML markup
Not being constrained to just using SVG, we can very easily create detailed UIs and have most of the heavy lifting in terms of layout handled by the browser. In this example we have a single node template:
<div class="jtk-show-outline-demo d-flex flex-column jtk-auto-size-node">
{{id}}
<textarea rows="{{rows}}">{{notes}}</textarea>
</div>
Which we use to render a few nodes that each have a rows
member in their backing data:
{
"nodes":[
{ "id":"1", "left":50, "top":50, "rows":6 },
{ "id":"2", "left":300, "top":250, "rows":9 },
{ "id":"3", "left":500, "top":0, "rows":3 }
]
}
We see the size of the text area is different in each node - and we didn't have to do any special computation to achieve this, it's all just HTML and CSS.
You cannot drag the nodes above by their textarea elements. The Toolkit automatically excludes form controls from being used for dragging an element.
Did you press the resize
button? We have an onclick handler attached to that button which invokes this function:
function resize() {
toolkit.getNodes().forEach(n => toolkit.updateNode(n, {rows:3}))
}
We set rows
to 3 for every node and the Toolkit re-renders everything, repositioning the edges as required. That's all we had to do, because once again we're able to rely on HTML and CSS. In this case, also, the Toolkit is running a ResizeObserver
to catch the resize event from the DOM, which is supported in all major browsers - for HTML elements. For SVG elements ResizeObserver support is patchy across browsers, and it isn't clear whether it will ever be supported for SVG shape elements such as rect
, circle
etc.
You'll have to resize the page if you want to resize again. Once you've resized, there's no going back.
Library integrations
One area where you can really benefit from the Toolkit's approach to rendering is when you use a library such as React, Angular, Vue or Svelte. The Toolkit's integration for each of these libraries lets you render each node as a component, with all of the richness that provides. This is a reworking of the HTML textarea example above that uses the Toolkit's React integration:
We rendered each of the nodes here as a React functional component:
const NodeComponent = ({ctx}) => {
const { vertex, toolkit } = ctx;
function clear() {
toolkit.updateNode(vertex, {notes:""})
}
function update(e) {
toolkit.updateNode(vertex, {notes:e.target.value})
}
return <div>
{vertex.data.id}
<textarea rows={vertex.data.rows} value={vertex.data.notes} onChange={update}></textarea>
<button onClick={clear}>Clear</button>
</div>
}
You cannot drag the nodes in the above snippet by their textarea
element - the Toolkit automatically excludes a range of input form fields from being able to instigate a drag.
Here we see an example of the complexity that is easily handled by the Toolkit - our NodeComponent
encapsulates a specific set of business logic in a React functional component, and the configuration to make the Toolkit use this component could not be simpler:
const viewOptions = () => {
return {
nodes: {
default: {
jsx: (ctx) => {
return <NodeComponent ctx={ctx}/>
}
}
}
}
}
Achieving similar results in Angular, Vue or Svelte is just as easy. The Toolkit is integrated with these libraries, not merely compatible.
Where it's going
There are of course tradeoffs to using a single element per edge and vertex, the most notable being performance. For large datasets there's an overhead having all of those DOM elements around. But not every application regularly deals in very large datasets, and in practise we find that with our current arrangement performance only becomes a concern once the number of vertices is up into the hundreds, which is far more than many applications require.
We said at the start of this article that we do not consider jsPlumb to be limited to being just a diagramming library - it can be used to create applications on a whole other level than just diagrams. But we do acknowledge that using jsPlumb to build a diagram creator does have complications.
We see two separate, but related, areas for us to investigate and to develop jsPlumb.
Performance
We'll be keeping the current rendering approach as we firmly believe that there is a whole class of applications beyond just diagrams for which it is the logical choice, but we are keen to tune it wherever possible. To that end, in the next release - 7.x - we have overhauled our rendering pipeline entirely, stripping away the duplication of code that existed between the original Community edition and the Toolkit edition, and refactoring connection painting to be more performant. We do not yet have a release date for 7.x.
Diagramming support
A few versions ago we introduced the concept of shape libraries, which is a means for you to render SVG shapes of various types onto the canvas. At the time we also shipped a set of "flowchart" shapes, and we'll soon be releasing a set of BPMN symbols.
Another great new feature in our latest release is client side support for export to SVG/PNG/JPG, used in conjunction with a shape library.
We're looking at releasing a single SVG renderer. This would mean that all edges and nodes would have to be SVG, limiting it's capabilities a little, but for diagramming applications a single SVG can suffice.
Start a free trial
Get in touch!
If you'd like to discuss any of the ideas/concepts in this article we'd love to hear from you - drop us a line at hello@jsplumbtoolkit.com.