This commit is contained in:
seb
2026-07-31 05:21:28 +02:00
parent 142e73a171
commit 806d077d57
2 changed files with 60 additions and 5 deletions

View File

@@ -711,6 +711,61 @@ function countSvgCrossings(svg) {
return n; 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(
/<g id="(edge\d+)" class="edge">\s*<title>(\w+):e&#45;&gt;\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&#45;&gt;${_table}:e</title>\n` +
`<g id="${aId}"><a ${aAttrs}>\n` +
`<path${pathAttrs} d="${newDFmt}"/>\n` +
`<polygon${polyAttrs} points="${newPointsFmt}"/>\n` +
`</a>\n` +
`</g>\n` +
`</g>`
);
}
);
}
const schemaSource = readFileSync(schemaPath, 'utf8'); const schemaSource = readFileSync(schemaPath, 'utf8');
const { tables, edges } = parseSchema(schemaSource); const { tables, edges } = parseSchema(schemaSource);
@@ -742,7 +797,7 @@ for (let pass = 0; pass < 2; pass++) {
const dot = buildDot(tables, edges, positions); const dot = buildDot(tables, edges, positions);
writeFileSync(dotOutputPath, dot, 'utf8'); writeFileSync(dotOutputPath, dot, 'utf8');
const svg = graphviz.layout(dot, 'svg', 'dot'); const svg = fixSelfLoopSvg(graphviz.layout(dot, 'svg', 'dot'));
writeFileSync(outputPath, svg, 'utf8'); writeFileSync(outputPath, svg, 'utf8');
const dim = svg.match(/width="([\d.]+)pt" height="([\d.]+)pt"/); const dim = svg.match(/width="([\d.]+)pt" height="([\d.]+)pt"/);

View File

@@ -670,8 +670,8 @@
<g id="edge2" class="edge"> <g id="edge2" class="edge">
<title>category:e&#45;&gt;category:e</title> <title>category:e&#45;&gt;category:e</title>
<g id="a_edge2"><a xlink:title="FK_category_parent"> <g id="a_edge2"><a xlink:title="FK_category_parent">
<path fill="none" stroke="#2f855a" stroke-width="1.4" d="M1699.9,-415.8C1721.23,-331.75 1747.9,-331.75 1747.9,-429.2 1747.9,-523.22 1723.07,-526.53 1702.17,-451.15"/> <path fill="none" stroke="#2f855a" stroke-width="1.4" d="M1699.9,-415.8C1738.17,-415.8 1738.17,-479.15 1709.27,-456.68"/>
<polygon fill="#2f855a" stroke="#2f855a" stroke-width="1.4" points="1704.6,-450.79 1700.44,-444.65 1699.87,-452.04 1704.6,-450.79"/> <polygon fill="#2f855a" stroke="#2f855a" stroke-width="1.4" points="1702.17,-451.15 1707.13,-459.44 1711.42,-453.91 1702.17,-451.15"/>
</a> </a>
</g> </g>
</g> </g>
@@ -753,8 +753,8 @@
<g id="edge16" class="edge"> <g id="edge16" class="edge">
<title>article:e&#45;&gt;article:e</title> <title>article:e&#45;&gt;article:e</title>
<g id="a_edge16"><a xlink:title="FK_article_fatherarticle"> <g id="a_edge16"><a xlink:title="FK_article_fatherarticle">
<path fill="none" stroke="#1a365d" stroke-width="1.4" d="M1990.58,-523C2011.91,-438.95 2038.58,-438.95 2038.58,-549.8 2038.58,-656.75 2013.75,-660.51 1992.85,-585.15"/> <path fill="none" stroke="#1a365d" stroke-width="1.4" d="M1990.58,-523C2028.85,-523 2028.85,-613.15 1999.95,-590.68"/>
<polygon fill="#1a365d" stroke="#1a365d" stroke-width="1.4" points="1995.28,-584.79 1991.12,-578.65 1990.55,-586.04 1995.28,-584.79"/> <polygon fill="#1a365d" stroke="#1a365d" stroke-width="1.4" points="1992.85,-585.15 1997.81,-593.44 2002.1,-587.91 1992.85,-585.15"/>
</a> </a>
</g> </g>
</g> </g>

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB