diff options
Diffstat (limited to 'subprojects/frontend/src/graph/postProcessSVG.ts')
-rw-r--r-- | subprojects/frontend/src/graph/postProcessSVG.ts | 79 |
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 | |||
7 | import { type BBox, parsePolygonBBox, parsePathBBox } from './parseBBox'; | ||
8 | |||
9 | const SVG_NS = 'http://www.w3.org/2000/svg'; | ||
10 | |||
11 | function 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 | |||
34 | function 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 | |||
48 | function 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 | |||
74 | export 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 | } | ||