aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/graph/postProcessSVG.ts
blob: 59cc15b9b0ab3fc70267deacf9d3b8317e9031fb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/*
 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
 *
 * SPDX-License-Identifier: EPL-2.0
 */

import { type BBox, parsePolygonBBox, parsePathBBox } from './parseBBox';

const SVG_NS = 'http://www.w3.org/2000/svg';

function clipCompartmentBackground(node: SVGGElement) {
  // Background rectangle of the node created by the `<table bgcolor="green">`
  // HTML element in dot. It was transformed into a rounded rect by `fixNodeBackground`.
  const container = node.querySelector<SVGRectElement>('rect[fill="green"]');
  // Background rectangle of the lower compartment created by the `<td bgcolor="white">`
  // HTML element in dot. It was transformed into a rounded rect by `fixNodeBackground`.
  // Since dot doesn't round the coners of `<td>` background,
  // we have to clip it ourselves.
  const compartment = node.querySelector<SVGPolygonElement>(
    'polygon[fill="white"]',
  );
  if (container === null || compartment === null) {
    return;
  }
  const copyOfContainer = container.cloneNode() as SVGRectElement;
  const clipPath = document.createElementNS(SVG_NS, 'clipPath');
  const clipId = `${node.id},,clip`;
  clipPath.setAttribute('id', clipId);
  clipPath.appendChild(copyOfContainer);
  node.appendChild(clipPath);
  compartment.setAttribute('clip-path', `url(#${clipId})`);
}

function createRect(
  { x, y, width, height }: BBox,
  original: SVGElement,
): SVGRectElement {
  const rect = document.createElementNS(SVG_NS, 'rect');
  rect.setAttribute('fill', original.getAttribute('fill') ?? '');
  rect.setAttribute('stroke', original.getAttribute('stroke') ?? '');
  rect.setAttribute('x', String(x));
  rect.setAttribute('y', String(y));
  rect.setAttribute('width', String(width));
  rect.setAttribute('height', String(height));
  return rect;
}

function optimizeNodeShapes(node: SVGGElement) {
  node.querySelectorAll('path').forEach((path) => {
    const bbox = parsePathBBox(path);
    const rect = createRect(bbox, path);
    rect.setAttribute('rx', '12');
    rect.setAttribute('ry', '12');
    node.replaceChild(rect, path);
  });
  node.querySelectorAll('polygon').forEach((polygon) => {
    const bbox = parsePolygonBBox(polygon);
    if (bbox.height === 0) {
      const polyline = document.createElementNS(SVG_NS, 'polyline');
      polyline.setAttribute('stroke', polygon.getAttribute('stroke') ?? '');
      polyline.setAttribute(
        'points',
        `${bbox.x},${bbox.y} ${bbox.x + bbox.width},${bbox.y}`,
      );
      node.replaceChild(polyline, polygon);
    } else {
      const rect = createRect(bbox, polygon);
      node.replaceChild(rect, polygon);
    }
  });
  clipCompartmentBackground(node);
}

export default function postProcessSvg(svg: SVGSVGElement) {
  svg
    .querySelectorAll<SVGTitleElement>('title')
    .forEach((title) => title.parentNode?.removeChild(title));
  svg.querySelectorAll<SVGGElement>('g.node').forEach(optimizeNodeShapes);
}