mermaid
Version:
Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.
479 lines (474 loc) • 19.3 kB
JavaScript
import {
getSubGraphTitleMargins
} from "./chunk-OBVCFTLP.mjs";
import {
createLabel_default,
createRoundedRectPathD,
intersect_rect_default
} from "./chunk-TEH6E4GO.mjs";
import {
styles2String,
userNodeOverrides
} from "./chunk-P2QGCYS3.mjs";
import {
createText
} from "./chunk-GMAD6QVW.mjs";
import {
evaluate,
getConfig2 as getConfig,
getEffectiveHtmlLabels
} from "./chunk-DU6HZSFF.mjs";
import {
log
} from "./chunk-X3CZISLH.mjs";
import {
__name
} from "./chunk-Y2CYZVJY.mjs";
// src/rendering-util/rendering-elements/clusters.js
import { select as select2 } from "d3";
import rough2 from "roughjs";
// src/rendering-util/rendering-elements/clusters/swimlane.js
import { select } from "d3";
import rough from "roughjs";
var swimlane = /* @__PURE__ */ __name(async (parent, node) => {
const siteConfig = getConfig();
const { themeVariables, handDrawnSeed } = siteConfig;
const { clusterBkg, clusterBorder } = themeVariables;
const laneStroke = clusterBorder;
const { labelStyles, nodeStyles, borderStyles, backgroundStyles } = styles2String(node);
const shapeSvg = parent.insert("g").attr("class", "cluster swimlane " + (node.cssClasses || "")).attr("id", node.id).attr("data-id", node.id).attr("data-et", "cluster").attr("data-look", node.look);
const useHtmlLabels = evaluate(siteConfig.flowchart.htmlLabels);
const isLR = node.direction === "LR";
const labelEl = shapeSvg.insert("g").attr("class", "cluster-label swimlane-label");
const text = await createText(labelEl, node.label, {
style: node.labelStyle,
useHtmlLabels,
isNode: true,
width: node.width
});
let bbox = text.getBBox();
if (useHtmlLabels) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
dv.attr("width", bbox.width);
dv.attr("height", bbox.height);
}
const padding = node.padding ?? 0;
const width = node.width <= bbox.width + padding ? bbox.width + padding : node.width;
if (node.width <= bbox.width + padding) {
node.diff = (width - node.width) / 2 - padding;
} else {
node.diff = -padding;
}
const height = node.height;
const laneTop = node.y - height / 2;
const laneBottom = node.y + height / 2;
const laneLeft = node.x - width / 2;
const contentTop = node.swimlaneContentTop !== void 0 ? node.swimlaneContentTop : laneTop + height / 3;
const titlePaddingY = isLR ? 4 : 0;
const desiredTitleSize = bbox.height + 2 * titlePaddingY;
let titleRect;
let bodyRect;
if (isLR) {
const titleWidth = Math.max(desiredTitleSize, bbox.height + 2 * titlePaddingY);
const bodyX = laneLeft + titleWidth;
const bodyWidth = Math.max(0, width - titleWidth);
if (node.look === "handDrawn") {
const rc = rough.svg(shapeSvg);
const titleOptions = userNodeOverrides(node, {
roughness: 0.7,
fill: clusterBkg,
stroke: laneStroke,
fillWeight: 3,
seed: handDrawnSeed
});
const bodyOptions = userNodeOverrides(node, {
roughness: 0.7,
fill: "none",
stroke: laneStroke,
seed: handDrawnSeed
});
const roughTitle = rc.rectangle(laneLeft, laneTop, titleWidth, height, titleOptions);
titleRect = shapeSvg.insert(() => roughTitle, ":first-child");
const roughBody = rc.rectangle(bodyX, laneTop, bodyWidth, height, bodyOptions);
bodyRect = shapeSvg.insert(() => roughBody, ":first-child");
titleRect.select("path:nth-child(2)").attr("style", borderStyles.join(";"));
titleRect.select("path").attr("style", backgroundStyles.join(";").replace("fill", "stroke"));
} else {
titleRect = shapeSvg.insert("rect", ":first-child");
bodyRect = shapeSvg.insert("rect", ":first-child");
titleRect.attr("class", "swimlane-title").attr("style", nodeStyles).attr("x", laneLeft).attr("y", laneTop).attr("width", titleWidth).attr("height", height).attr("fill", clusterBkg).attr("stroke", laneStroke);
bodyRect.attr("class", "swimlane-body").attr("style", nodeStyles).attr("x", bodyX).attr("y", laneTop).attr("width", bodyWidth).attr("height", height).attr("fill", "none").attr("stroke", laneStroke);
}
const labelCenterX = laneLeft + titleWidth / 2;
const labelCenterY = node.y;
labelEl.attr(
"transform",
`translate(${labelCenterX}, ${labelCenterY}) rotate(-90) translate(${-bbox.width / 2}, ${-bbox.height / 2})`
);
} else {
const headerMaxHeight = Math.max(0, contentTop - laneTop);
const titleHeight = Math.min(desiredTitleSize, headerMaxHeight);
const bodyY = laneTop + titleHeight;
const contentHeight = Math.max(0, laneBottom - bodyY);
const x = node.x - width / 2;
if (node.look === "handDrawn") {
const rc = rough.svg(shapeSvg);
const titleOptions = userNodeOverrides(node, {
roughness: 0.7,
fill: clusterBkg,
stroke: laneStroke,
fillWeight: 3,
seed: handDrawnSeed
});
const bodyOptions = userNodeOverrides(node, {
roughness: 0.7,
fill: "none",
stroke: laneStroke,
seed: handDrawnSeed
});
const roughTitle = rc.rectangle(x, laneTop, width, titleHeight, titleOptions);
titleRect = shapeSvg.insert(() => roughTitle, ":first-child");
const roughBody = rc.rectangle(x, bodyY, width, contentHeight, bodyOptions);
bodyRect = shapeSvg.insert(() => roughBody, ":first-child");
titleRect.select("path:nth-child(2)").attr("style", borderStyles.join(";"));
titleRect.select("path").attr("style", backgroundStyles.join(";").replace("fill", "stroke"));
} else {
titleRect = shapeSvg.insert("rect", ":first-child");
bodyRect = shapeSvg.insert("rect", ":first-child");
titleRect.attr("class", "swimlane-title").attr("style", nodeStyles).attr("x", x).attr("y", laneTop).attr("width", width).attr("height", titleHeight).attr("fill", clusterBkg).attr("stroke", laneStroke);
bodyRect.attr("class", "swimlane-body").attr("style", nodeStyles).attr("x", x).attr("y", bodyY).attr("width", width).attr("height", contentHeight).attr("fill", "none").attr("stroke", laneStroke);
}
const labelX = node.x - bbox.width / 2;
const labelY = laneTop + (titleHeight - bbox.height) / 2;
labelEl.attr("transform", `translate(${labelX}, ${labelY})`);
}
log.trace("Swimlane data ", node, JSON.stringify(node));
if (labelStyles) {
const span = labelEl.select("span");
if (span) {
span.attr("style", labelStyles);
}
}
node.offsetX = 0;
node.width = width;
node.height = height;
node.offsetY = bbox.height - padding / 2;
node.intersect = function(point) {
return intersect_rect_default(node, point);
};
return { cluster: shapeSvg, labelBBox: bbox };
}, "swimlane");
// 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 = getConfig();
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.domId).attr("data-look", node.look);
const useHtmlLabels = getEffectiveHtmlLabels(siteConfig);
const labelEl = shapeSvg.insert("g").attr("class", "cluster-label ");
let text;
if (node.labelType === "markdown") {
text = await createText(labelEl, node.label, {
style: node.labelStyle,
useHtmlLabels,
isNode: true,
width: node.width
});
} else {
text = await createLabel_default(labelEl, node.label, node.labelStyle || "", false, true);
}
let bbox = text.getBBox();
if (getEffectiveHtmlLabels(siteConfig)) {
const div = text.children[0];
const dv = select2(text);
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 = rough2.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.domId);
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 = getConfig();
const { themeVariables, handDrawnSeed } = siteConfig;
const { altBackground, compositeBackground, compositeTitleBackground, nodeBorder } = themeVariables;
const shapeSvg = parent.insert("g").attr("class", node.cssClasses).attr("id", node.domId).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 text = await createLabel_default(label, node.label, node.labelStyle, void 0, true);
let bbox = text.getBBox();
if (getEffectiveHtmlLabels(siteConfig)) {
const div = text.children[0];
const dv = select2(text);
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 = rough2.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 - (getEffectiveHtmlLabels(siteConfig) ? 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 = getConfig();
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.domId).attr("data-look", node.look);
const useHtmlLabels = getEffectiveHtmlLabels(siteConfig);
const labelEl = shapeSvg.insert("g").attr("class", "cluster-label ");
const text = await createText(labelEl, node.label, {
style: node.labelStyle,
useHtmlLabels,
isNode: true,
width: node.width
});
let bbox = text.getBBox();
if (getEffectiveHtmlLabels(siteConfig)) {
const div = text.children[0];
const dv = select2(text);
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 = rough2.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 = getConfig();
const { themeVariables, handDrawnSeed } = siteConfig;
const { nodeBorder } = themeVariables;
const shapeSvg = parent.insert("g").attr("class", node.cssClasses).attr("id", node.domId).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 = rough2.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");
let outerRectClass = "outer";
if (node.look === "neo") {
outerRectClass = "divider";
} else {
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,
swimlane
};
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");
export {
insertCluster,
clear
};