Skip to main content

27 posts tagged with "snaplines"

View All Tags

· 3 min read
Simon Porritt

NEW FUNCTIONALITY

  • Shape libraries now support defs - snippets of SVG that you can declare in the header of a shape set and reference in the individual templates. This reduces bloat and increases readability. See below for more detail.

UPDATES

  • Improvements to the hierarchy layout's crossing stage to reduce the chance of overlapping edges
  • Improvements to SVG/PNG/JPG export UI
  • Improvements to SVG export output: edges are drawn first in the SVG so that nodes will be placed on top in non-browser settings.

Shape Library Defs

In most UIs there is some amount of markup that is common amongst a number of vertices. For instance, in the network topology diagram below, there are three types of nodes - terminal, cloud and switch, each of which has its own icon representing it:

In version 6.8.0 we have added support for a defs element to shape sets - it is a wrapper for the defs element in SVG. The benefits of using defs are that you can reduce the number of elements in your DOM, and when exporting the SVG the resulting file will be smaller and easier to comprehend.

To configure this, we created a shape set like this:

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).

We then render a surface using this shape library:

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

const shapes = {...}
const sl = new ShapeLibraryImpl([shapes])
const tk = newInstance()

tk.render(container, {
...
shapes:{
library:sl
},
view:{
nodes:{
[DEFAULT]:{
template:`
<div data-jtk-target="true" style="width:60px;height:60px;">
<jtk-shape width="60" height="60"/>
</div>`
}
}
}
})

Further reading

You can read up on this in our documentation - https://docs.jsplumbtoolkit.com/toolkit/6.x/lib/shape-sets.


Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


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.

· 4 min read
Simon Porritt

The Toolkit has support for layouts baked in, shipping with a Force Directed layout, Hierarchy layout, Balloon layout and Circular layout, as well as supporting Absolute positioned nodes and a powerful and simple api for creating custom layouts.

Sometimes, though, you just want to make small adjustments to the elements in your UI to neaten things up a bit, and the jsPlumb Toolkit offers a very handy utility for doing so - the magnetizer. It's a collection of tools you can use to nudge elements around.

On demand magnetize

In this first example someone has carelessly left node 2 on top of node 1. Don't worry, though - if you tap on one of the nodes we'll invoke the magnetizer and push the other one away:

We set this up with a tap listener on the node definition in our view:

nodes:{
default:{
template:`<div>{{id}}</div>`,
events:{
[EVENT_TAP]:(p) => {
mySurface.magnetize(p.obj)
}
}
}
}

When you invoke the magnetizer in this way it uses the center of the object you pass in as the focus point, and it automatically excludes the object you passed in from being moved.

After drag magnetize

You can also set this up to happen after a drag - try dragging a node in this next canvas onto another node and see what happens:

This was configured in the `magnetizer` options to our render call:
toolkit.render(someElement, {

...,
magnetize:{
afterDrag:true
}

})

Setting this up on your canvas can be a quick usability win for your users.

In the above example the node that you have just dragged displaces any node(s) it was dragged onto, but you can switch that behaviour around and have the dragged node move if you want:

toolkit.render(someElement, {

...,
magnetize:{
afterDrag:true,
repositionDraggedElement:true
}

})

Constant magnetize

It's also possible to have the magnetizer permanently switched on while dragging. This one's quite fun:

toolkit.render(someElement, {

...,
magnetize:{
constant:true
}

})

Magnetize after layout

Here are some nodes that have been positioned absolute and are overlapping:

[
{ id:"1", left:50, top:50 },
{ id:"2", left:100, top:100 },
{ id:"3", left:450, top:0 },
]

You can switch on the magnetizer so that it runs after a layout has run:

toolkit.render(someElement, {

...,
magnetize:{
afterLayout:true
}

})

This is the same few overlapping nodes rendered with afterLayout:true set in the magnetize options:

Gathering elements

