Hi @walter I have another question, please help me solve the following situation. I have a tree map where a destination node can receive multiple source links from different groups. Currently, I have a fixed order set to 1, but it should automatically determine the order of the links that reach the destination node. How can I also allow the user to change this automatic configuration to their preference, i.e., if they want the link they created to be number 1 or 2, and vice versa?
onMounted(() => {
getDestination();
getOrigins();
getMappingLinks();
diagram = new Diagram('treeMapperDiv', {
'commandHandler.copiesTree': true,
'commandHandler.deletesTree': true,
'linkingTool.archetypeLinkData': { category: 'Mapping' },
'linkingTool.linkValidation': checkLink,
'relinkingTool.linkValidation': checkLink,
'undoManager.isEnabled': true,
TreeCollapsed: handleTreeCollapseExpand,
TreeExpanded: handleTreeCollapseExpand,
LinkDrawn: async (e: DiagramEvent) => {
const link = e.subject.part;
if (link && link.data.category === 'Mapping') {
const fromNode = link.fromNode;
const toNode = link.toNode;
const parentNode = fromNode.findTreeParentNode();
const toGroup = toNode.containingGroup;
const mappingLink: MappingData = {
origin: parentNode ? parentNode.data.key : null,
from: link.data.from,
group: 0,
package_id: packageName.value,
destination: toGroup ? toGroup.data.key : null,
to: link.data.to,
order: 1,
config: configId
};
mappingLinks.push(mappingLink);
}
}
});
let previousLinkDataArray = [...(diagram.model as go.GraphLinksModel).linkDataArray];
// diagram.addModelChangedListener((e) => {
// if (e.modelChange === "linkDataArray") {
// const model = e.model as go.GraphLinksModel;
// if (model && Array.isArray(model.linkDataArray)) {
// console.log("model.linkDataArray", model.linkDataArray);
// // Comprobar el estado anterior y actual
// console.log("Previous link data:", previousLinkDataArray);
// console.log("Current link data:", model.linkDataArray);
// mappingLinks = model.linkDataArray
// .filter((link: any) => link.category === 'Mapping')
// .map((link: any) => ({
// origin: link.origin || '',
// from: link.from,
// to: link.to,
// group: link.group || -1,
// package_id: link.package_id || '',
// })) as MappingData[];
// console.log("Model changed - Updated mappingLinks:", mappingLinks.length);
// }
// }
// });
// diagram.addModelChangedListener((e) => {
// if (e.modelChange === "linkDataArray") {
// const model = e.model as go.GraphLinksModel;
// if (model && Array.isArray(model.linkDataArray)) {
// // Actualiza previousLinkDataArray con el estado actual
// previousLinkDataArray = [...model.linkDataArray];
// console.log("Previous link data:", previousLinkDataArray);
// }
// }
// });
diagram.addModelChangedListener(evt => {
// ignore unimportant Transaction events
if (!evt.isTransactionFinished) return;
const txn = evt.object; // a Transaction
if (txn === null) return;
// iterate over all of the actual ChangedEvents of the Transaction
txn.changes.each(e => {
// ignore any kind of change other than adding/removing a node
if (e.modelChange !== "nodeDataArray") return;
// record node insertions and removals
if (e.change === go.ChangeType.Insert) {
console.log(evt.propertyName + " added node with key: " + e.newValue.key);
} else if (e.change === go.ChangeType.Remove) {
console.log(evt.propertyName + " removed node with key: " + e.oldValue.key);
}
});
});
const selectionAdornmentTemplate = $(
go.Adornment, 'Auto',
$(go.Shape, 'RoundedRectangle', {
fill: null,
stroke: '#55C6C0',
strokeWidth: 2
}),
$(go.Placeholder)
);
// const cxElement = document.getElementById('contextMenu');
// const myContextMenu = new go.HTMLInfo({
// show: showContextMenu,
// hide: hideContextMenu
// });
// cxElement!.addEventListener('contextmenu', (e) => {
// e.preventDefault();
// return false;
// }, false);
// function hideCX() {
// if (diagram.currentTool instanceof go.ContextMenuTool) {
// diagram.currentTool.doCancel();
// }
// }
// function showContextMenu(obj, diagram, tool) {
// var cmd = diagram.commandHandler;
// var hasMenuItem = false;
// function maybeShowItem(elt, pred) {
// if (pred) {
// elt.style.display = 'block';
// hasMenuItem = true;
// } else {
// elt.style.display = 'none';
// }
// }
// maybeShowItem(document.getElementById('cut'), cmd.canCutSelection());
// maybeShowItem(document.getElementById('copy'), cmd.canCopySelection());
// maybeShowItem(document.getElementById('delete'), cmd.canDeleteSelection());
// maybeShowItem(document.getElementById('color'), obj !== null);
// if (hasMenuItem) {
// cxElement!.classList.add('show-menu');
// var mousePt = diagram.lastInput.viewPoint;
// cxElement!.style.left = mousePt.x + 5 + 'px';
// cxElement!.style.top = mousePt.y + 'px';
// }
// window.addEventListener('pointerdown', hideCX, true);
// }
// function hideContextMenu() {
// cxElement!.classList.remove('show-menu');
// window.removeEventListener('pointerdown', hideCX, true);
// }
// function cxcommand(event, val) {
// if (val === undefined) val = event.currentTarget.id;
// var myDiagram = diagram;
// switch (val) {
// case 'cut':
// myDiagram.commandHandler.cutSelection();
// break;
// case 'copy':
// myDiagram.commandHandler.copySelection();
// break;
// case 'delete':
// myDiagram.commandHandler.deleteSelection();
// break;
// }
// diagram.currentTool.stopTool();
// }
diagram.nodeTemplate = $(
TreeNode,
{
isTreeExpanded: false,
movable: false,
copyable: false,
deletable: false,
selectionAdorned: true,
// contextMenu: myContextMenu,
selectionAdornmentTemplate: selectionAdornmentTemplate,
background: 'white'
},
new Binding('fromLinkable', 'group', (k) => k !== formDestination),
new Binding('toLinkable', 'group', (k) => k === formDestination),
$('TreeExpanderButton', {
width: 14,
height: 14,
'ButtonIcon.stroke': 'white',
'ButtonIcon.strokeWidth': 2,
'ButtonBorder.fill': '#55C6C0',
'ButtonBorder.stroke': null,
'ButtonBorder.figure': 'Circle',
_buttonFillOver: '#00AAA1',
_buttonStrokeOver: null,
_buttonFillPressed: null
}),
$(
Panel,
'Auto',
{ position: new Point(16, -10) },
$(
go.Shape,
'RoundedRectangle',
{
fill: 'white',
strokeWidth: 1,
stroke: 'transparent',
name: 'SHAPE'
}
),
$(
go.TextBlock,
{
stroke: '#616161',
margin: new go.Margin(4, 0, 4, 0),
},
new go.Binding('text', 'text'),
new go.Binding("font", "", function (node) {
return hasChildren(node.part) ? "600 18px Figtree, sans-serif" : "16px Figtree, sans-serif";
}).ofObject()
)
)
);
diagram.linkTemplate = $(Link);
diagram.linkTemplate = $(
Link,
{
selectable: true,
routing: Routing.Orthogonal,
fromEndSegmentLength: 4,
toEndSegmentLength: 4,
fromSpot: new Spot(0.001, 1, 7, 0),
toSpot: Spot.Left
},
);
diagram.linkTemplateMap.add(
'Mapping',
$(
MappingLink,
{
isTreeLink: false,
isLayoutPositioned: false,
layerName: 'Foreground'
},
{ fromSpot: Spot.Right, toSpot: Spot.Left },
{ relinkableFrom: true, relinkableTo: true },
$(Shape, { name: 'link', stroke: '#C2C2C2', strokeWidth: 2 }
),
)
);
diagram.groupTemplate = $(
Group,
'Auto',
{ deletable: false, layout: makeGroupLayout(), selectionObjectName: 'card' },
new Binding('position', 'xy', Point.parse).makeTwoWay(Point.stringify),
new Binding('layout', 'width', makeGroupLayout),
$(
go.Shape,
'RoundedRectangle',
{
name: 'card',
fill: 'white',
stroke: '#E8E8E8'
}
),
$(
Panel,
'Vertical',
{ defaultAlignment: Spot.Left },
$(
TextBlock,
{
font: '600 20px Figtree, sans-serif',
stroke: '#005450',
margin: new Margin(5, 5, 0, 5)
},
new Binding('text')
),
$(Placeholder, { padding: 5 })
)
);
diagram.model = new go.GraphLinksModel(nodeDataArray.value, linkDataArray.value);
});
const checkLink = (
fn: Node,
fp: GraphObject,
tn: Node,
tp: GraphObject,
link: Link
) => {
// make sure the nodes are inside different Groups
if (fn.containingGroup === null || fn.containingGroup.data.key === formDestination)
return false;
if (tn.containingGroup === null) return false;
// optional limit to a single mapping link per node
// if (fn.linksConnected.any(l => l.category === "Mapping")) return false;
// if (tn.linksConnected.any(l => l.category === "Mapping")) return false;
return true;
};