Skip to main content

JointJS+ changelog v4.0.0

We are delighted to announce version 4 of JointJS, and that JointJS open-source and JointJS+ now have no external dependencies! 🎉

Remove jQuery, backbone, and lodash dependencies from package.json

Since version 3.7.0, JointJS hasn't used Lodash internally. Building on this work, JointJS no longer uses jQuery and backbone. This finally allows us to remove these dependencies from our package.json, and provide our users with a leaner library without changing core functionality.

Details...

In version 3.7.0, Lodash was removed, and these utilities were replaced with our own code. Similarly, jQuery and backbone functionality is now handled with internal code under the mvc namespace.

jQuery, backbone, and lodash will no longer be installed when working with JointJS. If you are specifying these dependencies in a script tag, you can remove the script tags related to jQuery, backbone, and lodash.

<!DOCTYPE html>
<html>
<head> </head>
<body>
<div id="paper"></div>

<!--
Remove start
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.4.1/backbone.js"></script>
<!--
Remove end
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/@joint/core/4.0.0/joint.js"></script>

<script type="text/javascript">
const graph = new joint.dia.Graph();
const paper = new joint.dia.Paper({
el: document.getElementById('paper'),
model: graph,
// ...
});
// ...
</script>
</body>
</html>

format.BPMN - Enable import & export of Business Process Model and Notation (BPMN)

BPMN is a graphical representation for specifying business processes in a business process model. Along with the BPMN specification, an XML schema is also defined that allows a BPMN diagram to be imported from and exported to XML.

This new feature enables the import and export process within JointJS+.

Details...
fromBPMN(xmlDoc, options) function

This function handles the import process. It takes an XMLDocument containing a diagram and an options object. The inclusion of the options parameter provides the flexibility to customize the import process to meet specific requirements.

The import options object has the following 2 properties - cellFactories and bpmn2Shapes. They are a collection of cellFactories mapped by decisive XML elements, and a namespace that contains definitions of JointJS bpmn2Shapes respectively.

Basic usage

The most basic way to use the package is to convert an XMLDocument into JointJS cells by passing the document to the fromBPMN function without specifying the value of the cellFactories property in the options object.

The resulting cells are stored in the cells property of the import result object.

const importResult = fromBPMN(XMLDocument, {
cellNamespace: joint.shapes.bpmn2,
});

// graph.addCells(importResult.cells)
toBPMN(paper, options = {}) function

This function handles the export process. It takes a joint.dia.Paper containing a diagram and optionally the options object. The inclusion of the options parameter provides the flexibility to customize the export process to meet specific requirements.

Basic usage

The most basic way to use the package is to convert a paper with a diagram to XML without using an options parameter. It's as simple as calling toBPMN with the paper as a parameter.

const exportResult = toBPMN(paper);

// exportResult.cells instanceof XMLDocument

Add BPMN import & export feature to BPMNEditor application

gif-bpmn-editor

Live BPMNEditor app


Add ImageProcessor application

The Image Processor app allows users to easily manipulate images with filters and transformation tools in a node-based manner.

image-processor

Live ImageProcessor app


format.SVG - Add grid option to allow users to render the grid in the exported SVG


format.Raster - Add grid option to allow users to render the grid in the exported image


format.Print - Add grid option to allow users to display a grid on the print page



dia.Cell - Remove the Cell.parent(id) setter from type definitions and documentation

The Cell.parent(id) method is not intended to be used as a way to embed cells. To eliminate the confusion, it was decided to remove it from type definitions and documentation.

In case you were using the Cell.parent(cell) as a solution to embed cells, use the Cell.embed(cell) method instead.

const parent = joint.shapes.standard.Rectangle();
const child = joint.shapes.standard.Rectangle();
parent.embed(child);

linkTools.Vertices - A fix to trigger link:mouseleave event when the user stops dragging a vertex. The pointer is no longer over the vertex, and the redundancyRemoval option is disabled.


Breaking changes

jointjs dependency deprecated in favour of @joint/core - To utilize v4 of the JointJS library, install the @joint/core dependency instead of jointjs

Migration guide

If you have a stand-alone JointJS project, and you want to take advantage of the latest version of the open-source JointJS library, install the @joint/core dependency from the npm registry.

Previously:

npm i jointjs
yarn add jointjs

Version 4.0.0:

npm i @joint/core
yarn add @joint/core

jQuery removal - Remove jquery dependency

jQuery related functionality is now handled with internal code under the mvc namespace. The jQuery library was used as a starting point to create our own DOM utility. This new tool is for internal use only.

