Fix for generateRoundedPath

This commit is contained in:
Knut Sveidqvist
2025-02-11 10:04:26 +01:00
parent fa51a37d8c
commit 2247bc583f
5 changed files with 247 additions and 125 deletions

View File

@@ -91,31 +91,29 @@
<pre id="diagram4" class="mermaid"> <pre id="diagram4" class="mermaid">
--- ---
config: config:
look: neo look: classic
theme: forest
layout: elk
---
flowchart LR
A["A"] --> C
B("B B B B B") --> C[/"C C C C C"/]
C@{ shape: circle }
%%C@{ shape: question }
%%C@{ shape: stadium }
</pre>
<pre id="diagram4" class="mermaid">
---
config:
look: classic
layout: elk layout: elk
--- ---
flowchart LR flowchart LR
n1["n1"] --- C n1["n1"] --- C
B("Continue") --> C(("Evaluate")) B("Continue") --> C[/"Evaluate"/]
C -- One --> D["Option 1"] C -- One --> D["Option 1"]
C -- Two --> E["Option 2"] C -- Two --> E["Option 2"]
C -- Three --> F["fa:fa-car Option 3"] C -- Three --> F["fa:fa-car Option 3"]
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart TB
%% swimlane 1 - A E
%% swimlane 2 - B
%% swimlane 3 - C D
A --> E & B
B --> C
B@{ shape: cyl, label: 'Cylinder'}
</pre> </pre>
<pre id="diagram4" class="mermaid2"> <pre id="diagram4" class="mermaid2">
--- ---

View File

