From 806d077d573ca84eab30ab59d764316f12ddb0d9 Mon Sep 17 00:00:00 2001 From: seb Date: Fri, 31 Jul 2026 05:21:28 +0200 Subject: [PATCH] u --- generate-schema-graph.js | 57 +++++++++++++++++++++++++++++++++++++++- schema-graph.svg | 8 +++--- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/generate-schema-graph.js b/generate-schema-graph.js index 2245eaa..ae17f12 100644 --- a/generate-schema-graph.js +++ b/generate-schema-graph.js @@ -711,6 +711,61 @@ function countSvgCrossings(svg) { return n; } +/** + * Graphviz self-edges from a lower FK row to id:e dip southeast. Rebuild the + * spline so it bulges east + north (up) while keeping start/end ports. + * In this SVG, more-negative y is visually higher. + */ +function fixSelfLoopSvg(svg) { + return svg.replace( + /\s*(\w+):e->\2:e<\/title>\s*<g id="(a_edge\d+)"><a ([^>]+)>\s*<path([^>]*) d="([^"]+)"\/>\s*<polygon([^>]*) points="([^"]+)"\/>\s*<\/a>\s*<\/g>\s*<\/g>/g, + (match, edgeId, _table, aId, aAttrs, pathAttrs, d, polyAttrs) => { + const pts = [...d.matchAll(/([-\d.]+)[,\s]+([-\d.]+)/g)].map((m) => [ + Number(m[1]), + Number(m[2]), + ]); + if (pts.length < 2) return match; + + const [x0, y0] = pts[0]; + const [x1, y1] = pts[pts.length - 1]; + const east = Math.max(x0, x1) + 36; + const north = Math.min(y0, y1) - 28; + + // Cubic end tangent is P3-P2; orient arrow along that arrival direction. + const tx = x1 - east; + const ty = y1 - north; + const len = Math.hypot(tx, ty) || 1; + const dx = tx / len; + const dy = ty / len; + const arrowLen = 9; + const arrowHalf = 3.5; + const baseX = x1 - dx * arrowLen; + const baseY = y1 - dy * arrowLen; + const px = -dy * arrowHalf; + const py = dx * arrowHalf; + + const fmt = (n) => (Math.round(n * 100) / 100).toString(); + const newDFmt = `M${fmt(x0)},${fmt(y0)}C${fmt(east)},${fmt(y0)} ${fmt(east)},${fmt(north)} ${fmt(baseX)},${fmt(baseY)}`; + const newPointsFmt = + `${fmt(x1)},${fmt(y1)} ` + + `${fmt(baseX + px)},${fmt(baseY + py)} ` + + `${fmt(baseX - px)},${fmt(baseY - py)} ` + + `${fmt(x1)},${fmt(y1)}`; + + return ( + `<g id="${edgeId}" class="edge">\n` + + `<title>${_table}:e->${_table}:e\n` + + `\n` + + `\n` + + `\n` + + `\n` + + `\n` + + `` + ); + } + ); +} + const schemaSource = readFileSync(schemaPath, 'utf8'); const { tables, edges } = parseSchema(schemaSource); @@ -742,7 +797,7 @@ for (let pass = 0; pass < 2; pass++) { const dot = buildDot(tables, edges, positions); writeFileSync(dotOutputPath, dot, 'utf8'); -const svg = graphviz.layout(dot, 'svg', 'dot'); +const svg = fixSelfLoopSvg(graphviz.layout(dot, 'svg', 'dot')); writeFileSync(outputPath, svg, 'utf8'); const dim = svg.match(/width="([\d.]+)pt" height="([\d.]+)pt"/); diff --git a/schema-graph.svg b/schema-graph.svg index a315539..92cc07f 100644 --- a/schema-graph.svg +++ b/schema-graph.svg @@ -670,8 +670,8 @@ category:e->category:e - - + + @@ -753,8 +753,8 @@ article:e->article:e - - + +