Migration guide
  • Drop support for jQuery selectors. Now only CSS3 selectors are recognized.
    • Attribute not equal selector (!=)
    • Positional selectors (:first, :eq(n), :odd, etc.)
    • Type selectors (:input, :checkbox, :button, etc.)
    • State-based selectors (:animated, :visible, :hidden, etc.)
    • :has(selector) in browsers without native support
    • :not(complex selector) in IE
    • custom selectors via jQuery extensions
    • Reliable functionality on XML fragments
    • Matching against non-elements
    • Reliable sorting of disconnected nodes
    • querySelectorAll bug fixes (e.g., unreliable :focus on WebKit)
    • Selectors can not start with combinators (e.g > div needs to be converted to :scope > div)
  • view.$el and view.$() are no longer public properties. Use view.el and view.el.querySelectorAll() instead.
  • Remove the following protected properties from the paper: paper.$document, paper.$grid and paper.$background.
  • utils.sortElement returns a plain array of Elements, not a $ object.
  • It's no longer possible to access CellView from a DOM element ($.data(document.querySelector('.joint-cell').view).
  • cellView.prototype.findBySelector() has been removed in favor of findNode(), findPortNode() and findLabelNode() (findNodes(), findPortNodes() and findLabelNodes() are used to find nodes referenced by a group selector).

Backbone removal - Remove backbone dependency

Backbone related functionality is now handled with internal code under the mvc namespace. You will now find mvc.Events, mvc.Model, mvc.View, and mvc.Collection in the JointJS library.

Migration guide
  • mvc.Model - All lodash related methods have been removed from mvc.Model, along with any methods related to Backbone.Sync functionality.

  • mvc.Collection - Most lodash related methods have been removed from mvc.Collection, along with any methods related to Backbone.Sync functionality. The following collection methods remain, and if possible, use native equivalents:

    • each
    • find
    • findIndex
    • filter
    • first
    • includes
    • isEmpty
    • last
    • map
    • reduce
    • sortBy
    • toArray

If your application uses Backbone.Sync, Backbone.Router, Backbone.History, or any of the behaviour mentioned above, you should take the necessary steps to replace this functionality in your application. The backbone dependency will no longer be installed when working with JointJS.


format.Print - Use native DOM over jQuery elements in ready function

In previous versions, pages were represented as a collection of jQuery objects. After the change, pages are now represented as a collection of HTMLDivElement objects.

Migration guide

Note the change in the ready function signature…

Previously:

const options = {
/**
* @param {Array<jQuery>} pages
* @param {function} readyToPrint
* @param {{sheetSizePx: {width: number, height: number, cssWidth: string, cssHeight: string}}} opt
*/
ready: function (pages, send, opt) {
pages.forEach(function ($page) {
// custom action - e.g. add a border on every page:
$page.css('border', '1px solid gray');
});

send(pages);
},
};

print(paper, options);

Version 4.0.0:

const options = {
/**
* @param {Array<HTMLDivElement>} pages
* @param {function} readyToPrint
* @param {{sheetSizePx: {width: number, height: number, cssWidth: string, cssHeight: string}}} opt
*/
ready: function (pages, send, opt) {
pages.forEach(function (pageEl) {
// custom action - e.g. add a border on every page:
pageEl.style.border = '1px solid gray';
});

send(pages);
},
};

print(paper, options);

layout.GridLayout - Remove deprecated GridLayout options

  • Deprecated options dx, dy and center were removed.
  • Added options - verticalAlign and horizontalAlign.
  • Default values for verticalAlign and horizontalAlign options are 'middle' now (previously it was 'top' and 'left' respectively).
Migration guide

dx and dy

Deprecated options dx and dy were removed in favor of rowGap and columnGap.

Note, that previously dx and dy were also having an impact on overall layout margin, so you may want to adjust marginX and marginY options accordingly.

Previously:

layout.GridLayout.layout(graph, {
/* ... */
dx: 10,
dy: 10,
marginX: 10,
marginY: 10,
});

Version 4.0.0:

layout.GridLayout.layout(graph, {
/* ... */
columnGap: 10,
rowGap: 10,
marginX: 20,
marginY: 20,
});

center

Deprecated center option was removed in favor of verticalAlign and horizontalAlign. Previously center was true by default, so to keep the default behaviour, the same default values of verticalAlign and horizontalAlign options were changed to middle.

If you didn't specify the center option in your layout, the change has no impact on you.

If you were using center: false without setting verticalAlign and horizontalAlign options, you might want to specify their values as top and left respectively to preserve old behaviour.

Previously:

layout.GridLayout.layout(graph, {
/* ... */
center: false,
});

Version 4.0.0:

layout.GridLayout.layout(graph, {
/* ... */
verticalAlign: 'top',
horizontalAlign: 'left',
});

shapes.bpmn - Remove shapes.bpmn from source code and JointJS+ package

Redefine bpmn.Choreography, and make it available through the bpmn2 namespace.

Migration guide

The bpmn2 namespace should be used in place of shapes.bpmn.


ui.Inspector - labels can be arbitrary HTML (not only a string) regardless of the type

The labels are always parsed as HTML no matter what type of field they belong too.

Previously, only select-box, select-button-group and color-palette were parsed as HTML.

Previously:

ui.Inspector.create('.inspector', {
/* ... */
inputs: {
// interpreted as a string ("<b>1</b>" text was displayed)
attribute1: { type: 'text', label: '<b>1</b>' },
// interpreted as HTML (bold number was displayed)
attribute2: { type: 'select-box', label: '<b>2</b>' },
},
});

Version 4.0.0:

ui.Inspector.create('.inspector', {
/* ... */
inputs: {
attribute1: { type: 'text', label: '<b>1</b>' }, // is interpreted as HTML
attribute2: { type: 'select-box', label: '<b>2</b>' }, // is interpreted as HTML
},
});
Migration guide

If reserved HTML characters were intentionally used, they must now be replaced by entities. See MDN for more info.

Previously:

ui.Inspector.create('.inspector', {
/* ... */
inputs: {
attribute: { type: 'text', label: '<label>' },
},
});

Version 4.0.0:

ui.Inspector.create('.inspector', {
/* ... */
inputs: {
attribute: { type: 'text', label: '&lt;label&gt;' },
},
});

ui.PaperScroller - The scroll() method now accepts local coordinates

Fixed incorrect behaviour of the scroll() method in PaperScroller. Previously, the scroll() method didn't use the scale factor when calculating scroll value.

If you were using some scale adjustments before invoking the scroll() method, you should now pass unscaled coordinates to the scroll() method.

Migration guide

Previously:

const localPoint = new g.Point(1000, 0); // untransformed coordinates, the same as graph coordinates
const scale = paperScroller.zoom(); // current zoom level
const scrollPoint = localPoint.scale(scale, scale);
paperScroller.scroll(scrollPoint.x, scrollPoint.y);

Version 4.0.0:

const localPoint = new g.Point(1000, 0); // untransformed coordinates, the same as graph coordinates
paperScroller.scroll(localPoint.x, localPoint.y);

ui.PaperScroller - A new function to be used instead of $.fn.animate which no longer supports scroll animation

Migration guide
  • The opt.animation in scroll functions no longer accepts parameters of jQuery's animate method.
  • The opt.animation now consists of optional properties:
    • duration - duration of the animation in milliseconds
    • timingFunction - the jQuery's string syntax has been replaced with a callback function that is called on every frame of the animation
    • complete - this remains the same, a callback function executed after the animation is complete

Previously:

paperScroller.scroll(100, 100, {
animation: {
duration: 'slow',
easing: 'linear',
},
});

Version 4.0.0:

paperScroller.scroll(100, 100, {
animation: {
duration: 600,
timingFunction: (t) => t,
},
});

ui.Stencil - fix layout defaults

  • Previously when the layout option was provided as an object, the passed object was merged with the default GridLayout options. Now, the layout object will be used as it is for the purpose of the layout.GridLayout plugin. The default GridLayout options will be used only when using layout: true, or another truthy value.
Migration guide

If you used your custom layout function, or were using layout: true, the change has no impact on you.

Previously, if you specified the layout option as an object, the values would be merged with the default ui.Stencil layout options. Now, the options will be used as it is, without merging. Because of this, now you shouldn't reset the layout defaults as it was done previously.

Previously:

layout: {
columns: 2,
marginX: 10,
marginY: 10,
columnGap: 10,
columnWidth: 100,
// reset defaults
resizeToFit: false,
dx: 0,
dy: 0
}

Version 4.0.0:

layout: {
columns: 2,
marginX: 10,
marginY: 10,
columnGap: 10,
columnWidth: 100,
rowHeight: 80 // old definition relied on this default value from ui.Stencil
}

Also, if you relied on the default layout options from ui.Stencil, you should explicitly specify them in your layout object. The default merged layout object was as follows:

{
columnWidth: this.options.width / 2 - 10,
columns: 2,
rowHeight: 80,
resizeToFit: true,
dy: 10, // corresponds to `rowGap: 10, marginY: 10`
dx: 10 // corresponds to `columnGap: 10, marginX: 10`
}

If you relied on one of these properties you should explicitly specify them.

Previously:

const width = 200;

const stencil = new ui.Stencil({
/* ... */
width,
layout: {
marginY: 70,
columns: 1,
rowHeight: 'compact',
dx: 20,
dy: 10,
resizeToFit: false,
},
});

Version 4.0.0:

const width = 200;

const stencil = new ui.Stencil({
/* ... */
width,
layout: {
marginY: 80,
marginX: 20,
columns: 1,
columnWidth: width / 2 - 10,
rowHeight: 'compact',
columnGap: 20,
rowGap: 10,
},
});

ui.TreeLayoutView - Plugin no longer changes paper interactivity

Previously, the plugin was disabling the paper interactivity for all views.