As you've probably noticed, the magnetizer operates as if every element in your canvas has the same magnetic charge, and they therefore repel each other. But what if you want the opposite? You can also use the magnetizer to gather elements - try clicking on an element in this canvas:

This is configured in much the same way as the first example, except we use the gather method instead of magnetize:

nodes:{
default:{
template:`<div>{{id}}</div>`,
events:{
[EVENT_TAP]:(p) => {
mySurface.gather(p.obj)
}
}
}
}

Group expand

Here we see a collapsed group and a node positioned nearby. Tap on the group to toggle its collapsed state - the node will be magnetized out of the way:

toolkit.render(someElement, {

...,
magnetize:{
afterGroupExpand:true
}

})

Group collapse

Here we see a group and a node, which is connected to one of the group's children, positioned at some distance. Try tapping on the group now - you'll see node 3 be gathered in next to it:

toolkit.render(someElement, {

...,
magnetize:{
afterGroupCollapse:true
}

})

afterGroupCollapse and afterGroupExpand can of course be used at the same time.


Summary

The magnetizer is a useful tool for adding an extra level of polish to your applications. If you've got users working on diagrams with a lot of elements, having the canvas automatically nudge elements around can be a real productivity boost.


Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


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.

· 6 min read
Simon Porritt

Snaplines plugin

In version 6.7.0 we released a new plugin - Snaplines - which you can see in action in our Flowchart builder demonstration. In this post we're going to look at a couple of methods offered by the surface component which allowed us to implement snaplines in a matter of hours.

This is a picture of the snaplines plugin in action:

Snaplines - align your flowcharts perfectly - JsPlumb, industry standard diagramming and rich visual UI Javascript library

The internal workings of this plugin are quite simple:

  1. At drag start, record the position of the edges and center of each of the other elements in the canvas
  2. As the user drags an element, check these positions to see if one of the element's edges, or its center, is in proximity to an edge or the center of one or more other elements
  3. When the drag element is in proximity to an edge or center of some other element, draw a snapline.

Steps (1) and (2) are just a bit of maths, and we're not going to cover them here. It's step 3 we're here to talk about today - "draw a snapline". As always with computers, it's easy to say something, but how do you actually do it? Fortunately the Toolkit's surface component makes it straightforward.

Fixing elements

The key to the ease with which snaplines was developed is this method that is available on the surface component:

fixElement(el:Element, position:PointXY, constraints?:{left?:boolean, top?:boolean})

Given some DOM element, fixElement will place it on the surface canvas at position - where position is a point in the coordinate space of the elements of the canvas, and then fix it in one or both axes so that it never disappears from view. That's right: fixElement abstracts away all of the considerations about the current zoom level, or where the user has panned the canvas to, and just plonks down the element where you asked it to. When you subsequently pan and/or zoom the canvas, the position of the fixed element is updated so that it stays where you requested. Great!

Snaplines Example

Click on a node in this example and we'll create a horizontal and vertical line, each one passing through the element's center. Then we'll call fixElement for each line:

surface.fixElement(horizontalLine, 
{
x:p.obj.data.left - (p.obj.data.width * 0.5),
y:p.obj.data.top + (p.obj.data.height / 2)
}
)

surface.fixElement(verticalLine,
{
x:p.obj.data.left + (p.obj.data.width * 0.5),
y:p.obj.data.top - (p.obj.data.height / 2)
}
)

Now when you pan/zoom the canvas, the red cross stays in place where it was fixed. Note that it does not follow the node around - you can drag a node that you previously clicked and the cross will not move. This is by design.

It's important to keep in mind that you can fix arbitrary HTML elements using this method - they don't have to be simple red lines like in this example. You could fix any HTML you like.

Unfixing an element

Notice as you click on new nodes the cross moves? And when you click on whitespace it goes away? There is a companion method - unfixElement - which will remove a fixed element from the canvas. We call this whenever a click occurs on a node or the canvas:

