Back to jointjs.com.
CHANGELOG
-
upgrade jQuery dependency (v3.5.1)
Fix a security issue.
-
add Chatbot application (VueJS, Angular9, React Redux)

# Angular 9 Typescript {rappid_package_v3.2.0}/apps/Chatbot/Angular9/README.md # React Redux Typescript {rappid_package_v3.2.0}/apps/Chatbot/ReactReduxTs/README.md # Vue Typescript {rappid_package_v3.2.0}/apps/Chatbot/VueTs/README.md
-
add shapes.Measurements (Distance & Angle)

# open in browser {rappid_package_v3.2.0}/apps/Measurements/angle.html
# open in browser {rappid_package_v3.2.0}/apps/Measurements/distance.html
-
add KitchenSink integrated with Angular9, VueJS and VueTs
# Angular 9 Typescript {rappid_package_v3.2.0}/apps/KitchenSinkAngular9/README.md # Vue Javascript {rappid_package_v3.2.0}/apps/KitchenSinkVueJs/README.md # Vue Typescript {rappid_package_v3.2.0}/apps/KitchenSinkVueTs/README.md
-
add Sequence Diagram demo

-
add HTML Elements demo

-
apps.DataMapping: show translucent copy of an element being dragged

// Disable default element dragging paper.setInteractivity({ elementMove: false }); paper.on('element:pointermove', (view, evt, x, y) => { // `data` object is shared between `down`, `move` and `up` events const { data } = evt; let { ghost } = data; if (!ghost) { // Create a copy of the original element view on the first element pointer move const position = view.model.position(); ghost = view.vel.clone().attr('opacity', 0.3).appendTo(paper.viewport); data.ghost = ghost; data.dx = x - position.x; data.dy = y - position.y; } ghost.attr('transform', `translate(${x - data.dx},${y - data.dy})`); }); paper.on('element:pointerup', (view, evt, x, y) => { const { ghost, dx, dy } = evt.data; if (!ghost) return; // Finally, move the actual element and remove the ghost ghost.remove(); view.model.position(x - dx, y - dy); });
-
apps.Layout: implement dragging of embedded elements

