I have 3 elements with ports. My goal is to not allow linking ports between source and destination elements if one of their ports are already linked to each other. Please see my visualization below:
As you can see, I need only 1 to 1 connection between cells. How do I acheive this with JointJS? Please see my JSFiddle.
HTML
<html>
<body>
<button id="btnAdd">Add Table</button>
<div id="dbLookupCanvas"></div>
</body>
</html>
JS
$(document).ready(function() {
$('#btnAdd').on('click', function() {
AddTable();
});
InitializeCanvas();
// Adding of two sample tables on first load
AddTable(50, 50);
AddTable(250, 50);
AddTable(150, 180);
});
var graph;
var paper
var selectedElement;
var namespace;
function InitializeCanvas() {
let canvasContainer = $('#dbLookupCanvas').parent();
namespace = joint.shapes;
graph = new joint.dia.Graph({}, {
cellNamespace: namespace
});
paper = new joint.dia.Paper({
el: document.getElementById('dbLookupCanvas'),
model: graph,
width: canvasContainer.width(),
height: 500,
gridSize: 10,
drawGrid: true,
cellViewNamespace: namespace,
validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
// Prevent link to self
if (cellViewS === cellViewT)
return false;
// Prevent linking from input ports
if ((magnetS !== magnetT))
return true;
},
snapLinks: {
radius: 20
},
defaultLink: () => new joint.shapes.standard.Link({
router: {
name: 'manhattan'
},
connector: {
name: 'normal'
},
attrs: {
line: {
stroke: 'black',
strokeWidth: 1,
sourceMarker: {
'type': 'path',
'stroke': 'black',
'fill': 'black',
'd': 'M 10 -5 0 0 10 5 Z'
},
targetMarker: {
'type': 'path',
'stroke': 'black',
'fill': 'black',
'd': 'M 10 -5 0 0 10 5 Z'
}
}
}
})
});
//Dragging navigation on canvas
var dragStartPosition;
paper.on('blank:pointerdown',
function(event, x, y) {
dragStartPosition = {
x: x,
y: y
};
}
);
paper.on('cell:pointerup blank:pointerup', function(cellView, x, y) {
dragStartPosition = null;
});
$("#dbLookupCanvas")
.mousemove(function(event) {
if (dragStartPosition)
paper.translate(
event.offsetX - dragStartPosition.x,
event.offsetY - dragStartPosition.y);
});
// Remove links not connected to anything
paper.model.on('batch:stop', function() {
var links = paper.model.getLinks();
_.each(links, function(link) {
var source = link.get('source');
var target = link.get('target');
if (source.id === undefined || target.id === undefined) {
link.remove();
}
});
});
paper.on('cell:pointerdown', function(elementView) {
resetAll(this);
let isElement = elementView.model.isElement();
if (isElement) {
var currentElement = elementView.model;
currentElement.attr('body/stroke', 'orange');
selectedElement = elementView.model;
} else
selectedElement = null;
});
paper.on('blank:pointerdown', function(elementView) {
resetAll(this);
});
$('#dbLookupCanvas')
.attr('tabindex', 0)
.on('mouseover', function() {
this.focus();
})
.on('keydown', function(e) {
if (e.keyCode == 46)
if (selectedElement) selectedElement.remove();
});
}
function AddTable(xCoord = undefined, yCoord = undefined, portID = undefined) {
// This is a sample database data here
let data = [{
columnName: "radomData1"
},
{
columnName: "radomData2"
}
];
if (xCoord == undefined && yCoord == undefined) {
xCoord = 50;
yCoord = 50;
}
const rect = new joint.shapes.standard.Rectangle({
position: {
x: xCoord,
y: yCoord
},
size: {
width: 150,
height: 200
},
ports: {
groups: {
'a': {},
'b': {}
}
}
});
$.each(data, (i, v) => {
const port = {
group: 'a',
args: {}, // Extra arguments for the port layout function, see `layout.Port` section
label: {
position: {
name: 'right',
args: {
y: 6
} // Extra arguments for the label layout function, see `layout.PortLabel` section
},
markup: [{
tagName: 'text',
selector: 'label'
}]
},
attrs: {
body: {
magnet: true,
width: 16,
height: 16,
x: -8,
y: -4,
stroke: 'red',
fill: 'gray'
},
label: {
text: v.columnName,
fill: 'black'
}
},
markup: [{
tagName: 'rect',
selector: 'body'
}]
};
rect.addPort(port);
});
rect.resize(150, data.length * 40);
graph.addCell(rect);
}
function resetAll(paper) {
paper.drawBackground({
color: 'white'
});
var elements = paper.model.getElements();
for (var i = 0, ii = elements.length; i < ii; i++) {
var currentElement = elements[i];
currentElement.attr('body/stroke', 'black');
}
var links = paper.model.getLinks();
for (var j = 0, jj = links.length; j < jj; j++) {
var currentLink = links[j];
currentLink.attr('line/stroke', 'black');
currentLink.label(0, {
attrs: {
body: {
stroke: 'black'
}
}
});
}
}
Any help would be appreciated. Thanks!
If you add another condition to validateConnection
, it should do the trick.
If you compare the links of the source and target, then return false if they already contain a link with the same ID.
The following seems to work well in your JSFiddle.
validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
// Prevent link on body
if (magnetT == null)
return false;
// Prevent link to self
if (cellViewS === cellViewT)
return false;
const sourceCell = cellViewS.model;
const sourceLinks = graph.getConnectedLinks(sourceCell);
const targetCell = cellViewT.model;
const targetLinks = graph.getConnectedLinks(targetCell);
let isConnection;
// Compare link IDs of source and target elements
targetLinks.forEach((linkT) => {
sourceLinks.forEach((linkS) => {
if (linkS.id === linkT.id) isConnection = true;
});
});
// If source and target already contain a link with the same id , return false
if (isConnection) return false;
// Prevent linking from input ports
if ((magnetS !== magnetT))
return true;
},