This change ensures that the default interaction is prevented only for elements being dragged in the tree.

  • The canInteract(elementView, evt) callback now receives evt as the second parameter.
Migration guide

If you didn't use the canInteract() callback, the change has no impact on you.

Previously:

const paper = new dia.Paper({
/* ... */
});

/* ... */

const treeView = new ui.TreeLayoutView({
paper,
model: treeLayout,
canInteract: function (elementView) {
return elementView.model.get('disabled');
},
});

Version 4.0.0:

const paper = new dia.Paper({
/* ... */
interactive: false, // prevent the movement of `disabled` elements
});

/* ... */

const treeView = new ui.TreeLayoutView({
paper,
model: treeLayout,
canInteract: function (elementView) {
return elementView.model.get('disabled');
},
});

versionRappid - versionRappid property has been renamed to versionPlus

Migration guide

Previously:

console.log(joint.versionRappid); // => '3.7.7'

Version 4.0.0:

console.log(joint.versionPlus); // => '4.0.0'

@types - Remove dia.Paper format functions, and dia.Graph util functions

Migration guide

Use joint.format and joint.graphUtils namespaces instead.

  • If you were using paper.toSVG(callback, options), use joint.format.toSVG(paper, callback, options) instead, etc.
  • The same is applies to dia.Graph. Instead of graph.shortestPath(source, target, options), use joint.graphUtils(graph, source, target, options).

In light of these changes the types were moved to their corresponding namespaces

  • format.Print, format.Raster, format.SVG

    • Types have been moved from dia.Paper namespace to format namespace.
  • graph-utils

    • Types related to graph-utils from dia.Graph namespace have been removed.

format.Print, format.Raster, format.SVG

If you were using types for format plugins from the dia.Paper namespace, they are now accessible from the the format namespace instead.

Previously:

import { dia } from '@joint/plus';

const options: dia.Paper.RasterExportOptions;

Version 4.0.0:

import { format } from '@joint/plus';

const options: format.RasterExportOptions;

Here is a full list of changed types:

  • dia.Paper.PrintActions -> format.PrintActions
  • dia.Paper.PrintUnits -> format.PrintUnits
  • dia.Paper.PrintExportOptions -> format.PrintExportOptions
  • dia.Paper.BeforeSerialize -> format.BeforeSerialize
  • dia.Paper.SVGExportOptions -> format.SVGExportOptions
  • dia.Paper.CanvasExportOptions -> format.CanvasExportOptions
  • dia.Paper.RasterExportOptions -> format.RasterExportOptions

graph-utils

If you were using types for graph-utils from the dia.Graph namespace, they are now accessible from the graphUtils namespace instead.

Previously:

import { dia } from '@joint/plus';

const config: dia.Graph.ConstructTreeConfig;

Version 4.0.0:

import { graphUtils } from '@joint/plus';

const config: graphUtils.ConstructTreeConfig;

Here is a full list of changed types:

  • dia.Graph.ShortestPathOptions -> graphUtils.ShortestPathOptions
  • dia.Graph.ConstructTreeNode -> graphUtils.ConstructTreeNode
  • dia.Graph.ConstructTreeConfig -> graphUtils.ConstructTreeConfig

dia.Graph - Throw exception when cell constructor not found

Throws an exception when a new cell is to be created from JSON if a type does not refer to a constructor.

This is to solve the recurring issue with dia.ElementView: markup required.

  • The dia.ElementView: markup required error is thrown when a cell constructor is found, but the model defines no markup.

    const MyElement = dia.Element.define('MyElement');
    const graph = new dia.Graph({}, { cellNamespace: { MyElement } });
    graph.addCell({ type: 'MyElement' });

    const paper = new dia.Paper({ model: graph, frozen: true });
    paper.unfreeze(); // throws `dia.ElementView: markup required`
  • The new dia.Graph: Could not find cell constructor for type: 'MyElement'. Make sure to add the constructor to 'cellNamespace'. is thrown when the graph can not find a constructor for given cell type (e.g. when calling graph.fromJSON()).

    const MyElement = dia.Element.define('MyElement');
    const graph = new dia.Graph(
    {},
    {
    cellNamespace: {
    /* MyElement is not defined here */
    },
    }
    );
    graph.addCell({ type: 'MyElement' }); // throws `dia.Graph: Could not find cell constructor...`
Migration guide

Previously:

graph.addCell(new dia.Element()); // throws `dia.Graph: cell type must be a string.`
graph.addCell(new dia.Link()); // the link was successfully added to the graph

Version 4.0.0:

graph.addCell(new dia.Element()); // throws `dia.Graph: cell type must be a string.`
graph.addCell(new dia.Link()); // throws `dia.Graph: cell type must be a string.`

If you only work with models, and don't intend to draw links or use a custom link view, you can add a type to the link.

