|
6 | 6 | <title>{title}</title> |
7 | 7 | {styles} |
8 | 8 | <!-- Enhanced mermaid configuration for Reqvire diagrams --> |
9 | | - <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script> |
10 | 9 | <script src="https://cdn.jsdelivr.net/npm/hammerjs@2.0.8/hammer.min.js"></script> |
11 | 10 | <script src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.5.0/dist/svg-pan-zoom.min.js"></script> |
12 | | - <script> |
| 11 | + <script type="module"> |
| 12 | + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; |
| 13 | + import elkLayouts from 'https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk/dist/mermaid-layout-elk.esm.min.mjs'; |
| 14 | + |
| 15 | + // Register ELK layout for large diagrams |
| 16 | + mermaid.registerLayoutLoaders(elkLayouts); |
| 17 | + |
13 | 18 | // Capture Mermaid errors |
14 | 19 | mermaid.parseError = function(err, hash) { |
15 | 20 | console.error('Mermaid Parse Error:', err); |
|
24 | 29 | mermaid.initialize({ |
25 | 30 | startOnLoad: true, |
26 | 31 | theme: 'neutral', |
27 | | - maxTextSize: 120000, |
28 | | - maxEdges: 2000, |
| 32 | + maxTextSize: 5000000, |
| 33 | + maxEdges: 50000, |
29 | 34 | flowchart: { |
30 | 35 | useMaxWidth: true, |
31 | 36 | htmlLabels: true, |
32 | | - curve: 'basis' |
| 37 | + curve: 'basis', |
| 38 | + defaultRenderer: 'elk' |
| 39 | + }, |
| 40 | + layout: 'elk', |
| 41 | + elk: { |
| 42 | + mergeEdges: true, |
| 43 | + nodePlacementStrategy: 'SIMPLE' |
33 | 44 | }, |
34 | 45 | securityLevel: 'loose', |
35 | 46 | logLevel: 'error' |
36 | 47 | }); |
37 | 48 |
|
| 49 | + await mermaid.run(); |
| 50 | + window.mermaid = mermaid; |
| 51 | + |
38 | 52 | // Add pan/zoom to all Mermaid diagrams after rendering |
39 | | - window.addEventListener('load', function() { |
40 | | - setTimeout(function() { |
41 | | - document.querySelectorAll('.mermaid').forEach(function(mermaidContainer) { |
| 53 | + setTimeout(function() { |
| 54 | + document.querySelectorAll('.mermaid').forEach(function(mermaidContainer) { |
42 | 55 | var svg = mermaidContainer.querySelector('svg'); |
43 | 56 | if (!svg) return; |
44 | 57 |
|
|
137 | 150 | } |
138 | 151 | }); |
139 | 152 | }); |
| 153 | + }); |
| 154 | + }, 500); |
| 155 | + |
| 156 | + // Add CSS for edge highlighting (high specificity for SVG) |
| 157 | + var style = document.createElement('style'); |
| 158 | + style.textContent = ` |
| 159 | + path.edge-highlighted, |
| 160 | + .edge-highlighted path, |
| 161 | + svg path.edge-highlighted { |
| 162 | + stroke: #ff6b6b !important; |
| 163 | + stroke-width: 3px !important; |
| 164 | + } |
| 165 | + `; |
| 166 | + document.head.appendChild(style); |
| 167 | + |
| 168 | + // Add edge highlighting on node hover |
| 169 | + setTimeout(function() { |
| 170 | + document.querySelectorAll('.mermaid svg').forEach(function(svg) { |
| 171 | + var nodes = svg.querySelectorAll('.node'); |
| 172 | + |
| 173 | + // Find flowchart-link paths (actual edges) |
| 174 | + var edgePaths = svg.querySelectorAll('path.flowchart-link'); |
| 175 | + // Fallback: find paths in .edges group |
| 176 | + if (edgePaths.length === 0) { |
| 177 | + var edgesGroup = svg.querySelector('.edges'); |
| 178 | + if (edgesGroup) { |
| 179 | + edgePaths = edgesGroup.querySelectorAll('path'); |
| 180 | + } |
| 181 | + } |
| 182 | + |
| 183 | + // Get SVG point for coordinate transforms |
| 184 | + var svgPoint = svg.createSVGPoint(); |
| 185 | + |
| 186 | + // Helper: transform point through element's CTM |
| 187 | + function getTransformedBBox(element) { |
| 188 | + var bbox = element.getBBox(); |
| 189 | + var ctm = element.getCTM(); |
| 190 | + if (!ctm) return bbox; |
| 191 | + |
| 192 | + // Transform all four corners |
| 193 | + var corners = [ |
| 194 | + {x: bbox.x, y: bbox.y}, |
| 195 | + {x: bbox.x + bbox.width, y: bbox.y}, |
| 196 | + {x: bbox.x, y: bbox.y + bbox.height}, |
| 197 | + {x: bbox.x + bbox.width, y: bbox.y + bbox.height} |
| 198 | + ]; |
| 199 | + |
| 200 | + var transformed = corners.map(function(c) { |
| 201 | + svgPoint.x = c.x; |
| 202 | + svgPoint.y = c.y; |
| 203 | + var tp = svgPoint.matrixTransform(ctm); |
| 204 | + return {x: tp.x, y: tp.y}; |
| 205 | + }); |
| 206 | + |
| 207 | + var minX = Math.min.apply(null, transformed.map(function(p) { return p.x; })); |
| 208 | + var maxX = Math.max.apply(null, transformed.map(function(p) { return p.x; })); |
| 209 | + var minY = Math.min.apply(null, transformed.map(function(p) { return p.y; })); |
| 210 | + var maxY = Math.max.apply(null, transformed.map(function(p) { return p.y; })); |
| 211 | + |
| 212 | + return {x: minX, y: minY, width: maxX - minX, height: maxY - minY}; |
| 213 | + } |
| 214 | + |
| 215 | + // Helper: get all points from path |
| 216 | + function getPathPoints(path) { |
| 217 | + var d = path.getAttribute('d') || ''; |
| 218 | + var points = []; |
| 219 | + var ctm = path.getCTM(); |
| 220 | + |
| 221 | + // Get M (start) point |
| 222 | + var startMatch = d.match(/M\s*([\d.-]+)[,\s]+([\d.-]+)/i); |
| 223 | + if (startMatch) { |
| 224 | + var sx = parseFloat(startMatch[1]); |
| 225 | + var sy = parseFloat(startMatch[2]); |
| 226 | + if (ctm) { |
| 227 | + svgPoint.x = sx; svgPoint.y = sy; |
| 228 | + var tp = svgPoint.matrixTransform(ctm); |
| 229 | + points.push({x: tp.x, y: tp.y}); |
| 230 | + } else { |
| 231 | + points.push({x: sx, y: sy}); |
| 232 | + } |
| 233 | + } |
| 234 | + |
| 235 | + // Get last point - find last number pair before Z or end |
| 236 | + var allNums = d.match(/[\d.-]+/g); |
| 237 | + if (allNums && allNums.length >= 2) { |
| 238 | + var ex = parseFloat(allNums[allNums.length - 2]); |
| 239 | + var ey = parseFloat(allNums[allNums.length - 1]); |
| 240 | + if (ctm) { |
| 241 | + svgPoint.x = ex; svgPoint.y = ey; |
| 242 | + var tp = svgPoint.matrixTransform(ctm); |
| 243 | + points.push({x: tp.x, y: tp.y}); |
| 244 | + } else { |
| 245 | + points.push({x: ex, y: ey}); |
| 246 | + } |
| 247 | + } |
| 248 | + |
| 249 | + return points; |
| 250 | + } |
| 251 | + |
| 252 | + // Build node bounding boxes with transforms |
| 253 | + var nodeData = {}; |
| 254 | + nodes.forEach(function(node) { |
| 255 | + var nodeId = node.id || ''; |
| 256 | + var parts = nodeId.split('-'); |
| 257 | + var hashId = parts.length >= 2 ? parts[1] : nodeId; |
| 258 | + |
| 259 | + nodeData[hashId] = { |
| 260 | + node: node, |
| 261 | + bbox: getTransformedBBox(node) |
| 262 | + }; |
| 263 | + }); |
| 264 | + |
| 265 | + // Check if point is near bbox |
| 266 | + function isNearBbox(pt, bbox, margin) { |
| 267 | + margin = margin || 40; |
| 268 | + return pt.x >= bbox.x - margin && pt.x <= bbox.x + bbox.width + margin && |
| 269 | + pt.y >= bbox.y - margin && pt.y <= bbox.y + bbox.height + margin; |
| 270 | + } |
| 271 | + |
| 272 | + // Map edges to nodes |
| 273 | + var nodeEdgeMap = {}; |
| 274 | + Object.keys(nodeData).forEach(function(hashId) { |
| 275 | + nodeEdgeMap[hashId] = []; |
| 276 | + var bbox = nodeData[hashId].bbox; |
| 277 | + |
| 278 | + edgePaths.forEach(function(path) { |
| 279 | + var points = getPathPoints(path); |
| 280 | + for (var i = 0; i < points.length; i++) { |
| 281 | + if (isNearBbox(points[i], bbox, 40)) { |
| 282 | + if (!nodeEdgeMap[hashId].includes(path)) { |
| 283 | + nodeEdgeMap[hashId].push(path); |
| 284 | + } |
| 285 | + break; |
| 286 | + } |
| 287 | + } |
| 288 | + }); |
| 289 | + }); |
| 290 | + |
| 291 | + // Attach hover handlers |
| 292 | + nodes.forEach(function(node) { |
| 293 | + var nodeId = node.id || ''; |
| 294 | + var parts = nodeId.split('-'); |
| 295 | + var hashId = parts.length >= 2 ? parts[1] : nodeId; |
| 296 | + |
| 297 | + node.style.cursor = 'pointer'; |
| 298 | + |
| 299 | + node.addEventListener('mouseenter', function(e) { |
| 300 | + e.stopPropagation(); |
| 301 | + nodes.forEach(function(n) { n.style.filter = ''; }); |
| 302 | + node.style.filter = 'drop-shadow(0 0 6px #ff6b6b)'; |
| 303 | + |
| 304 | + var connectedEdges = nodeEdgeMap[hashId] || []; |
| 305 | + connectedEdges.forEach(function(path) { |
| 306 | + path.classList.add('edge-highlighted'); |
| 307 | + }); |
| 308 | + }); |
| 309 | + |
| 310 | + node.addEventListener('mouseleave', function() { |
| 311 | + node.style.filter = ''; |
| 312 | + var connectedEdges = nodeEdgeMap[hashId] || []; |
| 313 | + connectedEdges.forEach(function(path) { |
| 314 | + path.classList.remove('edge-highlighted'); |
| 315 | + }); |
| 316 | + }); |
140 | 317 | }); |
141 | | - }, 1500); |
142 | | - }); |
| 318 | + }); |
| 319 | + }, 1000); |
143 | 320 | </script> |
144 | 321 | </head> |
145 | 322 | <body> |
|
0 commit comments