From 6d74c5663f56d6b617ae38146da12f75c4c48552 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Fri, 21 Feb 2020 13:49:05 -0800 Subject: [PATCH 01/95] 1169- break out getRows Moved getRows function from `state/stateRenderer.js` and `state/shapes.js` into `common/common.js`. Broke out section into small one line functions for replacing line breaks, then moved the `sanitize` function from `utils.js` to this new module as there is shared functionality --- src/diagrams/class/classDb.js | 5 ++-- src/diagrams/common/common.js | 39 +++++++++++++++++++++++++++++ src/diagrams/flowchart/flowDb.js | 9 ++++--- src/diagrams/state/shapes.js | 9 ++----- src/diagrams/state/stateRenderer.js | 11 ++------ src/utils.js | 20 --------------- 6 files changed, 51 insertions(+), 42 deletions(-) create mode 100644 src/diagrams/common/common.js diff --git a/src/diagrams/class/classDb.js b/src/diagrams/class/classDb.js index dcb62164c..9bf00913a 100644 --- a/src/diagrams/class/classDb.js +++ b/src/diagrams/class/classDb.js @@ -1,6 +1,7 @@ import * as d3 from 'd3'; import { logger } from '../../logger'; import { getConfig } from '../../config'; +import common from '../common/common'; import utils from '../../utils'; const MERMAID_DOM_ID_PREFIX = 'classid-'; @@ -175,7 +176,7 @@ export const setLink = function(ids, linkStr, tooltip) { classes[id].link = utils.formatUrl(linkStr, config); if (tooltip) { - classes[id].tooltip = utils.sanitize(tooltip, config); + classes[id].tooltip = common.sanitizeText(tooltip, config); } } }); @@ -207,7 +208,7 @@ const setClickFunc = function(domId, functionName, tooltip) { } if (typeof classes[id] !== 'undefined') { if (tooltip) { - classes[id].tooltip = utils.sanitize(tooltip, config); + classes[id].tooltip = common.sanitizeText(tooltip, config); } funs.push(function() { diff --git a/src/diagrams/common/common.js b/src/diagrams/common/common.js new file mode 100644 index 000000000..cc91948fc --- /dev/null +++ b/src/diagrams/common/common.js @@ -0,0 +1,39 @@ +export const getRows = s => { + if (!s) return 1; + let str = breakToPlaceholder(s); + str = str.replace(/\\n/g, '#br#'); + return str.split('#br#'); +}; + +export const sanitizeText = (text, config) => { + let txt = text; + let htmlLabels = true; + if ( + config.flowchart && + (config.flowchart.htmlLabels === false || config.flowchart.htmlLabels === 'false') + ) + htmlLabels = false; + + if (config.securityLevel !== 'loose' && htmlLabels) { + // eslint-disable-line + txt = breakToPlaceholder(txt); + txt = txt.replace(//g, '>'); + txt = txt.replace(/=/g, '='); + txt = placeholderToBreak(txt); + } + + return txt; +}; + +const breakToPlaceholder = s => { + return s.replace(//gi, '#br#'); +}; + +const placeholderToBreak = s => { + return s.replace(/#br#/g, '
'); +}; + +export default { + getRows, + sanitizeText +}; diff --git a/src/diagrams/flowchart/flowDb.js b/src/diagrams/flowchart/flowDb.js index 404fdfceb..4917a54a7 100644 --- a/src/diagrams/flowchart/flowDb.js +++ b/src/diagrams/flowchart/flowDb.js @@ -2,6 +2,7 @@ import * as d3 from 'd3'; import { logger } from '../../logger'; import utils from '../../utils'; import { getConfig } from '../../config'; +import common from '../common/common'; // const MERMAID_DOM_ID_PREFIX = 'mermaid-dom-id-'; const MERMAID_DOM_ID_PREFIX = ''; @@ -43,7 +44,7 @@ export const addVertex = function(_id, text, type, style, classes) { vertices[id] = { id: id, styles: [], classes: [] }; } if (typeof text !== 'undefined') { - txt = utils.sanitize(text.trim(), config); + txt = common.sanitizeText(text.trim(), config); // strip quotes if string starts and ends with a quote if (txt[0] === '"' && txt[txt.length - 1] === '"') { @@ -93,7 +94,7 @@ export const addSingleLink = function(_start, _end, type, linktext) { linktext = type.text; if (typeof linktext !== 'undefined') { - edge.text = utils.sanitize(linktext.trim(), config); + edge.text = common.sanitizeText(linktext.trim(), config); // strip quotes if string starts and exnds with a quote if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') { @@ -210,7 +211,7 @@ export const setClass = function(ids, className) { const setTooltip = function(ids, tooltip) { ids.split(',').forEach(function(id) { if (typeof tooltip !== 'undefined') { - tooltips[id] = utils.sanitize(tooltip, config); + tooltips[id] = common.sanitizeText(tooltip, config); } }); }; @@ -410,7 +411,7 @@ export const addSubGraph = function(_id, list, _title) { id = id || 'subGraph' + subCount; if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; title = title || ''; - title = utils.sanitize(title, config); + title = common.sanitizeText(title, config); subCount = subCount + 1; const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] }; subGraphs.push(subGraph); diff --git a/src/diagrams/state/shapes.js b/src/diagrams/state/shapes.js index d42228e41..dcd5cf21f 100644 --- a/src/diagrams/state/shapes.js +++ b/src/diagrams/state/shapes.js @@ -2,6 +2,7 @@ import * as d3 from 'd3'; import idCache from './id-cache.js'; import stateDb from './stateDb'; import utils from '../../utils'; +import common from '../common/common'; import { getConfig } from '../../config'; // let conf; @@ -391,12 +392,6 @@ export const drawState = function(elem, stateDef) { return stateInfo; }; -const getRows = s => { - let str = s.replace(//gi, '#br#'); - str = str.replace(/\\n/g, '#br#'); - return str.split('#br#'); -}; - let edgeCount = 0; export const drawEdge = function(elem, path, relation) { const getRelationType = function(type) { @@ -455,7 +450,7 @@ export const drawEdge = function(elem, path, relation) { const { x, y } = utils.calcLabelPosition(path.points); - const rows = getRows(relation.title); + const rows = common.getRows(relation.title); // console.warn(rows); diff --git a/src/diagrams/state/stateRenderer.js b/src/diagrams/state/stateRenderer.js index 396bb976d..e11fbba79 100644 --- a/src/diagrams/state/stateRenderer.js +++ b/src/diagrams/state/stateRenderer.js @@ -3,6 +3,7 @@ import dagre from 'dagre'; import graphlib from 'graphlib'; import { logger } from '../../logger'; import stateDb from './stateDb'; +import common from '../common/common'; import { parser } from './parser/stateDiagram'; // import idCache from './id-cache'; import { drawState, addTitleAndBox, drawEdge } from './shapes'; @@ -99,14 +100,6 @@ const getLabelWidth = text => { return text ? text.length * conf.fontSizeFactor : 1; }; -/* TODO: REMOVE DUPLICATION, SEE SHAPES */ -const getRows = s => { - if (!s) return 1; - let str = s.replace(//gi, '#br#'); - str = str.replace(/\\n/g, '#br#'); - return str.split('#br#'); -}; - const renderDoc = (doc, diagram, parentId, altBkg) => { // // Layout graph, Create a new directed graph const graph = new graphlib.Graph({ @@ -239,7 +232,7 @@ const renderDoc = (doc, diagram, parentId, altBkg) => { { relation: relation, width: getLabelWidth(relation.title), - height: conf.labelHeight * getRows(relation.title).length, + height: conf.labelHeight * common.getRows(relation.title).length, labelpos: 'c' }, 'id' + cnt diff --git a/src/utils.js b/src/utils.js index 002dcc8b7..1aec62d4f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -74,25 +74,6 @@ export const interpolateToCurve = (interpolate, defaultCurve) => { return d3[curveName] || defaultCurve; }; -export const sanitize = (text, config) => { - let txt = text; - let htmlLabels = true; - if ( - config.flowchart && - (config.flowchart.htmlLabels === false || config.flowchart.htmlLabels === 'false') - ) - htmlLabels = false; - - if (config.securityLevel !== 'loose' && htmlLabels) { // eslint-disable-line - txt = txt.replace(//gi, '#br#'); - txt = txt.replace(//g, '>'); - txt = txt.replace(/=/g, '='); - txt = txt.replace(/#br#/g, '
'); - } - - return txt; -}; - export const formatUrl = (linkStr, config) => { let url = linkStr.trim(); @@ -225,7 +206,6 @@ export default { interpolateToCurve, calcLabelPosition, calcCardinalityPosition, - sanitize, formatUrl, getStylesFromArray }; From cd8b7e5528536a0773428fa1b62469e2668f1501 Mon Sep 17 00:00:00 2001 From: NeilCuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Fri, 21 Feb 2020 21:11:21 -0800 Subject: [PATCH 02/95] Update README.md Hi, I love your program and how it reimagines the whole dynamic of creating diagrams and graphs, I also like the wit of the documentation, but I had some small trouble in initially figuring out what the application was about. I propose a few line changes to clarify the purpose of the application and an additional a link to the syntax section of docs to hopefully decrease the friction and increase the interest in using the product. I changed: -"__mermaid is a Javascript based diagramming and charting tool. It generates diagrams flowcharts and more, using markdown-inspired text for ease and speed.__" and I added: -"__The following are some examples of the diagrams, charts and graphs that can be made using mermaid and the Markdown-inspired text specific to it. Click here jump into the [text syntax](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference).__" More power to you and your team. -Neil --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d09567c52..5bb69e6b3 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,14 @@ # mermaid [![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/Mermaid/mermaid) -__Generate diagrams, charts, graphs or flows from markdown-like text via javascript.__ -See our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Play in our [live editor](https://mermaidjs.github.io/mermaid-live-editor/) or jump straight to the [installation and usage](http://mermaid-js.github.io/mermaid/#/usage). +__mermaid is a Javascript based diagramming and charting tool. It generates diagrams flowcharts and more, using markdown-inspired text for ease and speed.__ + +For more information and help in gettin started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/) or jump straight to the [installation and usage](http://mermaid-js.github.io/mermaid/#/usage). :trophy: _"The most exciting use of technology"_ - [JS Open Source Awards (2019)](https://osawards.com/javascript/#nominees) +__The following are some examples of the diagrams, charts and graphs that can be made using mermaid and the Markdown-inspired text specific to it. Click here jump into the [text syntax](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference).__
From 17f99aeed82c8fce6a3dc680148f43d867ed25b3 Mon Sep 17 00:00:00 2001 From: NeilCuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Sat, 22 Feb 2020 19:21:37 -0800 Subject: [PATCH 03/95] Updated n00b-gettingStarted.md this section had some outdated info regarding the live editor and its capabilities. -replaced and outdated screenshot -edited the documentation to make it a little more n00b friendly. -added a link to the Atom mermaid plugin -Neil --- docs/n00b-gettingStarted.md | 70 +++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/docs/n00b-gettingStarted.md b/docs/n00b-gettingStarted.md index 96cd576c9..6619ebacf 100644 --- a/docs/n00b-gettingStarted.md +++ b/docs/n00b-gettingStarted.md @@ -1,42 +1,45 @@ -# A more basic getting started +# A basic Walkthrough for beginners -Writing mermaid code is simple. +Creating diagrams and charts, using mermaid code is simple. -But how is the code turned into a diagram in a web page? To do this we need a mermaid renderer. +But how is the code turned into a diagram in a web page? This is done with the use of a mermaid renderer. -Thankfully the mermaid renderer is very accessible, in essence it is a javascript. +Thankfully the mermaid renderer is very accessible, in essence it is a piece of javascript that can be called. -The requirement is on the part of the web browser. Modern web browsers, such as Firefox, Chrome and Safari, can render mermaid. But Internet Explorer cannot. The web browser also needs access to the online mermaid renderer which it downloads from https://cdn.jsdelivr.net/npm/mermaid +Most widely used web browsers, such as Firefox, Chrome and Safari, can render mermaid, Internet Explorer however cannot. The web browser also needs access to the online mermaid renderer which it downloads from https://cdn.jsdelivr.net/npm/mermaid -For an easy introduction, here follows three practical examples using: -1. an online mermaid editor -2. a mermaid plugin -3. a generic web server of your choosing +# For beginners, there are three relatively easy ways you can use mermaid: +1. Using the mermaid [live editor](https://mermaid-js.github.io/mermaid-live-editor/) +2. Using a mermaid plugin, such as that for Confluence or [Atom](https://atom.io/packages/atom-mermaid). +3. Calling mermaid with HTML, deployed in a friendly browser. -Following either of these examples, you can get started with converting your own mermaid code into web diagrams. +# Following either of these examples, you can get started with creating your own diagrams using mermaid code. -## the mermaid live editor +## 1. The mermaid live editor The quickest way to get started with mermaid is to visit [The mermaid live editor](https://mermaidjs.github.io/mermaid-live-editor). -In the `Code` section one can write or edit raw mermaid code, and instantly `Preview` the rendered result. +In the `Code` section one can write or edit raw mermaid code, and instantly `Preview` the rendered result on the panel beside it. This is a great way to get started. -It is also the easiest way to develop diagrams, the code of which can be pasted straight into documentation. +It is also the easiest way to develop diagrams. You can also click "Copy Markdown" to copy the markdown code for the diagram, that can then be pasted directly into your documentation. +You can also copy the code from the code section and paste it into either a mermaid plugin or in inside an html file, which will be taught in numbers 2 and 3. -![Flowchart](./img/n00b-liveEditor.png) +![Flowchart](./img/liveEditor-options.png.png) The `Mermaid configuration` is for controlling mermaid behaviour. An easy introduction to mermaid configuration is found in the [Advanced usage](n00b-advanced.md) section. A complete configuration reference cataloguing default values is found on the [mermaidAPI](mermaidAPI.md) page. -## mermaid using plugins +## 2. Using mermaid plugins Thanks to the growing popularity of mermaid, many plugins already exist which incorporate a mermaid renderer. +One example is the [Atom plugin](https://atom.io/packages/atom-mermaid) for mermaid. -One example is the [Atlassian Confluence mermaid plugin](https://marketplace.atlassian.com/apps/1214124/mermaid-plugin-for-confluence?hosting=server&tab=overview) +Another example is the [Atlassian Confluence mermaid plugin](https://marketplace.atlassian.com/apps/1214124/mermaid-plugin-for-confluence?hosting=server&tab=overview) When the mermaid plugin is installed on a Confluence server, one can insert a mermaid object into any Confluence page. +# Here is a step by step process for using the mermaid-Confluence plugin: --- @@ -64,24 +67,30 @@ When the mermaid plugin is installed on a Confluence server, one can insert a me --- -## mermaid using any web server (or just a browser) +## 3. mermaid using any web server (or just a browser) -This example can be used with any common web server. Apache, IIS, nginx, node express [...], you pick your favourite. +This method can be used with any common web server. Apache, IIS, nginx, node express [...], you pick your favourite. + +We do not need to install anything on the server, apart from a program (like Notepad++) that can generate an html file, which is then deployed by a web browser (such as Firefox, Chrome, Safari, but not Internet Explorer). + +So if you want to really simplify things when testing this out, don't use a web server at all but just create the file locally and drag it into your browser window. It is the browser which does all the work of rendering mermaid! + +# Here are instructions for creating an html file with mermaid code: +# Note that all this is written in the html `` section of the web page. + +When writing the html file, we give the web browser three instructions inside the html code: +a. A reference for fetching the online mermaid renderer, which is written in Javascript. +b. The mermaid code for the diagram we want to create. +c. The `mermaid.initialize()` command to start the rendering process. -We do not need to install anything on the server, apart from a normal file of html to be reached by a web browser (such as Firefox, Chrome, Safari, but not Internet Explorer). So if you want to really simplify things when testing this out, don't use a web server at all but just create the file locally and drag it into your browser window. It is the browser which does all the work of rendering mermaid! -Through the html file, we give the web browser three instructions inside the html code it retrieves: -1. a reference for fetching the online mermaid renderer, the renderer is just a javascript. -2. the mermaid code we want to diagram. -3. the `mermaid.initialize()` command to start the rendering process -All this is done in the html `` section of the web page. This is what needs to go into the html file: -1. The reference to the mermaid renderer is done in a ` diff --git a/src/diagrams/flowchart-v2/flowChartShapes.js b/src/diagrams/flowchart-v2/flowChartShapes.js new file mode 100644 index 000000000..23cb53049 --- /dev/null +++ b/src/diagrams/flowchart-v2/flowChartShapes.js @@ -0,0 +1,261 @@ +import dagreD3 from 'dagre-d3'; + +function question(parent, bbox, node) { + const w = bbox.width; + const h = bbox.height; + const s = (w + h) * 0.9; + const points = [ + { x: s / 2, y: 0 }, + { x: s, y: -s / 2 }, + { x: s / 2, y: -s }, + { x: 0, y: -s / 2 } + ]; + const shapeSvg = insertPolygonShape(parent, s, s, points); + node.intersect = function(point) { + return dagreD3.intersect.polygon(node, points, point); + }; + return shapeSvg; +} + +function hexagon(parent, bbox, node) { + const f = 4; + const h = bbox.height; + const m = h / f; + const w = bbox.width + 2 * m; + const points = [ + { x: m, y: 0 }, + { x: w - m, y: 0 }, + { x: w, y: -h / 2 }, + { x: w - m, y: -h }, + { x: m, y: -h }, + { x: 0, y: -h / 2 } + ]; + const shapeSvg = insertPolygonShape(parent, w, h, points); + node.intersect = function(point) { + return dagreD3.intersect.polygon(node, points, point); + }; + return shapeSvg; +} + +function rect_left_inv_arrow(parent, bbox, node) { + const w = bbox.width; + const h = bbox.height; + const points = [ + { x: -h / 2, y: 0 }, + { x: w, y: 0 }, + { x: w, y: -h }, + { x: -h / 2, y: -h }, + { x: 0, y: -h / 2 } + ]; + const shapeSvg = insertPolygonShape(parent, w, h, points); + node.intersect = function(point) { + return dagreD3.intersect.polygon(node, points, point); + }; + return shapeSvg; +} + +function lean_right(parent, bbox, node) { + const w = bbox.width; + const h = bbox.height; + const points = [ + { x: (-2 * h) / 6, y: 0 }, + { x: w - h / 6, y: 0 }, + { x: w + (2 * h) / 6, y: -h }, + { x: h / 6, y: -h } + ]; + const shapeSvg = insertPolygonShape(parent, w, h, points); + node.intersect = function(point) { + return dagreD3.intersect.polygon(node, points, point); + }; + return shapeSvg; +} + +function lean_left(parent, bbox, node) { + const w = bbox.width; + const h = bbox.height; + const points = [ + { x: (2 * h) / 6, y: 0 }, + { x: w + h / 6, y: 0 }, + { x: w - (2 * h) / 6, y: -h }, + { x: -h / 6, y: -h } + ]; + const shapeSvg = insertPolygonShape(parent, w, h, points); + node.intersect = function(point) { + return dagreD3.intersect.polygon(node, points, point); + }; + return shapeSvg; +} + +function trapezoid(parent, bbox, node) { + const w = bbox.width; + const h = bbox.height; + const points = [ + { x: (-2 * h) / 6, y: 0 }, + { x: w + (2 * h) / 6, y: 0 }, + { x: w - h / 6, y: -h }, + { x: h / 6, y: -h } + ]; + const shapeSvg = insertPolygonShape(parent, w, h, points); + node.intersect = function(point) { + return dagreD3.intersect.polygon(node, points, point); + }; + return shapeSvg; +} + +function inv_trapezoid(parent, bbox, node) { + const w = bbox.width; + const h = bbox.height; + const points = [ + { x: h / 6, y: 0 }, + { x: w - h / 6, y: 0 }, + { x: w + (2 * h) / 6, y: -h }, + { x: (-2 * h) / 6, y: -h } + ]; + const shapeSvg = insertPolygonShape(parent, w, h, points); + node.intersect = function(point) { + return dagreD3.intersect.polygon(node, points, point); + }; + return shapeSvg; +} + +function rect_right_inv_arrow(parent, bbox, node) { + const w = bbox.width; + const h = bbox.height; + const points = [ + { x: 0, y: 0 }, + { x: w + h / 2, y: 0 }, + { x: w, y: -h / 2 }, + { x: w + h / 2, y: -h }, + { x: 0, y: -h } + ]; + const shapeSvg = insertPolygonShape(parent, w, h, points); + node.intersect = function(point) { + return dagreD3.intersect.polygon(node, points, point); + }; + return shapeSvg; +} + +function stadium(parent, bbox, node) { + const h = bbox.height; + const w = bbox.width + h / 4; + + const shapeSvg = parent + .insert('rect', ':first-child') + .attr('rx', h / 2) + .attr('ry', h / 2) + .attr('x', -w / 2) + .attr('y', -h / 2) + .attr('width', w) + .attr('height', h); + + node.intersect = function(point) { + return dagreD3.intersect.rect(node, point); + }; + return shapeSvg; +} + +function cylinder(parent, bbox, node) { + const w = bbox.width; + const rx = w / 2; + const ry = rx / (2.5 + w / 50); + const h = bbox.height + ry; + + const shape = + 'M 0,' + + ry + + ' a ' + + rx + + ',' + + ry + + ' 0,0,0 ' + + w + + ' 0 a ' + + rx + + ',' + + ry + + ' 0,0,0 ' + + -w + + ' 0 l 0,' + + h + + ' a ' + + rx + + ',' + + ry + + ' 0,0,0 ' + + w + + ' 0 l 0,' + + -h; + + const shapeSvg = parent + .attr('label-offset-y', ry) + .insert('path', ':first-child') + .attr('d', shape) + .attr('transform', 'translate(' + -w / 2 + ',' + -(h / 2 + ry) + ')'); + + node.intersect = function(point) { + const pos = dagreD3.intersect.rect(node, point); + const x = pos.x - node.x; + + if ( + rx != 0 && + (Math.abs(x) < node.width / 2 || + (Math.abs(x) == node.width / 2 && Math.abs(pos.y - node.y) > node.height / 2 - ry)) + ) { + // ellipsis equation: x*x / a*a + y*y / b*b = 1 + // solve for y to get adjustion value for pos.y + let y = ry * ry * (1 - (x * x) / (rx * rx)); + if (y != 0) y = Math.sqrt(y); + y = ry - y; + if (point.y - node.y > 0) y = -y; + + pos.y += y; + } + + return pos; + }; + + return shapeSvg; +} + +export function addToRender(render) { + render.shapes().question = question; + render.shapes().hexagon = hexagon; + render.shapes().stadium = stadium; + render.shapes().cylinder = cylinder; + + // Add custom shape for box with inverted arrow on left side + render.shapes().rect_left_inv_arrow = rect_left_inv_arrow; + + // Add custom shape for box with inverted arrow on left side + render.shapes().lean_right = lean_right; + + // Add custom shape for box with inverted arrow on left side + render.shapes().lean_left = lean_left; + + // Add custom shape for box with inverted arrow on left side + render.shapes().trapezoid = trapezoid; + + // Add custom shape for box with inverted arrow on left side + render.shapes().inv_trapezoid = inv_trapezoid; + + // Add custom shape for box with inverted arrow on right side + render.shapes().rect_right_inv_arrow = rect_right_inv_arrow; +} + +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('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')'); +} + +export default { + addToRender +}; diff --git a/src/diagrams/flowchart-v2/flowChartShapes.spec.js b/src/diagrams/flowchart-v2/flowChartShapes.spec.js new file mode 100644 index 000000000..61e876d4b --- /dev/null +++ b/src/diagrams/flowchart-v2/flowChartShapes.spec.js @@ -0,0 +1,131 @@ +import { addToRender } from './flowChartShapes'; + +describe('flowchart shapes', function() { + // rect-based shapes + [ + ['stadium', useWidth, useHeight] + ].forEach(function([shapeType, getW, getH]) { + it(`should add a ${shapeType} shape that renders a properly positioned rect element`, function() { + const mockRender = MockRender(); + const mockSvg = MockSvg(); + addToRender(mockRender); + + [[100, 100], [123, 45], [71, 300]].forEach(function([width, height]) { + const shape = mockRender.shapes()[shapeType](mockSvg, { width, height }, {}); + const w = width + height / 4; + const h = height; + const dx = -getW(w, h) / 2; + const dy = -getH(w, h) / 2; + expect(shape.__tag).toEqual('rect'); + expect(shape.__attrs).toHaveProperty('x', dx); + expect(shape.__attrs).toHaveProperty('y', dy); + }); + }); + }); + + // path-based shapes + [ + ['cylinder', useWidth, useHeight] + ].forEach(function([shapeType, getW, getH]) { + it(`should add a ${shapeType} shape that renders a properly positioned path element`, function() { + const mockRender = MockRender(); + const mockSvg = MockSvg(); + addToRender(mockRender); + + [[100, 100], [123, 45], [71, 300]].forEach(function([width, height]) { + const shape = mockRender.shapes()[shapeType](mockSvg, { width, height }, {}); + expect(shape.__tag).toEqual('path'); + expect(shape.__attrs).toHaveProperty('d'); + }); + }); + }); + + // polygon-based shapes + [ + [ + 'question', + 4, + function(w, h) { + return (w + h) * 0.9; + }, + function(w, h) { + return (w + h) * 0.9; + } + ], + [ + 'hexagon', + 6, + function(w, h) { + return w + h / 2; + }, + useHeight + ], + ['rect_left_inv_arrow', 5, useWidth, useHeight], + ['rect_right_inv_arrow', 5, useWidth, useHeight], + ['lean_right', 4, useWidth, useHeight], + ['lean_left', 4, useWidth, useHeight], + ['trapezoid', 4, useWidth, useHeight], + ['inv_trapezoid', 4, useWidth, useHeight] + ].forEach(function([shapeType, expectedPointCount, getW, getH]) { + it(`should add a ${shapeType} shape that renders a properly translated polygon element`, function() { + const mockRender = MockRender(); + const mockSvg = MockSvg(); + addToRender(mockRender); + + [[100, 100], [123, 45], [71, 300]].forEach(function([width, height]) { + const shape = mockRender.shapes()[shapeType](mockSvg, { width, height }, {}); + const dx = -getW(width, height) / 2; + const dy = getH(width, height) / 2; + const points = shape.__attrs.points.split(' '); + expect(shape.__tag).toEqual('polygon'); + expect(shape.__attrs).toHaveProperty('transform', `translate(${dx},${dy})`); + expect(points).toHaveLength(expectedPointCount); + }); + }); + }); +}); + +function MockRender() { + const shapes = {}; + return { + shapes() { + return shapes; + } + }; +} + +function MockSvg(tag, ...args) { + const children = []; + const attributes = {}; + return { + get __args() { + return args; + }, + get __tag() { + return tag; + }, + get __children() { + return children; + }, + get __attrs() { + return attributes; + }, + insert: function(tag, ...args) { + const child = MockSvg(tag, ...args); + children.push(child); + return child; + }, + attr(name, value) { + this.__attrs[name] = value; + return this; + } + }; +} + +function useWidth(w, h) { + return w; +} + +function useHeight(w, h) { + return h; +} diff --git a/src/diagrams/flowchart-v2/flowDb.js b/src/diagrams/flowchart-v2/flowDb.js new file mode 100644 index 000000000..4917a54a7 --- /dev/null +++ b/src/diagrams/flowchart-v2/flowDb.js @@ -0,0 +1,644 @@ +import * as d3 from 'd3'; +import { logger } from '../../logger'; +import utils from '../../utils'; +import { getConfig } from '../../config'; +import common from '../common/common'; + +// const MERMAID_DOM_ID_PREFIX = 'mermaid-dom-id-'; +const MERMAID_DOM_ID_PREFIX = ''; + +const config = getConfig(); +let vertices = {}; +let edges = []; +let classes = []; +let subGraphs = []; +let subGraphLookup = {}; +let tooltips = {}; +let subCount = 0; +let firstGraphFlag = true; +let direction; +// Functions to be run after graph rendering +let funs = []; + +/** + * Function called by parser when a node definition has been found + * @param id + * @param text + * @param type + * @param style + * @param classes + */ +export const addVertex = function(_id, text, type, style, classes) { + let txt; + let id = _id; + if (typeof id === 'undefined') { + return; + } + if (id.trim().length === 0) { + return; + } + + if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; + + if (typeof vertices[id] === 'undefined') { + vertices[id] = { id: id, styles: [], classes: [] }; + } + if (typeof text !== 'undefined') { + txt = common.sanitizeText(text.trim(), config); + + // strip quotes if string starts and ends with a quote + if (txt[0] === '"' && txt[txt.length - 1] === '"') { + txt = txt.substring(1, txt.length - 1); + } + + vertices[id].text = txt; + } else { + if (typeof vertices[id].text === 'undefined') { + vertices[id].text = _id; + } + } + if (typeof type !== 'undefined') { + vertices[id].type = type; + } + if (typeof style !== 'undefined') { + if (style !== null) { + style.forEach(function(s) { + vertices[id].styles.push(s); + }); + } + } + if (typeof classes !== 'undefined') { + if (classes !== null) { + classes.forEach(function(s) { + vertices[id].classes.push(s); + }); + } + } +}; + +/** + * Function called by parser when a link/edge definition has been found + * @param start + * @param end + * @param type + * @param linktext + */ +export const addSingleLink = function(_start, _end, type, linktext) { + let start = _start; + let end = _end; + if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start; + if (end[0].match(/\d/)) end = MERMAID_DOM_ID_PREFIX + end; + logger.info('Got edge...', start, end); + + const edge = { start: start, end: end, type: undefined, text: '' }; + linktext = type.text; + + if (typeof linktext !== 'undefined') { + edge.text = common.sanitizeText(linktext.trim(), config); + + // strip quotes if string starts and exnds with a quote + if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') { + edge.text = edge.text.substring(1, edge.text.length - 1); + } + } + + if (typeof type !== 'undefined') { + edge.type = type.type; + edge.stroke = type.stroke; + } + edges.push(edge); +}; +export const addLink = function(_start, _end, type, linktext) { + let i, j; + for (i = 0; i < _start.length; i++) { + for (j = 0; j < _end.length; j++) { + addSingleLink(_start[i], _end[j], type, linktext); + } + } +}; + +/** + * Updates a link's line interpolation algorithm + * @param pos + * @param interpolate + */ +export const updateLinkInterpolate = function(positions, interp) { + positions.forEach(function(pos) { + if (pos === 'default') { + edges.defaultInterpolate = interp; + } else { + edges[pos].interpolate = interp; + } + }); +}; + +/** + * Updates a link with a style + * @param pos + * @param style + */ +export const updateLink = function(positions, style) { + positions.forEach(function(pos) { + if (pos === 'default') { + edges.defaultStyle = style; + } else { + if (utils.isSubstringInArray('fill', style) === -1) { + style.push('fill:none'); + } + edges[pos].style = style; + } + }); +}; + +export const addClass = function(id, style) { + if (typeof classes[id] === 'undefined') { + classes[id] = { id: id, styles: [], textStyles: [] }; + } + + if (typeof style !== 'undefined') { + if (style !== null) { + style.forEach(function(s) { + if (s.match('color')) { + const newStyle1 = s.replace('fill', 'bgFill'); + const newStyle2 = newStyle1.replace('color', 'fill'); + classes[id].textStyles.push(newStyle2); + } + classes[id].styles.push(s); + }); + } + } +}; + +/** + * Called by parser when a graph definition is found, stores the direction of the chart. + * @param dir + */ +export const setDirection = function(dir) { + direction = dir; + if (direction.match(/.*/)) { + direction = 'LR'; + } + if (direction.match(/.*v/)) { + direction = 'TB'; + } +}; + +/** + * Called by parser when a special node is found, e.g. a clickable element. + * @param ids Comma separated list of ids + * @param className Class to add + */ +export const setClass = function(ids, className) { + ids.split(',').forEach(function(_id) { + let id = _id; + if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; + if (typeof vertices[id] !== 'undefined') { + vertices[id].classes.push(className); + } + + if (typeof subGraphLookup[id] !== 'undefined') { + subGraphLookup[id].classes.push(className); + } + }); +}; + +const setTooltip = function(ids, tooltip) { + ids.split(',').forEach(function(id) { + if (typeof tooltip !== 'undefined') { + tooltips[id] = common.sanitizeText(tooltip, config); + } + }); +}; + +const setClickFun = function(_id, functionName) { + let id = _id; + if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; + if (config.securityLevel !== 'loose') { + return; + } + if (typeof functionName === 'undefined') { + return; + } + if (typeof vertices[id] !== 'undefined') { + funs.push(function() { + const elem = document.querySelector(`[id="${id}"]`); + if (elem !== null) { + elem.addEventListener( + 'click', + function() { + window[functionName](id); + }, + false + ); + } + }); + } +}; + +/** + * Called by parser when a link is found. Adds the URL to the vertex data. + * @param ids Comma separated list of ids + * @param linkStr URL to create a link for + * @param tooltip Tooltip for the clickable element + */ +export const setLink = function(ids, linkStr, tooltip) { + ids.split(',').forEach(function(_id) { + let id = _id; + if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; + if (typeof vertices[id] !== 'undefined') { + vertices[id].link = utils.formatUrl(linkStr, config); + } + }); + setTooltip(ids, tooltip); + setClass(ids, 'clickable'); +}; +export const getTooltip = function(id) { + return tooltips[id]; +}; + +/** + * Called by parser when a click definition is found. Registers an event handler. + * @param ids Comma separated list of ids + * @param functionName Function to be called on click + * @param tooltip Tooltip for the clickable element + */ +export const setClickEvent = function(ids, functionName, tooltip) { + ids.split(',').forEach(function(id) { + setClickFun(id, functionName); + }); + setTooltip(ids, tooltip); + setClass(ids, 'clickable'); +}; + +export const bindFunctions = function(element) { + funs.forEach(function(fun) { + fun(element); + }); +}; +export const getDirection = function() { + return direction.trim(); +}; +/** + * Retrieval function for fetching the found nodes after parsing has completed. + * @returns {{}|*|vertices} + */ +export const getVertices = function() { + return vertices; +}; + +/** + * Retrieval function for fetching the found links after parsing has completed. + * @returns {{}|*|edges} + */ +export const getEdges = function() { + return edges; +}; + +/** + * Retrieval function for fetching the found class definitions after parsing has completed. + * @returns {{}|*|classes} + */ +export const getClasses = function() { + return classes; +}; + +const setupToolTips = function(element) { + let tooltipElem = d3.select('.mermaidTooltip'); + if ((tooltipElem._groups || tooltipElem)[0][0] === null) { + tooltipElem = d3 + .select('body') + .append('div') + .attr('class', 'mermaidTooltip') + .style('opacity', 0); + } + + const svg = d3.select(element).select('svg'); + + const nodes = svg.selectAll('g.node'); + nodes + .on('mouseover', function() { + const el = d3.select(this); + const title = el.attr('title'); + // Dont try to draw a tooltip if no data is provided + if (title === null) { + return; + } + const rect = this.getBoundingClientRect(); + + tooltipElem + .transition() + .duration(200) + .style('opacity', '.9'); + tooltipElem + .html(el.attr('title')) + .style('left', rect.left + (rect.right - rect.left) / 2 + 'px') + .style('top', rect.top - 14 + document.body.scrollTop + 'px'); + el.classed('hover', true); + }) + .on('mouseout', function() { + tooltipElem + .transition() + .duration(500) + .style('opacity', 0); + const el = d3.select(this); + el.classed('hover', false); + }); +}; +funs.push(setupToolTips); + +/** + * Clears the internal graph db so that a new graph can be parsed. + */ +export const clear = function() { + vertices = {}; + classes = {}; + edges = []; + funs = []; + funs.push(setupToolTips); + subGraphs = []; + subGraphLookup = {}; + subCount = 0; + tooltips = []; + firstGraphFlag = true; +}; +/** + * + * @returns {string} + */ +export const defaultStyle = function() { + return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;'; +}; + +/** + * Clears the internal graph db so that a new graph can be parsed. + */ +export const addSubGraph = function(_id, list, _title) { + let id = _id.trim(); + let title = _title; + if (_id === _title && _title.match(/\s/)) { + id = undefined; + } + function uniq(a) { + const prims = { boolean: {}, number: {}, string: {} }; + const objs = []; + + return a.filter(function(item) { + const type = typeof item; + if (item.trim() === '') { + return false; + } + if (type in prims) { + return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true); // eslint-disable-line + } else { + return objs.indexOf(item) >= 0 ? false : objs.push(item); + } + }); + } + + let nodeList = []; + + nodeList = uniq(nodeList.concat.apply(nodeList, list)); + for (let i = 0; i < nodeList.length; i++) { + if (nodeList[i][0].match(/\d/)) nodeList[i] = MERMAID_DOM_ID_PREFIX + nodeList[i]; + } + + id = id || 'subGraph' + subCount; + if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; + title = title || ''; + title = common.sanitizeText(title, config); + subCount = subCount + 1; + const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] }; + subGraphs.push(subGraph); + subGraphLookup[id] = subGraph; + return id; +}; + +const getPosForId = function(id) { + for (let i = 0; i < subGraphs.length; i++) { + if (subGraphs[i].id === id) { + return i; + } + } + return -1; +}; +let secCount = -1; +const posCrossRef = []; +const indexNodes2 = function(id, pos) { + const nodes = subGraphs[pos].nodes; + secCount = secCount + 1; + if (secCount > 2000) { + return; + } + posCrossRef[secCount] = pos; + // Check if match + if (subGraphs[pos].id === id) { + return { + result: true, + count: 0 + }; + } + + let count = 0; + let posCount = 1; + while (count < nodes.length) { + const childPos = getPosForId(nodes[count]); + // Ignore regular nodes (pos will be -1) + if (childPos >= 0) { + const res = indexNodes2(id, childPos); + if (res.result) { + return { + result: true, + count: posCount + res.count + }; + } else { + posCount = posCount + res.count; + } + } + count = count + 1; + } + + return { + result: false, + count: posCount + }; +}; + +export const getDepthFirstPos = function(pos) { + return posCrossRef[pos]; +}; +export const indexNodes = function() { + secCount = -1; + if (subGraphs.length > 0) { + indexNodes2('none', subGraphs.length - 1, 0); + } +}; + +export const getSubGraphs = function() { + return subGraphs; +}; + +export const firstGraph = () => { + if (firstGraphFlag) { + firstGraphFlag = false; + return true; + } + return false; +}; + +const destructStartLink = _str => { + const str = _str.trim(); + + switch (str) { + case '<--': + return { type: 'arrow', stroke: 'normal' }; + case 'x--': + return { type: 'arrow_cross', stroke: 'normal' }; + case 'o--': + return { type: 'arrow_circle', stroke: 'normal' }; + case '<-.': + return { type: 'arrow', stroke: 'dotted' }; + case 'x-.': + return { type: 'arrow_cross', stroke: 'dotted' }; + case 'o-.': + return { type: 'arrow_circle', stroke: 'dotted' }; + case '<==': + return { type: 'arrow', stroke: 'thick' }; + case 'x==': + return { type: 'arrow_cross', stroke: 'thick' }; + case 'o==': + return { type: 'arrow_circle', stroke: 'thick' }; + case '--': + return { type: 'arrow_open', stroke: 'normal' }; + case '==': + return { type: 'arrow_open', stroke: 'thick' }; + case '-.': + return { type: 'arrow_open', stroke: 'dotted' }; + } +}; + +const destructEndLink = _str => { + const str = _str.trim(); + + switch (str) { + case '--x': + return { type: 'arrow_cross', stroke: 'normal' }; + case '-->': + return { type: 'arrow', stroke: 'normal' }; + case '<-->': + return { type: 'double_arrow_point', stroke: 'normal' }; + case 'x--x': + return { type: 'double_arrow_cross', stroke: 'normal' }; + case 'o--o': + return { type: 'double_arrow_circle', stroke: 'normal' }; + case 'o.-o': + return { type: 'double_arrow_circle', stroke: 'dotted' }; + case '<==>': + return { type: 'double_arrow_point', stroke: 'thick' }; + case 'o==o': + return { type: 'double_arrow_circle', stroke: 'thick' }; + case 'x==x': + return { type: 'double_arrow_cross', stroke: 'thick' }; + case 'x.-x': + return { type: 'double_arrow_cross', stroke: 'dotted' }; + case 'x-.-x': + return { type: 'double_arrow_cross', stroke: 'dotted' }; + case '<.->': + return { type: 'double_arrow_point', stroke: 'dotted' }; + case '<-.->': + return { type: 'double_arrow_point', stroke: 'dotted' }; + case 'o-.-o': + return { type: 'double_arrow_circle', stroke: 'dotted' }; + case '--o': + return { type: 'arrow_circle', stroke: 'normal' }; + case '---': + return { type: 'arrow_open', stroke: 'normal' }; + case '-.-x': + return { type: 'arrow_cross', stroke: 'dotted' }; + case '-.->': + return { type: 'arrow', stroke: 'dotted' }; + case '-.-o': + return { type: 'arrow_circle', stroke: 'dotted' }; + case '-.-': + return { type: 'arrow_open', stroke: 'dotted' }; + case '.-x': + return { type: 'arrow_cross', stroke: 'dotted' }; + case '.->': + return { type: 'arrow', stroke: 'dotted' }; + case '.-o': + return { type: 'arrow_circle', stroke: 'dotted' }; + case '.-': + return { type: 'arrow_open', stroke: 'dotted' }; + case '==x': + return { type: 'arrow_cross', stroke: 'thick' }; + case '==>': + return { type: 'arrow', stroke: 'thick' }; + case '==o': + return { type: 'arrow_circle', stroke: 'thick' }; + case '===': + return { type: 'arrow_open', stroke: 'thick' }; + } +}; + +const destructLink = (_str, _startStr) => { + const info = destructEndLink(_str); + let startInfo; + if (_startStr) { + startInfo = destructStartLink(_startStr); + + if (startInfo.stroke !== info.stroke) { + return { type: 'INVALID', stroke: 'INVALID' }; + } + + if (startInfo.type === 'arrow_open') { + // -- xyz --> - take arrow type form ending + startInfo.type = info.type; + } else { + // x-- xyz --> - not supported + if (startInfo.type !== info.type) return { type: 'INVALID', stroke: 'INVALID' }; + + startInfo.type = 'double_' + startInfo.type; + } + + if (startInfo.type === 'double_arrow') { + startInfo.type = 'double_arrow_point'; + } + + return startInfo; + } + + return info; +}; + +export default { + addVertex, + addLink, + updateLinkInterpolate, + updateLink, + addClass, + setDirection, + setClass, + getTooltip, + setClickEvent, + setLink, + bindFunctions, + getDirection, + getVertices, + getEdges, + getClasses, + clear, + defaultStyle, + addSubGraph, + getDepthFirstPos, + indexNodes, + getSubGraphs, + destructLink, + lex: { + firstGraph + } +}; diff --git a/src/diagrams/flowchart-v2/flowRenderer.js b/src/diagrams/flowchart-v2/flowRenderer.js new file mode 100644 index 000000000..10250a16c --- /dev/null +++ b/src/diagrams/flowchart-v2/flowRenderer.js @@ -0,0 +1,488 @@ +import graphlib from 'graphlib'; +import * as d3 from 'd3'; +import dagre from 'dagre'; + +import flowDb from '../flowchart/flowDb'; +import flow from '../flowchart/parser/flow'; +import { getConfig } from '../../config'; + +import dagreD3 from 'dagre-d3'; +import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js'; +import { logger } from '../../logger'; +import { interpolateToCurve, getStylesFromArray } from '../../utils'; +import flowChartShapes from '../flowchart/flowChartShapes'; + +const conf = {}; +export const setConf = function(cnf) { + const keys = Object.keys(cnf); + for (let i = 0; i < keys.length; i++) { + conf[keys[i]] = cnf[keys[i]]; + } +}; + +/** + * Function that adds the vertices found in the graph definition to the graph to be rendered. + * @param vert Object containing the vertices. + * @param g The graph that is to be drawn. + */ +export const addVertices = function(vert, g, svgId) { + const svg = d3.select(`[id="${svgId}"]`); + const keys = Object.keys(vert); + + // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition + keys.forEach(function(id) { + const vertex = vert[id]; + + /** + * Variable for storing the classes for the vertex + * @type {string} + */ + let classStr = 'default'; + if (vertex.classes.length > 0) { + classStr = vertex.classes.join(' '); + } + + const styles = getStylesFromArray(vertex.styles); + + // Use vertex id as text in the box if no text is provided by the graph definition + let vertexText = vertex.text !== undefined ? vertex.text : vertex.id; + + // We create a SVG label, either by delegating to addHtmlLabel or manually + let vertexNode; + if (getConfig().flowchart.htmlLabels) { + // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? + const node = { + label: vertexText.replace( + /fa[lrsb]?:fa-[\w-]+/g, + s => `` + ) + }; + vertexNode = addHtmlLabel(svg, node).node(); + vertexNode.parentNode.removeChild(vertexNode); + } else { + const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:')); + + const rows = vertexText.split(//gi); + + for (let j = 0; j < rows.length; j++) { + 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', '1'); + tspan.textContent = rows[j]; + svgLabel.appendChild(tspan); + } + vertexNode = svgLabel; + } + + let radious = 0; + let _shape = ''; + // Set the shape based parameters + switch (vertex.type) { + case 'round': + radious = 5; + _shape = 'rect'; + break; + case 'square': + _shape = 'rect'; + break; + case 'diamond': + _shape = 'question'; + break; + case 'hexagon': + _shape = 'hexagon'; + break; + case 'odd': + _shape = 'rect_left_inv_arrow'; + break; + case 'lean_right': + _shape = 'lean_right'; + break; + case 'lean_left': + _shape = 'lean_left'; + break; + case 'trapezoid': + _shape = 'trapezoid'; + break; + case 'inv_trapezoid': + _shape = 'inv_trapezoid'; + break; + case 'odd_right': + _shape = 'rect_left_inv_arrow'; + break; + case 'circle': + _shape = 'circle'; + break; + case 'ellipse': + _shape = 'ellipse'; + break; + case 'stadium': + _shape = 'stadium'; + break; + case 'cylinder': + _shape = 'cylinder'; + break; + case 'group': + _shape = 'rect'; + break; + default: + _shape = 'rect'; + } + // Add the node + g.setNode(vertex.id, { + labelType: 'svg', + labelStyle: styles.labelStyle, + shape: _shape, + label: vertexNode, + rx: radious, + ry: radious, + class: classStr, + style: styles.style, + id: vertex.id + }); + }); +}; + +/** + * Add edges to graph based on parsed graph defninition + * @param {Object} edges The edges to add to the graph + * @param {Object} g The graph object + */ +export const addEdges = function(edges, g) { + let cnt = 0; + + let defaultStyle; + let defaultLabelStyle; + + if (typeof edges.defaultStyle !== 'undefined') { + const defaultStyles = getStylesFromArray(edges.defaultStyle); + defaultStyle = defaultStyles.style; + defaultLabelStyle = defaultStyles.labelStyle; + } + + edges.forEach(function(edge) { + cnt++; + const edgeData = {}; + + // Set link type for rendering + if (edge.type === 'arrow_open') { + edgeData.arrowhead = 'none'; + } else { + edgeData.arrowhead = 'normal'; + } + + let style = ''; + let labelStyle = ''; + + if (typeof edge.style !== 'undefined') { + const styles = getStylesFromArray(edge.style); + style = styles.style; + labelStyle = styles.labelStyle; + } else { + switch (edge.stroke) { + case 'normal': + style = 'fill:none'; + if (typeof defaultStyle !== 'undefined') { + style = defaultStyle; + } + if (typeof defaultLabelStyle !== 'undefined') { + labelStyle = defaultLabelStyle; + } + break; + case 'dotted': + style = 'fill:none;stroke-width:2px;stroke-dasharray:3;'; + break; + case 'thick': + style = ' stroke-width: 3.5px;fill:none'; + break; + } + } + + edgeData.style = style; + edgeData.labelStyle = labelStyle; + + if (typeof edge.interpolate !== 'undefined') { + edgeData.curve = interpolateToCurve(edge.interpolate, d3.curveLinear); + } else if (typeof edges.defaultInterpolate !== 'undefined') { + edgeData.curve = interpolateToCurve(edges.defaultInterpolate, d3.curveLinear); + } else { + edgeData.curve = interpolateToCurve(conf.curve, d3.curveLinear); + } + + if (typeof edge.text === 'undefined') { + if (typeof edge.style !== 'undefined') { + edgeData.arrowheadStyle = 'fill: #333'; + } + } else { + edgeData.arrowheadStyle = 'fill: #333'; + edgeData.labelpos = 'c'; + + if (getConfig().flowchart.htmlLabels) { + edgeData.labelType = 'html'; + edgeData.label = '' + edge.text + ''; + } else { + edgeData.labelType = 'text'; + edgeData.label = edge.text.replace(//gi, '\n'); + + if (typeof edge.style === 'undefined') { + edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none'; + } + + edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:'); + } + } + // Add the edge to the graph + g.setEdge(edge.start, edge.end, edgeData, cnt); + }); +}; + +/** + * Returns the all the styles from classDef statements in the graph definition. + * @returns {object} classDef styles + */ +export const getClasses = function(text) { + logger.info('Extracting classes'); + flowDb.clear(); + const parser = flow.parser; + parser.yy = flowDb; + + // Parse the graph definition + parser.parse(text); + return flowDb.getClasses(); +}; + +/** + * Draws a flowchart in the tag with id: id based on the graph definition in text. + * @param text + * @param id + */ +export const draw = function(text, id) { + logger.info('Drawing flowchart'); + flowDb.clear(); + const parser = flow.parser; + parser.yy = flowDb; + + // Parse the graph definition + try { + parser.parse(text); + } catch (err) { + logger.debug('Parsing failed'); + } + + // Fetch the default direction, use TD if none was found + let dir = flowDb.getDirection(); + if (typeof dir === 'undefined') { + dir = 'TD'; + } + + const conf = getConfig().flowchart; + const nodeSpacing = conf.nodeSpacing || 50; + const rankSpacing = conf.rankSpacing || 50; + + // Create the input mermaid.graph + const g = new graphlib.Graph({ + multigraph: true, + compound: true + }) + .setGraph({ + rankdir: dir, + nodesep: nodeSpacing, + ranksep: rankSpacing, + marginx: 8, + marginy: 8 + }) + .setDefaultEdgeLabel(function() { + return {}; + }); + + let subG; + const subGraphs = flowDb.getSubGraphs(); + for (let i = subGraphs.length - 1; i >= 0; i--) { + subG = subGraphs[i]; + flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes); + } + + // Fetch the verices/nodes and edges/links from the parsed graph definition + const vert = flowDb.getVertices(); + + const edges = flowDb.getEdges(); + + let i = 0; + for (i = subGraphs.length - 1; i >= 0; i--) { + subG = subGraphs[i]; + + d3.selectAll('cluster').append('text'); + + for (let j = 0; j < subG.nodes.length; j++) { + g.setParent(subG.nodes[j], subG.id); + } + } + addVertices(vert, g, id); + addEdges(edges, g); + + // Create the renderer + const Render = dagreD3.render; + const render = new Render(); + + // Add custom shapes + flowChartShapes.addToRender(render); + + // Add our custom arrow - an empty arrowhead + render.arrows().none = function normal(parent, id, edge, type) { + const marker = parent + .append('marker') + .attr('id', id) + .attr('viewBox', '0 0 10 10') + .attr('refX', 9) + .attr('refY', 5) + .attr('markerUnits', 'strokeWidth') + .attr('markerWidth', 8) + .attr('markerHeight', 6) + .attr('orient', 'auto'); + + const path = marker.append('path').attr('d', 'M 0 0 L 0 0 L 0 0 z'); + dagreD3.util.applyStyle(path, edge[type + 'Style']); + }; + + // Override normal arrowhead defined in d3. Remove style & add class to allow css styling. + render.arrows().normal = function normal(parent, id) { + const marker = parent + .append('marker') + .attr('id', id) + .attr('viewBox', '0 0 10 10') + .attr('refX', 9) + .attr('refY', 5) + .attr('markerUnits', 'strokeWidth') + .attr('markerWidth', 8) + .attr('markerHeight', 6) + .attr('orient', 'auto'); + + marker + .append('path') + .attr('d', 'M 0 0 L 10 5 L 0 10 z') + .attr('class', 'arrowheadPath') + .style('stroke-width', 1) + .style('stroke-dasharray', '1,0'); + }; + + // Set up an SVG group so that we can translate the final graph. + const svg = d3.select(`[id="${id}"]`); + + // Run the renderer. This is what draws the final graph. + const element = d3.select('#' + id + ' g'); + render(element, g); + + element.selectAll('g.node').attr('title', function() { + return flowDb.getTooltip(this.id); + }); + + const padding = 8; + const svgBounds = svg.node().getBBox(); + const width = svgBounds.width + padding * 2; + const height = svgBounds.height + padding * 2; + logger.debug( + `new ViewBox 0 0 ${width} ${height}`, + `translate(${padding - g._label.marginx}, ${padding - g._label.marginy})` + ); + + if (conf.useMaxWidth) { + svg.attr('width', '100%'); + svg.attr('style', `max-width: ${width}px;`); + } else { + svg.attr('height', height); + svg.attr('width', width); + } + + svg.attr('viewBox', `0 0 ${width} ${height}`); + svg + .select('g') + .attr('transform', `translate(${padding - g._label.marginx}, ${padding - svgBounds.y})`); + + // Index nodes + flowDb.indexNodes('subGraph' + i); + + // reposition labels + for (i = 0; i < subGraphs.length; i++) { + subG = subGraphs[i]; + + if (subG.title !== 'undefined') { + const clusterRects = document.querySelectorAll('#' + id + ' [id="' + subG.id + '"] rect'); + const clusterEl = document.querySelectorAll('#' + id + ' [id="' + subG.id + '"]'); + + const xPos = clusterRects[0].x.baseVal.value; + const yPos = clusterRects[0].y.baseVal.value; + const width = clusterRects[0].width.baseVal.value; + const cluster = d3.select(clusterEl[0]); + const te = cluster.select('.label'); + te.attr('transform', `translate(${xPos + width / 2}, ${yPos + 14})`); + te.attr('id', id + 'Text'); + + for (let j = 0; j < subG.classes.length; j++) { + clusterEl[0].classList.add(subG.classes[j]); + } + } + } + + // Add label rects for non html labels + if (!conf.htmlLabels) { + const labels = document.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); + for (let k = 0; k < labels.length; k++) { + const label = labels[k]; + + // Get dimensions of label + const dim = label.getBBox(); + + const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + rect.setAttribute('rx', 0); + rect.setAttribute('ry', 0); + rect.setAttribute('width', dim.width); + rect.setAttribute('height', dim.height); + rect.setAttribute('style', 'fill:#e8e8e8;'); + + label.insertBefore(rect, label.firstChild); + } + } + + // If node has a link, wrap it in an anchor SVG object. + const keys = Object.keys(vert); + keys.forEach(function(key) { + const vertex = vert[key]; + + if (vertex.link) { + const node = d3.select('#' + id + ' [id="' + key + '"]'); + if (node) { + const link = document.createElementNS('http://www.w3.org/2000/svg', 'a'); + link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' ')); + link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link); + link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener'); + + const linkNode = node.insert(function() { + return link; + }, ':first-child'); + + const shape = node.select('.label-container'); + if (shape) { + linkNode.append(function() { + return shape.node(); + }); + } + + const label = node.select('.label'); + if (label) { + linkNode.append(function() { + return label.node(); + }); + } + } + } + }); +}; + +export default { + setConf, + addVertices, + addEdges, + getClasses, + draw +}; diff --git a/src/diagrams/flowchart/parser/flow.jison b/src/diagrams/flowchart/parser/flow.jison index f867e5713..58e4664a6 100644 --- a/src/diagrams/flowchart/parser/flow.jison +++ b/src/diagrams/flowchart/parser/flow.jison @@ -21,7 +21,8 @@ "classDef" return 'CLASSDEF'; "class" return 'CLASS'; "click" return 'CLICK'; -"graph" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';} +"graph" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';} +"flowchart" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';} "subgraph" return 'subgraph'; "end"\b\s* return 'end'; \s*"LR" { this.popState(); return 'DIR'; } diff --git a/src/experimental.js b/src/experimental.js new file mode 100644 index 000000000..d34de469d --- /dev/null +++ b/src/experimental.js @@ -0,0 +1,42 @@ +import dagre from 'dagre'; +import graphlib from 'graphlib'; + +// Create a new directed graph +var g = new dagre.graphlib.Graph({ compound: true }); + +// Set an object for the graph label +g.setGraph({}); + +// Default to assigning a new object as a label for each new edge. +g.setDefaultEdgeLabel(function() { + return {}; +}); + +// Add nodes to the graph. The first argument is the node id. The second is +// metadata about the node. In this case we're going to add labels to each of +// our nodes. +g.setNode('root', { label: 'Cluster' }); +g.setNode('kspacey', { label: 'Kevin Spacey', width: 144, height: 100, x: 200 }); +// g.setParent('kspacey', 'root'); +g.setNode('swilliams', { label: 'Saul Williams', width: 160, height: 100 }); +// g.setNode('bpitt', { label: 'Brad Pitt', width: 108, height: 100 }); +// g.setNode('hford', { label: 'Harrison Ford', width: 168, height: 100 }); +// g.setNode('lwilson', { label: 'Luke Wilson', width: 144, height: 100 }); +// g.setNode('kbacon', { label: 'Kevin Bacon', width: 121, height: 100 }); + +// Add edges to the graph. +g.setEdge('kspacey', 'swilliams'); +g.setEdge('swilliams'); +// g.setEdge('swilliams', 'kbacon'); +// g.setEdge('bpitt', 'kbacon'); +// g.setEdge('hford', 'lwilson'); +// g.setEdge('lwilson', 'kbacon'); + +dagre.layout(g); + +g.nodes().forEach(function(v) { + console.log('Node ' + v + ': ' + JSON.stringify(g.node(v))); +}); +g.edges().forEach(function(e) { + console.log('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e))); +}); diff --git a/src/mermaid.js b/src/mermaid.js index 0cf24e81f..6f2311390 100644 --- a/src/mermaid.js +++ b/src/mermaid.js @@ -6,7 +6,6 @@ import he from 'he'; import mermaidAPI from './mermaidAPI'; import { logger } from './logger'; - /** * ## init * Function that goes through the document to find the chart definitions in there and render them. diff --git a/src/mermaidAPI.js b/src/mermaidAPI.js index 205254540..600938027 100644 --- a/src/mermaidAPI.js +++ b/src/mermaidAPI.js @@ -17,6 +17,7 @@ import { setConfig, getConfig } from './config'; import { logger, setLogLevel } from './logger'; import utils from './utils'; import flowRenderer from './diagrams/flowchart/flowRenderer'; +import flowRendererV2 from './diagrams/flowchart-v2/flowRenderer'; import flowParser from './diagrams/flowchart/parser/flow'; import flowDb from './diagrams/flowchart/flowDb'; import sequenceRenderer from './diagrams/sequence/sequenceRenderer'; @@ -363,6 +364,11 @@ function parse(text) { parser = flowParser; parser.parser.yy = flowDb; break; + case 'flowchart-v2': + flowDb.clear(); + parser = flowRendererV2; + parser.parser.yy = flowDb; + break; case 'sequence': parser = sequenceParser; parser.parser.yy = sequenceDb; @@ -568,6 +574,11 @@ const render = function(id, _txt, cb, container) { flowRenderer.setConf(config.flowchart); flowRenderer.draw(txt, id, false); break; + case 'flowchart-v2': + config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute; + flowRendererV2.setConf(config.flowchart); + flowRendererV2.draw(txt, id, false); + break; case 'sequence': config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute; if (config.sequenceDiagram) { diff --git a/src/utils.js b/src/utils.js index 1aec62d4f..c6e37bb9d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -41,6 +41,9 @@ export const detectType = function(text) { if (text.match(/^\s*gitGraph/)) { return 'git'; } + if (text.match(/^\s*flowchart/)) { + return 'flowchart-v2'; + } if (text.match(/^\s*info/)) { return 'info'; From 1570ed4610bbfc9ec1c3a331f2ad1f7038d21f51 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Thu, 5 Mar 2020 17:34:06 -0800 Subject: [PATCH 50/95] Update mermaidAPI.md --- docs/mermaidAPI.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/mermaidAPI.md b/docs/mermaidAPI.md index 7b7b68e06..bd4f3787f 100644 --- a/docs/mermaidAPI.md +++ b/docs/mermaidAPI.md @@ -2,14 +2,13 @@ ## mermaidAPI -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. +This API can be used optionally handle the integration of mermaid to a web page, instead of using the default integration methods provided by mermaid.js. -The core of this api is the [**render**][1] function which, given a graph -definition as text, renders the graph/diagram and returns an svg element for the graph. +The core of this api is the [**render**][1] function which, renders the given text based input to a graph/diagram, that is returned as an svg element. -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. +After it is rendered, it is is then up to the user of the API to make use of the svg, which can be either inserted somewhere in the page or something else altogether. -In addition to the render function, a number of behavioral configuration options are available. +In addition to the render function, a number of behavioral configuration options are also available. ## Configuration From 997353ab71620ef2d0691a34b4df180afab3ba36 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Sat, 7 Mar 2020 00:59:39 -0800 Subject: [PATCH 51/95] Update gantt.md --- docs/gantt.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/gantt.md b/docs/gantt.md index 9d2124469..70f980bae 100755 --- a/docs/gantt.md +++ b/docs/gantt.md @@ -30,6 +30,11 @@ gantt ``` gantt +## excludes (excludes specified day, i.e, "weekends", saturday, sunday, monday, or specific dates, making it useful for computing the amount of time it will take before a project is finished, not the amount of time and effort spent on a project, by individual contributors. +important note when using exclude function, the graphic will accomodate the exclusion of certain days. however, if the date being excluded is inside the time alloted for a task, by adding an extra day to the duration of the task, rather than creating a gap within the scheduled task. + +## present some screenshots to show for it and the math to back it up. + dateFormat YYYY-MM-DD title Adding GANTT diagram functionality to mermaid From 061045016fc4eed8da1dd9c95369b8c0adbd76bc Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Sat, 7 Mar 2020 11:05:56 +0000 Subject: [PATCH 52/95] Minor tidy up of prototype code --- src/diagrams/er/erRenderer.js | 135 ++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 64 deletions(-) diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index 1a6368e1d..15eeb6499 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -21,28 +21,15 @@ export const setConf = function(cnf) { */ const addEntities = function(entities, g) { const keys = Object.keys(entities); - //const fontFamily = getConfig().fontFamily; keys.forEach(function(id) { const entity = entities[id]; - const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); - // Add the text content (the entity id) - /* - 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', '1'); - tspan.textContent = id; - tspan.setAttribute('style', 'font-family: monospace'); - svgLabel.appendChild(tspan); - */ g.setNode(entity, { labelType: 'svg', width: 100, height: 75, - //labelStyle: labelStyle, shape: 'rect', label: svgLabel, id: entity @@ -56,7 +43,7 @@ const addEntities = function(entities, g) { * @param entities the entities to be drawn * @param g the dagre graph that contains the vertex and edge definitions post-layout */ -const drawEntities = function(diagram, entities, g) { +const drawEntities = function(diagram, entities, g, svgId) { // For each vertex in the graph: // - append the text label centred in the right place // - get it's bounding box and calculate the size of the enclosing rectangle @@ -66,12 +53,14 @@ const drawEntities = function(diagram, entities, g) { console.debug('Handling node ', v); // Get the centre co-ordinate of the node so that we can centre the entity name - let centre = { x: g.node(v).x, y: g.node(v).y }; + const centre = { x: g.node(v).x, y: g.node(v).y }; // Label the entity - this is done first so that we can get the bounding box // which then determines the size of the rectangle - let textNode = diagram + const textId = 'entity-' + v + '-' + svgId; + const textNode = diagram .append('text') + .attr('id', textId) .attr('x', centre.x) .attr('y', centre.y) .attr('dominant-baseline', 'middle') @@ -79,19 +68,19 @@ const drawEntities = function(diagram, entities, g) { .attr('style', 'font-family: ' + getConfig().fontFamily) .text(v); - let textBBox = textNode.node().getBBox(); - let entityWidth = Math.max(conf.minEntityWidth, textBBox.width + conf.entityPadding * 2); - let entityHeight = Math.max(conf.minEntityHeight, textBBox.height + conf.entityPadding * 2); + const textBBox = textNode.node().getBBox(); + const entityWidth = Math.max(conf.minEntityWidth, textBBox.width + conf.entityPadding * 2); + const entityHeight = Math.max(conf.minEntityHeight, textBBox.height + conf.entityPadding * 2); // Add info to the node so that we can retrieve it later when drawing relationships g.node(v).width = entityWidth; g.node(v).height = entityHeight; - // Draw the rectangle - let rectX = centre.x - entityWidth / 2; - let rectY = centre.y - entityHeight / 2; + // Draw the rectangle - insert it before the text so that the text is not obscured + const rectX = centre.x - entityWidth / 2; + const rectY = centre.y - entityHeight / 2; diagram - .insert('rect') + .insert('rect', '#' + textId) .attr('fill', conf.fill) .attr('fill-opacity', conf.fillOpacity) .attr('stroke', conf.stroke) @@ -99,18 +88,6 @@ const drawEntities = function(diagram, entities, g) { .attr('y', rectY) .attr('width', entityWidth) .attr('height', entityHeight); - - // TODO: Revisit - // Bit of a hack - we're adding the text AGAIN because - // the rectangle is filled to obscure the lines that go to the centre! - diagram - .append('text') - .attr('x', centre.x) - .attr('y', centre.y) - .attr('dominant-baseline', 'middle') - .attr('text-anchor', 'middle') - .attr('style', 'font-family: ' + getConfig().fontFamily) - .text(v); }); }; // drawEntities @@ -118,7 +95,7 @@ const addRelationships = function(relationships, g) { relationships.forEach(function(r) { g.setEdge(r.entityA, r.entityB, { relationship: r }); }); -}; +}; // addRelationships const drawRelationships = function(diagram, relationships, g) { relationships.forEach(function(rel) { @@ -231,35 +208,66 @@ const getToes = function(relationship, fromPoint, toPoint, distance) { const toeXDelta = toeYDelta * Math.abs(gradient); if (gradient > 0) { - if (fromPoint.x < toPoint.x) { - // Scenario A - + const topToe = function(point) { return { - to: { - top: { - x: toPoint.x + toeXDelta, - y: toPoint.y - toeYDelta - }, - bottom: { - x: toPoint.x - toeXDelta, - y: toPoint.y + toeYDelta - } - }, - from: { - top: { - x: fromPoint.x + toeXDelta, - y: fromPoint.y - toeYDelta - }, - bottom: { - x: fromPoint.x - toeXDelta, - y: fromPoint.y + toeYDelta - } - } + x: point.x + toeXDelta, + y: point.y - toeYDelta }; - } else { - // Scenario E - } + }; + + const bottomToe = function(point) { + return { + x: point.x - toeXDelta, + y: point.y + toeYDelta + }; + }; + + const lower = { + top: fromPoint.x < toPoint.x ? topToe(toPoint) : topToe(fromPoint), + bottom: fromPoint.x < toPoint.x ? bottomToe(toPoint) : bottomToe(fromPoint) + }; + + const upper = { + top: fromPoint.x < toPoint.x ? topToe(fromPoint) : topToe(toPoint), + bottom: fromPoint.x < toPoint.x ? bottomToe(fromPoint) : bottomToe(toPoint) + }; + + return { + to: fromPoint.x < toPoint.x ? lower : upper, + from: fromPoint.x < toPoint.x ? upper : lower + }; } + + /* + if (fromPoint.x < toPoint.x) { + // Scenario A + + return { + to: { + top: { + x: toPoint.x + toeXDelta, + y: toPoint.y - toeYDelta + }, + bottom: { + x: toPoint.x - toeXDelta, + y: toPoint.y + toeYDelta + } + }, + from: { + top: { + x: fromPoint.x + toeXDelta, + y: fromPoint.y - toeYDelta + }, + bottom: { + x: fromPoint.x - toeXDelta, + y: fromPoint.y + toeYDelta + } + } + }; + } else { + // Scenario E + } + */ }; // getToes const getJoints = function(relationship, fromPoint, toPoint, distance) { @@ -445,7 +453,7 @@ export const draw = function(text, id) { drawRelationships(diagram, relationships, g); drawFeet(diagram, relationships, g); - drawEntities(diagram, entities, g); + drawEntities(diagram, entities, g, id); const padding = 8; @@ -473,6 +481,5 @@ export const draw = function(text, id) { export default { setConf, - //addEntities, draw }; From 005defbb5c65f1c225db48476fa5d7ff1a46b799 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Sat, 7 Mar 2020 16:17:03 -0800 Subject: [PATCH 53/95] Add files via upload --- docs/img/Gantt-excluded-days-within.png | Bin 0 -> 79453 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/img/Gantt-excluded-days-within.png diff --git a/docs/img/Gantt-excluded-days-within.png b/docs/img/Gantt-excluded-days-within.png new file mode 100644 index 0000000000000000000000000000000000000000..2283bf99d8d6ed1b721541ce1f0f8b38d6e19887 GIT binary patch literal 79453 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

eQe(Y%!Xg7+4bDT&Bc0< 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 Date: Sat, 7 Mar 2020 16:17:11 -0800 Subject: [PATCH 54/95] Update gantt.md --- docs/gantt.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/gantt.md b/docs/gantt.md index 70f980bae..7f61578de 100755 --- a/docs/gantt.md +++ b/docs/gantt.md @@ -1,8 +1,19 @@ # Gantt diagrams -> A Gantt chart is a type of bar chart, first developed by Karol Adamiecki in 1896, and independently by Henry Gantt in the 1910s, that illustrates a project schedule. Gantt charts illustrate the start and finish dates of the terminal elements and summary elements of a project. - -Mermaid can render Gantt diagrams. +> A Gantt chart is a type of bar chart, first developed by Karol Adamiecki in 1896, and independently by Henry Gantt in the 1910s, that illustrates a project schedule and the amount of time it would take for any one project to finish. Gantt charts illustrate number of days between the start and finish dates of the terminal elements and summary elements of a project. + + ## A note to users + Gannt Charts will record each scheduled task as one continuous bar that extends from the left to the right. The lower axis represents time and the right records the different tasks and their order. + + It is important to remember that when a date, day or collection of dates are "excluded", the Gannt Chart will accomodate those changes by extending another day, towards the right, not by creating a gap inside the task. + As shown here [excluded dates without](.docs/img/Gantt-excluded-days-within.png) + + However, if the excluded date/s is between two tasks that are set to start consecutively, the excluded dates will be skipped graphically and left blank, and the following task will begin after the end of the excluded date/s. + As shown here [excluded dates between](.docs/img/Gantt-long-weekend-look.png) + + Thus, it is useful for tracking the amount of time it would take before a project is finished, but it can also be used to graphically represent "non-working days. + +Mermaid can render Gantt diagrams as SVG, PNG or a MarkDown link that can be pasted into docs. ``` gantt @@ -30,14 +41,11 @@ gantt ``` gantt -## excludes (excludes specified day, i.e, "weekends", saturday, sunday, monday, or specific dates, making it useful for computing the amount of time it will take before a project is finished, not the amount of time and effort spent on a project, by individual contributors. -important note when using exclude function, the graphic will accomodate the exclusion of certain days. however, if the date being excluded is inside the time alloted for a task, by adding an extra day to the duration of the task, rather than creating a gap within the scheduled task. -## present some screenshots to show for it and the math to back it up. - - dateFormat YYYY-MM-DD - title Adding GANTT diagram functionality to mermaid + dateFormat :YYYY-MM-DD + title :Adding GANTT diagram functionality to mermaid + excludes :excludes the named objects from being charted. Accepts specific dates in YY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays". section A section Completed task :done, des1, 2014-01-06,2014-01-08 Active task :active, des2, 2014-01-09, 3d @@ -123,7 +131,7 @@ Tbd ### Date format -The default date format is YYYY-MM-DD. You can define your ``dateFormat``. For example: +The default date format is YYYY-MM-DD. You can define your ``dateFormat``. For example: 2020-3-7 ``` dateFormat YYYY MM DD From 5cd54c4831e31940952cd7cd4e3d7c7714350e85 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Sat, 7 Mar 2020 16:21:07 -0800 Subject: [PATCH 55/95] Update gantt.md --- docs/gantt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gantt.md b/docs/gantt.md index 7f61578de..5fc40436e 100755 --- a/docs/gantt.md +++ b/docs/gantt.md @@ -6,7 +6,7 @@ Gannt Charts will record each scheduled task as one continuous bar that extends from the left to the right. The lower axis represents time and the right records the different tasks and their order. It is important to remember that when a date, day or collection of dates are "excluded", the Gannt Chart will accomodate those changes by extending another day, towards the right, not by creating a gap inside the task. - As shown here [excluded dates without](.docs/img/Gantt-excluded-days-within.png) + As shown here [excluded dates without](https://github.com/NeilCuzon/mermaid/blob/develop/docs/img/Gantt-excluded-days-within.png) However, if the excluded date/s is between two tasks that are set to start consecutively, the excluded dates will be skipped graphically and left blank, and the following task will begin after the end of the excluded date/s. As shown here [excluded dates between](.docs/img/Gantt-long-weekend-look.png) From 5dae0c14d92f76ee19224847912d424c12549a39 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Sat, 7 Mar 2020 16:22:55 -0800 Subject: [PATCH 56/95] Add files via upload --- docs/img/Gantt-long-weekend-look.png | Bin 0 -> 79092 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/img/Gantt-long-weekend-look.png diff --git a/docs/img/Gantt-long-weekend-look.png b/docs/img/Gantt-long-weekend-look.png new file mode 100644 index 0000000000000000000000000000000000000000..1b2fc8e17ef9a4adb34de9dc326d41584873292c GIT binary patch literal 79092 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 Date: Sat, 7 Mar 2020 16:23:51 -0800 Subject: [PATCH 57/95] Update gantt.md --- docs/gantt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/gantt.md b/docs/gantt.md index 5fc40436e..3cae0f4ac 100755 --- a/docs/gantt.md +++ b/docs/gantt.md @@ -6,10 +6,10 @@ Gannt Charts will record each scheduled task as one continuous bar that extends from the left to the right. The lower axis represents time and the right records the different tasks and their order. It is important to remember that when a date, day or collection of dates are "excluded", the Gannt Chart will accomodate those changes by extending another day, towards the right, not by creating a gap inside the task. - As shown here [excluded dates without](https://github.com/NeilCuzon/mermaid/blob/develop/docs/img/Gantt-excluded-days-within.png) + As shown here ![](https://github.com/NeilCuzon/mermaid/blob/develop/docs/img/Gantt-excluded-days-within.png) However, if the excluded date/s is between two tasks that are set to start consecutively, the excluded dates will be skipped graphically and left blank, and the following task will begin after the end of the excluded date/s. - As shown here [excluded dates between](.docs/img/Gantt-long-weekend-look.png) + As shown here ![](https://github.com/NeilCuzon/mermaid/blob/develop/docs/img/Gantt-long-weekend-look.png) Thus, it is useful for tracking the amount of time it would take before a project is finished, but it can also be used to graphically represent "non-working days. From fdaf13bdc4b80545a2b87ca87bebb5370ea91ce2 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Sat, 7 Mar 2020 16:26:22 -0800 Subject: [PATCH 58/95] Update gantt.md --- docs/gantt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gantt.md b/docs/gantt.md index 3cae0f4ac..b57b933c6 100755 --- a/docs/gantt.md +++ b/docs/gantt.md @@ -3,7 +3,7 @@ > A Gantt chart is a type of bar chart, first developed by Karol Adamiecki in 1896, and independently by Henry Gantt in the 1910s, that illustrates a project schedule and the amount of time it would take for any one project to finish. Gantt charts illustrate number of days between the start and finish dates of the terminal elements and summary elements of a project. ## A note to users - Gannt Charts will record each scheduled task as one continuous bar that extends from the left to the right. The lower axis represents time and the right records the different tasks and their order. + Gannt Charts will record each scheduled task as one continuous bar that extends from the left to the right. The x axis represents time and the y records the different tasks and the order in which they are to be completed. It is important to remember that when a date, day or collection of dates are "excluded", the Gannt Chart will accomodate those changes by extending another day, towards the right, not by creating a gap inside the task. As shown here ![](https://github.com/NeilCuzon/mermaid/blob/develop/docs/img/Gantt-excluded-days-within.png) From e90ed7a85329168f610d18f06ec2cddbfbb8e7fd Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Sat, 7 Mar 2020 16:46:59 -0800 Subject: [PATCH 59/95] Update gantt.md --- docs/gantt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/gantt.md b/docs/gantt.md index b57b933c6..321bb5712 100755 --- a/docs/gantt.md +++ b/docs/gantt.md @@ -5,13 +5,13 @@ ## A note to users Gannt Charts will record each scheduled task as one continuous bar that extends from the left to the right. The x axis represents time and the y records the different tasks and the order in which they are to be completed. - It is important to remember that when a date, day or collection of dates are "excluded", the Gannt Chart will accomodate those changes by extending another day, towards the right, not by creating a gap inside the task. + It is important to remember that when a date, day or collection of dates specific to a task are "excluded", the Gannt Chart will accomodate those changes by extending an equal number of day, towards the right, not by creating a gap inside the task. As shown here ![](https://github.com/NeilCuzon/mermaid/blob/develop/docs/img/Gantt-excluded-days-within.png) However, if the excluded date/s is between two tasks that are set to start consecutively, the excluded dates will be skipped graphically and left blank, and the following task will begin after the end of the excluded date/s. As shown here ![](https://github.com/NeilCuzon/mermaid/blob/develop/docs/img/Gantt-long-weekend-look.png) - Thus, it is useful for tracking the amount of time it would take before a project is finished, but it can also be used to graphically represent "non-working days. + A Gantt chart is useful for tracking the amount of time it would take before a project is finished, but it can also be used to graphically represent "non-working days, with a few tweaks. Mermaid can render Gantt diagrams as SVG, PNG or a MarkDown link that can be pasted into docs. From df88300e6e9c25179893c40b4b3f7fbcd0d415f2 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Sat, 7 Mar 2020 16:50:39 -0800 Subject: [PATCH 60/95] Update README.md --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 22151a6f2..4e3f71c02 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,7 +9,7 @@ It is a simple markdown-inspired, script language for diagramming and charting that can be deployed via Javascipt. -The reasoning behind it's inception was a need for simplified documentation and frustration with heavy and time consuming tools like Visio, for explaining code. +The reasoning behind it's inception was a need for simplified documentation and frustration with heavy and time consuming tools for explaining code, like Visio. Thus mermaid was created to make the process of diagramming and charting, as simple and as close to markdown, as possible. From d3b69abd87941bf9f53bff70fadca0c02ea1e0f5 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Sat, 7 Mar 2020 16:54:49 -0800 Subject: [PATCH 61/95] Update gantt.md --- docs/gantt.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/gantt.md b/docs/gantt.md index 321bb5712..542f3a43e 100755 --- a/docs/gantt.md +++ b/docs/gantt.md @@ -45,7 +45,8 @@ gantt dateFormat :YYYY-MM-DD title :Adding GANTT diagram functionality to mermaid - excludes :excludes the named objects from being charted. Accepts specific dates in YY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays". + excludes :excludes the named dates/days from being included in a charted task.. + (Accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".) section A section Completed task :done, des1, 2014-01-06,2014-01-08 Active task :active, des2, 2014-01-09, 3d From 9aacc85a165a06df4319a981da0867d47aa7903a Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Sun, 8 Mar 2020 22:22:33 +0000 Subject: [PATCH 62/95] Working experimental version using markers --- src/diagrams/er/erMarkers.js | 174 ++++++++++++++++++++++++++++++++++ src/diagrams/er/erRenderer.js | 116 ++++++++++++++++++++++- 2 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 src/diagrams/er/erMarkers.js diff --git a/src/diagrams/er/erMarkers.js b/src/diagrams/er/erMarkers.js new file mode 100644 index 000000000..af9edbc9c --- /dev/null +++ b/src/diagrams/er/erMarkers.js @@ -0,0 +1,174 @@ +import * as d3 from 'd3'; + +const ERMarkers = { + ONLY_ONE_START: 'ONLY_ONE_START', + ONLY_ONE_END: 'ONLY_ONE_END', + + ZERO_OR_ONE_START: 'ZERO_OR_ONE_START', + ZERO_OR_ONE_END: 'ZERO_OR_ONE_END', + + ONE_OR_MORE_START: 'ONE_OR_MORE_START', + ONE_OR_MORE_END: 'ONE_OR_MORE_END', + + ZERO_OR_MORE_START: 'ZERO_OR_MORE_START', + ZERO_OR_MORE_END: 'ZERO_OR_MORE_END' +}; + +/** + * Put the markers into the svg DOM for use in paths + */ +const insertMarkers = function(elem, conf) { + let marker; + + const markerWidth = + elem + .append('defs') + .append('marker') + .attr('id', ERMarkers.ONLY_ONE_START) + .attr('refX', 0) + .attr('refY', 9) + .attr('markerWidth', 18) + .attr('markerHeight', 18) + .attr('orient', 'auto') + .append('path') + .attr('stroke', conf.stroke) + .attr('fill', 'none') + .attr('d', 'M9,0 L9,18 M15,0 L15,18'); + + elem + .append('defs') + .append('marker') + .attr('id', ERMarkers.ONLY_ONE_END) + .attr('refX', 18) + .attr('refY', 9) + .attr('markerWidth', 18) + .attr('markerHeight', 18) + .attr('orient', 'auto') + .append('path') + .attr('stroke', conf.stroke) + .attr('fill', 'none') + .attr('d', 'M3,0 L3,18 M9,0 L9,18'); + + marker = elem + .append('defs') + .append('marker') + .attr('id', ERMarkers.ZERO_OR_ONE_START) + .attr('refX', 0) + .attr('refY', 9) + .attr('markerWidth', 30) + .attr('markerHeight', 18) + .attr('orient', 'auto'); + marker + .append('circle') + .attr('stroke', conf.stroke) + .attr('fill', 'white') + .attr('cx', 21) + .attr('cy', 9) + .attr('r', 6); + marker + .append('path') + .attr('stroke', conf.stroke) + .attr('fill', 'none') + .attr('d', 'M9,0 L9,18'); + + marker = elem + .append('defs') + .append('marker') + .attr('id', ERMarkers.ZERO_OR_ONE_END) + .attr('refX', 30) + .attr('refY', 9) + .attr('markerWidth', 30) + .attr('markerHeight', 18) + .attr('orient', 'auto'); + marker + .append('circle') + .attr('stroke', conf.stroke) + .attr('fill', 'white') + .attr('cx', 9) + .attr('cy', 9) + .attr('r', 6); + marker + .append('path') + .attr('stroke', conf.stroke) + .attr('fill', 'none') + .attr('d', 'M21,0 L21,18'); + + elem + .append('defs') + .append('marker') + .attr('id', ERMarkers.ONE_OR_MORE_START) + .attr('refX', 0) + .attr('refY', 9) + .attr('markerWidth', 18) + .attr('markerHeight', 18) + .attr('orient', 'auto') + .append('path') + .attr('stroke', conf.stroke) + .attr('fill', 'none') + .attr('d', 'M0,0 L9,9 L0,18 M15,0 L15,18'); + + elem + .append('defs') + .append('marker') + .attr('id', ERMarkers.ONE_OR_MORE_END) + .attr('refX', 18) + .attr('refY', 9) + .attr('markerWidth', 21) + .attr('markerHeight', 18) + .attr('orient', 'auto') + .append('path') + .attr('stroke', conf.stroke) + .attr('fill', 'none') + .attr('d', 'M3,0 L3,18 M18,0 L9,9 L18,18'); + + marker = elem + .append('defs') + .append('marker') + .attr('id', ERMarkers.ZERO_OR_MORE_START) + .attr('refX', 0) + .attr('refY', 9) + .attr('markerWidth', 30) + .attr('markerHeight', 18) + .attr('orient', 'auto'); + marker + .append('circle') + .attr('stroke', conf.stroke) + .attr('fill', 'white') + .attr('cx', 21) + .attr('cy', 9) + .attr('r', 6); + marker + .append('path') + .attr('stroke', conf.stroke) + .attr('fill', 'none') + .attr('d', 'M0,0 L9,9 L0,18'); + + marker = elem + .append('defs') + .append('marker') + .attr('id', ERMarkers.ZERO_OR_MORE_END) + .attr('refX', 30) + .attr('refY', 9) + .attr('markerWidth', 30) + .attr('markerHeight', 18) + .attr('orient', 'auto'); + marker + .append('circle') + .attr('stroke', conf.stroke) + .attr('fill', 'white') + .attr('cx', 9) + .attr('cy', 9) + .attr('r', 6); + marker + .append('path') + .attr('stroke', conf.stroke) + .attr('fill', 'none') + .attr('d', 'M30,0 L21,9 L30,18'); + + return; +}; + +export default { + ERMarkers, + insertMarkers +}; diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index 15eeb6499..ba010dab7 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -5,6 +5,7 @@ import erParser from './parser/erDiagram'; import dagre from 'dagre'; import { getConfig } from '../../config'; import { logger } from '../../logger'; +import erMarkers from './erMarkers'; const conf = {}; export const setConf = function(cnf) { @@ -99,10 +100,119 @@ const addRelationships = function(relationships, g) { const drawRelationships = function(diagram, relationships, g) { relationships.forEach(function(rel) { - drawRelationship(diagram, rel, g); + //drawRelationship(diagram, rel, g); + drawRelationshipFromLayout(diagram, rel, g); }); }; // drawRelationships +const drawRelationshipFromLayout = function(diagram, rel, g) { + // Find the edge relating to this relationship + const edge = g.edge({ v: rel.entityA, w: rel.entityB }); + + // Using it's points, generate a line function + edge.points = edge.points.filter(p => !Number.isNaN(p.y)); // TODO: why is necessary? + + // Get a function that will generate the line path + const lineFunction = d3 + .line() + .x(function(d) { + return d.x; + }) + .y(function(d) { + return d.y; + }) + .curve(d3.curveBasis); + + // Append the line to the diagram node + const svgPath = diagram + .append('path') + .attr('d', lineFunction(edge.points)) + .attr('stroke', conf.stroke) + .attr('fill', 'none'); + + // TODO: Understand this + let url = ''; + if (conf.arrowMarkerAbsolute) { + url = + window.location.protocol + + '//' + + window.location.host + + window.location.pathname + + window.location.search; + url = url.replace(/\(/g, '\\('); + url = url.replace(/\)/g, '\\)'); + } + + // TODO: change the way enums are imported + // Decide which start and end markers it needs + switch (rel.cardinality) { + case erDb.Cardinality.ONLY_ONE_TO_ONE_OR_MORE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')'); + break; + case erDb.Cardinality.ONLY_ONE_TO_ZERO_OR_MORE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')'); + break; + case erDb.Cardinality.ZERO_OR_ONE_TO_ZERO_OR_MORE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')'); + break; + case erDb.Cardinality.ZERO_OR_ONE_TO_ONE_OR_MORE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')'); + break; + case erDb.Cardinality.ONE_OR_MORE_TO_ONLY_ONE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')'); + break; + case erDb.Cardinality.ZERO_OR_MORE_TO_ONLY_ONE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')'); + break; + case erDb.Cardinality.ZERO_OR_MORE_TO_ZERO_OR_ONE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')'); + break; + case erDb.Cardinality.ONE_OR_MORE_TO_ZERO_OR_ONE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')'); + break; + case erDb.Cardinality.ZERO_OR_ONE_TO_ONLY_ONE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')'); + break; + case erDb.Cardinality.ONLY_ONE_TO_ONLY_ONE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')'); + break; + case erDb.Cardinality.ONLY_ONE_TO_ZERO_OR_ONE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')'); + break; + case erDb.Cardinality.ZERO_OR_ONE_TO_ZERO_OR_ONE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')'); + break; + case erDb.Cardinality.ZERO_OR_MORE_TO_ZERO_OR_MORE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')'); + break; + case erDb.Cardinality.ZERO_OR_MORE_TO_ONE_OR_MORE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')'); + break; + case erDb.Cardinality.ONE_OR_MORE_TO_ZERO_OR_MORE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')'); + break; + case erDb.Cardinality.ONE_OR_MORE_TO_ONE_OR_MORE: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')'); + break; + } +}; + const drawRelationship = function(diagram, relationship, g) { // Set the from and to co-ordinates using the graph vertices @@ -406,7 +516,7 @@ export const draw = function(text, id) { const diagram = d3.select(`[id='${id}']`); // Add cardinality 'marker' definitions to the svg - //insertMarkers(diagram); + erMarkers.insertMarkers(diagram, conf); // Create the graph let g; @@ -451,8 +561,8 @@ export const draw = function(text, id) { //const element = d3.select('#' + id + ' g'); //render(element, g); + //drawFeet(diagram, relationships, g); drawRelationships(diagram, relationships, g); - drawFeet(diagram, relationships, g); drawEntities(diagram, entities, g, id); const padding = 8; From 29b6e00071e492a6ac21838086f8641d3530d08e Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Mon, 9 Mar 2020 12:36:43 +0000 Subject: [PATCH 63/95] clean up for eslint --- src/diagrams/er/erMarkers.js | 3 +- src/diagrams/er/erRenderer.js | 98 +++++++++++++++++++++++------------ 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/src/diagrams/er/erMarkers.js b/src/diagrams/er/erMarkers.js index af9edbc9c..96933693b 100644 --- a/src/diagrams/er/erMarkers.js +++ b/src/diagrams/er/erMarkers.js @@ -1,4 +1,4 @@ -import * as d3 from 'd3'; +//import * as d3 from 'd3'; const ERMarkers = { ONLY_ONE_START: 'ONLY_ONE_START', @@ -20,7 +20,6 @@ const ERMarkers = { const insertMarkers = function(elem, conf) { let marker; - const markerWidth = elem .append('defs') .append('marker') diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index ba010dab7..858f6b9cf 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -110,7 +110,7 @@ const drawRelationshipFromLayout = function(diagram, rel, g) { const edge = g.edge({ v: rel.entityA, w: rel.entityB }); // Using it's points, generate a line function - edge.points = edge.points.filter(p => !Number.isNaN(p.y)); // TODO: why is necessary? + edge.points = edge.points.filter(p => !Number.isNaN(p.y)); // TODO: why is necessary? // Get a function that will generate the line path const lineFunction = d3 @@ -146,7 +146,7 @@ const drawRelationshipFromLayout = function(diagram, rel, g) { // TODO: change the way enums are imported // Decide which start and end markers it needs switch (rel.cardinality) { - case erDb.Cardinality.ONLY_ONE_TO_ONE_OR_MORE: + case erDb.Cardinality.ONLY_ONE_TO_ONE_OR_MORE: svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')'); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')'); break; @@ -155,31 +155,52 @@ const drawRelationshipFromLayout = function(diagram, rel, g) { svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')'); break; case erDb.Cardinality.ZERO_OR_ONE_TO_ZERO_OR_MORE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')' + ); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')'); break; case erDb.Cardinality.ZERO_OR_ONE_TO_ONE_OR_MORE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')' + ); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')'); break; case erDb.Cardinality.ONE_OR_MORE_TO_ONLY_ONE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')' + ); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')'); break; case erDb.Cardinality.ZERO_OR_MORE_TO_ONLY_ONE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')' + ); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')'); break; case erDb.Cardinality.ZERO_OR_MORE_TO_ZERO_OR_ONE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')' + ); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')'); break; case erDb.Cardinality.ONE_OR_MORE_TO_ZERO_OR_ONE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')' + ); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')'); break; case erDb.Cardinality.ZERO_OR_ONE_TO_ONLY_ONE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')' + ); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')'); break; case erDb.Cardinality.ONLY_ONE_TO_ONLY_ONE: @@ -191,28 +212,44 @@ const drawRelationshipFromLayout = function(diagram, rel, g) { svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')'); break; case erDb.Cardinality.ZERO_OR_ONE_TO_ZERO_OR_ONE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')' + ); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')'); break; case erDb.Cardinality.ZERO_OR_MORE_TO_ZERO_OR_MORE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')' + ); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')'); break; case erDb.Cardinality.ZERO_OR_MORE_TO_ONE_OR_MORE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')' + ); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')'); break; case erDb.Cardinality.ONE_OR_MORE_TO_ZERO_OR_MORE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')' + ); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')'); break; case erDb.Cardinality.ONE_OR_MORE_TO_ONE_OR_MORE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')' + ); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')'); break; } }; +/* const drawRelationship = function(diagram, relationship, g) { // Set the from and to co-ordinates using the graph vertices @@ -234,7 +271,9 @@ const drawRelationship = function(diagram, relationship, g) { .attr('y2', to.y) .attr('stroke', conf.stroke); }; // drawRelationship +*/ +/* const drawFeet = function(diagram, relationships, g) { relationships.forEach(function(rel) { // Get the points of intersection with the entities @@ -307,7 +346,8 @@ const getToePath = function(heel, toe, tip) { return `M ${heel.x} ${heel.y} Q ${toe.x} ${toe.y} ${tip.x} ${tip.y}`; } }; - +*/ +/* const getToes = function(relationship, fromPoint, toPoint, distance) { if (conf.toeStyle === 'curved') { distance *= 2; @@ -347,8 +387,8 @@ const getToes = function(relationship, fromPoint, toPoint, distance) { from: fromPoint.x < toPoint.x ? upper : lower }; } - - /* +*/ +/* if (fromPoint.x < toPoint.x) { // Scenario A @@ -377,9 +417,11 @@ const getToes = function(relationship, fromPoint, toPoint, distance) { } else { // Scenario E } - */ +*/ +/* }; // getToes - +*/ +/* const getJoints = function(relationship, fromPoint, toPoint, distance) { const gradient = (fromPoint.y - toPoint.y) / (fromPoint.x - toPoint.x); let jointXDelta = getXDelta(distance, gradient); @@ -453,21 +495,9 @@ const getJoints = function(relationship, fromPoint, toPoint, distance) { to: { x: toX, y: toY } }; }; +*/ -// Calculate point pXDelta w.r.t. an intersect point - -// Calcualate point pYDelta w.r.t. an intersect point - -// Calculate point qXDelta w.r.t. an intersect point - -// Calculate point qYDelta w.r.t. an intersect point - -// Now draw from the heel to point P then to the centre of the target entity - -// Now do the same again using point Q instead of P - -// Now draw the ankle - +/* const getXDelta = function(hypotenuse, gradient) { return Math.sqrt((hypotenuse * hypotenuse) / (Math.abs(gradient) + 1)); }; @@ -493,7 +523,7 @@ const dot = function(diagram, p, color) { .attr('fill', color); } }; // dot - +*/ /** * Draw en E-R diagram in the tag with id: id based on the text definition of the graph * @param text From b0852222530fc441ca9402399ad4ac5147ed4540 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Mon, 9 Mar 2020 22:55:03 -0700 Subject: [PATCH 64/95] Delete README.md --- docs/README.md | 239 ------------------------------------------------- 1 file changed, 239 deletions(-) delete mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 4e3f71c02..000000000 --- a/docs/README.md +++ /dev/null @@ -1,239 +0,0 @@ -# Mermaid - -[![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) -[![Coverage Status](https://coveralls.io/repos/github/knsv/mermaid/badge.svg?branch=master)](https://coveralls.io/github/knsv/mermaid?branch=master) -[![Join the chat at https://gitter.im/knsv/mermaid](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -![banner](./img/header.png) -# What is mermaid? - -It is a simple markdown-inspired, script language for diagramming and charting that can be deployed via Javascipt. - -The reasoning behind it's inception was a need for simplified documentation and frustration with heavy and time consuming tools for explaining code, like Visio. - -Thus mermaid was created to make the process of diagramming and charting, as simple and as close to markdown, as possible. - -Check out the list of [Integrations and Usages of Mermaid](./integrations.md) -For the uninitiated, you can also try out our [live editor](https://mermaid-js.github.io/mermaid-live-editor/) and look at our [documentation](https://mermaid-js.github.io/mermaid/#/n00b-overview) for a deeper understanding of the tool itself, more importantly don't forget to have fun. - -**Mermaid was nominated and won the JS Open Source Awards (2019) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** - -## New diagrams in 8.4 - -With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. - -![Image show the two new diagram types](./img/new-diagrams.png) - -## Special note regarding version 8.2 - -In version 8.2 a security improvement was introduced. A securityLevel configuration was introduced which sets the level of trust to be used on the parsed diagrams. - -- **true**: (default) tags in text are encoded, click functionality is disabled -- false: tags in text are allowed, click functionality is enabled - -Closed issues: - -⚠️ **Note** : This changes the default behaviour of mermaid so that after upgrade to 8.2, if the securityLevel is not configured, tags in flowcharts are encoded as tags and clicking is prohibited. - -If your application is taking resposibility for the diagram source security you can set the securityLevel accordingly. By doing this clicks and tags are again allowed. - -```javascript -mermaidAPI.initialize({ - securityLevel: 'loose' -}); -``` - -**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/k3nsv/mermaid/issues/866)** - -## The following are examples of Diagrams that mermaid can make: - -### Flowchart - -``` -graph TD; - A-->B; - A-->C; - B-->D; - C-->D; -``` - -![Flowchart](./img/flow.png) - -### Sequence diagram - -``` -sequenceDiagram - participant Alice - participant Bob - Alice->>John: Hello John, how are you? - loop Healthcheck - John->>John: Fight against hypochondria - end - Note right of John: Rational thoughts
prevail! - John-->>Alice: Great! - John->>Bob: How about you? - Bob-->>John: Jolly good! -``` - -![Sequence diagram](./img/sequence.png) - -### Gantt diagram - -``` -gantt -dateFormat YYYY-MM-DD -title Adding GANTT diagram to mermaid -excludes weekdays 2014-01-10 - -section A section -Completed task :done, des1, 2014-01-06,2014-01-08 -Active task :active, des2, 2014-01-09, 3d -Future task : des3, after des2, 5d -Future task2 : des4, after des3, 5d -``` - -![Gantt diagram](./img/gantt.png) - -### Class diagram - :exclamation: experimental - -``` -classDiagram -Class01 <|-- AveryLongClass : Cool -Class03 *-- Class04 -Class05 o-- Class06 -Class07 .. Class08 -Class09 --> C2 : Where am i? -Class09 --* C3 -Class09 --|> Class07 -Class07 : equals() -Class07 : Object[] elementData -Class01 : size() -Class01 : int chimp -Class01 : int gorilla -Class08 <--> C2: Cool label -``` - -![Class diagram](./img/class.png) - -### Git graph - :exclamation: experimental - -``` -gitGraph: -options -{ - "nodeSpacing": 150, - "nodeRadius": 10 -} -end -commit -branch newbranch -checkout newbranch -commit -commit -checkout master -commit -commit -merge newbranch - -``` - -![Git graph](./img/git.png) - -## Installation - -### CDN - -``` -https://unpkg.com/mermaid@/dist/ -``` - -Replace `` with expected version number. - -Example: https://unpkg.com/mermaid@7.1.0/dist/ - -### Node.js - -``` -yarn add mermaid -``` - -## Documentation - -https://mermaidjs.github.io - -## Sibling projects - -- [mermaid CLI](https://github.com/mermaidjs/mermaid.cli) -- [mermaid live editor](https://github.com/mermaidjs/mermaid-live-editor) -- [mermaid webpack demo](https://github.com/mermaidjs/mermaid-webpack-demo) -- [mermaid Parcel demo](https://github.com/mermaidjs/mermaid-parcel-demo) - -## Request for assistance - -Things are piling up and I have a hard time keeping up. To remedy this -it would be great if we could form a core team of developers to cooperate -with the future development of mermaid. - -As part of this team you would get write access to the repository and would -represent the project when answering questions and issues. - -Together we could continue the work with things like: - -- Adding more types of diagrams like mindmaps, ert diagrams, etc. -- Improving existing diagrams - -Don't hesitate to contact me if you want to get involved. - -## For contributors - -### Setup - -``` -yarn install -``` - -### Build - -``` -yarn build:watch -``` - -### Lint - -``` -yarn lint -``` - -We use [eslint](https://eslint.org/). -We recommend you installing [editor plugins](https://eslint.org/docs/user-guide/integrations) so you can get real time lint result. - -### Test - -``` -yarn test -``` -Manual test in browser: open `dist/index.html` - -### Release - -For those who have the permission to do so: - -Update version number in `package.json`. - -``` -npm publish -``` - -Command above generates files into the `dist` folder and publishes them to npmjs.org. - -## Credits - -Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries! - -Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering. - -_Mermaid was created by Knut Sveidqvist for easier documentation._ - -_[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017._ - -Here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors). From 71cfec91afbfdf1223a28d029a82568c841201df Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Mon, 9 Mar 2020 23:02:14 -0700 Subject: [PATCH 65/95] Update README.md --- README.md | 344 +++++++++++++++++++++++++++++------------------------- 1 file changed, 187 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index 781cee7e1..791ac115c 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,29 @@ - -| :mega: :mega: :mega: | -| :----: | -| * If you're upgrading from a version __< v8.2.0__, there are [non-backward-compatible changes](http://mermaid-js.github.io/mermaid/#/usage?id=to-enable-click-event-and-tags-in-nodes) related to security issues. Default behaviour of the library might have changed for your implementation.| - -# mermaid [![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/Mermaid/mermaid) +# Mermaid - -__mermaid is a Javascript based diagramming and charting tool. It generates diagrams flowcharts and more, using markdown-inspired text for ease and speed.__ +[![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) +[![Coverage Status](https://coveralls.io/repos/github/knsv/mermaid/badge.svg?branch=master)](https://coveralls.io/github/knsv/mermaid?branch=master) +[![Join the chat at https://gitter.im/knsv/mermaid](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Check out the list of [Integrations and Usages of Mermaid](https://github.com/mermaid-js/mermaid/blob/develop/docs/integrations.md) +![banner](./img/header.png) +# What is mermaid? -For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Alternatively, you can also play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/). - +It is a simple markdown-inspired, script language for diagramming and charting that can be deployed via Javascipt. -:trophy: **Mermaid was nominated and won the [JS Open Source Awards (2019)](https://osawards.com/javascript/#nominees) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** +The reasoning behind it's inception was a need for simplified documentation and frustration with heavy and time consuming tools for explaining code, like Visio. + +Thus mermaid was created to make the process of diagramming and charting, as simple and as close to markdown, as possible. + +Check out the list of [Integrations and Usages of Mermaid](./integrations.md) +For the uninitiated, you can also try out our [live editor](https://mermaid-js.github.io/mermaid-live-editor/) and look at our [documentation](https://mermaid-js.github.io/mermaid/#/n00b-overview) for a deeper understanding of the tool itself, more importantly don't forget to have fun. + +**Mermaid was nominated and won the JS Open Source Awards (2019) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** ## New diagrams in 8.4 With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. -![Image show the two new diagram types](.docs/img/new-diagrams.png) +![Image show the two new diagram types](./img/new-diagrams.png) ## Special note regarding version 8.2 @@ -41,82 +44,64 @@ mermaidAPI.initialize({ }); ``` -For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/) or jump straight to the [installation and usage](http://mermaid-js.github.io/mermaid/#/usage). - +**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/k3nsv/mermaid/issues/866)** -__The following are some examples of the diagrams, charts and graphs that can be made using mermaid and the Markdown-inspired text specific to it. Click here jump into the [text syntax](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference).__ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Flow
- [docs - live editor] -
-graph TD
-A[Hard] -->|Text| B(Round)
-B --> C{Decision}
-C -->|One| D[Result 1]
-C -->|Two| E[Result 2]
-    
- -
- Sequence
- [docs - live editor] -
+## The following are examples of Diagrams that mermaid can make:
+
+### Flowchart
+
+```
+graph TD;
+    A-->B;
+    A-->C;
+    B-->D;
+    C-->D;
+```
+
+![Flowchart](./img/flow.png)
+
+### Sequence diagram
+
+```
 sequenceDiagram
-Alice->>John: Hello John, how are you?
-loop Healthcheck
-    John->>John: Fight against hypochondria
-end
-Note right of John: Rational thoughts!
-John-->>Alice: Great!
-John->>Bob: How about you?
-Bob-->>John: Jolly good!
-    
- -
- Gantt
- [docs - live editor] -
+    participant Alice
+    participant Bob
+    Alice->>John: Hello John, how are you?
+    loop Healthcheck
+        John->>John: Fight against hypochondria
+    end
+    Note right of John: Rational thoughts 
prevail! + John-->>Alice: Great! + John->>Bob: How about you? + Bob-->>John: Jolly good! +``` + +![Sequence diagram](./img/sequence.png) + +### Gantt diagram + +``` gantt -section Section -Completed :done, des1, 2014-01-06,2014-01-08 -Active :active, des2, 2014-01-07, 3d -Parallel 1 : des3, after des1, 1d -Parallel 2 : des4, after des1, 1d -Parallel 3 : des5, after des3, 1d -Parallel 4 : des6, after des4, 1d -
- -
- Class
- [docs - live editor] -
+dateFormat  YYYY-MM-DD
+title Adding GANTT diagram to mermaid
+excludes weekdays 2014-01-10
+section A section
+Completed task            :done,    des1, 2014-01-06,2014-01-08
+Active task               :active,  des2, 2014-01-09, 3d
+Future task               :         des3, after des2, 5d
+Future task2               :         des4, after des3, 5d
+```
+
+![Gantt diagram](./img/gantt.png)
+
+### Class diagram - :exclamation: experimental
+
+```
 classDiagram
-Class01 <|-- AveryLongClass : Cool
-<<interface>> Class01
+Class01 <|-- AveryLongClass : Cool
+Class03 *-- Class04
+Class05 o-- Class06
+Class07 .. Class08
 Class09 --> C2 : Where am i?
 Class09 --* C3
 Class09 --|> Class07
@@ -125,84 +110,129 @@ Class07 : Object[] elementData
 Class01 : size()
 Class01 : int chimp
 Class01 : int gorilla
-class Class10 {
-  <<service>>
-  int id
-  size()
+Class08 <--> C2: Cool label
+```
+
+![Class diagram](./img/class.png)
+
+### Git graph - :exclamation: experimental
+
+```
+gitGraph:
+options
+{
+    "nodeSpacing": 150,
+    "nodeRadius": 10
 }
-
- -
- State
- [docs - live editor] -
-stateDiagram
-[*] --> Still
-Still --> [*]
-Still --> Moving
-Moving --> Still
-Moving --> Crash
-Crash --> [*]
-
- -
- Pie
- [docs - live editor] -
-pie
-"Dogs" : 386
-"Cats" : 85
-"Rats" : 15 
-
- -
- Git
- [experimental - live editor] -
Coming soon!
+end +commit +branch newbranch +checkout newbranch +commit +commit +checkout master +commit +commit +merge newbranch +``` -## Related projects +![Git graph](./img/git.png) -- [Command Line Interface](https://github.com/mermaid-js/mermaid.cli) -- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor) +## Installation -# Contributors [![Help wanted](https://img.shields.io/github/labels/mermaid-js/mermaid/Help%20wanted!)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Help+wanted%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) +### CDN -Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out. +``` +https://unpkg.com/mermaid@/dist/ +``` -Detailed information about how to contribute can be found in the [contribution guide](CONTRIBUTING.md) +Replace `` with expected version number. -# Appreciation -A quick note from Knut Sveidqvist: ->*Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries!* ->*Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering.* ->*Thank you to [Tyler Long](https://github.com/tylerlong) who has been a collaborator since April 2017.* -> ->*Thank you to the ever-growing list of [contributors](https://github.com/knsv/mermaid/graphs/contributors) that brought the project this far!* +Example: https://unpkg.com/mermaid@7.1.0/dist/ ---- +### Node.js -*Mermaid was created by Knut Sveidqvist for easier documentation.* +``` +yarn add mermaid +``` + +## Documentation + +https://mermaidjs.github.io + +## Sibling projects + +- [mermaid CLI](https://github.com/mermaidjs/mermaid.cli) +- [mermaid live editor](https://github.com/mermaidjs/mermaid-live-editor) +- [mermaid webpack demo](https://github.com/mermaidjs/mermaid-webpack-demo) +- [mermaid Parcel demo](https://github.com/mermaidjs/mermaid-parcel-demo) + +## Request for assistance + +Things are piling up and I have a hard time keeping up. To remedy this +it would be great if we could form a core team of developers to cooperate +with the future development of mermaid. + +As part of this team you would get write access to the repository and would +represent the project when answering questions and issues. + +Together we could continue the work with things like: + +- Adding more types of diagrams like mindmaps, ert diagrams, etc. +- Improving existing diagrams + +Don't hesitate to contact me if you want to get involved. + +## For contributors + +### Setup + +``` +yarn install +``` + +### Build + +``` +yarn build:watch +``` + +### Lint + +``` +yarn lint +``` + +We use [eslint](https://eslint.org/). +We recommend you installing [editor plugins](https://eslint.org/docs/user-guide/integrations) so you can get real time lint result. + +### Test + +``` +yarn test +``` +Manual test in browser: open `dist/index.html` + +### Release + +For those who have the permission to do so: + +Update version number in `package.json`. + +``` +npm publish +``` + +Command above generates files into the `dist` folder and publishes them to npmjs.org. + +## Credits + +Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries! + +Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering. + +_Mermaid was created by Knut Sveidqvist for easier documentation._ + +_[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017._ + +Here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors). From c3e9ae0ca055eef0f1f944f616dd34bc7ae445f9 Mon Sep 17 00:00:00 2001 From: NeilCuzon Date: Mon, 9 Mar 2020 23:23:27 -0700 Subject: [PATCH 66/95] Revert "Delete README.md" This reverts commit b0852222530fc441ca9402399ad4ac5147ed4540. --- docs/README.md | 239 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..4e3f71c02 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,239 @@ +# Mermaid + +[![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) +[![Coverage Status](https://coveralls.io/repos/github/knsv/mermaid/badge.svg?branch=master)](https://coveralls.io/github/knsv/mermaid?branch=master) +[![Join the chat at https://gitter.im/knsv/mermaid](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +![banner](./img/header.png) +# What is mermaid? + +It is a simple markdown-inspired, script language for diagramming and charting that can be deployed via Javascipt. + +The reasoning behind it's inception was a need for simplified documentation and frustration with heavy and time consuming tools for explaining code, like Visio. + +Thus mermaid was created to make the process of diagramming and charting, as simple and as close to markdown, as possible. + +Check out the list of [Integrations and Usages of Mermaid](./integrations.md) +For the uninitiated, you can also try out our [live editor](https://mermaid-js.github.io/mermaid-live-editor/) and look at our [documentation](https://mermaid-js.github.io/mermaid/#/n00b-overview) for a deeper understanding of the tool itself, more importantly don't forget to have fun. + +**Mermaid was nominated and won the JS Open Source Awards (2019) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** + +## New diagrams in 8.4 + +With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. + +![Image show the two new diagram types](./img/new-diagrams.png) + +## Special note regarding version 8.2 + +In version 8.2 a security improvement was introduced. A securityLevel configuration was introduced which sets the level of trust to be used on the parsed diagrams. + +- **true**: (default) tags in text are encoded, click functionality is disabled +- false: tags in text are allowed, click functionality is enabled + +Closed issues: + +⚠️ **Note** : This changes the default behaviour of mermaid so that after upgrade to 8.2, if the securityLevel is not configured, tags in flowcharts are encoded as tags and clicking is prohibited. + +If your application is taking resposibility for the diagram source security you can set the securityLevel accordingly. By doing this clicks and tags are again allowed. + +```javascript +mermaidAPI.initialize({ + securityLevel: 'loose' +}); +``` + +**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/k3nsv/mermaid/issues/866)** + +## The following are examples of Diagrams that mermaid can make: + +### Flowchart + +``` +graph TD; + A-->B; + A-->C; + B-->D; + C-->D; +``` + +![Flowchart](./img/flow.png) + +### Sequence diagram + +``` +sequenceDiagram + participant Alice + participant Bob + Alice->>John: Hello John, how are you? + loop Healthcheck + John->>John: Fight against hypochondria + end + Note right of John: Rational thoughts
prevail! + John-->>Alice: Great! + John->>Bob: How about you? + Bob-->>John: Jolly good! +``` + +![Sequence diagram](./img/sequence.png) + +### Gantt diagram + +``` +gantt +dateFormat YYYY-MM-DD +title Adding GANTT diagram to mermaid +excludes weekdays 2014-01-10 + +section A section +Completed task :done, des1, 2014-01-06,2014-01-08 +Active task :active, des2, 2014-01-09, 3d +Future task : des3, after des2, 5d +Future task2 : des4, after des3, 5d +``` + +![Gantt diagram](./img/gantt.png) + +### Class diagram - :exclamation: experimental + +``` +classDiagram +Class01 <|-- AveryLongClass : Cool +Class03 *-- Class04 +Class05 o-- Class06 +Class07 .. Class08 +Class09 --> C2 : Where am i? +Class09 --* C3 +Class09 --|> Class07 +Class07 : equals() +Class07 : Object[] elementData +Class01 : size() +Class01 : int chimp +Class01 : int gorilla +Class08 <--> C2: Cool label +``` + +![Class diagram](./img/class.png) + +### Git graph - :exclamation: experimental + +``` +gitGraph: +options +{ + "nodeSpacing": 150, + "nodeRadius": 10 +} +end +commit +branch newbranch +checkout newbranch +commit +commit +checkout master +commit +commit +merge newbranch + +``` + +![Git graph](./img/git.png) + +## Installation + +### CDN + +``` +https://unpkg.com/mermaid@/dist/ +``` + +Replace `` with expected version number. + +Example: https://unpkg.com/mermaid@7.1.0/dist/ + +### Node.js + +``` +yarn add mermaid +``` + +## Documentation + +https://mermaidjs.github.io + +## Sibling projects + +- [mermaid CLI](https://github.com/mermaidjs/mermaid.cli) +- [mermaid live editor](https://github.com/mermaidjs/mermaid-live-editor) +- [mermaid webpack demo](https://github.com/mermaidjs/mermaid-webpack-demo) +- [mermaid Parcel demo](https://github.com/mermaidjs/mermaid-parcel-demo) + +## Request for assistance + +Things are piling up and I have a hard time keeping up. To remedy this +it would be great if we could form a core team of developers to cooperate +with the future development of mermaid. + +As part of this team you would get write access to the repository and would +represent the project when answering questions and issues. + +Together we could continue the work with things like: + +- Adding more types of diagrams like mindmaps, ert diagrams, etc. +- Improving existing diagrams + +Don't hesitate to contact me if you want to get involved. + +## For contributors + +### Setup + +``` +yarn install +``` + +### Build + +``` +yarn build:watch +``` + +### Lint + +``` +yarn lint +``` + +We use [eslint](https://eslint.org/). +We recommend you installing [editor plugins](https://eslint.org/docs/user-guide/integrations) so you can get real time lint result. + +### Test + +``` +yarn test +``` +Manual test in browser: open `dist/index.html` + +### Release + +For those who have the permission to do so: + +Update version number in `package.json`. + +``` +npm publish +``` + +Command above generates files into the `dist` folder and publishes them to npmjs.org. + +## Credits + +Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries! + +Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering. + +_Mermaid was created by Knut Sveidqvist for easier documentation._ + +_[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017._ + +Here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors). From d28e3374c77798b1f83330c977265311bd393ce6 Mon Sep 17 00:00:00 2001 From: NeilCuzon Date: Mon, 9 Mar 2020 23:23:44 -0700 Subject: [PATCH 67/95] Revert "Update README.md" This reverts commit 71cfec91afbfdf1223a28d029a82568c841201df. --- README.md | 344 +++++++++++++++++++++++++----------------------------- 1 file changed, 157 insertions(+), 187 deletions(-) diff --git a/README.md b/README.md index 791ac115c..781cee7e1 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,26 @@ + +| :mega: :mega: :mega: | +| :----: | +| * If you're upgrading from a version __< v8.2.0__, there are [non-backward-compatible changes](http://mermaid-js.github.io/mermaid/#/usage?id=to-enable-click-event-and-tags-in-nodes) related to security issues. Default behaviour of the library might have changed for your implementation.| + -# Mermaid +# mermaid [![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/Mermaid/mermaid) -[![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) -[![Coverage Status](https://coveralls.io/repos/github/knsv/mermaid/badge.svg?branch=master)](https://coveralls.io/github/knsv/mermaid?branch=master) -[![Join the chat at https://gitter.im/knsv/mermaid](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +__mermaid is a Javascript based diagramming and charting tool. It generates diagrams flowcharts and more, using markdown-inspired text for ease and speed.__ -![banner](./img/header.png) -# What is mermaid? +Check out the list of [Integrations and Usages of Mermaid](https://github.com/mermaid-js/mermaid/blob/develop/docs/integrations.md) -It is a simple markdown-inspired, script language for diagramming and charting that can be deployed via Javascipt. +For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Alternatively, you can also play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/). + -The reasoning behind it's inception was a need for simplified documentation and frustration with heavy and time consuming tools for explaining code, like Visio. - -Thus mermaid was created to make the process of diagramming and charting, as simple and as close to markdown, as possible. - -Check out the list of [Integrations and Usages of Mermaid](./integrations.md) -For the uninitiated, you can also try out our [live editor](https://mermaid-js.github.io/mermaid-live-editor/) and look at our [documentation](https://mermaid-js.github.io/mermaid/#/n00b-overview) for a deeper understanding of the tool itself, more importantly don't forget to have fun. - -**Mermaid was nominated and won the JS Open Source Awards (2019) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** +:trophy: **Mermaid was nominated and won the [JS Open Source Awards (2019)](https://osawards.com/javascript/#nominees) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** ## New diagrams in 8.4 With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. -![Image show the two new diagram types](./img/new-diagrams.png) +![Image show the two new diagram types](.docs/img/new-diagrams.png) ## Special note regarding version 8.2 @@ -44,64 +41,82 @@ mermaidAPI.initialize({ }); ``` -**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/k3nsv/mermaid/issues/866)** +For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/) or jump straight to the [installation and usage](http://mermaid-js.github.io/mermaid/#/usage). + -## The following are examples of Diagrams that mermaid can make: - -### Flowchart - -``` -graph TD; - A-->B; - A-->C; - B-->D; - C-->D; -``` - -![Flowchart](./img/flow.png) - -### Sequence diagram - -``` +__The following are some examples of the diagrams, charts and graphs that can be made using mermaid and the Markdown-inspired text specific to it. Click here jump into the [text syntax](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference).__ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Flow
+ [docs - live editor] +
+graph TD
+A[Hard] -->|Text| B(Round)
+B --> C{Decision}
+C -->|One| D[Result 1]
+C -->|Two| E[Result 2]
+    
+ +
+ Sequence
+ [docs - live editor] +
 sequenceDiagram
-    participant Alice
-    participant Bob
-    Alice->>John: Hello John, how are you?
-    loop Healthcheck
-        John->>John: Fight against hypochondria
-    end
-    Note right of John: Rational thoughts 
prevail! - John-->>Alice: Great! - John->>Bob: How about you? - Bob-->>John: Jolly good! -``` - -![Sequence diagram](./img/sequence.png) - -### Gantt diagram - -``` +Alice->>John: Hello John, how are you? +loop Healthcheck + John->>John: Fight against hypochondria +end +Note right of John: Rational thoughts! +John-->>Alice: Great! +John->>Bob: How about you? +Bob-->>John: Jolly good! +
+ +
+ Gantt
+ [docs - live editor] +
 gantt
-dateFormat  YYYY-MM-DD
-title Adding GANTT diagram to mermaid
-excludes weekdays 2014-01-10
-section A section
-Completed task            :done,    des1, 2014-01-06,2014-01-08
-Active task               :active,  des2, 2014-01-09, 3d
-Future task               :         des3, after des2, 5d
-Future task2               :         des4, after des3, 5d
-```
-
-![Gantt diagram](./img/gantt.png)
-
-### Class diagram - :exclamation: experimental
-
-```
+section Section
+Completed :done,    des1, 2014-01-06,2014-01-08
+Active        :active,  des2, 2014-01-07, 3d
+Parallel 1   :         des3, after des1, 1d
+Parallel 2   :         des4, after des1, 1d
+Parallel 3   :         des5, after des3, 1d
+Parallel 4   :         des6, after des4, 1d
+    
+ +
+ Class
+ [docs - live editor] +
 classDiagram
-Class01 <|-- AveryLongClass : Cool
-Class03 *-- Class04
-Class05 o-- Class06
-Class07 .. Class08
+Class01 <|-- AveryLongClass : Cool
+<<interface>> Class01
 Class09 --> C2 : Where am i?
 Class09 --* C3
 Class09 --|> Class07
@@ -110,129 +125,84 @@ Class07 : Object[] elementData
 Class01 : size()
 Class01 : int chimp
 Class01 : int gorilla
-Class08 <--> C2: Cool label
-```
-
-![Class diagram](./img/class.png)
-
-### Git graph - :exclamation: experimental
-
-```
-gitGraph:
-options
-{
-    "nodeSpacing": 150,
-    "nodeRadius": 10
+class Class10 {
+  <<service>>
+  int id
+  size()
 }
-end
-commit
-branch newbranch
-checkout newbranch
-commit
-commit
-checkout master
-commit
-commit
-merge newbranch
-```
+
+ +
+ State
+ [docs - live editor] +
+stateDiagram
+[*] --> Still
+Still --> [*]
+Still --> Moving
+Moving --> Still
+Moving --> Crash
+Crash --> [*]
+
+ +
+ Pie
+ [docs - live editor] +
+pie
+"Dogs" : 386
+"Cats" : 85
+"Rats" : 15 
+
+ +
+ Git
+ [experimental - live editor] +
Coming soon!
-![Git graph](./img/git.png) +## Related projects -## Installation +- [Command Line Interface](https://github.com/mermaid-js/mermaid.cli) +- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor) -### CDN +# Contributors [![Help wanted](https://img.shields.io/github/labels/mermaid-js/mermaid/Help%20wanted!)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Help+wanted%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) -``` -https://unpkg.com/mermaid@/dist/ -``` +Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out. -Replace `` with expected version number. +Detailed information about how to contribute can be found in the [contribution guide](CONTRIBUTING.md) -Example: https://unpkg.com/mermaid@7.1.0/dist/ +# Appreciation +A quick note from Knut Sveidqvist: +>*Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries!* +>*Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering.* +>*Thank you to [Tyler Long](https://github.com/tylerlong) who has been a collaborator since April 2017.* +> +>*Thank you to the ever-growing list of [contributors](https://github.com/knsv/mermaid/graphs/contributors) that brought the project this far!* -### Node.js +--- -``` -yarn add mermaid -``` - -## Documentation - -https://mermaidjs.github.io - -## Sibling projects - -- [mermaid CLI](https://github.com/mermaidjs/mermaid.cli) -- [mermaid live editor](https://github.com/mermaidjs/mermaid-live-editor) -- [mermaid webpack demo](https://github.com/mermaidjs/mermaid-webpack-demo) -- [mermaid Parcel demo](https://github.com/mermaidjs/mermaid-parcel-demo) - -## Request for assistance - -Things are piling up and I have a hard time keeping up. To remedy this -it would be great if we could form a core team of developers to cooperate -with the future development of mermaid. - -As part of this team you would get write access to the repository and would -represent the project when answering questions and issues. - -Together we could continue the work with things like: - -- Adding more types of diagrams like mindmaps, ert diagrams, etc. -- Improving existing diagrams - -Don't hesitate to contact me if you want to get involved. - -## For contributors - -### Setup - -``` -yarn install -``` - -### Build - -``` -yarn build:watch -``` - -### Lint - -``` -yarn lint -``` - -We use [eslint](https://eslint.org/). -We recommend you installing [editor plugins](https://eslint.org/docs/user-guide/integrations) so you can get real time lint result. - -### Test - -``` -yarn test -``` -Manual test in browser: open `dist/index.html` - -### Release - -For those who have the permission to do so: - -Update version number in `package.json`. - -``` -npm publish -``` - -Command above generates files into the `dist` folder and publishes them to npmjs.org. - -## Credits - -Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries! - -Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering. - -_Mermaid was created by Knut Sveidqvist for easier documentation._ - -_[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017._ - -Here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors). +*Mermaid was created by Knut Sveidqvist for easier documentation.* From a8eeb7f76a082097b6c14a504a9ae9d6095cc780 Mon Sep 17 00:00:00 2001 From: NeilCuzon Date: Mon, 9 Mar 2020 23:25:19 -0700 Subject: [PATCH 68/95] Revert "Revert "Delete README.md"" This reverts commit c3e9ae0ca055eef0f1f944f616dd34bc7ae445f9. --- docs/README.md | 239 ------------------------------------------------- 1 file changed, 239 deletions(-) delete mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 4e3f71c02..000000000 --- a/docs/README.md +++ /dev/null @@ -1,239 +0,0 @@ -# Mermaid - -[![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) -[![Coverage Status](https://coveralls.io/repos/github/knsv/mermaid/badge.svg?branch=master)](https://coveralls.io/github/knsv/mermaid?branch=master) -[![Join the chat at https://gitter.im/knsv/mermaid](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -![banner](./img/header.png) -# What is mermaid? - -It is a simple markdown-inspired, script language for diagramming and charting that can be deployed via Javascipt. - -The reasoning behind it's inception was a need for simplified documentation and frustration with heavy and time consuming tools for explaining code, like Visio. - -Thus mermaid was created to make the process of diagramming and charting, as simple and as close to markdown, as possible. - -Check out the list of [Integrations and Usages of Mermaid](./integrations.md) -For the uninitiated, you can also try out our [live editor](https://mermaid-js.github.io/mermaid-live-editor/) and look at our [documentation](https://mermaid-js.github.io/mermaid/#/n00b-overview) for a deeper understanding of the tool itself, more importantly don't forget to have fun. - -**Mermaid was nominated and won the JS Open Source Awards (2019) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** - -## New diagrams in 8.4 - -With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. - -![Image show the two new diagram types](./img/new-diagrams.png) - -## Special note regarding version 8.2 - -In version 8.2 a security improvement was introduced. A securityLevel configuration was introduced which sets the level of trust to be used on the parsed diagrams. - -- **true**: (default) tags in text are encoded, click functionality is disabled -- false: tags in text are allowed, click functionality is enabled - -Closed issues: - -⚠️ **Note** : This changes the default behaviour of mermaid so that after upgrade to 8.2, if the securityLevel is not configured, tags in flowcharts are encoded as tags and clicking is prohibited. - -If your application is taking resposibility for the diagram source security you can set the securityLevel accordingly. By doing this clicks and tags are again allowed. - -```javascript -mermaidAPI.initialize({ - securityLevel: 'loose' -}); -``` - -**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/k3nsv/mermaid/issues/866)** - -## The following are examples of Diagrams that mermaid can make: - -### Flowchart - -``` -graph TD; - A-->B; - A-->C; - B-->D; - C-->D; -``` - -![Flowchart](./img/flow.png) - -### Sequence diagram - -``` -sequenceDiagram - participant Alice - participant Bob - Alice->>John: Hello John, how are you? - loop Healthcheck - John->>John: Fight against hypochondria - end - Note right of John: Rational thoughts
prevail! - John-->>Alice: Great! - John->>Bob: How about you? - Bob-->>John: Jolly good! -``` - -![Sequence diagram](./img/sequence.png) - -### Gantt diagram - -``` -gantt -dateFormat YYYY-MM-DD -title Adding GANTT diagram to mermaid -excludes weekdays 2014-01-10 - -section A section -Completed task :done, des1, 2014-01-06,2014-01-08 -Active task :active, des2, 2014-01-09, 3d -Future task : des3, after des2, 5d -Future task2 : des4, after des3, 5d -``` - -![Gantt diagram](./img/gantt.png) - -### Class diagram - :exclamation: experimental - -``` -classDiagram -Class01 <|-- AveryLongClass : Cool -Class03 *-- Class04 -Class05 o-- Class06 -Class07 .. Class08 -Class09 --> C2 : Where am i? -Class09 --* C3 -Class09 --|> Class07 -Class07 : equals() -Class07 : Object[] elementData -Class01 : size() -Class01 : int chimp -Class01 : int gorilla -Class08 <--> C2: Cool label -``` - -![Class diagram](./img/class.png) - -### Git graph - :exclamation: experimental - -``` -gitGraph: -options -{ - "nodeSpacing": 150, - "nodeRadius": 10 -} -end -commit -branch newbranch -checkout newbranch -commit -commit -checkout master -commit -commit -merge newbranch - -``` - -![Git graph](./img/git.png) - -## Installation - -### CDN - -``` -https://unpkg.com/mermaid@/dist/ -``` - -Replace `` with expected version number. - -Example: https://unpkg.com/mermaid@7.1.0/dist/ - -### Node.js - -``` -yarn add mermaid -``` - -## Documentation - -https://mermaidjs.github.io - -## Sibling projects - -- [mermaid CLI](https://github.com/mermaidjs/mermaid.cli) -- [mermaid live editor](https://github.com/mermaidjs/mermaid-live-editor) -- [mermaid webpack demo](https://github.com/mermaidjs/mermaid-webpack-demo) -- [mermaid Parcel demo](https://github.com/mermaidjs/mermaid-parcel-demo) - -## Request for assistance - -Things are piling up and I have a hard time keeping up. To remedy this -it would be great if we could form a core team of developers to cooperate -with the future development of mermaid. - -As part of this team you would get write access to the repository and would -represent the project when answering questions and issues. - -Together we could continue the work with things like: - -- Adding more types of diagrams like mindmaps, ert diagrams, etc. -- Improving existing diagrams - -Don't hesitate to contact me if you want to get involved. - -## For contributors - -### Setup - -``` -yarn install -``` - -### Build - -``` -yarn build:watch -``` - -### Lint - -``` -yarn lint -``` - -We use [eslint](https://eslint.org/). -We recommend you installing [editor plugins](https://eslint.org/docs/user-guide/integrations) so you can get real time lint result. - -### Test - -``` -yarn test -``` -Manual test in browser: open `dist/index.html` - -### Release - -For those who have the permission to do so: - -Update version number in `package.json`. - -``` -npm publish -``` - -Command above generates files into the `dist` folder and publishes them to npmjs.org. - -## Credits - -Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries! - -Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering. - -_Mermaid was created by Knut Sveidqvist for easier documentation._ - -_[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017._ - -Here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors). From c598fd9ec4a14f38c789f741232f6d40fdb031b0 Mon Sep 17 00:00:00 2001 From: NeilCuzon Date: Mon, 9 Mar 2020 23:25:24 -0700 Subject: [PATCH 69/95] Revert "Revert "Update README.md"" This reverts commit d28e3374c77798b1f83330c977265311bd393ce6. --- README.md | 344 +++++++++++++++++++++++++++++------------------------- 1 file changed, 187 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index 781cee7e1..791ac115c 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,29 @@ - -| :mega: :mega: :mega: | -| :----: | -| * If you're upgrading from a version __< v8.2.0__, there are [non-backward-compatible changes](http://mermaid-js.github.io/mermaid/#/usage?id=to-enable-click-event-and-tags-in-nodes) related to security issues. Default behaviour of the library might have changed for your implementation.| - -# mermaid [![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/Mermaid/mermaid) +# Mermaid - -__mermaid is a Javascript based diagramming and charting tool. It generates diagrams flowcharts and more, using markdown-inspired text for ease and speed.__ +[![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) +[![Coverage Status](https://coveralls.io/repos/github/knsv/mermaid/badge.svg?branch=master)](https://coveralls.io/github/knsv/mermaid?branch=master) +[![Join the chat at https://gitter.im/knsv/mermaid](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Check out the list of [Integrations and Usages of Mermaid](https://github.com/mermaid-js/mermaid/blob/develop/docs/integrations.md) +![banner](./img/header.png) +# What is mermaid? -For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Alternatively, you can also play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/). - +It is a simple markdown-inspired, script language for diagramming and charting that can be deployed via Javascipt. -:trophy: **Mermaid was nominated and won the [JS Open Source Awards (2019)](https://osawards.com/javascript/#nominees) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** +The reasoning behind it's inception was a need for simplified documentation and frustration with heavy and time consuming tools for explaining code, like Visio. + +Thus mermaid was created to make the process of diagramming and charting, as simple and as close to markdown, as possible. + +Check out the list of [Integrations and Usages of Mermaid](./integrations.md) +For the uninitiated, you can also try out our [live editor](https://mermaid-js.github.io/mermaid-live-editor/) and look at our [documentation](https://mermaid-js.github.io/mermaid/#/n00b-overview) for a deeper understanding of the tool itself, more importantly don't forget to have fun. + +**Mermaid was nominated and won the JS Open Source Awards (2019) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** ## New diagrams in 8.4 With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. -![Image show the two new diagram types](.docs/img/new-diagrams.png) +![Image show the two new diagram types](./img/new-diagrams.png) ## Special note regarding version 8.2 @@ -41,82 +44,64 @@ mermaidAPI.initialize({ }); ``` -For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/) or jump straight to the [installation and usage](http://mermaid-js.github.io/mermaid/#/usage). - +**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/k3nsv/mermaid/issues/866)** -__The following are some examples of the diagrams, charts and graphs that can be made using mermaid and the Markdown-inspired text specific to it. Click here jump into the [text syntax](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference).__ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Flow
- [docs - live editor] -
-graph TD
-A[Hard] -->|Text| B(Round)
-B --> C{Decision}
-C -->|One| D[Result 1]
-C -->|Two| E[Result 2]
-    
- -
- Sequence
- [docs - live editor] -
+## The following are examples of Diagrams that mermaid can make:
+
+### Flowchart
+
+```
+graph TD;
+    A-->B;
+    A-->C;
+    B-->D;
+    C-->D;
+```
+
+![Flowchart](./img/flow.png)
+
+### Sequence diagram
+
+```
 sequenceDiagram
-Alice->>John: Hello John, how are you?
-loop Healthcheck
-    John->>John: Fight against hypochondria
-end
-Note right of John: Rational thoughts!
-John-->>Alice: Great!
-John->>Bob: How about you?
-Bob-->>John: Jolly good!
-    
- -
- Gantt
- [docs - live editor] -
+    participant Alice
+    participant Bob
+    Alice->>John: Hello John, how are you?
+    loop Healthcheck
+        John->>John: Fight against hypochondria
+    end
+    Note right of John: Rational thoughts 
prevail! + John-->>Alice: Great! + John->>Bob: How about you? + Bob-->>John: Jolly good! +``` + +![Sequence diagram](./img/sequence.png) + +### Gantt diagram + +``` gantt -section Section -Completed :done, des1, 2014-01-06,2014-01-08 -Active :active, des2, 2014-01-07, 3d -Parallel 1 : des3, after des1, 1d -Parallel 2 : des4, after des1, 1d -Parallel 3 : des5, after des3, 1d -Parallel 4 : des6, after des4, 1d -
- -
- Class
- [docs - live editor] -
+dateFormat  YYYY-MM-DD
+title Adding GANTT diagram to mermaid
+excludes weekdays 2014-01-10
+section A section
+Completed task            :done,    des1, 2014-01-06,2014-01-08
+Active task               :active,  des2, 2014-01-09, 3d
+Future task               :         des3, after des2, 5d
+Future task2               :         des4, after des3, 5d
+```
+
+![Gantt diagram](./img/gantt.png)
+
+### Class diagram - :exclamation: experimental
+
+```
 classDiagram
-Class01 <|-- AveryLongClass : Cool
-<<interface>> Class01
+Class01 <|-- AveryLongClass : Cool
+Class03 *-- Class04
+Class05 o-- Class06
+Class07 .. Class08
 Class09 --> C2 : Where am i?
 Class09 --* C3
 Class09 --|> Class07
@@ -125,84 +110,129 @@ Class07 : Object[] elementData
 Class01 : size()
 Class01 : int chimp
 Class01 : int gorilla
-class Class10 {
-  <<service>>
-  int id
-  size()
+Class08 <--> C2: Cool label
+```
+
+![Class diagram](./img/class.png)
+
+### Git graph - :exclamation: experimental
+
+```
+gitGraph:
+options
+{
+    "nodeSpacing": 150,
+    "nodeRadius": 10
 }
-
- -
- State
- [docs - live editor] -
-stateDiagram
-[*] --> Still
-Still --> [*]
-Still --> Moving
-Moving --> Still
-Moving --> Crash
-Crash --> [*]
-
- -
- Pie
- [docs - live editor] -
-pie
-"Dogs" : 386
-"Cats" : 85
-"Rats" : 15 
-
- -
- Git
- [experimental - live editor] -
Coming soon!
+end +commit +branch newbranch +checkout newbranch +commit +commit +checkout master +commit +commit +merge newbranch +``` -## Related projects +![Git graph](./img/git.png) -- [Command Line Interface](https://github.com/mermaid-js/mermaid.cli) -- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor) +## Installation -# Contributors [![Help wanted](https://img.shields.io/github/labels/mermaid-js/mermaid/Help%20wanted!)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Help+wanted%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) +### CDN -Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out. +``` +https://unpkg.com/mermaid@/dist/ +``` -Detailed information about how to contribute can be found in the [contribution guide](CONTRIBUTING.md) +Replace `` with expected version number. -# Appreciation -A quick note from Knut Sveidqvist: ->*Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries!* ->*Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering.* ->*Thank you to [Tyler Long](https://github.com/tylerlong) who has been a collaborator since April 2017.* -> ->*Thank you to the ever-growing list of [contributors](https://github.com/knsv/mermaid/graphs/contributors) that brought the project this far!* +Example: https://unpkg.com/mermaid@7.1.0/dist/ ---- +### Node.js -*Mermaid was created by Knut Sveidqvist for easier documentation.* +``` +yarn add mermaid +``` + +## Documentation + +https://mermaidjs.github.io + +## Sibling projects + +- [mermaid CLI](https://github.com/mermaidjs/mermaid.cli) +- [mermaid live editor](https://github.com/mermaidjs/mermaid-live-editor) +- [mermaid webpack demo](https://github.com/mermaidjs/mermaid-webpack-demo) +- [mermaid Parcel demo](https://github.com/mermaidjs/mermaid-parcel-demo) + +## Request for assistance + +Things are piling up and I have a hard time keeping up. To remedy this +it would be great if we could form a core team of developers to cooperate +with the future development of mermaid. + +As part of this team you would get write access to the repository and would +represent the project when answering questions and issues. + +Together we could continue the work with things like: + +- Adding more types of diagrams like mindmaps, ert diagrams, etc. +- Improving existing diagrams + +Don't hesitate to contact me if you want to get involved. + +## For contributors + +### Setup + +``` +yarn install +``` + +### Build + +``` +yarn build:watch +``` + +### Lint + +``` +yarn lint +``` + +We use [eslint](https://eslint.org/). +We recommend you installing [editor plugins](https://eslint.org/docs/user-guide/integrations) so you can get real time lint result. + +### Test + +``` +yarn test +``` +Manual test in browser: open `dist/index.html` + +### Release + +For those who have the permission to do so: + +Update version number in `package.json`. + +``` +npm publish +``` + +Command above generates files into the `dist` folder and publishes them to npmjs.org. + +## Credits + +Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries! + +Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering. + +_Mermaid was created by Knut Sveidqvist for easier documentation._ + +_[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017._ + +Here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors). From a414aa27b6d6bd2b483b00f122e6483991043f04 Mon Sep 17 00:00:00 2001 From: NeilCuzon Date: Mon, 9 Mar 2020 23:25:28 -0700 Subject: [PATCH 70/95] Revert "Revert "Revert "Update README.md""" This reverts commit c598fd9ec4a14f38c789f741232f6d40fdb031b0. --- README.md | 344 +++++++++++++++++++++++++----------------------------- 1 file changed, 157 insertions(+), 187 deletions(-) diff --git a/README.md b/README.md index 791ac115c..781cee7e1 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,26 @@ + +| :mega: :mega: :mega: | +| :----: | +| * If you're upgrading from a version __< v8.2.0__, there are [non-backward-compatible changes](http://mermaid-js.github.io/mermaid/#/usage?id=to-enable-click-event-and-tags-in-nodes) related to security issues. Default behaviour of the library might have changed for your implementation.| + -# Mermaid +# mermaid [![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/Mermaid/mermaid) -[![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) -[![Coverage Status](https://coveralls.io/repos/github/knsv/mermaid/badge.svg?branch=master)](https://coveralls.io/github/knsv/mermaid?branch=master) -[![Join the chat at https://gitter.im/knsv/mermaid](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +__mermaid is a Javascript based diagramming and charting tool. It generates diagrams flowcharts and more, using markdown-inspired text for ease and speed.__ -![banner](./img/header.png) -# What is mermaid? +Check out the list of [Integrations and Usages of Mermaid](https://github.com/mermaid-js/mermaid/blob/develop/docs/integrations.md) -It is a simple markdown-inspired, script language for diagramming and charting that can be deployed via Javascipt. +For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Alternatively, you can also play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/). + -The reasoning behind it's inception was a need for simplified documentation and frustration with heavy and time consuming tools for explaining code, like Visio. - -Thus mermaid was created to make the process of diagramming and charting, as simple and as close to markdown, as possible. - -Check out the list of [Integrations and Usages of Mermaid](./integrations.md) -For the uninitiated, you can also try out our [live editor](https://mermaid-js.github.io/mermaid-live-editor/) and look at our [documentation](https://mermaid-js.github.io/mermaid/#/n00b-overview) for a deeper understanding of the tool itself, more importantly don't forget to have fun. - -**Mermaid was nominated and won the JS Open Source Awards (2019) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** +:trophy: **Mermaid was nominated and won the [JS Open Source Awards (2019)](https://osawards.com/javascript/#nominees) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** ## New diagrams in 8.4 With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. -![Image show the two new diagram types](./img/new-diagrams.png) +![Image show the two new diagram types](.docs/img/new-diagrams.png) ## Special note regarding version 8.2 @@ -44,64 +41,82 @@ mermaidAPI.initialize({ }); ``` -**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/k3nsv/mermaid/issues/866)** +For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/) or jump straight to the [installation and usage](http://mermaid-js.github.io/mermaid/#/usage). + -## The following are examples of Diagrams that mermaid can make: - -### Flowchart - -``` -graph TD; - A-->B; - A-->C; - B-->D; - C-->D; -``` - -![Flowchart](./img/flow.png) - -### Sequence diagram - -``` +__The following are some examples of the diagrams, charts and graphs that can be made using mermaid and the Markdown-inspired text specific to it. Click here jump into the [text syntax](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference).__ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Flow
+ [docs - live editor] +
+graph TD
+A[Hard] -->|Text| B(Round)
+B --> C{Decision}
+C -->|One| D[Result 1]
+C -->|Two| E[Result 2]
+    
+ +
+ Sequence
+ [docs - live editor] +
 sequenceDiagram
-    participant Alice
-    participant Bob
-    Alice->>John: Hello John, how are you?
-    loop Healthcheck
-        John->>John: Fight against hypochondria
-    end
-    Note right of John: Rational thoughts 
prevail! - John-->>Alice: Great! - John->>Bob: How about you? - Bob-->>John: Jolly good! -``` - -![Sequence diagram](./img/sequence.png) - -### Gantt diagram - -``` +Alice->>John: Hello John, how are you? +loop Healthcheck + John->>John: Fight against hypochondria +end +Note right of John: Rational thoughts! +John-->>Alice: Great! +John->>Bob: How about you? +Bob-->>John: Jolly good! +
+ +
+ Gantt
+ [docs - live editor] +
 gantt
-dateFormat  YYYY-MM-DD
-title Adding GANTT diagram to mermaid
-excludes weekdays 2014-01-10
-section A section
-Completed task            :done,    des1, 2014-01-06,2014-01-08
-Active task               :active,  des2, 2014-01-09, 3d
-Future task               :         des3, after des2, 5d
-Future task2               :         des4, after des3, 5d
-```
-
-![Gantt diagram](./img/gantt.png)
-
-### Class diagram - :exclamation: experimental
-
-```
+section Section
+Completed :done,    des1, 2014-01-06,2014-01-08
+Active        :active,  des2, 2014-01-07, 3d
+Parallel 1   :         des3, after des1, 1d
+Parallel 2   :         des4, after des1, 1d
+Parallel 3   :         des5, after des3, 1d
+Parallel 4   :         des6, after des4, 1d
+    
+ +
+ Class
+ [docs - live editor] +
 classDiagram
-Class01 <|-- AveryLongClass : Cool
-Class03 *-- Class04
-Class05 o-- Class06
-Class07 .. Class08
+Class01 <|-- AveryLongClass : Cool
+<<interface>> Class01
 Class09 --> C2 : Where am i?
 Class09 --* C3
 Class09 --|> Class07
@@ -110,129 +125,84 @@ Class07 : Object[] elementData
 Class01 : size()
 Class01 : int chimp
 Class01 : int gorilla
-Class08 <--> C2: Cool label
-```
-
-![Class diagram](./img/class.png)
-
-### Git graph - :exclamation: experimental
-
-```
-gitGraph:
-options
-{
-    "nodeSpacing": 150,
-    "nodeRadius": 10
+class Class10 {
+  <<service>>
+  int id
+  size()
 }
-end
-commit
-branch newbranch
-checkout newbranch
-commit
-commit
-checkout master
-commit
-commit
-merge newbranch
-```
+
+ +
+ State
+ [docs - live editor] +
+stateDiagram
+[*] --> Still
+Still --> [*]
+Still --> Moving
+Moving --> Still
+Moving --> Crash
+Crash --> [*]
+
+ +
+ Pie
+ [docs - live editor] +
+pie
+"Dogs" : 386
+"Cats" : 85
+"Rats" : 15 
+
+ +
+ Git
+ [experimental - live editor] +
Coming soon!
-![Git graph](./img/git.png) +## Related projects -## Installation +- [Command Line Interface](https://github.com/mermaid-js/mermaid.cli) +- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor) -### CDN +# Contributors [![Help wanted](https://img.shields.io/github/labels/mermaid-js/mermaid/Help%20wanted!)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Help+wanted%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) -``` -https://unpkg.com/mermaid@/dist/ -``` +Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out. -Replace `` with expected version number. +Detailed information about how to contribute can be found in the [contribution guide](CONTRIBUTING.md) -Example: https://unpkg.com/mermaid@7.1.0/dist/ +# Appreciation +A quick note from Knut Sveidqvist: +>*Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries!* +>*Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering.* +>*Thank you to [Tyler Long](https://github.com/tylerlong) who has been a collaborator since April 2017.* +> +>*Thank you to the ever-growing list of [contributors](https://github.com/knsv/mermaid/graphs/contributors) that brought the project this far!* -### Node.js +--- -``` -yarn add mermaid -``` - -## Documentation - -https://mermaidjs.github.io - -## Sibling projects - -- [mermaid CLI](https://github.com/mermaidjs/mermaid.cli) -- [mermaid live editor](https://github.com/mermaidjs/mermaid-live-editor) -- [mermaid webpack demo](https://github.com/mermaidjs/mermaid-webpack-demo) -- [mermaid Parcel demo](https://github.com/mermaidjs/mermaid-parcel-demo) - -## Request for assistance - -Things are piling up and I have a hard time keeping up. To remedy this -it would be great if we could form a core team of developers to cooperate -with the future development of mermaid. - -As part of this team you would get write access to the repository and would -represent the project when answering questions and issues. - -Together we could continue the work with things like: - -- Adding more types of diagrams like mindmaps, ert diagrams, etc. -- Improving existing diagrams - -Don't hesitate to contact me if you want to get involved. - -## For contributors - -### Setup - -``` -yarn install -``` - -### Build - -``` -yarn build:watch -``` - -### Lint - -``` -yarn lint -``` - -We use [eslint](https://eslint.org/). -We recommend you installing [editor plugins](https://eslint.org/docs/user-guide/integrations) so you can get real time lint result. - -### Test - -``` -yarn test -``` -Manual test in browser: open `dist/index.html` - -### Release - -For those who have the permission to do so: - -Update version number in `package.json`. - -``` -npm publish -``` - -Command above generates files into the `dist` folder and publishes them to npmjs.org. - -## Credits - -Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries! - -Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering. - -_Mermaid was created by Knut Sveidqvist for easier documentation._ - -_[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017._ - -Here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors). +*Mermaid was created by Knut Sveidqvist for easier documentation.* From 09e6804761aa7bd8acd1bd3392120f58fa3703b5 Mon Sep 17 00:00:00 2001 From: NeilCuzon Date: Mon, 9 Mar 2020 23:25:55 -0700 Subject: [PATCH 71/95] Revert "Revert "Revert "Revert "Update README.md"""" This reverts commit a414aa27b6d6bd2b483b00f122e6483991043f04. --- README.md | 344 +++++++++++++++++++++++++++++------------------------- 1 file changed, 187 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index 781cee7e1..791ac115c 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,29 @@ - -| :mega: :mega: :mega: | -| :----: | -| * If you're upgrading from a version __< v8.2.0__, there are [non-backward-compatible changes](http://mermaid-js.github.io/mermaid/#/usage?id=to-enable-click-event-and-tags-in-nodes) related to security issues. Default behaviour of the library might have changed for your implementation.| - -# mermaid [![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/Mermaid/mermaid) +# Mermaid - -__mermaid is a Javascript based diagramming and charting tool. It generates diagrams flowcharts and more, using markdown-inspired text for ease and speed.__ +[![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) +[![Coverage Status](https://coveralls.io/repos/github/knsv/mermaid/badge.svg?branch=master)](https://coveralls.io/github/knsv/mermaid?branch=master) +[![Join the chat at https://gitter.im/knsv/mermaid](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Check out the list of [Integrations and Usages of Mermaid](https://github.com/mermaid-js/mermaid/blob/develop/docs/integrations.md) +![banner](./img/header.png) +# What is mermaid? -For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Alternatively, you can also play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/). - +It is a simple markdown-inspired, script language for diagramming and charting that can be deployed via Javascipt. -:trophy: **Mermaid was nominated and won the [JS Open Source Awards (2019)](https://osawards.com/javascript/#nominees) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** +The reasoning behind it's inception was a need for simplified documentation and frustration with heavy and time consuming tools for explaining code, like Visio. + +Thus mermaid was created to make the process of diagramming and charting, as simple and as close to markdown, as possible. + +Check out the list of [Integrations and Usages of Mermaid](./integrations.md) +For the uninitiated, you can also try out our [live editor](https://mermaid-js.github.io/mermaid-live-editor/) and look at our [documentation](https://mermaid-js.github.io/mermaid/#/n00b-overview) for a deeper understanding of the tool itself, more importantly don't forget to have fun. + +**Mermaid was nominated and won the JS Open Source Awards (2019) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** ## New diagrams in 8.4 With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. -![Image show the two new diagram types](.docs/img/new-diagrams.png) +![Image show the two new diagram types](./img/new-diagrams.png) ## Special note regarding version 8.2 @@ -41,82 +44,64 @@ mermaidAPI.initialize({ }); ``` -For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/) or jump straight to the [installation and usage](http://mermaid-js.github.io/mermaid/#/usage). - +**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/k3nsv/mermaid/issues/866)** -__The following are some examples of the diagrams, charts and graphs that can be made using mermaid and the Markdown-inspired text specific to it. Click here jump into the [text syntax](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference).__ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Flow
- [docs - live editor] -
-graph TD
-A[Hard] -->|Text| B(Round)
-B --> C{Decision}
-C -->|One| D[Result 1]
-C -->|Two| E[Result 2]
-    
- -
- Sequence
- [docs - live editor] -
+## The following are examples of Diagrams that mermaid can make:
+
+### Flowchart
+
+```
+graph TD;
+    A-->B;
+    A-->C;
+    B-->D;
+    C-->D;
+```
+
+![Flowchart](./img/flow.png)
+
+### Sequence diagram
+
+```
 sequenceDiagram
-Alice->>John: Hello John, how are you?
-loop Healthcheck
-    John->>John: Fight against hypochondria
-end
-Note right of John: Rational thoughts!
-John-->>Alice: Great!
-John->>Bob: How about you?
-Bob-->>John: Jolly good!
-    
- -
- Gantt
- [docs - live editor] -
+    participant Alice
+    participant Bob
+    Alice->>John: Hello John, how are you?
+    loop Healthcheck
+        John->>John: Fight against hypochondria
+    end
+    Note right of John: Rational thoughts 
prevail! + John-->>Alice: Great! + John->>Bob: How about you? + Bob-->>John: Jolly good! +``` + +![Sequence diagram](./img/sequence.png) + +### Gantt diagram + +``` gantt -section Section -Completed :done, des1, 2014-01-06,2014-01-08 -Active :active, des2, 2014-01-07, 3d -Parallel 1 : des3, after des1, 1d -Parallel 2 : des4, after des1, 1d -Parallel 3 : des5, after des3, 1d -Parallel 4 : des6, after des4, 1d -
- -
- Class
- [docs - live editor] -
+dateFormat  YYYY-MM-DD
+title Adding GANTT diagram to mermaid
+excludes weekdays 2014-01-10
+section A section
+Completed task            :done,    des1, 2014-01-06,2014-01-08
+Active task               :active,  des2, 2014-01-09, 3d
+Future task               :         des3, after des2, 5d
+Future task2               :         des4, after des3, 5d
+```
+
+![Gantt diagram](./img/gantt.png)
+
+### Class diagram - :exclamation: experimental
+
+```
 classDiagram
-Class01 <|-- AveryLongClass : Cool
-<<interface>> Class01
+Class01 <|-- AveryLongClass : Cool
+Class03 *-- Class04
+Class05 o-- Class06
+Class07 .. Class08
 Class09 --> C2 : Where am i?
 Class09 --* C3
 Class09 --|> Class07
@@ -125,84 +110,129 @@ Class07 : Object[] elementData
 Class01 : size()
 Class01 : int chimp
 Class01 : int gorilla
-class Class10 {
-  <<service>>
-  int id
-  size()
+Class08 <--> C2: Cool label
+```
+
+![Class diagram](./img/class.png)
+
+### Git graph - :exclamation: experimental
+
+```
+gitGraph:
+options
+{
+    "nodeSpacing": 150,
+    "nodeRadius": 10
 }
-
- -
- State
- [docs - live editor] -
-stateDiagram
-[*] --> Still
-Still --> [*]
-Still --> Moving
-Moving --> Still
-Moving --> Crash
-Crash --> [*]
-
- -
- Pie
- [docs - live editor] -
-pie
-"Dogs" : 386
-"Cats" : 85
-"Rats" : 15 
-
- -
- Git
- [experimental - live editor] -
Coming soon!
+end +commit +branch newbranch +checkout newbranch +commit +commit +checkout master +commit +commit +merge newbranch +``` -## Related projects +![Git graph](./img/git.png) -- [Command Line Interface](https://github.com/mermaid-js/mermaid.cli) -- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor) +## Installation -# Contributors [![Help wanted](https://img.shields.io/github/labels/mermaid-js/mermaid/Help%20wanted!)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Help+wanted%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) +### CDN -Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out. +``` +https://unpkg.com/mermaid@/dist/ +``` -Detailed information about how to contribute can be found in the [contribution guide](CONTRIBUTING.md) +Replace `` with expected version number. -# Appreciation -A quick note from Knut Sveidqvist: ->*Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries!* ->*Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering.* ->*Thank you to [Tyler Long](https://github.com/tylerlong) who has been a collaborator since April 2017.* -> ->*Thank you to the ever-growing list of [contributors](https://github.com/knsv/mermaid/graphs/contributors) that brought the project this far!* +Example: https://unpkg.com/mermaid@7.1.0/dist/ ---- +### Node.js -*Mermaid was created by Knut Sveidqvist for easier documentation.* +``` +yarn add mermaid +``` + +## Documentation + +https://mermaidjs.github.io + +## Sibling projects + +- [mermaid CLI](https://github.com/mermaidjs/mermaid.cli) +- [mermaid live editor](https://github.com/mermaidjs/mermaid-live-editor) +- [mermaid webpack demo](https://github.com/mermaidjs/mermaid-webpack-demo) +- [mermaid Parcel demo](https://github.com/mermaidjs/mermaid-parcel-demo) + +## Request for assistance + +Things are piling up and I have a hard time keeping up. To remedy this +it would be great if we could form a core team of developers to cooperate +with the future development of mermaid. + +As part of this team you would get write access to the repository and would +represent the project when answering questions and issues. + +Together we could continue the work with things like: + +- Adding more types of diagrams like mindmaps, ert diagrams, etc. +- Improving existing diagrams + +Don't hesitate to contact me if you want to get involved. + +## For contributors + +### Setup + +``` +yarn install +``` + +### Build + +``` +yarn build:watch +``` + +### Lint + +``` +yarn lint +``` + +We use [eslint](https://eslint.org/). +We recommend you installing [editor plugins](https://eslint.org/docs/user-guide/integrations) so you can get real time lint result. + +### Test + +``` +yarn test +``` +Manual test in browser: open `dist/index.html` + +### Release + +For those who have the permission to do so: + +Update version number in `package.json`. + +``` +npm publish +``` + +Command above generates files into the `dist` folder and publishes them to npmjs.org. + +## Credits + +Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries! + +Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering. + +_Mermaid was created by Knut Sveidqvist for easier documentation._ + +_[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017._ + +Here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors). From 5a763230e5945a37ad72b5ffd546f9ea0b8392b9 Mon Sep 17 00:00:00 2001 From: NeilCuzon Date: Mon, 9 Mar 2020 23:26:00 -0700 Subject: [PATCH 72/95] Revert "Revert "Revert "Revert "Revert "Update README.md""""" This reverts commit 09e6804761aa7bd8acd1bd3392120f58fa3703b5. --- README.md | 344 +++++++++++++++++++++++++----------------------------- 1 file changed, 157 insertions(+), 187 deletions(-) diff --git a/README.md b/README.md index 791ac115c..781cee7e1 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,26 @@ + +| :mega: :mega: :mega: | +| :----: | +| * If you're upgrading from a version __< v8.2.0__, there are [non-backward-compatible changes](http://mermaid-js.github.io/mermaid/#/usage?id=to-enable-click-event-and-tags-in-nodes) related to security issues. Default behaviour of the library might have changed for your implementation.| + -# Mermaid +# mermaid [![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/Mermaid/mermaid) -[![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) -[![Coverage Status](https://coveralls.io/repos/github/knsv/mermaid/badge.svg?branch=master)](https://coveralls.io/github/knsv/mermaid?branch=master) -[![Join the chat at https://gitter.im/knsv/mermaid](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +__mermaid is a Javascript based diagramming and charting tool. It generates diagrams flowcharts and more, using markdown-inspired text for ease and speed.__ -![banner](./img/header.png) -# What is mermaid? +Check out the list of [Integrations and Usages of Mermaid](https://github.com/mermaid-js/mermaid/blob/develop/docs/integrations.md) -It is a simple markdown-inspired, script language for diagramming and charting that can be deployed via Javascipt. +For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Alternatively, you can also play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/). + -The reasoning behind it's inception was a need for simplified documentation and frustration with heavy and time consuming tools for explaining code, like Visio. - -Thus mermaid was created to make the process of diagramming and charting, as simple and as close to markdown, as possible. - -Check out the list of [Integrations and Usages of Mermaid](./integrations.md) -For the uninitiated, you can also try out our [live editor](https://mermaid-js.github.io/mermaid-live-editor/) and look at our [documentation](https://mermaid-js.github.io/mermaid/#/n00b-overview) for a deeper understanding of the tool itself, more importantly don't forget to have fun. - -**Mermaid was nominated and won the JS Open Source Awards (2019) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** +:trophy: **Mermaid was nominated and won the [JS Open Source Awards (2019)](https://osawards.com/javascript/#nominees) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** ## New diagrams in 8.4 With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. -![Image show the two new diagram types](./img/new-diagrams.png) +![Image show the two new diagram types](.docs/img/new-diagrams.png) ## Special note regarding version 8.2 @@ -44,64 +41,82 @@ mermaidAPI.initialize({ }); ``` -**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/k3nsv/mermaid/issues/866)** +For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/) or jump straight to the [installation and usage](http://mermaid-js.github.io/mermaid/#/usage). + -## The following are examples of Diagrams that mermaid can make: - -### Flowchart - -``` -graph TD; - A-->B; - A-->C; - B-->D; - C-->D; -``` - -![Flowchart](./img/flow.png) - -### Sequence diagram - -``` +__The following are some examples of the diagrams, charts and graphs that can be made using mermaid and the Markdown-inspired text specific to it. Click here jump into the [text syntax](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference).__ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Flow
+ [docs - live editor] +
+graph TD
+A[Hard] -->|Text| B(Round)
+B --> C{Decision}
+C -->|One| D[Result 1]
+C -->|Two| E[Result 2]
+    
+ +
+ Sequence
+ [docs - live editor] +
 sequenceDiagram
-    participant Alice
-    participant Bob
-    Alice->>John: Hello John, how are you?
-    loop Healthcheck
-        John->>John: Fight against hypochondria
-    end
-    Note right of John: Rational thoughts 
prevail! - John-->>Alice: Great! - John->>Bob: How about you? - Bob-->>John: Jolly good! -``` - -![Sequence diagram](./img/sequence.png) - -### Gantt diagram - -``` +Alice->>John: Hello John, how are you? +loop Healthcheck + John->>John: Fight against hypochondria +end +Note right of John: Rational thoughts! +John-->>Alice: Great! +John->>Bob: How about you? +Bob-->>John: Jolly good! +
+ +
+ Gantt
+ [docs - live editor] +
 gantt
-dateFormat  YYYY-MM-DD
-title Adding GANTT diagram to mermaid
-excludes weekdays 2014-01-10
-section A section
-Completed task            :done,    des1, 2014-01-06,2014-01-08
-Active task               :active,  des2, 2014-01-09, 3d
-Future task               :         des3, after des2, 5d
-Future task2               :         des4, after des3, 5d
-```
-
-![Gantt diagram](./img/gantt.png)
-
-### Class diagram - :exclamation: experimental
-
-```
+section Section
+Completed :done,    des1, 2014-01-06,2014-01-08
+Active        :active,  des2, 2014-01-07, 3d
+Parallel 1   :         des3, after des1, 1d
+Parallel 2   :         des4, after des1, 1d
+Parallel 3   :         des5, after des3, 1d
+Parallel 4   :         des6, after des4, 1d
+    
+ +
+ Class
+ [docs - live editor] +
 classDiagram
-Class01 <|-- AveryLongClass : Cool
-Class03 *-- Class04
-Class05 o-- Class06
-Class07 .. Class08
+Class01 <|-- AveryLongClass : Cool
+<<interface>> Class01
 Class09 --> C2 : Where am i?
 Class09 --* C3
 Class09 --|> Class07
@@ -110,129 +125,84 @@ Class07 : Object[] elementData
 Class01 : size()
 Class01 : int chimp
 Class01 : int gorilla
-Class08 <--> C2: Cool label
-```
-
-![Class diagram](./img/class.png)
-
-### Git graph - :exclamation: experimental
-
-```
-gitGraph:
-options
-{
-    "nodeSpacing": 150,
-    "nodeRadius": 10
+class Class10 {
+  <<service>>
+  int id
+  size()
 }
-end
-commit
-branch newbranch
-checkout newbranch
-commit
-commit
-checkout master
-commit
-commit
-merge newbranch
-```
+
+ +
+ State
+ [docs - live editor] +
+stateDiagram
+[*] --> Still
+Still --> [*]
+Still --> Moving
+Moving --> Still
+Moving --> Crash
+Crash --> [*]
+
+ +
+ Pie
+ [docs - live editor] +
+pie
+"Dogs" : 386
+"Cats" : 85
+"Rats" : 15 
+
+ +
+ Git
+ [experimental - live editor] +
Coming soon!
-![Git graph](./img/git.png) +## Related projects -## Installation +- [Command Line Interface](https://github.com/mermaid-js/mermaid.cli) +- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor) -### CDN +# Contributors [![Help wanted](https://img.shields.io/github/labels/mermaid-js/mermaid/Help%20wanted!)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Help+wanted%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) -``` -https://unpkg.com/mermaid@/dist/ -``` +Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out. -Replace `` with expected version number. +Detailed information about how to contribute can be found in the [contribution guide](CONTRIBUTING.md) -Example: https://unpkg.com/mermaid@7.1.0/dist/ +# Appreciation +A quick note from Knut Sveidqvist: +>*Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries!* +>*Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering.* +>*Thank you to [Tyler Long](https://github.com/tylerlong) who has been a collaborator since April 2017.* +> +>*Thank you to the ever-growing list of [contributors](https://github.com/knsv/mermaid/graphs/contributors) that brought the project this far!* -### Node.js +--- -``` -yarn add mermaid -``` - -## Documentation - -https://mermaidjs.github.io - -## Sibling projects - -- [mermaid CLI](https://github.com/mermaidjs/mermaid.cli) -- [mermaid live editor](https://github.com/mermaidjs/mermaid-live-editor) -- [mermaid webpack demo](https://github.com/mermaidjs/mermaid-webpack-demo) -- [mermaid Parcel demo](https://github.com/mermaidjs/mermaid-parcel-demo) - -## Request for assistance - -Things are piling up and I have a hard time keeping up. To remedy this -it would be great if we could form a core team of developers to cooperate -with the future development of mermaid. - -As part of this team you would get write access to the repository and would -represent the project when answering questions and issues. - -Together we could continue the work with things like: - -- Adding more types of diagrams like mindmaps, ert diagrams, etc. -- Improving existing diagrams - -Don't hesitate to contact me if you want to get involved. - -## For contributors - -### Setup - -``` -yarn install -``` - -### Build - -``` -yarn build:watch -``` - -### Lint - -``` -yarn lint -``` - -We use [eslint](https://eslint.org/). -We recommend you installing [editor plugins](https://eslint.org/docs/user-guide/integrations) so you can get real time lint result. - -### Test - -``` -yarn test -``` -Manual test in browser: open `dist/index.html` - -### Release - -For those who have the permission to do so: - -Update version number in `package.json`. - -``` -npm publish -``` - -Command above generates files into the `dist` folder and publishes them to npmjs.org. - -## Credits - -Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries! - -Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering. - -_Mermaid was created by Knut Sveidqvist for easier documentation._ - -_[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017._ - -Here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors). +*Mermaid was created by Knut Sveidqvist for easier documentation.* From bab4649a1e8c1c5fc026e8e0cf6fce9bb822d6d6 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 10 Mar 2020 13:48:53 +0000 Subject: [PATCH 73/95] Use markers with rounded crows feet --- src/diagrams/er/erMarkers.js | 53 +++-- src/diagrams/er/erRenderer.js | 353 ++++------------------------------ src/mermaidAPI.js | 51 +---- 3 files changed, 69 insertions(+), 388 deletions(-) diff --git a/src/diagrams/er/erMarkers.js b/src/diagrams/er/erMarkers.js index 96933693b..3107671fd 100644 --- a/src/diagrams/er/erMarkers.js +++ b/src/diagrams/er/erMarkers.js @@ -1,21 +1,16 @@ -//import * as d3 from 'd3'; - const ERMarkers = { ONLY_ONE_START: 'ONLY_ONE_START', ONLY_ONE_END: 'ONLY_ONE_END', - ZERO_OR_ONE_START: 'ZERO_OR_ONE_START', ZERO_OR_ONE_END: 'ZERO_OR_ONE_END', - ONE_OR_MORE_START: 'ONE_OR_MORE_START', ONE_OR_MORE_END: 'ONE_OR_MORE_END', - ZERO_OR_MORE_START: 'ZERO_OR_MORE_START', ZERO_OR_MORE_END: 'ZERO_OR_MORE_END' }; /** - * Put the markers into the svg DOM for use in paths + * Put the markers into the svg DOM for later use with edge paths */ const insertMarkers = function(elem, conf) { let marker; @@ -96,73 +91,73 @@ const insertMarkers = function(elem, conf) { .append('defs') .append('marker') .attr('id', ERMarkers.ONE_OR_MORE_START) - .attr('refX', 0) - .attr('refY', 9) - .attr('markerWidth', 18) - .attr('markerHeight', 18) + .attr('refX', 18) + .attr('refY', 18) + .attr('markerWidth', 45) + .attr('markerHeight', 36) .attr('orient', 'auto') .append('path') .attr('stroke', conf.stroke) .attr('fill', 'none') - .attr('d', 'M0,0 L9,9 L0,18 M15,0 L15,18'); + .attr('d', 'M0,18 Q 18,0 36,18 Q 18,36 0,18 M42,9 L42,27'); elem .append('defs') .append('marker') .attr('id', ERMarkers.ONE_OR_MORE_END) - .attr('refX', 18) - .attr('refY', 9) - .attr('markerWidth', 21) - .attr('markerHeight', 18) + .attr('refX', 27) + .attr('refY', 18) + .attr('markerWidth', 45) + .attr('markerHeight', 36) .attr('orient', 'auto') .append('path') .attr('stroke', conf.stroke) .attr('fill', 'none') - .attr('d', 'M3,0 L3,18 M18,0 L9,9 L18,18'); + .attr('d', 'M3,9 L3,27 M9,18 Q27,0 45,18 Q27,36 9,18'); marker = elem .append('defs') .append('marker') .attr('id', ERMarkers.ZERO_OR_MORE_START) - .attr('refX', 0) - .attr('refY', 9) - .attr('markerWidth', 30) - .attr('markerHeight', 18) + .attr('refX', 18) + .attr('refY', 18) + .attr('markerWidth', 57) + .attr('markerHeight', 36) .attr('orient', 'auto'); marker .append('circle') .attr('stroke', conf.stroke) .attr('fill', 'white') - .attr('cx', 21) - .attr('cy', 9) + .attr('cx', 48) + .attr('cy', 18) .attr('r', 6); marker .append('path') .attr('stroke', conf.stroke) .attr('fill', 'none') - .attr('d', 'M0,0 L9,9 L0,18'); + .attr('d', 'M0,18 Q18,0 36,18 Q18,36 0,18'); marker = elem .append('defs') .append('marker') .attr('id', ERMarkers.ZERO_OR_MORE_END) - .attr('refX', 30) - .attr('refY', 9) - .attr('markerWidth', 30) - .attr('markerHeight', 18) + .attr('refX', 39) + .attr('refY', 18) + .attr('markerWidth', 57) + .attr('markerHeight', 36) .attr('orient', 'auto'); marker .append('circle') .attr('stroke', conf.stroke) .attr('fill', 'white') .attr('cx', 9) - .attr('cy', 9) + .attr('cy', 18) .attr('r', 6); marker .append('path') .attr('stroke', conf.stroke) .attr('fill', 'none') - .attr('d', 'M30,0 L21,9 L30,18'); + .attr('d', 'M21,18 Q39,0 57,18 Q39,36 21,18'); return; }; diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index 858f6b9cf..1933ffb95 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -16,9 +16,10 @@ export const setConf = function(cnf) { }; /** - * Function that adds the entities as vertices + * Function that adds the entities as vertices in the graph prior to laying out * @param entities The entities to be added to the graph * @param g The graph that is to be drawn + * @returns {Object} The object containing all the entities as properties */ const addEntities = function(entities, g) { const keys = Object.keys(entities); @@ -36,6 +37,7 @@ const addEntities = function(entities, g) { id: entity }); }); + return entities; }; /** @@ -92,26 +94,38 @@ const drawEntities = function(diagram, entities, g, svgId) { }); }; // drawEntities +/** + * Add each relationship to the graph + * @param relationships the relationships to be added + * @param g the graph + * @return {Array} The array of relationships + */ const addRelationships = function(relationships, g) { relationships.forEach(function(r) { g.setEdge(r.entityA, r.entityB, { relationship: r }); }); + return relationships; }; // addRelationships +/** + * + */ const drawRelationships = function(diagram, relationships, g) { relationships.forEach(function(rel) { - //drawRelationship(diagram, rel, g); drawRelationshipFromLayout(diagram, rel, g); }); }; // drawRelationships +/** + * Draw a relationship using edge information from the graph + * @param diagram the svg node + * @param rel the relationship to draw in the svg + * @param g the graph containing the edge information + */ const drawRelationshipFromLayout = function(diagram, rel, g) { // Find the edge relating to this relationship const edge = g.edge({ v: rel.entityA, w: rel.entityB }); - // Using it's points, generate a line function - edge.points = edge.points.filter(p => !Number.isNaN(p.y)); // TODO: why is necessary? - // Get a function that will generate the line path const lineFunction = d3 .line() @@ -130,7 +144,7 @@ const drawRelationshipFromLayout = function(diagram, rel, g) { .attr('stroke', conf.stroke) .attr('fill', 'none'); - // TODO: Understand this + // TODO: Understand this better let url = ''; if (conf.arrowMarkerAbsolute) { url = @@ -143,8 +157,8 @@ const drawRelationshipFromLayout = function(diagram, rel, g) { url = url.replace(/\)/g, '\\)'); } - // TODO: change the way enums are imported - // Decide which start and end markers it needs + // Decide which start and end markers it needs. It may be possible to be more concise here + // by reversing a start marker to make an end marker...but this will do for now switch (rel.cardinality) { case erDb.Cardinality.ONLY_ONE_TO_ONE_OR_MORE: svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')'); @@ -249,285 +263,10 @@ const drawRelationshipFromLayout = function(diagram, rel, g) { } }; -/* -const drawRelationship = function(diagram, relationship, g) { - // Set the from and to co-ordinates using the graph vertices - - let from = { - x: g.node(relationship.entityA).x, - y: g.node(relationship.entityA).y - }; - - let to = { - x: g.node(relationship.entityB).x, - y: g.node(relationship.entityB).y - }; - - diagram - .append('line') - .attr('x1', from.x) - .attr('y1', from.y) - .attr('x2', to.x) - .attr('y2', to.y) - .attr('stroke', conf.stroke); -}; // drawRelationship -*/ - -/* -const drawFeet = function(diagram, relationships, g) { - relationships.forEach(function(rel) { - // Get the points of intersection with the entities - const nodeA = g.node(rel.entityA); - const nodeB = g.node(rel.entityB); - - const fromIntersect = getIntersection( - nodeB.x - nodeA.x, - nodeB.y - nodeA.y, - nodeA.x, - nodeA.y, - nodeA.width / 2, - nodeA.height / 2 - ); - - dot(diagram, fromIntersect, conf.intersectColor); - - const toIntersect = getIntersection( - nodeA.x - nodeB.x, - nodeA.y - nodeB.y, - nodeB.x, - nodeB.y, - nodeB.width / 1, - nodeB.height / 2 - ); - - dot(diagram, toIntersect, conf.intersectColor); - - // Get the ankle and heel points - const anklePoints = getJoints(rel, fromIntersect, toIntersect, conf.ankleDistance); - - dot(diagram, { x: anklePoints.from.x, y: anklePoints.from.y }, conf.ankleColor); - dot(diagram, { x: anklePoints.to.x, y: anklePoints.to.y }, conf.ankleColor); - - const heelPoints = getJoints(rel, fromIntersect, toIntersect, conf.heelDistance); - - dot(diagram, { x: heelPoints.from.x, y: heelPoints.from.y }, conf.heelColor); - dot(diagram, { x: heelPoints.to.x, y: heelPoints.to.y }, conf.heelColor); - - // Get the toe points - const toePoints = getToes(rel, fromIntersect, toIntersect, conf.toeDistance); - - if (toePoints) { - dot(diagram, { x: toePoints.from.top.x, y: toePoints.from.top.y }, conf.toeColor); - dot(diagram, { x: toePoints.from.bottom.x, y: toePoints.from.bottom.y }, conf.toeColor); - dot(diagram, { x: toePoints.to.top.x, y: toePoints.to.top.y }, conf.toeColor); - dot(diagram, { x: toePoints.to.bottom.x, y: toePoints.to.bottom.y }, conf.toeColor); - - let paths = []; - paths.push(getToePath(heelPoints.from, toePoints.from.top, nodeA)); - paths.push(getToePath(heelPoints.from, toePoints.from.bottom, nodeA)); - paths.push(getToePath(heelPoints.to, toePoints.to.top, nodeB)); - paths.push(getToePath(heelPoints.to, toePoints.to.bottom, nodeB)); - - for (const path of paths) { - diagram - .append('path') - .attr('d', path) - .attr('stroke', conf.stroke) - .attr('fill', 'none'); - } - } - }); -}; // drawFeet - -const getToePath = function(heel, toe, tip) { - if (conf.toeStyle === 'straight') { - return `M ${heel.x} ${heel.y} L ${toe.x} ${toe.y} L ${tip.x} ${tip.y}`; - } else { - return `M ${heel.x} ${heel.y} Q ${toe.x} ${toe.y} ${tip.x} ${tip.y}`; - } -}; -*/ -/* -const getToes = function(relationship, fromPoint, toPoint, distance) { - if (conf.toeStyle === 'curved') { - distance *= 2; - } - - const gradient = (fromPoint.y - toPoint.y) / (fromPoint.x - toPoint.x); - const toeYDelta = getXDelta(distance, gradient); - const toeXDelta = toeYDelta * Math.abs(gradient); - - if (gradient > 0) { - const topToe = function(point) { - return { - x: point.x + toeXDelta, - y: point.y - toeYDelta - }; - }; - - const bottomToe = function(point) { - return { - x: point.x - toeXDelta, - y: point.y + toeYDelta - }; - }; - - const lower = { - top: fromPoint.x < toPoint.x ? topToe(toPoint) : topToe(fromPoint), - bottom: fromPoint.x < toPoint.x ? bottomToe(toPoint) : bottomToe(fromPoint) - }; - - const upper = { - top: fromPoint.x < toPoint.x ? topToe(fromPoint) : topToe(toPoint), - bottom: fromPoint.x < toPoint.x ? bottomToe(fromPoint) : bottomToe(toPoint) - }; - - return { - to: fromPoint.x < toPoint.x ? lower : upper, - from: fromPoint.x < toPoint.x ? upper : lower - }; - } -*/ -/* - if (fromPoint.x < toPoint.x) { - // Scenario A - - return { - to: { - top: { - x: toPoint.x + toeXDelta, - y: toPoint.y - toeYDelta - }, - bottom: { - x: toPoint.x - toeXDelta, - y: toPoint.y + toeYDelta - } - }, - from: { - top: { - x: fromPoint.x + toeXDelta, - y: fromPoint.y - toeYDelta - }, - bottom: { - x: fromPoint.x - toeXDelta, - y: fromPoint.y + toeYDelta - } - } - }; - } else { - // Scenario E - } -*/ -/* -}; // getToes -*/ -/* -const getJoints = function(relationship, fromPoint, toPoint, distance) { - const gradient = (fromPoint.y - toPoint.y) / (fromPoint.x - toPoint.x); - let jointXDelta = getXDelta(distance, gradient); - let jointYDelta = jointXDelta * Math.abs(gradient); - - let toX, toY; - let fromX, fromY; - - if (gradient > 0) { - if (fromPoint.x < toPoint.x) { - // Scenario A - } else { - // Scenario E - jointXDelta *= -1; - jointYDelta *= -1; - } - - toX = toPoint.x - jointXDelta; - toY = toPoint.y - jointYDelta; - fromX = fromPoint.x + jointXDelta; - fromY = fromPoint.y + jointYDelta; - } - - if (gradient < 0) { - if (fromPoint.x < toPoint.x) { - // Scenario C - jointXDelta *= -1; - jointYDelta *= -1; - } else { - // Scenario G - } - - toX = toPoint.x + jointXDelta; - toY = toPoint.y - jointYDelta; - fromX = fromPoint.x - jointXDelta; - fromY = fromPoint.y + jointYDelta; - } - - if (!isFinite(gradient)) { - if (fromPoint.y < toPoint.y) { - // Scenario B - } else { - // Scenario F - jointXDelta *= -1; - jointYDelta *= -1; - } - - toX = toPoint.x; - toY = toPoint.y - distance; - fromX = fromPoint.x; - fromY = fromPoint.y + distance; - } - - if (gradient === 0) { - if (fromPoint.x < toPoint.x) { - // Scenario D - } else { - // Scenario H - jointXDelta *= -1; - jointYDelta *= -1; - } - - toX = toPoint.x - distance; - toY = toPoint.y; - fromX = fromPoint.x + distance; - fromY = fromPoint.y; - } - - return { - from: { x: fromX, y: fromY }, - to: { x: toX, y: toY } - }; -}; -*/ - -/* -const getXDelta = function(hypotenuse, gradient) { - return Math.sqrt((hypotenuse * hypotenuse) / (Math.abs(gradient) + 1)); -}; - -const getIntersection = function(dx, dy, cx, cy, w, h) { - if (Math.abs(dy / dx) < h / w) { - // Hit vertical edge of box - return { x: cx + (dx > 0 ? w : -w), y: cy + (dy * w) / Math.abs(dx) }; - } else { - // Hit horizontal edge of box - return { x: cx + (dx * h) / Math.abs(dy), y: cy + (dy > 0 ? h : -h) }; - } -}; // getIntersection - -const dot = function(diagram, p, color) { - // stick a small circle at point p - if (conf.dots) { - diagram - .append('circle') - .attr('cx', p.x) - .attr('cy', p.y) - .attr('r', conf.dotRadius) - .attr('fill', color); - } -}; // dot -*/ /** - * Draw en E-R diagram in the tag with id: id based on the text definition of the graph - * @param text - * @param id + * Draw en E-R diagram in the tag with id: id based on the text definition of the diagram + * @param text the text of the diagram + * @param id the unique id of the DOM node that contains the diagram */ export const draw = function(text, id) { logger.info('Drawing ER diagram'); @@ -543,25 +282,26 @@ export const draw = function(text, id) { } // Get a reference to the diagram node - const diagram = d3.select(`[id='${id}']`); + const svg = d3.select(`[id='${id}']`); - // Add cardinality 'marker' definitions to the svg - erMarkers.insertMarkers(diagram, conf); + // Add cardinality marker definitions to the svg + erMarkers.insertMarkers(svg, conf); // Create the graph let g; // TODO: Explore directed vs undirected graphs, and how the layout is affected // An E-R diagram could be said to be undirected, but there is merit in setting - // the direction from parent to child (1 to many) as this influences graphlib to - // put the parent above the child, which is intuitive + // the direction from parent to child in a one-to-many as this influences graphlib to + // put the parent above the child (does it?), which is intuitive. Most relationships + // in ER diagrams are one-to-many. g = new graphlib.Graph({ multigraph: true, directed: true, compound: false }) .setGraph({ - rankdir: 'TB', + rankdir: 'LR', marginx: 20, marginy: 20, nodesep: 100, @@ -571,31 +311,18 @@ export const draw = function(text, id) { return {}; }); - // Fetch the entities (which will become vertices) - const entities = erDb.getEntities(); - - // Add all the entities to the graph - addEntities(entities, g); - - const relationships = erDb.getRelationships(); - // Add all the relationships as edges on the graph - addRelationships(relationships, g); - - // Set up an SVG group so that we can translate the final graph. - // TODO: This is redundant -just use diagram from above - const svg = d3.select(`[id="${id}"]`); + // Add the entities and relationships to the graph + const entities = addEntities(erDb.getEntities(), g); + const relationships = addRelationships(erDb.getRelationships(), g); dagre.layout(g); // Node and edge positions will be updated - // Run the renderer. This is what draws the final graph. - //const element = d3.select('#' + id + ' g'); - //render(element, g); + // Draw the relationships first because their markers need to be + // clipped by the entity boxes + drawRelationships(svg, relationships, g); + drawEntities(svg, entities, g, id); - //drawFeet(diagram, relationships, g); - drawRelationships(diagram, relationships, g); - drawEntities(diagram, entities, g, id); - - const padding = 8; + const padding = 8; // TODO: move this to config const svgBounds = svg.node().getBBox(); const width = svgBounds.width + padding * 4; diff --git a/src/mermaidAPI.js b/src/mermaidAPI.js index 95dc56e70..fa9e7ece5 100644 --- a/src/mermaidAPI.js +++ b/src/mermaidAPI.js @@ -369,7 +369,7 @@ const config = { /** * Stroke color of box edges and lines */ - stroke: 'purple', + stroke: 'gray', /** * Fill color of entity boxes @@ -377,52 +377,11 @@ const config = { fill: 'honeydew', /** - * Distance of the 'ankle' from the intersection point + * Opacity of entity boxes - if you want to see how the crows feet + * retain their elegant joins to the boxes regardless of the angle of incidence + * then override this to something less than 100% */ - ankleDistance: 35, - - /** - * Distance of the 'heel' from the intersection point - */ - heelDistance: 20, - - /** - * Distance of the side 'toes' perpendicular to the intersection point - */ - toeDistance: 12, - - /** - * The style of the toes on the crow's foot: either 'curved' or 'straight' - */ - toeStyle: 'curved', - - /** - * THE REMAINING CONFIG OPTIONS FOR 'er' DIAGRAMS ARE EXPERIMENTAL AND ARE USEFUL - * DURING DEVELOPMENT BUT WILL PROBABLY BE REMOVED BEFORE E-R DIAGRAMS ARE PRODUCTIONIZED. - * THEY ARE HELPFUL IN DIAGNOSING POSITIONAL AND LAYOUT-RELATED ISSUES; THEY WOULDN'T - * LOOK GOOD ON REAL DIAGRAMS - */ - - // Opacity of entity boxes - helpful when < 100% to see lines 'behind' the box - fillOpacity: '100%', - - // Whether to show dots at important points in the diagram geometry - dots: false, - - // Radius of dots - dotRadius: 1.5, - - // Color of intersection point dots - intersectColor: 'green', - - // Color of 'ankle' dots - ankleColor: 'red', - - // Color of 'heel' dots - heelColor: 'blue', - - // Color of 'toe' dots - toeColor: 'darkorchid' + fillOpacity: '100%' } }; From 9a6e4ba77fe9e906d71c1f7dc0fd379c5a29d3bf Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Tue, 10 Mar 2020 19:14:46 -0700 Subject: [PATCH 74/95] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 781cee7e1..83fe09ec7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ For more information and help in getting started, please view our [documentation With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. -![Image show the two new diagram types](.docs/img/new-diagrams.png) +![Image show the two new diagram types](./docs/img/new-diagrams.png) ## Special note regarding version 8.2 From e6910d3c5208b6f39b5589331764b6fbd38ad373 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Tue, 10 Mar 2020 19:15:44 -0700 Subject: [PATCH 75/95] Update SUMMARY.md --- docs/SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index f40657a49..e4be903f9 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,6 +1,6 @@ # Summary -- [mermaid-README](README.md) +- [mermaid](README.md) - [FAQ](faq.md) - [Breaking changes](breakingChanges.md) - [Usage](usage.md) From 71e0a788d1d6de7abcffaead10c21789fe3180e3 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Tue, 10 Mar 2020 19:16:24 -0700 Subject: [PATCH 76/95] Update mermaidAPI.md --- docs/mermaidAPI.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/mermaidAPI.md b/docs/mermaidAPI.md index bd4f3787f..7b7b68e06 100644 --- a/docs/mermaidAPI.md +++ b/docs/mermaidAPI.md @@ -2,13 +2,14 @@ ## mermaidAPI -This API can be used optionally handle the integration of mermaid to a web page, instead of using the default integration methods provided by mermaid.js. +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**][1] function which, renders the given text based input to a graph/diagram, that is returned as an svg element. +The core of this api is the [**render**][1] function which, given a graph +definition as text, renders the graph/diagram and returns an svg element for the graph. -After it is rendered, it is is then up to the user of the API to make use of the svg, which can be either inserted somewhere in the page or something else altogether. +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 also available. +In addition to the render function, a number of behavioral configuration options are available. ## Configuration From a9570f7298b88358e320ab5ae9b34859d910f75f Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Tue, 10 Mar 2020 19:24:55 -0700 Subject: [PATCH 77/95] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 83fe09ec7..781cee7e1 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ For more information and help in getting started, please view our [documentation With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. -![Image show the two new diagram types](./docs/img/new-diagrams.png) +![Image show the two new diagram types](.docs/img/new-diagrams.png) ## Special note regarding version 8.2 From 83c6aef0783d3bfce2fb49dbd9a957a81fa4e687 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Tue, 10 Mar 2020 19:26:26 -0700 Subject: [PATCH 78/95] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 781cee7e1..83fe09ec7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ For more information and help in getting started, please view our [documentation With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. -![Image show the two new diagram types](.docs/img/new-diagrams.png) +![Image show the two new diagram types](./docs/img/new-diagrams.png) ## Special note regarding version 8.2 From d2685f2544642f7394527f154bd7c6016c19a654 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Tue, 10 Mar 2020 19:42:11 -0700 Subject: [PATCH 79/95] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 83fe09ec7..781cee7e1 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ For more information and help in getting started, please view our [documentation With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. -![Image show the two new diagram types](./docs/img/new-diagrams.png) +![Image show the two new diagram types](.docs/img/new-diagrams.png) ## Special note regarding version 8.2 From 1e498eccb63ac9b7f72d66baec345e3145a7b856 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Wed, 11 Mar 2020 22:17:11 +0000 Subject: [PATCH 80/95] Change rendering algorithm --- src/diagrams/er/erRenderer.js | 124 ++++++++++++++++++++++++---------- src/mermaidAPI.js | 7 +- 2 files changed, 93 insertions(+), 38 deletions(-) diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index 1933ffb95..901fb8568 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -21,6 +21,7 @@ export const setConf = function(cnf) { * @param g The graph that is to be drawn * @returns {Object} The object containing all the entities as properties */ +/* const addEntities = function(entities, g) { const keys = Object.keys(entities); @@ -39,61 +40,91 @@ const addEntities = function(entities, g) { }); return entities; }; - +*/ /** * Use D3 to construct the svg elements for the entities - * @param diagram the svg node that contains the diagram + * @param svgNode the svg node that contains the diagram * @param entities the entities to be drawn * @param g the dagre graph that contains the vertex and edge definitions post-layout */ -const drawEntities = function(diagram, entities, g, svgId) { - // For each vertex in the graph: - // - append the text label centred in the right place - // - get it's bounding box and calculate the size of the enclosing rectangle - // - insert the enclosing rectangle +const drawEntities = function(svgNode, entities, svgId, graph) { + const keys = Object.keys(entities); + let firstOne; - g.nodes().forEach(function(v) { - console.debug('Handling node ', v); + keys.forEach(function(id) { + // Create a group for each entity + const groupNode = svgNode.append('g').attr('id', id); - // Get the centre co-ordinate of the node so that we can centre the entity name - const centre = { x: g.node(v).x, y: g.node(v).y }; + firstOne = firstOne === undefined ? id : firstOne; // Label the entity - this is done first so that we can get the bounding box // which then determines the size of the rectangle - const textId = 'entity-' + v + '-' + svgId; - const textNode = diagram + const textId = 'entity-' + id + '-' + svgId; + const textNode = groupNode .append('text') .attr('id', textId) - .attr('x', centre.x) - .attr('y', centre.y) + .attr('x', 0) + .attr('y', (conf.fontSize + 2 * conf.entityPadding) / 2) .attr('dominant-baseline', 'middle') .attr('text-anchor', 'middle') - .attr('style', 'font-family: ' + getConfig().fontFamily) - .text(v); + .attr('style', 'font-family: ' + getConfig().fontFamily + '; font-size: ' + conf.fontSize) + .text(id); + // Calculate the width and height of the entity const textBBox = textNode.node().getBBox(); const entityWidth = Math.max(conf.minEntityWidth, textBBox.width + conf.entityPadding * 2); const entityHeight = Math.max(conf.minEntityHeight, textBBox.height + conf.entityPadding * 2); - // Add info to the node so that we can retrieve it later when drawing relationships - g.node(v).width = entityWidth; - g.node(v).height = entityHeight; + // Make sure the text gets centred relative to the entity box + textNode.attr('transform', 'translate(' + entityWidth / 2 + ',' + entityHeight / 2 + ')'); // Draw the rectangle - insert it before the text so that the text is not obscured - const rectX = centre.x - entityWidth / 2; - const rectY = centre.y - entityHeight / 2; - diagram + const rectNode = groupNode .insert('rect', '#' + textId) .attr('fill', conf.fill) .attr('fill-opacity', conf.fillOpacity) .attr('stroke', conf.stroke) - .attr('x', rectX) - .attr('y', rectY) + .attr('x', 0) + .attr('y', 0) .attr('width', entityWidth) .attr('height', entityHeight); + + const rectBBox = rectNode.node().getBBox(); + + // Add the entity to the graph + // TODO: revisit this - need to understand properly + graph.setNode(id, { + labelType: 'svg', + width: rectBBox.width, + height: rectBBox.height, + shape: 'rect', + label: document.createElementNS('http://www.w3.org/2000/svg', 'text'), + id: id + }); }); + return firstOne; }; // drawEntities +const adjustEntities = function(svgNode, entities, graph) { + graph.nodes().forEach(function(v) { + if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') { + d3.select('#' + v).attr( + 'transform', + 'translate(' + + (graph.node(v).x - graph.node(v).width / 2) + + ',' + + (graph.node(v).y - graph.node(v).height / 2) + + ' )' + ); + } + }); + return; +}; + +const getEdgeName = function(rel) { + return (rel.entityA + rel.roleA + rel.roleB + rel.entityB).replace(/\s/g, ''); +}; + /** * Add each relationship to the graph * @param relationships the relationships to be added @@ -102,7 +133,7 @@ const drawEntities = function(diagram, entities, g, svgId) { */ const addRelationships = function(relationships, g) { relationships.forEach(function(r) { - g.setEdge(r.entityA, r.entityB, { relationship: r }); + g.setEdge(r.entityA, r.entityB, { relationship: r }, getEdgeName(r)); }); return relationships; }; // addRelationships @@ -110,9 +141,9 @@ const addRelationships = function(relationships, g) { /** * */ -const drawRelationships = function(diagram, relationships, g) { +const drawRelationships = function(diagram, relationships, g, insertId) { relationships.forEach(function(rel) { - drawRelationshipFromLayout(diagram, rel, g); + drawRelationshipFromLayout(diagram, rel, g, insertId); }); }; // drawRelationships @@ -122,9 +153,10 @@ const drawRelationships = function(diagram, relationships, g) { * @param rel the relationship to draw in the svg * @param g the graph containing the edge information */ -const drawRelationshipFromLayout = function(diagram, rel, g) { +const drawRelationshipFromLayout = function(diagram, rel, g, insert) { // Find the edge relating to this relationship - const edge = g.edge({ v: rel.entityA, w: rel.entityB }); + //const edge = g.edge({ v: rel.entityA, w: rel.entityB }); + const edge = g.edge(rel.entityA, rel.entityB, getEdgeName(rel)); // Get a function that will generate the line path const lineFunction = d3 @@ -137,9 +169,9 @@ const drawRelationshipFromLayout = function(diagram, rel, g) { }) .curve(d3.curveBasis); - // Append the line to the diagram node + // Insert the line at the right place const svgPath = diagram - .append('path') + .insert('path', '#' + insert) .attr('d', lineFunction(edge.points)) .attr('stroke', conf.stroke) .attr('fill', 'none'); @@ -281,12 +313,26 @@ export const draw = function(text, id) { logger.debug('Parsing failed'); } - // Get a reference to the diagram node + // Get a reference to the svg node that contains the text const svg = d3.select(`[id='${id}']`); // Add cardinality marker definitions to the svg erMarkers.insertMarkers(svg, conf); + // Now we have to construct the diagram in a specific way: + // --- + // 1. Create all the entities in the svg node at 0,0, but with the correct dimensions (allowing for text content) + // 2. Make sure they are all added to the graph + // 3. Add all the edges (relationships) to the graph aswell + // 4. Let dagre do its magic to layout the graph. This assigns: + // - the centre co-ordinates for each node, bearing in mind the dimensions and edge relationships + // - the path co-ordinates for each edge + // But it has no impact on the svg child nodes - the diagram remains with every entity rooted at 0,0 + // 5. Now assign a transform to each entity in the svg node so that it gets drawn in the correct place, as determined by + // its centre point, which is obtained from the graph, and it's width and height + // 6. And finally, create all the edges in the svg node using information from the graph + // --- + // Create the graph let g; @@ -311,16 +357,20 @@ export const draw = function(text, id) { return {}; }); - // Add the entities and relationships to the graph - const entities = addEntities(erDb.getEntities(), g); + const entities = erDb.getEntities(); + const firstEntity = drawEntities(svg, entities, id, g); + + //addEntities(erDb.getEntities(), g); const relationships = addRelationships(erDb.getRelationships(), g); dagre.layout(g); // Node and edge positions will be updated + adjustEntities(svg, entities, g); + // Draw the relationships first because their markers need to be // clipped by the entity boxes - drawRelationships(svg, relationships, g); - drawEntities(svg, entities, g, id); + drawRelationships(svg, relationships, g, firstEntity); + //drawEntities(svg, entities, id); const padding = 8; // TODO: move this to config diff --git a/src/mermaidAPI.js b/src/mermaidAPI.js index fa9e7ece5..8bfa33a0e 100644 --- a/src/mermaidAPI.js +++ b/src/mermaidAPI.js @@ -381,7 +381,12 @@ const config = { * retain their elegant joins to the boxes regardless of the angle of incidence * then override this to something less than 100% */ - fillOpacity: '100%' + fillOpacity: '100%', + + /** + * Font size + */ + fontSize: '12px' } }; From 96b8dce9821c0f1a40cdaaf37978e4e86af84356 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Wed, 11 Mar 2020 18:59:06 -0700 Subject: [PATCH 81/95] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 781cee7e1..83fe09ec7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ For more information and help in getting started, please view our [documentation With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. -![Image show the two new diagram types](.docs/img/new-diagrams.png) +![Image show the two new diagram types](./docs/img/new-diagrams.png) ## Special note regarding version 8.2 From c9071ff7246ce93e63a3d241a2f64bc5327f69a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2020 23:07:17 +0000 Subject: [PATCH 82/95] Bump acorn from 5.7.3 to 5.7.4 Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4. - [Release notes](https://github.com/acornjs/acorn/releases) - [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6632f0b88..fc5a047b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1681,9 +1681,9 @@ acorn-walk@^6.0.1: integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== acorn@^5.2.1, acorn@^5.5.3: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" - integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== acorn@^6.0.1, acorn@^6.2.1: version "6.4.0" From 10583610bda772ce82645c333bc8ecc8ce09f379 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sat, 14 Mar 2020 08:36:49 +0100 Subject: [PATCH 83/95] Lint fixes --- src/diagrams/flowchart-v2/flowRenderer.js | 1 - src/experimental.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/diagrams/flowchart-v2/flowRenderer.js b/src/diagrams/flowchart-v2/flowRenderer.js index 10250a16c..1baeeb9ad 100644 --- a/src/diagrams/flowchart-v2/flowRenderer.js +++ b/src/diagrams/flowchart-v2/flowRenderer.js @@ -1,6 +1,5 @@ import graphlib from 'graphlib'; import * as d3 from 'd3'; -import dagre from 'dagre'; import flowDb from '../flowchart/flowDb'; import flow from '../flowchart/parser/flow'; diff --git a/src/experimental.js b/src/experimental.js index d34de469d..e6c3bd806 100644 --- a/src/experimental.js +++ b/src/experimental.js @@ -1,5 +1,4 @@ import dagre from 'dagre'; -import graphlib from 'graphlib'; // Create a new directed graph var g = new dagre.graphlib.Graph({ compound: true }); From d86a7ccf3ef38612942e8dd0ffd96428d2dafaa7 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sat, 14 Mar 2020 08:49:30 +0100 Subject: [PATCH 84/95] Restoring docs README --- docs/README.md | 237 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..4fb40e62b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,237 @@ +# Mermaid + +[![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) +[![Coverage Status](https://coveralls.io/repos/github/knsv/mermaid/badge.svg?branch=master)](https://coveralls.io/github/knsv/mermaid?branch=master) +[![Join the chat at https://gitter.im/knsv/mermaid](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +![banner](./img/header.png) + +Generation of diagrams and flowcharts from text in a similar manner as markdown. + +Ever wanted to simplify documentation and avoid heavy tools like Visio when explaining your code? + +This is why mermaid was born, a simple markdown-like script language for generating charts from text via javascript. + +Check out the list of [Integrations and Usages of Mermaid](./integrations.md) + +**Mermaid was nominated and won the JS Open Source Awards (2019) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.** + +## New diagrams in 8.4 + +With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams. + +![Image show the two new diagram types](./img/new-diagrams.png) + +## Special note regarding version 8.2 + +In version 8.2 a security improvement was introduced. A securityLevel configuration was introduced which sets the level of trust to be used on the parsed diagrams. + +- **true**: (default) tags in text are encoded, click functionality is disabled +- false: tags in text are allowed, click functionality is enabled + +Closed issues: + +⚠️ **Note** : This changes the default behaviour of mermaid so that after upgrade to 8.2, if the securityLevel is not configured, tags in flowcharts are encoded as tags and clicking is prohibited. + +If your application is taking resposibility for the diagram source security you can set the securityLevel accordingly. By doing this clicks and tags are again allowed. + +```javascript +mermaidAPI.initialize({ + securityLevel: 'loose' +}); +``` + +**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/knsv/mermaid/issues/866)** + +## Diagrams + +### Flowchart + +``` +graph TD; + A-->B; + A-->C; + B-->D; + C-->D; +``` + +![Flowchart](./img/flow.png) + +### Sequence diagram + +``` +sequenceDiagram + participant Alice + participant Bob + Alice->>John: Hello John, how are you? + loop Healthcheck + John->>John: Fight against hypochondria + end + Note right of John: Rational thoughts
prevail! + John-->>Alice: Great! + John->>Bob: How about you? + Bob-->>John: Jolly good! +``` + +![Sequence diagram](./img/sequence.png) + +### Gantt diagram + +``` +gantt +dateFormat YYYY-MM-DD +title Adding GANTT diagram to mermaid +excludes weekdays 2014-01-10 + +section A section +Completed task :done, des1, 2014-01-06,2014-01-08 +Active task :active, des2, 2014-01-09, 3d +Future task : des3, after des2, 5d +Future task2 : des4, after des3, 5d +``` + +![Gantt diagram](./img/gantt.png) + +### Class diagram - :exclamation: experimental + +``` +classDiagram +Class01 <|-- AveryLongClass : Cool +Class03 *-- Class04 +Class05 o-- Class06 +Class07 .. Class08 +Class09 --> C2 : Where am i? +Class09 --* C3 +Class09 --|> Class07 +Class07 : equals() +Class07 : Object[] elementData +Class01 : size() +Class01 : int chimp +Class01 : int gorilla +Class08 <--> C2: Cool label +``` + +![Class diagram](./img/class.png) + +### Git graph - :exclamation: experimental + +``` +gitGraph: +options +{ + "nodeSpacing": 150, + "nodeRadius": 10 +} +end +commit +branch newbranch +checkout newbranch +commit +commit +checkout master +commit +commit +merge newbranch + +``` + +![Git graph](./img/git.png) + +## Installation + +### CDN + +``` +https://unpkg.com/mermaid@/dist/ +``` + +Replace `` with expected version number. + +Example: https://unpkg.com/mermaid@7.1.0/dist/ + +### Node.js + +``` +yarn add mermaid +``` + +## Documentation + +https://mermaidjs.github.io + +## Sibling projects + +- [mermaid CLI](https://github.com/mermaidjs/mermaid.cli) +- [mermaid live editor](https://github.com/mermaidjs/mermaid-live-editor) +- [mermaid webpack demo](https://github.com/mermaidjs/mermaid-webpack-demo) +- [mermaid Parcel demo](https://github.com/mermaidjs/mermaid-parcel-demo) + +## Request for assistance + +Things are piling up and I have a hard time keeping up. To remedy this +it would be great if we could form a core team of developers to cooperate +with the future development of mermaid. + +As part of this team you would get write access to the repository and would +represent the project when answering questions and issues. + +Together we could continue the work with things like: + +- Adding more types of diagrams like mindmaps, ert diagrams, etc. +- Improving existing diagrams + +Don't hesitate to contact me if you want to get involved. + +## For contributors + +### Setup + +``` +yarn install +``` + +### Build + +``` +yarn build:watch +``` + +### Lint + +``` +yarn lint +``` + +We use [eslint](https://eslint.org/). +We recommend you installing [editor plugins](https://eslint.org/docs/user-guide/integrations) so you can get real time lint result. + +### Test + +``` +yarn test +``` +Manual test in browser: open `dist/index.html` + +### Release + +For those who have the permission to do so: + +Update version number in `package.json`. + +``` +npm publish +``` + +Command above generates files into the `dist` folder and publishes them to npmjs.org. + +## Credits + +Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries! + +Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering. + +_Mermaid was created by Knut Sveidqvist for easier documentation._ + +_[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017._ + +Here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors). From 2d3b02df6a0e03e5c5c5ccb15f48a9e3fdd51a6a Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Mon, 16 Mar 2020 10:08:43 +0000 Subject: [PATCH 85/95] Minor tidy up --- src/diagrams/er/erRenderer.js | 58 +++++++++++++------------- src/diagrams/er/parser/erDiagram.jison | 2 +- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index 901fb8568..85e5df033 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -8,6 +8,12 @@ import { logger } from '../../logger'; import erMarkers from './erMarkers'; const conf = {}; + +/** + * Allows the top-level API module to inject config specific to this renderer, + * storing it in the local conf object. Note that generic config still needs to be + * retrieved using getConfig() imported from the config module + */ export const setConf = function(cnf) { const keys = Object.keys(cnf); for (let i = 0; i < keys.length; i++) { @@ -44,10 +50,11 @@ const addEntities = function(entities, g) { /** * Use D3 to construct the svg elements for the entities * @param svgNode the svg node that contains the diagram - * @param entities the entities to be drawn - * @param g the dagre graph that contains the vertex and edge definitions post-layout + * @param entities The entities to be drawn + * @param g The graph that contains the vertex and edge definitions post-layout + * @return The first entity that was inserted */ -const drawEntities = function(svgNode, entities, svgId, graph) { +const drawEntities = function(svgNode, entities, graph) { const keys = Object.keys(entities); let firstOne; @@ -59,7 +66,7 @@ const drawEntities = function(svgNode, entities, svgId, graph) { // Label the entity - this is done first so that we can get the bounding box // which then determines the size of the rectangle - const textId = 'entity-' + id + '-' + svgId; + const textId = 'entity-' + id; const textNode = groupNode .append('text') .attr('id', textId) @@ -92,20 +99,17 @@ const drawEntities = function(svgNode, entities, svgId, graph) { const rectBBox = rectNode.node().getBBox(); // Add the entity to the graph - // TODO: revisit this - need to understand properly graph.setNode(id, { - labelType: 'svg', width: rectBBox.width, height: rectBBox.height, shape: 'rect', - label: document.createElementNS('http://www.w3.org/2000/svg', 'text'), id: id }); }); return firstOne; }; // drawEntities -const adjustEntities = function(svgNode, entities, graph) { +const adjustEntities = function(svgNode, graph) { graph.nodes().forEach(function(v) { if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') { d3.select('#' + v).attr( @@ -138,24 +142,14 @@ const addRelationships = function(relationships, g) { return relationships; }; // addRelationships -/** - * - */ -const drawRelationships = function(diagram, relationships, g, insertId) { - relationships.forEach(function(rel) { - drawRelationshipFromLayout(diagram, rel, g, insertId); - }); -}; // drawRelationships - /** * Draw a relationship using edge information from the graph - * @param diagram the svg node + * @param svg the svg node * @param rel the relationship to draw in the svg * @param g the graph containing the edge information */ -const drawRelationshipFromLayout = function(diagram, rel, g, insert) { +const drawRelationshipFromLayout = function(svg, rel, g, insert) { // Find the edge relating to this relationship - //const edge = g.edge({ v: rel.entityA, w: rel.entityB }); const edge = g.edge(rel.entityA, rel.entityB, getEdgeName(rel)); // Get a function that will generate the line path @@ -170,7 +164,7 @@ const drawRelationshipFromLayout = function(diagram, rel, g, insert) { .curve(d3.curveBasis); // Insert the line at the right place - const svgPath = diagram + const svgPath = svg .insert('path', '#' + insert) .attr('d', lineFunction(edge.points)) .attr('stroke', conf.stroke) @@ -347,7 +341,7 @@ export const draw = function(text, id) { compound: false }) .setGraph({ - rankdir: 'LR', + rankdir: 'TB', marginx: 20, marginy: 20, nodesep: 100, @@ -357,20 +351,24 @@ export const draw = function(text, id) { return {}; }); - const entities = erDb.getEntities(); - const firstEntity = drawEntities(svg, entities, id, g); + // Draw the entities (at 0,0), returning the first svg node that got + // inserted - this represents the insertion point for relationship paths + const firstEntity = drawEntities(svg, erDb.getEntities(), g); - //addEntities(erDb.getEntities(), g); + // TODO: externalise the addition of entities to the graph - it's a bit 'buried' in the above + + // Add all the relationships to the graph const relationships = addRelationships(erDb.getRelationships(), g); dagre.layout(g); // Node and edge positions will be updated - adjustEntities(svg, entities, g); + // Adjust the positions of the entities so that they adhere to the layout + adjustEntities(svg, g); - // Draw the relationships first because their markers need to be - // clipped by the entity boxes - drawRelationships(svg, relationships, g, firstEntity); - //drawEntities(svg, entities, id); + // Draw the relationships + relationships.forEach(function(rel) { + drawRelationshipFromLayout(svg, rel, g, firstEntity); + }); const padding = 8; // TODO: move this to config diff --git a/src/diagrams/er/parser/erDiagram.jison b/src/diagrams/er/parser/erDiagram.jison index c254c9099..cdd6090cd 100644 --- a/src/diagrams/er/parser/erDiagram.jison +++ b/src/diagrams/er/parser/erDiagram.jison @@ -10,7 +10,7 @@ ["] { this.popState(); } [^"]* { return 'STR'; } "erDiagram" return 'ER_DIAGRAM'; -[A-Za-z][A-Za-z0-9]* return 'ALPHANUM'; +[A-Za-z][A-Za-z0-9\-]* return 'ALPHANUM'; \>\?\-\?\< return 'ZERO_OR_MORE_TO_ZERO_OR_MORE'; \>\?\-\!\< return 'ZERO_OR_MORE_TO_ONE_OR_MORE'; \>\!\-\!\< return 'ONE_OR_MORE_TO_ONE_OR_MORE'; From 50f983871b5b6fddd93cbfab13d557b2f072b5f9 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 17 Mar 2020 09:25:16 +0000 Subject: [PATCH 86/95] Add labels to relationships (needs further tidy up) --- src/diagrams/er/erRenderer.js | 65 ++++++++++++++++++++--------------- src/mermaidAPI.js | 6 ++++ 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index 85e5df033..42df1bb53 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -21,32 +21,6 @@ export const setConf = function(cnf) { } }; -/** - * Function that adds the entities as vertices in the graph prior to laying out - * @param entities The entities to be added to the graph - * @param g The graph that is to be drawn - * @returns {Object} The object containing all the entities as properties - */ -/* -const addEntities = function(entities, g) { - const keys = Object.keys(entities); - - keys.forEach(function(id) { - const entity = entities[id]; - const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); - - g.setNode(entity, { - labelType: 'svg', - width: 100, - height: 75, - shape: 'rect', - label: svgLabel, - id: entity - }); - }); - return entities; -}; -*/ /** * Use D3 to construct the svg elements for the entities * @param svgNode the svg node that contains the diagram @@ -142,6 +116,7 @@ const addRelationships = function(relationships, g) { return relationships; }; // addRelationships +let relCnt = 0; /** * Draw a relationship using edge information from the graph * @param svg the svg node @@ -149,6 +124,8 @@ const addRelationships = function(relationships, g) { * @param g the graph containing the edge information */ const drawRelationshipFromLayout = function(svg, rel, g, insert) { + relCnt++; + // Find the edge relating to this relationship const edge = g.edge(rel.entityA, rel.entityB, getEdgeName(rel)); @@ -287,6 +264,40 @@ const drawRelationshipFromLayout = function(svg, rel, g, insert) { svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')'); break; } + + // Now label the relationship + + // Find the half-way point + const len = svgPath.node().getTotalLength(); + const labelPoint = svgPath.node().getPointAtLength(len * 0.5); + + // Append a text node containing the label + const labelId = 'rel' + relCnt; + + const labelNode = svg + .append('text') + .attr('id', labelId) + .attr('x', labelPoint.x) + .attr('y', labelPoint.y) + .attr('text-anchor', 'middle') + .attr('dominant-baseline', 'middle') + .attr('style', 'font-family: ' + getConfig().fontFamily + '; font-size: ' + conf.fontSize) + .text(rel.roleA); + + // Figure out how big the opaque 'container' rectangle needs to be + const labelBBox = labelNode.node().getBBox(); + + // Insert the opaque rectangle in front of the text label + svg + .insert('rect', '#' + labelId) + .attr('x', labelPoint.x - labelBBox.width / 2) + .attr('y', labelPoint.y - labelBBox.height / 2) + .attr('width', labelBBox.width) + .attr('height', labelBBox.height) + .attr('fill', 'white') + .attr('fill-opacity', '85%'); + + return; }; /** @@ -341,7 +352,7 @@ export const draw = function(text, id) { compound: false }) .setGraph({ - rankdir: 'TB', + rankdir: conf.layoutDirection, marginx: 20, marginy: 20, nodesep: 100, diff --git a/src/mermaidAPI.js b/src/mermaidAPI.js index 8bfa33a0e..0731ae166 100644 --- a/src/mermaidAPI.js +++ b/src/mermaidAPI.js @@ -351,6 +351,12 @@ const config = { * The object containing configurations specific for entity relationship diagrams */ er: { + /** + * Directional bias for layout of entities. Can be either 'TB', 'BT', 'LR', or 'RL', + * where T = top, B = bottom, L = left, and R = right. + */ + layoutDirection: 'TB', + /** * The mimimum width of an entity box */ From 4f50e36e5b1a5a2711dd1209a69162e6726dac56 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 17 Mar 2020 09:48:32 +0000 Subject: [PATCH 87/95] Tidy up for lint --- src/diagrams/er/erRenderer.js | 2 +- src/mermaidAPI.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index 42df1bb53..2e18b678c 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -283,7 +283,7 @@ const drawRelationshipFromLayout = function(svg, rel, g, insert) { .attr('dominant-baseline', 'middle') .attr('style', 'font-family: ' + getConfig().fontFamily + '; font-size: ' + conf.fontSize) .text(rel.roleA); - + // Figure out how big the opaque 'container' rectangle needs to be const labelBBox = labelNode.node().getBBox(); diff --git a/src/mermaidAPI.js b/src/mermaidAPI.js index af52927f0..10ead65cb 100644 --- a/src/mermaidAPI.js +++ b/src/mermaidAPI.js @@ -353,8 +353,8 @@ const config = { */ er: { /** - * Directional bias for layout of entities. Can be either 'TB', 'BT', 'LR', or 'RL', - * where T = top, B = bottom, L = left, and R = right. + * Directional bias for layout of entities. Can be either 'TB', 'BT', 'LR', or 'RL', + * where T = top, B = bottom, L = left, and R = right. */ layoutDirection: 'TB', From a3b97f7c24bc4bdb450755347ce2e308559d06bd Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 17 Mar 2020 10:16:19 +0000 Subject: [PATCH 88/95] Remove roleB - only roleA is labelled --- src/diagrams/er/erDb.js | 10 ++++- src/diagrams/er/erRenderer.js | 2 +- src/diagrams/er/parser/erDiagram.jison | 6 +-- src/diagrams/er/parser/erDiagram.spec.js | 54 ++++++++++++------------ 4 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/diagrams/er/erDb.js b/src/diagrams/er/erDb.js index d43442ad7..556eb2f4f 100644 --- a/src/diagrams/er/erDb.js +++ b/src/diagrams/er/erDb.js @@ -35,12 +35,18 @@ const addEntity = function(name) { const getEntities = () => entities; -const addRelationship = function(entA, rolA, entB, rolB, card) { +/** + * Add a relationship + * @param entA The first entity in the relationship + * @param rolA The role played by the first entity in relation to the second + * @param entB The second entity in the relationship + * @param card The cardinality of the relationship between the two entities + */ +const addRelationship = function(entA, rolA, entB, card) { let rel = { entityA: entA, roleA: rolA, entityB: entB, - roleB: rolB, cardinality: card }; diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index 2e18b678c..1ae5a8d6a 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -100,7 +100,7 @@ const adjustEntities = function(svgNode, graph) { }; const getEdgeName = function(rel) { - return (rel.entityA + rel.roleA + rel.roleB + rel.entityB).replace(/\s/g, ''); + return (rel.entityA + rel.roleA + rel.entityB).replace(/\s/g, ''); }; /** diff --git a/src/diagrams/er/parser/erDiagram.jison b/src/diagrams/er/parser/erDiagram.jison index cdd6090cd..52ac48d3e 100644 --- a/src/diagrams/er/parser/erDiagram.jison +++ b/src/diagrams/er/parser/erDiagram.jison @@ -46,12 +46,12 @@ document ; statement - : entityName relationship entityName ':' role ',' role + : entityName relationship entityName ':' role { yy.addEntity($1); yy.addEntity($3); - yy.addRelationship($1, $5, $3, $7, $2); - /*console.log($1 + $2 + $3 + ':' + $5 + ',' + $7);*/ + yy.addRelationship($1, $5, $3, $2); + /*console.log($1 + $2 + $3 + ':' + $5);*/ }; entityName diff --git a/src/diagrams/er/parser/erDiagram.spec.js b/src/diagrams/er/parser/erDiagram.spec.js index e04c7130d..accdc4a40 100644 --- a/src/diagrams/er/parser/erDiagram.spec.js +++ b/src/diagrams/er/parser/erDiagram.spec.js @@ -15,7 +15,7 @@ describe('when parsing ER diagram it...', function() { }); it('should associate two entities correctly', function() { - erDiagram.parser.parse('erDiagram\nCAR !-?< DRIVER : "insured for", "can drive"'); + erDiagram.parser.parse('erDiagram\nCAR !-?< DRIVER : "insured for"'); const entities = erDb.getEntities(); const relationships = erDb.getRelationships(); const carEntity = entities.CAR; @@ -28,33 +28,31 @@ describe('when parsing ER diagram it...', function() { }); it('should not create duplicate entities', function() { - const line1 = 'CAR !-?< DRIVER : "insured for", "can drive"'; - const line2 = 'DRIVER !-! LICENSE : has, "belongs to"'; + const line1 = 'CAR !-?< DRIVER : "insured for"'; + const line2 = 'DRIVER !-! LICENSE : has'; erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`); const entities = erDb.getEntities(); expect(Object.keys(entities).length).toBe(3); }); - it('should create the roles specified', function() { + it('should create the role specified', function() { const teacherRole = 'is teacher of'; - const studentRole = 'is student of'; - const line1 = `TEACHER >?-?< STUDENT : "${teacherRole}", "${studentRole}"`; + const line1 = `TEACHER >?-?< STUDENT : "${teacherRole}"`; erDiagram.parser.parse(`erDiagram\n${line1}`); const rels = erDb.getRelationships(); expect(rels[0].roleA).toBe(`${teacherRole}`); - expect(rels[0].roleB).toBe(`${studentRole}`); }); it('should allow recursive relationships', function() { - erDiagram.parser.parse('erDiagram\nNODE !-?< NODE : "leads to", "comes from"'); + erDiagram.parser.parse('erDiagram\nNODE !-?< NODE : "leads to"'); expect(Object.keys(erDb.getEntities()).length).toBe(1); }); it('should allow more than one relationship between the same two entities', function() { - const line1 = 'CAR !-?< PERSON : "insured for", "may drive"'; - const line2 = 'CAR >?-! PERSON : "owned by", "owns"'; + const line1 = 'CAR !-?< PERSON : "insured for"'; + const line2 = 'CAR >?-! PERSON : "owned by"'; erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`); const entities = erDb.getEntities(); const rels = erDb.getRelationships(); @@ -67,12 +65,12 @@ describe('when parsing ER diagram it...', function() { /* TODO */ }); - it ('should not allow relationships between the same two entities unless the roles are different', function() { + it ('should not allow multiple relationships between the same two entities unless the roles are different', function() { /* TODO */ }); it('should handle only-one-to-one-or-more relationships', function() { - erDiagram.parser.parse('erDiagram\nA !-!< B : has, has'); + erDiagram.parser.parse('erDiagram\nA !-!< B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -81,7 +79,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle only-one-to-zero-or-more relationships', function() { - erDiagram.parser.parse('erDiagram\nA !-?< B : has, has'); + erDiagram.parser.parse('erDiagram\nA !-?< B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -91,7 +89,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle zero-or-one-to-zero-or-more relationships', function() { - erDiagram.parser.parse('erDiagram\nA ?-?< B : has, has'); + erDiagram.parser.parse('erDiagram\nA ?-?< B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -100,7 +98,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle zero-or-one-to-one-or-more relationships', function() { - erDiagram.parser.parse('erDiagram\nA ?-!< B : has, has'); + erDiagram.parser.parse('erDiagram\nA ?-!< B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -109,7 +107,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle one-or-more-to-only-one relationships', function() { - erDiagram.parser.parse('erDiagram\nA >!-! B : has, has'); + erDiagram.parser.parse('erDiagram\nA >!-! B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -118,7 +116,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle zero-or-more-to-only-one relationships', function() { - erDiagram.parser.parse('erDiagram\nA >?-! B : has, has'); + erDiagram.parser.parse('erDiagram\nA >?-! B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -127,7 +125,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle zero-or-more-to-zero-or-one relationships', function() { - erDiagram.parser.parse('erDiagram\nA >?-? B : has, has'); + erDiagram.parser.parse('erDiagram\nA >?-? B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -136,7 +134,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle one-or-more-to-zero-or-one relationships', function() { - erDiagram.parser.parse('erDiagram\nA >!-? B : has, has'); + erDiagram.parser.parse('erDiagram\nA >!-? B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -145,7 +143,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle zero-or-one-to-only-one relationships', function() { - erDiagram.parser.parse('erDiagram\nA ?-! B : has, has'); + erDiagram.parser.parse('erDiagram\nA ?-! B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -154,7 +152,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle only-one-to-only-one relationships', function() { - erDiagram.parser.parse('erDiagram\nA !-! B : has, has'); + erDiagram.parser.parse('erDiagram\nA !-! B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -163,7 +161,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle only-one-to-zero-or-one relationships', function() { - erDiagram.parser.parse('erDiagram\nA !-? B : has, has'); + erDiagram.parser.parse('erDiagram\nA !-? B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -172,7 +170,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle zero-or-one-to-zero-or-one relationships', function() { - erDiagram.parser.parse('erDiagram\nA ?-? B : has, has'); + erDiagram.parser.parse('erDiagram\nA ?-? B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -181,7 +179,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle zero-or-more-to-zero-or-more relationships', function() { - erDiagram.parser.parse('erDiagram\nA >?-?< B : has, has'); + erDiagram.parser.parse('erDiagram\nA >?-?< B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -190,7 +188,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle one-or-more-to-one-or-more relationships', function() { - erDiagram.parser.parse('erDiagram\nA >!-!< B : has, has'); + erDiagram.parser.parse('erDiagram\nA >!-!< B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -199,7 +197,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle zero-or-more-to-one-or-more relationships', function() { - erDiagram.parser.parse('erDiagram\nA >?-!< B : has, has'); + erDiagram.parser.parse('erDiagram\nA >?-!< B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -208,7 +206,7 @@ describe('when parsing ER diagram it...', function() { }); it('should handle one-or-more-to-zero-or-more relationships', function() { - erDiagram.parser.parse('erDiagram\nA >!-?< B : has, has'); + erDiagram.parser.parse('erDiagram\nA >!-?< B : has'); const rels = erDb.getRelationships(); expect(Object.keys(erDb.getEntities()).length).toBe(2); @@ -217,7 +215,7 @@ describe('when parsing ER diagram it...', function() { }); it('should not accept a syntax error', function() { - const doc = 'erDiagram\nA xxx B : has, has'; + const doc = 'erDiagram\nA xxx B : has'; expect(() => { erDiagram.parser.parse(doc); }).toThrowError(); From 3e76b2374c09e172eef1a00b2a0892b98a526915 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Wed, 18 Mar 2020 09:31:10 +0000 Subject: [PATCH 89/95] Initial documentation for ER diagrams --- docs/entityRelationshipDiagram.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/entityRelationshipDiagram.md diff --git a/docs/entityRelationshipDiagram.md b/docs/entityRelationshipDiagram.md new file mode 100644 index 000000000..cce0e5702 --- /dev/null +++ b/docs/entityRelationshipDiagram.md @@ -0,0 +1,21 @@ +# Entity Relationship Diagrams + +> An entity–relationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types). Wikipedia. + +Mermaid can render ER diagrams +``` +erDiagram + CUSTOMER !-?< ORDER : places + ORDER !-!< LINE-ITEM : contains +``` +```mermaid +erDiagram + CUSTOMER !-?< ORDER : places + ORDER !-!< LINE-ITEM : contains +``` + +## Syntax + +### Entities and Relationships + +To be completed From d3f78299e754dfeec99b732819ccc727455bfd0e Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Wed, 18 Mar 2020 16:53:26 +0000 Subject: [PATCH 90/95] Add integration tests, basic docs, and fix multi-diagram bug --- .../integration/rendering/erDiagram.spec.js | 92 +++++++++++++++++++ docs/entityRelationshipDiagram.md | 10 ++ src/diagrams/er/erRenderer.js | 19 ++-- 3 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 cypress/integration/rendering/erDiagram.spec.js diff --git a/cypress/integration/rendering/erDiagram.spec.js b/cypress/integration/rendering/erDiagram.spec.js new file mode 100644 index 000000000..1f625e2e9 --- /dev/null +++ b/cypress/integration/rendering/erDiagram.spec.js @@ -0,0 +1,92 @@ +/* eslint-env jest */ +import { imgSnapshotTest } from '../../helpers/util'; + +describe('Entity Relationship Diagram', () => { + it('should render a simple ER diagram', () => { + imgSnapshotTest( + ` + erDiagram + CUSTOMER !-?< ORDER : places + ORDER !-!< LINE-ITEM : contains + `, + {logLevel : 1} + ); + cy.get('svg'); + }); + + it('should render an ER diagram with a recursive relationship', () => { + imgSnapshotTest( + ` + erDiagram + CUSTOMER !-?< CUSTOMER : refers + CUSTOMER !-?< ORDER : places + ORDER !-!< LINE-ITEM : contains + `, + {logLevel : 1} + ); + cy.get('svg'); + }); + + it('should render an ER diagram with multiple relationships between the same two entities', () => { + imgSnapshotTest( + ` + erDiagram + CUSTOMER !-!< ADDRESS : "invoiced at" + CUSTOMER !-!< ADDRESS : "receives goods at" + `, + {logLevel : 1} + ); + cy.get('svg'); + }); + + it('should render a cyclical ER diagram', () => { + imgSnapshotTest( + ` + erDiagram + A !-!< B : likes + B !-!< C : likes + C !-!< A : likes + `, + {logLevel : 1} + ); + cy.get('svg'); + + }); + + it('should render a not-so-simple ER diagram', () => { + imgSnapshotTest( + ` + erDiagram + DELIVERY-ADDRESS !-?< ORDER : receives + CUSTOMER >!-!< DELIVERY-ADDRESS : has + CUSTOMER !-?< ORDER : places + CUSTOMER !-?< INVOICE : "liable for" + INVOICE !-!< ORDER : covers + ORDER !-!< ORDER-ITEM : includes + PRODUCT-CATEGORY !-!< PRODUCT : contains + PRODUCT !-?< ORDER-ITEM : "ordered in" + `, + {logLevel : 1} + ); + cy.get('svg'); + }); + + it('should render multiple ER diagrams', () => { + imgSnapshotTest( + [ + ` + erDiagram + CUSTOMER !-?< ORDER : places + ORDER !-!< LINE-ITEM : contains + `, + ` + erDiagram + CUSTOMER !-?< ORDER : places + ORDER !-!< LINE-ITEM : contains + ` + ], + {logLevel : 1} + ); + cy.get('svg'); + }); +}); diff --git a/docs/entityRelationshipDiagram.md b/docs/entityRelationshipDiagram.md index cce0e5702..6b5e46bb3 100644 --- a/docs/entityRelationshipDiagram.md +++ b/docs/entityRelationshipDiagram.md @@ -2,6 +2,8 @@ > An entity–relationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types). Wikipedia. +Note that practitioners of ER modelling almost always refer to entity types simply as entities. For example the CUSTOMER entity type would be referred to simply as the CUSTOMER entity. This is so common it would be inadvisable to do anything else, but technically an entity is an abstract instance of an entity type, and this is what an ER diagram shows - abstract instances, and the relationships between them. This is why entities are always named using singular nouns. + Mermaid can render ER diagrams ``` erDiagram @@ -14,6 +16,14 @@ erDiagram ORDER !-!< LINE-ITEM : contains ``` +Entity names are often capitalised, although there is no accepted standard on this, and it is not required in Mermaid. + +Relationships between entities are represented by lines with end markers representing cardinality. Mermaid uses the most popular crow's foot notation. The crow's foot intuitively conveys the possibility of many instances of the entity that it connects to. + +## Status + +ER diagrams are a new feature in Mermaid and are **experimental**. There are likely to be a few bugs and constraints, and enhancements will be made in due course. + ## Syntax ### Entities and Relationships diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index 1ae5a8d6a..3f37fd296 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -86,14 +86,16 @@ const drawEntities = function(svgNode, entities, graph) { const adjustEntities = function(svgNode, graph) { graph.nodes().forEach(function(v) { if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') { - d3.select('#' + v).attr( - 'transform', - 'translate(' + - (graph.node(v).x - graph.node(v).width / 2) + - ',' + - (graph.node(v).y - graph.node(v).height / 2) + - ' )' - ); + svgNode + .select('#' + v) + .attr( + 'transform', + 'translate(' + + (graph.node(v).x - graph.node(v).width / 2) + + ',' + + (graph.node(v).y - graph.node(v).height / 2) + + ' )' + ); } }); return; @@ -356,6 +358,7 @@ export const draw = function(text, id) { marginx: 20, marginy: 20, nodesep: 100, + edgesep: 100, ranksep: 100 }) .setDefaultEdgeLabel(function() { From d409da2201318f347969420f85480fe896ba5d03 Mon Sep 17 00:00:00 2001 From: Raphael Medaer Date: Tue, 24 Mar 2020 18:24:43 +0100 Subject: [PATCH 91/95] Multiline sequence message This commit allow user to write multiline messages in sequence diagram. For instance: ``` sequenceDiagram Alice->>+John: Hello John
How are you?
I mean...
Are you OK today? Alice->>+John: John, can you hear me? John-->>-Alice: Hi Alice, I can hear you!
Keep calm!
I'm there. John-->>-Alice: And I feel great! ``` --- src/diagrams/sequence/sequenceRenderer.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/diagrams/sequence/sequenceRenderer.js b/src/diagrams/sequence/sequenceRenderer.js index 68482a63e..b46f6873e 100644 --- a/src/diagrams/sequence/sequenceRenderer.js +++ b/src/diagrams/sequence/sequenceRenderer.js @@ -247,6 +247,8 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde const offsetLineCounter = counterBreaklines - 1; const totalOffset = offsetLineCounter * breaklineOffset; + bounds.bumpVerticalPos(totalOffset); + let textWidth = (textElem._groups || textElem)[0][0].getBBox().width; let line; @@ -295,9 +297,9 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde } else { line = g.append('line'); line.attr('x1', startx); - line.attr('y1', verticalPos); + line.attr('y1', verticalPos + totalOffset); line.attr('x2', stopx); - line.attr('y2', verticalPos); + line.attr('y2', verticalPos + totalOffset); bounds.insert( startx, bounds.getVerticalPos() - 10 + totalOffset, From cbdb2e6e6fa3d7fc7b761560c70ed983e8430330 Mon Sep 17 00:00:00 2001 From: Raphael Medaer Date: Tue, 24 Mar 2020 22:14:19 +0100 Subject: [PATCH 92/95] Align left/center/right multiline messages --- docs/mermaidAPI.md | 8 ++++++ src/diagrams/sequence/sequenceRenderer.js | 34 ++++++++++++++++------- src/mermaidAPI.js | 9 ++++++ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/docs/mermaidAPI.md b/docs/mermaidAPI.md index 7b7b68e06..ff25fd2f1 100644 --- a/docs/mermaidAPI.md +++ b/docs/mermaidAPI.md @@ -174,6 +174,14 @@ margin around notes. Space between messages. **Default value 35**. +### messageAlign + +Multiline message alignment. Possible values are: + +- left +- center **default** +- right + ### mirrorActors mirror actors under diagram. diff --git a/src/diagrams/sequence/sequenceRenderer.js b/src/diagrams/sequence/sequenceRenderer.js index b46f6873e..96f99d9c2 100644 --- a/src/diagrams/sequence/sequenceRenderer.js +++ b/src/diagrams/sequence/sequenceRenderer.js @@ -24,6 +24,8 @@ const conf = { noteMargin: 10, // Space between messages messageMargin: 35, + // Multiline message alignment + messageAlign: 'center', // mirror actors under diagram mirrorActors: false, // Depending on css styling this might need adjustment @@ -230,26 +232,38 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde const g = elem.append('g'); const txtCenter = startx + (stopx - startx) / 2; - let textElem; + let textElems = []; let counterBreaklines = 0; let breaklineOffset = 17; const breaklines = msg.message.split(//gi); for (const breakline of breaklines) { - textElem = g - .append('text') // text label for the x axis - .attr('x', txtCenter) - .attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset) - .style('text-anchor', 'middle') - .attr('class', 'messageText') - .text(breakline.trim()); + textElems.push( + g + .append('text') // text label for the x axis + .attr('x', txtCenter) + .attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset) + .style('text-anchor', 'middle') + .attr('class', 'messageText') + .text(breakline.trim()) + ); counterBreaklines++; } const offsetLineCounter = counterBreaklines - 1; const totalOffset = offsetLineCounter * breaklineOffset; - bounds.bumpVerticalPos(totalOffset); + let textWidths = textElems.map(function(textElem) { + return (textElem._groups || textElem)[0][0].getBBox().width; + }); + let textWidth = Math.max(...textWidths); + for (const textElem of textElems) { + if (conf.messageAlign === 'left') { + textElem.attr('x', txtCenter - textWidth / 2).style('text-anchor', 'start'); + } else if (conf.messageAlign === 'right') { + textElem.attr('x', txtCenter + textWidth / 2).style('text-anchor', 'end'); + } + } - let textWidth = (textElem._groups || textElem)[0][0].getBBox().width; + bounds.bumpVerticalPos(totalOffset); let line; if (startx === stopx) { diff --git a/src/mermaidAPI.js b/src/mermaidAPI.js index 10ead65cb..4a90f8134 100644 --- a/src/mermaidAPI.js +++ b/src/mermaidAPI.js @@ -229,6 +229,14 @@ const config = { */ messageMargin: 35, + /** + * Multiline message alignment. Possible values are: + * * left + * * center **default** + * * right + */ + messageAlign: 'center', + /** * mirror actors under diagram. * **Default value true**. @@ -809,6 +817,7 @@ export default mermaidAPI; * boxTextMargin:5, * noteMargin:10, * messageMargin:35, + * messageAlign:'center', * mirrorActors:true, * bottomMarginAdj:1, * useMaxWidth:true, From e0d97d44aa2f15adf8e24093ae4900fb43a49fe6 Mon Sep 17 00:00:00 2001 From: Kathryn DiPippo Date: Sun, 29 Mar 2020 00:55:14 -0400 Subject: [PATCH 93/95] Docs workaround for failing callback() section --- docs/flowchart.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/flowchart.md b/docs/flowchart.md index ee4f0e130..59083fece 100644 --- a/docs/flowchart.md +++ b/docs/flowchart.md @@ -425,10 +425,10 @@ Examples of tooltip usage below: ``` ``` ``` @@ -448,28 +448,30 @@ graph LR; ``` > **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2. +?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/s37cjoau/3/). + Beginners tip, a full example using interactive links in a html context: ```

graph LR; - A-->B; - click A callback "Tooltip" - click B "http://www.github.com" "This is a link" + A-->B; + click A callback "Tooltip" + click B "http://www.github.com" "This is a link"
+