Path: blob/main/src/resources/formats/html/mermaid/mermaid-init.js
12923 views
// mermaid-init.js1// Initializes the quarto-mermaid JS runtime2//3// Copyright (C) 2022 Posit Software, PBC45/**6* String.prototype.replaceAll() polyfill7* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/8* @author Chris Ferdinandi9* @license MIT10*/11if (!String.prototype.replaceAll) {12String.prototype.replaceAll = function (str, newStr) {13// If a regex pattern14if (15Object.prototype.toString.call(str).toLowerCase() === "[object regexp]"16) {17return this.replace(str, newStr);18}1920// If a string21return this.replace(new RegExp(str, "g"), newStr);22};23}2425const mermaidOpts = {26startOnLoad: false,27};28// this CSS is adapted from29// mkdocs-material30// Copyright (c) 2016-2022 Martin Donath <[email protected]>3132const defaultCSS =33".label text{fill:var(--mermaid-fg-color)}.node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--mermaid-node-bg-color);stroke:var(--mermaid-node-fg-color)}marker{fill:var(--mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.label{color:var(--mermaid-label-fg-color);font-family:var(--mermaid-font-family);font-weight:var(--mermaid-font-weight)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--mermaid-label-fg-color)}.edgeLabel,.edgeLabel rect,.label div .edgeLabel{background-color:var(--mermaid-label-bg-color)}.edgeLabel,.edgeLabel rect{fill:var(--mermaid-label-bg-color);color:var(--mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--mermaid-edge-color)}.edgePath .arrowheadPath{fill:var(--mermaid-edge-color);stroke:none}.cluster rect{fill:var(--mermaid-fg-color--lightest);stroke:var(--mermaid-fg-color--lighter)}.cluster span{color:var(--mermaid-label-fg-color);font-family:var(--mermaid-font-family);font-weight:var(--mermaid-font-weight)}defs #flowchart-circleEnd,defs #flowchart-circleStart,defs #flowchart-crossEnd,defs #flowchart-crossStart,defs #flowchart-pointEnd,defs #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--mermaid-node-bg-color);stroke:var(--mermaid-node-fg-color)}g.classGroup text{fill:var(--mermaid-label-fg-color);font-family:var(--mermaid-font-family);font-weight:var(--mermaid-font-weight)}.classLabel .box{fill:var(--mermaid-label-bg-color);background-color:var(--mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--mermaid-label-fg-color);font-family:var(--mermaid-font-family);font-weight:var(--mermaid-font-weight)}.node .divider{stroke:var(--mermaid-node-fg-color)}.relation{stroke:var(--mermaid-edge-color)}.cardinality{fill:var(--mermaid-label-fg-color);font-family:var(--mermaid-font-family);font-weight:var(--mermaid-font-weight)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--mermaid-edge-color)!important;stroke:var(--mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--mermaid-label-bg-color)!important;stroke:var(--mermaid-edge-color)!important}g.stateGroup rect{fill:var(--mermaid-node-bg-color);stroke:var(--mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--mermaid-label-fg-color)!important;font-family:var(--mermaid-font-family);font-weight:var(--mermaid-font-weight)}g.stateGroup .composit{fill:var(--mermaid-label-bg-color)}.nodeLabel{color:var(--mermaid-label-fg-color);font-family:var(--mermaid-font-family);font-weight:var(--mermaid-font-weight)}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--mermaid-label-bg-color)}.transition{stroke:var(--mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--mermaid-bg-color)}.statediagram-cluster rect{fill:var(--mermaid-node-bg-color);stroke:var(--mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--mermaid-fg-color--lightest);stroke:var(--mermaid-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--mermaid-edge-color)}.entityBox{fill:var(--mermaid-label-bg-color);stroke:var(--mermaid-node-fg-color)}.entityLabel{fill:var(--mermaid-label-fg-color);font-family:var(--mermaid-font-family);font-weight:var(--mermaid-font-weight)}.relationshipLabelBox{fill:var(--mermaid-label-bg-color);fill-opacity:1;background-color:var(--mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--mermaid-label-fg-color)}.relationshipLine{stroke:var(--mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--mermaid-edge-color)!important}.actor,defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--mermaid-label-bg-color)}.actor{stroke:var(--mermaid-node-fg-color)}text.actor>tspan{fill:var(--mermaid-label-fg-color);font-family:var(--mermaid-font-family);font-weight:var(--mermaid-font-weight)}line{stroke:var(--mermaid-fg-color--lighter)}.messageLine0,.messageLine1{stroke:var(--mermaid-edge-color)}.loopText>tspan,.messageText,.noteText>tspan{fill:var(--mermaid-edge-color);stroke:none;font-family:var(--mermaid-font-family)!important;font-weight:var(--mermaid-font-weight)!important}.noteText>tspan{fill:#000}#arrowhead path{fill:var(--mermaid-edge-color);stroke:none}.loopLine{stroke:var(--mermaid-node-fg-color)}.labelBox,.loopLine{fill:var(--mermaid-node-bg-color)}.labelBox{stroke:none}.labelText,.labelText>span{fill:var(--mermaid-node-fg-color);font-family:var(--mermaid-font-family);font-weight:var(--mermaid-font-weight)}";3435const mermaidThemeEl = document.querySelector('meta[name="mermaid-theme"]');36if (mermaidThemeEl) {37mermaidOpts.theme = mermaidThemeEl.content;38} else {39mermaidOpts.themeCSS = defaultCSS;40}4142mermaid.initialize(mermaidOpts);4344const _quartoMermaid = {45// NB: there's effectively a copy of this function46// in `core/svg.ts`.47// if you change something here, you must keep it consistent there as well.48setSvgSize(svg) {49const { widthInPoints, heightInPoints, explicitHeight, explicitWidth } =50this.resolveSize(svg);5152if (explicitWidth && explicitHeight) {53svg.setAttribute("width", widthInPoints);54svg.setAttribute("height", heightInPoints);55svg.style.maxWidth = null; // remove mermaid's default max-width56} else {57if (explicitWidth) {58svg.style.maxWidth = `${widthInPoints}px`;59}60if (explicitHeight) {61svg.style.maxHeight = `${heightInPoints}px`;62}63}64},6566// NB: there's effectively a copy of this function67// in `core/svg.ts`.68// if you change something here, you must keep it consistent there as well.69makeResponsive(svg) {70const width = svg.getAttribute("width");71if (width === null) {72throw new Error("Couldn't find SVG width");73}74const numWidth = Number(width.slice(0, -2));7576if (numWidth > 650) {77changed = true;78svg.setAttribute("width", "100%");79svg.removeAttribute("height");80}81},8283// NB: there's effectively a copy of this function84// in `core/svg.ts`.85// if you change something here, you must keep it consistent there as well.86fixupAlignment(svg, align) {87let style = svg.getAttribute("style") || "";8889switch (align) {90case "left":91style = `${style}; display: block; margin: auto auto auto 0`;92break;93case "right":94style = `${style}; display: block; margin: auto 0 auto auto`;95break;96case "center":97style = `${style}; display: block; margin: auto auto auto auto`;98break;99}100svg.setAttribute("style", style);101},102103resolveOptions(svgEl) {104return svgEl.parentElement.parentElement.parentElement.parentElement105.dataset;106},107108// NB: there's effectively a copy of this function109// in our mermaid runtime in `core/svg.ts`.110// if you change something here, you must keep it consistent there as well.111resolveSize(svgEl) {112const inInches = (size) => {113if (size.endsWith("in")) {114return Number(size.slice(0, -2));115}116if (size.endsWith("pt") || size.endsWith("px")) {117// assume 96 dpi for now118return Number(size.slice(0, -2)) / 96;119}120return Number(size);121};122123// these are figWidth and figHeight on purpose,124// because data attributes are translated to camelCase by the DOM API125const kFigWidth = "figWidth",126kFigHeight = "figHeight";127const options = this.resolveOptions(svgEl);128let width = svgEl.getAttribute("width");129let height = svgEl.getAttribute("height");130const getViewBox = () => {131const vb = svgEl.attributes.getNamedItem("viewBox").value; // do it the roundabout way so that viewBox isn't dropped by deno_dom and text/html132if (!vb) return undefined;133const lst = vb.trim().split(" ").map(Number);134if (lst.length !== 4) return undefined;135if (lst.some(isNaN)) return undefined;136return lst;137};138if (!width || !height) {139// attempt to resolve figure dimensions via viewBox140const viewBox = getViewBox();141if (viewBox !== undefined) {142const [_mx, _my, vbWidth, vbHeight] = viewBox;143width = `${vbWidth}px`;144height = `${vbHeight}px`;145} else {146throw new Error(147"Mermaid generated an SVG without a viewbox attribute. Without knowing the diagram dimensions, quarto cannot convert it to a PNG"148);149}150}151152let svgWidthInInches, svgHeightInInches;153154if (155(width.slice(0, -2) === "pt" && height.slice(0, -2) === "pt") ||156(width.slice(0, -2) === "px" && height.slice(0, -2) === "px") ||157(!isNaN(Number(width)) && !isNaN(Number(height)))158) {159// we assume 96 dpi which is generally what seems to be used.160svgWidthInInches = Number(width.slice(0, -2)) / 96;161svgHeightInInches = Number(height.slice(0, -2)) / 96;162}163const viewBox = getViewBox();164if (viewBox !== undefined) {165// assume width and height come from viewbox.166const [_mx, _my, vbWidth, vbHeight] = viewBox;167svgWidthInInches = vbWidth / 96;168svgHeightInInches = vbHeight / 96;169} else {170throw new Error(171"Internal Error: Couldn't resolve width and height of SVG"172);173}174const svgWidthOverHeight = svgWidthInInches / svgHeightInInches;175let widthInInches, heightInInches;176177if (options[kFigWidth] && options[kFigHeight]) {178// both were prescribed, so just go with them179widthInInches = inInches(String(options[kFigWidth]));180heightInInches = inInches(String(options[kFigHeight]));181} else if (options[kFigWidth]) {182// we were only given width, use that and adjust height based on aspect ratio;183widthInInches = inInches(String(options[kFigWidth]));184heightInInches = widthInInches / svgWidthOverHeight;185} else if (options[kFigHeight]) {186// we were only given height, use that and adjust width based on aspect ratio;187heightInInches = inInches(String(options[kFigHeight]));188widthInInches = heightInInches * svgWidthOverHeight;189} else {190// we were not given either, use svg's prescribed height191heightInInches = svgHeightInInches;192widthInInches = svgWidthInInches;193}194195return {196widthInInches,197heightInInches,198widthInPoints: Math.round(widthInInches * 96),199heightInPoints: Math.round(heightInInches * 96),200explicitWidth: options?.[kFigWidth] !== undefined,201explicitHeight: options?.[kFigHeight] !== undefined,202};203},204205postProcess(svg) {206const options = this.resolveOptions(svg);207if (208options.responsive &&209options["figWidth"] === undefined &&210options["figHeight"] === undefined211) {212this.makeResponsive(svg);213} else {214this.setSvgSize(svg);215}216if (options["reveal"]) {217this.fixupAlignment(svg, options["figAlign"] || "center");218}219220// forward align attributes to the correct parent dif221// so that the svg figure is aligned correctly222const div = svg.parentElement.parentElement.parentElement;223const align = div.parentElement.parentElement.dataset.layoutAlign;224if (align) {225div.classList.remove("quarto-figure-left");226div.classList.remove("quarto-figure-center");227div.classList.remove("quarto-figure-right");228div.classList.add(`quarto-figure-${align}`);229}230},231};232233// deno-lint-ignore no-window-prefix234window.addEventListener(235"load",236async function () {237let i = 0;238// we need pre because of whitespace preservation239for (const el of Array.from(document.querySelectorAll("pre.mermaid-js"))) {240// doesn't appear to be treated as whitespace by mermaid241// so we replace it with a space.242const text = el.textContent.replaceAll(" ", " ");243const { svg: output } = await mermaid.mermaidAPI.render(244`mermaid-${++i}`,245text,246el247);248el.innerHTML = output;249if (el.dataset.label) {250// patch mermaid's emitted style251const svg = el.firstChild;252const style = svg.querySelector("style");253style.innerHTML = style.innerHTML.replaceAll(254`#${svg.id}`,255`#${el.dataset.label}-mermaid`256);257svg.id = el.dataset.label + "-mermaid";258delete el.dataset.label;259}260261const svg = el.querySelector("svg");262const parent = el.parentElement;263parent.removeChild(el);264parent.appendChild(svg);265svg.classList.add("mermaid-js");266}267for (const svgEl of Array.from(268document.querySelectorAll("svg.mermaid-js")269)) {270_quartoMermaid.postProcess(svgEl);271}272},273false274);275276277