surface.unfixElement(horizontalLine)
surface.unfixElement(verticalLine)

Constraints

You may have noticed the constraints argument to the fixElement method - and also that in both examples above we did not provide a value. The snaplines plugin uses fixElement in the simplest way, but it has some extra tricks: using constraints, you can fix an element to a specific position on the canvas and then if that position ever goes out of the viewport the element will be shifted to ensure it is always visible.

In this next example we have positioned a label at {x:50, y:150} and fixed it so that it never goes into the negative x axis, and we've positioned another label at {x:300, y:20} and fixed it so that it never goes into the negative y axis:

surface.fixElement(myLabel, { x:50, y:150}, {left:true})
surface.fixElement(myOtherLabel, { x:300, y:20}, {top:true})

You can of course fix one element in both axes:

surface.fixElement(myLabel, { x:50, y:150}, {left:true, top:true})

Swim Lanes example

You can use fixed elements to implement sticky headers for swim lanes, which is pretty cool. Try dragging the canvas below to pan it and see how the lane headers stay in place when you pan to the left.

We fix the headers above with this code (this is for the first header):
surface.fixElement(l,{x:50, y:50},  {left:true})

Floating Elements

Another way to decorate your canvas is with floating elements - elements that are positioned at some position relative to the viewport origin, and which do not pan and zoom with the content:

const l = document.createElement("div")
l.innerHTML = "I AM FLOATING"
surface.floatElement(l,{x:10, y:10})

Floating inspector example

In this example we use floatElement to float an HTML element which is configured as an inspector - code is shown below. Click on nodes to inspect them and change their label/background color. Click the canvas to reset the selection.

Our nodes are like this:

{ id:"1", label:"Un", left:150, top:50, bg:"darkslateblue" },

We create the element for the inspector and initialize it like this:

const l = document.createElement("div")
l.className = "floating-inspector"

new VanillaInspector({
container:l,
surface:s,
emptyTemplate:`<span>No selection</span>`,
templateResolver:() => {
return `<div>
<div style="display:flex;align-items:center">
<strong>ID:</strong>
{{id}}
</div>
<strong>Label:</strong>
<input jtk-att="label" jtk-focus/>
<strong>Background:</strong>
<select jtk-att="bg">
<option value="darkslateblue">Dark Blue</option>
<option value="#396a1e">Dark Green</option>
</select>
</div>`
}
})

And then we ask the surface to float the element it is mounted in:

surface.floatElement(l, {x:10, y:10})

Summary

fixElement and floatElement are powerful methods that you can use to bring your apps to a new level. They're tightly integrated with the Toolkit ecosystem and they're DOM agnostic - you can use any markup you like, meaning it's simple to keep things on brand. The examples here only scratch the surface - pun not intended, but I'm leaving it in - of what you can do.


Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


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.

· One min read
Simon Porritt

UPDATES

  • Fixed an issue writing attributes to the xlink namespace in the vanilla template engine
  • Updated template parsing code to better handle extraneous whitespace.
  • Improvements to the preview view of the svg/png/jpg export code
  • Updated jsdocs for Vue 2 and Vue 3 integration packages
  • Added the ability to register a Surface on Vue 2 and Vue 3 manually - allowing you to use vanilla templating inside of a Vue app.

BREAKING

  • The order of arguments in the fixElement method has been switched from (el:Element, constraints:FixedElementConstraints, pos:PointXY) to (el:Element, pos:PointXY, constraints?:FixedElementConstraints). This better reflects the fact that constraints is an optional argument whereas pos is not.

Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


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.

· 2 min read
Simon Porritt

Hello World starter app

We've already got a bunch of great starter apps you can use as a basis for your own, but sometimes you want to start from something a little more bare-bones. So we've added a Hello World starter app - just enough to get you started.

Hello World - JsPlumb, flowcharts, chatbots, bar charts, decision trees, mindmaps, org charts and more

