jointjs
Version:
JavaScript diagramming library
876 lines (840 loc) • 25.4 kB
JavaScript
var graph = new joint.dia.Graph();
var paper = new joint.dia.Paper({
el: document.getElementById('paper'),
width: 800,
height: 600,
model: graph,
interactive: { linkMove: false },
defaultConnectionPoint: {
name: 'boundary',
args: {
extrapolate: true,
sticky: true
}
},
validateConnection: function() {
return false;
}
});
var link1 = new joint.shapes.standard.Link({
source: { x: 20, y: 20 },
target: { x: 350, y: 20 },
attrs: {
line: {
stroke: '#222138',
sourceMarker: {
'fill': '#31d0c6',
'stroke': 'none',
'd': 'M 5 -10 L -15 0 L 5 10 Z'
},
targetMarker: {
'fill': '#fe854f',
'stroke': 'none',
'd': 'M 5 -10 L -15 0 L 5 10 Z'
}
}
}
});
var link2 = new joint.shapes.standard.Link({
source: { x: 20, y: 80 },
target: { x: 350, y: 80 },
attrs: {
line: {
stroke: '#fe854f',
strokeWidth: 4,
sourceMarker: {
// if no fill or stroke specified, marker inherits the line color
'd': 'M 0 -5 L -10 0 L 0 5 Z'
},
targetMarker: {
// the marker can be an arbitrary SVGElement
'type': 'circle',
'r': 5
}
}
}
});
// Utility function for normalizing marker's path data.
// Translates the center of an arbitrary path at <0 + offset,0>.
function normalizeMarker(d, offset) {
var path = new g.Path(V.normalizePathData(d));
var bbox = path.bbox();
var ty = - bbox.height / 2 - bbox.y;
var tx = - bbox.width / 2 - bbox.x;
if (typeof offset === 'number') tx -= offset;
path.translate(tx, ty);
return path.serialize();
}
var link3 = new joint.shapes.standard.Link({
source: { x: 10, y: 140 },
target: { x: 350, y: 140 },
attrs: {
line: {
stroke: '#31d0c6',
strokeWidth: 3,
strokeDasharray: '5 2',
sourceMarker: {
'stroke': '#31d0c6',
'fill': '#31d0c6',
'd': normalizeMarker('M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z')
},
targetMarker: {
'stroke': '#31d0c6',
'fill': '#31d0c6',
'd': normalizeMarker('M4.834,4.834L4.833,4.833c-5.889,5.892-5.89,15.443,0.001,21.334s15.44,5.888,21.33-0.002c5.891-5.891,5.893-15.44,0.002-21.33C20.275-1.056,10.725-1.056,4.834,4.834zM25.459,5.542c0.833,0.836,1.523,1.757,2.104,2.726l-4.08,4.08c-0.418-1.062-1.053-2.06-1.912-2.918c-0.859-0.859-1.857-1.494-2.92-1.913l4.08-4.08C23.7,4.018,24.622,4.709,25.459,5.542zM10.139,20.862c-2.958-2.968-2.959-7.758-0.001-10.725c2.966-2.957,7.756-2.957,10.725,0c2.954,2.965,2.955,7.757-0.001,10.724C17.896,23.819,13.104,23.817,10.139,20.862zM5.542,25.459c-0.833-0.837-1.524-1.759-2.105-2.728l4.081-4.081c0.418,1.063,1.055,2.06,1.914,2.919c0.858,0.859,1.855,1.494,2.917,1.913l-4.081,4.081C7.299,26.982,6.379,26.292,5.542,25.459zM8.268,3.435l4.082,4.082C11.288,7.935,10.29,8.571,9.43,9.43c-0.858,0.859-1.494,1.855-1.912,2.918L3.436,8.267c0.58-0.969,1.271-1.89,2.105-2.727C6.377,4.707,7.299,4.016,8.268,3.435zM22.732,27.563l-4.082-4.082c1.062-0.418,2.061-1.053,2.919-1.912c0.859-0.859,1.495-1.857,1.913-2.92l4.082,4.082c-0.58,0.969-1.271,1.891-2.105,2.728C24.623,26.292,23.701,26.983,22.732,27.563z', 10)
}
}
}
});
var link4 = new joint.shapes.standard.Link({
source: { x: 400, y: 20 },
target: { x: 740, y: 20 },
vertices: [{ x: 400, y: 60 }, { x: 550, y: 60 }, { x: 550, y: 20 }],
attrs: {
line: {
stroke: '#3c4260',
strokeWidth: 2,
sourceMarker: {
'fill': '#4b4a67',
'stroke': '#4b4a67',
'd': normalizeMarker('M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z')
},
targetMarker: {
'fill': '#4b4a67',
'stroke': '#4b4a67',
'd': normalizeMarker('M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z')
},
vertexMarker: {
'type': 'circle',
'r': 5,
'stroke-width': 2,
'fill': 'white'
}
}
}
});
var link5 = new joint.shapes.standard.Link({
source: { x: 440, y: 100 },
target: { x: 740, y: 100 },
vertices: [{ x: 400, y: 140 }, { x: 550, y: 100 }, { x: 600, y: 140 }],
smooth: true,
attrs: {
line: {
stroke: '#7c68fc',
strokeWidth: 3,
sourceMarker: {
'stroke': '#7c68fc',
'fill': '#7c68fc',
'd': normalizeMarker('M24.316,5.318,9.833,13.682,9.833,5.5,5.5,5.5,5.5,25.5,9.833,25.5,9.833,17.318,24.316,25.682z')
},
targetMarker: {
'stroke': '#feb663',
'fill': '#feb663',
'd': normalizeMarker('M14.615,4.928c0.487-0.986,1.284-0.986,1.771,0l2.249,4.554c0.486,0.986,1.775,1.923,2.864,2.081l5.024,0.73c1.089,0.158,1.335,0.916,0.547,1.684l-3.636,3.544c-0.788,0.769-1.28,2.283-1.095,3.368l0.859,5.004c0.186,1.085-0.459,1.553-1.433,1.041l-4.495-2.363c-0.974-0.512-2.567-0.512-3.541,0l-4.495,2.363c-0.974,0.512-1.618,0.044-1.432-1.041l0.858-5.004c0.186-1.085-0.307-2.6-1.094-3.368L3.93,13.977c-0.788-0.768-0.542-1.525,0.547-1.684l5.026-0.73c1.088-0.158,2.377-1.095,2.864-2.081L14.615,4.928z')
}
}
}
});
var link6 = new joint.shapes.standard.DoubleLink({
source: { x: 10, y: 200 },
target: { x: 350, y: 200 },
attrs: {
line: {
stroke: '#7c68fc'
}
},
labels: [{
attrs: { text: { text: 'Label' }},
position: {
offset: 15,
distance: 0.5
}
}]
});
var link7 = new joint.shapes.standard.Link({
source: { x: 400, y: 200 },
target: { x: 740, y: 200 },
connector: { name: 'smooth' },
attrs: {
line: {
targetMarker: {
'd': 'M 0 -5 L -10 0 L 0 5 Z'
}
}
},
labels: [{
markup: [{
tagName: 'rect',
selector: 'labelBody'
}, {
tagName: 'text',
selector: 'labelText'
}],
attrs: {
labelText: {
text: 'First',
fill: '#7c68fc',
fontFamily: 'sans-serif',
textAnchor: 'middle',
textVerticalAnchor: 'middle'
},
labelBody: {
ref: 'labelText',
refX: -5,
refY: -5,
refWidth: '100%',
refHeight: '100%',
refWidth2: 10,
refHeight2: 10,
stroke: '#7c68fc',
fill: 'white',
strokeWidth: 2,
rx: 5,
ry: 5
}
},
position: {
distance: 0.3,
args: {
keepGradient: true,
ensureLegibility: true,
}
}
}, {
markup: [{
tagName: 'ellipse',
selector: 'labelBody'
}, {
tagName: 'text',
selector: 'labelText'
}],
attrs: {
labelText: {
text: 'Second',
fill: '#31d0c6',
fontFamily: 'sans-serif',
textAnchor: 'middle',
textVerticalAnchor: 'middle'
},
labelBody: {
ref: 'labelText',
refRx: '70%',
refRy: '80%',
stroke: '#31d0c6',
fill: 'white',
strokeWidth: 2
}
},
position: {
distance: 0.7,
angle: 45
}
}]
});
var link8 = new joint.shapes.standard.ShadowLink({
source: { x: 10, y: 280 },
target: { x: 440, y: 280 },
vertices: [{ x: 150, y: 350 }, { x: 300, y: 280 }],
smooth: true,
markup: [{
tagName: 'path',
selector: 'shadow',
attributes: {
'fill': 'none'
}
}, {
tagName: 'path',
selector: 'line',
attributes: {
'fill': 'none'
}
}, {
tagName: 'text',
selector: 'label'
}],
attrs: {
line: {
stroke: '#3c4260'
},
label: {
textPath: {
selector: 'line',
startOffset: '50%'
},
textAnchor: 'middle',
textVerticalAnchor: 'middle',
text: 'Label Along Path',
fill: '#f6f6f6',
fontSize: 15,
fontWeight: 'bold',
fontFamily: 'fantasy'
}
}
});
// Custom Link
var link9 = new joint.dia.Link({
markup: [{
tagName: 'path',
selector: 'p1'
}, {
tagName: 'rect',
selector: 'sign'
}, {
tagName: 'circle',
selector: 'c1',
}, {
tagName: 'path',
selector: 'p2'
}, {
tagName: 'circle',
selector: 'c2'
}, {
tagName: 'text',
selector: 'signText'
}],
source: { x: 380, y: 380 },
target: { x: 740, y: 280 },
vertices: [{ x: 600, y: 280 }],
attrs: {
p1: {
connection: true,
fill: 'none',
stroke: 'black',
strokeWidth: 6,
strokeLinejoin: 'round'
},
p2: {
connection: true,
fill: 'none',
stroke: '#fe854f',
strokeWidth: 4,
pointerEvents: 'none',
strokeLinejoin: 'round',
targetMarker: {
'type': 'path',
'fill': '#fe854f',
'stroke': 'black',
'stroke-width': 1,
'd': 'M 10 -3 10 -10 -2 0 10 10 10 3'
}
},
sign: {
x: -20,
y: -10,
width: 40,
height: 20,
stroke: 'black',
fill: '#fe854f',
atConnectionLength: 30,
strokeWidth: 1,
event: 'myclick:rect'
},
signText: {
atConnectionLength: 30,
textAnchor: 'middle',
textVerticalAnchor: 'middle',
text: 'Link',
},
c1: {
r: 10,
stroke: 'black',
fill: '#fe854f',
atConnectionRatio: .5,
strokeWidth: 1,
event: 'myclick:circle',
cursor: 'pointer'
},
c2: {
r: 5,
stroke: 'black',
fill: 'white',
atConnectionRatio: .5,
strokeWidth: 1,
pointerEvents: 'none'
}
}
});
var el1 = new joint.shapes.standard.Path({
position: { x: 500, y: 430 },
size: { width: 100, height: 100 },
attrs: {
body: {
fill: '#31d0c6',
refD: 'M 0 20 10 20 10 30 30 30 30 0 40 0 40 40 0 40 z'
}
}
});
var link10 = new joint.shapes.standard.Link({
source: { x: 300, y: 400 },
target: { id: el1.id },
attrs: {
line: {
sourceMarker: {
'd': 'M 0 0 15 0',
'stroke': 'white',
'stroke-width': 3
}
}
}
});
// Stubs
var link11 = new joint.dia.Link({
markup: [{
tagName: 'path',
selector: 'line'
}, {
tagName: 'g',
selector: 'sourceReference',
children: [{
tagName: 'rect',
selector: 'sourceReferenceBody',
groupSelector: 'endReferenceBody'
}, {
tagName: 'text',
selector: 'sourceReferenceLabel',
groupSelector: 'endReferenceLabel'
}]
}, {
tagName: 'g',
selector: 'targetReference',
children: [{
tagName: 'rect',
selector: 'targetReferenceBody',
groupSelector: 'endReferenceBody'
}, {
tagName: 'text',
selector: 'targetReferenceLabel',
groupSelector: 'endReferenceLabel'
}]
}],
source: { x: 120, y: 550 },
target: { x: 120, y: 400 },
attrs: {
line: {
connection: { stubs: 40 },
fill: 'none',
stroke: 'black',
strokeWidth: 2,
strokeLinejoin: 'round',
sourceMarker: {
'type': 'circle',
'r': 5,
'cx': 5,
'fill': 'white',
'stroke': 'black',
'stroke-width': 2
},
targetMarker: {
'type': 'circle',
'r': 5,
'cx': 5,
'fill': 'white',
'stroke': 'black',
'stroke-width': 2
}
},
endReferenceBody: {
x: -12,
y: -45,
width: 24,
height: 90,
fill: 'white',
stroke: 'black',
strokeWidth: 2
},
sourceReference: {
atConnectionLength: 50,
event: 'link:source:click'
},
targetReference: {
atConnectionLength: -50,
event: 'link:target:click'
},
endReferenceLabel: {
textAnchor: 'middle',
textVerticalAnchor: 'middle',
textDecoration: 'underline',
writingMode: 'TB',
fontFamily: 'sans-sarif',
fontSize: 15,
cursor: 'pointer',
annotations: [{
start: 6,
end: 12,
attrs: {
'font-weight': 'bold'
}
}]
},
sourceReferenceLabel: {
text: 'Go to Target'
},
targetReferenceLabel: {
text: 'Go to Source'
}
}
});
paper.on({
'link:source:click': function(linkView) {
linkView.model.attr({
sourceReferenceBody: { fill: 'white' },
targetReferenceBody: { fill: '#fe854f' }
});
},
'link:target:click': function(linkView) {
linkView.model.attr({
sourceReferenceBody: { fill: '#fe854f' },
targetReferenceBody: { fill: 'white' }
});
}
});
var link12 = new joint.dia.Link({
markup: [{
tagName: 'path',
selector: 'line'
}, {
tagName: 'path',
selector: 'crossing',
}],
source: { x: 220, y: 550 },
target: { x: 220, y: 400 },
attrs: {
line: {
connection: { stubs: -30 },
fill: 'none',
stroke: 'black',
strokeWidth: 2,
strokeLinejoin: 'round',
sourceMarker: {
'type': 'circle',
'r': 5,
'cx': 5,
'fill': 'white',
'stroke': 'black',
'stroke-width': 2
},
targetMarker: {
'type': 'circle',
'r': 5,
'cx': 5,
'fill': 'white',
'stroke': 'black',
'stroke-width': 2
}
},
crossing: {
atConnectionRatio: .5,
d: 'M -10 -20 0 20 M 0 -20 10 20',
fill: 'none',
stroke: 'black',
strokeWidth: 2
}
}
});
var link13 = new joint.shapes.standard.Link({
source: {
id: el1.id,
anchor: { name: 'bottomRight' },
connectionPoint: { name: 'anchor', args: { align: 'bottom', alignOffset: 20 }}
},
target: {
id: el1.id,
anchor: { name: 'bottomLeft' },
connectionPoint: { name: 'anchor', args: { align: 'bottom', alignOffset: 20 }}
},
attrs: {
line: {
strokeWidth: 3,
strokeDasharray: '3,1',
sourceMarker: {
'd': 'M 0 -10 0 10',
'stroke-width': 3
},
targetMarker: {
'd': 'M 0 -10 0 10',
'stroke-width': 3
}
}
}
});
var link14 = new joint.dia.Link({
markup: [{
tagName: 'path',
selector: 'line1',
groupSelector: 'lines'
}, {
tagName: 'path',
selector: 'line2',
groupSelector: 'lines'
}, {
tagName: 'path',
selector: 'line3',
groupSelector: 'lines'
}],
connector: { name: 'rounded' },
source: { x: 30, y: 550 },
target: { x: 30, y: 400 },
attrs: {
lines: {
connection: true,
strokeDasharray: '10,20',
strokeLinejoin: 'round',
fill: 'none'
},
line1: {
stroke: '#fe854f',
strokeWidth: 10
},
line2: {
stroke: '#7c68fc',
strokeDashoffset: 10,
strokeWidth: 10,
},
line3: {
stroke: '#222138',
strokeDashoffset: 20,
strokeWidth: 5,
sourceMarker: {
'type': 'circle',
'r': 10,
'cx': 5,
'fill': '#fe854f',
'stroke': '#222138',
'stroke-width': 5
},
targetMarker: {
'type': 'circle',
'r': 10,
'cx': 5,
'fill': '#7c68fc',
'stroke': '#222138',
'stroke-width': 5
}
}
}
});
graph.resetCells([el1, link1, link2, link3, link4, link5, link6, link7, link8, link9, link10, link11, link12, link13, link14]);
// Custom Link Tools
var RectangleSourceArrowhead = joint.linkTools.SourceArrowhead.extend({
tagName: 'rect',
attributes: {
'x': -15,
'y': -15,
'width': 30,
'height': 30,
'fill': 'black',
'fill-opacity': 0.3,
'stroke': 'black',
'stroke-width': 2,
'cursor': 'move',
'class': 'target-arrowhead'
}
});
var CircleTargetArrowhead = joint.linkTools.TargetArrowhead.extend({
tagName: 'circle',
attributes: {
'r': 20,
'fill': 'black',
'fill-opacity': 0.3,
'stroke': 'black',
'stroke-width': 2,
'cursor': 'move',
'class': 'target-arrowhead'
}
});
var CustomBoundary = joint.linkTools.Boundary.extend({
attributes: {
'fill': '#7c68fc',
'fill-opacity': 0.2,
'stroke': '#33334F',
'stroke-width': .5,
'stroke-dasharray': '5, 5',
'pointer-events': 'none'
},
});
// Interactions
paper.on('link:mouseenter', function(linkView) {
var tools;
switch (linkView.model) {
case link1:
case link3:
case link4:
tools = [
new joint.linkTools.Vertices({ stopPropagation: false }),
new joint.linkTools.Segments({ stopPropagation: false })
];
break;
case link2:
tools = [
new joint.linkTools.Button({
markup: [{
tagName: 'circle',
selector: 'button',
attributes: {
'r': 7,
'stroke': '#fe854f',
'stroke-width': 3,
'fill': 'white',
'cursor': 'pointer'
}
}, {
tagName: 'text',
textContent: 'B',
selector: 'icon',
attributes: {
'fill': '#fe854f',
'font-size': 10,
'text-anchor': 'middle',
'font-weight': 'bold',
'pointer-events': 'none',
'y': '0.3em'
}
}],
distance: -30,
action: function() {
var link = this.model;
var source = link.source();
var target = link.target();
link.source(target);
link.target(source);
}
}),
new joint.linkTools.Button({
markup: [{
tagName: 'circle',
selector: 'button',
attributes: {
'r': 7,
'stroke': '#fe854f',
'stroke-width': 3,
'fill': 'white',
'cursor': 'pointer'
}
}, {
tagName: 'text',
textContent: 'A',
selector: 'icon',
attributes: {
'fill': '#fe854f',
'font-size': 10,
'text-anchor': 'middle',
'font-weight': 'bold',
'pointer-events': 'none',
'y': '0.3em'
}
}],
distance: -50,
action: function() {
var link = this.model;
link.attr({
line: {
strokeDasharray: '5,1',
strokeDashoffset: (link.attr('line/strokeDashoffset') | 0) + 20
}
});
}
})
];
break;
case link5:
tools = [
new joint.linkTools.Vertices({
snapRadius: 0,
redundancyRemoval: false
}),
new RectangleSourceArrowhead(),
new CircleTargetArrowhead(),
];
break;
case link6:
tools = [
new joint.linkTools.Vertices(),
new CustomBoundary({ padding: 25 })
];
break;
case link7:
tools = [
new joint.linkTools.SourceArrowhead(),
new joint.linkTools.TargetArrowhead(),
new joint.linkTools.Remove({ distance: 20 })
];
break;
case link8:
tools = [
new joint.linkTools.Vertices({
snapRadius: 0,
redundancyRemoval: false,
vertexAdding: false
})
];
break;
case link11:
case link12:
tools = [
new joint.linkTools.SourceArrowhead(),
new joint.linkTools.TargetArrowhead()
];
break;
case link14:
tools = [
new joint.linkTools.Vertices(),
new joint.linkTools.SourceArrowhead(),
new joint.linkTools.TargetArrowhead()
];
break;
default:
return;
}
linkView.addTools(new joint.dia.ToolsView({
name: 'onhover',
tools: tools
}));
});
paper.on('link:mouseleave', function(linkView) {
if (!linkView.hasTools('onhover')) return;
linkView.removeTools();
});
// Permanent Link Tool
link10.findView(paper).addTools(new joint.dia.ToolsView({
name: 'permanent',
tools: [
new joint.linkTools.TargetAnchor(),
new RectangleSourceArrowhead()
]
}));
link13.findView(paper).addTools(new joint.dia.ToolsView({
name: 'permanent',
tools: [
new joint.linkTools.TargetAnchor({
restrictArea: false,
snap: function(coords) {
var bbox = this.model.getTargetCell().getBBox();
coords.y = bbox.bottomMiddle().y;
coords.x = Math.min(bbox.x + bbox.width, coords.x);
return coords;
}
}),
new joint.linkTools.SourceAnchor({
restrictArea: false,
snap: function(coords) {
var bbox = this.model.getSourceCell().getBBox();
coords.x = bbox.bottomRight().x;
coords.y = Math.max(bbox.y, coords.y);
return coords;
}
}),
]
}));
// Attribute Event
paper.on('myclick:circle', function(linkView, evt) {
evt.stopPropagation();
var link = linkView.model;
var t = (link.attr('c1/atConnectionRatio') > .2) ? .2 :.9;
var transitionOpt = {
delay: 100,
duration: 2000,
timingFunction: joint.util.timing.inout
};
link.transition('attrs/c1/atConnectionRatio', t, transitionOpt);
link.transition('attrs/c2/atConnectionRatio', t, transitionOpt);
});