feat: Implement intersection calculation for UML diagram node links
This commit is contained in:
parent
e99b1e644e
commit
a6cbce1749
2 changed files with 342 additions and 138 deletions
|
|
@ -1665,6 +1665,10 @@
|
|||
opmerkingen_inez: Geen eigen KvK inschrijving.
|
||||
samenwerkingsverband_platform: Collectie Gelderland
|
||||
museum_register: ja
|
||||
wikidata_id: Q2693553
|
||||
type:
|
||||
- M
|
||||
- F
|
||||
- plaatsnaam_bezoekadres: Arnhem
|
||||
straat_en_huisnummer_bezoekadres: Zijpendaalseweg 44
|
||||
organisatie: Kasteel Ammersoyen
|
||||
|
|
@ -1674,6 +1678,10 @@
|
|||
opmerkingen_inez: Geen eigen KvK inschrijving.
|
||||
samenwerkingsverband_platform: Collectie Gelderland
|
||||
museum_register: ja
|
||||
wikidata_id: Q2447969
|
||||
type:
|
||||
- M
|
||||
- F
|
||||
- plaatsnaam_bezoekadres: Arnhem
|
||||
straat_en_huisnummer_bezoekadres: Zijpendaalseweg 44
|
||||
organisatie: Kasteel Cannenburch
|
||||
|
|
@ -1682,6 +1690,10 @@
|
|||
type_organisatie: opengesteld monument
|
||||
opmerkingen_inez: Geen eigen KvK inschrijving.
|
||||
museum_register: ja
|
||||
wikidata_id: Q2175336
|
||||
type:
|
||||
- M
|
||||
- F
|
||||
- plaatsnaam_bezoekadres: Doornenburg
|
||||
straat_en_huisnummer_bezoekadres: Kerkstraat 27
|
||||
organisatie: Stichting tot Behoud van den Doornenburg
|
||||
|
|
@ -1693,6 +1705,10 @@
|
|||
museum_register: ja
|
||||
in_scope_voor_dc4eu: ja
|
||||
dc4eu_aansluit_route: via CN set=Collectie Gelderland
|
||||
wikidata_id: Q2012221
|
||||
type:
|
||||
- M
|
||||
- F
|
||||
- plaatsnaam_bezoekadres: Arnhem
|
||||
straat_en_huisnummer_bezoekadres: Zijpendaalseweg 44
|
||||
organisatie: Kasteel Doorwerth
|
||||
|
|
@ -1703,6 +1719,10 @@
|
|||
een belangenvereniging maar zijn niet de beheerders.
|
||||
samenwerkingsverband_platform: Collectie Gelderland
|
||||
museum_register: ja
|
||||
wikidata_id: Q680425
|
||||
type:
|
||||
- M
|
||||
- F
|
||||
- plaatsnaam_bezoekadres: Arnhem
|
||||
straat_en_huisnummer_bezoekadres: Zijpendaalseweg 44
|
||||
organisatie: Kasteel Hernen
|
||||
|
|
@ -1712,6 +1732,10 @@
|
|||
opmerkingen_inez: Geen eigen KvK inschrijving.
|
||||
samenwerkingsverband_platform: Collectie Gelderland
|
||||
museum_register: ja
|
||||
wikidata_id: Q4322946
|
||||
type:
|
||||
- M
|
||||
- F
|
||||
- plaatsnaam_bezoekadres: Arnhem
|
||||
straat_en_huisnummer_bezoekadres: Zijpendaalseweg 44
|
||||
organisatie: Kasteel Rosendael
|
||||
|
|
@ -1721,6 +1745,10 @@
|
|||
opmerkingen_inez: Geen eigen KvK inschrijving.
|
||||
samenwerkingsverband_platform: Collectie Gelderland
|
||||
museum_register: ja
|
||||
wikidata_id: Q2047615
|
||||
type:
|
||||
- M
|
||||
- F
|
||||
- plaatsnaam_bezoekadres: Nijmegen
|
||||
straat_en_huisnummer_bezoekadres: Erasmuslaan 36
|
||||
organisatie: Katholiek Documentatiecentrum in Nijmegen
|
||||
|
|
@ -1729,6 +1757,9 @@
|
|||
type_organisatie: documentatiecentrum
|
||||
opmerkingen_inez: Geen eigen KvK inschrijving.
|
||||
isil-code_na: NL-NmKDC
|
||||
wikidata_id: Q13742228
|
||||
type:
|
||||
- A
|
||||
- plaatsnaam_bezoekadres: Twello
|
||||
straat_en_huisnummer_bezoekadres: Dorpsstraat 11a
|
||||
organisatie: Historische Vereniging Voorst
|
||||
|
|
@ -1738,6 +1769,9 @@
|
|||
collectie_nederland: ja
|
||||
in_scope_voor_dc4eu: ja
|
||||
dc4eu_aansluit_route: via CN set=Collectie Gelderland
|
||||
wikidata_id: Q98895215
|
||||
type:
|
||||
- S
|
||||
- plaatsnaam_bezoekadres: Arnhem
|
||||
straat_en_huisnummer_bezoekadres: Velperweg 147
|
||||
organisatie: Koninklijk Tehuis voor Oud-Militairen en Museum Bronbeek
|
||||
|
|
@ -1749,12 +1783,17 @@
|
|||
rijkscollectie: ja
|
||||
linked_data: ja
|
||||
datasetregister: Stamboeken (colonialcollections)
|
||||
wikidata_id: Q61930724
|
||||
type:
|
||||
- M
|
||||
- plaatsnaam_bezoekadres: Apeldoorn
|
||||
straat_en_huisnummer_bezoekadres: St Eustatius 18
|
||||
organisatie: Stichting Korpora
|
||||
webadres_organisatie: https://www.korpora.nl/
|
||||
type_organisatie: kenniscentrum
|
||||
museum_register: ja
|
||||
type:
|
||||
- S
|
||||
- plaatsnaam_bezoekadres: Otterlo
|
||||
straat_en_huisnummer_bezoekadres: Houtkampweg 6
|
||||
organisatie: Stichting Kröller-Müller Museum
|
||||
|
|
@ -1766,12 +1805,18 @@
|
|||
museum_register: ja
|
||||
rijkscollectie: ja
|
||||
van_gogh_worldwide: ja
|
||||
wikidata_id: Q1051928
|
||||
type:
|
||||
- M
|
||||
- plaatsnaam_bezoekadres: Arnhem
|
||||
straat_en_huisnummer_bezoekadres: Markt 11
|
||||
organisatie: provincie Gelderland
|
||||
webadres_organisatie: https://www.gelderland.nl/themas/organisatie/over-de-provincie/huis-der-provincie/provinciale-kunstcollectie
|
||||
type_organisatie: provincie
|
||||
samenwerkingsverband_platform: Collectie Gelderland
|
||||
wikidata_id: Q775
|
||||
type:
|
||||
- O
|
||||
- plaatsnaam_bezoekadres: Buren
|
||||
organisatie: Marechausseemuseum
|
||||
type_organisatie: museum
|
||||
|
|
@ -1779,6 +1824,9 @@
|
|||
Nationaal Militair Museum in Soesterberg.
|
||||
museum_register: ja
|
||||
rijkscollectie: ja
|
||||
wikidata_id: Q18558954
|
||||
type:
|
||||
- M
|
||||
- plaatsnaam_bezoekadres: Arnhem
|
||||
straat_en_huisnummer_bezoekadres: Utrechtseweg 87
|
||||
organisatie: Stichting Museum Arnhem
|
||||
|
|
@ -1790,6 +1838,9 @@
|
|||
museum_register: ja
|
||||
modemuze: ja
|
||||
delfts_aardewerk: ja
|
||||
wikidata_id: Q2114028
|
||||
type:
|
||||
- M
|
||||
- plaatsnaam_bezoekadres: Bennekom
|
||||
straat_en_huisnummer_bezoekadres: Kerkstraat 1
|
||||
organisatie: Kijk- en Luistermuseum
|
||||
|
|
@ -1800,12 +1851,18 @@
|
|||
museum_register: ja
|
||||
in_scope_voor_dc4eu: ja
|
||||
dc4eu_aansluit_route: via CN set=Collectie Gelderland
|
||||
wikidata_id: Q13743314
|
||||
type:
|
||||
- M
|
||||
- plaatsnaam_bezoekadres: Wageningen
|
||||
straat_en_huisnummer_bezoekadres: Bowlespark 1A
|
||||
organisatie: Stichting Historisch Museum De Casteelse Poort
|
||||
webadres_organisatie: https://www.casteelsepoort.nl/
|
||||
type_organisatie: museum
|
||||
museum_register: ja
|
||||
wikidata_id: Q2546552
|
||||
type:
|
||||
- M
|
||||
- plaatsnaam_bezoekadres: Elburg
|
||||
straat_en_huisnummer_bezoekadres: Jufferenstraat 6-8
|
||||
organisatie: Stichting Museum Elburg
|
||||
|
|
@ -1814,6 +1871,9 @@
|
|||
samenwerkingsverband_platform: Collectie Gelderland
|
||||
collectie_nederland: ja
|
||||
museum_register: ja
|
||||
wikidata_id: Q56459645
|
||||
type:
|
||||
- M
|
||||
- plaatsnaam_bezoekadres: Zutphen
|
||||
straat_en_huisnummer_bezoekadres: ’s Gravenhof 4
|
||||
organisatie: Stichting Musea Zutphen (Stedelijk Museum Zutphen en Museum Henriette
|
||||
|
|
@ -1822,7 +1882,9 @@
|
|||
type_organisatie: museum
|
||||
samenwerkingsverband_platform: Collectie Gelderland
|
||||
museum_register: ja
|
||||
|
||||
wikidata_id: Q56423939
|
||||
type:
|
||||
- M
|
||||
- plaatsnaam_bezoekadres: Wijchen
|
||||
straat_en_huisnummer_bezoekadres: Kasteellaan 9
|
||||
organisatie: Stichting Museum Kasteel Wijchen
|
||||
|
|
|
|||
|
|
@ -265,6 +265,72 @@ export const UMLVisualization: React.FC<UMLVisualizationProps> = ({
|
|||
const methodHeight = 24;
|
||||
const nodePadding = 10;
|
||||
|
||||
// Helper function to calculate intersection point of a line with a rectangular box border
|
||||
// Given a line from (x1,y1) to (x2,y2) and a rectangle centered at (cx,cy) with width w and height h,
|
||||
// returns the point where the line intersects the rectangle border (from center outward)
|
||||
const getBoxBorderIntersection = (
|
||||
cx: number, cy: number, // Rectangle center
|
||||
w: number, h: number, // Rectangle width and height
|
||||
targetX: number, targetY: number // Target point (other end of line)
|
||||
): { x: number; y: number } => {
|
||||
const dx = targetX - cx;
|
||||
const dy = targetY - cy;
|
||||
|
||||
// If target is at center, return center
|
||||
if (dx === 0 && dy === 0) {
|
||||
return { x: cx, y: cy };
|
||||
}
|
||||
|
||||
const halfW = w / 2;
|
||||
const halfH = h / 2;
|
||||
|
||||
// Calculate intersection with each edge and find the closest one
|
||||
// The line exits through the edge that has the smallest positive t value
|
||||
let t = Infinity;
|
||||
|
||||
// Right edge (x = cx + halfW)
|
||||
if (dx > 0) {
|
||||
const tRight = halfW / dx;
|
||||
if (tRight < t && Math.abs(dy * tRight) <= halfH) {
|
||||
t = tRight;
|
||||
}
|
||||
}
|
||||
|
||||
// Left edge (x = cx - halfW)
|
||||
if (dx < 0) {
|
||||
const tLeft = -halfW / dx;
|
||||
if (tLeft < t && Math.abs(dy * tLeft) <= halfH) {
|
||||
t = tLeft;
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom edge (y = cy + halfH)
|
||||
if (dy > 0) {
|
||||
const tBottom = halfH / dy;
|
||||
if (tBottom < t && Math.abs(dx * tBottom) <= halfW) {
|
||||
t = tBottom;
|
||||
}
|
||||
}
|
||||
|
||||
// Top edge (y = cy - halfH)
|
||||
if (dy < 0) {
|
||||
const tTop = -halfH / dy;
|
||||
if (tTop < t && Math.abs(dx * tTop) <= halfW) {
|
||||
t = tTop;
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid intersection found (shouldn't happen), return center
|
||||
if (t === Infinity) {
|
||||
return { x: cx, y: cy };
|
||||
}
|
||||
|
||||
return {
|
||||
x: cx + dx * t,
|
||||
y: cy + dy * t
|
||||
};
|
||||
};
|
||||
|
||||
diagram.nodes.forEach(node => {
|
||||
const attributeCount = node.attributes?.length || 0;
|
||||
const methodCount = node.methods?.length || 0;
|
||||
|
|
@ -403,130 +469,7 @@ export const UMLVisualization: React.FC<UMLVisualizationProps> = ({
|
|||
link.isReversed = link.isReversed || false;
|
||||
});
|
||||
|
||||
// Draw nodes FIRST (so they appear behind links/arrows in SVG z-order)
|
||||
// In SVG, elements drawn later appear on top - we want arrows visible above node boxes
|
||||
const nodes = g.append('g')
|
||||
.attr('class', 'nodes')
|
||||
.selectAll('g')
|
||||
.data(diagram.nodes)
|
||||
.join('g')
|
||||
.attr('class', (d) => `node node-${d.type}`)
|
||||
.call(d3.drag<any, any>()
|
||||
.on('start', dragstarted)
|
||||
.on('drag', dragged)
|
||||
.on('end', dragended) as any);
|
||||
|
||||
// Node background
|
||||
nodes.append('rect')
|
||||
.attr('class', 'node-rect')
|
||||
.attr('width', (d) => d.width || nodeWidth)
|
||||
.attr('height', (d) => d.height || nodeHeaderHeight)
|
||||
.attr('rx', 8)
|
||||
.attr('fill', 'white')
|
||||
.attr('stroke', (d) => d.type === 'enum' ? '#ffc107' : '#0a3dfa')
|
||||
.attr('stroke-width', 2)
|
||||
.on('click', (event, d) => {
|
||||
event.stopPropagation();
|
||||
setSelectedNode(d);
|
||||
});
|
||||
|
||||
// Node header background
|
||||
nodes.append('rect')
|
||||
.attr('class', 'node-header')
|
||||
.attr('width', (d) => d.width || nodeWidth)
|
||||
.attr('height', nodeHeaderHeight)
|
||||
.attr('rx', 8)
|
||||
.attr('fill', (d) => d.type === 'enum' ? '#ffc107' : '#0a3dfa')
|
||||
.attr('opacity', 0.1);
|
||||
|
||||
// Node name
|
||||
nodes.append('text')
|
||||
.attr('class', 'node-name')
|
||||
.attr('x', (d) => (d.width || nodeWidth) / 2)
|
||||
.attr('y', nodeHeaderHeight / 2)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('fill', '#172a59')
|
||||
.attr('font-weight', 'bold')
|
||||
.attr('font-size', '14px')
|
||||
.text((d) => d.name);
|
||||
|
||||
// Node type badge
|
||||
nodes.append('text')
|
||||
.attr('class', 'node-type')
|
||||
.attr('x', 8)
|
||||
.attr('y', 12)
|
||||
.attr('fill', '#666')
|
||||
.attr('font-size', '10px')
|
||||
.attr('font-style', 'italic')
|
||||
.text((d) => `«${d.type}»`);
|
||||
|
||||
// Draw attributes section
|
||||
nodes.each(function(d) {
|
||||
if (!d.attributes || d.attributes.length === 0) return;
|
||||
|
||||
const nodeGroup = d3.select(this);
|
||||
let yOffset = nodeHeaderHeight + nodePadding;
|
||||
|
||||
// Attributes divider
|
||||
nodeGroup.append('line')
|
||||
.attr('x1', 0)
|
||||
.attr('y1', nodeHeaderHeight)
|
||||
.attr('x2', d.width || nodeWidth)
|
||||
.attr('y2', nodeHeaderHeight)
|
||||
.attr('stroke', '#0a3dfa')
|
||||
.attr('stroke-width', 1);
|
||||
|
||||
// Attribute entries
|
||||
d.attributes.forEach((attr, i) => {
|
||||
nodeGroup.append('text')
|
||||
.attr('class', 'node-attribute')
|
||||
.attr('x', 10)
|
||||
.attr('y', yOffset + i * attributeHeight)
|
||||
.attr('fill', '#172a59')
|
||||
.attr('font-size', '12px')
|
||||
.text(`${attr.name}: ${attr.type}`);
|
||||
});
|
||||
|
||||
yOffset += d.attributes.length * attributeHeight;
|
||||
});
|
||||
|
||||
// Draw methods section
|
||||
nodes.each(function(d) {
|
||||
if (!d.methods || d.methods.length === 0) return;
|
||||
|
||||
const nodeGroup = d3.select(this);
|
||||
const attributeCount = d.attributes?.length || 0;
|
||||
let yOffset = nodeHeaderHeight + nodePadding +
|
||||
(attributeCount > 0 ? attributeCount * attributeHeight + nodePadding : 0);
|
||||
|
||||
// Methods divider
|
||||
nodeGroup.append('line')
|
||||
.attr('x1', 0)
|
||||
.attr('y1', yOffset - nodePadding)
|
||||
.attr('x2', d.width || nodeWidth)
|
||||
.attr('y2', yOffset - nodePadding)
|
||||
.attr('stroke', '#0a3dfa')
|
||||
.attr('stroke-width', 1);
|
||||
|
||||
// Method entries
|
||||
d.methods.forEach((method, i) => {
|
||||
const methodText = method.returnType
|
||||
? `${method.name}(): ${method.returnType}`
|
||||
: `${method.name}()`;
|
||||
|
||||
nodeGroup.append('text')
|
||||
.attr('class', 'node-method')
|
||||
.attr('x', 10)
|
||||
.attr('y', yOffset + i * methodHeight)
|
||||
.attr('fill', '#172a59')
|
||||
.attr('font-size', '12px')
|
||||
.text(methodText);
|
||||
});
|
||||
});
|
||||
|
||||
// Draw links AFTER nodes (so they appear on top in SVG z-order)
|
||||
// This ensures arrows are visible above node boxes
|
||||
// Draw links first (edges between nodes)
|
||||
const links = g.append('g')
|
||||
.attr('class', 'links')
|
||||
.selectAll('g')
|
||||
|
|
@ -665,14 +608,164 @@ export const UMLVisualization: React.FC<UMLVisualizationProps> = ({
|
|||
return label;
|
||||
});
|
||||
|
||||
// Draw nodes (on top of links so boxes appear above edge lines)
|
||||
const nodes = g.append('g')
|
||||
.attr('class', 'nodes')
|
||||
.selectAll('g')
|
||||
.data(diagram.nodes)
|
||||
.join('g')
|
||||
.attr('class', (d) => `node node-${d.type}`)
|
||||
.call(d3.drag<any, any>()
|
||||
.on('start', dragstarted)
|
||||
.on('drag', dragged)
|
||||
.on('end', dragended) as any);
|
||||
|
||||
// Node background
|
||||
nodes.append('rect')
|
||||
.attr('class', 'node-rect')
|
||||
.attr('width', (d) => d.width || nodeWidth)
|
||||
.attr('height', (d) => d.height || nodeHeaderHeight)
|
||||
.attr('rx', 8)
|
||||
.attr('fill', 'white')
|
||||
.attr('stroke', (d) => d.type === 'enum' ? '#ffc107' : '#0a3dfa')
|
||||
.attr('stroke-width', 2)
|
||||
.on('click', (event, d) => {
|
||||
event.stopPropagation();
|
||||
setSelectedNode(d);
|
||||
});
|
||||
|
||||
// Node header background
|
||||
nodes.append('rect')
|
||||
.attr('class', 'node-header')
|
||||
.attr('width', (d) => d.width || nodeWidth)
|
||||
.attr('height', nodeHeaderHeight)
|
||||
.attr('rx', 8)
|
||||
.attr('fill', (d) => d.type === 'enum' ? '#ffc107' : '#0a3dfa')
|
||||
.attr('opacity', 0.1);
|
||||
|
||||
// Node name
|
||||
nodes.append('text')
|
||||
.attr('class', 'node-name')
|
||||
.attr('x', (d) => (d.width || nodeWidth) / 2)
|
||||
.attr('y', nodeHeaderHeight / 2)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('fill', '#172a59')
|
||||
.attr('font-weight', 'bold')
|
||||
.attr('font-size', '14px')
|
||||
.text((d) => d.name);
|
||||
|
||||
// Node type badge
|
||||
nodes.append('text')
|
||||
.attr('class', 'node-type')
|
||||
.attr('x', 8)
|
||||
.attr('y', 12)
|
||||
.attr('fill', '#666')
|
||||
.attr('font-size', '10px')
|
||||
.attr('font-style', 'italic')
|
||||
.text((d) => `«${d.type}»`);
|
||||
|
||||
// Draw attributes section
|
||||
nodes.each(function(d) {
|
||||
if (!d.attributes || d.attributes.length === 0) return;
|
||||
|
||||
const nodeGroup = d3.select(this);
|
||||
let yOffset = nodeHeaderHeight + nodePadding;
|
||||
|
||||
// Attributes divider
|
||||
nodeGroup.append('line')
|
||||
.attr('x1', 0)
|
||||
.attr('y1', nodeHeaderHeight)
|
||||
.attr('x2', d.width || nodeWidth)
|
||||
.attr('y2', nodeHeaderHeight)
|
||||
.attr('stroke', '#0a3dfa')
|
||||
.attr('stroke-width', 1);
|
||||
|
||||
// Attribute entries
|
||||
d.attributes.forEach((attr, i) => {
|
||||
nodeGroup.append('text')
|
||||
.attr('class', 'node-attribute')
|
||||
.attr('x', 10)
|
||||
.attr('y', yOffset + i * attributeHeight)
|
||||
.attr('fill', '#172a59')
|
||||
.attr('font-size', '12px')
|
||||
.text(`${attr.name}: ${attr.type}`);
|
||||
});
|
||||
|
||||
yOffset += d.attributes.length * attributeHeight;
|
||||
});
|
||||
|
||||
// Draw methods section
|
||||
nodes.each(function(d) {
|
||||
if (!d.methods || d.methods.length === 0) return;
|
||||
|
||||
const nodeGroup = d3.select(this);
|
||||
const attributeCount = d.attributes?.length || 0;
|
||||
let yOffset = nodeHeaderHeight + nodePadding +
|
||||
(attributeCount > 0 ? attributeCount * attributeHeight + nodePadding : 0);
|
||||
|
||||
// Methods divider
|
||||
nodeGroup.append('line')
|
||||
.attr('x1', 0)
|
||||
.attr('y1', yOffset - nodePadding)
|
||||
.attr('x2', d.width || nodeWidth)
|
||||
.attr('y2', yOffset - nodePadding)
|
||||
.attr('stroke', '#0a3dfa')
|
||||
.attr('stroke-width', 1);
|
||||
|
||||
// Method entries
|
||||
d.methods.forEach((method, i) => {
|
||||
const methodText = method.returnType
|
||||
? `${method.name}(): ${method.returnType}`
|
||||
: `${method.name}()`;
|
||||
|
||||
nodeGroup.append('text')
|
||||
.attr('class', 'node-method')
|
||||
.attr('x', 10)
|
||||
.attr('y', yOffset + i * methodHeight)
|
||||
.attr('fill', '#172a59')
|
||||
.attr('font-size', '12px')
|
||||
.text(methodText);
|
||||
});
|
||||
});
|
||||
|
||||
// Update positions on tick (force simulation) or immediately (dagre)
|
||||
if (simulation) {
|
||||
simulation.on('tick', () => {
|
||||
// Calculate edge endpoints at box borders for force simulation
|
||||
links.select('line')
|
||||
.attr('x1', (d: any) => d.source.x)
|
||||
.attr('y1', (d: any) => d.source.y)
|
||||
.attr('x2', (d: any) => d.target.x)
|
||||
.attr('y2', (d: any) => d.target.y);
|
||||
.attr('x1', (d: any) => {
|
||||
const sourceW = d.source.width || nodeWidth;
|
||||
const sourceH = d.source.height || nodeHeaderHeight;
|
||||
const intersection = getBoxBorderIntersection(
|
||||
d.source.x, d.source.y, sourceW, sourceH, d.target.x, d.target.y
|
||||
);
|
||||
return intersection.x;
|
||||
})
|
||||
.attr('y1', (d: any) => {
|
||||
const sourceW = d.source.width || nodeWidth;
|
||||
const sourceH = d.source.height || nodeHeaderHeight;
|
||||
const intersection = getBoxBorderIntersection(
|
||||
d.source.x, d.source.y, sourceW, sourceH, d.target.x, d.target.y
|
||||
);
|
||||
return intersection.y;
|
||||
})
|
||||
.attr('x2', (d: any) => {
|
||||
const targetW = d.target.width || nodeWidth;
|
||||
const targetH = d.target.height || nodeHeaderHeight;
|
||||
const intersection = getBoxBorderIntersection(
|
||||
d.target.x, d.target.y, targetW, targetH, d.source.x, d.source.y
|
||||
);
|
||||
return intersection.x;
|
||||
})
|
||||
.attr('y2', (d: any) => {
|
||||
const targetW = d.target.width || nodeWidth;
|
||||
const targetH = d.target.height || nodeHeaderHeight;
|
||||
const intersection = getBoxBorderIntersection(
|
||||
d.target.x, d.target.y, targetW, targetH, d.source.x, d.source.y
|
||||
);
|
||||
return intersection.y;
|
||||
});
|
||||
|
||||
links.select('text')
|
||||
.attr('x', (d: any) => (d.source.x + d.target.x) / 2)
|
||||
|
|
@ -682,22 +775,47 @@ export const UMLVisualization: React.FC<UMLVisualizationProps> = ({
|
|||
});
|
||||
} else {
|
||||
// Dagre layout - positions are already computed, update immediately
|
||||
// Calculate edge endpoints at box borders
|
||||
links.select('line')
|
||||
.attr('x1', (d: any) => {
|
||||
const source = diagram.nodes.find(n => n.id === (typeof d.source === 'string' ? d.source : d.source.id));
|
||||
return source?.x || 0;
|
||||
const target = diagram.nodes.find(n => n.id === (typeof d.target === 'string' ? d.target : d.target.id));
|
||||
if (!source || !target) return 0;
|
||||
const intersection = getBoxBorderIntersection(
|
||||
source.x!, source.y!, source.width || nodeWidth, source.height || nodeHeaderHeight,
|
||||
target.x!, target.y!
|
||||
);
|
||||
return intersection.x;
|
||||
})
|
||||
.attr('y1', (d: any) => {
|
||||
const source = diagram.nodes.find(n => n.id === (typeof d.source === 'string' ? d.source : d.source.id));
|
||||
return source?.y || 0;
|
||||
const target = diagram.nodes.find(n => n.id === (typeof d.target === 'string' ? d.target : d.target.id));
|
||||
if (!source || !target) return 0;
|
||||
const intersection = getBoxBorderIntersection(
|
||||
source.x!, source.y!, source.width || nodeWidth, source.height || nodeHeaderHeight,
|
||||
target.x!, target.y!
|
||||
);
|
||||
return intersection.y;
|
||||
})
|
||||
.attr('x2', (d: any) => {
|
||||
const source = diagram.nodes.find(n => n.id === (typeof d.source === 'string' ? d.source : d.source.id));
|
||||
const target = diagram.nodes.find(n => n.id === (typeof d.target === 'string' ? d.target : d.target.id));
|
||||
return target?.x || 0;
|
||||
if (!source || !target) return 0;
|
||||
const intersection = getBoxBorderIntersection(
|
||||
target.x!, target.y!, target.width || nodeWidth, target.height || nodeHeaderHeight,
|
||||
source.x!, source.y!
|
||||
);
|
||||
return intersection.x;
|
||||
})
|
||||
.attr('y2', (d: any) => {
|
||||
const source = diagram.nodes.find(n => n.id === (typeof d.source === 'string' ? d.source : d.source.id));
|
||||
const target = diagram.nodes.find(n => n.id === (typeof d.target === 'string' ? d.target : d.target.id));
|
||||
return target?.y || 0;
|
||||
if (!source || !target) return 0;
|
||||
const intersection = getBoxBorderIntersection(
|
||||
target.x!, target.y!, target.width || nodeWidth, target.height || nodeHeaderHeight,
|
||||
source.x!, source.y!
|
||||
);
|
||||
return intersection.y;
|
||||
});
|
||||
|
||||
links.select('text')
|
||||
|
|
@ -732,23 +850,47 @@ export const UMLVisualization: React.FC<UMLVisualizationProps> = ({
|
|||
nodes.attr('transform', (node: any) =>
|
||||
`translate(${node.x - (node.width || nodeWidth) / 2}, ${node.y - (node.height || nodeHeaderHeight) / 2})`
|
||||
);
|
||||
// Update links
|
||||
// Update links with border intersection calculation
|
||||
links.select('line')
|
||||
.attr('x1', (link: any) => {
|
||||
const source = diagram.nodes.find(n => n.id === (typeof link.source === 'string' ? link.source : link.source.id));
|
||||
return source?.x || 0;
|
||||
const target = diagram.nodes.find(n => n.id === (typeof link.target === 'string' ? link.target : link.target.id));
|
||||
if (!source || !target) return 0;
|
||||
const intersection = getBoxBorderIntersection(
|
||||
source.x!, source.y!, source.width || nodeWidth, source.height || nodeHeaderHeight,
|
||||
target.x!, target.y!
|
||||
);
|
||||
return intersection.x;
|
||||
})
|
||||
.attr('y1', (link: any) => {
|
||||
const source = diagram.nodes.find(n => n.id === (typeof link.source === 'string' ? link.source : link.source.id));
|
||||
return source?.y || 0;
|
||||
const target = diagram.nodes.find(n => n.id === (typeof link.target === 'string' ? link.target : link.target.id));
|
||||
if (!source || !target) return 0;
|
||||
const intersection = getBoxBorderIntersection(
|
||||
source.x!, source.y!, source.width || nodeWidth, source.height || nodeHeaderHeight,
|
||||
target.x!, target.y!
|
||||
);
|
||||
return intersection.y;
|
||||
})
|
||||
.attr('x2', (link: any) => {
|
||||
const source = diagram.nodes.find(n => n.id === (typeof link.source === 'string' ? link.source : link.source.id));
|
||||
const target = diagram.nodes.find(n => n.id === (typeof link.target === 'string' ? link.target : link.target.id));
|
||||
return target?.x || 0;
|
||||
if (!source || !target) return 0;
|
||||
const intersection = getBoxBorderIntersection(
|
||||
target.x!, target.y!, target.width || nodeWidth, target.height || nodeHeaderHeight,
|
||||
source.x!, source.y!
|
||||
);
|
||||
return intersection.x;
|
||||
})
|
||||
.attr('y2', (link: any) => {
|
||||
const source = diagram.nodes.find(n => n.id === (typeof link.source === 'string' ? link.source : link.source.id));
|
||||
const target = diagram.nodes.find(n => n.id === (typeof link.target === 'string' ? link.target : link.target.id));
|
||||
return target?.y || 0;
|
||||
if (!source || !target) return 0;
|
||||
const intersection = getBoxBorderIntersection(
|
||||
target.x!, target.y!, target.width || nodeWidth, target.height || nodeHeaderHeight,
|
||||
source.x!, source.y!
|
||||
);
|
||||
return intersection.y;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue