/** * This is the api to be used when optionally handling the integration with the web page, instead of using the default integration provided by mermaid.js. * * The core of this api is the [**render**](https://github.com/knsv/mermaid/blob/master/docs/mermaidAPI.md#render) function which, given a graph * definition as text, renders the graph/diagram and returns an svg element for the graph. * * It is is then up to the user of the API to make use of the svg, either insert it somewhere in the page or do something completely different. * * In addition to the render function, a number of behavioral configuration options are available. * * @name mermaidAPI */ import * as d3 from 'd3' import scope from 'scope-css' import pkg from '../package.json' import { setConfig, getConfig } from './config' import { logger, setLogLevel } from './logger' import utils from './utils' import flowRenderer from './diagrams/flowchart/flowRenderer' import flowParser from './diagrams/flowchart/parser/flow' import flowDb from './diagrams/flowchart/flowDb' import sequenceRenderer from './diagrams/sequence/sequenceRenderer' import sequenceParser from './diagrams/sequence/parser/sequenceDiagram' import sequenceDb from './diagrams/sequence/sequenceDb' import ganttRenderer from './diagrams/gantt/ganttRenderer' import ganttParser from './diagrams/gantt/parser/gantt' import ganttDb from './diagrams/gantt/ganttDb' import classRenderer from './diagrams/class/classRenderer' import classParser from './diagrams/class/parser/classDiagram' import classDb from './diagrams/class/classDb' import gitGraphRenderer from './diagrams/git/gitGraphRenderer' import gitGraphParser from './diagrams/git/parser/gitGraph' import gitGraphAst from './diagrams/git/gitGraphAst' import infoRenderer from './diagrams/info/infoRenderer' import infoParser from './diagrams/info/parser/info' import infoDb from './diagrams/info/infoDb' const themes = {} for (const themeName of ['default', 'forest', 'dark', 'neutral']) { themes[themeName] = require(`./themes/${themeName}/index.scss`) } /** * These are the default options which can be overridden with the initialization call like so: * **Example 1:** *
 * mermaid.initialize({
 *   flowchart:{
 *      htmlLabels: false
 *   }
 * });
 * 
* * **Example 2:** *
 *  
 * 
* A summary of all options and their defaults is found [here](https://github.com/knsv/mermaid/blob/master/docs/mermaidAPI.md#mermaidapi-configuration-defaults). A description of each option follows below. * * @name Configuration */ const config = { /** theme , the CSS style sheet * * **theme** - Choose one of the built-in themes: * * default * * forest * * dark * * neutral. * To disable any pre-defined mermaid theme, use "null". * * **themeCSS** - Use your own CSS. This overrides **theme**. *
  *  "theme": "forest",
  *  "themeCSS": ".node rect { fill: red; }"
  * 
*/ theme: 'default', themeCSS: undefined, /** * This option decides the amount of logging to be used. * * debug: 1 * * info: 2 * * warn: 3 * * error: 4 * * fatal: (**default**) 5 */ logLevel: 5, /** * Sets the level of trust to be used on the parsed diagrams. * * **strict**: (**default**) tags in text are encoded, click functionality is disabeled * * **loose**: tags in text are allowed, click functionality is enabled */ securityLevel: 'strict', /** * This options controls whether or mermaid starts when the page loads * **Default value true**. */ startOnLoad: true, /** * This options controls whether or arrow markers in html code will be absolute paths or * an anchor, #. This matters if you are using base tag settings. * **Default value false**. */ arrowMarkerAbsolute: false, /** * The object containing configurations specific for flowcharts */ flowchart: { /** * Flag for setting whether or not a html tag should be used for rendering labels * on the edges. * **Default value true**. */ htmlLabels: true, /** * How mermaid renders curves for flowcharts. Possible values are * * basis * * linear **default** * * cardinal */ curve: 'linear' }, /** * The object containing configurations specific for sequence diagrams */ sequence: { /** * margin to the right and left of the sequence diagram. * **Default value 50**. */ diagramMarginX: 50, /** * margin to the over and under the sequence diagram. * **Default value 10**. */ diagramMarginY: 10, /** * Margin between actors. * **Default value 50**. */ actorMargin: 50, /** * Width of actor boxes * **Default value 150**. */ width: 150, /** * Height of actor boxes * **Default value 65**. */ height: 65, /** * Margin around loop boxes * **Default value 10**. */ boxMargin: 10, /** * margin around the text in loop/alt/opt boxes * **Default value 5**. */ boxTextMargin: 5, /** * margin around notes. * **Default value 10**. */ noteMargin: 10, /** * Space between messages. * **Default value 35**. */ messageMargin: 35, /** * mirror actors under diagram. * **Default value true**. */ mirrorActors: true, /** * Depending on css styling this might need adjustment. * Prolongs the edge of the diagram downwards. * **Default value 1**. */ bottomMarginAdj: 1, /** * when this flag is set the height and width is set to 100% and is then scaling with the * available space if not the absolute space required is used. * **Default value true**. */ useMaxWidth: true, /** * This will display arrows that start and begin at the same node as right angles, rather than a curve * **Default value false**. */ rightAngles: false, /** * This will show the node numbers * **Default value false**. */ showSequenceNumbers: false }, /** * The object containing configurations specific for gantt diagrams* */ gantt: { /** * Margin top for the text over the gantt diagram * **Default value 25**. */ titleTopMargin: 25, /** * The height of the bars in the graph * **Default value 20**. */ barHeight: 20, /** * The margin between the different activities in the gantt diagram. * **Default value 4**. */ barGap: 4, /** * Margin between title and gantt diagram and between axis and gantt diagram. * **Default value 50**. */ topPadding: 50, /** * The space allocated for the section name to the left of the activities. * **Default value 75**. */ leftPadding: 75, /** * Vertical starting position of the grid lines. * **Default value 35**. */ gridLineStartPadding: 35, /** * Font size ... * **Default value 11**. */ fontSize: 11, /** * font family ... * **Default value '"Open-Sans", "sans-serif"'**. */ fontFamily: '"Open-Sans", "sans-serif"', /** * The number of alternating section styles. * **Default value 4**. */ numberSectionStyles: 4, /** * Datetime format of the axis. This might need adjustment to match your locale and preferences * **Default value '%Y-%m-%d'**. */ axisFormat: '%Y-%m-%d' }, class: {}, git: {} } setLogLevel(config.logLevel) setConfig(config) function parse (text) { const graphType = utils.detectType(text) let parser logger.debug('Type ' + graphType) switch (graphType) { case 'git': parser = gitGraphParser parser.parser.yy = gitGraphAst break case 'flowchart': parser = flowParser parser.parser.yy = flowDb break case 'sequence': parser = sequenceParser parser.parser.yy = sequenceDb break case 'gantt': parser = ganttParser parser.parser.yy = ganttDb break case 'class': parser = classParser parser.parser.yy = classDb break case 'info': logger.debug('info info info') console.warn('In API', pkg.version) parser = infoParser parser.parser.yy = infoDb break } parser.parser.yy.parseError = (str, hash) => { const error = { str, hash } throw error } parser.parse(text) } export const encodeEntities = function (text) { let txt = text txt = txt.replace(/style.*:\S*#.*;/g, function (s) { const innerTxt = s.substring(0, s.length - 1) return innerTxt }) txt = txt.replace(/classDef.*:\S*#.*;/g, function (s) { const innerTxt = s.substring(0, s.length - 1) return innerTxt }) txt = txt.replace(/#\w+;/g, function (s) { const innerTxt = s.substring(1, s.length - 1) const isInt = /^\+?\d+$/.test(innerTxt) if (isInt) { return 'fl°°' + innerTxt + '¶ß' } else { return 'fl°' + innerTxt + '¶ß' } }) return txt } export const decodeEntities = function (text) { let txt = text txt = txt.replace(/fl°°/g, function () { return '&#' }) txt = txt.replace(/fl°/g, function () { return '&' }) txt = txt.replace(/¶ß/g, function () { return ';' }) return txt } /** * Function that renders an svg with a graph from a chart definition. Usage example below. * * ```js * mermaidAPI.initialize({ * startOnLoad:true * }); * $(function(){ * const graphDefinition = 'graph TB\na-->b'; * const cb = function(svgGraph){ * console.log(svgGraph); * }; * mermaidAPI.render('id1',graphDefinition,cb); * }); *``` * @param id the id of the element to be rendered * @param txt the graph definition * @param cb callback which is called after rendering is finished with the svg code as inparam. * @param container selector to element in which a div with the graph temporarily will be inserted. In one is * provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is * completed. */ const render = function (id, txt, cb, container) { if (typeof container !== 'undefined') { container.innerHTML = '' d3.select(container).append('div') .attr('id', 'd' + id) .append('svg') .attr('id', id) .attr('width', '100%') .attr('xmlns', 'http://www.w3.org/2000/svg') .append('g') } else { const element = document.querySelector('#' + 'd' + id) if (element) { element.innerHTML = '' } d3.select('body').append('div') .attr('id', 'd' + id) .append('svg') .attr('id', id) .attr('width', '100%') .attr('xmlns', 'http://www.w3.org/2000/svg') .append('g') } window.txt = txt txt = encodeEntities(txt) const element = d3.select('#d' + id).node() const graphType = utils.detectType(txt) // insert inline style into svg const svg = element.firstChild const firstChild = svg.firstChild // pre-defined theme let style = themes[config.theme] if (style === undefined) { style = '' } // user provided theme CSS if (config.themeCSS !== undefined) { style += `\n${config.themeCSS}` } // classDef if (graphType === 'flowchart') { const classes = flowRenderer.getClasses(txt) for (const className in classes) { style += `\n.${className} > * { ${classes[className].styles.join(' !important; ')} !important; }` } } const style1 = document.createElement('style') style1.innerHTML = scope(style, `#${id}`) svg.insertBefore(style1, firstChild) const style2 = document.createElement('style') const cs = window.getComputedStyle(svg) style2.innerHTML = `#${id} { color: ${cs.color}; font: ${cs.font}; }` svg.insertBefore(style2, firstChild) switch (graphType) { case 'git': config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute gitGraphRenderer.setConf(config.git) gitGraphRenderer.draw(txt, id, false) break case 'flowchart': config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute flowRenderer.setConf(config.flowchart) flowRenderer.draw(txt, id, false) break case 'sequence': config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute if (config.sequenceDiagram) { // backwards compatibility sequenceRenderer.setConf(Object.assign(config.sequence, config.sequenceDiagram)) console.error('`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.') } else { sequenceRenderer.setConf(config.sequence) } sequenceRenderer.draw(txt, id) break case 'gantt': config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute ganttRenderer.setConf(config.gantt) ganttRenderer.draw(txt, id) break case 'class': config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute classRenderer.setConf(config.class) classRenderer.draw(txt, id) break case 'info': config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute infoRenderer.setConf(config.class) infoRenderer.draw(txt, id, pkg.version) break } d3.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', 'http://www.w3.org/1999/xhtml') let url = '' if (config.arrowMarkerAbsolute) { url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search url = url.replace(/\(/g, '\\(') url = url.replace(/\)/g, '\\)') } // Fix for when the base tag is used let svgCode = d3.select('#d' + id).node().innerHTML.replace(/url\(#arrowhead/g, 'url(' + url + '#arrowhead', 'g') svgCode = decodeEntities(svgCode) if (typeof cb !== 'undefined') { switch (graphType) { case 'flowchart': cb(svgCode, flowDb.bindFunctions) break case 'gantt': cb(svgCode, ganttDb.bindFunctions) break default: cb(svgCode) } } else { logger.debug('CB = undefined!') } const node = d3.select('#d' + id).node() if (node !== null && typeof node.remove === 'function') { d3.select('#d' + id).node().remove() } return svgCode } const setConf = function (cnf) { // Top level initially mermaid, gflow, sequenceDiagram and gantt const lvl1Keys = Object.keys(cnf) for (let i = 0; i < lvl1Keys.length; i++) { if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) { const lvl2Keys = Object.keys(cnf[lvl1Keys[i]]) for (let j = 0; j < lvl2Keys.length; j++) { logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j]) if (typeof config[lvl1Keys[i]] === 'undefined') { config[lvl1Keys[i]] = {} } logger.debug('Setting config: ' + lvl1Keys[i] + ' ' + lvl2Keys[j] + ' to ' + cnf[lvl1Keys[i]][lvl2Keys[j]]) config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]] } } else { config[lvl1Keys[i]] = cnf[lvl1Keys[i]] } } } function initialize (options) { logger.debug('Initializing mermaidAPI ', pkg.version) // Update default config with options supplied at initialization if (typeof options === 'object') { setConf(options) } setConfig(config) setLogLevel(config.logLevel) } // function getConfig () { // console.warn('get config') // return config // } const mermaidAPI = { render, parse, initialize, getConfig } export default mermaidAPI /** * ## mermaidAPI configuration defaults *
 *
 * 
 *
*/