Skip to content

Commit

Permalink
Use HTML instead of SVG elements for labels
Browse files Browse the repository at this point in the history
  • Loading branch information
porst17 committed Dec 19, 2023
1 parent 888e656 commit 6079eea
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 8 deletions.
10 changes: 6 additions & 4 deletions src/scss/svg-viz.scss
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@ svg.model-visualization {
}
}

text {
fill: black;
foreignObject {
overflow: visible;
}

.label {
font-size: 14.0286px;
line-height: 1.25;
font-family: sans-serif;
text-align: center;
text-anchor: middle;
stroke-width: 0.809342;
transform: translate(-50%, -50%);
}
}
89 changes: 85 additions & 4 deletions src/ts/visualization.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { strict as assert } from 'assert';
import kebabCase from 'lodash/kebabCase';

import { ModelElementObject } from './model';
Expand All @@ -24,6 +25,19 @@ const mainFlowIds = [
'repair',
] as const;

const stockLabels = {
'recycled-materials': 'Verwertungsanlage',
'hibernating-phones': 'Ungenutzte Handies',
'disposed-phones': 'Handies im Müll',
'broken-phones': 'Kaputte Handies',
'repaired-phones': 'Werkstatt',
'phones-in-use': 'Handies in Benutzung',
'refurbished-phones': 'Instandsetzung',
'newly-produced-phones': 'Handy-Fabrik',
'natural-resources': 'Natur',
'landfilled-phones': 'Deponie',
};

type MainFlowIds = typeof mainFlowIds;

const flowVizSigns: ModelElementObject<MainFlowIds> = {
Expand Down Expand Up @@ -169,10 +183,77 @@ export default class Visualization {
flowLayer.classList.add('flows');
svg.append(capacityLayer, flowLayer, supplyLayer, labelLayer);

svg.querySelectorAll('text').forEach((baseElement) => {
const clone = baseElement.cloneNode(true) as SVGTextElement;
clone.classList.add('label');
labelLayer.append(clone);
svg.querySelectorAll('text').forEach((element) => {
elementsToRemove.push(element);
});

function createForeignDiv(
x: number,
y: number,
width: number,
height: number,
) {
const foreignObjectElement = createSVGElement('foreignObject');
foreignObjectElement.setAttribute('x', `${x}`);
foreignObjectElement.setAttribute('y', `${y}`);
foreignObjectElement.setAttribute('width', `${width}`);
foreignObjectElement.setAttribute('height', `${height}`);

const divElement = foreignObjectElement.ownerDocument.createElementNS(
'http://www.w3.org/1999/xhtml',
'div',
);

foreignObjectElement.append(divElement);

return { foreignObjectElement, divElement };
}

function getCircleCenter(element: SVGCircleElement) {
const x = parseFloat(element.getAttribute('cx') ?? '');
assert(!Number.isNaN(x));
const y = parseFloat(element.getAttribute('cy') ?? '');
assert(!Number.isNaN(y));
return { x, y };
}

function getRectangleCenter(element: SVGRectElement) {
const x = parseFloat(element.getAttribute('x') ?? '');
assert(!Number.isNaN(x));
const y = parseFloat(element.getAttribute('y') ?? '');
assert(!Number.isNaN(y));
const width = parseFloat(element.getAttribute('width') ?? '');
assert(!Number.isNaN(width));
const height = parseFloat(element.getAttribute('height') ?? '');
assert(!Number.isNaN(height));
return { x: x + width / 2, y: y + height / 2 };
}

Object.entries(stockLabels).forEach(([id, label]) => {
const baseElement = guardedQuerySelector(
svg,
`#${id}`,
SVGGeometryElement,
);
assert(
baseElement.tagName === 'circle' || baseElement.tagName === 'rect',
);
const { x, y } =
baseElement.tagName === 'circle'
? getCircleCenter(baseElement as SVGCircleElement)
: getRectangleCenter(baseElement as SVGRectElement);

const { foreignObjectElement, divElement } = createForeignDiv(
x,
y,
200,
50,
);

divElement.textContent = label;
divElement.classList.add('label');

labelLayer.append(foreignObjectElement);
});

svg.querySelectorAll('rect').forEach((baseElement) => {
Expand Down

0 comments on commit 6079eea

Please sign in to comment.