Back to jointjs.com.
Jump to other additions.
CHANGELOG
-
Change dependencies automatic updates
The package
rappid.tgz
dependency versions are specified with a tilde instead of a caret (a tilde indicates that the patch version of a dependency can be auto updated)."dependencies": { "backbone": "~1.4.1", "canvg-rappid": "~1.0.3", "dagre": "~0.8.5", "graphlib": "~2.1.8", "jointjs": "~3.5.3", "jquery": "~3.6.0", "lodash": "~4.17.21" }
If you need to change a dependency version (allow minor/major versions, lock a specific version),
-
extract the package
tar -xvzf rappid.tgz
-
edit the dependencies in
package.json
file# using the editor of your choice emacs package/package.json
-
create a tarball using npm pack
cd package npm pack mv rappid-3.5.0.tgz /path/where/to/move
-
-
Add Kanban Application
Manage the work of your team or organization with this Kanban drag and drop board.
Note: The contents of the board together with the
dia.CommandManager
buffer are stored inlocalStorage
. Texts can be edited using the HTML<textarea>
overlay.
-
Add Yamazumi 3D Application
Optimize your processes with this Yamazumi chart drag and drop app (a stacked bar chart).
Note: The application uses a similar
dia.ElementView
to achieve a nice transition effect whenever the model changes position.class AnimatedElementView extends dia.ElementView { move: Animation; updateTransformation() { const { el, model } = this; const { x, y } = model.get('position'); const transform = `translate(${x}px, ${y}px)`; const keyframes = { transform: [transform] }; let move: Animation; if (this.move) { move = this.move; (<KeyframeEffect>move.effect).setKeyframes(keyframes); move.currentTime = 0; move.play(); } else { move = el.animate(keyframes, { easing: 'ease-in', fill: 'forwards', duration: 200 }); move.onfinish = () => el.parentNode && move.commitStyles(); // Do not animate the first position move.finish(); this.move = move; } } } // Using the view with all new elements paper.options.elementView = () => AnimatedElementView;
-
Add Curves Demo
The demo utilizes the beautiful curve connector, the new connect tool, and improved connection strategy.
-
Add Fills Demo
The updated gradients & patterns API in the example.
See the Pen JointJS: Control Tool by JointJS (@jointjs) on CodePen.
-
apps.Tokens - integrate high performance WebGL heatmap library
-
apps.DataMapping - redesign, add
standard.Record
scrollbars and decorate items with custom highlighters
-
apps.Planogram - display a label (custom element tool) with the number of products when resizing
-
apps.KitchenSink - add
React JS
versionAdd React JS version of our example application.
-
dia.CommandManager - reduce the stack memory usage by filtering unnecessary event options, add
stackLimit
optionnew dia.CommandManager({ // The maximum number of commands in the stack stackLimit: 20 });
-
dia.CommandManager - add
toJSON
andfromJSON
to export/import the history buffer// Export const graphJSON = graph.toJSON(); const historyJSON = commandManager.toJSON(); // a user-defined method to store data await saveToDB({ historyJSON, graphJSON });
// Import // a user-defined method to retrieve data const { historyJSON, graphJSON } = await loadFromDB(); graph.fromJSON(graphJSON); commandManager.fromJSON(historyJSON);
-
dia.CommandManager - add
squashUndo
andsquashRedo
graph.addCell(el1); graph.addCell(el2); // Adding of `el1` and `el2` is squashed into a single command cmd.squashUndo(2); // Removes both `el1` and `el2` cmd.undo(); // Add `el1` and `el2` back to the graph cmd.redo();
-
format.SVG -
convertImagesToDataUris
option uses minimum number of network requestsconst image1 = new shapes.standard.Image({ attrs: { image: { href: 'my-image.png' }}}); const image2 = image1.clone(); graph.addCells([image1, image2]); paper.toSVG(dataUri => { // The method converted the `my-image.png` to dataUri only once. }, { convertImagesToDataUris: true });
-
format.Visio - existing shapes from the Visio template are preserved
The visio template file may have pre-existing shapes (e.g., page numbers, headers, footers) that would be complex and cumbersome to express and maintain as JXON/XML objects in JavaScript code. To that end, the plugin copies any pre-existing shapes from the template file into the exported document. It is the caller’s responsibility to ensure that the
vsdx
template file doesn’t have any contents that should not be in the export.
-
layout.StackLayout - a new layout to organize elements in a one-dimensional stacks, either horizontally or vertically. Useful to create Stack bar charts, Yamazumi charts or Kanban boards.
import { layout } from 'rappid'; const { StackLayout } = layout; StackLayout.layout(graph, { direction: StackLayout.Directions.TopBottom, alignment: StackLayout.Alignments.Middle });
-
ui.StackLayoutView - a new drag & drop view for the stack layout graphs
import { ui } from 'rappid'; const stackLayoutView = new ui.StackLayoutView({ paper, layoutOptions: { direction: 'TB', topBottom: { x: 20, y: 20 }, stackCount: 4, stackSize: 200, } });
-
shapes.standard.Record - add
maxLineCount
toitemText
attributeconst record = new shapes.standard.Record(); record.attr(['itemLabels', 'itemText', 'maxLineCount'], 1);
-
ui.ContextToolbar - fix
target
option could throw an errorconst toolbar = new ui.ContextToolbar({ // Any of the following is a valid type for the `targetElement`. // CSS selector (string) | SVGElement | HTMLElement | JQuery target: targetElement });
-
ui.Halo - fix the
pie
type rendering in various browser zoom settings
-
ui.FreeTransform - trigger events during resize/rotate
freeTransform.on({ 'resize:start rotate:start': () => { // Triggered when the user starts resizing/rotating the element. }, 'resize rotate': () => { // Triggered while the element is being resized/rotated. }, 'resize:stop rotate:stop': () => { // Triggered when the user finishes resizing/rotating the element. } });
-
ui.Inspector - add
container
optionDefine the container element which the
'select-box'
or'color-palette'
options is appended to (Therefore it can respect the container’s boundaries and scroll along with the content when opened).
-
ui.Inspector - add value path to the
operator
callbackReference sibling properties within the
when
expression inside of lists.ui.Inspector.create('#inspector-container', { operators: { siblingPropertyIsLesser: function(cell, value, property, valuePath) { // Assert: cell.prop(valuePath) === value // Get the property absolute path based on the `valuePath`. // e.g 'test/0/p1' => ['myList', '0', 'p1'] => ['myList', '0'].concat(['p2']); const propPathArray = valuePath.split('/').slice(0, -1).concat(property); return (value ? (value.length > cell.prop(propPathArray)) : false); } } });
// Inspector List Definition { myList: { type: 'list', item: { type: 'object', properties: { 'p1': { type: 'text', defaultValue: '', when: { siblingPropertyIsLesser: { 'myList/${index}/p1': 'p2' }, dependencies: ['myList/${index}/p2'] } }, 'p2': { type: 'number', defaultValue: 10, } } } } }
// Example Data { myList: [ { p1: 'abcde', p2: 3 }, // visible as 'abcde'.length > 3 { p1: 'abc', p2: 5 } // invisible as 'abc'.length < 5 ] }
-
ui.SelectBox - respect target boundaries
When the generated dropdown items are rendered inside of a container (providing the
target
option), its boundaries are now respected.
-
ui.Stencil - dispose resources on render
// It's safe to call render() multiple times stencil.render(); stencil.render();
-
ui.PaperScroller - add
borderless
optionConvert the blank space around the paper to be the paper itself making the borders of the paper visible no more.
With borders
Borderless
-
dia.Paper - add
labelsLayer
optionAn option to move link labels to a separate layer (on top of all other links and elements)
new dia.Paper({ labelsLayer: true /* ... */ });
Note: adds a foundation for upcoming Layers API (including sortable
dia.PaperLayer
class).
-
dia.Paper - refactor
fitToContent()
- split
fitToContent()
into a newgetFitToContentArea()
method (to get the resulting paper bounding box to be used for further calculations). - prevent
translate
event being triggered aftersetOrigin()
without actual change of origin - prevent
resize
event being triggered aftersetDimension()
without actual change of size
- split
-
dia.Paper - improve
connectionStrategy
withsnapLinks
enabledMake sure that the
connectionStrategy
callback is called not only when a new closer magnet is found, but also whenever the pointer coordinates change (as the logic of the callback might depend on the pointer position).
-
dia.Paper - fix embedding mode with rotated elements
Take the rotation of elements into account when embedding an element into another.
-
dia.Paper - fix adding a new cell with an id of previously removed cell in async mode
const rect1 = new shapes.standard.Rectangle({ id: 'test-id', size: { width: 51, height: 52 }, position: { x: 1, y: 2 } }); rect1.addTo(graph); //--- paper.freeze(); // Here we remove an element, and add another element // with the same id but different attributes (position and size). rect1.remove(); rect2 = new shapes.standard.Rectangle({ id: rect1.id, size: { width: 101, height: 102 }, position: { x: 51, y: 52 } }); rect2.addTo(graph); paper.unfreeze(); // We expect the element view associated with the `test-id` element // to have the new size and position //--- // Assert: The view for `rect2` has bounding box // equal to { x: 201, y: 202, width: 101, height: 102 }
-
dia.Paper - cancel previous background image load
paper.drawBackground({ image: 'background.png' }); paper.drawBackground({ image: null }); // Assert: no image is displayed as the paper background
-
dia.Paper - improve markers, gradients and patterns
-
SVGMarkerElement can be now easily built from more than one element
const markerJSON = { markup: [{ tagName: 'circle', attributes: { 'r': 5, 'fill': '#FFF' } }, { tagName: 'path', attributes: { 'd': 'M 7 -5 7 5' } }], attrs: { 'stroke-width': 2, 'stroke': '#333' } }; link.attr(['line', 'sourceMarker'], markerJSON, { rewrite: true });
-
Add higher-level API to define SVGPatternElement
Define a
<pattern/>
and use it on a graphics SVGElement.const patternJSON = { type: 'pattern', attrs: { width: 10, height: 10 }, markup: [{ tagName: 'polygon', attributes: { points: '0,0 2,5 0,10 5,8 10,10 8,5 10,0 5,2' } }] }; const patternId = paper.definePattern(patternJSON); document.getElementById('my-svg-rect').setAttribute('fill', `url(#${patternId})`);
Or use the
<pattern/>
as thefill
of an element.element.attr('body/fill', patternJSON);
-
Add Typescript definition for Filters / Markers / Gradients
-
-
dia.Paper - migrate mousewheel/DOMMouseScroll to wheel
The mousewheel element event was delegated to the DOM events
mousewheel
andDOMMouseScroll
. The both of these are deprecated/non-standard (1, 2) and have been replaced bywheel
.
-
dia.Paper - fix update priority stats
Make sure that the
stats.priority
always points to the highest priority of all updates done betweenbeforeRender
andafterRender
.paper.requestViewUpdate(view1, flags1, priority1); paper.requestViewUpdate(view2, flags2, priority2); paper.unfreeze({ afterRender: stats => { // Note: The highest priority is the lowest number. // Assert: stats.priority === Math.min(priority1, priority2); } });
-
dia.Paper - make sure
paper:mouseleave
event is always triggeredWhen the mouse pointer enters a cell and the user clicks the cell to bring an HTML popup to front which covers the area of the cell, the
paper:mouseleave
event is triggered.
-
dia.ElementView - allow ports without labels
By default, every port contains an empty SVGTextElement. It is now possible to skip rendering of the label by providing an empty JSON markup.
const el1 = new shapes.standard.Rectangle({ portMarkup: [{ tagName: 'polygon' }], portLabelMarkup: [], // Port without the <text/> element ports: { items: [{ id: 'port1' }, { id: 'port2' }] } });
-
dia.Element -
position()
allowsrestrictedArea
to be used withoutdeep
option// Position the `el` within its parent // Make sure the `el` does not overlap the parent el.position(10, 10, { deep: false, // `restrictedArea` works for deep `true` and `false` now restrictedArea: el.getParentCell().getBBox() });
-
dia.Cell -
embed()
andunembed()
to accept an array of cells// embed el0.embed(el1); el2.embed([el3, el4]); // unembed el0.unembed(el1); el2.unembed([el3, el4]);
-
dia.Cell - fix
stopTransitions()
without path argumentel.transition('attribute1', value1); el.transition('attribute2', value2); el.stopTransitions(); // Assert: both `attribute1` and `attribute2` transitions are stopped // and no error was thrown
-
dia.Cell - stopping the transition does not require waiting for the transition to start
el.transition('attribute', value); // the start of this transition is asynchronous el.stopTransitions('attribute'); // Assert: the transition of `attribute` will never start
-
dia.Link - add
hasLabels()
methodlink.hasLabels() // => link.labels().length > 0
- connectionStrategies.pinRelative - reduce rounding errors (the relative offset is now calculated with greater precision).
-
connectors.curve - add a new curved connector with configurable source and target tangents
See the Pen JointJS: Curve Connector by JointJS (@jointjs) on CodePen.
-
elementTools.Connect - a new tool to create links from an element
See the Pen JointJS: Control Tool by JointJS (@jointjs) on CodePen.
-
elementTools.Control - a new tool to modify an arbitrary attribute
See the Pen JointJS: Control Tool by JointJS (@jointjs) on CodePen.
-
elementTools.RecordScrollbar - an element tool which adds a scrollbar thumb on the
standard.Record
shape.
-
linkTools.Connect - a new tool to create links from a link
See the Pen JointJS: Link Connect Tool by JointJS (@jointjs) on CodePen.
- linkTools.Vertices - allow vertex removal on touch devices (double tap)
- linkTools.Anchor - allow anchor reset on touch devices (double tap)
- linkTools.Vertices - fix creation and immediate dragging of a vertex on touch devices
-
dia.attributes - add
x
andy
properties tocalc()
expressionin order to support the use case where a node’s size and position is derived from another node of the same cell (e.g background rectangle of a text)
element.attr('myTextBackground', { ref: 'myText', refX: -5, refY: -5, width: 'calc(w + 10)', height: 'calc(h + 10)', });
without the need to use
refX
andrefY
attributes.element.attr('myTextBackground', { ref: 'myText', x: 'calc(x - 5)', y: 'calc(y - 5)', width: 'calc(w + 10)', height: 'calc(h + 10)', });
-
dia.attributes - support negative number addition/subtraction in
calc()
expressionSupport
calc()
expression in these forms:calc(number * property - -number) calc(number * property + -number)
This is common when values are inserted into the
calc()
expression via template literals.const offset = -20; const d1 = `M 0 0 calc(w-${offset}) 0`; const d2 = `M 0 0 calc(w+${offset}) 0`;
-
dia.attributes - support nested
calc()
functionsSupport
calc()
expression in these forms:calc(number * property +- calc(expr)) calc(calc(expr) * property +- number) calc(calc(expr1) * property +- calc(expr2))
// Nested calc() expression: calc(w-calc(0.5 * h)) const path = new shapes.standard.Path({ attrs: { d: 'M 0 calc(0.5*h) calc(0.5*h) 0 H calc(w-calc(0.5 * h)) a 3 3 0 0 1 3 calc(h) H calc(0.5*h) z' } });
-
dia.HighlighterView - add
z
option to control the stacking order of the highlightershighlighters.mask.add(elementView1, 'body', 'highlighter-id', { layer: 'back', z: 1 // default `z` is 0 });
-
dia.ToolsView - add
z
option to control the stacking order of the toolsnew dia.ToolsView({ tools [linkTools.Vertices()], z: 1 // default `z` is 0 });
-
mvc.View - support double tap event on touch devices
const MyView = mvc.View.extend({ events: { 'dblclick': 'onDoubleClick', 'dbltap': 'onDoubleTap' }, onDoubleClick: function(evt) { /* ... */ }, onDoubleTap: function(evt) { /* ... */ } });
-
mvc.View - allow views to have no theme
const MyView = mvc.View.extend({ defaultTheme: null }); const myView = new MyView(); // Assert: myView.el.classList.length === 0
-
Vectorizer - allow whitespace and line terminator characters in the constructor
const vGroup = V(` <g> <rect width="100" height="100"/> <text x="0" y="-20">Label</text> </g> `);
-
Geometry - implement performance oriented intersection between all objects (
Line
,Rect
,Ellipse
,Polyline
,Polygon
,Path
)g.intersection.exists( new g.Rect(0, 0, 10, 10), new g.Line({ x: 5, y: 5 }, { x: 30, y: 30 }) ); // returns `true`
-
Geometry - add
Polygon
objectWhile a
Polyline
is open ended, thePolygon
defines a closed area and includes the interior. It has the same API asPolyline
, but the interior is taken into account when used as an argument to find an intersection and it implicitly adds the final closing segment (e.g. when the length is calculated).const rect = new g.Rect(10, 10, 200, 100); const rhombus = new g.Polygon([ rect.topMiddle(), rect.rightMiddle(), rect.bottomMiddle(), rect.leftMiddle(), // the line from left-mid to top-mid is implicit ]);
Other Additions
-
@joint/decorators - a new package for defining shapes from an SVG template (text interpolation, data binding, using functions, define new SVG attributes).
-
Add Content Driven Element Tutorial
How to create an element where the content drives the size of the element.
See the Pen JointJS: Content Driven Shapes by JointJS (@jointjs) on CodePen.
Here’s one more example of a content-driven element with real-time updates.
See the Pen JointJS: Counters by JointJS (@jointjs) on CodePen.
-
Update Ports Tutorial
How to add, validate, or highlight ports.
-
Update Special Attributes Tutorial
How to use the
calc()
expression.
-
Example - Marey’s Chart
See the Pen JointJS: Marey's Chart by JointJS (@jointjs) on CodePen.
-
Example - Editing multiple elements at a time with
ui.Inspector
See the Pen JointJS+: Inspector For Selection by JointJS (@jointjs) on CodePen.
-
Example - Links in
ui.Stencil
See the Pen JointJS+: Links In Stencil by JointJS (@jointjs) on CodePen.
-
Example - Scale SVGMarker with link’s thickness
See the Pen JointJS: Scale SVGMarker with link's thickness by JointJS (@jointjs) on CodePen.