Collapsible Hierarchy demonstration

The Toolkit's real strength lies in how flexible it is. In our new Collapsible Hierarchy demonstration we combine our tried and trusted hierarchy layout with dynamic selection rendering to implement a layout in which you can collapse nodes and prune their descendants.

Collapsible hierarchy Italic language family - JsPlumb - Angular, React, Vue, Svelte diagramming library

The dataset we used for this is the Italic languages. Lookout for a separate post on this demo over the next couple of days.

Initial shape set selection in Shape Palettes

We have added support for the initialShape optional parameter as an argument to the shape library palette component in each of our Angular, React, Vue 2 and Vue 3 integrations. If you have a library with multiple shape sets and you want to initialize the palette showing only one set, this is the flag you need.

multiple svg shape sets for diagramming - JsPlumb, leading alternative to GoJS and JointJS

Inspector bugfix

We fixed a small issue in our inspectors: prior to 6.7.1 a change in a <select> element would not be detected dynamically, only when the user committed the inspector.


Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


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.

· 6 min read
Simon Porritt

One question we hear a lot from prospective licensees is how does jsPlumb compare as an alternative to JointJS? The people at JointJS have taken several stabs at summarizing this but the results can at best be described as an exercise in creative writing.

In today's post I'm going to jot down a few thoughts of our own, plus those of the many developers who come to us once they've reached the limit of JointJS's capabilities.

What, then, are the key differentiators between jsPlumb and JointJS? We can think of many, but these are what we consider the key ones:

jsPlumb is not limited to SVG but JointJS is

With jsPlumb you can use any valid HTML or SVG to render the elements in your UI - you are not limited to just SVG, which, while useful for a certain class of UIs such as diagrams, quickly becomes a nightmare to work with when you want something a little more bespoke. This was the topic of a recent post of ours, and it's quite fundamental. If you can build it in JointJS you can, naturally, build it in jsPlumb, because HTML is a superset of SVG in the browser. But you cannot say the opposite: there are plenty of UIs that you can build with jsPlumb that are difficult, if not impossible, to build with JointJS.

So, with jsPlumb you can build a UI in which some of your elements are plain old SVG:

<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>

and some of them are arbitrarily complex HTML:

<div class="demo-html" data-cow="show">
<strong>{{label}}</strong>
<select>
<option value="show">Show cow</option>
<option value="hide">Hide cow</option>
</select>

<input type="text" placeholder="enter label"/>
</div>

Notice the cow? You can hide and show that cow. One great advantage of HTML over SVG nodes is that you can use all the power of CSS. We use it here to hide and show the cow:


.my-node {
background:none;
}

[data-cow='show'] {
background-image: url('/img/351032562.jpg');
background-size: cover;
}

When the user makes a selection in the drop down, we simply update the data-cow attribute on the node element.

We've also put a nice box shadow on that node, via CSS. When you build applications with jsPlumb it's much easier to take advantage of all of the underlying technologies.

jsPlumb integrates with Angular, React, Vue (2/3) and Svelte

This blog is a Docusaurus application, meaning it runs on React, and I can embed React components into the page. For instance, how about this <ShowMeACow/> component here - try clicking this link:

This is a functional React component, and obviously a very useful one. The source is:

import React, {useState } from "react"

export default function ShowMeACow({size, startShown}) {

const s = size || 450
const [showCow, setShowCow] = useState(startShown === true)

return <div className="show-me-a-cow">
<button onClick={() => setShowCow(!showCow)}>{showCow ? "Hide that" : "Show me a"} cow</button>
{showCow && <img src="/img/351032562.jpg" width={s}/>}
</div>
}

So what do I mean when I say jsPlumb integrates with React? I mean I can just use the Toolkit's React integration and put that ShowMeACow component right inside a node:


I wrote a component:

const CowComponent = function({ctx}) {
return <div className="show-me-a-cow-demo-node">
<ShowMeACow size={150} startShown={ctx.vertex.data.cowShown}/>
</div>
}

