mermaid/src/mermaidAPI.js

492 lines
13 KiB
JavaScript
Raw Normal View History

/**
* ---
* title: mermaidAPI
* order: 5
* ---
* # mermaidAPI
* This is the api to be used when handling the integration with the web page instead of using the default integration
* (mermaid.js).
*
* The core of this api is the **render** function that given a graph definitionas text renders the graph/diagram and
* returns a 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 something completely different.
2017-09-10 00:29:22 +08:00
*/
2018-03-08 21:42:39 +08:00
import * as d3 from 'd3'
2018-03-11 21:36:59 +08:00
import scope from 'scope-css'
2018-03-08 21:42:39 +08:00
2017-09-10 19:41:34 +08:00
import { logger, setLogLevel } from './logger'
import utils from './utils'
import flowRenderer from './diagrams/flowchart/flowRenderer'
import flowParser from './diagrams/flowchart/parser/flow'
2018-03-13 15:28:48 +08:00
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'
2018-03-12 21:06:49 +08:00
import ganttRenderer from './diagrams/gantt/ganttRenderer'
2017-09-10 19:41:34 +08:00
import ganttParser from './diagrams/gantt/parser/gantt'
import ganttDb from './diagrams/gantt/ganttDb'
2018-03-13 15:28:48 +08:00
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'
2017-04-11 22:14:25 +08:00
2018-03-13 14:31:56 +08:00
const themes = {}
for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
themes[themeName] = require(`./themes/${themeName}/index.scss`)
2017-09-12 22:54:50 +08:00
}
2017-09-12 22:06:19 +08:00
/**
* ## Configuration
* These are the default options which can be overridden with the initialization call as in the example below:
* ```
* mermaid.initialize({
* flowchart:{
* htmlLabels: false
* }
* });
* ```
*/
2017-09-14 19:40:38 +08:00
const config = {
2018-03-15 21:22:49 +08:00
theme: 'default',
2018-03-15 21:47:53 +08:00
themeCSS: undefined,
2017-09-12 22:54:50 +08:00
2017-04-16 23:24:47 +08:00
/**
* logLevel , decides the amount of logging to be used.
* * debug: 1
* * info: 2
* * warn: 3
* * error: 4
* * fatal: 5
*/
2017-04-11 22:14:25 +08:00
logLevel: 5,
2017-04-16 23:24:47 +08:00
/**
* **startOnLoad** - This options controls whether or mermaid starts when the page loads
*/
2017-04-11 22:14:25 +08:00
startOnLoad: true,
2017-04-16 23:24:47 +08:00
/**
* **arrowMarkerAbsolute** - 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.
*/
2017-04-11 22:14:25 +08:00
arrowMarkerAbsolute: false,
2017-04-16 23:24:47 +08:00
/**
* ### flowchart
* *The object containing configurations specific for flowcharts*
*/
flowchart: {
/**
2017-04-16 23:24:47 +08:00
* **htmlLabels** - Flag for setting whether or not a html tag should be used for rendering labels
* on the edges
*/
2017-04-11 22:14:25 +08:00
htmlLabels: true,
2017-04-16 23:24:47 +08:00
/**
* **useMaxWidth** - Flag for setting whether or not a all available width should be used for
* the diagram.
*/
2018-03-17 23:05:14 +08:00
useMaxWidth: true,
curve: 'linear'
2017-04-11 22:14:25 +08:00
},
2017-04-16 23:24:47 +08:00
/**
* ### sequenceDiagram
* The object containing configurations specific for sequence diagrams
*/
2018-03-12 21:16:22 +08:00
sequence: {
2017-04-16 23:24:47 +08:00
/**
* **diagramMarginX** - margin to the right and left of the sequence diagram
*/
2017-04-11 22:14:25 +08:00
diagramMarginX: 50,
2017-04-16 23:24:47 +08:00
/**
* **diagramMarginY** - margin to the over and under the sequence diagram
*/
2017-04-11 22:14:25 +08:00
diagramMarginY: 10,
/**
* **actorMargin** - Margin between actors
*/
2017-04-11 22:14:25 +08:00
actorMargin: 50,
/**
* **width** - Width of actor boxes
*/
2017-04-11 22:14:25 +08:00
width: 150,
/**
* **height** - Height of actor boxes
*/
2017-04-11 22:14:25 +08:00
height: 65,
/**
* **boxMargin** - Margin around loop boxes
*/
2017-04-11 22:14:25 +08:00
boxMargin: 10,
2017-04-16 23:24:47 +08:00
/**
* **boxTextMargin** - margin around the text in loop/alt/opt boxes
*/
2017-04-11 22:14:25 +08:00
boxTextMargin: 5,
2017-04-16 23:24:47 +08:00
/**
* **noteMargin** - margin around notes
2017-04-16 23:24:47 +08:00
*/
2017-04-11 22:14:25 +08:00
noteMargin: 10,
2017-04-16 23:24:47 +08:00
/**
* **messageMargin** - Space between messages
2017-04-16 23:24:47 +08:00
*/
2017-04-11 22:14:25 +08:00
messageMargin: 35,
/**
* **mirrorActors** - mirror actors under diagram
*/
2017-04-11 22:14:25 +08:00
mirrorActors: true,
/**
* **bottomMarginAdj** - Depending on css styling this might need adjustment.
* Prolongs the edge of the diagram downwards
*/
2017-04-11 22:14:25 +08:00
bottomMarginAdj: 1,
/**
* **useMaxWidth** - 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
*/
2017-04-11 22:14:25 +08:00
useMaxWidth: true
},
2017-04-16 23:24:47 +08:00
/** ### gantt
* The object containing configurations specific for gantt diagrams*
*/
2017-04-11 22:14:25 +08:00
gantt: {
2017-04-16 23:24:47 +08:00
/**
* **titleTopMargin** - margin top for the text over the gantt diagram
*/
2017-04-11 22:14:25 +08:00
titleTopMargin: 25,
2017-04-16 23:24:47 +08:00
/**
* **barHeight** - the height of the bars in the graph
*/
2017-04-11 22:14:25 +08:00
barHeight: 20,
2017-04-16 23:24:47 +08:00
/**
* **barGap** - the margin between the different activities in the gantt diagram
*/
2017-04-11 22:14:25 +08:00
barGap: 4,
2017-04-16 23:24:47 +08:00
/**
* **topPadding** - margin between title and gantt diagram and between axis and gantt diagram.
*/
2017-04-11 22:14:25 +08:00
topPadding: 50,
2017-04-16 23:24:47 +08:00
/**
* **leftPadding** - the space allocated for the section name to the left of the activities.
*/
2017-04-11 22:14:25 +08:00
leftPadding: 75,
2017-04-16 23:24:47 +08:00
/**
* **gridLineStartPadding** - Vertical starting position of the grid lines
*/
2017-04-11 22:14:25 +08:00
gridLineStartPadding: 35,
2017-04-16 23:24:47 +08:00
/**
* **fontSize** - font size ...
*/
2017-04-11 22:14:25 +08:00
fontSize: 11,
2017-04-16 23:24:47 +08:00
/**
* **fontFamily** - font family ...
*/
2017-04-11 22:14:25 +08:00
fontFamily: '"Open-Sans", "sans-serif"',
2017-04-16 23:24:47 +08:00
/**
* **numberSectionStyles** - the number of alternating section styles
*/
2018-03-13 21:17:32 +08:00
numberSectionStyles: 4,
2017-04-16 23:24:47 +08:00
/**
* **axisFormat** - datetime format of the axis, this might need adjustment to match your locale and preferences
*/
axisFormat: '%Y-%m-%d'
2017-04-11 22:14:25 +08:00
},
2018-03-12 21:16:22 +08:00
class: {},
2018-03-13 15:19:24 +08:00
git: {}
2017-04-11 22:14:25 +08:00
}
2017-09-10 19:41:34 +08:00
setLogLevel(config.logLevel)
2015-10-04 19:30:53 +02:00
2017-09-10 10:45:38 +08:00
function parse (text) {
2017-09-14 19:40:38 +08:00
const graphType = utils.detectType(text)
let parser
2017-04-11 22:14:25 +08:00
switch (graphType) {
2018-03-12 21:16:22 +08:00
case 'git':
2017-04-11 22:14:25 +08:00
parser = gitGraphParser
parser.parser.yy = gitGraphAst
break
2018-03-12 21:06:49 +08:00
case 'flowchart':
2017-04-11 22:14:25 +08:00
parser = flowParser
2018-03-12 20:52:06 +08:00
parser.parser.yy = flowDb
2017-04-11 22:14:25 +08:00
break
2018-03-12 21:16:22 +08:00
case 'sequence':
2017-04-11 22:14:25 +08:00
parser = sequenceParser
parser.parser.yy = sequenceDb
break
case 'gantt':
parser = ganttParser
parser.parser.yy = ganttDb
break
2018-03-12 21:16:22 +08:00
case 'class':
2017-04-11 22:14:25 +08:00
parser = classParser
parser.parser.yy = classDb
break
}
parser.parser.yy.parseError = (str, hash) => {
const error = { str, hash }
throw error
2017-09-10 00:29:22 +08:00
}
parser.parse(text)
2017-04-11 22:14:25 +08:00
}
2015-05-26 20:59:23 +02:00
2017-09-10 21:23:04 +08:00
export const encodeEntities = function (text) {
2017-09-14 19:40:38 +08:00
let txt = text
2017-04-11 22:14:25 +08:00
txt = txt.replace(/style.*:\S*#.*;/g, function (s) {
2017-09-14 19:40:38 +08:00
const innerTxt = s.substring(0, s.length - 1)
2017-04-11 22:14:25 +08:00
return innerTxt
})
txt = txt.replace(/classDef.*:\S*#.*;/g, function (s) {
2017-09-14 19:40:38 +08:00
const innerTxt = s.substring(0, s.length - 1)
2017-04-11 22:14:25 +08:00
return innerTxt
})
txt = txt.replace(/#\w+;/g, function (s) {
2017-09-14 19:40:38 +08:00
const innerTxt = s.substring(1, s.length - 1)
2017-04-11 22:14:25 +08:00
2017-09-14 19:40:38 +08:00
const isInt = /^\+?\d+$/.test(innerTxt)
2017-04-11 22:14:25 +08:00
if (isInt) {
return 'fl°°' + innerTxt + '¶ß'
} else {
return 'fl°' + innerTxt + '¶ß'
}
})
return txt
}
2017-09-10 21:23:04 +08:00
export const decodeEntities = function (text) {
2017-09-14 19:40:38 +08:00
let txt = text
2017-04-11 22:14:25 +08:00
txt = txt.replace(/fl°°/g, function () {
2017-04-11 22:14:25 +08:00
return '&#'
})
txt = txt.replace(/fl°/g, function () {
2017-04-11 22:14:25 +08:00
return '&'
})
txt = txt.replace(/¶ß/g, function () {
return ';'
})
return txt
}
/**
* ##render
* Function that renders an svg with a graph from a chart definition. Usage example below.
*
* ```
* mermaidAPI.initialize({
* startOnLoad:true
* });
* $(function(){
2017-09-14 19:40:38 +08:00
* 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.
*/
2017-09-14 19:40:38 +08:00
const render = function (id, txt, cb, container) {
2017-04-11 22:14:25 +08:00
if (typeof container !== 'undefined') {
container.innerHTML = ''
2017-04-11 22:14:25 +08:00
d3.select(container).append('div')
2017-04-16 23:24:47 +08:00
.attr('id', 'd' + id)
.append('svg')
.attr('id', id)
.attr('width', '100%')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.append('g')
2017-04-11 22:14:25 +08:00
} else {
const element = document.querySelector('#' + 'd' + id)
2017-04-11 22:14:25 +08:00
if (element) {
element.innerHTML = ''
2015-06-16 10:40:08 +02:00
}
2017-04-11 22:14:25 +08:00
d3.select('body').append('div')
2017-04-16 23:24:47 +08:00
.attr('id', 'd' + id)
.append('svg')
.attr('id', id)
.attr('width', '100%')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.append('g')
2017-04-11 22:14:25 +08:00
}
window.txt = txt
2017-09-10 21:23:04 +08:00
txt = encodeEntities(txt)
2017-04-11 22:14:25 +08:00
2017-09-14 19:40:38 +08:00
const element = d3.select('#d' + id).node()
const graphType = utils.detectType(txt)
2017-04-11 22:14:25 +08:00
switch (graphType) {
2018-03-12 21:16:22 +08:00
case 'git':
2017-04-11 22:14:25 +08:00
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute
2018-03-12 21:16:22 +08:00
gitGraphRenderer.setConf(config.git)
2017-04-11 22:14:25 +08:00
gitGraphRenderer.draw(txt, id, false)
break
2018-03-12 21:06:49 +08:00
case 'flowchart':
2017-04-11 22:14:25 +08:00
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute
flowRenderer.setConf(config.flowchart)
flowRenderer.draw(txt, id, false)
break
2018-03-12 21:16:22 +08:00
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)
}
2018-03-12 21:06:49 +08:00
sequenceRenderer.draw(txt, id)
2017-04-11 22:14:25 +08:00
break
case 'gantt':
config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute
2018-03-12 21:06:49 +08:00
ganttRenderer.setConf(config.gantt)
ganttRenderer.draw(txt, id)
2017-04-11 22:14:25 +08:00
break
2018-03-12 21:16:22 +08:00
case 'class':
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute
classRenderer.setConf(config.class)
2017-04-11 22:14:25 +08:00
classRenderer.draw(txt, id)
break
}
2017-09-12 22:06:19 +08:00
// insert inline style into svg
const svg = element.firstChild
const firstChild = svg.firstChild
2018-03-15 21:47:53 +08:00
// pre-defined theme
2018-03-15 22:44:00 +08:00
let style = themes[config.theme]
if (style === undefined) {
style = ''
}
2018-03-15 21:47:53 +08:00
// user provided theme CSS
if (config.themeCSS !== undefined) {
style += `\n${config.themeCSS}`
}
2018-03-14 21:36:10 +08:00
// classDef
if (graphType === 'flowchart') {
const classes = flowRenderer.getClasses(txt)
for (const className in classes) {
2018-03-14 21:38:29 +08:00
style += `\n.${className} > * { ${classes[className].styles.join(' !important; ')} !important; }`
2018-03-14 21:36:10 +08:00
}
}
const style1 = document.createElement('style')
2018-03-14 21:36:10 +08:00
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)
2017-09-12 22:06:19 +08:00
2018-03-17 09:10:10 +08:00
d3.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', 'http://www.w3.org/1999/xhtml')
2017-04-11 22:14:25 +08:00
2017-09-14 19:40:38 +08:00
let url = ''
2017-04-11 22:14:25 +08:00
if (config.arrowMarkerAbsolute) {
url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search
url = url.replace(/\(/g, '\\(')
url = url.replace(/\)/g, '\\)')
}
2015-05-26 20:59:23 +02:00
2017-04-16 23:24:47 +08:00
// Fix for when the base tag is used
2017-09-14 19:40:38 +08:00
let svgCode = d3.select('#d' + id).node().innerHTML.replace(/url\(#arrowhead/g, 'url(' + url + '#arrowhead', 'g')
2017-09-10 21:23:04 +08:00
svgCode = decodeEntities(svgCode)
2017-04-11 22:14:25 +08:00
if (typeof cb !== 'undefined') {
2018-03-12 20:52:06 +08:00
cb(svgCode, flowDb.bindFunctions)
2017-04-11 22:14:25 +08:00
} else {
2017-09-10 19:41:34 +08:00
logger.warn('CB = undefined!')
2017-04-11 22:14:25 +08:00
}
2015-06-16 10:40:08 +02:00
2017-09-14 19:40:38 +08:00
const node = d3.select('#d' + id).node()
2017-04-11 22:14:25 +08:00
if (node !== null && typeof node.remove === 'function') {
d3.select('#d' + id).node().remove()
}
2015-11-15 15:06:24 +01:00
2017-04-11 22:14:25 +08:00
return svgCode
}
2015-05-26 20:59:23 +02:00
2017-09-14 19:40:38 +08:00
const setConf = function (cnf) {
2017-04-16 23:24:47 +08:00
// Top level initially mermaid, gflow, sequenceDiagram and gantt
2017-09-14 19:40:38 +08:00
const lvl1Keys = Object.keys(cnf)
for (let i = 0; i < lvl1Keys.length; i++) {
2018-03-15 22:44:00 +08:00
if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) {
2017-09-14 19:40:38 +08:00
const lvl2Keys = Object.keys(cnf[lvl1Keys[i]])
2017-04-11 22:14:25 +08:00
2017-09-14 19:40:38 +08:00
for (let j = 0; j < lvl2Keys.length; j++) {
2017-09-10 19:41:34 +08:00
logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j])
2017-04-11 22:14:25 +08:00
if (typeof config[lvl1Keys[i]] === 'undefined') {
config[lvl1Keys[i]] = {}
}
2017-09-10 19:41:34 +08:00
logger.debug('Setting config: ' + lvl1Keys[i] + ' ' + lvl2Keys[j] + ' to ' + cnf[lvl1Keys[i]][lvl2Keys[j]])
2017-04-11 22:14:25 +08:00
config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]]
}
} else {
config[lvl1Keys[i]] = cnf[lvl1Keys[i]]
}
2017-04-11 22:14:25 +08:00
}
}
2016-10-30 16:13:06 +01:00
2017-09-10 10:45:38 +08:00
function initialize (options) {
2017-09-10 19:41:34 +08:00
logger.debug('Initializing mermaidAPI')
2017-04-16 23:24:47 +08:00
// Update default config with options supplied at initialization
2017-04-11 22:14:25 +08:00
if (typeof options === 'object') {
setConf(options)
}
2017-09-10 19:41:34 +08:00
setLogLevel(config.logLevel)
2017-04-11 22:14:25 +08:00
}
2017-09-10 10:45:38 +08:00
function getConfig () {
2017-04-11 22:14:25 +08:00
return config
}
2017-09-10 10:45:38 +08:00
const mermaidAPI = {
2018-03-11 22:23:26 +08:00
render,
2017-09-10 10:45:38 +08:00
parse,
initialize,
getConfig
2017-04-11 22:14:25 +08:00
}
2017-09-10 10:45:38 +08:00
export default mermaidAPI