graph.addCell(new dia.Link({ type: 'link' })); // the link was successfully added to the graph

In other scenarios, please create a custom link (dia.Link.define('MyLink', /* ... */)), or use built-in links such as standard.Link.


dia.Paper - Change the default cell sorting to APPROX type

Details...

You can revert the change by setting the paper sorting option back to dia.Paper.sorting.EXACT.

paper.options.sorting = dia.Paper.sorting.EXACT;

However, it's recommended to refrain from using EXACT sorting for performance reasons, and due to the existence of this issue.


Migration guide

Instead of using perpendicularLinks: true, set the defaultAnchor paper option to the perpendicular connection point.

paper.options.defaultAnchor = { name: 'perpendicular' };

dia.Paper, util - Remove deprecated dia.Paper.prototype.options.linkConnectionPoint, and deprecated util.shapePerimeterConnectionPoint

Migration guide

Instead of using linkConnectionPoint: util.shapePerimeterConnectionPoint, set the defaultConnectionPoint paper option to boundary connection point.

paper.options.defaultConnectionPoint = { name: 'boundary' };

dia.Paper - Change the value of the defaultConnectionPoint option to boundary to make the diagrams more visually appealing by default.

Migration guide

To use the bbox connection point set the defaultConnectionPoint paper option as shown below:

new dia.Paper({
/* ... */
defaultConnectionPoint: { name: 'bbox' },
});

dia.Paper - Add SVG grid layer

The grid is now rendered as an SVG document inside the paper layer.

  • Paper transformations are applied to it in the same way as to cells, i.e. the grid does not need to be completely redrawn when the paper is transformed (unlike the previous solution with a HTMLDivElement CSS background).
  • The grid can now also be easily exported as SVG/PNG.
Migration guide

API change. Drop drawGrid() and clearGrid() methods.

Previously:

// show the grid
paper.setGrid('mesh');
paper.drawGrid();

// remove the grid
paper.clearGrid();

// change color of grid
const paper = new dia.Paper({ drawGrid: { name: 'dots', color: 'red' }});
paper.drawGrid({ color: 'blue' });

