Mindmap
The source code for this demo is available on Github.
https://github.com/jsplumb-demonstrations/mindmap-builder
If you haven't got a license for JsPlumb, we offer 30 day evaluations.
Start a free trialWhat is a mindmap and how can I build one with JsPlumb?
A mindmap is a diagram that allows you to group concepts, tasks and other items around a central concept or subject. It is an intuitive means of representing the way the human mind thinks about the world.
People use mindmaps for all sorts of different purposes:
- Brainstorming
- Organizing research
- Planning meetings
- Comparing JsPlumb's features favourably to its competitors
The term mindmap was popularized by a British psychologist named Tony Buzan on a 1974 BBC TV show called Use your head, but usage of this type of diagram has been going on for hundreds of years, with recorded instances stretching back to the 3rd century.
Angular Mindmaps
JsPlumb's deep Angular integration makes building an Angular Mindmap very simple. The Mindmap builder starter app uses our ControlsComponent, SurfaceComponent, MiniviewComponent and Angular service to provide a fully featured Mindmap. Tested with Angular 16, 17, 18 and 19.
Our Mindmap also takes advantage of JsPlumb's ability to support custom layouts.
Building an app with JsPlumb Angular lets you take real advantage of everything that Angular has to offer - the tabs below show how modular the process is.
Note in the View Options tab how JsPlumb lets you map Angular components to render your node types -JsPlumb Angular slots right in seamlessly to your Angular app. These components are first class Angular components and can use Angular's dependency injection and services just as any other component in your app
- Mindmap Component
- View Options
- Render Options
- Subtopic
- Focus Component
- Service
import React from "react"
import { SurfaceProvider,
SurfaceComponent, ControlsComponent,
MiniviewComponent} from "@jsplumbtoolkit/browser-ui-react"
export default function MyMindmap() {
return <div className="jtk-demo-main">
<SurfaceProvider>
<div className="jtk-demo-canvas">
<SurfaceComponent viewOptions={view} renderOptions={renderOptions} ref={surface}/>
<ControlsComponent/>
<MiniviewComponent/>
</div>
<div className="jtk-demo-rhs">
<div className="description">
<h3>Mindmap Builder</h3>
<ul>
<li>Click the note icon in the upper left to inspect/edit a node.</li>
<li>Click the trashcan icon to delete a node</li>
<li>Click the + button to add a new subtopic. Subtopics can be added to the left or right of the main node.</li>
</ul>
</div>
<Inspector/>
</div>
</SurfaceProvider>
</div>
}
view = {
nodes:{
subtopic:{
component:SubtopicComponent
},
main:{
component:MainComponent
}
}
}
renderOptions = {
zoomToFit:true,
layout:{
type:MINDMAP_LAYOUT
},
// show connections to ports as being attached to their parent nodes. We use this for the main node: its edges
// are connected to either a `right` or `left` port on the main node, but these ports are logical ports only - they
// do not have their own DOM element assigned.
logicalPorts:true,
// in this app, elements are not draggable; they are fixed by the layout.
elementsDraggable:false,
// Run a relayout whenever a new edge is established, which happens programmatically when the user adds a new subtopic.
refreshLayoutOnEdgeConnect:true,
// for the purposes of testing. Without this the right mouse button is disabled by default.
consumeRightClick:false,
defaults:{
connector:{
type:StraightConnector.type,
options:{
stub:20
}
},
anchor:[ AnchorLocations.Left, AnchorLocations.Right ]
},
events:{
[EVENT_CANVAS_CLICK]:() => this.toolkit.clearSelection()
}
}
import {BaseNodeComponent} from "@jsplumbtoolkit/browser-ui-angular"
import {Component} from "@angular/core"
import {CLASS_MINDMAP_INFO, CLASS_ADD_CHILD, CLASS_MINDMAP_DELETE} from "./definitions"
import {MindmapService} from "./mindmap.service"
@Component({
template:`<div class="jtk-mindmap-subtopic jtk-mindmap-vertex">
{{obj.label}}
<div class="jtk-mindmap-info" (click)="this.service.info(this.getNode())"></div>
<div class="jtk-mindmap-addchild" [attr.data-direction]="obj.direction" (click)="this.service.addChild(this.getNode())"></div>
<div class="jtk-mindmap-delete" (click)="this.service.deleteVertex(this.getNode())"></div>
</div>`,
standalone:false
})
export class SubtopicComponent extends BaseNodeComponent {
constructor(public service:MindmapService) {
super()
}
}
import {BaseNodeComponent} from "@jsplumbtoolkit/browser-ui-angular"
import {Component} from "@angular/core"
import {CLASS_MINDMAP_INFO, CLASS_ADD_CHILD, LEFT, RIGHT} from "./definitions"
import {MindmapService} from "./mindmap.service"
@Component({
template:`<div class="jtk-mindmap-main jtk-mindmap-vertex">
{{obj.label}}
<div class="jtk-mindmap-info" (click)="this.service.info(this.getNode())"></div>
<div class="jtk-mindmap-addchild" data-direction="left" (click)="this.service.addChild(this.getNode(), 'left')"></div>
<div class="jtk-mindmap-addchild" data-direction="right" (click)="this.service.addChild(this.getNode(), 'right')"></div>
</div>`,
standalone:false
})
export class MainComponent extends BaseNodeComponent {
constructor(public service:MindmapService) {
super()
}
}
import {Injectable} from "@angular/core"
import {DEFAULT_ANGULAR_TOOLKIT_ID, jsPlumbService} from "@jsplumbtoolkit/browser-ui-angular"
import {Node, uuid} from "@jsplumbtoolkit/browser-ui"
import {DIRECTION, SUBTOPIC} from "./definitions"
@Injectable()
export class MindmapService {
constructor(private jsplumb:jsPlumbService) { }
info(v:Node) {
const toolkit = this.jsplumb.getToolkit(DEFAULT_ANGULAR_TOOLKIT_ID)
toolkit.setSelection(v)
}
deleteVertex(v:Node) {
const toolkit = this.jsplumb.getToolkit(DEFAULT_ANGULAR_TOOLKIT_ID)
// select the node that was clicked and all of its descendants (we get a Selection object back)
const nodeAndDescendants = toolkit.selectDescendants(v, true)
// inside a transaction, remove everything in that selection from the Toolkit (including edges to each of the nodes).
// we do this inside a transaction so we can undo the whole operation as one unit.
toolkit.transaction(() => {
toolkit.remove(nodeAndDescendants)
})
}
addChild(v:Node, direction?:DIRECTION) {
const toolkit = this.jsplumb.getToolkit(DEFAULT_ANGULAR_TOOLKIT_ID)
// for edges from the main node, we attach them to a port on the node, because the main node can
// have `left` and `right` edges. For subtopic nodes we attach directly to the node. So this code tests
// for a matching port and uses it as the source if found, otherwise it uses the source node.
const source = direction != null ? v.getPort(direction) : v
const payload = {
id:uuid(),
parentId:v.id,
label:"New subtopic",
children:[],
type:SUBTOPIC,
direction
}
toolkit.transaction(() => {
const node = toolkit.addNode(payload)
toolkit.addEdge({source, target:node})
})
}
}
React Mindmaps
React Mindmaps with JsPlumb are a snap. Our Mindmap builder starter app uses JsPlumb's advanced React integration with its providers and support for functional components, to give you a solid foundation on which to base your app. JsPlumb's React integration works with all the latest releases - we've tested it on React 16, 17, 18 and 19.
Our Mindmap also takes advantage of JsPlumb's ability to support custom layouts.
Building an app with JsPlumb React lets you take real advantage of everything that React has to offer - the tabs below show how modular the process is.
Note in the View Options tab how JsPlumb lets you map arbitrary JSX to render your node types -JsPlumb React is fully composable and slots right in seamlessly to your React app.
Note also that we can encapsulate business logic inside a service - and then use Angular's DI to inject that service directly into the components we use to render our nodes.
- Mindmap Component
- View Options
- Render Options
import React from "react"
import { SurfaceProvider,
SurfaceComponent, ControlsComponent,
MiniviewComponent} from "@jsplumbtoolkit/browser-ui-react"
export default function MyMindmap() {
return <div className="jtk-demo-main">
<SurfaceProvider>
<div className="jtk-demo-canvas">
<SurfaceComponent viewOptions={view} renderOptions={renderOptions} ref={surface}/>
<ControlsComponent/>
<MiniviewComponent/>
</div>
<div className="jtk-demo-rhs">
<div className="description">
<h3>Mindmap Builder</h3>
<ul>
<li>Click the note icon in the upper left to inspect/edit a node.</li>
<li>Click the trashcan icon to delete a node</li>
<li>Click the + button to add a new subtopic. Subtopics can be added to the left or right of the main node.</li>
</ul>
</div>
<Inspector/>
</div>
</SurfaceProvider>
</div>
}
const view = {
nodes:{
main:{
jsx:(ctx) => <div className="jtk-mindmap-main jtk-mindmap-vertex">
{ctx.data.label}
<div className={CLASS_MINDMAP_INFO}/>
<div className={CLASS_ADD_CHILD} data-direction={LEFT} onClick={() => addChild(ctx.vertex, LEFT)}/>
<div className={CLASS_ADD_CHILD} data-direction={RIGHT} onClick={() => addChild(ctx.vertex, RIGHT)}/>
</div>
},
subtopic:{
jsx:(ctx) => <div className="jtk-mindmap-subtopic jtk-mindmap-vertex">
{ctx.data.label}
<div className={CLASS_MINDMAP_INFO} onClick={() => showInfo(ctx.vertex)}/>
<div className={CLASS_ADD_CHILD} data-direction={ctx.data.direction} onClick={() => addChild(ctx.vertex)}/>
<div className={CLASS_MINDMAP_DELETE} onClick={() => deleteVertex(ctx.vertex)}/>
</div>
}
}
}
const renderOptions = {
// in this app, elements are not draggable; they are fixed by the layout.
elementsDraggable:false,
// after load, zoom the display so all nodes are visible.
zoomToFit:true,
// show connections to ports as being attached to their parent nodes. We use this for the main node: its edges
// are connected to either a `right` or `left` port on the main node, but these ports are logical ports only - they
// do not have their own DOM element assigned.
logicalPorts:true,
// Run a relayout whenever a new edge is established, which happens programmatically when the user adds a new subtopic.
refreshLayoutOnEdgeConnect:true,
// for the purposes of testing. Without this the right mouse button is disabled by default.
consumeRightClick:false,
// Use our custom mindmap layout.
layout:{
type:MindmapLayout.type,
},
defaults:{
connector:{
type:StraightConnector.type,
options:{
stub:20
}
},
anchor:[ AnchorLocations.Left, AnchorLocations.Right ]
},
events:{
[EVENT_CANVAS_CLICK]:() => toolkit.current.clearSelection()
}
}
Typescript Mindmaps
JsPlumb is written in Typescript and ships with a full set of type definitions, for a seamless Typescript development experience.
JsPlumb's Surface component is the heart of the Mindmap, and JsPlumb ships a large number of useful helper components such as the Miniview, the Controls component for managing zoom, undo/redo etc, which lets you easily configure drag/drop of new elements, plus a long list of plugins. Our Mindmap also takes advantage of JsPlumb's ability to support custom layouts.
Our Typescript Mindmap builder starter app provides you with everything you need to get a solid start, and is easily extensible.
<div id="mindmap-wrapper">
<div id="canvas">
<div id="controls"></div>
</div>
<div id="palette"></div>
</div>
import { newInstance,
ControlsComponent } from "@jsplumbtoolkit/browser-ui"
export class MyMindmap() {
constructor() {
const toolkit = newInstance()
const surface = toolkit.render(document.querySelector("#canvas"), {
view:{
nodes:{
default:{
template:`<div data-jtk-target="true"></div>`
}
}
},
layout:{
type:MindMapLayout.type,
options:{
}
}
})
new ControlsComponent(document.querySelector("#controls"), surface)
}
}
Vue Mindmaps
It's easy to build a Vue Mindmap with JsPlumb - we've done most of the hard work for you in our Vue Mindmap builder starter app
Our Vue Mindmap takes advantage of JsPlumb's ability to integrate deeply with Vue, using Vue components to render nodes, and several of JsPlumb's Vue helper components and plugins such as the ControlsComponent, MiniviewComponent. Our Mindmap also takes advantage of JsPlumb's ability to support custom layouts..
<script>
import { defineComponent } from "vue";
import { loadSurface, DEFAULT_VUE_SURFACE_ID } from "@jsplumbtoolkit/browser-ui-vue3"
let surface
let toolkit
export default defineComponent({
name:"mindmap",
mounted() {
loadSurface(DEFAULT_VUE_SURFACE_ID, (s) => {
surface = s;
toolkit = surface.toolkitInstance;
})
},
methods:{
viewParams:function() {
return {
nodes:{
default:{
component:NodeComponent
}
}
}
},
renderParams:function() {
return {
layout:{
type:MindMapLayout.type,
options:{}
},
// set the size of elements from the width/height values in their backing data
useModelForSizes:true,
// on load, zoom the dataset so its all visible
zoomToFit:true
}
}
}
</script>
<template>
<div id="app">
<div class="jtk-demo-canvas">
<SurfaceComponent :renderOptions="this.renderParams()"
:viewOptions="this.viewParams()"
url="mindmap.json">
</SurfaceComponent>
<ControlsComponent/>
<MiniviewComponent/>
</div>
</div>
</template>
Svelte Mindmaps
JsPlumb's Svelte integration has everything you need to quickly build a Svelte Mindmap, and what's great is we've already pulled it all together for you in our Svelte Mindmap builder starter app
Our Svelte Mindmap uses several of JsPlumb's Svelte components, including the SurfaceComponent, ControlsComponent and MiniviewComponent, and it also takes advantage of JsPlumb's ability to support custom layouts.
<script>
const view = {
nodes:{
[DEFAULT]:{
component:NodeComponent
}
}
}
const renderOptions = {
layout:{
type:MindMapLayout.type,
options:{}
},
// set the size of elements from the width/height values in their backing data
useModelForSizes:true,
// on load, zoom the dataset so its all visible
zoomToFit:true
}
</script>
<div>
<SurfaceProvider>
<SurfaceComponent renderOptions={renderOptions}
viewOptions={view}
className="jtk-demo-canvas"
url="/mindmap.json">
<ControlsComponent/>
<MiniviewComponent/>
</SurfaceComponent>
</SurfaceProvider>
</div>



