-
shapes.Standard - support element ports on the
Recordshapeconst record = new joint.shapes.standard.Record({ ports: { group: { in: { position: { name: 'top' }}, out: { position: { name: 'bottom' }} } } }); record.addPort({ id: 'in1', group: 'in' });
-
format.GridLayout - add
setAttributes,verticalAlign,horizontalAlign,rowGap,columnGapoptions// Layout children of an element into a grid const gridElements = element.getEmbeddedCells(); joint.layout.GridLayout.layout(gridElements, { columns: 3, columnGap: 10, rowGap: 10, // `x` coordinate of each element's origin is at left of the grid cell horizontalAlign: 'left', // Find the minimal column width based on the elements in the column columnWidth: 'compact', // `y` coordinate of each element's center is in the middle of the grid cell verticalAlign: 'middle', rowHeight: 100, parentRelative: true, // dx: 10, @deprecated - use `columnGap` instead // dy: 10, @deprecated - use `rowGap` instead // centre: false, @deprecated - use `verticalAlign` and `horizontalAlign` instead centre: false // needs to be disabled for backwards compatibility reasons }); element.fitEmbeds({ padding: 10 });
-
ui.FreeTransform - support asynchronous updates
Updates of the FreeTransform are now bound to the Paper’s update mechanism (Same as
ui.Selection).
Thepaper.options.viewportmay be fired with the FreeTransform view.
Callingpaper.freeze()will now stop the updates of the FreeTransform.
-
ui.FreeTransform - add
usePaperScale,resizeDirectionsandpaddingoptionsconst freeTransform = new joint.ui.FreeTransform({ /* ... */ // The FreeTransform receives the same transformations (scale) as the Paper. usePaperScale: true, // Display resize handles on the left and right side only resizeDirections: ['left', 'right'] // The gap between the FreeTransform frame and the element boundary padding: 4, useModelGeometry: true }); freeTransform.render();
-
ui.Halo -
magnetoption callback receiveseventargument// Implementing a Halo with two link handles (each connects the link to a different magnet) new joint.ui.Halo({ /* ... */ handles: [{ name: 'link-right', position: 'w', events: { pointerdown: 'startLinking', pointermove: 'doLink', pointerup: 'stopLinking' }, attrs: { '.handle': { // This is available under `evt.target.dataset.selector` 'data-selector': 'rightMagnet' } } }, { name: 'link-left', position: 'e', events: { pointerdown: 'startLinking', pointermove: 'doLink', pointerup: 'stopLinking' }, attrs: { '.handle': { // This is available under `evt.target.dataset.selector` 'data-selector': 'leftMagnet' } } }], magnet: (elementView, endType, evt) => { // Here you can decide what sub-element of the elementView // should become the link's magnet. // Selector: https://resources.jointjs.com/docs/jointjs/v3.2/joint.html#dia.Cell.markup const { selector } = evt.target.dataset; const [linkMagnet] = elementView.findBySelector(selector); return linkMagnet; } });
-
ui.Inspector - relative
whenexpressions for nested objects and listsnew joint.ui.Inspector({ /* ... */ inputs: { user_list: { // An array of users type: 'list', item: { // An object describing a single user type: 'object', properties: { contact_option: { type: 'select', options: ['email', 'tel'] }, contact_email: { type: 'text', when: { // Show when the current list item `contact_option` equals `email` eq: { 'user_list/${index}/contact_option': 'email' } } }, contact_tel: { type: 'number', when: { // Show when the current list item `contact_option` equals `tel` eq: { 'user_list/${index}/contact_option': 'tel' } } } } } } } });
-
ui.Inspector - add
updateCellOnCloseoptionjoint.ui.Inspector.create(inspectorHTMLContainer, { /* ... */ cell: selectedElement, // Do not send values from inputs to the `cell` when the inspector closes updateCellOnClose: false });
-
ui.Inspector - make sure fields are saved before close
Just before the inspector gets closed, the active element focus is now removed. This may trigger an input
changeevent resulting in storing the changed value to the cell.
-
ui.Navigator - add
useContentBBoxoptionnew joint.ui.Navigator({ /* ... */ // Show the paper area occupied by cells only. useContentBBox: true });
-
ui.Navigator - add
freeze(),unfreeze()const navigator - new joint.ui.Navigator({ /* ... */ paperOptions: { frozen: true, async: true, sorting: joint.dia.Paper.sorting.NONE } }); // Unfreeze when the navigator is visible in the DOM // For a large graph, limit the number of updates in a single batch navigator.unfreeze({ batchSize: 100 }); // When the navigator is about to be hidden (e.g collapsed) from the page navigator.freeze();
-
ui.Navigator - trigger
pan:start,pan:stop,zoom:start,zoom:stopeventsnavigator.on('pan:start zoom:start', () => { console.log('viewport will change.'); }); navigator.on('pan:stop zoom:stop', () => { console.log('viewport has changed.'); });
-
ui.PaperScroller - add
scrollWhileDraggingoption
const scroller = new joint.ui.PaperScroller({ /* ... */ scrollWhileDragging: { interval: 25, // ms padding: -20, // px // `distance` from the mouse coordinates to // the scroller border (inflated by `padding`) in `px` // Returns the number of `px` we move the paper per `interval`. scrollingFunction: (distance) => distance < 20 ? 5 : 20 } });
-
ui.PaperScroller -
paddingoption callback accepts paper scroller instanceconst scroller = new joint.ui.PaperScroller({ /* ... */ padding: (scroller) => { // Set the `padding` to 50% of the current dimension of the scroller. const { width, height } = scroller.getClientSize(); const ratio = 0.5; return { left: width * ratio, right: width * ratio, top: height * ratio, bottom: height * ratio }; } });
-
ui.PaperScroller -
positionContent(),scrollToContent()acceptsuseModelGeometryoption// Scroll the content (all cells of the graph) to // the the top right corner of the scroller. // Add `useModelGeometry: true` when the paper is set to `async` mode // and the cell views may not be rendered yet. scroller.positionContent('top-right', { useModelGeometry: true });
-
ui.PaperScroller - fix paper size after
zoomToRect()Fix for missing
scroller.adjustPaper()call after the paper content is scaled.
-
ui.PaperScroller - fix
autoResizePaperoption for synchronous rendering modeFix for the Paper inside the PaperScroller not getting resized after a graph change.
-
ui.Stencil - add
freeze(),unfreeze()// Create and populate the stencil const stencil = new joint.ui.Stencil({ /* ... */ paperOptions: { frozen: true } }); stencil.load(stencilElements); // Unfreeze the Stencil after it's mounted. // Note: Rendering the Stencil elements while it is not mounted may // cause element views to render incorrectly. stencilHTMLContainer.appendChild(stencil.el); stencil.unfreeze();
-
ui.Stencil - add
contentOptionsandcanDragoptionsnew joint.ui.Stencil({ /* ... */ // The Stencil Paper size depends on the elements in the paper. // To adjust this behavior use `contentOptions`. contentOptions: { // Available options: // https://resources.jointjs.com//docs/JointJS#dia.Paper.prototype.fitToContent padding: 5, useModelGeometry: true, minHeight: 200 }, // paperPadding: 5, @deprecated in favour of contentOptions.padding // A function to determine whether an element from the stencil // can be dragged by the user or not. canDrag: (elementView) => !elementView.model.get('disabled'); });
-
ui.Stencil - add
stencil-filteredCSS class when stencil is filtered/* Hide group labels when showing result of a search */ .joint-stencil.stencil-filtered .group-label { display: none; }
-
ui.Stencil - fix listeners not being unbound after drag end
Make sure
stencil.onDrag()is called only when the user drags a stencil element.
-
ui.Tooltip - add
containeroptionnew joint.ui.Tooltip({ // Append the tooltip to a custom container element (default is `body`) container: tooltipHTMLContainer });
-
ui.Tooltip - fix default
templateoptionFix related to jQuery upgrade.
-
dia.Paper - add
beforeRenderandafterRenderoptions, addhasScheduledUpdates(), triggerrender:donein sync mode// Assert: paper.isAsync() === true paper.freeze(); // Assert: paper.hasScheduledUpdates() === false element.attr('body/fill', 'red'); // The update of the fill color is scheduled. // Assert: paper.hasScheduledUpdates() === true paper.unfreeze({ beforeRender: () => { // The update of the fill is still scheduled. // Assert: paper.hasScheduledUpdates() === true }, afterRender: () => { // All updates have been done. // Assert: paper.hasScheduledUpdates() === false element.attr('body/fill', 'red'); // The fill color of the element is already `red`. // Assert: paper.hasScheduledUpdates() === false } });
// Assert: paper.isAsync() === false graph.on('change:position', function() { // Called once for `parent` and once for `child` }); paper.on('render:done', () => { // Called after both `parent` and the `child` has been translated }); // A different part of an application parent.embed(child); parent.translate(10, 0);
-
dia.Paper - fix missing initial
render:doneeventpaper.on('render:done', () => { // Make sure `render:done` event is triggered after the first render. }); graph.resetCells(arrayOfCells);
-
dia.Paper - prevent the prototype options modification, persist functions passed as options
new joint.dia.Paper({ // e.g. fixes `defaultConnector` option defined as a function defaultConnector: (sourcePoint, targetPoint, route) => { const polyline = new g.Polyline([sourcePoint, ...route, targetPoint]); return `M ${polyline.serialize()}`; } });
-
dia.Paper -
scaleContentToFit()optionpaddingaccepts an objectpaper.scaleContentToFit({ // Set a non-uniform padding padding: { top: 50, bottom: 10, // left and right horizontal: 10 }});
-
dia.Paper - fix
isMountedargument ofviewport()optionnew joint.dia.Paper({ viewport: (view, isMounted) => { if (isMounted) { // the view was already mounted // it's visible in the paper } else { // the view was just created or was detached by this function in the previous run // it's not visible in the paper (it's not in the DOM) } // return `true` to mount the view // return `false` to detach the view } });
-
dia.Paper - dynamic link update priorities (fix for “link connected to other two links” update bug)
A link
link1connected tolink2andlink3is updated after bothlink2andlink3are up-to-date. The connection points oflink1located onlink2andlink3depend on the latest geometry of both links.
-
dia.Element - port removal runs in batch
// Remove the port and all the connected links in a single batch. // The `CommandManager` will register a single undo/redo command. element.removePort('port_id');
-
dia.Element - add
getGroupPorts()element.getGroupPorts('out'); // Returns `[{ id: 'out1', group: 'out' }, { id: 'out2', group: 'out' }]`
-
dia.Element - prevent exception in
getPointFromConnectedLink()when port does not exist// Assert: element.hasPort('invalid_port_id') === false link.set('target', { id: element.id, port: 'invalid_port_id' }); element.getPointFromConnectedLink(link, 'target'); // Expect: No exception is thrown.
-
dia.LinkView - fix never ending batch for legacy link tools
Fix for legacy
joint.dia.Link(use joint.shapes.standard.Link instead together with linkTools) for which clicking the link tools started a batch, which was never stopped.
-
dia.LinkView - add
requestConnectionUpdate()// Assert: paper.isAsync() === true // Assert: paper.options.defaultRouter === { name: 'manhattan' } graph.on('change:position', (element) => // Manually request updates for all links (even so, that no link model was changed) graph.getLinks().forEach(link => { // The link's route needs to be recalculated as the `element` moved and // may become an obstacle. link.findView(paper).requestConnectionUpdate(); }); });
-
dia.LinkView - trigger
link:snap:connectandlink:snap:disconnectevents
-
dia.LinkView - prevent exception when labels and connection require update
paper.freeze(); // Assert: The link is rendered in the paper link.set({ source: { x: 20, y: 20 }, labels: [ { position: 20, attrs: { text: { text: 'label1' }}}, { position: -20, attrs: { text: { text: 'label2' }}} ] }); paper.unfreeze(); // Expect: No exception is thrown.
-
dia.LinkView - measure snap distance for links from magnet’s boundary
Fix snapping for magnets larger than
radius.
-
dia.LinkView - add
getEndConnectionPoint()const sourceConnectionPoint = linkView.getEndConnectionPoint('source'); // It returns a geometry point (in local coordinates), // where the link connects to the source element.
-
mvc.View - add
DETACHABLEproperty to ignore viewport matching,FLAG_INSERT&FLAG_REMOVEdefined on per view basisconst CustomFreeTransform = joint.ui.FreeTransform.extend({ // The FreeTransform view will not be checked inside `paper.options.viewport` function. // It will be always mounted in the DOM DETACHABLE: false });
-
linkTools.Anchor: add
resetAnchoroptionnew joint.linkTools.TargetAnchor({ // prevent `double-click` to reset the anchor resetAnchor: false });
-
linkTools.Segments: add
stopPropagationoptionnew joint.linkTools.Segments({ // `link:pointerdown` event will be triggered // on the related linkView on `mousedown` stoPropagation: false });
-
connectionPoints.anchor - add
alignandalignOffsetoptionsA way to attach links to elements as shown in the Distance Demo.
link.set('target', { id: targetElement.id, connectionPoint: { name: 'anchor', args: { // Align the target connection point with the source anchor, // if the source anchor `y` coordinate is larger than the target anchor align: 'bottom', alignOffset: 20 } } })
-
attributes.textWrap: add
maxLineCountoptionelement.attr({ header: { text: 'Rappid Component', textWrap: { width: -10, // make sure the header never spans over more than 1 line maxLineCount: 1 } } });
-
util.breakText - retain new line characters, add
maxLineCountoptionjoint.util.breakText('One\n\nThree\nFour', /*...*/); // The empty line is retained /* One↵ ↵ Three↵ Four */
joint.util.breakText('Lorem ipsum dolor sit amet, consectetur adipiscing elit.', { width: 100 }, { 'font-family': 'sans-serif', 'font-size': 14 }, { maxLineCount: 2, ellipsis: true });
-
util.sanitizeHTML: sanitize attribute values with
"data:"and"vbscript:"Fix for Improper Input Validation.
joint.util.sanitizeHTML('<a href="data:something">'); // => '<a></a>'
-
Geometry - add
parallel()to Line, addserialize()to Point and Lineconst line = new g.Line('0@0', '10@10'); const pathLeft = V('path', { 'd': `M ${ line.parallel(-5).serialize()}` }); const pathRight = V('path', { 'd': `M ${ line.parallel(5).serialize()}` });
-
various Typescript fixes
If you come across any Typescript mismatch, we suggest to fix it directly in your local
rappid.d.tsfile and let us know about the issue, so we can patch it for the next release. Please email us at support@client.io.