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.TextEditor
editor 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.StackLayoutView
plugin (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
pasteCellsAtPoint
methodAbility 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
addCellOptions
andremoveCellOptions
options.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
z
attributes (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
anchor
andscale
optionsAdd 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
close
eventProvide 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
.modal
class 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 -
clone
option can duplicate nested cellsThe
clone
&fork
handles 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
filter
option, addisConsumerElement()
methodBy default, the
KeyboardEvent
event 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
contextmenu
actionAdding a missing
contextmenu
action and unify the definition of theui.Halo
andui.Selection
handles.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
translateConnectedLinks
optionAdd an option to control how the connected links are translated. Implements more intuitive behavior, denoted by the
ui.Selection.ConnectedLinksTranslation.SUBGRAPH
type, 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
preserveAspectRatio
optionAdd 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.Stencil
integration 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
dark
andmaterial
theme - fix
textarea
focus after initialization - fix
textarea
position 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
urlAnnotation
can be defined as a function- improve URL detection for
annotateUrls
option - the appearance of the cursor reflects the current annotation
-
ui.TextEditor - add
onOutsidePointerdown
option 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
onKeydown
option 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
Element
or 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
validateConnection
callbackAdd more information to
validateConnection
to 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
scrollWhileDragging
padding
optionFix the way non-uniform
padding
changes 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
SVGPathSeg
andSVGPathSegList
API.
-
ui.Popup - add
scale
,position
,anchor
andarrowPosition
optionsAdd 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
Choreography
to allow text editingMaintain the same SVGTextElement in
bpmn.Choreography
throughout updates to allow text editing.
-
shapes.standard - fix automatic resize after item removal in
Record
If 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 Mapping
set 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
pointerdblclick
on touch devicesPrior to this change the
cell:pointerdblclick
was not triggered when the user double tapped a cell.
-
dia.Paper - consistent firing of
contextmenu
eventThe
blank:contextmenu
event is always fired on themousedown
event, regardless of the different implementations of thecontextmenu
event.
-
dia.Paper - fix memory leak (layers not being removed)
Properly remove Layer views when disposing of paper.
-
dia.Paper - trigger
paper:pan
andpaper:pinch
eventsThe paper now triggers two new events on touchpad / trackpad devices.
It triggers
paper:pan
event when a pan event is emitted while the pointer is on the paper. Pan events are similar tomousewheel
events, but whilemousewheel
events cover only the Y axis, pan events cover both X and Y axis.It triggers
paper:pinch
event when a pinch event is emitted while the pointer is on the paper (blank
orcell
). Event values and frequencies are normalized to work similarly in all browsers.You can try this here.
-
dia.Paper - add
snapLinksSelf
option 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
portRoot
andlabelRoot
selectorsThere was no selector referring to the implicit
<g>
SVGElement that wraps ports (or port labels) with more than one node.- Added
portRoot
selector pointing to the wrapper of the port body - Added
labelRoot
selector 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
portRoot
selector.[{ 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_ATTRIBUTES
propertyDefine 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 forx
andy
const button = new elementTools.Button({ x: 'calc(w + 10)', y: 10 });
-
elementTools & linkTools - add
scale
optionAn 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
rotate
optionMake the
curve
connector 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 (forx
andy
attributes). 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 ornull
forwidth
andheight
intextWrap
attributeUse
calc
expression intextWrap
for more flexible bounding box definition// leave 5 pixels margin around the text { textWrap: { width: 'calc(w - 10)', height: 'calc(h - 10)' }}
Use
null
intextWrap
to prevent wrapping in a given dimension// no limit to the text height { textWrap: { width: 'calc(w)', height: null }}
-
dia.attributes -
fontSize
andstrokeWidth
usecalc()
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
,Dijkstra
applications 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
preserveSpaces
optionsAdd 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-height
is 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