aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/graph/postProcessSVG.ts
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src/graph/postProcessSVG.ts')
-rw-r--r--subprojects/frontend/src/graph/postProcessSVG.ts79
1 files changed, 79 insertions, 0 deletions
diff --git a/subprojects/frontend/src/graph/postProcessSVG.ts b/subprojects/frontend/src/graph/postProcessSVG.ts
new file mode 100644
index 00000000..59cc15b9
--- /dev/null
+++ b/subprojects/frontend/src/graph/postProcessSVG.ts
@@ -0,0 +1,79 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { type BBox, parsePolygonBBox, parsePathBBox } from './parseBBox';
8
9const SVG_NS = 'http://www.w3.org/2000/svg';
10
11function clipCompartmentBackground(node: SVGGElement) {
12 // Background rectangle of the node created by the `<table bgcolor="green">`
13 // HTML element in dot. It was transformed into a rounded rect by `fixNodeBackground`.
14 const container = node.querySelector<SVGRectElement>('rect[fill="green"]');
15 // Background rectangle of the lower compartment created by the `<td bgcolor="white">`
16 // HTML element in dot. It was transformed into a rounded rect by `fixNodeBackground`.
17 // Since dot doesn't round the coners of `<td>` background,
18 // we have to clip it ourselves.
19 const compartment = node.querySelector<SVGPolygonElement>(
20 'polygon[fill="white"]',
21 );
22 if (container === null || compartment === null) {
23 return;
24 }
25 const copyOfContainer = container.cloneNode() as SVGRectElement;
26 const clipPath = document.createElementNS(SVG_NS, 'clipPath');
27 const clipId = `${node.id},,clip`;
28 clipPath.setAttribute('id', clipId);
29 clipPath.appendChild(copyOfContainer);
30 node.appendChild(clipPath);
31 compartment.setAttribute('clip-path', `url(#${clipId})`);
32}
33
34function createRect(
35 { x, y, width, height }: BBox,
36 original: SVGElement,
37): SVGRectElement {
38 const rect = document.createElementNS(SVG_NS, 'rect');
39 rect.setAttribute('fill', original.getAttribute('fill') ?? '');
40 rect.setAttribute('stroke', original.getAttribute('stroke') ?? '');
41 rect.setAttribute('x', String(x));
42 rect.setAttribute('y', String(y));
43 rect.setAttribute('width', String(width));
44 rect.setAttribute('height', String(height));
45 return rect;
46}
47
48function optimizeNodeShapes(node: SVGGElement) {
49 node.querySelectorAll('path').forEach((path) => {
50 const bbox = parsePathBBox(path);
51 const rect = createRect(bbox, path);
52 rect.setAttribute('rx', '12');
53 rect.setAttribute('ry', '12');
54 node.replaceChild(rect, path);
55 });
56 node.querySelectorAll('polygon').forEach((polygon) => {
57 const bbox = parsePolygonBBox(polygon);
58 if (bbox.height === 0) {
59 const polyline = document.createElementNS(SVG_NS, 'polyline');
60 polyline.setAttribute('stroke', polygon.getAttribute('stroke') ?? '');
61 polyline.setAttribute(
62 'points',
63 `${bbox.x},${bbox.y} ${bbox.x + bbox.width},${bbox.y}`,
64 );
65 node.replaceChild(polyline, polygon);
66 } else {
67 const rect = createRect(bbox, polygon);
68 node.replaceChild(rect, polygon);
69 }
70 });
71 clipCompartmentBackground(node);
72}
73
74export default function postProcessSvg(svg: SVGSVGElement) {
75 svg
76 .querySelectorAll<SVGTitleElement>('title')
77 .forEach((title) => title.parentNode?.removeChild(title));
78 svg.querySelectorAll<SVGGElement>('g.node').forEach(optimizeNodeShapes);
79}