Flowchart Builder (Vue 2)
This Vue 2 app was created with Vite. The code is very similar to the code in the vanilla flowchart builder, but it's just arranged slightly differently. The biggest difference is in the node rendering - in the Toolkit's Vue 2 integration we render each node as a component. We also use components from the Toolkit's vue 2 integration for the miniview and pan/zoom controls.

On this page we discuss the Vue 2 implementation. There are implementations available for each of our library integrations, and also for vanilla JS:
The source code for this application is available on Github at https://github.com/jsplumb/jsplumbtoolkit-applications.
Overview
The main features of this application are:
A drawing canvas which renders nodes as SVG shapes, and allows a user to resize nodes. Nodes are dragged on a grid, and resizing is constrained to the same grid.
A palette of SVG shapes which can be dragged onto the canvas.
Editable edge paths, labels and colors
Editable node labels, fill/outline color and text color
A miniview
A set of controls for managing zoom, undo, redo, etc.
Note that the code snippets on this page refer to constants in many places, which, for the sake of brevity, we have not included. You can find these in the source code on Github.
Throughout the code you will see references to a variable called anchorPositions
. This is an array of anchor positions that dictate where on each node edges can be connected. It is declared at the start of the demonstration code:
const anchorPositions = [
{ x:0, y:0.5, ox:-1, oy:0, id:"left" },
{ x:1, y:0.5, ox:1, oy:0, id:"right" },
{ x:0.5, y:0, ox:0, oy:-1, id:"top" },
{ x:0.5, y:1, ox:0, oy:1, id:"bottom" }
]
Each anchor position has x
, y
, ox
and oy
values - see anchors for details of this. In addition, we set an id
on each anchor, which is not part of the data model but rather is used for CSS purposes. This is discussed below.
Imports
"dependencies":{
"@jsplumbtoolkit/browser-ui-vue2":"^6.2.0"
}
Setup
main.js
We import jsPlumbToolkitVue2Plugin
, which contains the components for the surface, miniview and drag/drop palette, as well as ShapeLibraryPlugin
, for rendering SVG shapes, and InspectorPlugin
, for rendering a node/edge inspector.
import Vue from 'vue'
import App from './App.vue'
import { JsPlumbToolkitVue2Plugin, ShapeLibraryPlugin, InspectorPlugin } from '@jsplumbtoolkit/browser-ui-vue2'
import './assets/main.css'
// import Toolkit plugin
Vue.use(JsPlumbToolkitVue2Plugin)
Vue.use(ShapeLibraryPlugin)
Vue.use(InspectorPlugin)
new Vue({
render: (h) => h(App)
}).$mount('#app')
Flowchart component
The app's entry point is Flowchart.vue
. The template for this is:
<div id="app">
<ControlsComponent surface-id="surfaceId"/>
<div class="jtk-demo-canvas">
<jsplumb-toolkit ref="toolkitComponent"
surface-id="surfaceId"
v-bind:render-params="renderParams"
v-bind:view="view"
v-bind:toolkit-params="toolkitParams"
url="copyright.json"
>
</jsplumb-toolkit>
<!-- miniview -->
<jsplumb-miniview surface-id="surfaceId"></jsplumb-miniview>
</div>
<div class="jtk-demo-rhs">
<!-- the node palette -->
<div class="sidebar node-palette">
<jsplumb-shape-palette surface-id="surfaceId"
:shape-library="shapeLibrary"
:data-generator="dataGenerator"/>
</div>
<!-- node/edge inspector -->
<InspectorComponent surface-id="surfaceId" v-bind:edge-mappings="edgeMappings"/>
<div class="description">
<p>
This sample application is a builder for flowcharts.
</p>
</div>
</div>
</div>
Note how the various jsplumb-
components all take a surface-id
as input. The Vue 2 integration offers method to manage the creation of, and access to, surfaces.
Rendering the canvas
The canvas is rendered via the jsplumb-toolkit
element shown above. We give it the ID of the surface to create, and we store it on a ref
. We plug in a view
, some render-params
and some toolkit-params
, and in this app we also provide the url
for a dataset to initially load.
View params
The view is returned as part of the Flowchart's data()
value:
view:{
nodes:{
default:{
component:NodeComponent,
events: {
[EVENT_TAP]: (params) => {
edgeEditor.stopEditing()
// if zero nodes currently selected, or the shift key wasnt pressed, make this node the only one in the selection.
if (toolkit.getSelection()._nodes.length < 1 || params.e.shiftKey !== true) {
toolkit.setSelection(params.obj)
} else {
// if multiple nodes already selected, or shift was pressed, add this node to the current selection.
toolkit.addToSelection(params.obj)
}
}
},
inject:{
shapeLibrary:shapeLibrary,
anchorPositions:anchorPositions
},
anchorPositions,
maxConnections:-1
}
},
edges: {
[DEFAULT]: {
endpoint:BlankEndpoint.type,
connector: {
type:OrthogonalConnector.type,
options:{
cornerRadius: 3,
alwaysRespectStubs:true
}
},
cssClass:CLASS_FLOWCHART_EDGE,
labelClass:CLASS_EDGE_LABEL,
label:"{{label}}",
outlineWidth:10,
events: {
click:(p) => {
toolkit.setSelection(p.edge)
edgeEditor.startEditing(p.edge, {
deleteButton:true
})
}
}
}
}
}
This view is almost identical to the view in the render call for the vanilla app. The key difference is that in the Vue 2 integration nodes are rendered by Vue components.
Render params
renderParams:AngularRenderOptions = {
// see `edge-mappings.js` for details.
propertyMappings:{
edgeMappings:edgeMappings()
},
// Layout the nodes using an absolute layout
layout: {
type: AbsoluteLayout.type
},
grid:{
size:GRID_SIZE
},
events: {
[EVENT_CANVAS_CLICK]: (e) => {
this.toolkit.clearSelection()
this.edgeEditor.stopEditing()
}
},
consumeRightClick: false,
dragOptions: {
filter: ".jtk-draw-handle, .node-action, .node-action i"
},
plugins:[
{
type:DrawingToolsPlugin.type,
options:{
widthAttribute:"width",
heightAttribute:"height"
}
},
{
type:LassoPlugin.type,
options: {
lassoInvert:true,
lassoEdges:true
}
},
{
type:BackgroundPlugin.type,
options:GRID_BACKGROUND_OPTIONS
}
],
zoomToFit:true
}
The render params are almost identical to the render params for the vanilla app, with one notable exception being that in the Angular app we use a jsplumb-miniview
component inside AppComponent
, whereas in the vanilla app we configure a miniview as a plugin. Another difference is that we do not have a modelEvents
section in the render params for the Angular app, because we write that logic directly into the node component.
Toolkit params
These are injected into the jsplumb-surface
component.
toolkitParams = {
// set the Toolkit's selection mode to 'isolated', meaning it can select a set of edges, or a set of nodes, but it
// cannot select a set of nodes and edges. In this demonstration we use an inspector that responds to events from the
// toolkit's selection, so setting this to `isolated` helps us ensure we dont try to inspect edges and nodes at the same
// time.
selectionMode:SelectionModes.isolated,
// This is the payload to set when a user begins to drag an edge - we return values for the
// edge's label, color and line style. If you wanted to implement a mechanism whereby you have
// some "current style" you could update this method to return some dynamically configured
// values.
beforeStartConnect:(node, edgeType) => {
return {
[PROPERTY_LABEL]:"",
[PROPERTY_COLOR]:DEFAULT_STROKE,
[PROPERTY_LINE_STYLE]:EDGE_TYPE_TARGET_ARROW
}
}
}
Nodes
Rendering Nodes
In the viewParams above, we map a single node type DEFAULT
to an angular component:
nodes: {
[DEFAULT]:{
component:NodeComponent,
events: {
[EVENT_TAP]: (params) => {
this.edgeEditor.stopEditing()
// if zero nodes currently selected, or the shift key wasnt pressed, make this node the only one in the selection.
if (this.toolkit.getSelection()._nodes.length < 1 || params.e.shiftKey !== true) {
this.toolkit.setSelection(params.obj)
} else {
// if multiple nodes already selected, or shift was pressed, add this node to the current selection.
this.toolkit.addToSelection(params.obj)
}
}
}
}
}
The component's code looks like this:
import {Component} from "@angular/core"
import {BaseNodeComponent} from "@jsplumbtoolkit/browser-ui-angular"
import { anchorPositions } from "./app.component"
@Component({
template:`<div style="width:{{obj.width}}px;height:{{obj.height}}px;color:{{obj.textColor}}" class="flowchart-object" data-jtk-target="true" data-jtk-target-port-type="target">
<span>{{obj.text}}</span>
<jtk-shape [obj]="obj" [width]="obj.width" [height]="obj.height"></jtk-shape>
<div *ngFor="let anchor of anchorPositions"
class="jtk-connect jtk-connect-{{anchor.id}}"
[attr.data-jtk-anchor-x]="anchor.x"
[attr.data-jtk-anchor-y]="anchor.y"
[attr.data-jtk-orientation-x]="anchor.ox"
[attr.data-jtk-orientation-y]="anchor.oy"
data-jtk-source="true"></div>
<div class="node-delete node-action delete" (click)="this.removeNode()"></div>
</div>`
})
export class NodeComponent extends BaseNodeComponent {
anchorPositions = anchorPositions
}
Points to note:
- We write out values for
width
,height
andcolor
into the root element's style. In this app, each node's backing data contains values for width and height, and we use a Drawing Tools plugin to allow the user to resize elements. - We use a
jtk-shape
component to write out the SVG for each node. See below. - We import
anchorPositions
from the main component and iterate through its values to write out a set of elements from which connections can be dragged - We use the
removeNode()
method from the Toolkit'sBaseNodeComponent
to remove a node from the dataset
Shape library
This application uses a shape library to support rendering SVG into node elements.
Configuration for this comes in two parts:
Instantiating a shape library
We do this in the AppComponent
constructor:
import { FLOWCHART_SHAPES } from "@jsplumbtoolkit/browser-ui"
constructor(public $jsplumb:jsPlumbService) {
this.$jsplumb.registerShapeLibrary([FLOWCHART_SHAPES])
}
registerShapeLibrary
is a method in the Toolkit's angular integration which creates a shape library in the Toolkit's angular service, which other components, such as a node palette can connect to.
In this app we use registerShapeLibrary
without providing an ID for the library, as we only have one shape library in the app. But we could provide an ID as the second argument, allowing us to support multiple shape libraries:
this.$jsplumb.registerShapeLibrary([FLOWCHART_SHAPES], "flowchartShapeLibrary")
this.$jsplumb.registerShapeLibrary([otherShapes], "otherShapeLibrary")
Rendering shapes
Shapes are rendered with the jtk-shape
component:
Dragging new nodes
This application makes use of a shape library to render SVG into the nodes, and also to support a palette of SVG shapes from which the user can drag new nodes to the canvas, and a jtk-shape-palette component to support dragging new nodes.
The jtk-shape-palette
is declared in the template for AppComponent
:
<jtk-shape-palette surfaceId="surface" [dataGenerator]="dataGenerator"></jtk-shape-palette>
In the shape library section above, we mentioned that the registerShapeLibrary
method takes an optional second argument that identifies the shape library, and if this is omitted, a default ID is used instead. The jtk-shape-palette
component also supports this concept: in the HTML shown above, notice that we do not provide an ID for the shape library we want this component to render, so it uses the default value.
We could provide an ID if we wanted to:
<jtk-shape-palette shapeLibraryId="otherShapeLibrary" surfaceId="surface" [dataGenerator]="dataGenerator"></jtk-shape-palette>
Data generator
The jtk-shape-palette
component is based on the surface drop manager component, and uses the same mechanism to get an initial payload for any element that is being dragged out of the palette - the dataGenerator
.
In this app, dataGenerator
is a method declared on the AppComponent
class:
/**
* Generator for data for nodes dragged from palette.
* @param el
*/
dataGenerator = (el:Element) => {
return {
fill:DEFAULT_FILL,
outline:DEFAULT_STROKE,
textColor:DEFAULT_TEXT_COLOR,
outlineWidth:DEFAULT_OUTLINE_WIDTH
}
}
Resizing nodes
Node resize is supported via the inclusion of the drawing tools plugin, in the renderParams
:
plugins:[
{
type:DrawingToolsPlugin.type,
options:{
widthAttribute:"width",
heightAttribute:"height"
}
}
]
In this application we set widthAttribute
and heightAttribute
because their defaults are w
and h
, and we are transitioning the Toolkit's UI code over to use width
and height
throughout the code, because those are the variable names used in the DOM. In 7.x the drawing tools plugin will be updated to use width
and height
by default.
Editing nodes
Nodes are edited via the InspectorComponent
, which is an Angular component that internally creates an Inspector. The after view init method of this component looks like this:
ngAfterViewInit(): void {
this.$jsplumb.getSurface(this.surfaceId, (surface) => {
this.inspector = new Inspector({
container:this.el.nativeElement,
surface,
renderEmptyContainer:() => {
this.currentType = ''
},
refresh:(obj:Base, cb:() => void) => {
this.currentType = obj.objectType
setTimeout(cb, 0)
this.changeDetector.detectChanges()
}
})
})
}
The call to create a new inspector takes four arguments:
- container the DOM element the inspector should use to apply/extract values. Here it is the component's native element.
- surface The surface to attach the inspector to. We extract this from the Toolkit's service using the same surface name as we used to instantiate the
jsplumb-surface
component. - renderEmptyContainer The inspector calls this method when nothing is selected. Here, we set the
currentType
member to an empty string, which will result in the template rendering an empty div element (see below). - refresh The inspector calls this method when it needs this component to render an appropriate UI for a newly selected object (or objects). This method passes in the selected object and a callback to invoke once the UI is ready. We set
currentType
to the object's type, and then we set a timeout which will invoke the callback after this tick is complete. Lastly we instruct the change detector to run. When the timeout fires and the callback is invoked the UI will have been updated.
Edges
Rendering edges
Edges are rendered via the DEFAULT
mapping in the edges
section of the viewParams
shown above. For more information on views, see this page.
edges: {
[DEFAULT]: {
endpoint:BlankEndpoint.type,
connector: {
type:OrthogonalConnector.type,
options:{
cornerRadius: 3,
alwaysRespectStubs:true
}
},
cssClass:CLASS_FLOWCHART_EDGE,
labelClass:CLASS_EDGE_LABEL,
label:"{{label}}",
events: {
[EVENT_CLICK]:(p) => {
this.toolkit.setSelection(p.edge)
this.edgeEditor.startEditing(p.edge, {
deleteButton:true
})
}
}
}
}
One particular point to note here is the EVENT_CLICK
handler:
[EVENT_CLICK]:(p) => {
this.toolkit.setSelection(p.edge)
this.edgeEditor.startEditing(p.edge, {
deleteButton:true
})
}
This method does two things:
- it sets the clicked edge to be the currently selected object in the Toolkit, which the attached inspector will detect, and then display an editor for the edge
- it calls
startEditing
on the edge path editor, requesting adeleteButton
be attached. Prior to version 6.3.0 you will want to passanchorPositions
in here, as the path editor did not automatically discover them. But from 6.3.0 onwards the path editor will automatically discover the anchor positions.
Edge overlays
The default edge type mapping declares a label
:
label:"{{label}}"
This instructs the Toolkit to extract label
from each edge's backing data and use it to display a label overlay (at location 0.5). The other overlays you see in this app are achieved via the use of edge property mappings to render different types of edges.
We support 5 types - a plain edge, a dashed edge, an edge with an arrow at the source, an edge with an arrow at the target, and an edge with an arrow at both ends.
These types are declared in the edge-mappings.ts
file in the source:
export default function edgeMappings(arrowWidth, arrowLength) {
arrowWidth = arrowWidth || ARROW_WIDTH
arrowLength = arrowLength || ARROW_LENGTH
return [
{
property:PROPERTY_LINE_STYLE,
mappings:{
[EDGE_TYPE_SOURCE_ARROW]:{
overlays:[ { type:ArrowOverlay.type, options:{location:0, direction:-1, width:arrowWidth, Length:arrowLength} } ]
},
[EDGE_TYPE_TARGET_ARROW]:{
overlays:[ { type:ArrowOverlay.type, options:{location:1, width:arrowWidth, length:arrowLength} } ]
},
[EDGE_TYPE_BOTH_ARROWS]:{
overlays:[ {
type:ArrowOverlay.type,
options:{
location:1,
width:arrowWidth,
length:arrowLength
}
}, {
type:ArrowOverlay.type,
options:{
location:0,
direction:-1,
width:arrowWidth,
length:arrowLength
}
} ]
},
[EDGE_TYPE_PLAIN]:{},
[EDGE_TYPE_DASHED]:{
cssClass:CLASS_DASHED_EDGE
}
}
}
]
}
and are set on the surface in the render params.
Default edge type
The beforeStartConnect
method that we pass in to the Toolkit's constructor is the means we use to set the default payload for a new edge dragged by the user:
// This is the payload to set when a user begins to drag an edge - we return values for the
// edge's label, color and line style. If you wanted to implement a mechanism whereby you have
// some "current style" you could update this method to return some dynamically configured
// values.
beforeStartConnect:(node, edgeType) => {
return {
[PROPERTY_LABEL]:"",
[PROPERTY_COLOR]:DEFAULT_STROKE,
[PROPERTY_LINE_STYLE]:EDGE_TYPE_TARGET_ARROW
}
}
Editing edges
Edge label, color and "line style" are also edited via the FlowchartInspector
: it contains a template for both nodes and for edges, and picks the appropriate one when something is selected.
Edge label and color are both set/changed by making use of the simple edge styles concept, in which there is a basic set of properties that the Toolkit will use to automatically set certain parts of the appearance of an edge.
The "line style" of an edge in this demonstration is a reference to what, if any, overlays the edge has, and whether it is a dashed line or not. As mentioned in the edge rendering section above, this is accomplished by edge property mappings.
In the edge inspector of this demonstration you will see a Line style
section:

We use the EdgeTypePicker
tag, discussed here to achieve this. The code that does this - and which uses the tag in the edge inspector template - is all in flowchart-inspector.js
.
Editing edge paths
Paths can be edited in this application, via an EdgePathEditor. By default (since 6.2.11) this is created in active mode:
edgeEditor = new EdgePathEditor(renderer, {activeMode: true})
..but we leave the activeMode
setter in the code to allow for versions prior to 6.2.11.
The call shown above creates an edge path editor but does not activate it. The path editor is activated in the click handler for edges:
edgeEditor.startEditing(p.edge, {
// Show a delete button.
deleteButton:true
})
Background grid
The application renders a background grid via the background plugin. The options we pass (shown above as GRID_BACKGROUND_OPTIONS
) are:
{
dragOnGrid:true,
showGrid:true,
showBorder:false,
autoShrink:true,
minWidth:10000,
maxWidth:null,
minHeight:10000,
maxHeight:null,
showTickMarks:false,
type:GeneratedGridBackground.type
}
There is a discussion of each of these options on the background plugin page.
Miniview
We use a miniview in this starter app, via the jsplumb-miniview
component that ships with the angular integration.
The miniview component is declared in the template for our app component. We pass in the ID of the surface to which to attach:
<!-- miniview -->
<jsplumb-miniview surfaceId="surface"></jsplumb-miniview>
Controls
Here we use another component that was created for our demonstrations but which now ships with the Toolkit, as we thought it might be useful for others:
<jsplumb-controls surfaceId="surface"></jsplumb-controls>
CSS
Styles for the app itself are in app.css
and styles.css
.
styles.css
also imports a few of the CSS files that ship with the Toolkit:
@import "../node_modules/@jsplumbtoolkit/browser-ui/css/jsplumbtoolkit.css";
@import "../node_modules/@jsplumbtoolkit/browser-ui/css/jsplumbtoolkit-connector-editors.css";
@import "../node_modules/@jsplumbtoolkit/browser-ui/css/jsplumbtoolkit-controls.css";
jsplumbtoolkit.css
provides basic 'structural' CSS, and it is recommended that you include this stylesheet in your cascade, at least when you first start developing with the Toolkit.
jsplumbtoolkit-connector-editors.css
contains a mixture of structural and also theming styles, for use by the EdgePathEditor
.
jsplumbtoolkit-controls.css
contains styles for the controls component, which was created for our demonstrations but since 6.2.0 has been shipped with the Toolkit as it may be useful for others.