Back to jointjs.com.
Jump to other additions.
CHANGELOG
- Remove SVGPathSeg polyfill from the package.
-
Add MindMap Application
Visually organize information with this interactive mind mapping application (open the text editor just by typing, browse/add new ideas using the keyboard).

Video Dev Notes
-
Add RichTextEditor Application
Add or edit the content of your shapes with the
ui.TextEditoreditor and custom made rich-text toolbar containing a set of text formatting tools.
-
Add TeamOrder Application
Sort a list of people in a drag & drop fashion. Yet another use-case for the
ui.StackLayoutViewplugin (Kanban, Yamazumi).
-
Revamp Dijkstra Application
Switch between the edit and view mode using the latest
mvc.Listener.
-
Add editing capabilities to Sequence demo
Create roles with lifelines. Define the sequence of messages and lifespans.

-
Hide details when zoomed out in ELK demo
Shows how to hide/show graph components as the zoom changes.
-
ui.Clipboard - add
pasteCellsAtPointmethodAbility to paste the clipboard cells at a given point.
clipboard.pasteCellsAtPoint(graph, paper.clientToLocalPoint(evt.clientX, evt.clientY));
-
ui.Clipboard - allow passing custom model flags
Add
addCellOptionsandremoveCellOptionsoptions.graph.on('add remove', (cell, collection, flags) { if (!flags.ignore) { /* We're not interested in events that result from the commands below */ return; } /* ... */ }); // Paste will `add` new cells to the graph. clipboard.pasteCells(graph, { addCellsOptions: { ignore: true }}); // Cut will `remove` existing elements from the graph. clipboard.cutElements([el1, el2], graph, { removeCellsOptions: { ignore: true }});
-
ui.Clipboard - fix z-index assignment in
pasteCells()Make sure that the z-indexes of the pasted cells do not depend on the order in which they were copied. The z-indexes are generated based on the original
zattributes (in the same order, but on top of everything else). This is especially important for hierarchical diagrams.// Assert: Both examples must yield the same result. // Example 1: clipboard.copyElements([el1, el2], graph); clipboard.pasteCells(graph); // Example2: clipboard.copyElements([el2, el1], graph); clipboard.pasteCells(graph);
-
ui.ContextToolbar - add
anchorandscaleoptionsAdd options to control the position and size of the popup.
See the Pen JointJS+: ui.ContextToolbar - scale, anchor options by JointJS (@jointjs) on CodePen.
-
ui.ContextToolbar, ui.Dialog, ui.Lightbox, ui.Inspector, ui.Halo, ui.FlashMessage, ui.Popup, ui.Tooltip - trigger
closeeventProvide a way to know when the user closes UI plugins.
const graph = new dia.Graph({}, { cellNamespace: shapes }); const paper = new dia.Paper({ width: 400, height: 200, model: graph, cellViewNamespace: shapes, frozen: true }); graph.addCells([ new shapes.standard.Rectangle({ id: 'a', position: { x: 20, y: 120 }, size: { width: 80, height: 40 }, attrs: { label: { text: 'A' }} }), new shapes.standard.Rectangle({ id: 'b', position: { x: 300, y: 60 }, size: { width: 80, height: 40 }, attrs: { label: { text: 'B' }} }), new shapes.standard.Link({ source: { id: 'a' }, target: { id: 'b' } }) ]); const dialog = new joint.ui.Dialog({ width: 420, draggable: true, title: 'A dialog box with a diagram', content: paper.el }); // Prevent memory leaks by disposing of the paper when the dialog is dismissed dialog.on('close', () => paper.remove()); // NEW dialog.open(); paper.unfreeze();
-
ui.Dialog - prevent 3rd party libraries overriding visibility
Addresses issues such as the lightbox not showing when exporting to PNG in the Kitchen Sink application when a 3rd party CSS library was also used - which hid the
.modalclass by default.// Bootstrap CSS .modal { display: none; } // JointJS+ .joint-dialog.modal { display: block; }
-
ui.FreeTransform - fix rotation on touch devices
Fix an issue with the initial angle at the start of each rotation handle interaction on touch devices.
-
ui.Halo - fix memory leak while validating forking
Halo tries to check if the connection between the element and its clone is valid. To do this, it creates a clone view that has not been deleted after the check.
-
ui.Halo -
cloneoption can duplicate nested cellsThe
clone&forkhandles can now clone elements along with their descendants.See the Pen JointJS+: ui.ContextToolbar - open at position & scale and anchor options by JointJS (@jointjs) on CodePen.
-
ui.Inspector - update focus behavior, add
focusField()option and methodImprove how the inspector fields are focused when a new list item is added. Add option to define focus behavior for custom fields.
-
ui.Keyboard - add
filteroption, addisConsumerElement()methodBy default, the
KeyboardEventevent dispatched on input elements (INPUT, SELECT, TEXTAREA, contenteditable) is consumed and therefore no registered keyboard callback is invoked. To override this behavior, a new option has been added.const keyboard = new joint.ui.Keyboard({ filter: (evt, keyboard) => { if (toolbar.el.contains(evt.target)) { // Keep triggering keyboard callbacks // if the active element e.g. HTMLInputRange with focus // is part of the toolbar return true; } // Default behavior return !keyboard.isConsumerElement(evt); } });
-
ui.Selection - add
contextmenuactionAdding a missing
contextmenuaction and unify the definition of theui.Haloandui.Selectionhandles.type EventHandler = (evt: dia.Event, x: number, y: number) => void; // Used by both ui.Halo and ui.Selection interface HandleEvents { pointerdown?: string | EventHandler; pointermove?: string | EventHandler; pointerup?: string | EventHandler; contextmenu?: string | EventHandler; }
-
ui.Selection - fix lasso coordinates while scrolling
Keep the lasso frame under the pointer regardless of how the user scrolls the paper container.
-
ui.Selection - add
translateConnectedLinksoptionAdd an option to control how the connected links are translated. Implements more intuitive behavior, denoted by the
ui.Selection.ConnectedLinksTranslation.SUBGRAPHtype, where only those links whose both ends are connected to one of the selected element are translated.
-
ui.Selection - links can be now added to selection collection.
Explicitly add a link to the collection and rotate and resize it using the selection handles.
-
ui.Selection - add
preserveAspectRatiooptionAdd the option to keep the original width and height ratio of the selection bounding box when resizing.
-
ui.Snaplines - add
enable(),disable(),isDisabled()APIAdd methods to manage the active state of snaplines.
const snaplines = ui.Snaplines({ /* ... */ }); // Snap elements to other elements snaplines.enable(); // Don't snap snaplines.disable();
-
ui.Stencil - automatically disable the snaplines
Improve the
ui.Stencilintegration withui.Snaplines. The template does not attempt to snap elements if the corresponding snaplines instance is disabled.
-
ui.TextEditor - various improvements for better text editing
- fix cursor position in Firefox
- fix invisible cursor in
darkandmaterialtheme - fix
textareafocus after initialization - fix
textareaposition before focus (HTMLElement.focus() scrolls to an element that is not in the view by default, it is important to specify the position of the text box beforehand) - add missing theme styles
urlAnnotationcan be defined as a function- improve URL detection for
annotateUrlsoption - the appearance of the cursor reflects the current annotation
-
ui.TextEditor - add
onOutsidePointerdownoption callbackUse this new option to define what should happen when the user clicks outside the edited text.
ui.TextEditor.edit(textNode, { /* ... */ onOutsidePointerdown: (evt: PointerEvent) => { saveChanges(); ui.TextEditor.close(); } })
-
ui.TextEditor - add
onKeydownoption callbackDefine a special function for certain keys when editing text.
ui.TextEditor.edit(textNode, { /* ... */ onKeydown: (evt: KeyboardEvent) => { const { code, shiftKey } = evt; switch (code) { case 'Enter': { if (shiftKey) break; // `Enter` saves the changes and closes the text editor evt.stopPropagation(); TextEditor.close(); saveChanges(); break; } case 'Escape': { // `Escape` cancels the changes and closes the text editor evt.stopPropagation(); TextEditor.close(); cancelChanges(); break; } case 'Tab': { // Disable the `Tab` key while the text editor is open evt.preventDefault(); break; } } } })
-
ui.TextEditor - add
normalizeAnnotations(),getCombinedAnnotationAttrsBetweenIndexes()andgetCombinedAnnotationAttrsAtIndex()static methodsAdding useful functions to make working with annotations easier. Whether you need to see what annotations exist under the current text selection or add annotations without worrying about duplicates, these functions will help.
-
ui.TextEditor - add
isLineStart(),isLineEnding(),isEmptyLine()static methodsAdd utility functions to identify whether a character at a given position is the beginning of a line, the end of a line, or whether the character indicates an empty line.
-
ui.TextEditor - add
updateCaret()andstartSelecting()methods.Call
updateCaret()when the container transformation or current annotation changes and the caret visualization needs to be updated.Call
startSelecting()to initiate the text mouse selection.
-
ui.Tooltip - add
show(),hide(),toggle()andisVisible()methodsAdd methods for programmatically displaying tooltips on the target
Elementor at a specified position.const tooltip = new joint.ui.Tooltip({ content: 'Tooltip', target: '[data-tooltip]' }); // show the tooltip on `.my-element` with `[data-tooltip]` attribute tooltip.show({ target: document.querySelector('.my-element') }) // show the tooltip at given position tooltip.show({ x: 100, y: 100 });
-
ui.TreeLayoutView - add
enable(),disable(),isDisabled() APIAdd methods to manage the active state of the tree layout view.
const treeView = ui.TreeLayoutView({ /* ... */ }); // In `edit` mode treeView.enable(); // In `view` mode treeView.disable();
-
ui.TreeLayoutView - add additional info to
validateConnectioncallbackAdd more information to
validateConnectionto assess whether the candidate connection is valid or not.new ui.TreeLayoutView({ validateConnection: (child, parent, treeLayoutView, details) => { const { // Does the last sibling have a different meaning than the other siblings? // Do you need to prevent other elements from being dropped after the last sibling? siblingRank, siblings, // Do you need to limit the height of the tree? level, // Do you need to allow connections in a certain direction only? direction } = details; /* your logic */ } })See how to prevent elements from being dropped after add buttons:
See the Pen JointJS+: ui.TreeLayoutView - tree with add buttons by JointJS (@jointjs) on CodePen.
-
ui.PaperScroller - fix
scrollWhileDraggingpaddingoptionFix the way non-uniform
paddingchanges the area that determines when to start auto-scrolling.
-
ui.PaperScroller - support RTL direction
Fix scrolling when the directionality of the text is set to right-to-left (typical for Arabic languages and Hebrew)
-
ui.PathDrawer & ui.PathEditor - replace SVGPathSeg API with Geometry
Use g.Path from our Geometry library to replace deprecated
SVGPathSegandSVGPathSegListAPI.
-
ui.Popup - add
scale,position,anchorandarrowPositionoptionsAdd options to control the position and size of the popup, and the direction of the arrow.
paper.on('element:mouseenter', function(elementView) { const popup = new joint.ui.Popup({ content: `ID: ${elementView.model.id}`, target: elementView.el, position: 'top', scale: paper.scale().sx }); popup.render(); });See the Pen JointJS+: ui.Popup - position, scale, anchor, arrowPosition options by JointJS (@jointjs) on CodePen.
-
ui.widgets - add
<input type=color/>widgetAdd native color picker available in the
ui.Toolbar.See the MindMap example.
-
shapes.bpmn - re-use participant SVGTextElements in
Choreographyto allow text editingMaintain the same SVGTextElement in
bpmn.Choreographythroughout updates to allow text editing.
-
shapes.standard - fix automatic resize after item removal in
RecordIf an item is removed from a record, the record height must be updated to reflect the new number of items (this was broken when the scrollbar feature was introduced).
-
shapes.vsm - add
Value Stream Mappingset of shapesA new set of shapes that comes as a separate installable module.
{ "dependencies": { "jointjs": "3.6.2", "@joint/vsm-shapes": "file:joint-vsm-shapes.tgz" } }import { dia, shapes as defaultShapes } from 'jointjs'; import * as VSMShapes from '@joint/vsm-shapes'; const shapes = { ...VSMShapes, ...defaultShapes }; const graph = new dia.Graph({}, { cellNamespace: shapes }); const paper = new dia.Paper({ model: graph, cellViewNamespace: shapes, /* ... */ }); graph.addCell(new shapes.VSMTruck()); graph.addCell({ type: 'VSMTruck' });
-
dia.Paper - support
pointerdblclickon touch devicesPrior to this change the
cell:pointerdblclickwas not triggered when the user double tapped a cell.
-
dia.Paper - consistent firing of
contextmenueventThe
blank:contextmenuevent is always fired on themousedownevent, regardless of the different implementations of thecontextmenuevent.
-
dia.Paper - fix memory leak (layers not being removed)
Properly remove Layer views when disposing of paper.
-
dia.Paper - trigger
paper:panandpaper:pincheventsThe paper now triggers two new events on touchpad / trackpad devices.
It triggers
paper:panevent when a pan event is emitted while the pointer is on the paper. Pan events are similar tomousewheelevents, but whilemousewheelevents cover only the Y axis, pan events cover both X and Y axis.It triggers
paper:pinchevent when a pinch event is emitted while the pointer is on the paper (blankorcell). Event values and frequencies are normalized to work similarly in all browsers.You can try this here.
-
dia.Paper - add
snapLinksSelfoption to snap arrowheads to the anchor and verticesSee the Pen snapLinksSelf by JointJS (@jointjs) on CodePen.
-
dia.ElementView -
findPortNode()search for all port nodesThe method previously did not allow you to retrieve SVGElements from port labels.
const port1LabelEl = el.findPortNode('port1', 'portLabel'); // Assert: `port1LabelEl` is an SVGElement if it exists.
-
dia.ElementView - add
portRootandlabelRootselectorsThere was no selector referring to the implicit
<g>SVGElement that wraps ports (or port labels) with more than one node.- Added
portRootselector pointing to the wrapper of the port body - Added
labelRootselector pointing to the wrapper of the port label
If ports are defined using JSON markup in this way:
[{ tagName: 'circle', selector: 'circle1', attributes: { r: 10 } }, { tagName: 'circle', selector: 'circle2', attributes: { r: 14 } }]It is internally wrapped with an SVGGElement with a
portRootselector.[{ tagName: 'g', selector: 'portRoot', // NEW children: [{ tagName: 'circle', selector: 'circle1', attributes: { r: 10 } }, { tagName: 'circle', selector: 'circle2', attributes: { r: 14 } }] }] - Added
-
dia.ElementView - fix
getNodeMatrix()andgetNodeBBox()for elements with rotatable groupReturn the correct bounding box and transformation matrix for nodes within the rotatable group.
-
dia.Cell - throw exception on embedding of an embedded cell
An exception is thrown on attempt to embed a cell more than once to prevent an invalid graph state.
-
dia.HighlighterView - add
removeAll()static methodA convenient way to remove all highlighters (of a certain type or id) at once.
dia.HighlighterView.add(elementView1, 'body', 'id1'); dia.HighlighterView.add(elementView1, 'body', 'id2'); dia.HighlighterView.add(elementView2, 'body', 'id1'); dia.HighlighterView.add(elementView3, 'body', 'id1'); // Would remove all 4 highlighters. dia.HighlighterView.removeAll(paper); // Would remove all 3 highlighters with id `id1`. // The `id2` highlighter would remain on `elementView1`. dia.HighlighterView.removeAll(paper, 'id1');
-
dia.HighlighterView - add
UPDATE_ATTRIBUTESpropertyDefine a new highlighter that automatically updates on a model change with ease.
See the Pen HighlighterView UPDATE_ATTRIBUTE by JointJS (@jointjs) on CodePen.
-
elementTools.HoverConnect - new connection tool displayed on mouseover
A connection button that appears at the mouse cursor position when the element is hovered over.
See the Pen JointJS: Hover Link Connect Tool by JointJS (@jointjs) on CodePen.
-
elementTools.Button - enable
calc()expression forxandyconst button = new elementTools.Button({ x: 'calc(w + 10)', y: 10 });
-
elementTools & linkTools - add
scaleoptionAn option to make changing the size of the tools easier.
See the Pen Tool scale option by JointJS (@jointjs) on CodePen.
-
linkTools.HoverConnect - new connection tool displayed on mouseover
A connection button that appears at the mouse cursor position when the link is hovered over.
See the Pen JointJS: Hover Link Connect Tool by JointJS (@jointjs) on CodePen.
-
linkTools.Connect - fix touch support
Fix the link tool throwing an exception when the user touches the button.
-
connectors.Curve - add
rotateoptionMake the
curveconnector rotate with the element it connects to.el.set('angle', 45); el.set('connector', { name: 'curve', args: { rotate: true // take the element's angle into account } });
-
layout.Port - enable the use of the
calc()expression for port positioningUse
calc()expressions to position individual ports (forxandyattributes). Take angle into account when defined on a port.const rectangle = new shapes.standard.Rectangle({ ports: { groups: { myGroup: { position: 'absolute', markup: [{ tagName: 'rect', attributes: { 'x': -10, 'y': -10, 'width': 20, 'height': 20, 'stroke-width': 2, 'fill': '#FFFFFF', 'stroke': '#000000' } }], } }, items: [{ group: 'myGroup', args: { x: 'calc(w+10)', // NEW y: 10, angle: 45 // FIXED } }, { group: 'myGroup', args: { x: 'calc(w+10)', // NEW y: 'calc(h-10)', // NEW } }] } });See the Pen JointJS: calc() expression in port layout by JointJS (@jointjs) on CodePen.
-
highlighters.list - add a new highlighter to render a dynamic list of icons
An easy way to add a list of images or status icons to an element. You can now enhance your existing shapes with dynamic content without touching the existing shapes.
Status Icons
See the Pen JointJS: dynamic status icons by JointJS (@jointjs) on CodePen.
Row / Column of Images
// Define a custom highlighter list. class IconsList extends highlighters.list { createListItem(imageSrc, { width, height }, currentItemNode) { let itemNode = currentItemNode; if (!itemNode) { // The item node has not been created yet itemNode = V('image', { preserveAspectRatio: 'xMidYMid', width, height, }).node; } // Update the item node itemNode.setAttribute('href', imageSrc); return itemNode; } } // Add highlighter to `el1` element. IconsList.add(el1.findView(paper), 'root', 'icons', { attribute: 'icons', position: 'top-left', margin: 5, // The distance from the top and left. size: 20, // The size of an icon gap: 5, // The gap between icons }); // Add 2 icons to the `el1` element. el1.set('icons', ['/images/1.svg', '/images/2.svg']);
-
highlighters.mask & highlighters.stroke - fix rotation and position for nodes within rotatable group
Fix elements containing the rotatable group where the action of highlighting a node within the group ended up with the wrong position and angle.
-
dia.attributes -
calc()expression supports divisionMake
calc()expression understand division to increase comfort and precision.// precision calc(0.3333 * w) // vs. calc(w / 3) // comfort calc(0.5 * w) // vs. calc(w / 2)
-
dia.attributes - enable
calc()expression ornullforwidthandheightintextWrapattributeUse
calcexpression intextWrapfor more flexible bounding box definition// leave 5 pixels margin around the text { textWrap: { width: 'calc(w - 10)', height: 'calc(h - 10)' }}Use
nullintextWrapto prevent wrapping in a given dimension// no limit to the text height { textWrap: { width: 'calc(w)', height: null }}
-
dia.attributes -
fontSizeandstrokeWidthusecalc()See the Pen Dynamic font-size by JointJS (@jointjs) on CodePen.
-
dia.attributes - prevent negative values for dimension attributes
The dimension attributes (
width,height,r,rx,ry) must be always greater than or equal to0. The browser throws"A negative value is not valid"otherwise. The result of thecalc()expression for the listed attributes is always valid.calc(w-20) // greater or equal to 0 when used with e.g. `width`.
-
dia.attributes - use cached segments subdivisions for calculating the connection stubs
Improve the performance of Bezier connections with stubs.
link.set('connector', { name: 'smooth' }); link.attr(['line', 'connection'], { stubs: 20 });
-
mvc.Listener - add new class for attaching multiple event listeners on multiple objects.
Add a low-level class that allows you to listen for events triggered by other objects and, if needed, remove them all at once.
const listener = new mvc.Listener(application); listener.listenTo(graph, 'add', (application, cell) => { /* ... */ }); listener.listenTo(paper, 'element:pointerclick', (application, elementView, evt) => { /* ... */ }); // Remove all of its registered callbacks listener.stopListening();See
ChatBot,MindMap,Dijkstraapplications source code to see how to use it.
-
util.svg - added tagged template for SVG parsing
A utility for converting an XML string markup to JSON markup. It can run once at startup and does not affect performance (as opposed to directly defining the shape’s markup as string XML markup).
An example of markup defined as JSON:
const color = 'red'; const markup = [{ tagName: "rect", selector: "body", attributes: { fill: color }]Now, some equivalent code using a tagged template:
const color = 'red'; const markup = util.svg`<rect @selector="body" fill="${color}"/>`See the Pen Tagged template literal by JointJS (@jointjs) on CodePen.
-
util.breakText - add
preserveSpacesoptionsAdd option to preserve the text spaces (avoid all consecutive spaces being deleted and replaced by one space, and preserve a space at the beginning and the end of the text) while text wrapping.
See the Pen Preserve spaces - textWrap by JointJS (@jointjs) on CodePen.
-
util.guid - can be called without an object argument
// Generate a global unique ID const id = util.guid(); // NEW // Generate global unique `id` for `obj` and store it as a property of the object const obj = {}; util.guid(obj); obj.id // A guid
-
util.getRectPoint - add utility to get a point on a rect from a given keyword
Unify the way to search for points on rectangles using keywords in different parts of the library.
const rect = new g.Rect(10, 20, 5, 5); const point = util.getRectPoint(rect, 'top-right'); // Assert: point === rect.topRight() === { x: 15, y: 20 }
-
Vectorizer -
text()reads empty line heights from annotationsEmpty lines can be annotated and the annotation of an empty line is taken into account when the
line-heightis calculated. Note that the empty line at the beginning of the text is annotated at(-1,0>.
Other Additions
-
@joint/decorators - support functions with multiple dependencies
{ "dependencies": { "jointjs": "3.6.2", "@joint/decorators": "0.2.0" } }
-
Demo Wednesday - our way of sharing and celebrating the results of our continuous work
-
Add Svelte Tutorial - Get started with JointJS+ and Svelte.
-
Add E2E Testing with Playwright Tutorial - Test your JointJS applications with microsoft Playwright