and then I mapped it inside my Toolkit view:

const view = {
nodes: {
default: {
jsx: (ctx) => <CowComponent ctx={ctx}/>
}
}
}

And then I used the JsPlumbToolkitSurfaceComponent from our React integration to draw that canvas above. In one of my nodes I set cowShown:true and in the other one I didn't.

It is not possible to do these things with JointJS, and whilst this charming cow is perhaps a spurious example, it's not hard to see how powerfully rich your UI can be when you can integrate to this level. We offer the same level of support for Angular, Vue 2, Vue 3 and Svelte.

jsPlumb has no external dependencies. JointJS has 3. And more if you want touch events.

To get JointJS to do anything at all you have to include 3 external dependencies:

  • jQuery
  • Backbone
  • Lodash

These are all quite antiquated technologies now, and they do not come from the modern reactive world. The last lodash release was in 2016!

jsPlumb has no external dependencies: everything is tightly integrated, and if we wanted to update some core piece of our library we wouldn't have to wait for an external entity to do it.

This dependency on external libraries in JointJS actually extends to support touch events, surprisingly. In the Touch Gestures demo, the question is asked "Have you been wondering how to implement touch gestures such as pan and pinch in your JointJS application?", and the answer given is "use this third party library". I mean obviously I paraphrased that, but I would do that, since I am here representing jsPlumb, which has no external dependencies and also the smoothest pinch to zoom for miles around.

Summary

Those are 3 key differences between JointJS and JsPlumb, and I of course consider each one of them on its own to be sufficient reason to choose jsPlumb. If you can build it with JointJS you can build it with jsPlumb - faster, and cheaper, probably.

Here's a matrix of our own:

JointJS+jsPlumb
Integration with Angular, React, Vue2, Vue 3 and Svelte
Depends on multiple external libraries
Supports touch events
Access to unminified source code
No restrictions on use of license
Graph Layout Algorithms
Dedicated support available
Export to SVG, PNG and JPG
Full text search
Dialogs module
Transactions
Render dynamic selections

Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


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.

· 2 min read
Simon Porritt

What's new?

Snaplines plugin

We've added a brand new plugin in this release - Snaplines:

Snaplines - align your flowcharts and diagrams perfectly - JsPlumb - Angular, React, Vue, Svelte diagramming library

When the edge or center of any two elements is in proximity a snapline appears, in green (by default - but you can change that with CSS). When they are exactly in line, the snapline goes red. The related element for an active snapline is also assigned a CSS class, for maximum flexibility for your design team.

You can see this plugin in action in the Flowchart builder. We're looking forward to seeing what you build with it!

Basic shapes

A new shape set is available in 6.7.0. The basic shapes set includes things like rectangles, hexagons, triangles, and more:

Basic shapes for diagrams - JsPlumb - JavaScript and Typescript diagramming library that fuels exceptional UIs

We've added these to the Flowchart builder (but you'll need to pick them from the dropdown - they're not initially visible)

Updates

We've made a few miscellaneous updates in this release:

  • The surface component now supports an editablePaths flag, which, if set, sets up a path editor internally and allows you to call methods on the Surface directly to edit paths. It also ensures the path editors are included and will not slip through any tree shaker.
  • The drawing tools plugin now supports a constrainGroups flag, which, if set, will prevent the group from being resized to such a point that any of its child members are out of the viewport.
  • The drawing tools plugin will not now resize a collapsed group.
  • We fixed an issue with the grid background plugin where if any elements were positioned in the negative x or y axis the surface's clamping was computed incorrectly.
  • Some CSS classes were added for basic styling of a shape library palette.
  • The shape library palette takes an optional initialSet parameter, allow you to specify an initial shape set to show and hide the others.
  • When a single shape set is shown, the shape library palette doesnt show that set's title, since it is shown in the dropdown above.

Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


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.