// paper.options.drawGrid value
const paper = new dia.Paper({ drawGrid: { name: 'dots' }});
paper.drawGrid({ name: 'mesh; }); // paper.options.drawGrid === { name: 'dots' }

Version 4.0.0:

// show the gird
paper.setGrid('mesh');

// remove the grid
paper.setGrid(null);

// change color of grid
const paper = new dia.Paper({ drawGrid: { name: 'dots', color: 'red' } });
paper.setGrid({ name: 'dots', color: 'blue' });

// paper.options.drawGrid value
const paper = new dia.Paper({ drawGrid: { name: 'dots' } });
paper.setGrid({ name: 'mesh' }); // paper.options.drawGrid === { name: 'mesh' }

Unless you somehow rely on the structure of a paper SVG document, this change shouldn't affect you in any way. The paper is missing <div class="joint-paper-grid"/>, and instead has a new layer <g class="joint-grid-layout" />.


dia.Paper - Transformation events improved

Improvements to the current transformation API.

  • Remove outdated methods:

    • origin option removed
    • setOrigin() method removed
    • rotate() (experimental) method removed
  • Introducing a new transform event which is triggered after both scale and translate are finished.

  • Allows passing custom data along with transformation events (similarly to mvc.Events).

    • scale(sx, sy, [data])
    • translate(tx, ty, [data])
    • matrix(matrix, [data])

    paper.on('scale', (sx, sy, data) => console.log(sx, sy, data.foo));
    paper.on('translate', (tx, ty, data) => console.log(tx, ty, data.foo));
    paper.on('transform', (matrix, data) => console.log(matrix, data.foo));

    paper.matrix({ a: 2, b: 0, c: 0, d: 2, e: 10, f: 10 }, { foo: 'bar' }); // will trigger all events
  • In addition, it allows passing custom data along with the resize event.

    paper.on('resize', (width, height, data) =>
    console.log(width, height, data.foo)
    );
    paper.setDimensions(100, 200, { foo: 'bar' });
  • Simplifying the "zooming under pointer" functionality by:

    • Adding a new method scaleUniformAtPoint(scale, { x, y }, [data])
    • scale() no longer accepts scaling origin
    • fixing the original scaling at point logic (taking the previous scale into account)
Migration guide

dia.Paper.prototype.options.origin option removed

Note that passing origin to the dia.Paper constructor had no effect in version 3.7. If you read the values of origin from the paper options, use paper.translate() (or paper.matrix()) instead.

Previously:

const { x, y } = paper.option.origin;

Version 4.0.0:

const { tx: x, ty: y } = paper.translate();

Paper scale event handler arguments changed

Previously:

paper.on('scale', (sx, sy, ox, oy) => {});

Version 4.0.0:

paper.on('scale', (sx, sy, data) => {
const { tx: ox, ty: oy } = paper.translate();
});

dia.Paper.prototype.setOrigin method removed

Previously:

paper.setOrigin(100, 200);

Version 4.0.0:

paper.translate(100, 200);

scale() no longer accepts scaling origin

Previously:

// Zoom at center of the paper
const center = paper.getArea.center();
paper.translate(0, 0);
paper.scale(zoomLevel, zoomLevel, center.x, center.y);

Version 4.0.0:

// Zoom at center of the paper
const center = paper.getArea.center();
paper.scaleUniformAtPoint(zoomLevel, center);

Paper panning and zooming can be implemented simply as shown below:

paper.on('paper:pinch', function (evt, x, y, sx) {
const { sx: sx0 } = paper.scale();
paper.scaleUniformAtPoint(sx0 * sx, { x, y });
});

paper.on('paper:pan', function (evt, tx, ty) {
evt.preventDefault();
const { tx: tx0, ty: ty0 } = paper.translate();
paper.translate(tx0 - tx, ty0 - ty);
});

dia.Paper - Fix paper:pinch dispatched event type

For consistency, all events fired on paper are passed native events wrapped in the mvc.$.Event wrapper.

Migration guide

Previously:

paper.on('paper:pinch', (evt) => console.log('this is a native event', evt));

Version 4.0.0:

paper.on('paper:pinch', (evt) =>
console.log('this is a native event', evt.originalEvent)
);

dia.Cell - Add mergeArrays options to constructor

The class array default attributes are now overridden instead of merged. A new option to support the previous behavior was added.

Details...

The reason for this change is to make it easier to instantiate the class, and define the initial attributes, which are arrays.

const MyRect = joint.shapes.standard.Rectangle.define('Rect', {
array: [1, 2],
});

const rect1 = new MyRect({ array: [3] });
console.log(rect1.get('array')); // [3] array was overridden

const rect2 = new MyRect({ array: [3] }, { mergeArrays: true });
console.log(rect2.get('array')); // [3,2] array was merged

dia.CellView - Early evaluation of calc attributes

Perform the following actions at the start of the cell view update:

  • evaluate the calc expressions used in the presentation attributes
  • translate (e.g. to dash-case) the presentation attribute names

This way, their evaluated value, and their true name become available for anyone dealing with them later (it's no longer needed to evaluate the calc() value multiple times or check for the existence of an attribute under different names).

Migration guide

This is only a breaking change if you define custom attributes (undocumented yet).

Previously:

const MyElement = dia.Element.define(
'MyElement',
{
root: {
myAttribute: 5,
},
},
{
/* prototype */
},
{
/* static */
attributes: {
// the name must match the attribute as used on the model `root/myAttribute`.
myAttribute: {
set: function (value, bbox, node, attrs) {
return (
value +
(attrs['myOtherAttribute'] ||
attrs['my-other-attribute'])
);
},
},
},
}
);

Version 4.0.0:

const MyElement = dia.Element.define(
'MyElement',
{
root: {
myAttribute: 5, // could be dash-cased or camel-cased
},
},
{
/* prototype */
},
{
/* static */
attributes: {
// must be dash-cased (more precisely, it must be the true name returned by `V.attributeNames`)
'my-attribute': {
set: function (value, bbox, node, attrs) {
return value + attrs['my-other-attribute']; // always `true` name
},
},
},
}
);

dia.CellView - Disable useCSSSelectors by default

Use of CSS selectors within the model's attrs is now disabled by default.

Migration guide

Previously:

joint.dia.Element.define(
'Rectangle',
{
attrs: {
'.rectangle': {
// CSS Selector for the <rect /> element
fill: 'red',
},
},
},
{
markup: '<rect class="rectangle"/>',
}
);

Version 4.0.0 (quick fix - per shape):

joint.dia.Element.define(
'Rectangle',
{
attrs: {
'.rectangle': {
// CSS Selector for the <rect /> element
fill: 'red',
},
},
},
{
markup: '<rect class="rectangle"/>',
useCSSSelectors: true,
}
);

Version 4.0.0 (quick fix - global):

joint.config.useCSSSelectors = true;

joint.dia.Element.define(
'Rectangle',
{
attrs: {
'.rectangle': {
// CSS Selector for the <rect /> element
fill: 'red',
},
},
},
{
markup: '<rect class="rectangle"/>',
}
);

Version 4.0.0 (recommended fix):

joint.dia.Element.define(
'Rectangle',
{
attrs: {
rectangle: {
// JSON Selector for the <rect /> element
fill: 'red',
},
},
},
{
markup: util.svg`<rect @selector="rectangle" />`,
}
);

Legacy attributes in the left colum have now been replaced with the attributes in the right column.

PreviouslyVersion 4.0.0
yAlignmenttextVerticalAnchor
refXx
refYy
refWidthwidth
refHeightheight
Migration guide

If you were changing any of the label legacy attributes (left-side) in your application, you should change it to its new form (right-side).


Migration guide

Previously:

link.set('smooth', true);
link.set('manhattan', true);

Version 4.0.0:

link.connector('smooth'); // or link.set('connector', { name: 'smooth' });
link.router('orthogonal'); // or link.set('router', { name: 'orthogonal' });

dia.LinkView - Remove support for legacy features of joint.dia.LinkView. (It has long been deprecated due to poor performance caused by built-in link tools that had to be rendered for each link at start-up even though they are only visible when the user hovers over them.)

Details...
  • joint.dia.Link has no markup defined, and effectively becomes an abstract class
  • drop support for semantic string markup (connection, connection-wrap, marker-source, marker-target, marker-vertices, marker-arrowheads, and link-tools in the markup no longer affect user interactivity)
  • drop support for vertexAdd, vertexRemove, vertexMove, arrowheadMove, and useLinkTools interactivity paper options
  • the following protected methods have been removed for LinkView: dragConnectionStart, dragConnection, dragConnectionEnd, dragVertexStart, dragVertex, dragVertexEnd
  • the paper defaultLink is now standard.Link

The linkTools.Vertices tools now accept 2 new options: vertexAdding and vertexRemoving

Migration guide

Switch to joint.shapes.standard.Link, and add link tools dynamically.

For the interactive options:

  • vertexAdd, vertexRemove & vertexMove - add linkTools.Vertices to enable users to interact with vertices

    new linkTools.Vertices({
    vertexAdding: boolean,
    vertexMoving: boolean,
    vertexRemoving: boolean,
    });
  • arrowheadMove - add linkTools.Arrowhead to enable arrowhead move

  • useLinkTools - add linkTools.Button to the link view


shapes.basic - Remove basic legacy shapes from joint-core package

  • joint.shapes.basic were removed completely in favor of joint.shapes.standard shapes
  • pn, uml, logic, org, chess, fsa shapes were removed from joint-core, and are implemented only as custom shapes within the demos
Migration guide

You can copy the shape definition from v3.7 directly to your application.

Examples can be found: umlcd, umlsc, pn, logic, org, fsa, erd.


shapes.devs - Remove devs legacy shapes from joint-core package

  • joint.shapes.devs were removed completely in favor of joint.shapes.standard shapes
  • devs shapes were removed from joint-core, and are implemented only as custom shapes within the demos
Migration guide

You can copy the shape definition from v3.7 directly to your application.

An example can be found here: devs.


shapes.standard - Update standard shapes by removing legacy attributes such as refWidth, refHeight, refX, refY, etc, and use native SVG attributes with calc expressions

Details...

For instance refWidth: 100%; is replaced with width: 'calc(w)'.

If you change these values in your application (e.g. by changing the refD attribute of the path, or changing the position of the label using refY), this is another breaking change.

Migration guide
  • a. You can copy the shape definition from v3.7 directly to your application, and override the built-in ones.
  • b. You need to switch from ref* attributes to native attributes in your code, and you might also need to change the attributes in any JSON you try to import (should the JSON contain modified standard shapes ref* attributes).

highlighters.opacity - Add alphaValue to opacity highlighter to control the value of transparency

Migration guide

Opacity no longer adds a CSS class to the node. It's set to opacity via the inline CSS style attribute. If opacity was set on a node via the inline attribute, the original value will be removed when the highlighter is removed.

If I add a highlighter to such a node, the opacity property will be removed along with the highlighter.

<rect style="opacity: 0.5"/>

In this case, everything works as before.

<rect opacity="0.5"/>

If you need to support the opacity in the inline style attribute, use highlighter.addClass instead.

highlighters.addClass.add(cellView, selector, id, {
className: 'highlight-opacity',
});
.highlight-opacity {
opacity: 0.3;
}

highlighters.stroke - Add nonScalingStroke option to stroke highlighter to add vector-effect=non-scaling-stroke to the highlighter

Migration guide

It's visually a breaking change because the highlighter now scales with the paper (when the zoom level of the paper changes, the stroke thickness changes too).

Previously:

highlighters stroke.add(elementView, 'body', id);

Version 4.0.0:

highlighters stroke.add(elementView, 'body', id, { nonScalingStroke: true });

layout.DirectedGraph, dia.Graph - Remove the DirectedGraph module from the joint.layout namespace, and remove dia.Graph.toGraphLib() and dia.Graph.fromGraphLib() functions

dagre and graphlib are imported via the standard process. It's no longer necessary to pass instances of dagre and graphlib as options to layout() and toGraphLib() methods.

Migration guide

If you need the DirectedGraph functionality (joint.layout.DirectedGraph.___), import it from @joint/layout-directed-graph (e.g. import { DirectedGraph } from @joint/layout-directed-graph). The functionality of the three exported functions is unchanged:

  • const bbox = DirectedGraph.layout(graph, { opt });
  • const glGraph = DirectedGraph.toGraphLib(graph, { opt });
  • const graph = DirectedGraph.fromGraphLib(glGraph, { opt });

If you were previously using dia.Graph.toGraphLib(), use the toGraphLib() function imported from @joint/layout-directed-graph instead. The graph is passed in as the first argument of the replacement function:

  • const glGraph = graph.toGraphLib({ opt }); becomes const glGraph = DirectedGraph.toGraphLib(graph, { opt });

Similarly, if you were previously using dia.Graph.fromGraphLib(), use the fromGraphLib() function imported from @joint/layout-directed-graph instead. The graph is passed in as the context of the replacement function:

  • graph.fromGraphLib(glGraph, { opt }); becomes DirectedGraph.fromGraphLib.call(graph, glGraph, { opt });

linkTools, elementTools - As per documentation "The names of built-in link tools are kebab-case versions of their class names".

This was not the case for the linkTools.Remove and elementTools.Remove buttons.

Migration guide

You may have styled the Remove button using CSS in your app.

Previously:

.joint-tool[data-tool-name='button'] circle {
fill: #333;
}

Version 4.0.0:

.joint-tool[data-tool-name='remove'] circle {
fill: #333;
}

attributes.filter - Change how default filters are rendered (change the coordinate system of the filters from objectBoundingBox to userSpaceOnUse)

This fixes applying filters to shapes with a width or height of 0 (such as horizontal/vertical links).

Migration guide

If you need your filter to look the same as the previous version, you can add an attrs object to your filter as shown below.

element1.attr('body/filter', {
// for backwards compatibility
attrs: {
filterUnits: 'objectBoundingBox',
x: -1,
y: -1,
width: 3,
height: 3,
},
name: 'dropShadow',
args: {
dx: 2,
dy: 2,
blur: 3,
},
});

Vectorizer - Enable camel case attribute support by default, and make the attributeNames property public

The supportCamelCaseAttributes property remains private.

Previously:

V('g').attr('myAttribute', 'value'); // <g myAttribute="value"/>

Version 4.0.0:

V('g').attr('myAttribute', 'value'); // <g my-attribute="value"/>

Allows you to use camel case attribute names anywhere in JointJS. e.g targetMarker, mvc.View.prototype.attributes

Migration guide

If you need any attribute to stay camel cased, you have to define it through the attributeNames property.

V.attributeNames['myAttribute'] = 'myAttribute';
V('g').attr('myAttribute', 'value'); // <g myAttribute="value"/>

CSS - JointJS is no longer distributed with CSS

Details...
  1. JointJS is no longer distributed with CSS (joint.css).
  2. Custom shapes have no cursor set by default. It's the user's responsibility to define the right cursor for their shapes. The standard shapes now have a cursor set via an SVG attribute.
  3. Magnets are no longer opaque on hover and have no cursor: crosshair.
  4. There are no themes defined in JointJS (they were only used to style the legacy LinkView, and to change the color of the paper).
  5. Font lato-light is no longer installed.
Migration guide
  1. Do not load joint.css in your application. For instance: Previously: <link rel="stylesheet" type="text/css" href="joint.css" /> Version 4.0.0: <link rel="stylesheet" type="text/css" href="joint.css" />

  2. Define a cursor for your shapes to give the user a hint that they can drag & drop the element.

    In CSS:

    .joint-element {
    cursor: move;
    }

    Or in JavaScript:

    element.attr('root/cursor', 'move');
  3. Add CSS to your application.

    [magnet='true']:hover {
    opacity: 0.7;
    }
  4. To change the paper color use the background paper option.

    const paper = new dia.Paper({
    /* ... */
    background: { color: 'black' },
    });
  5. Download the font from latofonts.com.


Other additions

Add Tree of life demo

Explore evolutionary relationships while looking at pressure-sensitive freehand lines, and seeing how we utilized SVGTextPathElement to wrap text.

gif-tree-of-life

Live Tree of life Demo

You can also view the source code on GitHub.


Important notes

JointJS open-source is now using a monorepo architecture with workspaces.

If you want to contribute to JointJS, you will now need to ensure Yarn is installed on your system locally. Instructions on how to contribute to JointJS can be found in the relevant repository README file.

Note: JointJS open-source can still be installed from the npm registry via the cli tool of your choice such as npm. As noted at the beginning of the changelog, to utilize v4 of JointJS, you should install the @joint/core package.


JointJS now utilizes ES2020 features in its source code.

If your application is using old versions of some JavaScript tooling such as Webpack version 4, it may be time to update. Webpack 4 doesn't support some ES2020 features like nullish coalescing, or optional chaining. You can read about it more in the following Webpack issue.