mermaid
Version:
Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.
1,314 lines (1,291 loc) • 190 kB
JavaScript
import {
getSubGraphTitleMargins
} from "./chunk-AC5SNWB5.mjs";
import {
compileStyles,
solidStateFill,
styles2String,
userNodeOverrides
} from "./chunk-UWXLY5YG.mjs";
import {
createText,
getIconSVG
} from "./chunk-QESNASVV.mjs";
import {
calculateTextWidth,
decodeEntities,
handleUndefinedAttr,
parseFontSize
} from "./chunk-55PJQP7W.mjs";
import {
__name,
common_default,
defaultConfig_default,
evaluate,
getConfig,
getConfig2,
hasKatex,
log,
parseGenericTypes,
renderKatex,
sanitizeText,
sanitizeText2
} from "./chunk-3XYRH5AP.mjs";
// src/rendering-util/rendering-elements/shapes/util.ts
import { select } from "d3";
var labelHelper = /* @__PURE__ */ __name(async (parent, node, _classes) => {
let cssClasses;
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig2()?.htmlLabels);
if (!_classes) {
cssClasses = "node default";
} else {
cssClasses = _classes;
}
const shapeSvg = parent.insert("g").attr("class", cssClasses).attr("id", node.domId || node.id);
const labelEl = shapeSvg.insert("g").attr("class", "label").attr("style", handleUndefinedAttr(node.labelStyle));
let label;
if (node.label === void 0) {
label = "";
} else {
label = typeof node.label === "string" ? node.label : node.label[0];
}
const text2 = await createText(labelEl, sanitizeText(decodeEntities(label), getConfig2()), {
useHtmlLabels,
width: node.width || getConfig2().flowchart?.wrappingWidth,
// @ts-expect-error -- This is currently not used. Should this be `classes` instead?
cssClasses: "markdown-node-label",
style: node.labelStyle,
addSvgBackground: !!node.icon || !!node.img
});
let bbox = text2.getBBox();
const halfPadding = (node?.padding ?? 0) / 2;
if (useHtmlLabels) {
const div = text2.children[0];
const dv = select(text2);
const images = div.getElementsByTagName("img");
if (images) {
const noImgText = label.replace(/<img[^>]*>/g, "").trim() === "";
await Promise.all(
[...images].map(
(img) => new Promise((res) => {
function setupImage() {
img.style.display = "flex";
img.style.flexDirection = "column";
if (noImgText) {
const bodyFontSize = getConfig2().fontSize ? getConfig2().fontSize : window.getComputedStyle(document.body).fontSize;
const enlargingFactor = 5;
const [parsedBodyFontSize = defaultConfig_default.fontSize] = parseFontSize(bodyFontSize);
const width = parsedBodyFontSize * enlargingFactor + "px";
img.style.minWidth = width;
img.style.maxWidth = width;
} else {
img.style.width = "100%";
}
res(img);
}
__name(setupImage, "setupImage");
setTimeout(() => {
if (img.complete) {
setupImage();
}
});
img.addEventListener("error", setupImage);
img.addEventListener("load", setupImage);
})
)
);
}
bbox = div.getBoundingClientRect();
dv.attr("width", bbox.width);
dv.attr("height", bbox.height);
}
if (useHtmlLabels) {
labelEl.attr("transform", "translate(" + -bbox.width / 2 + ", " + -bbox.height / 2 + ")");
} else {
labelEl.attr("transform", "translate(0, " + -bbox.height / 2 + ")");
}
if (node.centerLabel) {
labelEl.attr("transform", "translate(" + -bbox.width / 2 + ", " + -bbox.height / 2 + ")");
}
labelEl.insert("rect", ":first-child");
return { shapeSvg, bbox, halfPadding, label: labelEl };
}, "labelHelper");
var insertLabel = /* @__PURE__ */ __name(async (parent, label, options) => {
const useHtmlLabels = options.useHtmlLabels || evaluate(getConfig2()?.flowchart?.htmlLabels);
const labelEl = parent.insert("g").attr("class", "label").attr("style", options.labelStyle || "");
const text2 = await createText(labelEl, sanitizeText(decodeEntities(label), getConfig2()), {
useHtmlLabels,
width: options.width || getConfig2()?.flowchart?.wrappingWidth,
style: options.labelStyle,
addSvgBackground: !!options.icon || !!options.img
});
let bbox = text2.getBBox();
const halfPadding = options.padding / 2;
if (evaluate(getConfig2()?.flowchart?.htmlLabels)) {
const div = text2.children[0];
const dv = select(text2);
bbox = div.getBoundingClientRect();
dv.attr("width", bbox.width);
dv.attr("height", bbox.height);
}
if (useHtmlLabels) {
labelEl.attr("transform", "translate(" + -bbox.width / 2 + ", " + -bbox.height / 2 + ")");
} else {
labelEl.attr("transform", "translate(0, " + -bbox.height / 2 + ")");
}
if (options.centerLabel) {
labelEl.attr("transform", "translate(" + -bbox.width / 2 + ", " + -bbox.height / 2 + ")");
}
labelEl.insert("rect", ":first-child");
return { shapeSvg: parent, bbox, halfPadding, label: labelEl };
}, "insertLabel");
var updateNodeBounds = /* @__PURE__ */ __name((node, element) => {
const bbox = element.node().getBBox();
node.width = bbox.width;
node.height = bbox.height;
}, "updateNodeBounds");
var getNodeClasses = /* @__PURE__ */ __name((node, extra) => (node.look === "handDrawn" ? "rough-node" : "node") + " " + node.cssClasses + " " + (extra || ""), "getNodeClasses");
function createPathFromPoints(points) {
const pointStrings = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`);
pointStrings.push("Z");
return pointStrings.join(" ");
}
__name(createPathFromPoints, "createPathFromPoints");
function generateFullSineWavePoints(x1, y1, x2, y2, amplitude, numCycles) {
const points = [];
const steps = 50;
const deltaX = x2 - x1;
const deltaY = y2 - y1;
const cycleLength = deltaX / numCycles;
const frequency = 2 * Math.PI / cycleLength;
const midY = y1 + deltaY / 2;
for (let i = 0; i <= steps; i++) {
const t = i / steps;
const x = x1 + t * deltaX;
const y = midY + amplitude * Math.sin(frequency * (x - x1));
points.push({ x, y });
}
return points;
}
__name(generateFullSineWavePoints, "generateFullSineWavePoints");
function generateCirclePoints(centerX, centerY, radius, numPoints, startAngle, endAngle) {
const points = [];
const startAngleRad = startAngle * Math.PI / 180;
const endAngleRad = endAngle * Math.PI / 180;
const angleRange = endAngleRad - startAngleRad;
const angleStep = angleRange / (numPoints - 1);
for (let i = 0; i < numPoints; i++) {
const angle = startAngleRad + i * angleStep;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
points.push({ x: -x, y: -y });
}
return points;
}
__name(generateCirclePoints, "generateCirclePoints");
// src/rendering-util/rendering-elements/clusters.js
import { select as select3 } from "d3";
import rough from "roughjs";
// src/rendering-util/rendering-elements/intersect/intersect-rect.js
var intersectRect = /* @__PURE__ */ __name((node, point) => {
var x = node.x;
var y = node.y;
var dx = point.x - x;
var dy = point.y - y;
var w = node.width / 2;
var h = node.height / 2;
var sx, sy;
if (Math.abs(dy) * w > Math.abs(dx) * h) {
if (dy < 0) {
h = -h;
}
sx = dy === 0 ? 0 : h * dx / dy;
sy = h;
} else {
if (dx < 0) {
w = -w;
}
sx = w;
sy = dx === 0 ? 0 : w * dy / dx;
}
return { x: x + sx, y: y + sy };
}, "intersectRect");
var intersect_rect_default = intersectRect;
// src/rendering-util/rendering-elements/createLabel.js
import { select as select2 } from "d3";
function applyStyle(dom, styleFn) {
if (styleFn) {
dom.attr("style", styleFn);
}
}
__name(applyStyle, "applyStyle");
async function addHtmlLabel(node) {
const fo = select2(document.createElementNS("http://www.w3.org/2000/svg", "foreignObject"));
const div = fo.append("xhtml:div");
let label = node.label;
if (node.label && hasKatex(node.label)) {
label = await renderKatex(node.label.replace(common_default.lineBreakRegex, "\n"), getConfig2());
}
const labelClass = node.isNode ? "nodeLabel" : "edgeLabel";
div.html(
'<span class="' + labelClass + '" ' + (node.labelStyle ? 'style="' + node.labelStyle + '"' : "") + // codeql [js/html-constructed-from-input] : false positive
">" + label + "</span>"
);
applyStyle(div, node.labelStyle);
div.style("display", "inline-block");
div.style("padding-right", "1px");
div.style("white-space", "nowrap");
div.attr("xmlns", "http://www.w3.org/1999/xhtml");
return fo.node();
}
__name(addHtmlLabel, "addHtmlLabel");
var createLabel = /* @__PURE__ */ __name(async (_vertexText, style, isTitle, isNode) => {
let vertexText = _vertexText || "";
if (typeof vertexText === "object") {
vertexText = vertexText[0];
}
if (evaluate(getConfig2().flowchart.htmlLabels)) {
vertexText = vertexText.replace(/\\n|\n/g, "<br />");
log.info("vertexText" + vertexText);
const node = {
isNode,
label: decodeEntities(vertexText).replace(
/fa[blrs]?:fa-[\w-]+/g,
(s) => `<i class='${s.replace(":", " ")}'></i>`
),
labelStyle: style ? style.replace("fill:", "color:") : style
};
let vertexNode = await addHtmlLabel(node);
return vertexNode;
} else {
const svgLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");
svgLabel.setAttribute("style", style.replace("color:", "fill:"));
let rows = [];
if (typeof vertexText === "string") {
rows = vertexText.split(/\\n|\n|<br\s*\/?>/gi);
} else if (Array.isArray(vertexText)) {
rows = vertexText;
} else {
rows = [];
}
for (const row of rows) {
const tspan = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
tspan.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", "preserve");
tspan.setAttribute("dy", "1em");
tspan.setAttribute("x", "0");
if (isTitle) {
tspan.setAttribute("class", "title-row");
} else {
tspan.setAttribute("class", "row");
}
tspan.textContent = row.trim();
svgLabel.appendChild(tspan);
}
return svgLabel;
}
}, "createLabel");
var createLabel_default = createLabel;
// src/rendering-util/rendering-elements/shapes/roundedRectPath.ts
var createRoundedRectPathD = /* @__PURE__ */ __name((x, y, totalWidth, totalHeight, radius) => [
"M",
x + radius,
y,
// Move to the first point
"H",
x + totalWidth - radius,
// Draw horizontal line to the beginning of the right corner
"A",
radius,
radius,
0,
0,
1,
x + totalWidth,
y + radius,
// Draw arc to the right top corner
"V",
y + totalHeight - radius,
// Draw vertical line down to the beginning of the right bottom corner
"A",
radius,
radius,
0,
0,
1,
x + totalWidth - radius,
y + totalHeight,
// Draw arc to the right bottom corner
"H",
x + radius,
// Draw horizontal line to the beginning of the left bottom corner
"A",
radius,
radius,
0,
0,
1,
x,
y + totalHeight - radius,
// Draw arc to the left bottom corner
"V",
y + radius,
// Draw vertical line up to the beginning of the left top corner
"A",
radius,
radius,
0,
0,
1,
x + radius,
y,
// Draw arc to the left top corner
"Z"
// Close the path
].join(" "), "createRoundedRectPathD");
// src/rendering-util/rendering-elements/clusters.js
var rect = /* @__PURE__ */ __name(async (parent, node) => {
log.info("Creating subgraph rect for ", node.id, node);
const siteConfig = getConfig2();
const { themeVariables, handDrawnSeed } = siteConfig;
const { clusterBkg, clusterBorder } = themeVariables;
const { labelStyles, nodeStyles, borderStyles, backgroundStyles } = styles2String(node);
const shapeSvg = parent.insert("g").attr("class", "cluster " + node.cssClasses).attr("id", node.id).attr("data-look", node.look);
const useHtmlLabels = evaluate(siteConfig.flowchart.htmlLabels);
const labelEl = shapeSvg.insert("g").attr("class", "cluster-label ");
const text2 = await createText(labelEl, node.label, {
style: node.labelStyle,
useHtmlLabels,
isNode: true
});
let bbox = text2.getBBox();
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text2.children[0];
const dv = select3(text2);
bbox = div.getBoundingClientRect();
dv.attr("width", bbox.width);
dv.attr("height", bbox.height);
}
const width = node.width <= bbox.width + node.padding ? bbox.width + node.padding : node.width;
if (node.width <= bbox.width + node.padding) {
node.diff = (width - node.width) / 2 - node.padding;
} else {
node.diff = -node.padding;
}
const height = node.height;
const x = node.x - width / 2;
const y = node.y - height / 2;
log.trace("Data ", node, JSON.stringify(node));
let rect2;
if (node.look === "handDrawn") {
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {
roughness: 0.7,
fill: clusterBkg,
// fill: 'red',
stroke: clusterBorder,
fillWeight: 3,
seed: handDrawnSeed
});
const roughNode = rc.path(createRoundedRectPathD(x, y, width, height, 0), options);
rect2 = shapeSvg.insert(() => {
log.debug("Rough node insert CXC", roughNode);
return roughNode;
}, ":first-child");
rect2.select("path:nth-child(2)").attr("style", borderStyles.join(";"));
rect2.select("path").attr("style", backgroundStyles.join(";").replace("fill", "stroke"));
} else {
rect2 = shapeSvg.insert("rect", ":first-child");
rect2.attr("style", nodeStyles).attr("rx", node.rx).attr("ry", node.ry).attr("x", x).attr("y", y).attr("width", width).attr("height", height);
}
const { subGraphTitleTopMargin } = getSubGraphTitleMargins(siteConfig);
labelEl.attr(
"transform",
// This puts the label on top of the box instead of inside it
`translate(${node.x - bbox.width / 2}, ${node.y - node.height / 2 + subGraphTitleTopMargin})`
);
if (labelStyles) {
const span = labelEl.select("span");
if (span) {
span.attr("style", labelStyles);
}
}
const rectBox = rect2.node().getBBox();
node.offsetX = 0;
node.width = rectBox.width;
node.height = rectBox.height;
node.offsetY = bbox.height - node.padding / 2;
node.intersect = function(point) {
return intersect_rect_default(node, point);
};
return { cluster: shapeSvg, labelBBox: bbox };
}, "rect");
var noteGroup = /* @__PURE__ */ __name((parent, node) => {
const shapeSvg = parent.insert("g").attr("class", "note-cluster").attr("id", node.id);
const rect2 = shapeSvg.insert("rect", ":first-child");
const padding = 0 * node.padding;
const halfPadding = padding / 2;
rect2.attr("rx", node.rx).attr("ry", node.ry).attr("x", node.x - node.width / 2 - halfPadding).attr("y", node.y - node.height / 2 - halfPadding).attr("width", node.width + padding).attr("height", node.height + padding).attr("fill", "none");
const rectBox = rect2.node().getBBox();
node.width = rectBox.width;
node.height = rectBox.height;
node.intersect = function(point) {
return intersect_rect_default(node, point);
};
return { cluster: shapeSvg, labelBBox: { width: 0, height: 0 } };
}, "noteGroup");
var roundedWithTitle = /* @__PURE__ */ __name(async (parent, node) => {
const siteConfig = getConfig2();
const { themeVariables, handDrawnSeed } = siteConfig;
const { altBackground, compositeBackground, compositeTitleBackground, nodeBorder } = themeVariables;
const shapeSvg = parent.insert("g").attr("class", node.cssClasses).attr("id", node.id).attr("data-id", node.id).attr("data-look", node.look);
const outerRectG = shapeSvg.insert("g", ":first-child");
const label = shapeSvg.insert("g").attr("class", "cluster-label");
let innerRect = shapeSvg.append("rect");
const text2 = label.node().appendChild(await createLabel_default(node.label, node.labelStyle, void 0, true));
let bbox = text2.getBBox();
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text2.children[0];
const dv = select3(text2);
bbox = div.getBoundingClientRect();
dv.attr("width", bbox.width);
dv.attr("height", bbox.height);
}
const padding = 0 * node.padding;
const halfPadding = padding / 2;
const width = (node.width <= bbox.width + node.padding ? bbox.width + node.padding : node.width) + padding;
if (node.width <= bbox.width + node.padding) {
node.diff = (width - node.width) / 2 - node.padding;
} else {
node.diff = -node.padding;
}
const height = node.height + padding;
const innerHeight = node.height + padding - bbox.height - 6;
const x = node.x - width / 2;
const y = node.y - height / 2;
node.width = width;
const innerY = node.y - node.height / 2 - halfPadding + bbox.height + 2;
let rect2;
if (node.look === "handDrawn") {
const isAlt = node.cssClasses.includes("statediagram-cluster-alt");
const rc = rough.svg(shapeSvg);
const roughOuterNode = node.rx || node.ry ? rc.path(createRoundedRectPathD(x, y, width, height, 10), {
roughness: 0.7,
fill: compositeTitleBackground,
fillStyle: "solid",
stroke: nodeBorder,
seed: handDrawnSeed
}) : rc.rectangle(x, y, width, height, { seed: handDrawnSeed });
rect2 = shapeSvg.insert(() => roughOuterNode, ":first-child");
const roughInnerNode = rc.rectangle(x, innerY, width, innerHeight, {
fill: isAlt ? altBackground : compositeBackground,
fillStyle: isAlt ? "hachure" : "solid",
stroke: nodeBorder,
seed: handDrawnSeed
});
rect2 = shapeSvg.insert(() => roughOuterNode, ":first-child");
innerRect = shapeSvg.insert(() => roughInnerNode);
} else {
rect2 = outerRectG.insert("rect", ":first-child");
const outerRectClass = "outer";
rect2.attr("class", outerRectClass).attr("x", x).attr("y", y).attr("width", width).attr("height", height).attr("data-look", node.look);
innerRect.attr("class", "inner").attr("x", x).attr("y", innerY).attr("width", width).attr("height", innerHeight);
}
label.attr(
"transform",
`translate(${node.x - bbox.width / 2}, ${y + 1 - (evaluate(siteConfig.flowchart.htmlLabels) ? 0 : 3)})`
);
const rectBox = rect2.node().getBBox();
node.height = rectBox.height;
node.offsetX = 0;
node.offsetY = bbox.height - node.padding / 2;
node.labelBBox = bbox;
node.intersect = function(point) {
return intersect_rect_default(node, point);
};
return { cluster: shapeSvg, labelBBox: bbox };
}, "roundedWithTitle");
var kanbanSection = /* @__PURE__ */ __name(async (parent, node) => {
log.info("Creating subgraph rect for ", node.id, node);
const siteConfig = getConfig2();
const { themeVariables, handDrawnSeed } = siteConfig;
const { clusterBkg, clusterBorder } = themeVariables;
const { labelStyles, nodeStyles, borderStyles, backgroundStyles } = styles2String(node);
const shapeSvg = parent.insert("g").attr("class", "cluster " + node.cssClasses).attr("id", node.id).attr("data-look", node.look);
const useHtmlLabels = evaluate(siteConfig.flowchart.htmlLabels);
const labelEl = shapeSvg.insert("g").attr("class", "cluster-label ");
const text2 = await createText(labelEl, node.label, {
style: node.labelStyle,
useHtmlLabels,
isNode: true,
width: node.width
});
let bbox = text2.getBBox();
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text2.children[0];
const dv = select3(text2);
bbox = div.getBoundingClientRect();
dv.attr("width", bbox.width);
dv.attr("height", bbox.height);
}
const width = node.width <= bbox.width + node.padding ? bbox.width + node.padding : node.width;
if (node.width <= bbox.width + node.padding) {
node.diff = (width - node.width) / 2 - node.padding;
} else {
node.diff = -node.padding;
}
const height = node.height;
const x = node.x - width / 2;
const y = node.y - height / 2;
log.trace("Data ", node, JSON.stringify(node));
let rect2;
if (node.look === "handDrawn") {
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {
roughness: 0.7,
fill: clusterBkg,
// fill: 'red',
stroke: clusterBorder,
fillWeight: 4,
seed: handDrawnSeed
});
const roughNode = rc.path(createRoundedRectPathD(x, y, width, height, node.rx), options);
rect2 = shapeSvg.insert(() => {
log.debug("Rough node insert CXC", roughNode);
return roughNode;
}, ":first-child");
rect2.select("path:nth-child(2)").attr("style", borderStyles.join(";"));
rect2.select("path").attr("style", backgroundStyles.join(";").replace("fill", "stroke"));
} else {
rect2 = shapeSvg.insert("rect", ":first-child");
rect2.attr("style", nodeStyles).attr("rx", node.rx).attr("ry", node.ry).attr("x", x).attr("y", y).attr("width", width).attr("height", height);
}
const { subGraphTitleTopMargin } = getSubGraphTitleMargins(siteConfig);
labelEl.attr(
"transform",
// This puts the label on top of the box instead of inside it
`translate(${node.x - bbox.width / 2}, ${node.y - node.height / 2 + subGraphTitleTopMargin})`
);
if (labelStyles) {
const span = labelEl.select("span");
if (span) {
span.attr("style", labelStyles);
}
}
const rectBox = rect2.node().getBBox();
node.offsetX = 0;
node.width = rectBox.width;
node.height = rectBox.height;
node.offsetY = bbox.height - node.padding / 2;
node.intersect = function(point) {
return intersect_rect_default(node, point);
};
return { cluster: shapeSvg, labelBBox: bbox };
}, "kanbanSection");
var divider = /* @__PURE__ */ __name((parent, node) => {
const siteConfig = getConfig2();
const { themeVariables, handDrawnSeed } = siteConfig;
const { nodeBorder } = themeVariables;
const shapeSvg = parent.insert("g").attr("class", node.cssClasses).attr("id", node.id).attr("data-look", node.look);
const outerRectG = shapeSvg.insert("g", ":first-child");
const padding = 0 * node.padding;
const width = node.width + padding;
node.diff = -node.padding;
const height = node.height + padding;
const x = node.x - width / 2;
const y = node.y - height / 2;
node.width = width;
let rect2;
if (node.look === "handDrawn") {
const rc = rough.svg(shapeSvg);
const roughOuterNode = rc.rectangle(x, y, width, height, {
fill: "lightgrey",
roughness: 0.5,
strokeLineDash: [5],
stroke: nodeBorder,
seed: handDrawnSeed
});
rect2 = shapeSvg.insert(() => roughOuterNode, ":first-child");
} else {
rect2 = outerRectG.insert("rect", ":first-child");
const outerRectClass = "divider";
rect2.attr("class", outerRectClass).attr("x", x).attr("y", y).attr("width", width).attr("height", height).attr("data-look", node.look);
}
const rectBox = rect2.node().getBBox();
node.height = rectBox.height;
node.offsetX = 0;
node.offsetY = 0;
node.intersect = function(point) {
return intersect_rect_default(node, point);
};
return { cluster: shapeSvg, labelBBox: {} };
}, "divider");
var squareRect = rect;
var shapes = {
rect,
squareRect,
roundedWithTitle,
noteGroup,
divider,
kanbanSection
};
var clusterElems = /* @__PURE__ */ new Map();
var insertCluster = /* @__PURE__ */ __name(async (elem, node) => {
const shape = node.shape || "rect";
const cluster = await shapes[shape](elem, node);
clusterElems.set(node.id, cluster);
return cluster;
}, "insertCluster");
var clear = /* @__PURE__ */ __name(() => {
clusterElems = /* @__PURE__ */ new Map();
}, "clear");
// src/rendering-util/rendering-elements/intersect/intersect-node.js
function intersectNode(node, point) {
return node.intersect(point);
}
__name(intersectNode, "intersectNode");
var intersect_node_default = intersectNode;
// src/rendering-util/rendering-elements/intersect/intersect-ellipse.js
function intersectEllipse(node, rx, ry, point) {
var cx = node.x;
var cy = node.y;
var px = cx - point.x;
var py = cy - point.y;
var det = Math.sqrt(rx * rx * py * py + ry * ry * px * px);
var dx = Math.abs(rx * ry * px / det);
if (point.x < cx) {
dx = -dx;
}
var dy = Math.abs(rx * ry * py / det);
if (point.y < cy) {
dy = -dy;
}
return { x: cx + dx, y: cy + dy };
}
__name(intersectEllipse, "intersectEllipse");
var intersect_ellipse_default = intersectEllipse;
// src/rendering-util/rendering-elements/intersect/intersect-circle.js
function intersectCircle(node, rx, point) {
return intersect_ellipse_default(node, rx, rx, point);
}
__name(intersectCircle, "intersectCircle");
var intersect_circle_default = intersectCircle;
// src/rendering-util/rendering-elements/intersect/intersect-line.js
function intersectLine(p1, p2, q1, q2) {
var a1, a2, b1, b2, c1, c2;
var r1, r2, r3, r4;
var denom, offset, num;
var x, y;
a1 = p2.y - p1.y;
b1 = p1.x - p2.x;
c1 = p2.x * p1.y - p1.x * p2.y;
r3 = a1 * q1.x + b1 * q1.y + c1;
r4 = a1 * q2.x + b1 * q2.y + c1;
if (r3 !== 0 && r4 !== 0 && sameSign(r3, r4)) {
return;
}
a2 = q2.y - q1.y;
b2 = q1.x - q2.x;
c2 = q2.x * q1.y - q1.x * q2.y;
r1 = a2 * p1.x + b2 * p1.y + c2;
r2 = a2 * p2.x + b2 * p2.y + c2;
if (r1 !== 0 && r2 !== 0 && sameSign(r1, r2)) {
return;
}
denom = a1 * b2 - a2 * b1;
if (denom === 0) {
return;
}
offset = Math.abs(denom / 2);
num = b1 * c2 - b2 * c1;
x = num < 0 ? (num - offset) / denom : (num + offset) / denom;
num = a2 * c1 - a1 * c2;
y = num < 0 ? (num - offset) / denom : (num + offset) / denom;
return { x, y };
}
__name(intersectLine, "intersectLine");
function sameSign(r1, r2) {
return r1 * r2 > 0;
}
__name(sameSign, "sameSign");
var intersect_line_default = intersectLine;
// src/rendering-util/rendering-elements/intersect/intersect-polygon.js
function intersectPolygon(node, polyPoints, point) {
let x1 = node.x;
let y1 = node.y;
let intersections = [];
let minX = Number.POSITIVE_INFINITY;
let minY = Number.POSITIVE_INFINITY;
if (typeof polyPoints.forEach === "function") {
polyPoints.forEach(function(entry) {
minX = Math.min(minX, entry.x);
minY = Math.min(minY, entry.y);
});
} else {
minX = Math.min(minX, polyPoints.x);
minY = Math.min(minY, polyPoints.y);
}
let left = x1 - node.width / 2 - minX;
let top = y1 - node.height / 2 - minY;
for (let i = 0; i < polyPoints.length; i++) {
let p1 = polyPoints[i];
let p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0];
let intersect = intersect_line_default(
node,
point,
{ x: left + p1.x, y: top + p1.y },
{ x: left + p2.x, y: top + p2.y }
);
if (intersect) {
intersections.push(intersect);
}
}
if (!intersections.length) {
return node;
}
if (intersections.length > 1) {
intersections.sort(function(p, q) {
let pdx = p.x - point.x;
let pdy = p.y - point.y;
let distp = Math.sqrt(pdx * pdx + pdy * pdy);
let qdx = q.x - point.x;
let qdy = q.y - point.y;
let distq = Math.sqrt(qdx * qdx + qdy * qdy);
return distp < distq ? -1 : distp === distq ? 0 : 1;
});
}
return intersections[0];
}
__name(intersectPolygon, "intersectPolygon");
var intersect_polygon_default = intersectPolygon;
// src/rendering-util/rendering-elements/intersect/index.js
var intersect_default = {
node: intersect_node_default,
circle: intersect_circle_default,
ellipse: intersect_ellipse_default,
polygon: intersect_polygon_default,
rect: intersect_rect_default
};
// src/rendering-util/rendering-elements/shapes/anchor.ts
import rough2 from "roughjs";
function anchor(parent, node) {
const { labelStyles } = styles2String(node);
node.labelStyle = labelStyles;
const classes = getNodeClasses(node);
let cssClasses = classes;
if (!classes) {
cssClasses = "anchor";
}
const shapeSvg = parent.insert("g").attr("class", cssClasses).attr("id", node.domId || node.id);
const radius = 1;
const { cssStyles } = node;
const rc = rough2.svg(shapeSvg);
const options = userNodeOverrides(node, { fill: "black", stroke: "none", fillStyle: "solid" });
if (node.look !== "handDrawn") {
options.roughness = 0;
}
const roughNode = rc.circle(0, 0, radius * 2, options);
const circleElem = shapeSvg.insert(() => roughNode, ":first-child");
circleElem.attr("class", "anchor").attr("style", handleUndefinedAttr(cssStyles));
updateNodeBounds(node, circleElem);
node.intersect = function(point) {
log.info("Circle intersect", node, radius, point);
return intersect_default.circle(node, radius, point);
};
return shapeSvg;
}
__name(anchor, "anchor");
// src/rendering-util/rendering-elements/shapes/bowTieRect.ts
import rough3 from "roughjs";
function generateArcPoints(x1, y1, x2, y2, rx, ry, clockwise) {
const numPoints = 20;
const midX = (x1 + x2) / 2;
const midY = (y1 + y2) / 2;
const angle = Math.atan2(y2 - y1, x2 - x1);
const dx = (x2 - x1) / 2;
const dy = (y2 - y1) / 2;
const transformedX = dx / rx;
const transformedY = dy / ry;
const distance = Math.sqrt(transformedX ** 2 + transformedY ** 2);
if (distance > 1) {
throw new Error("The given radii are too small to create an arc between the points.");
}
const scaledCenterDistance = Math.sqrt(1 - distance ** 2);
const centerX = midX + scaledCenterDistance * ry * Math.sin(angle) * (clockwise ? -1 : 1);
const centerY = midY - scaledCenterDistance * rx * Math.cos(angle) * (clockwise ? -1 : 1);
const startAngle = Math.atan2((y1 - centerY) / ry, (x1 - centerX) / rx);
const endAngle = Math.atan2((y2 - centerY) / ry, (x2 - centerX) / rx);
let angleRange = endAngle - startAngle;
if (clockwise && angleRange < 0) {
angleRange += 2 * Math.PI;
}
if (!clockwise && angleRange > 0) {
angleRange -= 2 * Math.PI;
}
const points = [];
for (let i = 0; i < numPoints; i++) {
const t = i / (numPoints - 1);
const angle2 = startAngle + t * angleRange;
const x = centerX + rx * Math.cos(angle2);
const y = centerY + ry * Math.sin(angle2);
points.push({ x, y });
}
return points;
}
__name(generateArcPoints, "generateArcPoints");
async function bowTieRect(parent, node) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
const w = bbox.width + node.padding + 20;
const h = bbox.height + node.padding;
const ry = h / 2;
const rx = ry / (2.5 + h / 50);
const { cssStyles } = node;
const points = [
{ x: w / 2, y: -h / 2 },
{ x: -w / 2, y: -h / 2 },
...generateArcPoints(-w / 2, -h / 2, -w / 2, h / 2, rx, ry, false),
{ x: w / 2, y: h / 2 },
...generateArcPoints(w / 2, h / 2, w / 2, -h / 2, rx, ry, true)
];
const rc = rough3.svg(shapeSvg);
const options = userNodeOverrides(node, {});
if (node.look !== "handDrawn") {
options.roughness = 0;
options.fillStyle = "solid";
}
const bowTieRectPath = createPathFromPoints(points);
const bowTieRectShapePath = rc.path(bowTieRectPath, options);
const bowTieRectShape = shapeSvg.insert(() => bowTieRectShapePath, ":first-child");
bowTieRectShape.attr("class", "basic label-container");
if (cssStyles && node.look !== "handDrawn") {
bowTieRectShape.selectAll("path").attr("style", cssStyles);
}
if (nodeStyles && node.look !== "handDrawn") {
bowTieRectShape.selectAll("path").attr("style", nodeStyles);
}
bowTieRectShape.attr("transform", `translate(${rx / 2}, 0)`);
updateNodeBounds(node, bowTieRectShape);
node.intersect = function(point) {
const pos = intersect_default.polygon(node, points, point);
return pos;
};
return shapeSvg;
}
__name(bowTieRect, "bowTieRect");
// src/rendering-util/rendering-elements/shapes/card.ts
import rough4 from "roughjs";
// src/rendering-util/rendering-elements/shapes/insertPolygonShape.ts
function insertPolygonShape(parent, w, h, points) {
return parent.insert("polygon", ":first-child").attr(
"points",
points.map(function(d) {
return d.x + "," + d.y;
}).join(" ")
).attr("class", "label-container").attr("transform", "translate(" + -w / 2 + "," + h / 2 + ")");
}
__name(insertPolygonShape, "insertPolygonShape");
// src/rendering-util/rendering-elements/shapes/card.ts
async function card(parent, node) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
const h = bbox.height + node.padding;
const padding = 12;
const w = bbox.width + node.padding + padding;
const left = 0;
const right = w;
const top = -h;
const bottom = 0;
const points = [
{ x: left + padding, y: top },
{ x: right, y: top },
{ x: right, y: bottom },
{ x: left, y: bottom },
{ x: left, y: top + padding },
{ x: left + padding, y: top }
];
let polygon;
const { cssStyles } = node;
if (node.look === "handDrawn") {
const rc = rough4.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createPathFromPoints(points);
const roughNode = rc.path(pathData, options);
polygon = shapeSvg.insert(() => roughNode, ":first-child").attr("transform", `translate(${-w / 2}, ${h / 2})`);
if (cssStyles) {
polygon.attr("style", cssStyles);
}
} else {
polygon = insertPolygonShape(shapeSvg, w, h, points);
}
if (nodeStyles) {
polygon.attr("style", nodeStyles);
}
updateNodeBounds(node, polygon);
node.intersect = function(point) {
return intersect_default.polygon(node, points, point);
};
return shapeSvg;
}
__name(card, "card");
// src/rendering-util/rendering-elements/shapes/choice.ts
import rough5 from "roughjs";
function choice(parent, node) {
const { nodeStyles } = styles2String(node);
node.label = "";
const shapeSvg = parent.insert("g").attr("class", getNodeClasses(node)).attr("id", node.domId ?? node.id);
const { cssStyles } = node;
const s = Math.max(28, node.width ?? 0);
const points = [
{ x: 0, y: s / 2 },
{ x: s / 2, y: 0 },
{ x: 0, y: -s / 2 },
{ x: -s / 2, y: 0 }
];
const rc = rough5.svg(shapeSvg);
const options = userNodeOverrides(node, {});
if (node.look !== "handDrawn") {
options.roughness = 0;
options.fillStyle = "solid";
}
const choicePath = createPathFromPoints(points);
const roughNode = rc.path(choicePath, options);
const choiceShape = shapeSvg.insert(() => roughNode, ":first-child");
if (cssStyles && node.look !== "handDrawn") {
choiceShape.selectAll("path").attr("style", cssStyles);
}
if (nodeStyles && node.look !== "handDrawn") {
choiceShape.selectAll("path").attr("style", nodeStyles);
}
node.width = 28;
node.height = 28;
node.intersect = function(point) {
return intersect_default.polygon(node, points, point);
};
return shapeSvg;
}
__name(choice, "choice");
// src/rendering-util/rendering-elements/shapes/circle.ts
import rough6 from "roughjs";
async function circle(parent, node) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox, halfPadding } = await labelHelper(parent, node, getNodeClasses(node));
const radius = bbox.width / 2 + halfPadding;
let circleElem;
const { cssStyles } = node;
if (node.look === "handDrawn") {
const rc = rough6.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const roughNode = rc.circle(0, 0, radius * 2, options);
circleElem = shapeSvg.insert(() => roughNode, ":first-child");
circleElem.attr("class", "basic label-container").attr("style", handleUndefinedAttr(cssStyles));
} else {
circleElem = shapeSvg.insert("circle", ":first-child").attr("class", "basic label-container").attr("style", nodeStyles).attr("r", radius).attr("cx", 0).attr("cy", 0);
}
updateNodeBounds(node, circleElem);
node.intersect = function(point) {
log.info("Circle intersect", node, radius, point);
return intersect_default.circle(node, radius, point);
};
return shapeSvg;
}
__name(circle, "circle");
// src/rendering-util/rendering-elements/shapes/crossedCircle.ts
import rough7 from "roughjs";
function createLine(r) {
const xAxis45 = Math.cos(Math.PI / 4);
const yAxis45 = Math.sin(Math.PI / 4);
const lineLength = r * 2;
const pointQ1 = { x: lineLength / 2 * xAxis45, y: lineLength / 2 * yAxis45 };
const pointQ2 = { x: -(lineLength / 2) * xAxis45, y: lineLength / 2 * yAxis45 };
const pointQ3 = { x: -(lineLength / 2) * xAxis45, y: -(lineLength / 2) * yAxis45 };
const pointQ4 = { x: lineLength / 2 * xAxis45, y: -(lineLength / 2) * yAxis45 };
return `M ${pointQ2.x},${pointQ2.y} L ${pointQ4.x},${pointQ4.y}
M ${pointQ1.x},${pointQ1.y} L ${pointQ3.x},${pointQ3.y}`;
}
__name(createLine, "createLine");
function crossedCircle(parent, node) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
node.label = "";
const shapeSvg = parent.insert("g").attr("class", getNodeClasses(node)).attr("id", node.domId ?? node.id);
const radius = Math.max(30, node?.width ?? 0);
const { cssStyles } = node;
const rc = rough7.svg(shapeSvg);
const options = userNodeOverrides(node, {});
if (node.look !== "handDrawn") {
options.roughness = 0;
options.fillStyle = "solid";
}
const circleNode = rc.circle(0, 0, radius * 2, options);
const linePath = createLine(radius);
const lineNode = rc.path(linePath, options);
const crossedCircle2 = shapeSvg.insert(() => circleNode, ":first-child");
crossedCircle2.insert(() => lineNode);
if (cssStyles && node.look !== "handDrawn") {
crossedCircle2.selectAll("path").attr("style", cssStyles);
}
if (nodeStyles && node.look !== "handDrawn") {
crossedCircle2.selectAll("path").attr("style", nodeStyles);
}
updateNodeBounds(node, crossedCircle2);
node.intersect = function(point) {
log.info("crossedCircle intersect", node, { radius, point });
const pos = intersect_default.circle(node, radius, point);
return pos;
};
return shapeSvg;
}
__name(crossedCircle, "crossedCircle");
// src/rendering-util/rendering-elements/shapes/curlyBraceLeft.ts
import rough8 from "roughjs";
function generateCirclePoints2(centerX, centerY, radius, numPoints = 100, startAngle = 0, endAngle = 180) {
const points = [];
const startAngleRad = startAngle * Math.PI / 180;
const endAngleRad = endAngle * Math.PI / 180;
const angleRange = endAngleRad - startAngleRad;
const angleStep = angleRange / (numPoints - 1);
for (let i = 0; i < numPoints; i++) {
const angle = startAngleRad + i * angleStep;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
points.push({ x: -x, y: -y });
}
return points;
}
__name(generateCirclePoints2, "generateCirclePoints");
async function curlyBraceLeft(parent, node) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
const w = bbox.width + (node.padding ?? 0);
const h = bbox.height + (node.padding ?? 0);
const radius = Math.max(5, h * 0.1);
const { cssStyles } = node;
const points = [
...generateCirclePoints2(w / 2, -h / 2, radius, 30, -90, 0),
{ x: -w / 2 - radius, y: radius },
...generateCirclePoints2(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
...generateCirclePoints2(w / 2 + radius * 2, radius, radius, 20, -90, -180),
{ x: -w / 2 - radius, y: -h / 2 },
...generateCirclePoints2(w / 2, h / 2, radius, 20, 0, 90)
];
const rectPoints = [
{ x: w / 2, y: -h / 2 - radius },
{ x: -w / 2, y: -h / 2 - radius },
...generateCirclePoints2(w / 2, -h / 2, radius, 20, -90, 0),
{ x: -w / 2 - radius, y: -radius },
...generateCirclePoints2(w / 2 + w * 0.1, -radius, radius, 20, -180, -270),
...generateCirclePoints2(w / 2 + w * 0.1, radius, radius, 20, -90, -180),
{ x: -w / 2 - radius, y: h / 2 },
...generateCirclePoints2(w / 2, h / 2, radius, 20, 0, 90),
{ x: -w / 2, y: h / 2 + radius },
{ x: w / 2, y: h / 2 + radius }
];
const rc = rough8.svg(shapeSvg);
const options = userNodeOverrides(node, { fill: "none" });
if (node.look !== "handDrawn") {
options.roughness = 0;
options.fillStyle = "solid";
}
const curlyBraceLeftPath = createPathFromPoints(points);
const newCurlyBracePath = curlyBraceLeftPath.replace("Z", "");
const curlyBraceLeftNode = rc.path(newCurlyBracePath, options);
const rectPath = createPathFromPoints(rectPoints);
const rectShape = rc.path(rectPath, { ...options });
const curlyBraceLeftShape = shapeSvg.insert("g", ":first-child");
curlyBraceLeftShape.insert(() => rectShape, ":first-child").attr("stroke-opacity", 0);
curlyBraceLeftShape.insert(() => curlyBraceLeftNode, ":first-child");
curlyBraceLeftShape.attr("class", "text");
if (cssStyles && node.look !== "handDrawn") {
curlyBraceLeftShape.selectAll("path").attr("style", cssStyles);
}
if (nodeStyles && node.look !== "handDrawn") {
curlyBraceLeftShape.selectAll("path").attr("style", nodeStyles);
}
curlyBraceLeftShape.attr("transform", `translate(${radius}, 0)`);
label.attr(
"transform",
`translate(${-w / 2 + radius - (bbox.x - (bbox.left ?? 0))},${-h / 2 + (node.padding ?? 0) / 2 - (bbox.y - (bbox.top ?? 0))})`
);
updateNodeBounds(node, curlyBraceLeftShape);
node.intersect = function(point) {
const pos = intersect_default.polygon(node, rectPoints, point);
return pos;
};
return shapeSvg;
}
__name(curlyBraceLeft, "curlyBraceLeft");
// src/rendering-util/rendering-elements/shapes/curlyBraceRight.ts
import rough9 from "roughjs";
function generateCirclePoints3(centerX, centerY, radius, numPoints = 100, startAngle = 0, endAngle = 180) {
const points = [];
const startAngleRad = startAngle * Math.PI / 180;
const endAngleRad = endAngle * Math.PI / 180;
const angleRange = endAngleRad - startAngleRad;
const angleStep = angleRange / (numPoints - 1);
for (let i = 0; i < numPoints; i++) {
const angle = startAngleRad + i * angleStep;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
points.push({ x, y });
}
return points;
}
__name(generateCirclePoints3, "generateCirclePoints");
async function curlyBraceRight(parent, node) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
const w = bbox.width + (node.padding ?? 0);
const h = bbox.height + (node.padding ?? 0);
const radius = Math.max(5, h * 0.1);
const { cssStyles } = node;
const points = [
...generateCirclePoints3(w / 2, -h / 2, radius, 20, -90, 0),
{ x: w / 2 + radius, y: -radius },
...generateCirclePoints3(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
...generateCirclePoints3(w / 2 + radius * 2, radius, radius, 20, -90, -180),
{ x: w / 2 + radius, y: h / 2 },
...generateCirclePoints3(w / 2, h / 2, radius, 20, 0, 90)
];
const rectPoints = [
{ x: -w / 2, y: -h / 2 - radius },
{ x: w / 2, y: -h / 2 - radius },
...generateCirclePoints3(w / 2, -h / 2, radius, 20, -90, 0),
{ x: w / 2 + radius, y: -radius },
...generateCirclePoints3(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
...generateCirclePoints3(w / 2 + radius * 2, radius, radius, 20, -90, -180),
{ x: w / 2 + radius, y: h / 2 },
...generateCirclePoints3(w / 2, h / 2, radius, 20, 0, 90),
{ x: w / 2, y: h / 2 + radius },
{ x: -w / 2, y: h / 2 + radius }
];
const rc = rough9.svg(shapeSvg);
const options = userNodeOverrides(node, { fill: "none" });
if (node.look !== "handDrawn") {
options.roughness = 0;
options.fillStyle = "solid";
}
const curlyBraceRightPath = createPathFromPoints(points);
const newCurlyBracePath = curlyBraceRightPath.replace("Z", "");
const curlyBraceRightNode = rc.path(newCurlyBracePath, options);
const rectPath = createPathFromPoints(rectPoints);
const rectShape = rc.path(rectPath, { ...options });
const curlyBraceRightShape = shapeSvg.insert("g", ":first-child");
curlyBraceRightShape.insert(() => rectShape, ":first-child").attr("stroke-opacity", 0);
curlyBraceRightShape.insert(() => curlyBraceRightNode, ":first-child");
curlyBraceRightShape.attr("class", "text");
if (cssStyles && node.look !== "handDrawn") {
curlyBraceRightShape.selectAll("path").attr("style", cssStyles);
}
if (nodeStyles && node.look !== "handDrawn") {
curlyBraceRightShape.selectAll("path").attr("style", nodeStyles);
}
curlyBraceRightShape.attr("transform", `translate(${-radius}, 0)`);
label.attr(
"transform",
`translate(${-w / 2 + (node.padding ?? 0) / 2 - (bbox.x - (bbox.left ?? 0))},${-h / 2 + (node.padding ?? 0) / 2 - (bbox.y - (bbox.top ?? 0))})`
);
updateNodeBounds(node, curlyBraceRightShape);
node.intersect = function(point) {
const pos = intersect_default.polygon(node, rectPoints, point);
return pos;
};
return shapeSvg;
}
__name(curlyBraceRight, "curlyBraceRight");
// src/rendering-util/rendering-elements/shapes/curlyBraces.ts
import rough10 from "roughjs";
function generateCirclePoints4(centerX, centerY, radius, numPoints = 100, startAngle = 0, endAngle = 180) {
const points = [];
const startAngleRad = startAngle * Math.PI / 180;
const endAngleRad = endAngle * Math.PI / 180;
const angleRange = endAngleRad - startAngleRad;
const angleStep = angleRange / (numPoints - 1);
for (let i = 0; i < numPoints; i++) {
const angle = startAngleRad + i * angleStep;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
points.push({ x: -x, y: -y });
}
return points;
}
__name(generateCirclePoints4, "generateCirclePoints");
async function curlyBraces(parent, node) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
const w = bbox.width + (node.padding ?? 0);
const h = bbox.height + (node.padding ?? 0);
const radius = Math.max(5, h * 0.1);
const { cssStyles } = node;
const leftCurlyBracePoints = [
...generateCirclePoints4(w / 2, -h / 2, radius, 30, -90, 0),
{ x: -w / 2 - radius, y: radius },
...generateCirclePoints4(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
...generateCirclePoints4(w / 2 + radius * 2, radius, radius, 20, -90, -180),
{ x: -w / 2 - radius, y: -h / 2 },
...generateCirclePoints4(w / 2, h / 2, radius, 20, 0, 90)
];
const rightCurlyBracePoints = [
...generateCirclePoints4(-w / 2 + radius + radius / 2, -h / 2, radius, 20, -90, -180),
{ x: w / 2 - radius / 2, y: radius },
...generateCirclePoints4(-w / 2 - radius / 2, -radius, radius, 20, 0, 90),
...generateCirclePoints4(-w / 2 - radius / 2, radius, radius, 20, -90, 0),
{ x: w / 2 - radius / 2, y: -radius },
...generateCirclePoints4(-w / 2 + radius + radius / 2, h / 2, radius, 30, -180, -270)
];
const rectPoints = [
{ x: w / 2, y: -h / 2 - radius },
{ x: -w / 2, y: -h / 2 - radius },
...generateCirclePoints4(w / 2, -h / 2, radius, 20, -90, 0),
{ x: -w / 2 - radius, y: -radius },
...generateCirclePoints4(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
...generateCirclePoints4(w / 2 + radius * 2, radius, radius, 20, -90, -180),
{ x: -w / 2 - radius, y: h / 2 },
...generateCirclePoints4(w / 2, h / 2, radius, 20, 0, 90),
{ x: -w / 2, y: h / 2 + radius },
{ x: w / 2 - radius - radius / 2, y: h / 2 + radius },
...generateCirclePoints4(-w / 2 + radius + radius / 2, -h / 2, radius, 20, -90, -180),
{ x: w / 2 - radius / 2, y: radius },
...generateCirclePoints4(-w / 2 - radius / 2, -radius, radius, 20, 0, 90),
...generateCirclePoints4(-w / 2 - radius / 2, radius, radius, 20, -90, 0),
{ x: w / 2 - radius / 2, y: -radius },
...generateCirclePoints4(-w / 2 + radius + radius / 2, h / 2, radius, 30, -180, -270)
];
const rc = rough10.svg(shapeSvg);
const options = userNodeOverrides(node, { fill: "none" });
if (node.look !== "handDrawn") {
options.roughness = 0;
options.fillStyle = "solid";
}
const leftCurlyBracePath = createPathFromPoints(leftCurlyBracePoints);
const newLeftCurlyBracePath = leftCurlyBracePath.replace("Z", "");
const leftCurlyBraceNode = rc.path(newLeftCurlyBracePath, options);
const rightCurlyBracePath = createPathFromPoints(rightCurlyBracePoints);
const newRightCurlyBracePath = rightCurlyBracePath.replace("Z", "");
const rightCurlyBraceNode = rc.path(newRightCurlyBracePath, options);
const rectPath = createPathFromPoints(rectPoints);
const rectShape = rc.path(rectPath, { ...options });
const curlyBracesShape = shapeSvg.insert("g", ":first-child");
curlyBracesShape.insert(() => rectShape, ":first-child").attr("stroke-opacity", 0);
curlyBracesShape.insert(() => leftCurlyBraceNode, ":first-child");
curlyBracesShape.insert(() => rightCurlyBraceNode, ":first-child");
curlyBracesShape.attr("class", "text");
if (cssStyles && node.look !== "handDrawn") {
curlyBracesShape.selectAll("path").attr("style", cssStyles);
}
if (nodeStyles && node.look !== "handDrawn") {
curlyBracesShape.selectAll("path").attr("style", nodeStyles);
}
curlyBracesShape.attr("transform", `translate(${radius - radius / 4}, 0)`);
label.attr(
"transform",
`translate(${-w / 2 + (node.padding ?? 0) / 2 - (bbox.x - (bbox.left ?? 0))},${-h / 2 + (node.padding ?? 0) / 2 - (bbox.y - (bbox.top ?? 0))})`
);
updateNodeBounds(node, curlyBracesShape);
node.intersect = function(point) {
const pos = intersect_default.polygon(node, rectPoin