@@ -1,103 +1,173 @@
<!doctype html> <html>
<html lang="en">
<head> <head>
<meta charset="utf-8" /> <link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<title>Mindmap Mermaid Quick Test Page</title> <link
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" /> rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
rel="stylesheet"
/>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
rel="stylesheet"
/>
<style> <style>
div.mermaid { body {
/* font-family: 'trebuchet ms', verdana, arial; */ /* background: rgb(221, 208, 208); */
font-family: 'Courier New', Courier, monospace !important; /* background: #333; */
font-family: 'Arial';
/* color: white; */
/* font-size: 18px !important; */
} }
h1 {
color: grey;
}
.mermaid2 {
display: none;
}
.mermaid svg {
/* font-size: 18px !important; */
/* background-color: #efefef;
background-image: radial-gradient(#fff 51%, transparent 91%),
radial-gradient(#fff 51%, transparent 91%);
background-size: 20px 20px;
background-position:
0 0,
10px 10px;
background-repeat: repeat; */
}
.malware {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 150px;
background: red;
color: black;
display: flex;
display: flex;
justify-content: center;
align-items: center;
font-family: monospace;
font-size: 72px;
}
pre {
width: 100%;
}
/* tspan {
font-size: 6px !important;
} */
</style> </style>
</head> </head>
<body> <body>
<h1>Mindmap diagram demo</h1> <div class="flex gap-4">
<pre class="mermaid"> <pre id="diagram4" class="mermaid">
mindmap ---
root config:
child1((Circle)) look: classic
grandchild 1 theme: forest
grandchild 2 layout: elk
child2(Round rectangle) ---
grandchild 3 flowchart LR
grandchild 4 n1["n1"] --> C
child3[Square] n2("n2") --> C
grandchild 5 C@{ shape: circle }
::icon(mdi mdi-fire) </pre
gc6((grand<br/>child 6)) >
::icon(mdi mdi-fire) <pre id="diagram4" class="mermaid">
gc7((grand<br/>grand<br/>child 8)) ---
</pre> config:
look: neo
<h2>Mindmap with root wrapping text and a shape</h2> theme: redux
<pre class="mermaid"> layout: elk
mindmap ---
root[A root with a long text that wraps to keep the node size in check] flowchart LR
</pre> n1["n1"] --> C
n2("n2") --> C
C@{ shape: circle }
</pre
>
<pre id="diagram4" class="mermaid">
---
config:
look: handDrawn
theme: forest
layout: elk
---
flowchart LR
n1["n1"] --> C
n2("n2") --> C
C@{ shape: circle }
</pre
>
</div>
<script type="module"> <script type="module">
// import mermaid from './mermaid.esm.mjs'; import mermaid from './mermaid.esm.mjs';
import mermaid from '../../packages/mermaid/dist/mermaid.esm.mjs'; import layouts from './mermaid-layout-elk.esm.mjs';
// import mermaidMindmap from './mermaid-mindmap.esm.mjs';
// import mermaidMindmap from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-mindmap@9.3.0/+esm'; const staticBellIconPack = {
// await mermaid.registerExternalDiagrams([mermaidMindmap]); prefix: 'fa6-regular',
icons: {
bell: {
body: '<path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32v19.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416h400c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6c-28.3-35.5-43.8-79.6-43.8-125V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32m0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3c25.8-40 39.7-86.7 39.7-134.6V208c0-61.9 50.1-112 112-112m64 352H160c0 17 6.7 33.3 18.7 45.3S207 512 224 512s33.3-6.7 45.3-18.7S288 465 288 448"/>',
width: 448,
},
},
width: 512,
height: 512,
};
const ALLOWED_TAGS = [ mermaid.registerIconPacks([
'a', {
'b', name: 'logos',
'blockquote', loader: () =>
'br', fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()),
'dd', },
'div', {
'dl', name: 'fa',
'dt', loader: () => staticBellIconPack,
'em', },
'foreignObject', ]);
'h1', mermaid.registerLayoutLoaders(layouts);
'h2',
'h3',
'h4',
'h5',
'h6',
'h7',
'h8',
'hr',
'i',
'li',
'ul',
'ol',
'p',
'pre',
'span',
'strike',
'strong',
'table',
'tbody',
'td',
'tfoot',
'th',
'thead',
'tr',
];
mermaid.parseError = function (err, hash) { mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err); console.error('Mermaid error: ', err);
};
window.callback = function () {
alert('A callback was triggered');
}; };
mermaid.initialize({ mermaid.initialize({
theme: 'base', flowchart: { titleTopMargin: 10, useMaxWidth: false },
startOnLoad: true, fontSize: 12,
logLevel: 0, logLevel: 0,
flowchart: { securityLevel: 'loose',
useMaxWidth: false,
htmlLabels: true,
},
gantt: {
useMaxWidth: false,
},
useMaxWidth: false,
}); });
function callback() { function callback() {
alert('It worked'); alert('It worked');

View File

@@ -68,19 +68,25 @@ export const render = async (
// Add the element to the DOM // Add the element to the DOM
if (!node.isGroup) { if (!node.isGroup) {
const child: NodeWithVertex = { // const child: NodeWithVertex = {
...node, // ...node,
}; // };
graph.children.push(child); graph.children.push(node);
nodeDb[node.id] = child; nodeDb[node.id] = node;
const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir }); const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir });
const boundingBox = childNodeEl.node()!.getBBox(); const boundingBox = childNodeEl.node()!.getBBox();
child.domId = childNodeEl; node.domId = childNodeEl;
child.calcIntersect = node.calcIntersect; node.width = boundingBox.width;
child.intersect = node.intersect; node.height = boundingBox.height;
child.width = boundingBox.width; // child.calcIntersect = node.calcIntersect;
child.height = boundingBox.height; // child.intersect = node.intersect;
// child.width = boundingBox.width;
// child.height = boundingBox.height;
// child.updateIntersect = node.updateIntersect;
// if (child.updateIntersect) {
// child.updateIntersect();
// }
} else { } else {
// A subgraph // A subgraph
const child: NodeWithVertex & { children: NodeWithVertex[] } = { const child: NodeWithVertex & { children: NodeWithVertex[] } = {
@@ -820,7 +826,7 @@ export const render = async (
'spacing.nodeNode': 20, 'spacing.nodeNode': 20,
'spacing.nodeNodeBetweenLayers': 25, 'spacing.nodeNodeBetweenLayers': 25,
'spacing.edgeNode': 20, 'spacing.edgeNode': 20,
'spacing.edgeNodeBetweenLayers': 10, 'spacing.edgeNodeBetweenLayers': 20,
'spacing.edgeEdge': 10, 'spacing.edgeEdge': 10,
'spacing.edgeEdgeBetweenLayers': 20, 'spacing.edgeEdgeBetweenLayers': 20,
'spacing.nodeSelfLoop': 20, 'spacing.nodeSelfLoop': 20,
@@ -845,7 +851,7 @@ export const render = async (
// 'elk.layered.considerModelOrder.strategy': 'PREFER_NODES', // 'elk.layered.considerModelOrder.strategy': 'PREFER_NODES',
// 'elk.layered.wrapping.cutting.strategy': 'ARD', // NODES_AND_EDGES // 'elk.layered.wrapping.cutting.strategy': 'ARD', // NODES_AND_EDGES
// 'elk.layered.wrapping.cutting.strategy': 'NODES_AND_EDGES', // 'elk.layered.wrapping.cutting.strategy': 'NODES_AND_EDGES',
'elk.alignment': 'BOTTOM', // 'elk.alignment': 'BOTTOM',
// 'elk.layered.nodePlacement.bk.fixedAlignment': 'RIGHTDOWN', // 'elk.layered.nodePlacement.bk.fixedAlignment': 'RIGHTDOWN',
// 'elk.edgeRouting': 'UNDEFINED', // 'elk.edgeRouting': 'UNDEFINED',
'elk.layered.crossingMinimization.forceNodeModelOrder': false, 'elk.layered.crossingMinimization.forceNodeModelOrder': false,
@@ -1046,12 +1052,14 @@ export const render = async (
const intersection = startNode.intersect(edge.points[0]); const intersection = startNode.intersect(edge.points[0]);
if (distance(intersection2, edge.points[0]) > epsilon) { if (distance(intersection2, edge.points[0]) > epsilon) {
// intersection.x = -startNode.offset.posX + intersection.x + startNode.width / 2;
// intersection.y = intersection.y + startNode.height / 2 + 5;
edge.points.unshift(intersection2); edge.points.unshift(intersection2);
} }
} }
if (endNode.intersect) { if (endNode.intersect) {
// Remove the last point of the edge points // Remove the last point of the edge points
edge.points.pop(); // edge.points.pop();
const intersection2 = endNode.calcIntersect( const intersection2 = endNode.calcIntersect(
{ {
x: endNode.offset.posX + endNode.width / 2, x: endNode.offset.posX + endNode.width / 2,
@@ -1073,9 +1081,27 @@ export const render = async (
console.log('APA13 intersection2.2', intersection, intersection2); console.log('APA13 intersection2.2', intersection, intersection2);
if (distance(intersection2, edge.points[edge.points.length - 1]) > epsilon) { if (distance(intersection2, edge.points[edge.points.length - 1]) > epsilon) {
// console.log('APA13! distance ok\nintersection:', intersection); console.log(
'APA13! distance ok\nintersection:',
intersection,
'\nstartNode:',
startNode.id,
'\nendNode:',
endNode.id,
'\n distance',
distance(intersection2, edge.points[edge.points.length - 1])
);
edge.points.push(intersection2); edge.points.push(intersection2);
// console.log('APA13! distance ok\npoints:', edge.points); // console.log('APA13! distance ok\npoints:', edge.points);
} else {
console.log(
'APA13! distance not ok\nintersection:',
intersection,
'\nstartNode:',
startNode.id,
'\nendNode:',
endNode.id
);
} }
} }

View File

@@ -15,6 +15,13 @@ import createLabel from './createLabel.js';
import { addEdgeMarkers } from './edgeMarker.ts'; import { addEdgeMarkers } from './edgeMarker.ts';
import { isLabelStyle } from './shapes/handDrawnShapeStyles.js'; import { isLabelStyle } from './shapes/handDrawnShapeStyles.js';
function distance(p1, p2) {
if (!p1 || !p2) {
return 0;
}
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
}
const edgeLabels = new Map(); const edgeLabels = new Map();
const terminalLabels = new Map(); const terminalLabels = new Map();
@@ -370,7 +377,6 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
} }
edgeClassStyles.push(edge.cssCompiledStyles[key]); edgeClassStyles.push(edge.cssCompiledStyles[key]);
} }
console.log('APA13 edge.trim', edge.trim);
if (head.intersect && tail.intersect && edge.trim) { if (head.intersect && tail.intersect && edge.trim) {
points = points.slice(1, edge.points.length - 1); points = points.slice(1, edge.points.length - 1);
points.unshift(tail.intersect(points[0])); points.unshift(tail.intersect(points[0]));
@@ -405,6 +411,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
} }
let lineData = points.filter((p) => !Number.isNaN(p.y)); let lineData = points.filter((p) => !Number.isNaN(p.y));
console.log('APA13 lineData ', lineData);
//lineData = fixCorners(lineData); //lineData = fixCorners(lineData);
let curve = curveBasis; let curve = curveBasis;
curve = curveLinear; curve = curveLinear;
@@ -460,7 +467,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
let svgPath; let svgPath;
let linePath = let linePath =
edge.curve === 'rounded' edge.curve === 'rounded'
? generateRoundedPath(applyMarkerOffsetsToPoints(lineData, edge), 5) ? generateRoundedPath(applyMarkerOffsetsToPoints(lineData, edge), 3)
: lineFunction(lineData); : lineFunction(lineData);
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style]; const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
let animatedEdge = false; let animatedEdge = false;
@@ -590,6 +597,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
* @returns {String} - SVG path data string * @returns {String} - SVG path data string
*/ */
function generateRoundedPath(points, radius) { function generateRoundedPath(points, radius) {
console.log('APA13 points', points);
if (points.length < 2) { if (points.length < 2) {
return ''; return '';
} }
@@ -597,7 +605,7 @@ function generateRoundedPath(points, radius) {
let path = ''; let path = '';
const size = points.length; const size = points.length;
const epsilon = 1e-5; const epsilon = 1e-5;
const lastPoint = points[size - 1];
for (let i = 0; i < size; i++) { for (let i = 0; i < size; i++) {
const currPoint = points[i]; const currPoint = points[i];
const prevPoint = points[i - 1]; const prevPoint = points[i - 1];
@@ -639,6 +647,10 @@ function generateRoundedPath(points, radius) {
// Skip rounding if the angle is too small or too close to 180 degrees // Skip rounding if the angle is too small or too close to 180 degrees
if (angle < epsilon || Math.abs(Math.PI - angle) < epsilon) { if (angle < epsilon || Math.abs(Math.PI - angle) < epsilon) {
console.log(
'APA13 Skip rounding if the angle is too small',
`L${currPoint.x},${currPoint.y}`
);
path += `L${currPoint.x},${currPoint.y}`; path += `L${currPoint.x},${currPoint.y}`;
continue; continue;
} }
@@ -652,11 +664,21 @@ function generateRoundedPath(points, radius) {
const endX = currPoint.x + nx2 * cutLen; const endX = currPoint.x + nx2 * cutLen;
const endY = currPoint.y + ny2 * cutLen; const endY = currPoint.y + ny2 * cutLen;
// Draw the line to the start of the curve const d = distance(currPoint, lastPoint);
path += `L${startX},${startY}`; if (d > 3) {
// Draw the line to the start of the curve
path += `L${startX},${startY}`;
// Draw the quadratic Bezier curve // Draw the quadratic Bezier curve
path += `Q${currPoint.x},${currPoint.y} ${endX},${endY}`; path += `Q${currPoint.x},${currPoint.y} ${endX},${endY}`;
} else {
// // Draw the line to the start of the curve
// path += `L${startX},${startY}`;
// path += `Q${currPoint.x},${currPoint.y} ${currPoint.x},${currPoint.y}`;
// Draw the line to the start of the curve
// path += `L${startX},${startY}`;
path += `L${lastPoint.x},${lastPoint.y}`;
}
} }
} }

View File

@@ -54,6 +54,12 @@ export async function circle<T extends SVGGraphicsElement>(parent: D3Selection<T
log.info('Circle intersect', node, radius, point); log.info('Circle intersect', node, radius, point);
return intersect.circle(node, radius, point); return intersect.circle(node, radius, point);
}; };
node.updateIntersect = function () {
node.intersect = function (point) {
log.info('Circle intersect', node, radius, point);
return intersect.circle(node, radius, point);
};
};
return shapeSvg; return shapeSvg;
} }