From 9a692b343717001b090ad253566caa054f104525 Mon Sep 17 00:00:00 2001 From: steph Date: Thu, 15 Jun 2023 16:11:57 -0700 Subject: [PATCH 001/105] update latest news section --- docs/news/announcements.md | 6 +++--- docs/news/blog.md | 6 ++++++ packages/mermaid/src/docs/news/announcements.md | 6 +++--- packages/mermaid/src/docs/news/blog.md | 6 ++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/news/announcements.md b/docs/news/announcements.md index a843ae419..1837b33db 100644 --- a/docs/news/announcements.md +++ b/docs/news/announcements.md @@ -6,8 +6,8 @@ # Announcements -## [subhash-halder contributed quadrant charts so you can show your manager what to select - just like the strategy consultants at BCG do](https://www.mermaidchart.com/blog/posts/subhash-halder-contributed-quadrant-charts-so-you-can-show-your-manager-what-to-select-just-like-the-strategy-consultants-at-bcg-do/) +## [Sequence diagrams, the only good thing UML brought to software development](https://www.mermaidchart.com/blog/posts/sequence-diagrams-the-good-thing-uml-brought-to-software-development/) -8 June 2023 · 7 mins +15 June 2023 · 12 mins -A quadrant chart is a useful diagram that helps users visualize data and identify patterns in a data set. +Sequence diagrams really shine when you’re documenting different parts of a system and the various ways these parts interact with each other. diff --git a/docs/news/blog.md b/docs/news/blog.md index 48b669d79..eb9ec6c47 100644 --- a/docs/news/blog.md +++ b/docs/news/blog.md @@ -6,6 +6,12 @@ # Blog +## [Sequence diagrams, the only good thing UML brought to software development](https://www.mermaidchart.com/blog/posts/sequence-diagrams-the-good-thing-uml-brought-to-software-development/) + +15 June 2023 · 12 mins + +Sequence diagrams really shine when you’re documenting different parts of a system and the various ways these parts interact with each other. + ## [subhash-halder contributed quadrant charts so you can show your manager what to select - just like the strategy consultants at BCG do](https://www.mermaidchart.com/blog/posts/subhash-halder-contributed-quadrant-charts-so-you-can-show-your-manager-what-to-select-just-like-the-strategy-consultants-at-bcg-do/) 8 June 2023 · 7 mins diff --git a/packages/mermaid/src/docs/news/announcements.md b/packages/mermaid/src/docs/news/announcements.md index b752d3d57..83dafaab6 100644 --- a/packages/mermaid/src/docs/news/announcements.md +++ b/packages/mermaid/src/docs/news/announcements.md @@ -1,7 +1,7 @@ # Announcements -## [subhash-halder contributed quadrant charts so you can show your manager what to select - just like the strategy consultants at BCG do](https://www.mermaidchart.com/blog/posts/subhash-halder-contributed-quadrant-charts-so-you-can-show-your-manager-what-to-select-just-like-the-strategy-consultants-at-bcg-do/) +## [Sequence diagrams, the only good thing UML brought to software development](https://www.mermaidchart.com/blog/posts/sequence-diagrams-the-good-thing-uml-brought-to-software-development/) -8 June 2023 · 7 mins +15 June 2023 · 12 mins -A quadrant chart is a useful diagram that helps users visualize data and identify patterns in a data set. +Sequence diagrams really shine when you’re documenting different parts of a system and the various ways these parts interact with each other. diff --git a/packages/mermaid/src/docs/news/blog.md b/packages/mermaid/src/docs/news/blog.md index e62a327b4..61d53656d 100644 --- a/packages/mermaid/src/docs/news/blog.md +++ b/packages/mermaid/src/docs/news/blog.md @@ -1,5 +1,11 @@ # Blog +## [Sequence diagrams, the only good thing UML brought to software development](https://www.mermaidchart.com/blog/posts/sequence-diagrams-the-good-thing-uml-brought-to-software-development/) + +15 June 2023 · 12 mins + +Sequence diagrams really shine when you’re documenting different parts of a system and the various ways these parts interact with each other. + ## [subhash-halder contributed quadrant charts so you can show your manager what to select - just like the strategy consultants at BCG do](https://www.mermaidchart.com/blog/posts/subhash-halder-contributed-quadrant-charts-so-you-can-show-your-manager-what-to-select-just-like-the-strategy-consultants-at-bcg-do/) 8 June 2023 · 7 mins From 4018fad4167227087f447de112c221770ff8168a Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 002/105] Ideas about sankey diagram syntax --- .../mermaid/src/sankey/parser/desired syntax | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 packages/mermaid/src/sankey/parser/desired syntax diff --git a/packages/mermaid/src/sankey/parser/desired syntax b/packages/mermaid/src/sankey/parser/desired syntax new file mode 100644 index 000000000..b2b998748 --- /dev/null +++ b/packages/mermaid/src/sankey/parser/desired syntax @@ -0,0 +1,37 @@ +This is example of data for Sakey diagrams from d3 author: +circular graph not supported + +Berlin,Job Applications,102 +Barcelona,Job Applications,39 +Madrid,Job Applications,35 +Amsterdam,Job Applications,15 +Paris,Job Applications,14 +London,Job Applications,6 +Munich,Job Applications,5 +Brussels,Job Applications,4 +Dubai,Job Applications,3 +Dublin,Job Applications,3 +Other Cities,Job Applications,12 +Job Applications,No Response,189 +Job Applications,Responded,49,orange +Responded,Rejected,38 +Responded,Interviewed,11,orange +Interviewed,No Offer,8 +Interviewed,Declined Offer,2 +Interviewed,Accepted Offer,1,orange + +Attributes for + +.nodeSort(null) +.linkSort(null) +.nodeWidth(4) +.nodePadding(20) +.extent([[0, 5], [width, height - 5]]) // margin? + +berlin [label="asdf" color="red"] #node + + +Berlin,Job, +JobApplications[label="Job Applications"] + +madrid=35 -> \ No newline at end of file From 7f19e50403cd0d7453dca2ee435b6cb4fce5c19c Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 003/105] Desired sankey syntax --- .../{desired syntax => desired_syntax.md} | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) rename packages/mermaid/src/sankey/parser/{desired syntax => desired_syntax.md} (52%) diff --git a/packages/mermaid/src/sankey/parser/desired syntax b/packages/mermaid/src/sankey/parser/desired_syntax.md similarity index 52% rename from packages/mermaid/src/sankey/parser/desired syntax rename to packages/mermaid/src/sankey/parser/desired_syntax.md index b2b998748..61543c266 100644 --- a/packages/mermaid/src/sankey/parser/desired syntax +++ b/packages/mermaid/src/sankey/parser/desired_syntax.md @@ -1,6 +1,9 @@ -This is example of data for Sakey diagrams from d3 author: -circular graph not supported +# Sankey diagrams syntax proposal +Circular graphs are not supported by d3. There are some alternatives for that. +This is example of data for Sakey diagrams from d3 author (simple csv): + +```csv Berlin,Job Applications,102 Barcelona,Job Applications,39 Madrid,Job Applications,35 @@ -19,19 +22,53 @@ Responded,Interviewed,11,orange Interviewed,No Offer,8 Interviewed,Declined Offer,2 Interviewed,Accepted Offer,1,orange +``` -Attributes for +We also need graph and node attributes like this: +``` .nodeSort(null) .linkSort(null) .nodeWidth(4) .nodePadding(20) .extent([[0, 5], [width, height - 5]]) // margin? +``` -berlin [label="asdf" color="red"] #node +Also needed: +* coloring strategy (source, target, transition) +* graph alignment (left, right, width) + +Proposed syntax: +``` +a -> 30 -> b +a -> 40 -> c + +a -> { + 30 -> b + 40 -> c +} + +{ + a -> 30 + b -> 40 +} -> c -Berlin,Job, -JobApplications[label="Job Applications"] +a -> { + 30 + 40 +} -> b -madrid=35 -> \ No newline at end of file +a -> 30 -> { + b + c +} + +a -> { + 30 + 40 +} -> { + b + c +} +``` From 116453d2a748462d67c318dbeb90bc5f60f1e45d Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 004/105] Desired syntax sankey --- .../src/sankey/parser/desired_syntax.md | 85 ++++++++++++++++--- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/packages/mermaid/src/sankey/parser/desired_syntax.md b/packages/mermaid/src/sankey/parser/desired_syntax.md index 61543c266..1f0e58b41 100644 --- a/packages/mermaid/src/sankey/parser/desired_syntax.md +++ b/packages/mermaid/src/sankey/parser/desired_syntax.md @@ -1,6 +1,10 @@ # Sankey diagrams syntax proposal -Circular graphs are not supported by d3. There are some alternatives for that. +## What is used now + +**Circular graphs are not supported by d3. There are some alternatives for that.** +**Dropped flows are not supported by d3** + This is example of data for Sakey diagrams from d3 author (simple csv): ```csv @@ -24,46 +28,85 @@ Interviewed,Declined Offer,2 Interviewed,Accepted Offer,1,orange ``` +GoJS uses similar approach +```json +{ +"nodeDataArray": [ +{"key":"Coal reserves", "text":"Coal reserves", "color":"#9d75c2"}, +{"key":"Coal imports", "text":"Coal imports", "color":"#9d75c2"}, +{"key":"Oil reserves", "text":"Oil\nreserves", "color":"#9d75c2"}, +{"key":"Oil imports", "text":"Oil imports", "color":"#9d75c2"} +], +"linkDataArray": [ +{"from":"Coal reserves", "to":"Coal", "width":31}, +{"from":"Coal imports", "to":"Coal", "width":86}, +{"from":"Oil reserves", "to":"Oil", "width":244} +} +``` + +## What do we need + +Mainly we need: +* collection of nodes +* collection of links + We also need graph and node attributes like this: - -``` -.nodeSort(null) -.linkSort(null) -.nodeWidth(4) -.nodePadding(20) -.extent([[0, 5], [width, height - 5]]) // margin? -``` - -Also needed: -* coloring strategy (source, target, transition) +* link sort +* node sort +* coloring strategy for links (source, target, transition) * graph alignment (left, right, width) +* node color +* node title +* node width +* node padding +* graph margin -Proposed syntax: +## Desired syntax + +Graph is a list of flows (or links). +Flow is a sequence `node -> value -> node -> value...` ``` a -> 30 -> b a -> 40 -> c +``` +All outflows from the node can be grouped: +``` a -> { 30 -> b 40 -> c } +``` +All inflows to the node can be grouped too: +``` { a -> 30 b -> 40 } -> c +``` +2 separate streams between 2 nodes they can be grouped as well: +``` a -> { 30 40 } -> b +``` +**Probably ambiguous!** + +Does the sample below mean that total outflow from "a" is 60? +``` a -> 30 -> { b c } +``` +Or does this one mean that total outflow must be 140 (70 to "b" and "c" respectively)? +``` a -> { 30 40 @@ -72,3 +115,19 @@ a -> { c } ``` + +**Overcomplicated** + +Nested: +``` +{ + { + a -> 30 + b -> 40 + } -> c -> 12 + { + d -> 10 + e -> 20 + } -> f -> 43 +} -> g +``` From c2417de5f1cb4a523034c94fe5c4a9bcd31fa9d3 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 005/105] Stated sankey backbone --- .../src/diagram-api/diagram-orchestration.ts | 4 +- packages/mermaid/src/diagrams/sankey/db.js | 69 +++++++++++++++++++ .../mermaid/src/diagrams/sankey/detector.ts | 20 ++++++ .../sankey/parser/desired_syntax.md | 18 +++-- .../src/diagrams/sankey/parser/sankey.jison | 13 ++++ .../mermaid/src/diagrams/sankey/renderer.ts | 27 ++++++++ .../src/diagrams/sankey/sankeyDiagram.ts | 13 ++++ .../mermaid/src/diagrams/sankey/styles.js | 5 ++ 8 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 packages/mermaid/src/diagrams/sankey/db.js create mode 100644 packages/mermaid/src/diagrams/sankey/detector.ts rename packages/mermaid/src/{ => diagrams}/sankey/parser/desired_syntax.md (96%) create mode 100644 packages/mermaid/src/diagrams/sankey/parser/sankey.jison create mode 100644 packages/mermaid/src/diagrams/sankey/renderer.ts create mode 100644 packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts create mode 100644 packages/mermaid/src/diagrams/sankey/styles.js diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index 0253be45d..6c9cc4a03 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -18,6 +18,7 @@ import errorDiagram from '../diagrams/error/errorDiagram.js'; import flowchartElk from '../diagrams/flowchart/elk/detector.js'; import timeline from '../diagrams/timeline/detector.js'; import mindmap from '../diagrams/mindmap/detector.js'; +import sankey from '../diagrams/sankey/detector.js'; import { registerLazyLoadedDiagrams } from './detectType.js'; import { registerDiagram } from './diagramAPI.js'; @@ -79,6 +80,7 @@ export const addDiagrams = () => { stateV2, state, journey, - quadrantChart + quadrantChart, + sankey, ); }; diff --git a/packages/mermaid/src/diagrams/sankey/db.js b/packages/mermaid/src/diagrams/sankey/db.js new file mode 100644 index 000000000..2c86752c6 --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/db.js @@ -0,0 +1,69 @@ +import { log } from '../../logger.js'; +import mermaidAPI from '../../mermaidAPI.js'; +import * as configApi from '../../config.js'; +import common from '../common/common.js'; +import { + setAccTitle, + getAccTitle, + setDiagramTitle, + getDiagramTitle, + getAccDescription, + setAccDescription, + clear as commonClear, +} from '../../commonDb.js'; + +let sections = {}; +let showData = false; + +export const parseDirective = function (statement, context, type) { + mermaidAPI.parseDirective(this, statement, context, type); +}; + +const addSection = function (id, value) { + id = common.sanitizeText(id, configApi.getConfig()); + if (sections[id] === undefined) { + sections[id] = value; + log.debug('Added new section :', id); + } +}; +const getSections = () => sections; + +const setShowData = function (toggle) { + showData = toggle; +}; + +const getShowData = function () { + return showData; +}; + +const cleanupValue = function (value) { + if (value.substring(0, 1) === ':') { + value = value.substring(1).trim(); + return Number(value.trim()); + } else { + return Number(value.trim()); + } +}; + +const clear = function () { + sections = {}; + showData = false; + commonClear(); +}; + +export default { + parseDirective, + getConfig: () => configApi.getConfig().pie, + addSection, + getSections, + cleanupValue, + clear, + setAccTitle, + getAccTitle, + setDiagramTitle, + getDiagramTitle, + setShowData, + getShowData, + getAccDescription, + setAccDescription, +}; diff --git a/packages/mermaid/src/diagrams/sankey/detector.ts b/packages/mermaid/src/diagrams/sankey/detector.ts new file mode 100644 index 000000000..6c0bcdae8 --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/detector.ts @@ -0,0 +1,20 @@ +import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types.js'; + +const id = 'sankey'; + +const detector: DiagramDetector = (txt) => { + return txt.match(/^\s*sankey/) !== null; +}; + +const loader = async () => { + const { diagram } = await import('./sankeyDiagram.js'); + return { id, diagram }; +}; + +const plugin: ExternalDiagramDefinition = { + id, + detector, + loader, +}; + +export default plugin; diff --git a/packages/mermaid/src/sankey/parser/desired_syntax.md b/packages/mermaid/src/diagrams/sankey/parser/desired_syntax.md similarity index 96% rename from packages/mermaid/src/sankey/parser/desired_syntax.md rename to packages/mermaid/src/diagrams/sankey/parser/desired_syntax.md index 1f0e58b41..52057fce9 100644 --- a/packages/mermaid/src/sankey/parser/desired_syntax.md +++ b/packages/mermaid/src/diagrams/sankey/parser/desired_syntax.md @@ -67,7 +67,15 @@ Graph is a list of flows (or links). Flow is a sequence `node -> value -> node -> value...` ``` a -> 30 -> b -a -> 40 -> c +a -> 40 -> b +``` + +2 separate streams between 2 nodes they can be grouped as well: +``` +a -> { + 30 + 40 +} -> b ``` All outflows from the node can be grouped: @@ -86,13 +94,15 @@ All inflows to the node can be grouped too: } -> c ``` -2 separate streams between 2 nodes they can be grouped as well: - +Chaining example: ``` a -> { 30 40 -} -> b +} -> b -> { + 20 -> d + 50 -> e +} ``` **Probably ambiguous!** diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison new file mode 100644 index 000000000..394994927 --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -0,0 +1,13 @@ +%lex +%options case-insensitive + +%% + +/lex + +%start graph + +%% + +graph + : node -> \ No newline at end of file diff --git a/packages/mermaid/src/diagrams/sankey/renderer.ts b/packages/mermaid/src/diagrams/sankey/renderer.ts new file mode 100644 index 000000000..cb1e2d33c --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/renderer.ts @@ -0,0 +1,27 @@ +// import { select, scaleOrdinal, pie as d3pie, arc } from 'd3'; + +// import { select, selectAll } from 'd3'; +// import { log } from '../../logger.js'; +// import common from '../common/common.js'; +// import * as svgDrawCommon from '../common/svgDrawCommon'; +import * as configApi from '../../config.js'; +// import assignWithDepth from '../../assignWithDepth.js'; +// import utils from '../../utils.js'; +// import { configureSvgSize } from '../../setupGraphViewbox.js'; +import { Diagram } from '../../Diagram.js'; + +// import { parseFontSize } from '../../utils.js'; + +const conf = configApi.getConfig(); + +/** + * Draws a Sankey Diagram with the data given in text. + * + * @param text + * @param id + */ +export const draw = function (_text: string, id: string, _version: string, _diagObj: Diagram) {} + +export default { + draw, +}; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts new file mode 100644 index 000000000..e6b502227 --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts @@ -0,0 +1,13 @@ +import { DiagramDefinition } from '../../diagram-api/types.js'; +// @ts-ignore: TODO Fix ts errors +import parser from './parser/sankey.jison'; +import db from './db.js'; +import styles from './styles.js'; +import renderer from './renderer.js'; + +export const diagram: DiagramDefinition = { + parser, + db, + renderer, + styles, +}; diff --git a/packages/mermaid/src/diagrams/sankey/styles.js b/packages/mermaid/src/diagrams/sankey/styles.js new file mode 100644 index 000000000..54881fe6f --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/styles.js @@ -0,0 +1,5 @@ +const getStyles = (options) => + ` +`; + +export default getStyles; From 6b40b394c8b6c35e7c9f57493059561fd492b4f1 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 006/105] Picked state diagram as a sample for sankey --- __mocks__/sankeyRenderer.js | 13 + .../src/diagram-api/diagram-orchestration.ts | 2 +- .../src/diagrams/sankey/parser/sankey.jison | 345 +++- .../mermaid/src/diagrams/sankey/renderer.ts | 27 - .../diagrams/sankey/{db.js => sankeyDB.js} | 0 .../sankey/{detector.ts => sankeyDetector.ts} | 0 .../src/diagrams/sankey/sankeyDiagram.spec.js | 24 + .../src/diagrams/sankey/sankeyDiagram.ts | 4 +- .../src/diagrams/sankey/sankeyRenderer.ts | 1446 +++++++++++++++++ packages/mermaid/src/styles.spec.ts | 2 + 10 files changed, 1829 insertions(+), 34 deletions(-) create mode 100644 __mocks__/sankeyRenderer.js delete mode 100644 packages/mermaid/src/diagrams/sankey/renderer.ts rename packages/mermaid/src/diagrams/sankey/{db.js => sankeyDB.js} (100%) rename packages/mermaid/src/diagrams/sankey/{detector.ts => sankeyDetector.ts} (100%) create mode 100644 packages/mermaid/src/diagrams/sankey/sankeyDiagram.spec.js create mode 100644 packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts diff --git a/__mocks__/sankeyRenderer.js b/__mocks__/sankeyRenderer.js new file mode 100644 index 000000000..76324c93f --- /dev/null +++ b/__mocks__/sankeyRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked Sankey diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index 6c9cc4a03..afebaf1ae 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -18,7 +18,7 @@ import errorDiagram from '../diagrams/error/errorDiagram.js'; import flowchartElk from '../diagrams/flowchart/elk/detector.js'; import timeline from '../diagrams/timeline/detector.js'; import mindmap from '../diagrams/mindmap/detector.js'; -import sankey from '../diagrams/sankey/detector.js'; +import sankey from '../diagrams/sankey/sankeyDetector.js'; import { registerLazyLoadedDiagrams } from './detectType.js'; import { registerDiagram } from './diagramAPI.js'; diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison index 394994927..074cd5975 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -1,13 +1,350 @@ +/** mermaid + * https://mermaidjs.github.io/ + * (c) 2014-2015 Knut Sveidqvist + * MIT license. + * + * Based on js sequence diagrams jison grammr + * https://bramp.github.io/js-sequence-diagrams/ + * (c) 2012-2013 Andrew Brampton (bramp.net) + * Simplified BSD license. + */ %lex + %options case-insensitive +// Special states for recognizing aliases +// A special state for grabbing text up to the first comment/newline +%x ID ALIAS LINE + +// Directive states +%x open_directive type_directive arg_directive +%x acc_title +%x acc_descr +%x acc_descr_multiline %% +\%\%\{ { this.begin('open_directive'); return 'open_directive'; } +((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } +":" { this.popState(); this.begin('arg_directive'); return ':'; } +\}\%\% { this.popState(); this.popState(); return 'close_directive'; } +((?:(?!\}\%\%).|\n)*) return 'arg_directive'; +[\n]+ return 'NEWLINE'; +\s+ /* skip all whitespace */ +((?!\n)\s)+ /* skip same-line whitespace */ +\#[^\n]* /* skip comments */ +\%%(?!\{)[^\n]* /* skip comments */ +[^\}]\%\%[^\n]* /* skip comments */ +[0-9]+(?=[ \n]+) return 'NUM'; +"box" { this.begin('LINE'); return 'box'; } +"participant" { this.begin('ID'); return 'participant'; } +"actor" { this.begin('ID'); return 'participant_actor'; } +[^\->:\n,;]+?([\-]*[^\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } +"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } +(?:) { this.popState(); this.popState(); return 'NEWLINE'; } +"loop" { this.begin('LINE'); return 'loop'; } +"rect" { this.begin('LINE'); return 'rect'; } +"opt" { this.begin('LINE'); return 'opt'; } +"alt" { this.begin('LINE'); return 'alt'; } +"else" { this.begin('LINE'); return 'else'; } +"par" { this.begin('LINE'); return 'par'; } +"par_over" { this.begin('LINE'); return 'par_over'; } +"and" { this.begin('LINE'); return 'and'; } +"critical" { this.begin('LINE'); return 'critical'; } +"option" { this.begin('LINE'); return 'option'; } +"break" { this.begin('LINE'); return 'break'; } +(?:[:]?(?:no)?wrap:)?[^#\n;]* { this.popState(); return 'restOfLine'; } +"end" return 'end'; +"left of" return 'left_of'; +"right of" return 'right_of'; +"links" return 'links'; +"link" return 'link'; +"properties" return 'properties'; +"details" return 'details'; +"over" return 'over'; +"note" return 'note'; +"activate" { this.begin('ID'); return 'activate'; } +"deactivate" { this.begin('ID'); return 'deactivate'; } +"title"\s[^#\n;]+ return 'title'; +"title:"\s[^#\n;]+ return 'legacy_title'; +accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } +(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } +accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } +(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } +accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} +[\}] { this.popState(); } +[^\}]* return "acc_descr_multiline_value"; +"sequenceDiagram" return 'SD'; +"autonumber" return 'autonumber'; +"off" return 'off'; +"," return ','; +";" return 'NEWLINE'; +[^\+\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\->:\n,;]+)* { yytext = yytext.trim(); return 'ACTOR'; } +"->>" return 'SOLID_ARROW'; +"-->>" return 'DOTTED_ARROW'; +"->" return 'SOLID_OPEN_ARROW'; +"-->" return 'DOTTED_OPEN_ARROW'; +\-[x] return 'SOLID_CROSS'; +\-\-[x] return 'DOTTED_CROSS'; +\-[\)] return 'SOLID_POINT'; +\-\-[\)] return 'DOTTED_POINT'; +":"(?:(?:no)?wrap:)?[^#\n;]+ return 'TXT'; +"+" return '+'; +"-" return '-'; +<> return 'NEWLINE'; +. return 'INVALID'; + /lex -%start graph +%left '^' + +%start start + +%% /* language grammar */ + +start + : SPACE start + | NEWLINE start + | directive start + | SD document { yy.apply($2);return $2; } + ; + +document + : /* empty */ { $$ = [] } + | document line {$1.push($2);$$ = $1} + ; + +line + : SPACE statement { $$ = $2 } + | statement { $$ = $1 } + | NEWLINE { $$=[]; } + ; + +box_section + : /* empty */ { $$ = [] } + | box_section box_line {$1.push($2);$$ = $1} + ; + +box_line + : SPACE participant_statement { $$ = $2 } + | participant_statement { $$ = $1 } + | NEWLINE { $$=[]; } + ; + + +directive + : openDirective typeDirective closeDirective 'NEWLINE' + | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' + ; + +statement + : participant_statement + | 'box' restOfLine box_section end + { + $3.unshift({type: 'boxStart', boxData:yy.parseBoxData($2) }); + $3.push({type: 'boxEnd', boxText:$2}); + $$=$3;} + | signal 'NEWLINE' + | autonumber NUM NUM 'NEWLINE' { $$= {type:'sequenceIndex',sequenceIndex: Number($2), sequenceIndexStep:Number($3), sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER};} + | autonumber NUM 'NEWLINE' { $$ = {type:'sequenceIndex',sequenceIndex: Number($2), sequenceIndexStep:1, sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER};} + | autonumber off 'NEWLINE' { $$ = {type:'sequenceIndex', sequenceVisible:false, signalType:yy.LINETYPE.AUTONUMBER};} + | autonumber 'NEWLINE' {$$ = {type:'sequenceIndex', sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER}; } + | 'activate' actor 'NEWLINE' {$$={type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $2};} + | 'deactivate' actor 'NEWLINE' {$$={type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $2};} + | note_statement 'NEWLINE' + | links_statement 'NEWLINE' + | link_statement 'NEWLINE' + | properties_statement 'NEWLINE' + | details_statement 'NEWLINE' + | title {yy.setDiagramTitle($1.substring(6));$$=$1.substring(6);} + | legacy_title {yy.setDiagramTitle($1.substring(7));$$=$1.substring(7);} + | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } + | acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); } + | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } + | 'loop' restOfLine document end + { + $3.unshift({type: 'loopStart', loopText:yy.parseMessage($2), signalType: yy.LINETYPE.LOOP_START}); + $3.push({type: 'loopEnd', loopText:$2, signalType: yy.LINETYPE.LOOP_END}); + $$=$3;} + | 'rect' restOfLine document end + { + $3.unshift({type: 'rectStart', color:yy.parseMessage($2), signalType: yy.LINETYPE.RECT_START }); + $3.push({type: 'rectEnd', color:yy.parseMessage($2), signalType: yy.LINETYPE.RECT_END }); + $$=$3;} + | opt restOfLine document end + { + $3.unshift({type: 'optStart', optText:yy.parseMessage($2), signalType: yy.LINETYPE.OPT_START}); + $3.push({type: 'optEnd', optText:yy.parseMessage($2), signalType: yy.LINETYPE.OPT_END}); + $$=$3;} + | alt restOfLine else_sections end + { + // Alt start + $3.unshift({type: 'altStart', altText:yy.parseMessage($2), signalType: yy.LINETYPE.ALT_START}); + // Content in alt is already in $3 + // End + $3.push({type: 'altEnd', signalType: yy.LINETYPE.ALT_END}); + $$=$3;} + | par restOfLine par_sections end + { + // Parallel start + $3.unshift({type: 'parStart', parText:yy.parseMessage($2), signalType: yy.LINETYPE.PAR_START}); + // Content in par is already in $3 + // End + $3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END}); + $$=$3;} + | par_over restOfLine par_sections end + { + // Parallel (overlapped) start + $3.unshift({type: 'parStart', parText:yy.parseMessage($2), signalType: yy.LINETYPE.PAR_OVER_START}); + // Content in par is already in $3 + // End + $3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END}); + $$=$3;} + | critical restOfLine option_sections end + { + // critical start + $3.unshift({type: 'criticalStart', criticalText:yy.parseMessage($2), signalType: yy.LINETYPE.CRITICAL_START}); + // Content in critical is already in $3 + // critical end + $3.push({type: 'criticalEnd', signalType: yy.LINETYPE.CRITICAL_END}); + $$=$3;} + | break restOfLine document end + { + $3.unshift({type: 'breakStart', breakText:yy.parseMessage($2), signalType: yy.LINETYPE.BREAK_START}); + $3.push({type: 'breakEnd', optText:yy.parseMessage($2), signalType: yy.LINETYPE.BREAK_END}); + $$=$3;} + | directive + ; + +option_sections + : document + | document option restOfLine option_sections + { $$ = $1.concat([{type: 'option', optionText:yy.parseMessage($3), signalType: yy.LINETYPE.CRITICAL_OPTION}, $4]); } + ; + +par_sections + : document + | document and restOfLine par_sections + { $$ = $1.concat([{type: 'and', parText:yy.parseMessage($3), signalType: yy.LINETYPE.PAR_AND}, $4]); } + ; + +else_sections + : document + | document else restOfLine else_sections + { $$ = $1.concat([{type: 'else', altText:yy.parseMessage($3), signalType: yy.LINETYPE.ALT_ELSE}, $4]); } + ; + +participant_statement + : 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;} + | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;} + ; + +note_statement + : 'note' placement actor text2 + { + $$ = [$3, {type:'addNote', placement:$2, actor:$3.actor, text:$4}];} + | 'note' 'over' actor_pair text2 + { + // Coerce actor_pair into a [to, from, ...] array + $2 = [].concat($3, $3).slice(0, 2); + $2[0] = $2[0].actor; + $2[1] = $2[1].actor; + $$ = [$3, {type:'addNote', placement:yy.PLACEMENT.OVER, actor:$2.slice(0, 2), text:$4}];} + ; + +links_statement + : 'links' actor text2 + { + $$ = [$2, {type:'addLinks', actor:$2.actor, text:$3}]; + } + ; + +link_statement + : 'link' actor text2 + { + $$ = [$2, {type:'addALink', actor:$2.actor, text:$3}]; + } + ; + +properties_statement + : 'properties' actor text2 + { + $$ = [$2, {type:'addProperties', actor:$2.actor, text:$3}]; + } + ; + +details_statement + : 'details' actor text2 + { + $$ = [$2, {type:'addDetails', actor:$2.actor, text:$3}]; + } + ; + +spaceList + : SPACE spaceList + | SPACE + ; +actor_pair + : actor ',' actor { $$ = [$1, $3]; } + | actor { $$ = $1; } + ; + +placement + : 'left_of' { $$ = yy.PLACEMENT.LEFTOF; } + | 'right_of' { $$ = yy.PLACEMENT.RIGHTOF; } + ; + +signal + : actor signaltype '+' actor text2 + { $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$2, msg:$5}, + {type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $4} + ]} + | actor signaltype '-' actor text2 + { $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$2, msg:$5}, + {type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $1} + ]} + | actor signaltype actor text2 + { $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]} + ; + +// actor +// : actor_participant +// | actor_actor +// ; + +actor: ACTOR {$$={ type: 'addParticipant', actor:$1}}; +// actor_actor: ACTOR {$$={type: 'addActor', actor:$1}}; + +signaltype + : SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; } + | DOTTED_OPEN_ARROW { $$ = yy.LINETYPE.DOTTED_OPEN; } + | SOLID_ARROW { $$ = yy.LINETYPE.SOLID; } + | DOTTED_ARROW { $$ = yy.LINETYPE.DOTTED; } + | SOLID_CROSS { $$ = yy.LINETYPE.SOLID_CROSS; } + | DOTTED_CROSS { $$ = yy.LINETYPE.DOTTED_CROSS; } + | SOLID_POINT { $$ = yy.LINETYPE.SOLID_POINT; } + | DOTTED_POINT { $$ = yy.LINETYPE.DOTTED_POINT; } + ; + +text2 + : TXT {$$ = yy.parseMessage($1.trim().substring(1)) } + ; + +openDirective + : open_directive { yy.parseDirective('%%{', 'open_directive'); } + ; + +typeDirective + : type_directive { yy.parseDirective($1, 'type_directive'); } + ; + +argDirective + : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } + ; + +closeDirective + : close_directive { yy.parseDirective('}%%', 'close_directive', 'sequence'); } + ; %% - -graph - : node -> \ No newline at end of file diff --git a/packages/mermaid/src/diagrams/sankey/renderer.ts b/packages/mermaid/src/diagrams/sankey/renderer.ts deleted file mode 100644 index cb1e2d33c..000000000 --- a/packages/mermaid/src/diagrams/sankey/renderer.ts +++ /dev/null @@ -1,27 +0,0 @@ -// import { select, scaleOrdinal, pie as d3pie, arc } from 'd3'; - -// import { select, selectAll } from 'd3'; -// import { log } from '../../logger.js'; -// import common from '../common/common.js'; -// import * as svgDrawCommon from '../common/svgDrawCommon'; -import * as configApi from '../../config.js'; -// import assignWithDepth from '../../assignWithDepth.js'; -// import utils from '../../utils.js'; -// import { configureSvgSize } from '../../setupGraphViewbox.js'; -import { Diagram } from '../../Diagram.js'; - -// import { parseFontSize } from '../../utils.js'; - -const conf = configApi.getConfig(); - -/** - * Draws a Sankey Diagram with the data given in text. - * - * @param text - * @param id - */ -export const draw = function (_text: string, id: string, _version: string, _diagObj: Diagram) {} - -export default { - draw, -}; diff --git a/packages/mermaid/src/diagrams/sankey/db.js b/packages/mermaid/src/diagrams/sankey/sankeyDB.js similarity index 100% rename from packages/mermaid/src/diagrams/sankey/db.js rename to packages/mermaid/src/diagrams/sankey/sankeyDB.js diff --git a/packages/mermaid/src/diagrams/sankey/detector.ts b/packages/mermaid/src/diagrams/sankey/sankeyDetector.ts similarity index 100% rename from packages/mermaid/src/diagrams/sankey/detector.ts rename to packages/mermaid/src/diagrams/sankey/sankeyDetector.ts diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.spec.js b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.spec.js new file mode 100644 index 000000000..ee9d0e23e --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.spec.js @@ -0,0 +1,24 @@ +import diagram from './parser/sankey.jison'; +import { parser } from './parser/sankey.jison'; +import db from './sankeyDB.js'; + +describe('state diagram V2, ', function () { + // TODO - these examples should be put into ./parser/stateDiagram.spec.js + describe('when parsing an info graph it', function () { + beforeEach(function () { + parser.yy = stateDb; + diagram.parser.yy = db; + diagram.parser.yy.clear(); + }); + + it('super simple', function () { + const str = ` + stateDiagram-v2 + [*] --> State1 + State1 --> [*] + `; + + parser.parse(str); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts index e6b502227..e7a46d8bc 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts @@ -1,9 +1,9 @@ import { DiagramDefinition } from '../../diagram-api/types.js'; // @ts-ignore: TODO Fix ts errors import parser from './parser/sankey.jison'; -import db from './db.js'; +import db from './sankeyDB.js'; import styles from './styles.js'; -import renderer from './renderer.js'; +import renderer from './sankeyRenderer.js'; export const diagram: DiagramDefinition = { parser, diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts new file mode 100644 index 000000000..243fbddad --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -0,0 +1,1446 @@ +// @ts-nocheck TODO: fix file +import { select, selectAll } from 'd3'; +import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw.js'; +import { log } from '../../logger.js'; +import common from '../common/common.js'; +import * as svgDrawCommon from '../common/svgDrawCommon.js'; +import * as configApi from '../../config.js'; +import assignWithDepth from '../../assignWithDepth.js'; +import utils from '../../utils.js'; +import { configureSvgSize } from '../../setupGraphViewbox.js'; +import { Diagram } from '../../Diagram.js'; + +let conf = {}; + +export const bounds = { + data: { + startx: undefined, + stopx: undefined, + starty: undefined, + stopy: undefined, + }, + verticalPos: 0, + sequenceItems: [], + activations: [], + models: { + getHeight: function () { + return ( + Math.max.apply( + null, + this.actors.length === 0 ? [0] : this.actors.map((actor) => actor.height || 0) + ) + + (this.loops.length === 0 + ? 0 + : this.loops.map((it) => it.height || 0).reduce((acc, h) => acc + h)) + + (this.messages.length === 0 + ? 0 + : this.messages.map((it) => it.height || 0).reduce((acc, h) => acc + h)) + + (this.notes.length === 0 + ? 0 + : this.notes.map((it) => it.height || 0).reduce((acc, h) => acc + h)) + ); + }, + clear: function () { + this.actors = []; + this.boxes = []; + this.loops = []; + this.messages = []; + this.notes = []; + }, + addBox: function (boxModel) { + this.boxes.push(boxModel); + }, + addActor: function (actorModel) { + this.actors.push(actorModel); + }, + addLoop: function (loopModel) { + this.loops.push(loopModel); + }, + addMessage: function (msgModel) { + this.messages.push(msgModel); + }, + addNote: function (noteModel) { + this.notes.push(noteModel); + }, + lastActor: function () { + return this.actors[this.actors.length - 1]; + }, + lastLoop: function () { + return this.loops[this.loops.length - 1]; + }, + lastMessage: function () { + return this.messages[this.messages.length - 1]; + }, + lastNote: function () { + return this.notes[this.notes.length - 1]; + }, + actors: [], + boxes: [], + loops: [], + messages: [], + notes: [], + }, + init: function () { + this.sequenceItems = []; + this.activations = []; + this.models.clear(); + this.data = { + startx: undefined, + stopx: undefined, + starty: undefined, + stopy: undefined, + }; + this.verticalPos = 0; + setConf(configApi.getConfig()); + }, + updateVal: function (obj, key, val, fun) { + if (obj[key] === undefined) { + obj[key] = val; + } else { + obj[key] = fun(val, obj[key]); + } + }, + updateBounds: function (startx, starty, stopx, stopy) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const _self = this; + let cnt = 0; + /** @param type - Either `activation` or `undefined` */ + function updateFn(type?: 'activation') { + return function updateItemBounds(item) { + cnt++; + // The loop sequenceItems is a stack so the biggest margins in the beginning of the sequenceItems + const n = _self.sequenceItems.length - cnt + 1; + + _self.updateVal(item, 'starty', starty - n * conf.boxMargin, Math.min); + _self.updateVal(item, 'stopy', stopy + n * conf.boxMargin, Math.max); + + _self.updateVal(bounds.data, 'startx', startx - n * conf.boxMargin, Math.min); + _self.updateVal(bounds.data, 'stopx', stopx + n * conf.boxMargin, Math.max); + + if (!(type === 'activation')) { + _self.updateVal(item, 'startx', startx - n * conf.boxMargin, Math.min); + _self.updateVal(item, 'stopx', stopx + n * conf.boxMargin, Math.max); + + _self.updateVal(bounds.data, 'starty', starty - n * conf.boxMargin, Math.min); + _self.updateVal(bounds.data, 'stopy', stopy + n * conf.boxMargin, Math.max); + } + }; + } + + this.sequenceItems.forEach(updateFn()); + this.activations.forEach(updateFn('activation')); + }, + insert: function (startx, starty, stopx, stopy) { + const _startx = common.getMin(startx, stopx); + const _stopx = common.getMax(startx, stopx); + const _starty = common.getMin(starty, stopy); + const _stopy = common.getMax(starty, stopy); + + this.updateVal(bounds.data, 'startx', _startx, Math.min); + this.updateVal(bounds.data, 'starty', _starty, Math.min); + this.updateVal(bounds.data, 'stopx', _stopx, Math.max); + this.updateVal(bounds.data, 'stopy', _stopy, Math.max); + + this.updateBounds(_startx, _starty, _stopx, _stopy); + }, + newActivation: function (message, diagram, actors) { + const actorRect = actors[message.from.actor]; + const stackedSize = actorActivations(message.from.actor).length || 0; + const x = actorRect.x + actorRect.width / 2 + ((stackedSize - 1) * conf.activationWidth) / 2; + this.activations.push({ + startx: x, + starty: this.verticalPos + 2, + stopx: x + conf.activationWidth, + stopy: undefined, + actor: message.from.actor, + anchored: svgDraw.anchorElement(diagram), + }); + }, + endActivation: function (message) { + // find most recent activation for given actor + const lastActorActivationIdx = this.activations + .map(function (activation) { + return activation.actor; + }) + .lastIndexOf(message.from.actor); + return this.activations.splice(lastActorActivationIdx, 1)[0]; + }, + createLoop: function (title = { message: undefined, wrap: false, width: undefined }, fill) { + return { + startx: undefined, + starty: this.verticalPos, + stopx: undefined, + stopy: undefined, + title: title.message, + wrap: title.wrap, + width: title.width, + height: 0, + fill: fill, + }; + }, + newLoop: function (title = { message: undefined, wrap: false, width: undefined }, fill) { + this.sequenceItems.push(this.createLoop(title, fill)); + }, + endLoop: function () { + return this.sequenceItems.pop(); + }, + isLoopOverlap: function () { + return this.sequenceItems.length + ? this.sequenceItems[this.sequenceItems.length - 1].overlap + : false; + }, + addSectionToLoop: function (message) { + const loop = this.sequenceItems.pop(); + loop.sections = loop.sections || []; + loop.sectionTitles = loop.sectionTitles || []; + loop.sections.push({ y: bounds.getVerticalPos(), height: 0 }); + loop.sectionTitles.push(message); + this.sequenceItems.push(loop); + }, + saveVerticalPos: function () { + if (this.isLoopOverlap()) { + this.savedVerticalPos = this.verticalPos; + } + }, + resetVerticalPos: function () { + if (this.isLoopOverlap()) { + this.verticalPos = this.savedVerticalPos; + } + }, + bumpVerticalPos: function (bump) { + this.verticalPos = this.verticalPos + bump; + this.data.stopy = common.getMax(this.data.stopy, this.verticalPos); + }, + getVerticalPos: function () { + return this.verticalPos; + }, + getBounds: function () { + return { bounds: this.data, models: this.models }; + }, +}; + +/** Options for drawing a note in {@link drawNote} */ +interface NoteModel { + /** x axis start position */ + startx: number; + /** y axis position */ + starty: number; + /** the message to be shown */ + message: string; + /** Set this with a custom width to override the default configured width. */ + width: number; +} + +/** + * Draws an note in the diagram with the attached line + * + * @param elem - The diagram to draw to. + * @param noteModel - Note model options. + */ +const drawNote = function (elem: any, noteModel: NoteModel) { + bounds.bumpVerticalPos(conf.boxMargin); + noteModel.height = conf.boxMargin; + noteModel.starty = bounds.getVerticalPos(); + const rect = svgDrawCommon.getNoteRect(); + rect.x = noteModel.startx; + rect.y = noteModel.starty; + rect.width = noteModel.width || conf.width; + rect.class = 'note'; + + const g = elem.append('g'); + const rectElem = svgDraw.drawRect(g, rect); + const textObj = svgDrawCommon.getTextObj(); + textObj.x = noteModel.startx; + textObj.y = noteModel.starty; + textObj.width = rect.width; + textObj.dy = '1em'; + textObj.text = noteModel.message; + textObj.class = 'noteText'; + textObj.fontFamily = conf.noteFontFamily; + textObj.fontSize = conf.noteFontSize; + textObj.fontWeight = conf.noteFontWeight; + textObj.anchor = conf.noteAlign; + textObj.textMargin = conf.noteMargin; + textObj.valign = 'center'; + + const textElem = drawText(g, textObj); + + const textHeight = Math.round( + textElem + .map((te) => (te._groups || te)[0][0].getBBox().height) + .reduce((acc, curr) => acc + curr) + ); + + rectElem.attr('height', textHeight + 2 * conf.noteMargin); + noteModel.height += textHeight + 2 * conf.noteMargin; + bounds.bumpVerticalPos(textHeight + 2 * conf.noteMargin); + noteModel.stopy = noteModel.starty + textHeight + 2 * conf.noteMargin; + noteModel.stopx = noteModel.startx + rect.width; + bounds.insert(noteModel.startx, noteModel.starty, noteModel.stopx, noteModel.stopy); + bounds.models.addNote(noteModel); +}; + +const messageFont = (cnf) => { + return { + fontFamily: cnf.messageFontFamily, + fontSize: cnf.messageFontSize, + fontWeight: cnf.messageFontWeight, + }; +}; +const noteFont = (cnf) => { + return { + fontFamily: cnf.noteFontFamily, + fontSize: cnf.noteFontSize, + fontWeight: cnf.noteFontWeight, + }; +}; +const actorFont = (cnf) => { + return { + fontFamily: cnf.actorFontFamily, + fontSize: cnf.actorFontSize, + fontWeight: cnf.actorFontWeight, + }; +}; + +/** + * Process a message by adding its dimensions to the bound. It returns the Y coordinate of the + * message so it can be drawn later. We do not draw the message at this point so the arrowhead can + * be on top of the activation box. + * + * @param _diagram - The parent of the message element. + * @param msgModel - The model containing fields describing a message + * @returns `lineStartY` - The Y coordinate at which the message line starts + */ +function boundMessage(_diagram, msgModel): number { + bounds.bumpVerticalPos(10); + const { startx, stopx, message } = msgModel; + const lines = common.splitBreaks(message).length; + const textDims = utils.calculateTextDimensions(message, messageFont(conf)); + const lineHeight = textDims.height / lines; + msgModel.height += lineHeight; + + bounds.bumpVerticalPos(lineHeight); + + let lineStartY; + let totalOffset = textDims.height - 10; + const textWidth = textDims.width; + + if (startx === stopx) { + lineStartY = bounds.getVerticalPos() + totalOffset; + if (!conf.rightAngles) { + totalOffset += conf.boxMargin; + lineStartY = bounds.getVerticalPos() + totalOffset; + } + totalOffset += 30; + const dx = common.getMax(textWidth / 2, conf.width / 2); + bounds.insert( + startx - dx, + bounds.getVerticalPos() - 10 + totalOffset, + stopx + dx, + bounds.getVerticalPos() + 30 + totalOffset + ); + } else { + totalOffset += conf.boxMargin; + lineStartY = bounds.getVerticalPos() + totalOffset; + bounds.insert(startx, lineStartY - 10, stopx, lineStartY); + } + bounds.bumpVerticalPos(totalOffset); + msgModel.height += totalOffset; + msgModel.stopy = msgModel.starty + msgModel.height; + bounds.insert(msgModel.fromBounds, msgModel.starty, msgModel.toBounds, msgModel.stopy); + + return lineStartY; +} + +/** + * Draws a message. Note that the bounds have previously been updated by boundMessage. + * + * @param diagram - The parent of the message element + * @param msgModel - The model containing fields describing a message + * @param lineStartY - The Y coordinate at which the message line starts + * @param diagObj - The diagram object. + */ +const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Diagram) { + const { startx, stopx, starty, message, type, sequenceIndex, sequenceVisible } = msgModel; + const textDims = utils.calculateTextDimensions(message, messageFont(conf)); + const textObj = svgDrawCommon.getTextObj(); + textObj.x = startx; + textObj.y = starty + 10; + textObj.width = stopx - startx; + textObj.class = 'messageText'; + textObj.dy = '1em'; + textObj.text = message; + textObj.fontFamily = conf.messageFontFamily; + textObj.fontSize = conf.messageFontSize; + textObj.fontWeight = conf.messageFontWeight; + textObj.anchor = conf.messageAlign; + textObj.valign = 'center'; + textObj.textMargin = conf.wrapPadding; + textObj.tspan = false; + + drawText(diagram, textObj); + + const textWidth = textDims.width; + + let line; + if (startx === stopx) { + if (conf.rightAngles) { + line = diagram + .append('path') + .attr( + 'd', + `M ${startx},${lineStartY} H ${ + startx + common.getMax(conf.width / 2, textWidth / 2) + } V ${lineStartY + 25} H ${startx}` + ); + } else { + line = diagram + .append('path') + .attr( + 'd', + 'M ' + + startx + + ',' + + lineStartY + + ' C ' + + (startx + 60) + + ',' + + (lineStartY - 10) + + ' ' + + (startx + 60) + + ',' + + (lineStartY + 30) + + ' ' + + startx + + ',' + + (lineStartY + 20) + ); + } + } else { + line = diagram.append('line'); + line.attr('x1', startx); + line.attr('y1', lineStartY); + line.attr('x2', stopx); + line.attr('y2', lineStartY); + } + // Make an SVG Container + // Draw the line + if ( + type === diagObj.db.LINETYPE.DOTTED || + type === diagObj.db.LINETYPE.DOTTED_CROSS || + type === diagObj.db.LINETYPE.DOTTED_POINT || + type === diagObj.db.LINETYPE.DOTTED_OPEN + ) { + line.style('stroke-dasharray', '3, 3'); + line.attr('class', 'messageLine1'); + } else { + line.attr('class', 'messageLine0'); + } + + 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, '\\)'); + } + + line.attr('stroke-width', 2); + line.attr('stroke', 'none'); // handled by theme/css anyway + line.style('fill', 'none'); // remove any fill colour + if (type === diagObj.db.LINETYPE.SOLID || type === diagObj.db.LINETYPE.DOTTED) { + line.attr('marker-end', 'url(' + url + '#arrowhead)'); + } + if (type === diagObj.db.LINETYPE.SOLID_POINT || type === diagObj.db.LINETYPE.DOTTED_POINT) { + line.attr('marker-end', 'url(' + url + '#filled-head)'); + } + + if (type === diagObj.db.LINETYPE.SOLID_CROSS || type === diagObj.db.LINETYPE.DOTTED_CROSS) { + line.attr('marker-end', 'url(' + url + '#crosshead)'); + } + + // add node number + if (sequenceVisible || conf.showSequenceNumbers) { + line.attr('marker-start', 'url(' + url + '#sequencenumber)'); + diagram + .append('text') + .attr('x', startx) + .attr('y', lineStartY + 4) + .attr('font-family', 'sans-serif') + .attr('font-size', '12px') + .attr('text-anchor', 'middle') + .attr('class', 'sequenceNumber') + .text(sequenceIndex); + } +}; + +export const drawActors = function ( + diagram, + actors, + actorKeys, + verticalPos, + configuration, + messages, + isFooter +) { + if (configuration.hideUnusedParticipants === true) { + const newActors = new Set(); + messages.forEach((message) => { + newActors.add(message.from); + newActors.add(message.to); + }); + actorKeys = actorKeys.filter((actorKey) => newActors.has(actorKey)); + } + + // Draw the actors + let prevWidth = 0; + let prevMargin = 0; + let maxHeight = 0; + let prevBox = undefined; + + for (const actorKey of actorKeys) { + const actor = actors[actorKey]; + const box = actor.box; + + // end of box + if (prevBox && prevBox != box) { + if (!isFooter) { + bounds.models.addBox(prevBox); + } + prevMargin += conf.boxMargin + prevBox.margin; + } + + // new box + if (box && box != prevBox) { + if (!isFooter) { + box.x = prevWidth + prevMargin; + box.y = verticalPos; + } + prevMargin += box.margin; + } + + // Add some rendering data to the object + actor.width = actor.width || conf.width; + actor.height = common.getMax(actor.height || conf.height, conf.height); + actor.margin = actor.margin || conf.actorMargin; + + actor.x = prevWidth + prevMargin; + actor.y = bounds.getVerticalPos(); + + // Draw the box with the attached line + const height = svgDraw.drawActor(diagram, actor, conf, isFooter); + maxHeight = common.getMax(maxHeight, height); + bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height); + + prevWidth += actor.width + prevMargin; + if (actor.box) { + actor.box.width = prevWidth + box.margin - actor.box.x; + } + prevMargin = actor.margin; + prevBox = actor.box; + bounds.models.addActor(actor); + } + + // end of box + if (prevBox && !isFooter) { + bounds.models.addBox(prevBox); + } + + // Add a margin between the actor boxes and the first arrow + bounds.bumpVerticalPos(maxHeight); +}; + +export const drawActorsPopup = function (diagram, actors, actorKeys, doc) { + let maxHeight = 0; + let maxWidth = 0; + for (const actorKey of actorKeys) { + const actor = actors[actorKey]; + const minMenuWidth = getRequiredPopupWidth(actor); + const menuDimensions = svgDraw.drawPopup( + diagram, + actor, + minMenuWidth, + conf, + conf.forceMenus, + doc + ); + if (menuDimensions.height > maxHeight) { + maxHeight = menuDimensions.height; + } + if (menuDimensions.width + actor.x > maxWidth) { + maxWidth = menuDimensions.width + actor.x; + } + } + + return { maxHeight: maxHeight, maxWidth: maxWidth }; +}; + +export const setConf = function (cnf) { + assignWithDepth(conf, cnf); + + if (cnf.fontFamily) { + conf.actorFontFamily = conf.noteFontFamily = conf.messageFontFamily = cnf.fontFamily; + } + if (cnf.fontSize) { + conf.actorFontSize = conf.noteFontSize = conf.messageFontSize = cnf.fontSize; + } + if (cnf.fontWeight) { + conf.actorFontWeight = conf.noteFontWeight = conf.messageFontWeight = cnf.fontWeight; + } +}; + +const actorActivations = function (actor) { + return bounds.activations.filter(function (activation) { + return activation.actor === actor; + }); +}; + +const activationBounds = function (actor, actors) { + // handle multiple stacked activations for same actor + const actorObj = actors[actor]; + const activations = actorActivations(actor); + + const left = activations.reduce(function (acc, activation) { + return common.getMin(acc, activation.startx); + }, actorObj.x + actorObj.width / 2); + const right = activations.reduce(function (acc, activation) { + return common.getMax(acc, activation.stopx); + }, actorObj.x + actorObj.width / 2); + return [left, right]; +}; + +function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoopFn) { + bounds.bumpVerticalPos(preMargin); + let heightAdjust = postMargin; + if (msg.id && msg.message && loopWidths[msg.id]) { + const loopWidth = loopWidths[msg.id].width; + const textConf = messageFont(conf); + msg.message = utils.wrapLabel(`[${msg.message}]`, loopWidth - 2 * conf.wrapPadding, textConf); + msg.width = loopWidth; + msg.wrap = true; + + // const lines = common.splitBreaks(msg.message).length; + const textDims = utils.calculateTextDimensions(msg.message, textConf); + const totalOffset = common.getMax(textDims.height, conf.labelBoxHeight); + heightAdjust = postMargin + totalOffset; + log.debug(`${totalOffset} - ${msg.message}`); + } + addLoopFn(msg); + bounds.bumpVerticalPos(heightAdjust); +} + +/** + * Draws a sequenceDiagram in the tag with id: id based on the graph definition in text. + * + * @param _text - The text of the diagram + * @param id - The id of the diagram which will be used as a DOM element id¨ + * @param _version - Mermaid version from package.json + * @param diagObj - A standard diagram containing the db and the text and type etc of the diagram + */ +export const draw = function (_text: string, id: string, _version: string, diagObj: Diagram) { + const { securityLevel, sequence } = configApi.getConfig(); + conf = sequence; + diagObj.db.clear(); + // Parse the graph definition + diagObj.parser.parse(_text); + // Handle root and Document for when rendering in sandbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + bounds.init(); + log.debug(diagObj.db); + + const diagram = + securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : select(`[id="${id}"]`); + + // Fetch data from the parsing + const actors = diagObj.db.getActors(); + const boxes = diagObj.db.getBoxes(); + const actorKeys = diagObj.db.getActorKeys(); + const messages = diagObj.db.getMessages(); + const title = diagObj.db.getDiagramTitle(); + const hasBoxes = diagObj.db.hasAtLeastOneBox(); + const hasBoxTitles = diagObj.db.hasAtLeastOneBoxWithTitle(); + const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages, diagObj); + conf.height = calculateActorMargins(actors, maxMessageWidthPerActor, boxes); + + svgDraw.insertComputerIcon(diagram); + svgDraw.insertDatabaseIcon(diagram); + svgDraw.insertClockIcon(diagram); + + if (hasBoxes) { + bounds.bumpVerticalPos(conf.boxMargin); + if (hasBoxTitles) { + bounds.bumpVerticalPos(boxes[0].textMaxHeight); + } + } + + drawActors(diagram, actors, actorKeys, 0, conf, messages, false); + const loopWidths = calculateLoopBounds(messages, actors, maxMessageWidthPerActor, diagObj); + + // The arrow head definition is attached to the svg once + svgDraw.insertArrowHead(diagram); + svgDraw.insertArrowCrossHead(diagram); + svgDraw.insertArrowFilledHead(diagram); + svgDraw.insertSequenceNumber(diagram); + + /** + * @param msg - The message to draw. + * @param verticalPos - The vertical position of the message. + */ + function activeEnd(msg: any, verticalPos: number) { + const activationData = bounds.endActivation(msg); + if (activationData.starty + 18 > verticalPos) { + activationData.starty = verticalPos - 6; + verticalPos += 12; + } + svgDraw.drawActivation( + diagram, + activationData, + verticalPos, + conf, + actorActivations(msg.from.actor).length + ); + + bounds.insert(activationData.startx, verticalPos - 10, activationData.stopx, verticalPos); + } + + // Draw the messages/signals + let sequenceIndex = 1; + let sequenceIndexStep = 1; + const messagesToDraw = []; + messages.forEach(function (msg) { + let loopModel, noteModel, msgModel; + + switch (msg.type) { + case diagObj.db.LINETYPE.NOTE: + bounds.resetVerticalPos(); + noteModel = msg.noteModel; + drawNote(diagram, noteModel); + break; + case diagObj.db.LINETYPE.ACTIVE_START: + bounds.newActivation(msg, diagram, actors); + break; + case diagObj.db.LINETYPE.ACTIVE_END: + activeEnd(msg, bounds.getVerticalPos()); + break; + case diagObj.db.LINETYPE.LOOP_START: + adjustLoopHeightForWrap( + loopWidths, + msg, + conf.boxMargin, + conf.boxMargin + conf.boxTextMargin, + (message) => bounds.newLoop(message) + ); + break; + case diagObj.db.LINETYPE.LOOP_END: + loopModel = bounds.endLoop(); + svgDraw.drawLoop(diagram, loopModel, 'loop', conf); + bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); + bounds.models.addLoop(loopModel); + break; + case diagObj.db.LINETYPE.RECT_START: + adjustLoopHeightForWrap(loopWidths, msg, conf.boxMargin, conf.boxMargin, (message) => + bounds.newLoop(undefined, message.message) + ); + break; + case diagObj.db.LINETYPE.RECT_END: + loopModel = bounds.endLoop(); + svgDraw.drawBackgroundRect(diagram, loopModel); + bounds.models.addLoop(loopModel); + bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); + break; + case diagObj.db.LINETYPE.OPT_START: + adjustLoopHeightForWrap( + loopWidths, + msg, + conf.boxMargin, + conf.boxMargin + conf.boxTextMargin, + (message) => bounds.newLoop(message) + ); + break; + case diagObj.db.LINETYPE.OPT_END: + loopModel = bounds.endLoop(); + svgDraw.drawLoop(diagram, loopModel, 'opt', conf); + bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); + bounds.models.addLoop(loopModel); + break; + case diagObj.db.LINETYPE.ALT_START: + adjustLoopHeightForWrap( + loopWidths, + msg, + conf.boxMargin, + conf.boxMargin + conf.boxTextMargin, + (message) => bounds.newLoop(message) + ); + break; + case diagObj.db.LINETYPE.ALT_ELSE: + adjustLoopHeightForWrap( + loopWidths, + msg, + conf.boxMargin + conf.boxTextMargin, + conf.boxMargin, + (message) => bounds.addSectionToLoop(message) + ); + break; + case diagObj.db.LINETYPE.ALT_END: + loopModel = bounds.endLoop(); + svgDraw.drawLoop(diagram, loopModel, 'alt', conf); + bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); + bounds.models.addLoop(loopModel); + break; + case diagObj.db.LINETYPE.PAR_START: + case diagObj.db.LINETYPE.PAR_OVER_START: + adjustLoopHeightForWrap( + loopWidths, + msg, + conf.boxMargin, + conf.boxMargin + conf.boxTextMargin, + (message) => bounds.newLoop(message) + ); + bounds.saveVerticalPos(); + break; + case diagObj.db.LINETYPE.PAR_AND: + adjustLoopHeightForWrap( + loopWidths, + msg, + conf.boxMargin + conf.boxTextMargin, + conf.boxMargin, + (message) => bounds.addSectionToLoop(message) + ); + break; + case diagObj.db.LINETYPE.PAR_END: + loopModel = bounds.endLoop(); + svgDraw.drawLoop(diagram, loopModel, 'par', conf); + bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); + bounds.models.addLoop(loopModel); + break; + case diagObj.db.LINETYPE.AUTONUMBER: + sequenceIndex = msg.message.start || sequenceIndex; + sequenceIndexStep = msg.message.step || sequenceIndexStep; + if (msg.message.visible) { + diagObj.db.enableSequenceNumbers(); + } else { + diagObj.db.disableSequenceNumbers(); + } + break; + case diagObj.db.LINETYPE.CRITICAL_START: + adjustLoopHeightForWrap( + loopWidths, + msg, + conf.boxMargin, + conf.boxMargin + conf.boxTextMargin, + (message) => bounds.newLoop(message) + ); + break; + case diagObj.db.LINETYPE.CRITICAL_OPTION: + adjustLoopHeightForWrap( + loopWidths, + msg, + conf.boxMargin + conf.boxTextMargin, + conf.boxMargin, + (message) => bounds.addSectionToLoop(message) + ); + break; + case diagObj.db.LINETYPE.CRITICAL_END: + loopModel = bounds.endLoop(); + svgDraw.drawLoop(diagram, loopModel, 'critical', conf); + bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); + bounds.models.addLoop(loopModel); + break; + case diagObj.db.LINETYPE.BREAK_START: + adjustLoopHeightForWrap( + loopWidths, + msg, + conf.boxMargin, + conf.boxMargin + conf.boxTextMargin, + (message) => bounds.newLoop(message) + ); + break; + case diagObj.db.LINETYPE.BREAK_END: + loopModel = bounds.endLoop(); + svgDraw.drawLoop(diagram, loopModel, 'break', conf); + bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); + bounds.models.addLoop(loopModel); + break; + default: + try { + // lastMsg = msg + bounds.resetVerticalPos(); + msgModel = msg.msgModel; + msgModel.starty = bounds.getVerticalPos(); + msgModel.sequenceIndex = sequenceIndex; + msgModel.sequenceVisible = diagObj.db.showSequenceNumbers(); + const lineStartY = boundMessage(diagram, msgModel); + messagesToDraw.push({ messageModel: msgModel, lineStartY: lineStartY }); + bounds.models.addMessage(msgModel); + } catch (e) { + log.error('error while drawing message', e); + } + } + + // Increment sequence counter if msg.type is a line (and not another event like activation or note, etc) + if ( + [ + diagObj.db.LINETYPE.SOLID_OPEN, + diagObj.db.LINETYPE.DOTTED_OPEN, + diagObj.db.LINETYPE.SOLID, + diagObj.db.LINETYPE.DOTTED, + diagObj.db.LINETYPE.SOLID_CROSS, + diagObj.db.LINETYPE.DOTTED_CROSS, + diagObj.db.LINETYPE.SOLID_POINT, + diagObj.db.LINETYPE.DOTTED_POINT, + ].includes(msg.type) + ) { + sequenceIndex = sequenceIndex + sequenceIndexStep; + } + }); + + messagesToDraw.forEach((e) => drawMessage(diagram, e.messageModel, e.lineStartY, diagObj)); + + if (conf.mirrorActors) { + // Draw actors below diagram + bounds.bumpVerticalPos(conf.boxMargin * 2); + drawActors(diagram, actors, actorKeys, bounds.getVerticalPos(), conf, messages, true); + bounds.bumpVerticalPos(conf.boxMargin); + fixLifeLineHeights(diagram, bounds.getVerticalPos()); + } + + bounds.models.boxes.forEach(function (box) { + box.height = bounds.getVerticalPos() - box.y; + bounds.insert(box.x, box.y, box.x + box.width, box.height); + box.startx = box.x; + box.starty = box.y; + box.stopx = box.startx + box.width; + box.stopy = box.starty + box.height; + box.stroke = 'rgb(0,0,0, 0.5)'; + svgDraw.drawBox(diagram, box, conf); + }); + + if (hasBoxes) { + bounds.bumpVerticalPos(conf.boxMargin); + } + + // only draw popups for the top row of actors. + const requiredBoxSize = drawActorsPopup(diagram, actors, actorKeys, doc); + + const { bounds: box } = bounds.getBounds(); + + // Adjust line height of actor lines now that the height of the diagram is known + log.debug('For line height fix Querying: #' + id + ' .actor-line'); + const actorLines = selectAll('#' + id + ' .actor-line'); + actorLines.attr('y2', box.stopy); + + // Make sure the height of the diagram supports long menus. + let boxHeight = box.stopy - box.starty; + if (boxHeight < requiredBoxSize.maxHeight) { + boxHeight = requiredBoxSize.maxHeight; + } + + let height = boxHeight + 2 * conf.diagramMarginY; + if (conf.mirrorActors) { + height = height - conf.boxMargin + conf.bottomMarginAdj; + } + + // Make sure the width of the diagram supports wide menus. + let boxWidth = box.stopx - box.startx; + if (boxWidth < requiredBoxSize.maxWidth) { + boxWidth = requiredBoxSize.maxWidth; + } + const width = boxWidth + 2 * conf.diagramMarginX; + + if (title) { + diagram + .append('text') + .text(title) + .attr('x', (box.stopx - box.startx) / 2 - 2 * conf.diagramMarginX) + .attr('y', -25); + } + + configureSvgSize(diagram, height, width, conf.useMaxWidth); + + const extraVertForTitle = title ? 40 : 0; + diagram.attr( + 'viewBox', + box.startx - + conf.diagramMarginX + + ' -' + + (conf.diagramMarginY + extraVertForTitle) + + ' ' + + width + + ' ' + + (height + extraVertForTitle) + ); + + log.debug(`models:`, bounds.models); +}; + +/** + * Retrieves the max message width of each actor, supports signals (messages, loops) and notes. + * + * It will enumerate each given message, and will determine its text width, in relation to the actor + * it originates from, and destined to. + * + * @param actors - The actors map + * @param messages - A list of message objects to iterate + * @param diagObj - The diagram object. + * @returns The max message width of each actor. + */ +function getMaxMessageWidthPerActor( + actors: { [id: string]: any }, + messages: any[], + diagObj: Diagram +): { [id: string]: number } { + const maxMessageWidthPerActor = {}; + + messages.forEach(function (msg) { + if (actors[msg.to] && actors[msg.from]) { + const actor = actors[msg.to]; + + // If this is the first actor, and the message is left of it, no need to calculate the margin + if (msg.placement === diagObj.db.PLACEMENT.LEFTOF && !actor.prevActor) { + return; + } + + // If this is the last actor, and the message is right of it, no need to calculate the margin + if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF && !actor.nextActor) { + return; + } + + const isNote = msg.placement !== undefined; + const isMessage = !isNote; + + const textFont = isNote ? noteFont(conf) : messageFont(conf); + const wrappedMessage = msg.wrap + ? utils.wrapLabel(msg.message, conf.width - 2 * conf.wrapPadding, textFont) + : msg.message; + const messageDimensions = utils.calculateTextDimensions(wrappedMessage, textFont); + const messageWidth = messageDimensions.width + 2 * conf.wrapPadding; + + /* + * The following scenarios should be supported: + * + * - There's a message (non-note) between fromActor and toActor + * - If fromActor is on the right and toActor is on the left, we should + * define the toActor's margin + * - If fromActor is on the left and toActor is on the right, we should + * define the fromActor's margin + * - There's a note, in which case fromActor == toActor + * - If the note is to the left of the actor, we should define the previous actor + * margin + * - If the note is on the actor, we should define both the previous and next actor + * margins, each being the half of the note size + * - If the note is on the right of the actor, we should define the current actor + * margin + */ + if (isMessage && msg.from === actor.nextActor) { + maxMessageWidthPerActor[msg.to] = common.getMax( + maxMessageWidthPerActor[msg.to] || 0, + messageWidth + ); + } else if (isMessage && msg.from === actor.prevActor) { + maxMessageWidthPerActor[msg.from] = common.getMax( + maxMessageWidthPerActor[msg.from] || 0, + messageWidth + ); + } else if (isMessage && msg.from === msg.to) { + maxMessageWidthPerActor[msg.from] = common.getMax( + maxMessageWidthPerActor[msg.from] || 0, + messageWidth / 2 + ); + + maxMessageWidthPerActor[msg.to] = common.getMax( + maxMessageWidthPerActor[msg.to] || 0, + messageWidth / 2 + ); + } else if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF) { + maxMessageWidthPerActor[msg.from] = common.getMax( + maxMessageWidthPerActor[msg.from] || 0, + messageWidth + ); + } else if (msg.placement === diagObj.db.PLACEMENT.LEFTOF) { + maxMessageWidthPerActor[actor.prevActor] = common.getMax( + maxMessageWidthPerActor[actor.prevActor] || 0, + messageWidth + ); + } else if (msg.placement === diagObj.db.PLACEMENT.OVER) { + if (actor.prevActor) { + maxMessageWidthPerActor[actor.prevActor] = common.getMax( + maxMessageWidthPerActor[actor.prevActor] || 0, + messageWidth / 2 + ); + } + + if (actor.nextActor) { + maxMessageWidthPerActor[msg.from] = common.getMax( + maxMessageWidthPerActor[msg.from] || 0, + messageWidth / 2 + ); + } + } + } + }); + + log.debug('maxMessageWidthPerActor:', maxMessageWidthPerActor); + return maxMessageWidthPerActor; +} + +const getRequiredPopupWidth = function (actor) { + let requiredPopupWidth = 0; + const textFont = actorFont(conf); + for (const key in actor.links) { + const labelDimensions = utils.calculateTextDimensions(key, textFont); + const labelWidth = labelDimensions.width + 2 * conf.wrapPadding + 2 * conf.boxMargin; + if (requiredPopupWidth < labelWidth) { + requiredPopupWidth = labelWidth; + } + } + + return requiredPopupWidth; +}; + +/** + * This will calculate the optimal margin for each given actor, + * for a given actor → messageWidth map. + * + * An actor's margin is determined by the width of the actor, the width of the largest message that + * originates from it, and the configured conf.actorMargin. + * + * @param actors - The actors map to calculate margins for + * @param actorToMessageWidth - A map of actor key → max message width it holds + * @param boxes - The boxes around the actors if any + */ +function calculateActorMargins( + actors: { [id: string]: any }, + actorToMessageWidth: ReturnType, + boxes +) { + let maxHeight = 0; + Object.keys(actors).forEach((prop) => { + const actor = actors[prop]; + if (actor.wrap) { + actor.description = utils.wrapLabel( + actor.description, + conf.width - 2 * conf.wrapPadding, + actorFont(conf) + ); + } + const actDims = utils.calculateTextDimensions(actor.description, actorFont(conf)); + actor.width = actor.wrap + ? conf.width + : common.getMax(conf.width, actDims.width + 2 * conf.wrapPadding); + + actor.height = actor.wrap ? common.getMax(actDims.height, conf.height) : conf.height; + maxHeight = common.getMax(maxHeight, actor.height); + }); + + for (const actorKey in actorToMessageWidth) { + const actor = actors[actorKey]; + + if (!actor) { + continue; + } + + const nextActor = actors[actor.nextActor]; + + // No need to space out an actor that doesn't have a next link + if (!nextActor) { + const messageWidth = actorToMessageWidth[actorKey]; + const actorWidth = messageWidth + conf.actorMargin - actor.width / 2; + actor.margin = common.getMax(actorWidth, conf.actorMargin); + continue; + } + + const messageWidth = actorToMessageWidth[actorKey]; + const actorWidth = messageWidth + conf.actorMargin - actor.width / 2 - nextActor.width / 2; + + actor.margin = common.getMax(actorWidth, conf.actorMargin); + } + + let maxBoxHeight = 0; + boxes.forEach((box) => { + const textFont = messageFont(conf); + let totalWidth = box.actorKeys.reduce((total, aKey) => { + return (total += actors[aKey].width + (actors[aKey].margin || 0)); + }, 0); + + totalWidth -= 2 * conf.boxTextMargin; + if (box.wrap) { + box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont); + } + + const boxMsgDimensions = utils.calculateTextDimensions(box.name, textFont); + maxBoxHeight = common.getMax(boxMsgDimensions.height, maxBoxHeight); + const minWidth = common.getMax(totalWidth, boxMsgDimensions.width + 2 * conf.wrapPadding); + box.margin = conf.boxTextMargin; + if (totalWidth < minWidth) { + const missing = (minWidth - totalWidth) / 2; + box.margin += missing; + } + }); + boxes.forEach((box) => (box.textMaxHeight = maxBoxHeight)); + + return common.getMax(maxHeight, conf.height); +} + +const buildNoteModel = function (msg, actors, diagObj) { + const startx = actors[msg.from].x; + const stopx = actors[msg.to].x; + const shouldWrap = msg.wrap && msg.message; + + let textDimensions = utils.calculateTextDimensions( + shouldWrap ? utils.wrapLabel(msg.message, conf.width, noteFont(conf)) : msg.message, + noteFont(conf) + ); + const noteModel = { + width: shouldWrap + ? conf.width + : common.getMax(conf.width, textDimensions.width + 2 * conf.noteMargin), + height: 0, + startx: actors[msg.from].x, + stopx: 0, + starty: 0, + stopy: 0, + message: msg.message, + }; + if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF) { + noteModel.width = shouldWrap + ? common.getMax(conf.width, textDimensions.width) + : common.getMax( + actors[msg.from].width / 2 + actors[msg.to].width / 2, + textDimensions.width + 2 * conf.noteMargin + ); + noteModel.startx = startx + (actors[msg.from].width + conf.actorMargin) / 2; + } else if (msg.placement === diagObj.db.PLACEMENT.LEFTOF) { + noteModel.width = shouldWrap + ? common.getMax(conf.width, textDimensions.width + 2 * conf.noteMargin) + : common.getMax( + actors[msg.from].width / 2 + actors[msg.to].width / 2, + textDimensions.width + 2 * conf.noteMargin + ); + noteModel.startx = startx - noteModel.width + (actors[msg.from].width - conf.actorMargin) / 2; + } else if (msg.to === msg.from) { + textDimensions = utils.calculateTextDimensions( + shouldWrap + ? utils.wrapLabel( + msg.message, + common.getMax(conf.width, actors[msg.from].width), + noteFont(conf) + ) + : msg.message, + noteFont(conf) + ); + noteModel.width = shouldWrap + ? common.getMax(conf.width, actors[msg.from].width) + : common.getMax( + actors[msg.from].width, + conf.width, + textDimensions.width + 2 * conf.noteMargin + ); + noteModel.startx = startx + (actors[msg.from].width - noteModel.width) / 2; + } else { + noteModel.width = + Math.abs(startx + actors[msg.from].width / 2 - (stopx + actors[msg.to].width / 2)) + + conf.actorMargin; + noteModel.startx = + startx < stopx + ? startx + actors[msg.from].width / 2 - conf.actorMargin / 2 + : stopx + actors[msg.to].width / 2 - conf.actorMargin / 2; + } + if (shouldWrap) { + noteModel.message = utils.wrapLabel( + msg.message, + noteModel.width - 2 * conf.wrapPadding, + noteFont(conf) + ); + } + log.debug( + `NM:[${noteModel.startx},${noteModel.stopx},${noteModel.starty},${noteModel.stopy}:${noteModel.width},${noteModel.height}=${msg.message}]` + ); + return noteModel; +}; + +const buildMessageModel = function (msg, actors, diagObj) { + let process = false; + if ( + [ + diagObj.db.LINETYPE.SOLID_OPEN, + diagObj.db.LINETYPE.DOTTED_OPEN, + diagObj.db.LINETYPE.SOLID, + diagObj.db.LINETYPE.DOTTED, + diagObj.db.LINETYPE.SOLID_CROSS, + diagObj.db.LINETYPE.DOTTED_CROSS, + diagObj.db.LINETYPE.SOLID_POINT, + diagObj.db.LINETYPE.DOTTED_POINT, + ].includes(msg.type) + ) { + process = true; + } + if (!process) { + return {}; + } + const fromBounds = activationBounds(msg.from, actors); + const toBounds = activationBounds(msg.to, actors); + const fromIdx = fromBounds[0] <= toBounds[0] ? 1 : 0; + const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1; + const allBounds = [...fromBounds, ...toBounds]; + const boundedWidth = Math.abs(toBounds[toIdx] - fromBounds[fromIdx]); + if (msg.wrap && msg.message) { + msg.message = utils.wrapLabel( + msg.message, + common.getMax(boundedWidth + 2 * conf.wrapPadding, conf.width), + messageFont(conf) + ); + } + const msgDims = utils.calculateTextDimensions(msg.message, messageFont(conf)); + + return { + width: common.getMax( + msg.wrap ? 0 : msgDims.width + 2 * conf.wrapPadding, + boundedWidth + 2 * conf.wrapPadding, + conf.width + ), + height: 0, + startx: fromBounds[fromIdx], + stopx: toBounds[toIdx], + starty: 0, + stopy: 0, + message: msg.message, + type: msg.type, + wrap: msg.wrap, + fromBounds: Math.min.apply(null, allBounds), + toBounds: Math.max.apply(null, allBounds), + }; +}; + +const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagObj) { + const loops = {}; + const stack = []; + let current, noteModel, msgModel; + + messages.forEach(function (msg) { + msg.id = utils.random({ length: 10 }); + switch (msg.type) { + case diagObj.db.LINETYPE.LOOP_START: + case diagObj.db.LINETYPE.ALT_START: + case diagObj.db.LINETYPE.OPT_START: + case diagObj.db.LINETYPE.PAR_START: + case diagObj.db.LINETYPE.PAR_OVER_START: + case diagObj.db.LINETYPE.CRITICAL_START: + case diagObj.db.LINETYPE.BREAK_START: + stack.push({ + id: msg.id, + msg: msg.message, + from: Number.MAX_SAFE_INTEGER, + to: Number.MIN_SAFE_INTEGER, + width: 0, + }); + break; + case diagObj.db.LINETYPE.ALT_ELSE: + case diagObj.db.LINETYPE.PAR_AND: + case diagObj.db.LINETYPE.CRITICAL_OPTION: + if (msg.message) { + current = stack.pop(); + loops[current.id] = current; + loops[msg.id] = current; + stack.push(current); + } + break; + case diagObj.db.LINETYPE.LOOP_END: + case diagObj.db.LINETYPE.ALT_END: + case diagObj.db.LINETYPE.OPT_END: + case diagObj.db.LINETYPE.PAR_END: + case diagObj.db.LINETYPE.CRITICAL_END: + case diagObj.db.LINETYPE.BREAK_END: + current = stack.pop(); + loops[current.id] = current; + break; + case diagObj.db.LINETYPE.ACTIVE_START: + { + const actorRect = actors[msg.from ? msg.from.actor : msg.to.actor]; + const stackedSize = actorActivations(msg.from ? msg.from.actor : msg.to.actor).length; + const x = + actorRect.x + actorRect.width / 2 + ((stackedSize - 1) * conf.activationWidth) / 2; + const toAdd = { + startx: x, + stopx: x + conf.activationWidth, + actor: msg.from.actor, + enabled: true, + }; + bounds.activations.push(toAdd); + } + break; + case diagObj.db.LINETYPE.ACTIVE_END: + { + const lastActorActivationIdx = bounds.activations + .map((a) => a.actor) + .lastIndexOf(msg.from.actor); + delete bounds.activations.splice(lastActorActivationIdx, 1)[0]; + } + break; + } + const isNote = msg.placement !== undefined; + if (isNote) { + noteModel = buildNoteModel(msg, actors, diagObj); + msg.noteModel = noteModel; + stack.forEach((stk) => { + current = stk; + current.from = common.getMin(current.from, noteModel.startx); + current.to = common.getMax(current.to, noteModel.startx + noteModel.width); + current.width = + common.getMax(current.width, Math.abs(current.from - current.to)) - conf.labelBoxWidth; + }); + } else { + msgModel = buildMessageModel(msg, actors, diagObj); + msg.msgModel = msgModel; + if (msgModel.startx && msgModel.stopx && stack.length > 0) { + stack.forEach((stk) => { + current = stk; + if (msgModel.startx === msgModel.stopx) { + const from = actors[msg.from]; + const to = actors[msg.to]; + current.from = common.getMin( + from.x - msgModel.width / 2, + from.x - from.width / 2, + current.from + ); + current.to = common.getMax( + to.x + msgModel.width / 2, + to.x + from.width / 2, + current.to + ); + current.width = + common.getMax(current.width, Math.abs(current.to - current.from)) - + conf.labelBoxWidth; + } else { + current.from = common.getMin(msgModel.startx, current.from); + current.to = common.getMax(msgModel.stopx, current.to); + current.width = common.getMax(current.width, msgModel.width) - conf.labelBoxWidth; + } + }); + } + } + }); + bounds.activations = []; + log.debug('Loop type widths:', loops); + return loops; +}; + +export default { + bounds, + drawActors, + drawActorsPopup, + setConf, + draw, +}; diff --git a/packages/mermaid/src/styles.spec.ts b/packages/mermaid/src/styles.spec.ts index 51951f190..d1edf4111 100644 --- a/packages/mermaid/src/styles.spec.ts +++ b/packages/mermaid/src/styles.spec.ts @@ -29,6 +29,7 @@ import state from './diagrams/state/styles.js'; import journey from './diagrams/user-journey/styles.js'; import timeline from './diagrams/timeline/styles.js'; import mindmap from './diagrams/mindmap/styles.js'; +import sankey from './diagrams/sankey/styles.js'; import themes from './themes/index.js'; async function checkValidStylisCSSStyleSheet(stylisString: string) { @@ -98,6 +99,7 @@ describe('styles', () => { sequence, state, timeline, + sankey, })) { test(`should return a valid style for diagram ${diagramId} and theme ${themeId}`, async () => { const { default: getStyles, addStylesForDiagram } = await import('./styles.js'); From 8e001b92f2005f3ffdc4166b3c5d7b9315d15a29 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 007/105] Cleared sankey renderer --- cypress/integration/rendering/sankey.spec.js | 14 + .../diagrams/sankey/parser/desired_syntax.md | 2 +- .../src/diagrams/sankey/parser/sankey.spec.js | 44 + .../src/diagrams/sankey/sankeyDiagram.spec.js | 24 - .../src/diagrams/sankey/sankeyDiagram.ts | 1 + .../src/diagrams/sankey/sankeyRenderer.ts | 1431 +---------------- 6 files changed, 61 insertions(+), 1455 deletions(-) create mode 100644 cypress/integration/rendering/sankey.spec.js create mode 100644 packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js delete mode 100644 packages/mermaid/src/diagrams/sankey/sankeyDiagram.spec.js diff --git a/cypress/integration/rendering/sankey.spec.js b/cypress/integration/rendering/sankey.spec.js new file mode 100644 index 000000000..5b9bcf870 --- /dev/null +++ b/cypress/integration/rendering/sankey.spec.js @@ -0,0 +1,14 @@ +import { imgSnapshotTest, renderGraph } from '../../helpers/util.js'; + +describe('Sankey Diagram', () => { + it('should render a simple sankey diagram', () => { + imgSnapshotTest( + ` + sankey + a -> 30 -> b + `, + {} + ); + cy.get('svg'); + }); +}); diff --git a/packages/mermaid/src/diagrams/sankey/parser/desired_syntax.md b/packages/mermaid/src/diagrams/sankey/parser/desired_syntax.md index 52057fce9..d0cbd4e8a 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/desired_syntax.md +++ b/packages/mermaid/src/diagrams/sankey/parser/desired_syntax.md @@ -28,7 +28,7 @@ Interviewed,Declined Offer,2 Interviewed,Accepted Offer,1,orange ``` -GoJS uses similar approach +GoJS uses similar approach: ```json { "nodeDataArray": [ diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js new file mode 100644 index 000000000..995c92478 --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js @@ -0,0 +1,44 @@ +import diagram from './sankey.jison'; +import { parser } from './sankey.jison'; +import db from '../sankeyDB.js'; + +describe('Sankey diagram', function () { + // TODO - these examples should be put into ./parser/stateDiagram.spec.js + describe('when parsing an info graph it', function () { + beforeEach(function () { + parser.yy = db; + diagram.parser.yy = db; + diagram.parser.yy.clear(); + }); + + it('one simple flow', function () { + const str = ` + sankey + a -> 30 -> b + `; + + parser.parse(str); + }); + + it('multiple flows', function () { + const str = ` + sankey + a -> 30 -> b + c -> 30 -> d + c -> 40 -> e + `; + + parser.parse(str); + }); + + it('multiple flows', function () { + const str = ` + sankey + a -> 30 -> b + c -> 30 -> d + `; + + parser.parse(str); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.spec.js b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.spec.js deleted file mode 100644 index ee9d0e23e..000000000 --- a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.spec.js +++ /dev/null @@ -1,24 +0,0 @@ -import diagram from './parser/sankey.jison'; -import { parser } from './parser/sankey.jison'; -import db from './sankeyDB.js'; - -describe('state diagram V2, ', function () { - // TODO - these examples should be put into ./parser/stateDiagram.spec.js - describe('when parsing an info graph it', function () { - beforeEach(function () { - parser.yy = stateDb; - diagram.parser.yy = db; - diagram.parser.yy.clear(); - }); - - it('super simple', function () { - const str = ` - stateDiagram-v2 - [*] --> State1 - State1 --> [*] - `; - - parser.parse(str); - }); - }); -}); diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts index e7a46d8bc..482a75a48 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts @@ -11,3 +11,4 @@ export const diagram: DiagramDefinition = { renderer, styles, }; + diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 243fbddad..f1482210e 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -1,638 +1,5 @@ -// @ts-nocheck TODO: fix file -import { select, selectAll } from 'd3'; -import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw.js'; -import { log } from '../../logger.js'; -import common from '../common/common.js'; -import * as svgDrawCommon from '../common/svgDrawCommon.js'; -import * as configApi from '../../config.js'; -import assignWithDepth from '../../assignWithDepth.js'; -import utils from '../../utils.js'; -import { configureSvgSize } from '../../setupGraphViewbox.js'; import { Diagram } from '../../Diagram.js'; -let conf = {}; - -export const bounds = { - data: { - startx: undefined, - stopx: undefined, - starty: undefined, - stopy: undefined, - }, - verticalPos: 0, - sequenceItems: [], - activations: [], - models: { - getHeight: function () { - return ( - Math.max.apply( - null, - this.actors.length === 0 ? [0] : this.actors.map((actor) => actor.height || 0) - ) + - (this.loops.length === 0 - ? 0 - : this.loops.map((it) => it.height || 0).reduce((acc, h) => acc + h)) + - (this.messages.length === 0 - ? 0 - : this.messages.map((it) => it.height || 0).reduce((acc, h) => acc + h)) + - (this.notes.length === 0 - ? 0 - : this.notes.map((it) => it.height || 0).reduce((acc, h) => acc + h)) - ); - }, - clear: function () { - this.actors = []; - this.boxes = []; - this.loops = []; - this.messages = []; - this.notes = []; - }, - addBox: function (boxModel) { - this.boxes.push(boxModel); - }, - addActor: function (actorModel) { - this.actors.push(actorModel); - }, - addLoop: function (loopModel) { - this.loops.push(loopModel); - }, - addMessage: function (msgModel) { - this.messages.push(msgModel); - }, - addNote: function (noteModel) { - this.notes.push(noteModel); - }, - lastActor: function () { - return this.actors[this.actors.length - 1]; - }, - lastLoop: function () { - return this.loops[this.loops.length - 1]; - }, - lastMessage: function () { - return this.messages[this.messages.length - 1]; - }, - lastNote: function () { - return this.notes[this.notes.length - 1]; - }, - actors: [], - boxes: [], - loops: [], - messages: [], - notes: [], - }, - init: function () { - this.sequenceItems = []; - this.activations = []; - this.models.clear(); - this.data = { - startx: undefined, - stopx: undefined, - starty: undefined, - stopy: undefined, - }; - this.verticalPos = 0; - setConf(configApi.getConfig()); - }, - updateVal: function (obj, key, val, fun) { - if (obj[key] === undefined) { - obj[key] = val; - } else { - obj[key] = fun(val, obj[key]); - } - }, - updateBounds: function (startx, starty, stopx, stopy) { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const _self = this; - let cnt = 0; - /** @param type - Either `activation` or `undefined` */ - function updateFn(type?: 'activation') { - return function updateItemBounds(item) { - cnt++; - // The loop sequenceItems is a stack so the biggest margins in the beginning of the sequenceItems - const n = _self.sequenceItems.length - cnt + 1; - - _self.updateVal(item, 'starty', starty - n * conf.boxMargin, Math.min); - _self.updateVal(item, 'stopy', stopy + n * conf.boxMargin, Math.max); - - _self.updateVal(bounds.data, 'startx', startx - n * conf.boxMargin, Math.min); - _self.updateVal(bounds.data, 'stopx', stopx + n * conf.boxMargin, Math.max); - - if (!(type === 'activation')) { - _self.updateVal(item, 'startx', startx - n * conf.boxMargin, Math.min); - _self.updateVal(item, 'stopx', stopx + n * conf.boxMargin, Math.max); - - _self.updateVal(bounds.data, 'starty', starty - n * conf.boxMargin, Math.min); - _self.updateVal(bounds.data, 'stopy', stopy + n * conf.boxMargin, Math.max); - } - }; - } - - this.sequenceItems.forEach(updateFn()); - this.activations.forEach(updateFn('activation')); - }, - insert: function (startx, starty, stopx, stopy) { - const _startx = common.getMin(startx, stopx); - const _stopx = common.getMax(startx, stopx); - const _starty = common.getMin(starty, stopy); - const _stopy = common.getMax(starty, stopy); - - this.updateVal(bounds.data, 'startx', _startx, Math.min); - this.updateVal(bounds.data, 'starty', _starty, Math.min); - this.updateVal(bounds.data, 'stopx', _stopx, Math.max); - this.updateVal(bounds.data, 'stopy', _stopy, Math.max); - - this.updateBounds(_startx, _starty, _stopx, _stopy); - }, - newActivation: function (message, diagram, actors) { - const actorRect = actors[message.from.actor]; - const stackedSize = actorActivations(message.from.actor).length || 0; - const x = actorRect.x + actorRect.width / 2 + ((stackedSize - 1) * conf.activationWidth) / 2; - this.activations.push({ - startx: x, - starty: this.verticalPos + 2, - stopx: x + conf.activationWidth, - stopy: undefined, - actor: message.from.actor, - anchored: svgDraw.anchorElement(diagram), - }); - }, - endActivation: function (message) { - // find most recent activation for given actor - const lastActorActivationIdx = this.activations - .map(function (activation) { - return activation.actor; - }) - .lastIndexOf(message.from.actor); - return this.activations.splice(lastActorActivationIdx, 1)[0]; - }, - createLoop: function (title = { message: undefined, wrap: false, width: undefined }, fill) { - return { - startx: undefined, - starty: this.verticalPos, - stopx: undefined, - stopy: undefined, - title: title.message, - wrap: title.wrap, - width: title.width, - height: 0, - fill: fill, - }; - }, - newLoop: function (title = { message: undefined, wrap: false, width: undefined }, fill) { - this.sequenceItems.push(this.createLoop(title, fill)); - }, - endLoop: function () { - return this.sequenceItems.pop(); - }, - isLoopOverlap: function () { - return this.sequenceItems.length - ? this.sequenceItems[this.sequenceItems.length - 1].overlap - : false; - }, - addSectionToLoop: function (message) { - const loop = this.sequenceItems.pop(); - loop.sections = loop.sections || []; - loop.sectionTitles = loop.sectionTitles || []; - loop.sections.push({ y: bounds.getVerticalPos(), height: 0 }); - loop.sectionTitles.push(message); - this.sequenceItems.push(loop); - }, - saveVerticalPos: function () { - if (this.isLoopOverlap()) { - this.savedVerticalPos = this.verticalPos; - } - }, - resetVerticalPos: function () { - if (this.isLoopOverlap()) { - this.verticalPos = this.savedVerticalPos; - } - }, - bumpVerticalPos: function (bump) { - this.verticalPos = this.verticalPos + bump; - this.data.stopy = common.getMax(this.data.stopy, this.verticalPos); - }, - getVerticalPos: function () { - return this.verticalPos; - }, - getBounds: function () { - return { bounds: this.data, models: this.models }; - }, -}; - -/** Options for drawing a note in {@link drawNote} */ -interface NoteModel { - /** x axis start position */ - startx: number; - /** y axis position */ - starty: number; - /** the message to be shown */ - message: string; - /** Set this with a custom width to override the default configured width. */ - width: number; -} - -/** - * Draws an note in the diagram with the attached line - * - * @param elem - The diagram to draw to. - * @param noteModel - Note model options. - */ -const drawNote = function (elem: any, noteModel: NoteModel) { - bounds.bumpVerticalPos(conf.boxMargin); - noteModel.height = conf.boxMargin; - noteModel.starty = bounds.getVerticalPos(); - const rect = svgDrawCommon.getNoteRect(); - rect.x = noteModel.startx; - rect.y = noteModel.starty; - rect.width = noteModel.width || conf.width; - rect.class = 'note'; - - const g = elem.append('g'); - const rectElem = svgDraw.drawRect(g, rect); - const textObj = svgDrawCommon.getTextObj(); - textObj.x = noteModel.startx; - textObj.y = noteModel.starty; - textObj.width = rect.width; - textObj.dy = '1em'; - textObj.text = noteModel.message; - textObj.class = 'noteText'; - textObj.fontFamily = conf.noteFontFamily; - textObj.fontSize = conf.noteFontSize; - textObj.fontWeight = conf.noteFontWeight; - textObj.anchor = conf.noteAlign; - textObj.textMargin = conf.noteMargin; - textObj.valign = 'center'; - - const textElem = drawText(g, textObj); - - const textHeight = Math.round( - textElem - .map((te) => (te._groups || te)[0][0].getBBox().height) - .reduce((acc, curr) => acc + curr) - ); - - rectElem.attr('height', textHeight + 2 * conf.noteMargin); - noteModel.height += textHeight + 2 * conf.noteMargin; - bounds.bumpVerticalPos(textHeight + 2 * conf.noteMargin); - noteModel.stopy = noteModel.starty + textHeight + 2 * conf.noteMargin; - noteModel.stopx = noteModel.startx + rect.width; - bounds.insert(noteModel.startx, noteModel.starty, noteModel.stopx, noteModel.stopy); - bounds.models.addNote(noteModel); -}; - -const messageFont = (cnf) => { - return { - fontFamily: cnf.messageFontFamily, - fontSize: cnf.messageFontSize, - fontWeight: cnf.messageFontWeight, - }; -}; -const noteFont = (cnf) => { - return { - fontFamily: cnf.noteFontFamily, - fontSize: cnf.noteFontSize, - fontWeight: cnf.noteFontWeight, - }; -}; -const actorFont = (cnf) => { - return { - fontFamily: cnf.actorFontFamily, - fontSize: cnf.actorFontSize, - fontWeight: cnf.actorFontWeight, - }; -}; - -/** - * Process a message by adding its dimensions to the bound. It returns the Y coordinate of the - * message so it can be drawn later. We do not draw the message at this point so the arrowhead can - * be on top of the activation box. - * - * @param _diagram - The parent of the message element. - * @param msgModel - The model containing fields describing a message - * @returns `lineStartY` - The Y coordinate at which the message line starts - */ -function boundMessage(_diagram, msgModel): number { - bounds.bumpVerticalPos(10); - const { startx, stopx, message } = msgModel; - const lines = common.splitBreaks(message).length; - const textDims = utils.calculateTextDimensions(message, messageFont(conf)); - const lineHeight = textDims.height / lines; - msgModel.height += lineHeight; - - bounds.bumpVerticalPos(lineHeight); - - let lineStartY; - let totalOffset = textDims.height - 10; - const textWidth = textDims.width; - - if (startx === stopx) { - lineStartY = bounds.getVerticalPos() + totalOffset; - if (!conf.rightAngles) { - totalOffset += conf.boxMargin; - lineStartY = bounds.getVerticalPos() + totalOffset; - } - totalOffset += 30; - const dx = common.getMax(textWidth / 2, conf.width / 2); - bounds.insert( - startx - dx, - bounds.getVerticalPos() - 10 + totalOffset, - stopx + dx, - bounds.getVerticalPos() + 30 + totalOffset - ); - } else { - totalOffset += conf.boxMargin; - lineStartY = bounds.getVerticalPos() + totalOffset; - bounds.insert(startx, lineStartY - 10, stopx, lineStartY); - } - bounds.bumpVerticalPos(totalOffset); - msgModel.height += totalOffset; - msgModel.stopy = msgModel.starty + msgModel.height; - bounds.insert(msgModel.fromBounds, msgModel.starty, msgModel.toBounds, msgModel.stopy); - - return lineStartY; -} - -/** - * Draws a message. Note that the bounds have previously been updated by boundMessage. - * - * @param diagram - The parent of the message element - * @param msgModel - The model containing fields describing a message - * @param lineStartY - The Y coordinate at which the message line starts - * @param diagObj - The diagram object. - */ -const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Diagram) { - const { startx, stopx, starty, message, type, sequenceIndex, sequenceVisible } = msgModel; - const textDims = utils.calculateTextDimensions(message, messageFont(conf)); - const textObj = svgDrawCommon.getTextObj(); - textObj.x = startx; - textObj.y = starty + 10; - textObj.width = stopx - startx; - textObj.class = 'messageText'; - textObj.dy = '1em'; - textObj.text = message; - textObj.fontFamily = conf.messageFontFamily; - textObj.fontSize = conf.messageFontSize; - textObj.fontWeight = conf.messageFontWeight; - textObj.anchor = conf.messageAlign; - textObj.valign = 'center'; - textObj.textMargin = conf.wrapPadding; - textObj.tspan = false; - - drawText(diagram, textObj); - - const textWidth = textDims.width; - - let line; - if (startx === stopx) { - if (conf.rightAngles) { - line = diagram - .append('path') - .attr( - 'd', - `M ${startx},${lineStartY} H ${ - startx + common.getMax(conf.width / 2, textWidth / 2) - } V ${lineStartY + 25} H ${startx}` - ); - } else { - line = diagram - .append('path') - .attr( - 'd', - 'M ' + - startx + - ',' + - lineStartY + - ' C ' + - (startx + 60) + - ',' + - (lineStartY - 10) + - ' ' + - (startx + 60) + - ',' + - (lineStartY + 30) + - ' ' + - startx + - ',' + - (lineStartY + 20) - ); - } - } else { - line = diagram.append('line'); - line.attr('x1', startx); - line.attr('y1', lineStartY); - line.attr('x2', stopx); - line.attr('y2', lineStartY); - } - // Make an SVG Container - // Draw the line - if ( - type === diagObj.db.LINETYPE.DOTTED || - type === diagObj.db.LINETYPE.DOTTED_CROSS || - type === diagObj.db.LINETYPE.DOTTED_POINT || - type === diagObj.db.LINETYPE.DOTTED_OPEN - ) { - line.style('stroke-dasharray', '3, 3'); - line.attr('class', 'messageLine1'); - } else { - line.attr('class', 'messageLine0'); - } - - 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, '\\)'); - } - - line.attr('stroke-width', 2); - line.attr('stroke', 'none'); // handled by theme/css anyway - line.style('fill', 'none'); // remove any fill colour - if (type === diagObj.db.LINETYPE.SOLID || type === diagObj.db.LINETYPE.DOTTED) { - line.attr('marker-end', 'url(' + url + '#arrowhead)'); - } - if (type === diagObj.db.LINETYPE.SOLID_POINT || type === diagObj.db.LINETYPE.DOTTED_POINT) { - line.attr('marker-end', 'url(' + url + '#filled-head)'); - } - - if (type === diagObj.db.LINETYPE.SOLID_CROSS || type === diagObj.db.LINETYPE.DOTTED_CROSS) { - line.attr('marker-end', 'url(' + url + '#crosshead)'); - } - - // add node number - if (sequenceVisible || conf.showSequenceNumbers) { - line.attr('marker-start', 'url(' + url + '#sequencenumber)'); - diagram - .append('text') - .attr('x', startx) - .attr('y', lineStartY + 4) - .attr('font-family', 'sans-serif') - .attr('font-size', '12px') - .attr('text-anchor', 'middle') - .attr('class', 'sequenceNumber') - .text(sequenceIndex); - } -}; - -export const drawActors = function ( - diagram, - actors, - actorKeys, - verticalPos, - configuration, - messages, - isFooter -) { - if (configuration.hideUnusedParticipants === true) { - const newActors = new Set(); - messages.forEach((message) => { - newActors.add(message.from); - newActors.add(message.to); - }); - actorKeys = actorKeys.filter((actorKey) => newActors.has(actorKey)); - } - - // Draw the actors - let prevWidth = 0; - let prevMargin = 0; - let maxHeight = 0; - let prevBox = undefined; - - for (const actorKey of actorKeys) { - const actor = actors[actorKey]; - const box = actor.box; - - // end of box - if (prevBox && prevBox != box) { - if (!isFooter) { - bounds.models.addBox(prevBox); - } - prevMargin += conf.boxMargin + prevBox.margin; - } - - // new box - if (box && box != prevBox) { - if (!isFooter) { - box.x = prevWidth + prevMargin; - box.y = verticalPos; - } - prevMargin += box.margin; - } - - // Add some rendering data to the object - actor.width = actor.width || conf.width; - actor.height = common.getMax(actor.height || conf.height, conf.height); - actor.margin = actor.margin || conf.actorMargin; - - actor.x = prevWidth + prevMargin; - actor.y = bounds.getVerticalPos(); - - // Draw the box with the attached line - const height = svgDraw.drawActor(diagram, actor, conf, isFooter); - maxHeight = common.getMax(maxHeight, height); - bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height); - - prevWidth += actor.width + prevMargin; - if (actor.box) { - actor.box.width = prevWidth + box.margin - actor.box.x; - } - prevMargin = actor.margin; - prevBox = actor.box; - bounds.models.addActor(actor); - } - - // end of box - if (prevBox && !isFooter) { - bounds.models.addBox(prevBox); - } - - // Add a margin between the actor boxes and the first arrow - bounds.bumpVerticalPos(maxHeight); -}; - -export const drawActorsPopup = function (diagram, actors, actorKeys, doc) { - let maxHeight = 0; - let maxWidth = 0; - for (const actorKey of actorKeys) { - const actor = actors[actorKey]; - const minMenuWidth = getRequiredPopupWidth(actor); - const menuDimensions = svgDraw.drawPopup( - diagram, - actor, - minMenuWidth, - conf, - conf.forceMenus, - doc - ); - if (menuDimensions.height > maxHeight) { - maxHeight = menuDimensions.height; - } - if (menuDimensions.width + actor.x > maxWidth) { - maxWidth = menuDimensions.width + actor.x; - } - } - - return { maxHeight: maxHeight, maxWidth: maxWidth }; -}; - -export const setConf = function (cnf) { - assignWithDepth(conf, cnf); - - if (cnf.fontFamily) { - conf.actorFontFamily = conf.noteFontFamily = conf.messageFontFamily = cnf.fontFamily; - } - if (cnf.fontSize) { - conf.actorFontSize = conf.noteFontSize = conf.messageFontSize = cnf.fontSize; - } - if (cnf.fontWeight) { - conf.actorFontWeight = conf.noteFontWeight = conf.messageFontWeight = cnf.fontWeight; - } -}; - -const actorActivations = function (actor) { - return bounds.activations.filter(function (activation) { - return activation.actor === actor; - }); -}; - -const activationBounds = function (actor, actors) { - // handle multiple stacked activations for same actor - const actorObj = actors[actor]; - const activations = actorActivations(actor); - - const left = activations.reduce(function (acc, activation) { - return common.getMin(acc, activation.startx); - }, actorObj.x + actorObj.width / 2); - const right = activations.reduce(function (acc, activation) { - return common.getMax(acc, activation.stopx); - }, actorObj.x + actorObj.width / 2); - return [left, right]; -}; - -function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoopFn) { - bounds.bumpVerticalPos(preMargin); - let heightAdjust = postMargin; - if (msg.id && msg.message && loopWidths[msg.id]) { - const loopWidth = loopWidths[msg.id].width; - const textConf = messageFont(conf); - msg.message = utils.wrapLabel(`[${msg.message}]`, loopWidth - 2 * conf.wrapPadding, textConf); - msg.width = loopWidth; - msg.wrap = true; - - // const lines = common.splitBreaks(msg.message).length; - const textDims = utils.calculateTextDimensions(msg.message, textConf); - const totalOffset = common.getMax(textDims.height, conf.labelBoxHeight); - heightAdjust = postMargin + totalOffset; - log.debug(`${totalOffset} - ${msg.message}`); - } - addLoopFn(msg); - bounds.bumpVerticalPos(heightAdjust); -} - /** * Draws a sequenceDiagram in the tag with id: id based on the graph definition in text. * @@ -642,805 +9,9 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop * @param diagObj - A standard diagram containing the db and the text and type etc of the diagram */ export const draw = function (_text: string, id: string, _version: string, diagObj: Diagram) { - const { securityLevel, sequence } = configApi.getConfig(); - conf = sequence; - diagObj.db.clear(); - // Parse the graph definition - diagObj.parser.parse(_text); - // Handle root and Document for when rendering in sandbox mode - let sandboxElement; - if (securityLevel === 'sandbox') { - sandboxElement = select('#i' + id); - } - - const root = - securityLevel === 'sandbox' - ? select(sandboxElement.nodes()[0].contentDocument.body) - : select('body'); - const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; - bounds.init(); - log.debug(diagObj.db); - - const diagram = - securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : select(`[id="${id}"]`); - - // Fetch data from the parsing - const actors = diagObj.db.getActors(); - const boxes = diagObj.db.getBoxes(); - const actorKeys = diagObj.db.getActorKeys(); - const messages = diagObj.db.getMessages(); - const title = diagObj.db.getDiagramTitle(); - const hasBoxes = diagObj.db.hasAtLeastOneBox(); - const hasBoxTitles = diagObj.db.hasAtLeastOneBoxWithTitle(); - const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages, diagObj); - conf.height = calculateActorMargins(actors, maxMessageWidthPerActor, boxes); - - svgDraw.insertComputerIcon(diagram); - svgDraw.insertDatabaseIcon(diagram); - svgDraw.insertClockIcon(diagram); - - if (hasBoxes) { - bounds.bumpVerticalPos(conf.boxMargin); - if (hasBoxTitles) { - bounds.bumpVerticalPos(boxes[0].textMaxHeight); - } - } - - drawActors(diagram, actors, actorKeys, 0, conf, messages, false); - const loopWidths = calculateLoopBounds(messages, actors, maxMessageWidthPerActor, diagObj); - - // The arrow head definition is attached to the svg once - svgDraw.insertArrowHead(diagram); - svgDraw.insertArrowCrossHead(diagram); - svgDraw.insertArrowFilledHead(diagram); - svgDraw.insertSequenceNumber(diagram); - - /** - * @param msg - The message to draw. - * @param verticalPos - The vertical position of the message. - */ - function activeEnd(msg: any, verticalPos: number) { - const activationData = bounds.endActivation(msg); - if (activationData.starty + 18 > verticalPos) { - activationData.starty = verticalPos - 6; - verticalPos += 12; - } - svgDraw.drawActivation( - diagram, - activationData, - verticalPos, - conf, - actorActivations(msg.from.actor).length - ); - - bounds.insert(activationData.startx, verticalPos - 10, activationData.stopx, verticalPos); - } - - // Draw the messages/signals - let sequenceIndex = 1; - let sequenceIndexStep = 1; - const messagesToDraw = []; - messages.forEach(function (msg) { - let loopModel, noteModel, msgModel; - - switch (msg.type) { - case diagObj.db.LINETYPE.NOTE: - bounds.resetVerticalPos(); - noteModel = msg.noteModel; - drawNote(diagram, noteModel); - break; - case diagObj.db.LINETYPE.ACTIVE_START: - bounds.newActivation(msg, diagram, actors); - break; - case diagObj.db.LINETYPE.ACTIVE_END: - activeEnd(msg, bounds.getVerticalPos()); - break; - case diagObj.db.LINETYPE.LOOP_START: - adjustLoopHeightForWrap( - loopWidths, - msg, - conf.boxMargin, - conf.boxMargin + conf.boxTextMargin, - (message) => bounds.newLoop(message) - ); - break; - case diagObj.db.LINETYPE.LOOP_END: - loopModel = bounds.endLoop(); - svgDraw.drawLoop(diagram, loopModel, 'loop', conf); - bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); - bounds.models.addLoop(loopModel); - break; - case diagObj.db.LINETYPE.RECT_START: - adjustLoopHeightForWrap(loopWidths, msg, conf.boxMargin, conf.boxMargin, (message) => - bounds.newLoop(undefined, message.message) - ); - break; - case diagObj.db.LINETYPE.RECT_END: - loopModel = bounds.endLoop(); - svgDraw.drawBackgroundRect(diagram, loopModel); - bounds.models.addLoop(loopModel); - bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); - break; - case diagObj.db.LINETYPE.OPT_START: - adjustLoopHeightForWrap( - loopWidths, - msg, - conf.boxMargin, - conf.boxMargin + conf.boxTextMargin, - (message) => bounds.newLoop(message) - ); - break; - case diagObj.db.LINETYPE.OPT_END: - loopModel = bounds.endLoop(); - svgDraw.drawLoop(diagram, loopModel, 'opt', conf); - bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); - bounds.models.addLoop(loopModel); - break; - case diagObj.db.LINETYPE.ALT_START: - adjustLoopHeightForWrap( - loopWidths, - msg, - conf.boxMargin, - conf.boxMargin + conf.boxTextMargin, - (message) => bounds.newLoop(message) - ); - break; - case diagObj.db.LINETYPE.ALT_ELSE: - adjustLoopHeightForWrap( - loopWidths, - msg, - conf.boxMargin + conf.boxTextMargin, - conf.boxMargin, - (message) => bounds.addSectionToLoop(message) - ); - break; - case diagObj.db.LINETYPE.ALT_END: - loopModel = bounds.endLoop(); - svgDraw.drawLoop(diagram, loopModel, 'alt', conf); - bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); - bounds.models.addLoop(loopModel); - break; - case diagObj.db.LINETYPE.PAR_START: - case diagObj.db.LINETYPE.PAR_OVER_START: - adjustLoopHeightForWrap( - loopWidths, - msg, - conf.boxMargin, - conf.boxMargin + conf.boxTextMargin, - (message) => bounds.newLoop(message) - ); - bounds.saveVerticalPos(); - break; - case diagObj.db.LINETYPE.PAR_AND: - adjustLoopHeightForWrap( - loopWidths, - msg, - conf.boxMargin + conf.boxTextMargin, - conf.boxMargin, - (message) => bounds.addSectionToLoop(message) - ); - break; - case diagObj.db.LINETYPE.PAR_END: - loopModel = bounds.endLoop(); - svgDraw.drawLoop(diagram, loopModel, 'par', conf); - bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); - bounds.models.addLoop(loopModel); - break; - case diagObj.db.LINETYPE.AUTONUMBER: - sequenceIndex = msg.message.start || sequenceIndex; - sequenceIndexStep = msg.message.step || sequenceIndexStep; - if (msg.message.visible) { - diagObj.db.enableSequenceNumbers(); - } else { - diagObj.db.disableSequenceNumbers(); - } - break; - case diagObj.db.LINETYPE.CRITICAL_START: - adjustLoopHeightForWrap( - loopWidths, - msg, - conf.boxMargin, - conf.boxMargin + conf.boxTextMargin, - (message) => bounds.newLoop(message) - ); - break; - case diagObj.db.LINETYPE.CRITICAL_OPTION: - adjustLoopHeightForWrap( - loopWidths, - msg, - conf.boxMargin + conf.boxTextMargin, - conf.boxMargin, - (message) => bounds.addSectionToLoop(message) - ); - break; - case diagObj.db.LINETYPE.CRITICAL_END: - loopModel = bounds.endLoop(); - svgDraw.drawLoop(diagram, loopModel, 'critical', conf); - bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); - bounds.models.addLoop(loopModel); - break; - case diagObj.db.LINETYPE.BREAK_START: - adjustLoopHeightForWrap( - loopWidths, - msg, - conf.boxMargin, - conf.boxMargin + conf.boxTextMargin, - (message) => bounds.newLoop(message) - ); - break; - case diagObj.db.LINETYPE.BREAK_END: - loopModel = bounds.endLoop(); - svgDraw.drawLoop(diagram, loopModel, 'break', conf); - bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); - bounds.models.addLoop(loopModel); - break; - default: - try { - // lastMsg = msg - bounds.resetVerticalPos(); - msgModel = msg.msgModel; - msgModel.starty = bounds.getVerticalPos(); - msgModel.sequenceIndex = sequenceIndex; - msgModel.sequenceVisible = diagObj.db.showSequenceNumbers(); - const lineStartY = boundMessage(diagram, msgModel); - messagesToDraw.push({ messageModel: msgModel, lineStartY: lineStartY }); - bounds.models.addMessage(msgModel); - } catch (e) { - log.error('error while drawing message', e); - } - } - - // Increment sequence counter if msg.type is a line (and not another event like activation or note, etc) - if ( - [ - diagObj.db.LINETYPE.SOLID_OPEN, - diagObj.db.LINETYPE.DOTTED_OPEN, - diagObj.db.LINETYPE.SOLID, - diagObj.db.LINETYPE.DOTTED, - diagObj.db.LINETYPE.SOLID_CROSS, - diagObj.db.LINETYPE.DOTTED_CROSS, - diagObj.db.LINETYPE.SOLID_POINT, - diagObj.db.LINETYPE.DOTTED_POINT, - ].includes(msg.type) - ) { - sequenceIndex = sequenceIndex + sequenceIndexStep; - } - }); - - messagesToDraw.forEach((e) => drawMessage(diagram, e.messageModel, e.lineStartY, diagObj)); - - if (conf.mirrorActors) { - // Draw actors below diagram - bounds.bumpVerticalPos(conf.boxMargin * 2); - drawActors(diagram, actors, actorKeys, bounds.getVerticalPos(), conf, messages, true); - bounds.bumpVerticalPos(conf.boxMargin); - fixLifeLineHeights(diagram, bounds.getVerticalPos()); - } - - bounds.models.boxes.forEach(function (box) { - box.height = bounds.getVerticalPos() - box.y; - bounds.insert(box.x, box.y, box.x + box.width, box.height); - box.startx = box.x; - box.starty = box.y; - box.stopx = box.startx + box.width; - box.stopy = box.starty + box.height; - box.stroke = 'rgb(0,0,0, 0.5)'; - svgDraw.drawBox(diagram, box, conf); - }); - - if (hasBoxes) { - bounds.bumpVerticalPos(conf.boxMargin); - } - - // only draw popups for the top row of actors. - const requiredBoxSize = drawActorsPopup(diagram, actors, actorKeys, doc); - - const { bounds: box } = bounds.getBounds(); - - // Adjust line height of actor lines now that the height of the diagram is known - log.debug('For line height fix Querying: #' + id + ' .actor-line'); - const actorLines = selectAll('#' + id + ' .actor-line'); - actorLines.attr('y2', box.stopy); - - // Make sure the height of the diagram supports long menus. - let boxHeight = box.stopy - box.starty; - if (boxHeight < requiredBoxSize.maxHeight) { - boxHeight = requiredBoxSize.maxHeight; - } - - let height = boxHeight + 2 * conf.diagramMarginY; - if (conf.mirrorActors) { - height = height - conf.boxMargin + conf.bottomMarginAdj; - } - - // Make sure the width of the diagram supports wide menus. - let boxWidth = box.stopx - box.startx; - if (boxWidth < requiredBoxSize.maxWidth) { - boxWidth = requiredBoxSize.maxWidth; - } - const width = boxWidth + 2 * conf.diagramMarginX; - - if (title) { - diagram - .append('text') - .text(title) - .attr('x', (box.stopx - box.startx) / 2 - 2 * conf.diagramMarginX) - .attr('y', -25); - } - - configureSvgSize(diagram, height, width, conf.useMaxWidth); - - const extraVertForTitle = title ? 40 : 0; - diagram.attr( - 'viewBox', - box.startx - - conf.diagramMarginX + - ' -' + - (conf.diagramMarginY + extraVertForTitle) + - ' ' + - width + - ' ' + - (height + extraVertForTitle) - ); - - log.debug(`models:`, bounds.models); -}; - -/** - * Retrieves the max message width of each actor, supports signals (messages, loops) and notes. - * - * It will enumerate each given message, and will determine its text width, in relation to the actor - * it originates from, and destined to. - * - * @param actors - The actors map - * @param messages - A list of message objects to iterate - * @param diagObj - The diagram object. - * @returns The max message width of each actor. - */ -function getMaxMessageWidthPerActor( - actors: { [id: string]: any }, - messages: any[], - diagObj: Diagram -): { [id: string]: number } { - const maxMessageWidthPerActor = {}; - - messages.forEach(function (msg) { - if (actors[msg.to] && actors[msg.from]) { - const actor = actors[msg.to]; - - // If this is the first actor, and the message is left of it, no need to calculate the margin - if (msg.placement === diagObj.db.PLACEMENT.LEFTOF && !actor.prevActor) { - return; - } - - // If this is the last actor, and the message is right of it, no need to calculate the margin - if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF && !actor.nextActor) { - return; - } - - const isNote = msg.placement !== undefined; - const isMessage = !isNote; - - const textFont = isNote ? noteFont(conf) : messageFont(conf); - const wrappedMessage = msg.wrap - ? utils.wrapLabel(msg.message, conf.width - 2 * conf.wrapPadding, textFont) - : msg.message; - const messageDimensions = utils.calculateTextDimensions(wrappedMessage, textFont); - const messageWidth = messageDimensions.width + 2 * conf.wrapPadding; - - /* - * The following scenarios should be supported: - * - * - There's a message (non-note) between fromActor and toActor - * - If fromActor is on the right and toActor is on the left, we should - * define the toActor's margin - * - If fromActor is on the left and toActor is on the right, we should - * define the fromActor's margin - * - There's a note, in which case fromActor == toActor - * - If the note is to the left of the actor, we should define the previous actor - * margin - * - If the note is on the actor, we should define both the previous and next actor - * margins, each being the half of the note size - * - If the note is on the right of the actor, we should define the current actor - * margin - */ - if (isMessage && msg.from === actor.nextActor) { - maxMessageWidthPerActor[msg.to] = common.getMax( - maxMessageWidthPerActor[msg.to] || 0, - messageWidth - ); - } else if (isMessage && msg.from === actor.prevActor) { - maxMessageWidthPerActor[msg.from] = common.getMax( - maxMessageWidthPerActor[msg.from] || 0, - messageWidth - ); - } else if (isMessage && msg.from === msg.to) { - maxMessageWidthPerActor[msg.from] = common.getMax( - maxMessageWidthPerActor[msg.from] || 0, - messageWidth / 2 - ); - - maxMessageWidthPerActor[msg.to] = common.getMax( - maxMessageWidthPerActor[msg.to] || 0, - messageWidth / 2 - ); - } else if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF) { - maxMessageWidthPerActor[msg.from] = common.getMax( - maxMessageWidthPerActor[msg.from] || 0, - messageWidth - ); - } else if (msg.placement === diagObj.db.PLACEMENT.LEFTOF) { - maxMessageWidthPerActor[actor.prevActor] = common.getMax( - maxMessageWidthPerActor[actor.prevActor] || 0, - messageWidth - ); - } else if (msg.placement === diagObj.db.PLACEMENT.OVER) { - if (actor.prevActor) { - maxMessageWidthPerActor[actor.prevActor] = common.getMax( - maxMessageWidthPerActor[actor.prevActor] || 0, - messageWidth / 2 - ); - } - - if (actor.nextActor) { - maxMessageWidthPerActor[msg.from] = common.getMax( - maxMessageWidthPerActor[msg.from] || 0, - messageWidth / 2 - ); - } - } - } - }); - - log.debug('maxMessageWidthPerActor:', maxMessageWidthPerActor); - return maxMessageWidthPerActor; + return ''; } -const getRequiredPopupWidth = function (actor) { - let requiredPopupWidth = 0; - const textFont = actorFont(conf); - for (const key in actor.links) { - const labelDimensions = utils.calculateTextDimensions(key, textFont); - const labelWidth = labelDimensions.width + 2 * conf.wrapPadding + 2 * conf.boxMargin; - if (requiredPopupWidth < labelWidth) { - requiredPopupWidth = labelWidth; - } - } - - return requiredPopupWidth; -}; - -/** - * This will calculate the optimal margin for each given actor, - * for a given actor → messageWidth map. - * - * An actor's margin is determined by the width of the actor, the width of the largest message that - * originates from it, and the configured conf.actorMargin. - * - * @param actors - The actors map to calculate margins for - * @param actorToMessageWidth - A map of actor key → max message width it holds - * @param boxes - The boxes around the actors if any - */ -function calculateActorMargins( - actors: { [id: string]: any }, - actorToMessageWidth: ReturnType, - boxes -) { - let maxHeight = 0; - Object.keys(actors).forEach((prop) => { - const actor = actors[prop]; - if (actor.wrap) { - actor.description = utils.wrapLabel( - actor.description, - conf.width - 2 * conf.wrapPadding, - actorFont(conf) - ); - } - const actDims = utils.calculateTextDimensions(actor.description, actorFont(conf)); - actor.width = actor.wrap - ? conf.width - : common.getMax(conf.width, actDims.width + 2 * conf.wrapPadding); - - actor.height = actor.wrap ? common.getMax(actDims.height, conf.height) : conf.height; - maxHeight = common.getMax(maxHeight, actor.height); - }); - - for (const actorKey in actorToMessageWidth) { - const actor = actors[actorKey]; - - if (!actor) { - continue; - } - - const nextActor = actors[actor.nextActor]; - - // No need to space out an actor that doesn't have a next link - if (!nextActor) { - const messageWidth = actorToMessageWidth[actorKey]; - const actorWidth = messageWidth + conf.actorMargin - actor.width / 2; - actor.margin = common.getMax(actorWidth, conf.actorMargin); - continue; - } - - const messageWidth = actorToMessageWidth[actorKey]; - const actorWidth = messageWidth + conf.actorMargin - actor.width / 2 - nextActor.width / 2; - - actor.margin = common.getMax(actorWidth, conf.actorMargin); - } - - let maxBoxHeight = 0; - boxes.forEach((box) => { - const textFont = messageFont(conf); - let totalWidth = box.actorKeys.reduce((total, aKey) => { - return (total += actors[aKey].width + (actors[aKey].margin || 0)); - }, 0); - - totalWidth -= 2 * conf.boxTextMargin; - if (box.wrap) { - box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont); - } - - const boxMsgDimensions = utils.calculateTextDimensions(box.name, textFont); - maxBoxHeight = common.getMax(boxMsgDimensions.height, maxBoxHeight); - const minWidth = common.getMax(totalWidth, boxMsgDimensions.width + 2 * conf.wrapPadding); - box.margin = conf.boxTextMargin; - if (totalWidth < minWidth) { - const missing = (minWidth - totalWidth) / 2; - box.margin += missing; - } - }); - boxes.forEach((box) => (box.textMaxHeight = maxBoxHeight)); - - return common.getMax(maxHeight, conf.height); -} - -const buildNoteModel = function (msg, actors, diagObj) { - const startx = actors[msg.from].x; - const stopx = actors[msg.to].x; - const shouldWrap = msg.wrap && msg.message; - - let textDimensions = utils.calculateTextDimensions( - shouldWrap ? utils.wrapLabel(msg.message, conf.width, noteFont(conf)) : msg.message, - noteFont(conf) - ); - const noteModel = { - width: shouldWrap - ? conf.width - : common.getMax(conf.width, textDimensions.width + 2 * conf.noteMargin), - height: 0, - startx: actors[msg.from].x, - stopx: 0, - starty: 0, - stopy: 0, - message: msg.message, - }; - if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF) { - noteModel.width = shouldWrap - ? common.getMax(conf.width, textDimensions.width) - : common.getMax( - actors[msg.from].width / 2 + actors[msg.to].width / 2, - textDimensions.width + 2 * conf.noteMargin - ); - noteModel.startx = startx + (actors[msg.from].width + conf.actorMargin) / 2; - } else if (msg.placement === diagObj.db.PLACEMENT.LEFTOF) { - noteModel.width = shouldWrap - ? common.getMax(conf.width, textDimensions.width + 2 * conf.noteMargin) - : common.getMax( - actors[msg.from].width / 2 + actors[msg.to].width / 2, - textDimensions.width + 2 * conf.noteMargin - ); - noteModel.startx = startx - noteModel.width + (actors[msg.from].width - conf.actorMargin) / 2; - } else if (msg.to === msg.from) { - textDimensions = utils.calculateTextDimensions( - shouldWrap - ? utils.wrapLabel( - msg.message, - common.getMax(conf.width, actors[msg.from].width), - noteFont(conf) - ) - : msg.message, - noteFont(conf) - ); - noteModel.width = shouldWrap - ? common.getMax(conf.width, actors[msg.from].width) - : common.getMax( - actors[msg.from].width, - conf.width, - textDimensions.width + 2 * conf.noteMargin - ); - noteModel.startx = startx + (actors[msg.from].width - noteModel.width) / 2; - } else { - noteModel.width = - Math.abs(startx + actors[msg.from].width / 2 - (stopx + actors[msg.to].width / 2)) + - conf.actorMargin; - noteModel.startx = - startx < stopx - ? startx + actors[msg.from].width / 2 - conf.actorMargin / 2 - : stopx + actors[msg.to].width / 2 - conf.actorMargin / 2; - } - if (shouldWrap) { - noteModel.message = utils.wrapLabel( - msg.message, - noteModel.width - 2 * conf.wrapPadding, - noteFont(conf) - ); - } - log.debug( - `NM:[${noteModel.startx},${noteModel.stopx},${noteModel.starty},${noteModel.stopy}:${noteModel.width},${noteModel.height}=${msg.message}]` - ); - return noteModel; -}; - -const buildMessageModel = function (msg, actors, diagObj) { - let process = false; - if ( - [ - diagObj.db.LINETYPE.SOLID_OPEN, - diagObj.db.LINETYPE.DOTTED_OPEN, - diagObj.db.LINETYPE.SOLID, - diagObj.db.LINETYPE.DOTTED, - diagObj.db.LINETYPE.SOLID_CROSS, - diagObj.db.LINETYPE.DOTTED_CROSS, - diagObj.db.LINETYPE.SOLID_POINT, - diagObj.db.LINETYPE.DOTTED_POINT, - ].includes(msg.type) - ) { - process = true; - } - if (!process) { - return {}; - } - const fromBounds = activationBounds(msg.from, actors); - const toBounds = activationBounds(msg.to, actors); - const fromIdx = fromBounds[0] <= toBounds[0] ? 1 : 0; - const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1; - const allBounds = [...fromBounds, ...toBounds]; - const boundedWidth = Math.abs(toBounds[toIdx] - fromBounds[fromIdx]); - if (msg.wrap && msg.message) { - msg.message = utils.wrapLabel( - msg.message, - common.getMax(boundedWidth + 2 * conf.wrapPadding, conf.width), - messageFont(conf) - ); - } - const msgDims = utils.calculateTextDimensions(msg.message, messageFont(conf)); - - return { - width: common.getMax( - msg.wrap ? 0 : msgDims.width + 2 * conf.wrapPadding, - boundedWidth + 2 * conf.wrapPadding, - conf.width - ), - height: 0, - startx: fromBounds[fromIdx], - stopx: toBounds[toIdx], - starty: 0, - stopy: 0, - message: msg.message, - type: msg.type, - wrap: msg.wrap, - fromBounds: Math.min.apply(null, allBounds), - toBounds: Math.max.apply(null, allBounds), - }; -}; - -const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagObj) { - const loops = {}; - const stack = []; - let current, noteModel, msgModel; - - messages.forEach(function (msg) { - msg.id = utils.random({ length: 10 }); - switch (msg.type) { - case diagObj.db.LINETYPE.LOOP_START: - case diagObj.db.LINETYPE.ALT_START: - case diagObj.db.LINETYPE.OPT_START: - case diagObj.db.LINETYPE.PAR_START: - case diagObj.db.LINETYPE.PAR_OVER_START: - case diagObj.db.LINETYPE.CRITICAL_START: - case diagObj.db.LINETYPE.BREAK_START: - stack.push({ - id: msg.id, - msg: msg.message, - from: Number.MAX_SAFE_INTEGER, - to: Number.MIN_SAFE_INTEGER, - width: 0, - }); - break; - case diagObj.db.LINETYPE.ALT_ELSE: - case diagObj.db.LINETYPE.PAR_AND: - case diagObj.db.LINETYPE.CRITICAL_OPTION: - if (msg.message) { - current = stack.pop(); - loops[current.id] = current; - loops[msg.id] = current; - stack.push(current); - } - break; - case diagObj.db.LINETYPE.LOOP_END: - case diagObj.db.LINETYPE.ALT_END: - case diagObj.db.LINETYPE.OPT_END: - case diagObj.db.LINETYPE.PAR_END: - case diagObj.db.LINETYPE.CRITICAL_END: - case diagObj.db.LINETYPE.BREAK_END: - current = stack.pop(); - loops[current.id] = current; - break; - case diagObj.db.LINETYPE.ACTIVE_START: - { - const actorRect = actors[msg.from ? msg.from.actor : msg.to.actor]; - const stackedSize = actorActivations(msg.from ? msg.from.actor : msg.to.actor).length; - const x = - actorRect.x + actorRect.width / 2 + ((stackedSize - 1) * conf.activationWidth) / 2; - const toAdd = { - startx: x, - stopx: x + conf.activationWidth, - actor: msg.from.actor, - enabled: true, - }; - bounds.activations.push(toAdd); - } - break; - case diagObj.db.LINETYPE.ACTIVE_END: - { - const lastActorActivationIdx = bounds.activations - .map((a) => a.actor) - .lastIndexOf(msg.from.actor); - delete bounds.activations.splice(lastActorActivationIdx, 1)[0]; - } - break; - } - const isNote = msg.placement !== undefined; - if (isNote) { - noteModel = buildNoteModel(msg, actors, diagObj); - msg.noteModel = noteModel; - stack.forEach((stk) => { - current = stk; - current.from = common.getMin(current.from, noteModel.startx); - current.to = common.getMax(current.to, noteModel.startx + noteModel.width); - current.width = - common.getMax(current.width, Math.abs(current.from - current.to)) - conf.labelBoxWidth; - }); - } else { - msgModel = buildMessageModel(msg, actors, diagObj); - msg.msgModel = msgModel; - if (msgModel.startx && msgModel.stopx && stack.length > 0) { - stack.forEach((stk) => { - current = stk; - if (msgModel.startx === msgModel.stopx) { - const from = actors[msg.from]; - const to = actors[msg.to]; - current.from = common.getMin( - from.x - msgModel.width / 2, - from.x - from.width / 2, - current.from - ); - current.to = common.getMax( - to.x + msgModel.width / 2, - to.x + from.width / 2, - current.to - ); - current.width = - common.getMax(current.width, Math.abs(current.to - current.from)) - - conf.labelBoxWidth; - } else { - current.from = common.getMin(msgModel.startx, current.from); - current.to = common.getMax(msgModel.stopx, current.to); - current.width = common.getMax(current.width, msgModel.width) - conf.labelBoxWidth; - } - }); - } - } - }); - bounds.activations = []; - log.debug('Loop type widths:', loops); - return loops; -}; - export default { - bounds, - drawActors, - drawActorsPopup, - setConf, draw, }; From fe3dd5a531f1489bfc9b0823fe26711a8d69b88d Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 008/105] Updated dockerfile --- docker-compose.yml | 2 ++ run | 33 ++++++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2762f3b99..cdf21c936 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,3 +7,5 @@ services: working_dir: /mermaid volumes: - ./:/mermaid + ports: + - 9000:9000 diff --git a/run b/run index 6afe76eee..4da4093ee 100755 --- a/run +++ b/run @@ -1,8 +1,12 @@ #!/bin/bash RUN="docker-compose run --rm" +# UP="docker-compose up" + +name=$(basename $0) command=$1 args=${@:2} + case $command in sh) @@ -17,24 +21,43 @@ test) $RUN mermaid sh -c "npx pnpm test" ;; +e2e) +$RUN mermaid sh -c "npx pnpm e2e" +;; + lint) $RUN mermaid sh -c "npx pnpm -w run lint:fix" ;; +dev) +$RUN --service-ports mermaid sh -c "npx pnpm run dev" +# $UP --rm mermaid sh -c "npx pnpm run dev" +;; + help) +usage=$( cat < Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 009/105] Some fixes to docker and demos --- demos/sankey.html | 43 +++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 5 +++++ run | 4 ++-- 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 demos/sankey.html diff --git a/demos/sankey.html b/demos/sankey.html new file mode 100644 index 000000000..57f3a8af8 --- /dev/null +++ b/demos/sankey.html @@ -0,0 +1,43 @@ + + + + + + States Mermaid Quick Test Page + + + + +

Sankey diagram demos

+

Simple flow

+
+      stateDiagram-v2
+      direction LR
+      State1: A state with a note
+      note right of State1
+        Important information!
You can write notes.
And\nthey\ncan\nbe\nmulti-\nline. + end note + State1 --> State2 + note left of State2 : Notes can be to the left of a state\n(like this one). + note right of State2 : Notes can be to the right of a state\n(like this one). +
+ + + diff --git a/docker-compose.yml b/docker-compose.yml index cdf21c936..2bd00a277 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,5 +7,10 @@ services: working_dir: /mermaid volumes: - ./:/mermaid + - root_cache:/root/.cache + - root_local:/root/.local ports: - 9000:9000 +volumes: + root_cache: + root_local: \ No newline at end of file diff --git a/run b/run index 4da4093ee..7dbdf918a 100755 --- a/run +++ b/run @@ -13,7 +13,7 @@ sh) $RUN mermaid sh $args ;; -install) +i | install) $RUN mermaid sh -c "npx pnpm install" ;; @@ -40,7 +40,7 @@ cat < Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 010/105] Started sankey syntax --- .../src/diagrams/sankey/parser/sankey.jison | 342 +----------------- .../src/diagrams/sankey/parser/sankey.spec.js | 52 +-- 2 files changed, 40 insertions(+), 354 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison index 074cd5975..d77b0b241 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -1,350 +1,30 @@ -/** mermaid - * https://mermaidjs.github.io/ - * (c) 2014-2015 Knut Sveidqvist - * MIT license. - * - * Based on js sequence diagrams jison grammr - * https://bramp.github.io/js-sequence-diagrams/ - * (c) 2012-2013 Andrew Brampton (bramp.net) - * Simplified BSD license. - */ +/** mermaid */ %lex %options case-insensitive -// Special states for recognizing aliases -// A special state for grabbing text up to the first comment/newline -%x ID ALIAS LINE - -// Directive states -%x open_directive type_directive arg_directive -%x acc_title -%x acc_descr -%x acc_descr_multiline %% +"sankey" return 'SANKEY' +"->" return 'ARROW' +\w+ return 'NODE' +[\n]+ return 'NEWLINE'; +\s+ /* skip all whitespace */ -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; -[\n]+ return 'NEWLINE'; -\s+ /* skip all whitespace */ -((?!\n)\s)+ /* skip same-line whitespace */ -\#[^\n]* /* skip comments */ -\%%(?!\{)[^\n]* /* skip comments */ -[^\}]\%\%[^\n]* /* skip comments */ -[0-9]+(?=[ \n]+) return 'NUM'; -"box" { this.begin('LINE'); return 'box'; } -"participant" { this.begin('ID'); return 'participant'; } -"actor" { this.begin('ID'); return 'participant_actor'; } -[^\->:\n,;]+?([\-]*[^\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } -"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } -(?:) { this.popState(); this.popState(); return 'NEWLINE'; } -"loop" { this.begin('LINE'); return 'loop'; } -"rect" { this.begin('LINE'); return 'rect'; } -"opt" { this.begin('LINE'); return 'opt'; } -"alt" { this.begin('LINE'); return 'alt'; } -"else" { this.begin('LINE'); return 'else'; } -"par" { this.begin('LINE'); return 'par'; } -"par_over" { this.begin('LINE'); return 'par_over'; } -"and" { this.begin('LINE'); return 'and'; } -"critical" { this.begin('LINE'); return 'critical'; } -"option" { this.begin('LINE'); return 'option'; } -"break" { this.begin('LINE'); return 'break'; } -(?:[:]?(?:no)?wrap:)?[^#\n;]* { this.popState(); return 'restOfLine'; } -"end" return 'end'; -"left of" return 'left_of'; -"right of" return 'right_of'; -"links" return 'links'; -"link" return 'link'; -"properties" return 'properties'; -"details" return 'details'; -"over" return 'over'; -"note" return 'note'; -"activate" { this.begin('ID'); return 'activate'; } -"deactivate" { this.begin('ID'); return 'deactivate'; } -"title"\s[^#\n;]+ return 'title'; -"title:"\s[^#\n;]+ return 'legacy_title'; -accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } -(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } -accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } -(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } -accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} -[\}] { this.popState(); } -[^\}]* return "acc_descr_multiline_value"; -"sequenceDiagram" return 'SD'; -"autonumber" return 'autonumber'; -"off" return 'off'; -"," return ','; -";" return 'NEWLINE'; -[^\+\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\->:\n,;]+)* { yytext = yytext.trim(); return 'ACTOR'; } -"->>" return 'SOLID_ARROW'; -"-->>" return 'DOTTED_ARROW'; -"->" return 'SOLID_OPEN_ARROW'; -"-->" return 'DOTTED_OPEN_ARROW'; -\-[x] return 'SOLID_CROSS'; -\-\-[x] return 'DOTTED_CROSS'; -\-[\)] return 'SOLID_POINT'; -\-\-[\)] return 'DOTTED_POINT'; -":"(?:(?:no)?wrap:)?[^#\n;]+ return 'TXT'; -"+" return '+'; -"-" return '-'; -<> return 'NEWLINE'; -. return 'INVALID'; +// TODO: check if jison will return 2 separate tokens (for nodes) while ignoring whitespace /lex -%left '^' - %start start %% /* language grammar */ start : SPACE start - | NEWLINE start - | directive start - | SD document { yy.apply($2);return $2; } + | SANKEY document ; document - : /* empty */ { $$ = [] } - | document line {$1.push($2);$$ = $1} + : node document + | /* empty */ ; - -line - : SPACE statement { $$ = $2 } - | statement { $$ = $1 } - | NEWLINE { $$=[]; } - ; - -box_section - : /* empty */ { $$ = [] } - | box_section box_line {$1.push($2);$$ = $1} - ; - -box_line - : SPACE participant_statement { $$ = $2 } - | participant_statement { $$ = $1 } - | NEWLINE { $$=[]; } - ; - - -directive - : openDirective typeDirective closeDirective 'NEWLINE' - | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' - ; - -statement - : participant_statement - | 'box' restOfLine box_section end - { - $3.unshift({type: 'boxStart', boxData:yy.parseBoxData($2) }); - $3.push({type: 'boxEnd', boxText:$2}); - $$=$3;} - | signal 'NEWLINE' - | autonumber NUM NUM 'NEWLINE' { $$= {type:'sequenceIndex',sequenceIndex: Number($2), sequenceIndexStep:Number($3), sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER};} - | autonumber NUM 'NEWLINE' { $$ = {type:'sequenceIndex',sequenceIndex: Number($2), sequenceIndexStep:1, sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER};} - | autonumber off 'NEWLINE' { $$ = {type:'sequenceIndex', sequenceVisible:false, signalType:yy.LINETYPE.AUTONUMBER};} - | autonumber 'NEWLINE' {$$ = {type:'sequenceIndex', sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER}; } - | 'activate' actor 'NEWLINE' {$$={type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $2};} - | 'deactivate' actor 'NEWLINE' {$$={type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $2};} - | note_statement 'NEWLINE' - | links_statement 'NEWLINE' - | link_statement 'NEWLINE' - | properties_statement 'NEWLINE' - | details_statement 'NEWLINE' - | title {yy.setDiagramTitle($1.substring(6));$$=$1.substring(6);} - | legacy_title {yy.setDiagramTitle($1.substring(7));$$=$1.substring(7);} - | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } - | acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); } - | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } - | 'loop' restOfLine document end - { - $3.unshift({type: 'loopStart', loopText:yy.parseMessage($2), signalType: yy.LINETYPE.LOOP_START}); - $3.push({type: 'loopEnd', loopText:$2, signalType: yy.LINETYPE.LOOP_END}); - $$=$3;} - | 'rect' restOfLine document end - { - $3.unshift({type: 'rectStart', color:yy.parseMessage($2), signalType: yy.LINETYPE.RECT_START }); - $3.push({type: 'rectEnd', color:yy.parseMessage($2), signalType: yy.LINETYPE.RECT_END }); - $$=$3;} - | opt restOfLine document end - { - $3.unshift({type: 'optStart', optText:yy.parseMessage($2), signalType: yy.LINETYPE.OPT_START}); - $3.push({type: 'optEnd', optText:yy.parseMessage($2), signalType: yy.LINETYPE.OPT_END}); - $$=$3;} - | alt restOfLine else_sections end - { - // Alt start - $3.unshift({type: 'altStart', altText:yy.parseMessage($2), signalType: yy.LINETYPE.ALT_START}); - // Content in alt is already in $3 - // End - $3.push({type: 'altEnd', signalType: yy.LINETYPE.ALT_END}); - $$=$3;} - | par restOfLine par_sections end - { - // Parallel start - $3.unshift({type: 'parStart', parText:yy.parseMessage($2), signalType: yy.LINETYPE.PAR_START}); - // Content in par is already in $3 - // End - $3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END}); - $$=$3;} - | par_over restOfLine par_sections end - { - // Parallel (overlapped) start - $3.unshift({type: 'parStart', parText:yy.parseMessage($2), signalType: yy.LINETYPE.PAR_OVER_START}); - // Content in par is already in $3 - // End - $3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END}); - $$=$3;} - | critical restOfLine option_sections end - { - // critical start - $3.unshift({type: 'criticalStart', criticalText:yy.parseMessage($2), signalType: yy.LINETYPE.CRITICAL_START}); - // Content in critical is already in $3 - // critical end - $3.push({type: 'criticalEnd', signalType: yy.LINETYPE.CRITICAL_END}); - $$=$3;} - | break restOfLine document end - { - $3.unshift({type: 'breakStart', breakText:yy.parseMessage($2), signalType: yy.LINETYPE.BREAK_START}); - $3.push({type: 'breakEnd', optText:yy.parseMessage($2), signalType: yy.LINETYPE.BREAK_END}); - $$=$3;} - | directive - ; - -option_sections - : document - | document option restOfLine option_sections - { $$ = $1.concat([{type: 'option', optionText:yy.parseMessage($3), signalType: yy.LINETYPE.CRITICAL_OPTION}, $4]); } - ; - -par_sections - : document - | document and restOfLine par_sections - { $$ = $1.concat([{type: 'and', parText:yy.parseMessage($3), signalType: yy.LINETYPE.PAR_AND}, $4]); } - ; - -else_sections - : document - | document else restOfLine else_sections - { $$ = $1.concat([{type: 'else', altText:yy.parseMessage($3), signalType: yy.LINETYPE.ALT_ELSE}, $4]); } - ; - -participant_statement - : 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;} - | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;} - ; - -note_statement - : 'note' placement actor text2 - { - $$ = [$3, {type:'addNote', placement:$2, actor:$3.actor, text:$4}];} - | 'note' 'over' actor_pair text2 - { - // Coerce actor_pair into a [to, from, ...] array - $2 = [].concat($3, $3).slice(0, 2); - $2[0] = $2[0].actor; - $2[1] = $2[1].actor; - $$ = [$3, {type:'addNote', placement:yy.PLACEMENT.OVER, actor:$2.slice(0, 2), text:$4}];} - ; - -links_statement - : 'links' actor text2 - { - $$ = [$2, {type:'addLinks', actor:$2.actor, text:$3}]; - } - ; - -link_statement - : 'link' actor text2 - { - $$ = [$2, {type:'addALink', actor:$2.actor, text:$3}]; - } - ; - -properties_statement - : 'properties' actor text2 - { - $$ = [$2, {type:'addProperties', actor:$2.actor, text:$3}]; - } - ; - -details_statement - : 'details' actor text2 - { - $$ = [$2, {type:'addDetails', actor:$2.actor, text:$3}]; - } - ; - -spaceList - : SPACE spaceList - | SPACE - ; -actor_pair - : actor ',' actor { $$ = [$1, $3]; } - | actor { $$ = $1; } - ; - -placement - : 'left_of' { $$ = yy.PLACEMENT.LEFTOF; } - | 'right_of' { $$ = yy.PLACEMENT.RIGHTOF; } - ; - -signal - : actor signaltype '+' actor text2 - { $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$2, msg:$5}, - {type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $4} - ]} - | actor signaltype '-' actor text2 - { $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$2, msg:$5}, - {type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $1} - ]} - | actor signaltype actor text2 - { $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]} - ; - -// actor -// : actor_participant -// | actor_actor -// ; - -actor: ACTOR {$$={ type: 'addParticipant', actor:$1}}; -// actor_actor: ACTOR {$$={type: 'addActor', actor:$1}}; - -signaltype - : SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; } - | DOTTED_OPEN_ARROW { $$ = yy.LINETYPE.DOTTED_OPEN; } - | SOLID_ARROW { $$ = yy.LINETYPE.SOLID; } - | DOTTED_ARROW { $$ = yy.LINETYPE.DOTTED; } - | SOLID_CROSS { $$ = yy.LINETYPE.SOLID_CROSS; } - | DOTTED_CROSS { $$ = yy.LINETYPE.DOTTED_CROSS; } - | SOLID_POINT { $$ = yy.LINETYPE.SOLID_POINT; } - | DOTTED_POINT { $$ = yy.LINETYPE.DOTTED_POINT; } - ; - -text2 - : TXT {$$ = yy.parseMessage($1.trim().substring(1)) } - ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'sequence'); } - ; - -%% + \ No newline at end of file diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js index 995c92478..5ef6f80fe 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js @@ -11,34 +11,40 @@ describe('Sankey diagram', function () { diagram.parser.yy.clear(); }); - it('one simple flow', function () { - const str = ` - sankey - a -> 30 -> b - `; - + it('recognized its type', function() { + const str=`sankey`; + parser.parse(str); }); - it('multiple flows', function () { - const str = ` - sankey - a -> 30 -> b - c -> 30 -> d - c -> 40 -> e - `; + // it('one simple flow', function () { + // const str = ` + // sankey + // a -> 30 -> b + // `; - parser.parse(str); - }); + // parser.parse(str); + // }); - it('multiple flows', function () { - const str = ` - sankey - a -> 30 -> b - c -> 30 -> d - `; + // it('multiple flows', function () { + // const str = ` + // sankey + // a -> 30 -> b + // c -> 30 -> d + // c -> 40 -> e + // `; - parser.parse(str); - }); + // parser.parse(str); + // }); + + // it('multiple flows', function () { + // const str = ` + // sankey + // a -> 30 -> b + // c -> 30 -> d + // `; + + // parser.parse(str); + // }); }); }); From 567686e140a4a2d4f35d5b5edf3df52486be29e4 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 011/105] Implementing new syntax --- .../diagrams/sankey/parser/desired_syntax.md | 6 +- .../src/diagrams/sankey/parser/sankey.jison | 59 ++++++++++-- .../src/diagrams/sankey/parser/sankey.spec.js | 90 ++++++++++++++++--- run | 10 ++- 4 files changed, 139 insertions(+), 26 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/parser/desired_syntax.md b/packages/mermaid/src/diagrams/sankey/parser/desired_syntax.md index d0cbd4e8a..1b5cb81d2 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/desired_syntax.md +++ b/packages/mermaid/src/diagrams/sankey/parser/desired_syntax.md @@ -100,9 +100,9 @@ a -> { 30 40 } -> b -> { - 20 -> d - 50 -> e -} + 20 -> d -> 11 + 50 -> e -> 11 +} -> f -> 30 ``` **Probably ambiguous!** diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison index d77b0b241..e0b4252ca 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -2,13 +2,21 @@ %lex %options case-insensitive +%s group +// %x attributes %% +"{" { this.pushState('group'); return 'OPEN_GROUP'; } +"}" { this.popState('group'); return 'CLOSE_GROUP'; } "sankey" return 'SANKEY' -"->" return 'ARROW' -\w+ return 'NODE' -[\n]+ return 'NEWLINE'; -\s+ /* skip all whitespace */ +\d+ return 'VALUE' +"->" return 'ARROW' +\w+ return 'NODE' +"[" {/*this.pushState('attributes');*/ return 'OPEN_ATTRIBUTES'; } +"]" { /* this.popState(); */ return 'CLOSE_ATTRIBUTES'; } +(<>|[\n;])+ return 'EOS' // end of statement +\s+ // skip all whitespace +// [\n]+ return 'NEWLINE'; // TODO: check if jison will return 2 separate tokens (for nodes) while ignoring whitespace @@ -16,15 +24,50 @@ %start start -%% /* language grammar */ +%% // language grammar start - : SPACE start + : EOS SANKEY document | SANKEY document ; document - : node document + : line document + | // empty + ; + +line + // : node_with_attributes // one node with attributes + : flow EOS + | node_with_attributes EOS + | EOS + ; + + +node_with_attributes + : NODE + | NODE attributes_group + ; + +attributes_group + : OPEN_ATTRIBUTES attributes CLOSE_ATTRIBUTES + ; + +attributes: + | // TODO + ; + +flow + : NODE ARROW value_or_values_group ARROW flow + | NODE + ; + +value_or_values_group + : OPEN_GROUP values CLOSE_GROUP + | VALUE + ; + +values + : values VALUE | /* empty */ ; - \ No newline at end of file diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js index 5ef6f80fe..d96663f31 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js @@ -1,6 +1,7 @@ import diagram from './sankey.jison'; import { parser } from './sankey.jison'; import db from '../sankeyDB.js'; +// import { fail } from 'assert'; describe('Sankey diagram', function () { // TODO - these examples should be put into ./parser/stateDiagram.spec.js @@ -11,39 +12,104 @@ describe('Sankey diagram', function () { diagram.parser.yy.clear(); }); - it('recognized its type', function() { + it('recognizes its type', () => { const str=`sankey`; parser.parse(str); }); - // it('one simple flow', function () { + it('recognizes one flow', () => { + const str = ` + sankey + a -> 30 -> b -> 20 -> c + `; + + parser.parse(str); + }); + + it('recognizes multiple flows', () => { + const str = ` + sankey + a -> 30 -> b -> 12 -> e + c -> 30 -> d -> 12 -> e + c -> 40 -> e -> 12 -> e + `; + + parser.parse(str); + }); + + it('recognizes grouped values', () => { + const str = ` + sankey + a -> {30} -> b + `; + + parser.parse(str); + }); + + it('recognizes a separate node with its attributes', () => { + const str = ` + sankey + c[] + `; + + parser.parse(str); + }); + + // it('recognizes intake group', () => { // const str = ` // sankey - // a -> 30 -> b + // a -> { + // 30 -> b + // 40 -> c + // } // `; - + // parser.parse(str); // }); - // it('multiple flows', function () { + // it('recognizes exhaust group', () => { // const str = ` // sankey - // a -> 30 -> b - // c -> 30 -> d - // c -> 40 -> e + // { + // b -> 30 + // c -> 40 + // } -> a // `; - + // parser.parse(str); // }); - // it('multiple flows', function () { + // it('what to do?', () => { // const str = ` // sankey - // a -> 30 -> b - // c -> 30 -> d + // { + // b -> 30 + // c -> 40 + // } -> { + // e + // d + // } // `; + + // parser.parse(str); + // }); + // it('complex', () => { + // const str = ` + // sankey + // { + // a -> 30 + // b -> 40 + // } -> c -> { + // 20 -> e -> 49 + // 20 -> d -> 23 + // } -> k -> 20 -> i -> { + // 10 -> f + // 11 -> j + // } + // `; + // parser.parse(str); // }); }); diff --git a/run b/run index 7dbdf918a..9b02b0741 100755 --- a/run +++ b/run @@ -14,15 +14,19 @@ $RUN mermaid sh $args ;; i | install) -$RUN mermaid sh -c "npx pnpm install" +$RUN mermaid sh -c "npx pnpm install $args" ;; test) -$RUN mermaid sh -c "npx pnpm test" +$RUN mermaid sh -c "npx pnpm test $args" +;; + +vitest) +$RUN mermaid sh -c "npx pnpm vitest $args" ;; e2e) -$RUN mermaid sh -c "npx pnpm e2e" +$RUN mermaid sh -c "npx pnpm e2e $args" ;; lint) From d2226604e447a2a339abdbb400c403173775c2fb Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 012/105] Recognizing attrubutes --- .../src/diagrams/sankey/parser/sankey.jison | 62 +++++++++---------- .../src/diagrams/sankey/parser/sankey.spec.js | 30 +++++---- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison index e0b4252ca..cc262f672 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -3,20 +3,29 @@ %options case-insensitive %s group -// %x attributes +%x attributes +%x attribute +%x value %% -"{" { this.pushState('group'); return 'OPEN_GROUP'; } -"}" { this.popState('group'); return 'CLOSE_GROUP'; } "sankey" return 'SANKEY' -\d+ return 'VALUE' +\d+ return 'AMOUNT' "->" return 'ARROW' \w+ return 'NODE' -"[" {/*this.pushState('attributes');*/ return 'OPEN_ATTRIBUTES'; } -"]" { /* this.popState(); */ return 'CLOSE_ATTRIBUTES'; } -(<>|[\n;])+ return 'EOS' // end of statement +(?:<>|[\n;])+ { return 'EOS'; } // end of statement is ; \n or end of file \s+ // skip all whitespace -// [\n]+ return 'NEWLINE'; +"{" { this.pushState('group'); return 'OPEN_GROUP'; } +"}" { this.popState('group'); return 'CLOSE_GROUP'; } +"[" { this.pushState('attributes'); return 'OPEN_ATTRIBUTES'; } +"]" { this.popState(); return 'CLOSE_ATTRIBUTES'; } +\w+ { return 'ATTRIBUTE'; } // string followed by = sign is "attrName" +(?=\=s*)[\s\w] {return 'VALUE';} +\= { this.pushState('attribute'); return 'EQUAL'; } +\s+ // skip all whitespace +[\w]+ {this.popState(); return 'VALUE';} +\s+ //skip +\" { this.pushState('value'); return 'OPEN_VALUE'; } +\" { this.popState(); return 'CLOSE_VALUE'; } // TODO: check if jison will return 2 separate tokens (for nodes) while ignoring whitespace @@ -33,41 +42,26 @@ start document : line document - | // empty + | ; line - // : node_with_attributes // one node with attributes : flow EOS | node_with_attributes EOS | EOS ; - -node_with_attributes - : NODE - | NODE attributes_group - ; +node_with_attributes: NODE OPEN_ATTRIBUTES attributes CLOSE_ATTRIBUTES; -attributes_group - : OPEN_ATTRIBUTES attributes CLOSE_ATTRIBUTES - ; +attributes: attribute attributes | ; +attribute: ATTRIBUTE EQUAL VALUE | ATTRIBUTE; -attributes: - | // TODO - ; +// flow +// : NODE ARROW value_or_values_group ARROW flow +// | NODE +// ; -flow - : NODE ARROW value_or_values_group ARROW flow - | NODE - ; +flow: n_chain_a; -value_or_values_group - : OPEN_GROUP values CLOSE_GROUP - | VALUE - ; - -values - : values VALUE - | /* empty */ - ; +n_chain_a: NODE ARROW a_chain_n | NODE; +a_chain_n: AMOUNT ARROW n_chain_a | AMOUNT; diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js index d96663f31..8617c4bbc 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js @@ -30,31 +30,37 @@ describe('Sankey diagram', function () { it('recognizes multiple flows', () => { const str = ` sankey - a -> 30 -> b -> 12 -> e - c -> 30 -> d -> 12 -> e - c -> 40 -> e -> 12 -> e + a -> 30 -> b -> 12 -> e; + c -> 30 -> d -> 12 -> e; + c -> 40 -> e -> 12 -> q; `; parser.parse(str); }); - it('recognizes grouped values', () => { + it('recognizes a separate node with its attributes', () => { const str = ` sankey - a -> {30} -> b + a[] + b[attr=1] + c[attr=2] + d[attrWithoutValue] + d[attr = 3] `; parser.parse(str); }); - it('recognizes a separate node with its attributes', () => { - const str = ` - sankey - c[] - `; + // it('recognizes grouped values', () => { + // const str = ` + // sankey + // a -> {30} -> b + // `; - parser.parse(str); - }); + // parser.parse(str); + // }); + + // it('recognizes intake group', () => { // const str = ` From b4b535f9973dd4e7073791a179df5f9d27421697 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 013/105] Recognizing attributes --- .../src/diagrams/sankey/parser/sankey.jison | 46 +++++++++++-------- .../src/diagrams/sankey/parser/sankey.spec.js | 13 ++++-- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison index cc262f672..3adc6a406 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -2,30 +2,36 @@ %lex %options case-insensitive +%options easy_keyword_rules + %s group +// when we are inside [] section we are defining attrubutes %x attributes -%x attribute -%x value +// after attr= we are expecting a value without quotes +%x value +// or if we use "" we are expecting a string containing value +%x string %% -"sankey" return 'SANKEY' -\d+ return 'AMOUNT' -"->" return 'ARROW' -\w+ return 'NODE' -(?:<>|[\n;])+ { return 'EOS'; } // end of statement is ; \n or end of file -\s+ // skip all whitespace -"{" { this.pushState('group'); return 'OPEN_GROUP'; } -"}" { this.popState('group'); return 'CLOSE_GROUP'; } -"[" { this.pushState('attributes'); return 'OPEN_ATTRIBUTES'; } -"]" { this.popState(); return 'CLOSE_ATTRIBUTES'; } -\w+ { return 'ATTRIBUTE'; } // string followed by = sign is "attrName" -(?=\=s*)[\s\w] {return 'VALUE';} -\= { this.pushState('attribute'); return 'EQUAL'; } -\s+ // skip all whitespace -[\w]+ {this.popState(); return 'VALUE';} -\s+ //skip -\" { this.pushState('value'); return 'OPEN_VALUE'; } -\" { this.popState(); return 'CLOSE_VALUE'; } +"sankey" { return 'SANKEY'; } +\d+ { return 'AMOUNT'; } +"->" { return 'ARROW'; } +\w+ { return 'NODE'; } +(?:<>|[\n;])+ { return 'EOS'; } // end of statement is ; \n or end of file +\s+ // skip all whitespace +"{" { this.pushState('group'); return 'OPEN_GROUP'; } +"}" { this.popState('group'); return 'CLOSE_GROUP'; } +"[" { this.pushState('attributes'); return 'OPEN_ATTRIBUTES'; } +"]" { this.popState(); return 'CLOSE_ATTRIBUTES'; } +\w+ { return 'ATTRIBUTE'; } +(?=\=s*)[\s\w] { return 'VALUE';} +\= { this.pushState('value'); return 'EQUAL'; } +\s+ // skip all whitespace +[\w]+ { this.popState(); return 'VALUE';} +\s+ //skip +\" { this.pushState('string'); return 'OPEN_STRING'; } +\" { this.popState(); return 'CLOSE_STRING'; } +[\w\s]+(?=\") { return 'STRING'; } // TODO: check if jison will return 2 separate tokens (for nodes) while ignoring whitespace diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js index 8617c4bbc..4cd1befae 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js @@ -41,11 +41,14 @@ describe('Sankey diagram', function () { it('recognizes a separate node with its attributes', () => { const str = ` sankey - a[] - b[attr=1] - c[attr=2] - d[attrWithoutValue] - d[attr = 3] + node[] + node[attr=1] + node[attr=2] + a -> 30 -> b + node[attrWithoutValue] + node[attr = 3] + node[attr1 = 23413 attr2=1234] + node[x1dfqowie attr1 = 23413 attr2] `; parser.parse(str); From f71769e07ba119c165c9bc2c02ed201f65b243a3 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 014/105] Attributes within strings --- .../src/diagrams/sankey/parser/sankey.jison | 17 ++++---- .../src/diagrams/sankey/parser/sankey.spec.js | 41 ++++++++++++------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison index 3adc6a406..53c427729 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -20,7 +20,7 @@ (?:<>|[\n;])+ { return 'EOS'; } // end of statement is ; \n or end of file \s+ // skip all whitespace "{" { this.pushState('group'); return 'OPEN_GROUP'; } -"}" { this.popState('group'); return 'CLOSE_GROUP'; } +"}" { this.popState(); return 'CLOSE_GROUP'; } "[" { this.pushState('attributes'); return 'OPEN_ATTRIBUTES'; } "]" { this.popState(); return 'CLOSE_ATTRIBUTES'; } \w+ { return 'ATTRIBUTE'; } @@ -30,8 +30,12 @@ [\w]+ { this.popState(); return 'VALUE';} \s+ //skip \" { this.pushState('string'); return 'OPEN_STRING'; } -\" { this.popState(); return 'CLOSE_STRING'; } -[\w\s]+(?=\") { return 'STRING'; } +(?!\\)\" { + if(this.topState()==='string') this.popState(); + if(this.topState()==='value') this.popState(); + return 'CLOSE_STRING'; +} +([^"\\]|\\\")+ { console.log(this.state); return 'STRING'; } // TODO: check if jison will return 2 separate tokens (for nodes) while ignoring whitespace @@ -60,12 +64,9 @@ line node_with_attributes: NODE OPEN_ATTRIBUTES attributes CLOSE_ATTRIBUTES; attributes: attribute attributes | ; -attribute: ATTRIBUTE EQUAL VALUE | ATTRIBUTE; +attribute: ATTRIBUTE EQUAL value | ATTRIBUTE; -// flow -// : NODE ARROW value_or_values_group ARROW flow -// | NODE -// ; +value: VALUE | OPEN_STRING STRING CLOSE_STRING; flow: n_chain_a; diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js index 4cd1befae..576862fd8 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js @@ -38,22 +38,35 @@ describe('Sankey diagram', function () { parser.parse(str); }); - it('recognizes a separate node with its attributes', () => { - const str = ` - sankey - node[] - node[attr=1] - node[attr=2] - a -> 30 -> b - node[attrWithoutValue] - node[attr = 3] - node[attr1 = 23413 attr2=1234] - node[x1dfqowie attr1 = 23413 attr2] - `; - - parser.parse(str); + describe('while attributes parsing', () => { + it('parses different quotless variations', () => { + const str = ` + sankey + node[] + node[attr=1] + a -> 30 -> b + node[attrWithoutValue] + node[attr = 3] + node[attr1 = 23413 attr2=1234] + node[x1dfqowie attr1 = 23413 attr2] + `; + + parser.parse(str); + }); + + it('parses strings as values', () => { + const str = ` + sankey + node[attr="hello, how are you?"] + node[attr="hello\\""] + `; + + parser.parse(str); + }); }); + + // it('recognizes grouped values', () => { // const str = ` // sankey From 5eae790740fe30599040aac0167629c832e4d4ad Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 015/105] Sankey stream is ending on node only --- .../src/diagrams/sankey/parser/sankey.jison | 24 +++--- .../src/diagrams/sankey/parser/sankey.spec.js | 80 ++----------------- 2 files changed, 20 insertions(+), 84 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison index 53c427729..e15dcf5d0 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -35,9 +35,7 @@ if(this.topState()==='value') this.popState(); return 'CLOSE_STRING'; } -([^"\\]|\\\")+ { console.log(this.state); return 'STRING'; } - -// TODO: check if jison will return 2 separate tokens (for nodes) while ignoring whitespace +([^"\\]|\\\")+ { return 'STRING'; } /lex @@ -55,20 +53,28 @@ document | ; + // : NODE exhaust intake exhaust_chain optional_attributes EOS line - : flow EOS - | node_with_attributes EOS + : stream optional_attributes EOS + | NODE optional_attributes EOS | EOS ; -node_with_attributes: NODE OPEN_ATTRIBUTES attributes CLOSE_ATTRIBUTES; +optional_attributes: OPEN_ATTRIBUTES attributes CLOSE_ATTRIBUTES | ; attributes: attribute attributes | ; attribute: ATTRIBUTE EQUAL value | ATTRIBUTE; value: VALUE | OPEN_STRING STRING CLOSE_STRING; -flow: n_chain_a; +stream: NODE ARROW AMOUNT ARROW tail; +tail: stream | NODE; -n_chain_a: NODE ARROW a_chain_n | NODE; -a_chain_n: AMOUNT ARROW n_chain_a | AMOUNT; +// exhaust_chain: ARROW AMOUNT intake_chain | ; +// intake_chain: ARROW NODE exhaust_chain | ; + +// exhaust: ARROW AMOUNT; +// intake: ARROW NODE; + +// node_chain_amount: NODE ARROW amount_node_chain | NODE; +// amount_node_chain: AMOUNT ARROW node_chain_amount | AMOUNT; diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js index 576862fd8..83d87ac71 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js @@ -30,9 +30,9 @@ describe('Sankey diagram', function () { it('recognizes multiple flows', () => { const str = ` sankey - a -> 30 -> b -> 12 -> e; - c -> 30 -> d -> 12 -> e; - c -> 40 -> e -> 12 -> q; + a -> 30 -> b -> 12 -> e + c -> 30 -> d -> 12 -> e + c -> 40 -> e -> 12 -> q `; parser.parse(str); @@ -58,81 +58,11 @@ describe('Sankey diagram', function () { const str = ` sankey node[attr="hello, how are you?"] - node[attr="hello\\""] + node[attr="hello\\"afaasd"] `; parser.parse(str); }); - }); - - - - // it('recognizes grouped values', () => { - // const str = ` - // sankey - // a -> {30} -> b - // `; - - // parser.parse(str); - // }); - - - - // it('recognizes intake group', () => { - // const str = ` - // sankey - // a -> { - // 30 -> b - // 40 -> c - // } - // `; - - // parser.parse(str); - // }); - - // it('recognizes exhaust group', () => { - // const str = ` - // sankey - // { - // b -> 30 - // c -> 40 - // } -> a - // `; - - // parser.parse(str); - // }); - - // it('what to do?', () => { - // const str = ` - // sankey - // { - // b -> 30 - // c -> 40 - // } -> { - // e - // d - // } - // `; - - // parser.parse(str); - // }); - - // it('complex', () => { - // const str = ` - // sankey - // { - // a -> 30 - // b -> 40 - // } -> c -> { - // 20 -> e -> 49 - // 20 -> d -> 23 - // } -> k -> 20 -> i -> { - // 10 -> f - // 11 -> j - // } - // `; - - // parser.parse(str); - // }); + }); }); }); From 40f7105ae4a77d4b6869e1e5d24e2ef164edf2ef Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 016/105] Started renderer development --- demos/index.html | 3 + demos/sankey.html | 14 +-- .../mermaid/src/diagrams/sankey/energy.csv | 69 ++++++++++++ .../src/diagrams/sankey/parser/sankey.jison | 10 +- .../mermaid/src/diagrams/sankey/sankeyDB.js | 69 ------------ .../mermaid/src/diagrams/sankey/sankeyDB.ts | 106 ++++++++++++++++++ .../src/diagrams/sankey/sankeyRenderer.ts | 12 +- 7 files changed, 199 insertions(+), 84 deletions(-) create mode 100644 packages/mermaid/src/diagrams/sankey/energy.csv delete mode 100644 packages/mermaid/src/diagrams/sankey/sankeyDB.js create mode 100644 packages/mermaid/src/diagrams/sankey/sankeyDB.ts diff --git a/demos/index.html b/demos/index.html index 391042182..24c4fbf3b 100644 --- a/demos/index.html +++ b/demos/index.html @@ -75,6 +75,9 @@
  • ZenUML

  • +
  • +

    Sankey

    +
  • diff --git a/demos/sankey.html b/demos/sankey.html index 57f3a8af8..fa06b37dd 100644 --- a/demos/sankey.html +++ b/demos/sankey.html @@ -16,15 +16,11 @@

    Sankey diagram demos

    Simple flow

    -      stateDiagram-v2
    -      direction LR
    -      State1: A state with a note
    -      note right of State1
    -        Important information!
    You can write notes.
    And\nthey\ncan\nbe\nmulti-\nline. - end note - State1 --> State2 - note left of State2 : Notes can be to the left of a state\n(like this one). - note right of State2 : Notes can be to the right of a state\n(like this one). + sankey + a--10->b + c--20->b + b--15->d + a-- 7->d
    diff --git a/docker-compose.yml b/docker-compose.yml index 21da2edd5..5cf97ef6d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,8 +13,10 @@ services: - ./:/mermaid - root_cache:/root/.cache - root_local:/root/.local + - root_local:/root/.npm ports: - 9000:9000 volumes: root_cache: root_local: + root_npm: diff --git a/package.json b/package.json index ac197712c..6a291f0d6 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,12 @@ "coveralls": "^3.1.1", "cypress": "^12.10.0", "cypress-image-snapshot": "^4.0.1", +<<<<<<< HEAD "esbuild": "^0.18.0", +======= + "d3-sankey": "^0.12.3", + "esbuild": "^0.17.18", +>>>>>>> 84c278d0 (At last something is working) "eslint": "^8.39.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-cypress": "^2.13.2", diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 140c200fb..3f3716928 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -32,6 +32,7 @@ export interface MermaidConfig { mindmap?: MindmapDiagramConfig; gitGraph?: GitGraphDiagramConfig; c4?: C4DiagramConfig; + sankey?: SankeyDiagramConfig; dompurifyConfig?: DOMPurify.Config; wrap?: boolean; fontSize?: number; @@ -411,6 +412,8 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig { wrappingWidth?: number; } +export interface SankeyDiagramConfig extends BaseDiagramConfig {} + export interface FontConfig { fontSize?: string | number; fontFamily?: string; diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison index cab630252..231c3fd50 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -40,6 +40,7 @@ /lex %start start +%left ARROW %% // language grammar @@ -66,10 +67,19 @@ attribute: ATTRIBUTE EQUAL value | ATTRIBUTE; value: VALUE | OPEN_STRING STRING CLOSE_STRING; -stream: node ARROW AMOUNT ARROW tail { yy.addNode($1); yy.addLink(); }; -tail: stream | node; +stream: node[source] ARROW amount ARROW tail[target] { + $$=$source; + yy.addLink($source, $target, $amount); +}; -node: NODE { yy.addNode($1) }; +amount: AMOUNT { $$=parseFloat($AMOUNT); }; + +tail + : stream { $$ = $stream } + | node { $$ = $node; } + ; + +node: NODE { $$ = yy.addNode($NODE); }; // : NODE exhaust intake exhaust_chain optional_attributes EOS // exhaust_chain: ARROW AMOUNT intake_chain | ; diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js index 47345d1b6..ee52efefc 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js @@ -21,7 +21,7 @@ describe('Sankey diagram', function () { it('recognizes one flow', () => { const str = ` sankey - a -> 30 -> b -> 20 -> c + node_a -> 30 -> node_b -> 20 -> node_c `; parser.parse(str); @@ -30,9 +30,9 @@ describe('Sankey diagram', function () { it('recognizes multiple flows', () => { const str = ` sankey - a -> 30 -> b -> 12 -> e - c -> 30 -> d -> 12 -> e - c -> 40 -> e -> 12 -> q + node_a -> 30 -> node_b -> 12 -> node_e + node_c -> 30 -> node_d -> 12 -> node_e + node_c -> 40 -> node_e -> 12 -> node_q `; parser.parse(str); @@ -44,7 +44,7 @@ describe('Sankey diagram', function () { sankey node[] node[attr=1] - a -> 30 -> b + node_a -> 30 -> node_b node[attrWithoutValue] node[attr = 3] node[attr1 = 23413 attr2=1234] diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts index f43f3514a..4f50fa4cc 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts @@ -1,12 +1,12 @@ -import { log } from '../../logger.js'; -import mermaidAPI from '../../mermaidAPI.js'; +// import { log } from '../../logger.js'; +// import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import common from '../common/common.js'; import { - // setAccTitle, - // getAccTitle, - // getAccDescription, - // setAccDescription, + setAccTitle, + getAccTitle, + getAccDescription, + setAccDescription, setDiagramTitle, getDiagramTitle, clear as commonClear, @@ -20,46 +20,54 @@ import { // return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, ''); // }; let links: Array = []; -let nodes: { [id: string]: Node } = {}; +let nodes: Array = []; +let nodesHash: Record = {}; -const clear = function () { +const clear = () => { links = []; - nodes = {}; + nodes = []; + nodesHash = {}; commonClear(); }; type Nullable = T | null; +interface ILink { + source?: Node; + target?: Node; + amount?: number; +} + class Link { - sourceNode: Nullable; - targetNode: Nullable; + source: Nullable; + target: Nullable; + amount: Nullable; constructor() { - this.sourceNode = null; - this.targetNode = null; + this.source = null; + this.target = null; + this.amount = 0; } } /** - * Adds a stream between two elements on the diagram + * Adds a link between two elements on the diagram * - * @param sourceNodeID - The id Node where the link starts - * @param targetNodeID - The id Node where the link ends + * @param source - Node where the link starts + * @param target - Node where the link ends + * @param amount - number, float or integer, describes the amount to be passed */ - -interface IAddLink { - sourceNodeID?: string; - targetNodeID?: string; - // amount?: number; -} - -const addLink = ({ sourceNodeID, targetNodeID }: IAddLink = {}): Link => { +// const addLink = ({ source, target, amount }: ILink = {}): Link => { +const addLink = (source?: Node, target?: Node, amount?: number): Link => { const link: Link = new Link(); - if (sourceNodeID !== undefined) { - link.sourceNode = addNode(sourceNodeID); + if (source !== undefined) { + link.source = source; } - if (targetNodeID !== undefined) { - link.targetNode = addNode(targetNodeID); + if (target !== undefined) { + link.target = target; + } + if (amount !== undefined) { + link.amount = amount; } links.push(link); @@ -68,11 +76,11 @@ const addLink = ({ sourceNodeID, targetNodeID }: IAddLink = {}): Link => { }; class Node { - id: string; + ID: string; title: string; - constructor(id: string) { - this.id = id; - this.title = id; + constructor(ID: string) { + this.ID = ID; + this.title = ID; } } @@ -81,25 +89,28 @@ class Node { * * @param id - The id Node */ -const addNode = (id: string): Node => { - id = common.sanitizeText(id, configApi.getConfig()); - if (nodes[id] === undefined) { - nodes[id] = new Node(id); +const addNode = (ID: string): Node => { + ID = common.sanitizeText(ID, configApi.getConfig()); + if (nodesHash[ID] === undefined) { + nodesHash[ID] = new Node(ID); } - const node = nodes[id]; + const node = nodesHash[ID]; + nodes.push(node); + // debugger; return node; }; export default { - // sankey interface + nodesHash, + nodes, + links, addLink, addNode, - // common DB interface // TODO: If this is a must this probably should be an interface - // getAccTitle, - // setAccTitle, - // getAccDescription, - // setAccDescription, + getAccTitle, + setAccTitle, + getAccDescription, + setAccDescription, getDiagramTitle, setDiagramTitle, clear, diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 916fd7ed0..5d7fb2f64 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -1,22 +1,202 @@ +// @ts-nocheck TODO: fix file import { Diagram } from '../../Diagram.js'; +import { log } from '../../logger.js'; +import * as configApi from '../../config.js'; +import { + select as d3select, + scaleOrdinal as d3scaleOrdinal, + schemeTableau10 as d3schemeTableau10, + // rgb as d3rgb, + map as d3map, +} from 'd3'; +import { + sankey as d3sankey, + sankeyLinkHorizontal +} from 'd3-sankey'; +import { configureSvgSize } from '../../setupGraphViewbox.js'; +import sankeyDB from './sankeyDB.js'; /** * Draws a sequenceDiagram in the tag with id: id based on the graph definition in text. * - * @param _text - The text of the diagram + * @param text - The text of the diagram * @param id - The id of the diagram which will be used as a DOM element id¨ * @param _version - Mermaid version from package.json * @param diagObj - A standard diagram containing the db and the text and type etc of the diagram */ -export const draw = function (text: string, id: string, _version: string, diagObj: Diagram) { - // debugger; - // diagObj.db.clear(); +export const draw = function (text: string, id: string, _version: string, diagObj: Diagram): void { + + // First of all parse sankey language + // Everything that is parsed will be stored in diagObj.DB + // That is why we need to clear DB first + // + if (typeof (diagObj?.db?.clear) !== 'undefined') { // why do we need to check for undefined? typescript complains + diagObj?.db?.clear(); + } + // Launch parsing diagObj.parser.parse(text); + log.debug('Parsed sankey diagram'); - // const elem = doc.getElementById(id); + // Figure out what is happening there + // The main thing is svg object that is a wrapper from d3 for svg operations + // + const { securityLevel, sequence: conf } = configApi.getConfig(); + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? d3select(sandboxElement.nodes()[0].contentDocument.body) + : d3select('body'); + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + const svg = securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : d3select(`[id="${id}"]`); + + // Establish svg dimensions + // + const elem = doc.getElementById(id); + const width = elem.parentElement.offsetWidth; + + const height = 600; + configureSvgSize(svg, height, width, true); + + // Prepare data for construction + // This must be a mutable object with 2 properties: + // `nodes` and `links` + // + // let graph = { + // "nodes": [ + // { "id": "Alice" }, + // { "id": "Bob" }, + // { "id": "Carol" } + // ], + // "links": [ + // { "source": "Alice", "target": "Bob", "value": 23 }, + // { "source": "Bob", "target": "Carol", "value": 43 } + // ] + // }; + // + let graph = { + "nodes": [ + { "id": "Alice" }, + { "id": "Bob" }, + { "id": "Carol" } + ], + "links": [ + { "source": "Alice", "target": "Bob", "value": 23 }, + { "source": "Bob", "target": "Carol", "value": 43 } + ] + }; + + // Construct and configure a Sankey generator + // That will be a functino that calculates nodes and links dimentions + // + const sankey = d3sankey() + .nodeId((d) => d.id) // we use 'id' property to identify node + .nodeWidth(36) + .nodePadding(290) + .size([width, height]); + + // .nodeAlign(d3Sankey.sankeyLeft) // d3.sankeyLeft, etc. + // .nodeWidth(15) + // .nodePadding(10) + // .extent([[1, 5], [width - 1, height - 5]]); + // .nodeId(d => d['id']) + // + + // Compute the Sankey layout + // Namely calalculate nodes and links positions + // Our `graph` object will be mutated by this + // + sankey(graph); // debugger; - return 'TEST'; + + // const node = svg.append("g") + // .selectAll("rect") + // .data(graph.nodes) + // .join("rect") + // .attr("x", d => d.x0) + // .attr("y", d => d.y0) + // .attr("height", d => d.y1 - d.y0) + // .attr("width", d => d.x1 - d.x0); + // // .attr("stroke", nodeStroke) + // // .attr("stroke-width", nodeStrokeWidth) + // // .attr("stroke-opacity", nodeStrokeOpacity) + // // .attr("stroke-linejoin", nodeStrokeLinejoin) + + var color = d3scaleOrdinal(d3schemeTableau10); + // Creates the rects that represent the nodes. + const rect = svg.append("g") + .attr("stroke", "#000") + .selectAll("rect") + .data(graph.nodes) + .join("rect") + .attr("x", d => d.x0) + .attr("y", d => d.y0) + .attr("height", d => d.y1 - d.y0) + .attr("width", d => d.x1 - d.x0) + .attr("fill", d => color(d.node)); + + + // // add in the links + // var link = svg.append("g") + // .selectAll(".link") + // .data(graph.links) + // .enter() + // .append("path") + // .attr("class", "link") + // .attr("d", sankeyLinkHorizontal()) + // .style("stroke-width", function (d) { return Math.max(1, d.dy); }) + // .sort(function (a, b) { return b.dy - a.dy; }); + + // // add in the nodes + // var node = svg.append("g") + // .selectAll(".node") + // .data(graph.nodes) + // .enter().append("g") + // .attr("class", "node") + // .attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; }) + // // .call(d3.drag() + // // .subject(function(d) { return d; }) + // // .on("start", function() { this.parentNode.appendChild(this); }) + // // .on("drag", dragmove)) + // ; + + + // // add the rectangles for the nodes + // node + // .append("rect") + // .attr("height", function (d) { return d.dy; }) + // .attr("width", generator.nodeWidth()) + // .style("fill", function (d) { return d.color = color(d.name.replace(/ .*/, "")); }) + // .style("stroke", function (d) { return d3rgb(d.color).darker(2); }) + // // Add hover text + // .append("title") + // .text(function (d) { return d.name + "\n" + "There is " + d.value + " stuff in this node"; }); + + + // // add in the title for the nodes + // node + // .append("text") + // .attr("x", -6) + // .attr("y", function (d) { return d.dy / 2; }) + // .attr("dy", ".35em") + // .attr("text-anchor", "end") + // .attr("transform", null) + // .text(function (d) { return d.name; }) + // .filter(function (d) { return d.x < width / 2; }) + // .attr("x", 6 + generator.nodeWidth()) + // .attr("text-anchor", "start"); + + // console.log(); + // debugger; + // .layout(1); + + // const { nodes, links } = generator({ + // nodes: graph.nodes, + // links: graph.links, + // }); }; export default { diff --git a/run b/run index 9b02b0741..09f63cb82 100755 --- a/run +++ b/run @@ -10,23 +10,19 @@ args=${@:2} case $command in sh) -$RUN mermaid sh $args +$RUN --service-ports mermaid sh $args ;; i | install) $RUN mermaid sh -c "npx pnpm install $args" ;; -test) -$RUN mermaid sh -c "npx pnpm test $args" +add) +$RUN mermaid sh -c "npx pnpm -w add $args" ;; -vitest) -$RUN mermaid sh -c "npx pnpm vitest $args" -;; - -e2e) -$RUN mermaid sh -c "npx pnpm e2e $args" +test | vitest | e2e ) +$RUN mermaid sh -c "npx pnpm $command $args" ;; lint) @@ -46,6 +42,7 @@ Run commonly used commands within docker containers \033[1m$name install\033[0m # Equvalent of pnpm install \033[1m$name dev\033[0m # Run dev server with examples, open http://localhost:9000 +$name add # Add package, 'run add d3-sankey' $name lint # Equvalent of pnpm -w run lint:fix $name test # Run unit tests $name vitest # Run watcher for unit tests From 81542142f569c87efbb29abb96024c1dccfd5997 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 019/105] Fix errors --- packages/mermaid/src/config.type.ts | 2 +- .../src/diagrams/sankey/sankeyRenderer.ts | 55 ++++++++----------- pnpm-lock.yaml | 4 ++ 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 3f3716928..f50a15d6b 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -412,7 +412,7 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig { wrappingWidth?: number; } -export interface SankeyDiagramConfig extends BaseDiagramConfig {} +export type SankeyDiagramConfig = BaseDiagramConfig; export interface FontConfig { fontSize?: string | number; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 5d7fb2f64..8731dd5ce 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -9,10 +9,7 @@ import { // rgb as d3rgb, map as d3map, } from 'd3'; -import { - sankey as d3sankey, - sankeyLinkHorizontal -} from 'd3-sankey'; +import { sankey as d3sankey, sankeyLinkHorizontal } from 'd3-sankey'; import { configureSvgSize } from '../../setupGraphViewbox.js'; import sankeyDB from './sankeyDB.js'; @@ -25,12 +22,12 @@ import sankeyDB from './sankeyDB.js'; * @param diagObj - A standard diagram containing the db and the text and type etc of the diagram */ export const draw = function (text: string, id: string, _version: string, diagObj: Diagram): void { - // First of all parse sankey language // Everything that is parsed will be stored in diagObj.DB // That is why we need to clear DB first // - if (typeof (diagObj?.db?.clear) !== 'undefined') { // why do we need to check for undefined? typescript complains + if (diagObj?.db?.clear !== undefined) { + // why do we need to check for undefined? typescript complains diagObj?.db?.clear(); } // Launch parsing @@ -75,21 +72,17 @@ export const draw = function (text: string, id: string, _version: string, diagOb // { "source": "Bob", "target": "Carol", "value": 43 } // ] // }; - // - let graph = { - "nodes": [ - { "id": "Alice" }, - { "id": "Bob" }, - { "id": "Carol" } + // + const graph = { + nodes: [{ id: 'Alice' }, { id: 'Bob' }, { id: 'Carol' }], + links: [ + { source: 'Alice', target: 'Bob', value: 23 }, + { source: 'Bob', target: 'Carol', value: 43 }, ], - "links": [ - { "source": "Alice", "target": "Bob", "value": 23 }, - { "source": "Bob", "target": "Carol", "value": 43 } - ] }; // Construct and configure a Sankey generator - // That will be a functino that calculates nodes and links dimentions + // That will be a function that calculates nodes and links dimensions // const sankey = d3sankey() .nodeId((d) => d.id) // we use 'id' property to identify node @@ -102,10 +95,10 @@ export const draw = function (text: string, id: string, _version: string, diagOb // .nodePadding(10) // .extent([[1, 5], [width - 1, height - 5]]); // .nodeId(d => d['id']) - // + // // Compute the Sankey layout - // Namely calalculate nodes and links positions + // Namely calculate nodes and links positions // Our `graph` object will be mutated by this // sankey(graph); @@ -125,19 +118,19 @@ export const draw = function (text: string, id: string, _version: string, diagOb // // .attr("stroke-opacity", nodeStrokeOpacity) // // .attr("stroke-linejoin", nodeStrokeLinejoin) - var color = d3scaleOrdinal(d3schemeTableau10); + const color = d3scaleOrdinal(d3schemeTableau10); // Creates the rects that represent the nodes. - const rect = svg.append("g") - .attr("stroke", "#000") - .selectAll("rect") + const rect = svg + .append('g') + .attr('stroke', '#000') + .selectAll('rect') .data(graph.nodes) - .join("rect") - .attr("x", d => d.x0) - .attr("y", d => d.y0) - .attr("height", d => d.y1 - d.y0) - .attr("width", d => d.x1 - d.x0) - .attr("fill", d => color(d.node)); - + .join('rect') + .attr('x', (d) => d.x0) + .attr('y', (d) => d.y0) + .attr('height', (d) => d.y1 - d.y0) + .attr('width', (d) => d.x1 - d.x0) + .attr('fill', (d) => color(d.node)); // // add in the links // var link = svg.append("g") @@ -163,7 +156,6 @@ export const draw = function (text: string, id: string, _version: string, diagOb // // .on("drag", dragmove)) // ; - // // add the rectangles for the nodes // node // .append("rect") @@ -175,7 +167,6 @@ export const draw = function (text: string, id: string, _version: string, diagOb // .append("title") // .text(function (d) { return d.name + "\n" + "There is " + d.value + " stuff in this node"; }); - // // add in the title for the nodes // node // .append("text") diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da40c7d36..f02a51f09 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15810,3 +15810,7 @@ packages: /zwitch@2.0.2: resolution: {integrity: sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==} dev: true + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false From c41fc672545b9936d3d3e2e5472103c361464cd8 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 03:23:11 +0300 Subject: [PATCH 020/105] Added nodes and paths --- .../src/diagrams/sankey/sankeyRenderer.ts | 132 ++++++++++++++---- packages/mermaid/src/setupGraphViewbox.js | 1 + 2 files changed, 108 insertions(+), 25 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 8731dd5ce..6a184ef86 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -9,7 +9,7 @@ import { // rgb as d3rgb, map as d3map, } from 'd3'; -import { sankey as d3sankey, sankeyLinkHorizontal } from 'd3-sankey'; +import { sankey as d3Sankey, sankeyLinkHorizontal as d3SankeyLinkHorizontal } from 'd3-sankey'; import { configureSvgSize } from '../../setupGraphViewbox.js'; import sankeyDB from './sankeyDB.js'; @@ -35,7 +35,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb log.debug('Parsed sankey diagram'); // Figure out what is happening there - // The main thing is svg object that is a wrapper from d3 for svg operations + // The main thing is svg object that is a d3 wrapper for svg operations // const { securityLevel, sequence: conf } = configApi.getConfig(); let sandboxElement; @@ -49,13 +49,15 @@ export const draw = function (text: string, id: string, _version: string, diagOb const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; const svg = securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : d3select(`[id="${id}"]`); - // Establish svg dimensions + // Establish svg dimensions and get width and height // const elem = doc.getElementById(id); const width = elem.parentElement.offsetWidth; + const height = 100; // TODO calculate height? - const height = 600; + // FIX: using max width prevents height from being set configureSvgSize(svg, height, width, true); + svg.attr('height', height); // that's why we need this line // Prepare data for construction // This must be a mutable object with 2 properties: @@ -84,7 +86,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb // Construct and configure a Sankey generator // That will be a function that calculates nodes and links dimensions // - const sankey = d3sankey() + const sankey = d3Sankey() .nodeId((d) => d.id) // we use 'id' property to identify node .nodeWidth(36) .nodePadding(290) @@ -100,10 +102,10 @@ export const draw = function (text: string, id: string, _version: string, diagOb // Compute the Sankey layout // Namely calculate nodes and links positions // Our `graph` object will be mutated by this + // and enriched with some properties // sankey(graph); - // debugger; // const node = svg.append("g") // .selectAll("rect") @@ -118,30 +120,110 @@ export const draw = function (text: string, id: string, _version: string, diagOb // // .attr("stroke-opacity", nodeStrokeOpacity) // // .attr("stroke-linejoin", nodeStrokeLinejoin) + // Get color scheme for the graph const color = d3scaleOrdinal(d3schemeTableau10); - // Creates the rects that represent the nodes. - const rect = svg - .append('g') - .attr('stroke', '#000') - .selectAll('rect') - .data(graph.nodes) - .join('rect') - .attr('x', (d) => d.x0) - .attr('y', (d) => d.y0) - .attr('height', (d) => d.y1 - d.y0) - .attr('width', (d) => d.x1 - d.x0) - .attr('fill', (d) => color(d.node)); - // // add in the links - // var link = svg.append("g") + // Creates the groups for nodes + svg + .append('g') + .attr('class', 'nodes') + .attr('stroke', '#000') + .selectAll('.node') + .data(graph.nodes) + .join('g') + .attr('class', 'node') + .attr("transform", function (d) { return "translate(" + d.x0 + "," + d.y0 + ")"; }) + .attr('x', (d) => d.x0) + .attr('y', (d) => d.y0) + .append('rect') + .attr('height', (d) => {console.log(d); return (d.y1 - d.y0);}) + .attr('width', (d) => d.x1 - d.x0) + .attr('fill', (d) => color(d.id)); + + // Create text for nodes + svg + .append("g") + .attr('class', 'node-labels') + .attr("font-family", "sans-serif") + .attr("font-size", 12) + .selectAll('text') + .data(graph.nodes) + .join('text') + .attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6) + .attr("y", d => (d.y1 + d.y0) / 2) + .attr("dy", "0.35em") + .attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end") + .text(d => d.id) + + // Add links + // svg + // .append("g") // .selectAll(".link") // .data(graph.links) // .enter() - // .append("path") - // .attr("class", "link") - // .attr("d", sankeyLinkHorizontal()) - // .style("stroke-width", function (d) { return Math.max(1, d.dy); }) - // .sort(function (a, b) { return b.dy - a.dy; }); + // .append("path") + // .attr("class", "link") + // .attr("d", sankeyLinkHorizontal()) + // .style("stroke-width", function (d) { return Math.max(1, d.dy); }) + // .sort(function (a, b) { return b.dy - a.dy; }); + + // Creates the paths that represent the links. + const link_g = svg.append("g") + .attr('class', 'links') + .attr("fill", "none") + .attr("stroke-opacity", 0.5) + .selectAll(".link") + .data(graph.links) + .join("g") + .attr('class', 'link') + .style("mix-blend-mode", "multiply"); + + link_g.append("path") + .attr("d", d3SankeyLinkHorizontal()) + .attr("stroke", d => color(d.source.id)) + .attr("stroke-width", d => Math.max(1, d.width)); + + // linkColor === "source-target" ? (d) => d.uid + // : linkColor === "source" ? (d) => color(d.source.category) + // : linkColor === "target" ? (d) => color(d.target.category) + // : linkColor + + // svg.append("g") + // .attr("font-family", "sans-serif") + // .attr("font-size", 10) + // .selectAll("text") + // .data(nodes) + // .join("text") + // .attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6) + // .attr("y", d => (d.y1 + d.y0) / 2) + // .attr("dy", "0.35em") + // .attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end") + // .text(d => d.name); + + // Create links + // .attr("transform", null) + // .append("g") + // .attr("font-family", "sans-serif") + // .attr("font-size", 10) + // .selectAll("text") + // .data(nodes) + // .join("text") + // .attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6) + // .attr("y", d => (d.y1 + d.y0) / 2) + // .attr("dy", "0.35em") + // .attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end") + // .text(d => d.name); + // .attr("y", function (d) { return d.dy / 2; }) + // .attr("dy", ".35em") + // .attr("text-anchor", "end") + // .attr("transform", null) + // .text(function (d) { return d.name; }) + // .filter(function (d) { return d.x < width / 2; }) + // .attr("x", 6 + generator.nodeWidth()) + // .attr("text-anchor", "start"); + + // .selectAll('rect') + // // add in the nodes // var node = svg.append("g") diff --git a/packages/mermaid/src/setupGraphViewbox.js b/packages/mermaid/src/setupGraphViewbox.js index 0396d654f..9294f8024 100644 --- a/packages/mermaid/src/setupGraphViewbox.js +++ b/packages/mermaid/src/setupGraphViewbox.js @@ -25,6 +25,7 @@ export const calculateSvgSizeAttrs = function (height, width, useMaxWidth) { if (useMaxWidth) { attrs.set('width', '100%'); attrs.set('style', `max-width: ${width}px;`); + // TODO: when using max width it does not set height? Is it intended? } else { attrs.set('height', height); attrs.set('width', width); From f5add81e29114dc5dd2e88041da80ecef4db32b9 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 03:36:54 +0300 Subject: [PATCH 021/105] Simple flow is done --- .../src/diagrams/sankey/sankeyRenderer.ts | 143 +++--------------- 1 file changed, 25 insertions(+), 118 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 6a184ef86..484eeab1a 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -7,9 +7,16 @@ import { scaleOrdinal as d3scaleOrdinal, schemeTableau10 as d3schemeTableau10, // rgb as d3rgb, - map as d3map, + // map as d3map, } from 'd3'; -import { sankey as d3Sankey, sankeyLinkHorizontal as d3SankeyLinkHorizontal } from 'd3-sankey'; + +import { + sankey as d3Sankey, + sankeyLinkHorizontal as d3SankeyLinkHorizontal, + sankeyLeft as d3SankeyLeft, + sankeyRight as d3SankeyRight, + sankeyJustify as d3SankeyJustify, +} from 'd3-sankey'; import { configureSvgSize } from '../../setupGraphViewbox.js'; import sankeyDB from './sankeyDB.js'; @@ -53,7 +60,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb // const elem = doc.getElementById(id); const width = elem.parentElement.offsetWidth; - const height = 100; // TODO calculate height? + const height = 300; // TODO calculate height? // FIX: using max width prevents height from being set configureSvgSize(svg, height, width, true); @@ -76,10 +83,18 @@ export const draw = function (text: string, id: string, _version: string, diagOb // }; // const graph = { - nodes: [{ id: 'Alice' }, { id: 'Bob' }, { id: 'Carol' }], + nodes: [ + { id: 'Alice' }, + { id: 'Bob' }, + { id: 'Carol' }, + { id: 'Andrew' }, + { id: 'Peter' } + ], links: [ + { source: 'Alice', target: 'Andrew', value: 11 }, { source: 'Alice', target: 'Bob', value: 23 }, { source: 'Bob', target: 'Carol', value: 43 }, + { source: 'Peter', target: 'Carol', value: 15 }, ], }; @@ -88,11 +103,12 @@ export const draw = function (text: string, id: string, _version: string, diagOb // const sankey = d3Sankey() .nodeId((d) => d.id) // we use 'id' property to identify node - .nodeWidth(36) - .nodePadding(290) + .nodeWidth(10) + .nodePadding(10) + .nodeAlign(d3SankeyJustify) // d3.sankeyLeft, etc. .size([width, height]); - - // .nodeAlign(d3Sankey.sankeyLeft) // d3.sankeyLeft, etc. + + //["left", "sankeyLeft"], ["right", "sankeyRight"], ["center", "sankeyCenter"], ["justify", "sankeyJustify"] // .nodeWidth(15) // .nodePadding(10) // .extent([[1, 5], [width - 1, height - 5]]); @@ -106,20 +122,6 @@ export const draw = function (text: string, id: string, _version: string, diagOb // sankey(graph); - - // const node = svg.append("g") - // .selectAll("rect") - // .data(graph.nodes) - // .join("rect") - // .attr("x", d => d.x0) - // .attr("y", d => d.y0) - // .attr("height", d => d.y1 - d.y0) - // .attr("width", d => d.x1 - d.x0); - // // .attr("stroke", nodeStroke) - // // .attr("stroke-width", nodeStrokeWidth) - // // .attr("stroke-opacity", nodeStrokeOpacity) - // // .attr("stroke-linejoin", nodeStrokeLinejoin) - // Get color scheme for the graph const color = d3scaleOrdinal(d3schemeTableau10); @@ -145,7 +147,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb .append("g") .attr('class', 'node-labels') .attr("font-family", "sans-serif") - .attr("font-size", 12) + .attr("font-size", 14) .selectAll('text') .data(graph.nodes) .join('text') @@ -155,18 +157,6 @@ export const draw = function (text: string, id: string, _version: string, diagOb .attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end") .text(d => d.id) - // Add links - // svg - // .append("g") - // .selectAll(".link") - // .data(graph.links) - // .enter() - // .append("path") - // .attr("class", "link") - // .attr("d", sankeyLinkHorizontal()) - // .style("stroke-width", function (d) { return Math.max(1, d.dy); }) - // .sort(function (a, b) { return b.dy - a.dy; }); - // Creates the paths that represent the links. const link_g = svg.append("g") .attr('class', 'links') @@ -183,89 +173,6 @@ export const draw = function (text: string, id: string, _version: string, diagOb .attr("stroke", d => color(d.source.id)) .attr("stroke-width", d => Math.max(1, d.width)); - // linkColor === "source-target" ? (d) => d.uid - // : linkColor === "source" ? (d) => color(d.source.category) - // : linkColor === "target" ? (d) => color(d.target.category) - // : linkColor - - // svg.append("g") - // .attr("font-family", "sans-serif") - // .attr("font-size", 10) - // .selectAll("text") - // .data(nodes) - // .join("text") - // .attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6) - // .attr("y", d => (d.y1 + d.y0) / 2) - // .attr("dy", "0.35em") - // .attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end") - // .text(d => d.name); - - // Create links - // .attr("transform", null) - // .append("g") - // .attr("font-family", "sans-serif") - // .attr("font-size", 10) - // .selectAll("text") - // .data(nodes) - // .join("text") - // .attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6) - // .attr("y", d => (d.y1 + d.y0) / 2) - // .attr("dy", "0.35em") - // .attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end") - // .text(d => d.name); - // .attr("y", function (d) { return d.dy / 2; }) - // .attr("dy", ".35em") - // .attr("text-anchor", "end") - // .attr("transform", null) - // .text(function (d) { return d.name; }) - // .filter(function (d) { return d.x < width / 2; }) - // .attr("x", 6 + generator.nodeWidth()) - // .attr("text-anchor", "start"); - - // .selectAll('rect') - - - // // add in the nodes - // var node = svg.append("g") - // .selectAll(".node") - // .data(graph.nodes) - // .enter().append("g") - // .attr("class", "node") - // .attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; }) - // // .call(d3.drag() - // // .subject(function(d) { return d; }) - // // .on("start", function() { this.parentNode.appendChild(this); }) - // // .on("drag", dragmove)) - // ; - - // // add the rectangles for the nodes - // node - // .append("rect") - // .attr("height", function (d) { return d.dy; }) - // .attr("width", generator.nodeWidth()) - // .style("fill", function (d) { return d.color = color(d.name.replace(/ .*/, "")); }) - // .style("stroke", function (d) { return d3rgb(d.color).darker(2); }) - // // Add hover text - // .append("title") - // .text(function (d) { return d.name + "\n" + "There is " + d.value + " stuff in this node"; }); - - // // add in the title for the nodes - // node - // .append("text") - // .attr("x", -6) - // .attr("y", function (d) { return d.dy / 2; }) - // .attr("dy", ".35em") - // .attr("text-anchor", "end") - // .attr("transform", null) - // .text(function (d) { return d.name; }) - // .filter(function (d) { return d.x < width / 2; }) - // .attr("x", 6 + generator.nodeWidth()) - // .attr("text-anchor", "start"); - - // console.log(); - // debugger; - // .layout(1); - // const { nodes, links } = generator({ // nodes: graph.nodes, // links: graph.links, From d22131e2fb373065379de8ca9837b2e9c51f42e0 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 04:33:20 +0300 Subject: [PATCH 022/105] It can read syntax and draw diagram --- .../mermaid/src/diagrams/sankey/sankeyDB.ts | 35 ++++++------ .../src/diagrams/sankey/sankeyRenderer.ts | 53 +++++++++++++------ 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts index 4f50fa4cc..23ef95528 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts @@ -23,7 +23,7 @@ let links: Array = []; let nodes: Array = []; let nodesHash: Record = {}; -const clear = () => { +const clear = function() { links = []; nodes = []; nodesHash = {}; @@ -32,20 +32,20 @@ const clear = () => { type Nullable = T | null; -interface ILink { - source?: Node; - target?: Node; - amount?: number; -} +// interface ILink { +// source?: Node; +// target?: Node; +// value?: number; +// } class Link { source: Nullable; target: Nullable; - amount: Nullable; + value: Nullable; constructor() { this.source = null; this.target = null; - this.amount = 0; + this.value = 0; } } @@ -54,20 +54,21 @@ class Link { * * @param source - Node where the link starts * @param target - Node where the link ends - * @param amount - number, float or integer, describes the amount to be passed + * @param value - number, float or integer, describes the amount to be passed */ // const addLink = ({ source, target, amount }: ILink = {}): Link => { -const addLink = (source?: Node, target?: Node, amount?: number): Link => { +const addLink = function(source?: Node, target?: Node, value?: number): Link { const link: Link = new Link(); + // TODO: make attribute setters if (source !== undefined) { link.source = source; } if (target !== undefined) { link.target = target; } - if (amount !== undefined) { - link.amount = amount; + if (value !== undefined) { + link.value = value; } links.push(link); @@ -89,7 +90,7 @@ class Node { * * @param id - The id Node */ -const addNode = (ID: string): Node => { +const addNode = function(ID: string): Node { ID = common.sanitizeText(ID, configApi.getConfig()); if (nodesHash[ID] === undefined) { nodesHash[ID] = new Node(ID); @@ -100,10 +101,14 @@ const addNode = (ID: string): Node => { return node; }; +const getNodes = () => nodes; +const getLinks = () => links; + export default { nodesHash, - nodes, - links, + getConfig: () => configApi.getConfig().sankey, + getNodes, + getLinks, addLink, addNode, // TODO: If this is a must this probably should be an interface diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 484eeab1a..b0fac5c12 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -15,10 +15,13 @@ import { sankeyLinkHorizontal as d3SankeyLinkHorizontal, sankeyLeft as d3SankeyLeft, sankeyRight as d3SankeyRight, + sankeyCenter as d3SankeyCenter, sankeyJustify as d3SankeyJustify, } from 'd3-sankey'; import { configureSvgSize } from '../../setupGraphViewbox.js'; import sankeyDB from './sankeyDB.js'; +import { db } from '../info/infoDb.js'; +import { debug } from 'console'; /** * Draws a sequenceDiagram in the tag with id: id based on the graph definition in text. @@ -39,6 +42,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb } // Launch parsing diagObj.parser.parse(text); + debugger; log.debug('Parsed sankey diagram'); // Figure out what is happening there @@ -66,7 +70,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb configureSvgSize(svg, height, width, true); svg.attr('height', height); // that's why we need this line - // Prepare data for construction + // Prepare data for construction based on diagObj.db // This must be a mutable object with 2 properties: // `nodes` and `links` // @@ -82,21 +86,36 @@ export const draw = function (text: string, id: string, _version: string, diagOb // ] // }; // - const graph = { - nodes: [ - { id: 'Alice' }, - { id: 'Bob' }, - { id: 'Carol' }, - { id: 'Andrew' }, - { id: 'Peter' } - ], - links: [ - { source: 'Alice', target: 'Andrew', value: 11 }, - { source: 'Alice', target: 'Bob', value: 23 }, - { source: 'Bob', target: 'Carol', value: 43 }, - { source: 'Peter', target: 'Carol', value: 15 }, - ], - }; + let graph = { + nodes: [], + links: [] + } + + diagObj.db.getNodes().forEach(node => { + graph.nodes.push({id: node.ID}); + }); + + diagObj.db.getLinks().forEach(link => { + graph.links.push({source: link.source.ID, target: link.target.ID, value: link.value}); + }); + + + debugger; + // const graph = { + // nodes: [ + // { id: 'Alice' }, + // { id: 'Bob' }, + // { id: 'Carol' }, + // { id: 'Andrew' }, + // { id: 'Peter' } + // ], + // links: [ + // { source: 'Alice', target: 'Andrew', value: 11 }, + // { source: 'Alice', target: 'Bob', value: 23 }, + // { source: 'Bob', target: 'Carol', value: 43 }, + // { source: 'Peter', target: 'Carol', value: 15 }, + // ], + // }; // Construct and configure a Sankey generator // That will be a function that calculates nodes and links dimensions @@ -107,6 +126,8 @@ export const draw = function (text: string, id: string, _version: string, diagOb .nodePadding(10) .nodeAlign(d3SankeyJustify) // d3.sankeyLeft, etc. .size([width, height]); + // .extent([[5, 20], [width - 5, height - 20]]); alias for size + // paddings //["left", "sankeyLeft"], ["right", "sankeyRight"], ["center", "sankeyCenter"], ["justify", "sankeyJustify"] // .nodeWidth(15) From 19f858b73b9baf093e53fbd9bc1e3961deb89dad Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 23:53:57 +0300 Subject: [PATCH 023/105] Fixed nodes duplicates --- demos/sankey.html | 10 ++++++---- packages/mermaid/src/diagrams/sankey/sankeyDB.ts | 10 ++++++---- packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts | 1 - 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/demos/sankey.html b/demos/sankey.html index b06ead15e..ebacae649 100644 --- a/demos/sankey.html +++ b/demos/sankey.html @@ -17,10 +17,12 @@

    Simple flow

           sankey
    -      node_a->10->node_b;
    -      node_c->20->node_b;
    -      node_b->15->node_d;
    -      node_a-> 7->node_d;
    +      a->28->b->10->c
    +      a->11->b
    +      k->18->b->35->d->13->e->43->dd11
    +      d->5->e
    +      d->10->c
    +      k->25->e
         
    - - + + + + \ No newline at end of file diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 54b129098..06a33980b 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -42,7 +42,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb } // Launch parsing diagObj.parser.parse(text); - debugger; + // debugger; log.debug('Parsed sankey diagram'); // Figure out what is happening there @@ -86,20 +86,20 @@ export const draw = function (text: string, id: string, _version: string, diagOb // ] // }; // - let graph = { + const graph = { nodes: [], links: [] } diagObj.db.getNodes().forEach(node => { - graph.nodes.push({id: node.ID}); + graph.nodes.push({ id: node.ID }); }); diagObj.db.getLinks().forEach(link => { - graph.links.push({source: link.source.ID, target: link.target.ID, value: link.value}); + graph.links.push({ source: link.source.ID, target: link.target.ID, value: link.value }); }); - debugger; + // debugger; // const graph = { // nodes: [ // { id: 'Alice' }, @@ -125,10 +125,10 @@ export const draw = function (text: string, id: string, _version: string, diagOb .nodePadding(10) .nodeAlign(d3SankeyJustify) // d3.sankeyLeft, etc. .size([width, height]); - // .extent([[5, 20], [width - 5, height - 20]]); alias for size - // paddings - - //["left", "sankeyLeft"], ["right", "sankeyRight"], ["center", "sankeyCenter"], ["justify", "sankeyJustify"] + // .extent([[5, 20], [width - 5, height - 20]]); alias for size + // paddings + + //["left", "sankeyLeft"], ["right", "sankeyRight"], ["center", "sankeyCenter"], ["justify", "sankeyJustify"] // .nodeWidth(15) // .nodePadding(10) // .extent([[1, 5], [width - 1, height - 5]]); @@ -148,45 +148,45 @@ export const draw = function (text: string, id: string, _version: string, diagOb // Creates the groups for nodes svg .append('g') - .attr('class', 'nodes') - .attr('stroke', '#000') + .attr('class', 'nodes') + .attr('stroke', '#000') .selectAll('.node') .data(graph.nodes) .join('g') - .attr('class', 'node') - .attr("transform", function (d) { return "translate(" + d.x0 + "," + d.y0 + ")"; }) - .attr('x', (d) => d.x0) - .attr('y', (d) => d.y0) - .append('rect') - .attr('height', (d) => {console.log(d); return (d.y1 - d.y0);}) - .attr('width', (d) => d.x1 - d.x0) - .attr('fill', (d) => color(d.id)); + .attr('class', 'node') + .attr("transform", function (d) { return "translate(" + d.x0 + "," + d.y0 + ")"; }) + .attr('x', (d) => d.x0) + .attr('y', (d) => d.y0) + .append('rect') + .attr('height', (d) => { console.log(d); return (d.y1 - d.y0); }) + .attr('width', (d) => d.x1 - d.x0) + .attr('fill', (d) => color(d.id)); // Create text for nodes svg .append("g") - .attr('class', 'node-labels') - .attr("font-family", "sans-serif") - .attr("font-size", 14) + .attr('class', 'node-labels') + .attr("font-family", "sans-serif") + .attr("font-size", 14) .selectAll('text') .data(graph.nodes) .join('text') - .attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6) - .attr("y", d => (d.y1 + d.y0) / 2) - .attr("dy", "0.35em") - .attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end") - .text(d => d.id) + .attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6) + .attr("y", d => (d.y1 + d.y0) / 2) + .attr("dy", "0.35em") + .attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end") + .text(d => d.id) // Creates the paths that represent the links. const link_g = svg.append("g") - .attr('class', 'links') - .attr("fill", "none") - .attr("stroke-opacity", 0.5) + .attr('class', 'links') + .attr("fill", "none") + .attr("stroke-opacity", 0.5) .selectAll(".link") .data(graph.links) .join("g") - .attr('class', 'link') - .style("mix-blend-mode", "multiply"); + .attr('class', 'link') + .style("mix-blend-mode", "multiply"); link_g.append("path") .attr("d", d3SankeyLinkHorizontal()) From 726efdad5346c7969f683de0490f0050b0e2a9a0 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Mon, 19 Jun 2023 01:09:21 +0300 Subject: [PATCH 025/105] Fixed packaged --- package.json | 6 +----- pnpm-lock.yaml | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 6a291f0d6..785f8056c 100644 --- a/package.json +++ b/package.json @@ -85,12 +85,8 @@ "coveralls": "^3.1.1", "cypress": "^12.10.0", "cypress-image-snapshot": "^4.0.1", -<<<<<<< HEAD - "esbuild": "^0.18.0", -======= "d3-sankey": "^0.12.3", - "esbuild": "^0.17.18", ->>>>>>> 84c278d0 (At last something is working) + "esbuild": "^0.18.0", "eslint": "^8.39.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-cypress": "^2.13.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f02a51f09..365f1bc5c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - importers: .: @@ -86,6 +82,9 @@ importers: cypress-image-snapshot: specifier: ^4.0.1 version: 4.0.1(cypress@12.10.0)(jest@29.5.0) + d3-sankey: + specifier: ^0.12.3 + version: 0.12.3 esbuild: specifier: ^0.18.0 version: 0.18.0 @@ -7046,6 +7045,12 @@ packages: lodash: 4.17.21 dev: false + /d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + dependencies: + internmap: 1.0.1 + dev: true + /d3-array@3.2.0: resolution: {integrity: sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==} engines: {node: '>=12'} @@ -7163,6 +7168,10 @@ packages: d3-color: 3.1.0 dev: false + /d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + dev: true + /d3-path@3.0.1: resolution: {integrity: sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==} engines: {node: '>=12'} @@ -7183,6 +7192,13 @@ packages: engines: {node: '>=12'} dev: false + /d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + dev: true + /d3-scale-chromatic@3.0.0: resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==} engines: {node: '>=12'} @@ -7207,6 +7223,12 @@ packages: engines: {node: '>=12'} dev: false + /d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + dependencies: + d3-path: 1.0.9 + dev: true + /d3-shape@3.1.0: resolution: {integrity: sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==} engines: {node: '>=12'} @@ -9442,6 +9464,10 @@ packages: side-channel: 1.0.4 dev: true + /internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + dev: true + /internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} From 6722ac754082d3ba77bbe7d9be23a6e0c70ce2d7 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Mon, 19 Jun 2023 03:45:24 +0300 Subject: [PATCH 026/105] Multiple improvements on syntax Syntax has been simplified Removed extra initial states Removed unused groups Nodes can be wrapped in double qotes Updated demo page --- demos/sankey.html | 95 +++++++++++++++++-- .../src/diagrams/sankey/parser/sankey.jison | 66 ++++++------- .../src/diagrams/sankey/parser/sankey.spec.js | 93 +++++++++++++++++- .../mermaid/src/diagrams/sankey/sankeyDB.ts | 4 +- .../src/diagrams/sankey/sankeyRenderer.ts | 12 +-- 5 files changed, 214 insertions(+), 56 deletions(-) diff --git a/demos/sankey.html b/demos/sankey.html index 2cf778206..c35b907d2 100644 --- a/demos/sankey.html +++ b/demos/sankey.html @@ -16,16 +16,93 @@

    Sankey diagram demos

    -

    Simple flow

    +

    Simple example

    -      sankey
    -      a->28->b->10->c
    -      a->11->b
    -      k->18->b->35->d->13->e->43->dd11
    -      d->5->e
    -      d->10->c
    -      k->25->e
    -    
    + sankey + + node[title="hello, how are you?"] + node[title="hello, mister Sankey"] + + First -> 30 -> Second + First -> 10 -> Third + Second -> 20 -> Third + + + +

    Energy flow

    +
    +    sankey
    +
    +    "Agricultural 'waste'"      ->      124.729  -> "Bio-conversion"
    +    "Bio-conversion"            ->      0.597    -> "Liquid"
    +    "Bio-conversion"            ->      26.862   -> "Losses"
    +    "Bio-conversion"            ->      280.322  -> "Solid"
    +    "Bio-conversion"            ->      81.144   -> "Gas"
    +    "Biofuel imports"           ->      35       -> "Liquid"
    +    "Biomass imports"           ->      35       -> "Solid"
    +    "Coal imports"              ->      11.606   -> "Coal"
    +    "Coal reserves"             ->      63.965   -> "Coal"
    +    "Coal"                      ->      75.571   -> "Solid"
    +    "District heating"          ->      10.639   -> "Industry"
    +    "District heating"          ->      22.505   -> "Heating and cooling - commercial"
    +    "District heating"          ->      46.184   -> "Heating and cooling - homes"
    +    "Electricity grid"          ->      104.453  -> "Over generation / exports"
    +    "Electricity grid"          ->      113.726  -> "Heating and cooling - homes"
    +    "Electricity grid"          ->      27.14    -> "H2 conversion"
    +    "Electricity grid"          ->      342.165  -> "Industry"
    +    "Electricity grid"          ->      37.797   -> "Road transport"
    +    "Electricity grid"          ->      4.412    -> "Agriculture"
    +    "Electricity grid"          ->      40.858   -> "Heating and cooling - commercial"
    +    "Electricity grid"          ->      56.691   -> "Losses"
    +    "Electricity grid"          ->      7.863    -> "Rail transport"
    +    "Electricity grid"          ->      90.008   -> "Lighting & appliances - commercial"
    +    "Electricity grid"          ->      93.494   -> "Lighting & appliances - homes"
    +    "Gas imports"               ->      40.719   -> "Ngas"
    +    "Gas reserves"              ->      82.233   -> "Ngas"
    +    "Gas"                       ->      0.129    -> "Heating and cooling - commercial"
    +    "Gas"                       ->      1.401    -> "Losses"
    +    "Gas"                       ->      151.891  -> "Thermal generation"
    +    "Gas"                       ->      2.096    -> "Agriculture"
    +    "Gas"                       ->      48.58    -> "Industry"
    +    "Geothermal"                ->      7.013    -> "Electricity grid"
    +    "H2 conversion"             ->      20.897   -> "H2"
    +    "H2 conversion"             ->      6.242    -> "Losses"
    +    "H2"                        ->      20.897   -> "Road transport"
    +    "Hydro"                     ->      6.995    -> "Electricity grid"
    +    "Liquid"                    ->      121.066  -> "Industry"
    +    "Liquid"                    ->      128.69   -> "International shipping"
    +    "Liquid"                    ->      135.835  -> "Road transport"
    +    "Liquid"                    ->      14.458   -> "Domestic aviation"
    +    "Liquid"                    ->      206.267  -> "International aviation"
    +    "Liquid"                    ->      3.64     -> "Agriculture"
    +    "Liquid"                    ->      33.218   -> "National navigation"
    +    "Liquid"                    ->      4.413    -> "Rail transport"
    +    "Marine algae"              ->      4.375    -> "Bio-conversion"
    +    "Ngas"                      ->      122.952  -> "Gas"
    +    "Nuclear"                   ->      839.978  -> "Thermal generation"
    +    "Oil imports"               ->      504.287  -> "Oil"
    +    "Oil reserves"              ->      107.703  -> "Oil"
    +    "Oil"                       ->      611.99   -> "Liquid"
    +    "Other waste"               ->      56.587   -> "Solid"
    +    "Other waste"               ->      77.81    -> "Bio-conversion"
    +    "Pumped heat"               ->      193.026  -> "Heating and cooling - homes"
    +    "Pumped heat"               ->      70.672   -> "Heating and cooling - commercial"
    +    "Solar PV"                  ->      59.901   -> "Electricity grid"
    +    "Solar Thermal"             ->      19.263   -> "Heating and cooling - homes"
    +    "Solar"                     ->      19.263   -> "Solar Thermal"
    +    "Solar"                     ->      59.901   -> "Solar PV"
    +    "Solid"                     ->      0.882    -> "Agriculture"
    +    "Solid"                     ->      400.12   -> "Thermal generation"
    +    "Solid"                     ->      46.477   -> "Industry"
    +    "Thermal generation"        ->      525.531  -> "Electricity grid"
    +    "Thermal generation"        ->      787.129  -> "Losses"
    +    "Thermal generation"        ->      79.329   -> "District heating"
    +    "Tidal"                     ->      9.452    -> "Electricity grid"
    +    "UK land based bioenergy"   ->      182.01   -> "Bio-conversion"
    +    "Wave"                      ->      19.013   -> "Electricity grid"
    +    "Wind"                      ->      289.366  -> "Electricity grid"
    +  
    + - - - \ No newline at end of file + + + diff --git a/run b/run index 473c6aaff..0f9c451d9 100755 --- a/run +++ b/run @@ -47,6 +47,7 @@ Run commonly used commands within docker containers \033[1m$name dev\033[0m # Run dev server with examples, open http://localhost:9000 $name add # Add package, 'run add d3-sankey' +$name prettier # Prettify a file 'run prettier ' $name lint # Equvalent of pnpm -w run lint:fix $name test # Run unit tests $name vitest # Run watcher for unit tests @@ -65,4 +66,4 @@ echo -n -e "$usage" $name help ;; -esac \ No newline at end of file +esac From 6077ba54054eed165052d73fe342b6ad3bf04e8c Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Tue, 20 Jun 2023 02:37:27 +0300 Subject: [PATCH 029/105] Updated syntax and fixed comments from review --- .../src/diagrams/sankey/parser/sankey.jison | 111 +++++++++++------- .../src/diagrams/sankey/parser/sankey.spec.js | 12 +- .../mermaid/src/diagrams/sankey/sankeyDB.ts | 86 +++++--------- .../src/diagrams/sankey/sankeyDiagram.ts | 2 +- .../src/diagrams/sankey/sankeyRenderer.ts | 43 ++----- 5 files changed, 114 insertions(+), 140 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison index 8f970b815..c981267a0 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -1,39 +1,58 @@ /** mermaid */ %lex +TOKEN \w+ +NUM \d+(.\d+)? %options case-insensitive -%options easy_keyword_rules +%options easy_keword_rules + +%s link_value -// when we are inside [] section we are defining attrubutes %x attributes -// or if we use "" we are expecting a string containing value +%x attr_value %x string -%x value %% +//-------------------------------------------------------------- // skip all whitespace EXCEPT newlines, but not within a string -[^\S\r\n]+ {} +//-------------------------------------------------------------- -// main -"sankey" { return 'SANKEY'; } -\d+(.\d+)? { return 'AMOUNT'; } -"->" { return 'ARROW'; } -\w+ { return 'NODE'; } -(?:<>|[\n;])+ { return 'EOS'; } // end of statement is semicolon ; new line \n or end of file +[^\S\r\n]+ {} + +//-------------- +// basic tokens +//-------------- + +(<>|[\n;])+ { return 'EOS'; } // end of statement is semicolon ; new line \n or end of file +"sankey" { return 'SANKEY'; } +{TOKEN} { return 'NODE_ID'; } +{NUM} { return 'AMOUNT'; } +"->" { + if(this.topState()!=='link_value') this.pushState('link_value'); + else this.popState(); + return 'ARROW'; + } +//------------ // attributes -"[" { this.pushState('attributes'); return 'OPEN_ATTRIBUTES'; } -"]" { this.popState(); return 'CLOSE_ATTRIBUTES'; } -\w+ { return 'ATTRIBUTE'; } -\= { this.pushState('value'); return 'EQUAL'; } -\w+ { this.popState(); return 'VALUE'; } +//------------ + +"[" { this.pushState('attributes'); return 'OPEN_ATTRIBUTES'; } +"]" { this.popState(); return 'CLOSE_ATTRIBUTES'; } +{TOKEN} { return 'ATTRIBUTE'; } +\= { this.pushState('attr_value'); return 'EQUAL'; } +{TOKEN} { this.popState(); return 'VALUE'; } + +//------------ // strings -\" { this.pushState('string'); return 'OPEN_STRING'; } -(?!\\)\" { - if(this.topState()==='string') this.popState(); - if(this.topState()==='value') this.popState(); - return 'CLOSE_STRING'; - } -([^"\\]|\\\"|\\\\)+ { return 'STRING'; } +//------------ + +\" { this.pushState('string'); return 'OPEN_STRING'; } +(?!\\)\" { + if(this.topState()==='string') this.popState(); + if(this.topState()==='attr_value') this.popState(); + return 'CLOSE_STRING'; + } +([^"\\]|\\\"|\\\\)+ { return 'STRING'; } /lex @@ -43,20 +62,20 @@ %% // language grammar start - : EOS SANKEY document - | SANKEY document - ; + : EOS SANKEY document + | SANKEY document + ; document - : line document - | - ; + : line document + | + ; line - : stream optional_attributes EOS - | node optional_attributes EOS - | EOS - ; + : node optional_attributes EOS + | stream optional_attributes EOS + | EOS + ; optional_attributes: OPEN_ATTRIBUTES attributes CLOSE_ATTRIBUTES | ; @@ -65,20 +84,22 @@ attribute: ATTRIBUTE EQUAL value | ATTRIBUTE; value: VALUE | OPEN_STRING STRING CLOSE_STRING; -stream: node[source] ARROW amount ARROW tail[target] { - $$=$source; - yy.addLink($source, $target, $amount); -}; +stream + : node\[source] ARROW amount ARROW tail\[target] { + $$=$source; + yy.addLink($source, $target, $amount); + } + ; + +tail + : stream { $$ = $stream } + | node { $$ = $node; } + ; amount: AMOUNT { $$=parseFloat($AMOUNT); }; -tail - : stream { $$ = $stream } - | node { $$ = $node; } - ; - node - : NODE { $$ = yy.addNode($NODE); } - | OPEN_STRING STRING[title] CLOSE_STRING { $$ = yy.addNode($title); /* TODO: add title and id separately?*/ } - ; + : NODE_ID { $$ = yy.findOrCreateNode($NODE_ID); } + | OPEN_STRING STRING\[node_label] CLOSE_STRING { $$ = yy.findOrCreateNode($node_label); } + ; diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js index d42fa7f74..58fe31ab1 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js @@ -50,11 +50,20 @@ describe('Sankey diagram', function () { }); describe('while attributes parsing', () => { + it('recognized node and attribute ids starting with numbers', () => { + const str = ` + sankey + 1st -> 200 -> 2nd -> 180 -> 3rd; + `; + + parser.parse(str); + }); + it('parses different quotless variations', () => { const str = ` sankey node[] - + node[attr=1] node_a -> 30 -> node_b node[attrWithoutValue] @@ -149,6 +158,7 @@ describe('Sankey diagram', function () { "Wave" -> 19.013 -> "Electricity grid" "Wind" -> 289.366 -> "Electricity grid" `; + parser.parse(str); }); }); diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts index fd96ef1c2..d44f3e6e8 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts @@ -12,16 +12,13 @@ import { clear as commonClear, } from '../../commonDb.js'; -// export const parseDirective = function (statement, context, type) { -// mermaidAPI.parseDirective(this, statement, context, type); -// }; - -// export const cleanupComments = (text: string): string => { -// return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, ''); -// }; -let links: Array = []; -let nodes: Array = []; -let nodesHash: Record = {}; +// Variables where graph data is stored +// Sankey diagram represented by nodes and links between those nodes +// We have to track nodes uniqueness (by ID), thats why we need hash also +// +let links: Array = []; +let nodes: Array = []; +let nodesHash: Record = {}; const clear = function () { links = []; @@ -30,71 +27,35 @@ const clear = function () { commonClear(); }; -type Nullable = T | null; - -// interface ILink { -// source?: Node; -// target?: Node; -// value?: number; -// } - -class Link { - source: Nullable; - target: Nullable; - value: Nullable; - constructor() { - this.source = null; - this.target = null; - this.value = 0; - } +class SankeyLink { + constructor(public source: SankeyNode, public target: SankeyNode, public value: number = 0) {} } /** - * Adds a link between two elements on the diagram - * * @param source - Node where the link starts * @param target - Node where the link ends * @param value - number, float or integer, describes the amount to be passed */ -// const addLink = ({ source, target, amount }: ILink = {}): Link => { -const addLink = function (source?: Node, target?: Node, value?: number): Link { - const link: Link = new Link(); - - // TODO: make attribute setters - if (source !== undefined) { - link.source = source; - } - if (target !== undefined) { - link.target = target; - } - if (value !== undefined) { - link.value = value; - } +const addLink = function (source: SankeyNode, target: SankeyNode, value: number): SankeyLink { + const link: SankeyLink = new SankeyLink(source, target, value); links.push(link); return link; }; -class Node { - ID: string; - title: string; - constructor(ID: string, title: string = ID) { - this.ID = ID; - this.title = title; - } +class SankeyNode { + constructor(public ID: string, public label: string = ID) {} } /** - * Finds or creates a new Node by ID - * - * @param id - The id Node + * @param ID - The id of the node */ -const addNode = function (ID: string): Node { +const findOrCreateNode = function (ID: string): SankeyNode { ID = common.sanitizeText(ID, configApi.getConfig()); - let node: Node; + let node: SankeyNode; if (nodesHash[ID] === undefined) { - node = new Node(ID); + node = new SankeyNode(ID); nodesHash[ID] = node; nodes.push(node); } else { @@ -103,16 +64,27 @@ const addNode = function (ID: string): Node { return node; }; +// TODO: this will be better using getters in typescript const getNodes = () => nodes; const getLinks = () => links; +const getGraph = () => ({ + nodes: nodes.map((node) => ({ id: node.ID, label: node.label })), + links: links.map((link) => ({ + source: link.source.ID, + target: link.target.ID, + value: link.value, + })), +}); + export default { nodesHash, getConfig: () => configApi.getConfig().sankey, getNodes, getLinks, + getGraph, addLink, - addNode, + findOrCreateNode, // TODO: If this is a must this probably should be an interface getAccTitle, setAccTitle, diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts index e7a46d8bc..9c8fbaa2d 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts @@ -1,5 +1,5 @@ import { DiagramDefinition } from '../../diagram-api/types.js'; -// @ts-ignore: TODO Fix ts errors +// @ts-ignore: jison doesn't export types import parser from './parser/sankey.jison'; import db from './sankeyDB.js'; import styles from './styles.js'; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 37362803a..c8a3edaa8 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -2,6 +2,7 @@ import { Diagram } from '../../Diagram.js'; // import { log } from '../../logger.js'; import * as configApi from '../../config.js'; + import { select as d3select, scaleOrdinal as d3scaleOrdinal, @@ -19,9 +20,7 @@ import { sankeyJustify as d3SankeyJustify, } from 'd3-sankey'; import { configureSvgSize } from '../../setupGraphViewbox.js'; -import sankeyDB from './sankeyDB.js'; -import { db } from '../info/infoDb.js'; -import { debug } from 'console'; +// import { debug } from 'console'; /** * Draws a sequenceDiagram in the tag with id: id based on the graph definition in text. @@ -51,7 +50,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb const { securityLevel, sequence: conf } = configApi.getConfig(); let sandboxElement; if (securityLevel === 'sandbox') { - sandboxElement = select('#i' + id); + sandboxElement = d3select('#i' + id); } const root = securityLevel === 'sandbox' @@ -86,35 +85,8 @@ export const draw = function (text: string, id: string, _version: string, diagOb // ] // }; // - const graph = { - nodes: [], - links: [], - }; - diagObj.db.getNodes().forEach((node) => { - graph.nodes.push({ id: node.ID, title: node.title }); - }); - - diagObj.db.getLinks().forEach((link) => { - graph.links.push({ source: link.source.ID, target: link.target.ID, value: link.value }); - }); - - // debugger; - // const graph = { - // nodes: [ - // { id: 'Alice' }, - // { id: 'Bob' }, - // { id: 'Carol' }, - // { id: 'Andrew' }, - // { id: 'Peter' } - // ], - // links: [ - // { source: 'Alice', target: 'Andrew', value: 11 }, - // { source: 'Alice', target: 'Bob', value: 23 }, - // { source: 'Bob', target: 'Carol', value: 43 }, - // { source: 'Peter', target: 'Carol', value: 15 }, - // ], - // }; + const graph = diagObj.db.getGraph(); // Construct and configure a Sankey generator // That will be a function that calculates nodes and links dimensions @@ -145,11 +117,10 @@ export const draw = function (text: string, id: string, _version: string, diagOb // Get color scheme for the graph const color = d3scaleOrdinal(d3schemeTableau10); - // Creates the groups for nodes + // Create groups for nodes svg .append('g') .attr('class', 'nodes') - .attr('stroke', '#000') .selectAll('.node') .data(graph.nodes) .join('g') @@ -166,7 +137,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb .attr('width', (d) => d.x1 - d.x0) .attr('fill', (d) => color(d.id)); - // Create text for nodes + // Create labels for nodes svg .append('g') .attr('class', 'node-labels') @@ -179,7 +150,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb .attr('y', (d) => (d.y1 + d.y0) / 2) .attr('dy', '0.35em') .attr('text-anchor', (d) => (d.x0 < width / 2 ? 'start' : 'end')) - .text((d) => d.title); + .text((d) => d.label); // Creates the paths that represent the links. const link_g = svg From a2c055ba5d492231ebceefe5c32db90e8927fb26 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Wed, 21 Jun 2023 01:28:59 +0300 Subject: [PATCH 030/105] CSV syntax implementation --- demos/sankey.html | 150 ++++++++---------- .../diagrams/sankey/{ => parser}/energy.csv | 1 - .../diagrams/sankey/parser/sankey-arrow.jison | 105 ++++++++++++ .../{sankey.spec.js => sankey-arrow.spec.js} | 0 .../src/diagrams/sankey/parser/sankey.jison | 131 +++++++-------- .../src/diagrams/sankey/parser/sankey.spec.ts | 33 ++++ 6 files changed, 260 insertions(+), 160 deletions(-) rename packages/mermaid/src/diagrams/sankey/{ => parser}/energy.csv (99%) create mode 100644 packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.jison rename packages/mermaid/src/diagrams/sankey/parser/{sankey.spec.js => sankey-arrow.spec.js} (100%) create mode 100644 packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts diff --git a/demos/sankey.html b/demos/sankey.html index d6ae1d2ec..d49bfc77e 100644 --- a/demos/sankey.html +++ b/demos/sankey.html @@ -15,92 +15,78 @@

    Sankey diagram demos

    -

    Simple example

    -
    -    sankey
    -
    -    node[title="hello, how are you?"]
    -    node[title="hello, mister Sankey"]
    -
    -    First -> 30 -> Second
    -    First -> 10 -> Third 
    -    Second -> 20 -> Third
    -  
    - -

    Energy flow

         sankey
     
    -    "Agricultural 'waste'"      ->      124.729  -> "Bio-conversion"
    -    "Bio-conversion"            ->      0.597    -> "Liquid"
    -    "Bio-conversion"            ->      26.862   -> "Losses"
    -    "Bio-conversion"            ->      280.322  -> "Solid"
    -    "Bio-conversion"            ->      81.144   -> "Gas"
    -    "Biofuel imports"           ->      35       -> "Liquid"
    -    "Biomass imports"           ->      35       -> "Solid"
    -    "Coal imports"              ->      11.606   -> "Coal"
    -    "Coal reserves"             ->      63.965   -> "Coal"
    -    "Coal"                      ->      75.571   -> "Solid"
    -    "District heating"          ->      10.639   -> "Industry"
    -    "District heating"          ->      22.505   -> "Heating and cooling - commercial"
    -    "District heating"          ->      46.184   -> "Heating and cooling - homes"
    -    "Electricity grid"          ->      104.453  -> "Over generation / exports"
    -    "Electricity grid"          ->      113.726  -> "Heating and cooling - homes"
    -    "Electricity grid"          ->      27.14    -> "H2 conversion"
    -    "Electricity grid"          ->      342.165  -> "Industry"
    -    "Electricity grid"          ->      37.797   -> "Road transport"
    -    "Electricity grid"          ->      4.412    -> "Agriculture"
    -    "Electricity grid"          ->      40.858   -> "Heating and cooling - commercial"
    -    "Electricity grid"          ->      56.691   -> "Losses"
    -    "Electricity grid"          ->      7.863    -> "Rail transport"
    -    "Electricity grid"          ->      90.008   -> "Lighting & appliances - commercial"
    -    "Electricity grid"          ->      93.494   -> "Lighting & appliances - homes"
    -    "Gas imports"               ->      40.719   -> "Ngas"
    -    "Gas reserves"              ->      82.233   -> "Ngas"
    -    "Gas"                       ->      0.129    -> "Heating and cooling - commercial"
    -    "Gas"                       ->      1.401    -> "Losses"
    -    "Gas"                       ->      151.891  -> "Thermal generation"
    -    "Gas"                       ->      2.096    -> "Agriculture"
    -    "Gas"                       ->      48.58    -> "Industry"
    -    "Geothermal"                ->      7.013    -> "Electricity grid"
    -    "H2 conversion"             ->      20.897   -> "H2"
    -    "H2 conversion"             ->      6.242    -> "Losses"
    -    "H2"                        ->      20.897   -> "Road transport"
    -    "Hydro"                     ->      6.995    -> "Electricity grid"
    -    "Liquid"                    ->      121.066  -> "Industry"
    -    "Liquid"                    ->      128.69   -> "International shipping"
    -    "Liquid"                    ->      135.835  -> "Road transport"
    -    "Liquid"                    ->      14.458   -> "Domestic aviation"
    -    "Liquid"                    ->      206.267  -> "International aviation"
    -    "Liquid"                    ->      3.64     -> "Agriculture"
    -    "Liquid"                    ->      33.218   -> "National navigation"
    -    "Liquid"                    ->      4.413    -> "Rail transport"
    -    "Marine algae"              ->      4.375    -> "Bio-conversion"
    -    "Ngas"                      ->      122.952  -> "Gas"
    -    "Nuclear"                   ->      839.978  -> "Thermal generation"
    -    "Oil imports"               ->      504.287  -> "Oil"
    -    "Oil reserves"              ->      107.703  -> "Oil"
    -    "Oil"                       ->      611.99   -> "Liquid"
    -    "Other waste"               ->      56.587   -> "Solid"
    -    "Other waste"               ->      77.81    -> "Bio-conversion"
    -    "Pumped heat"               ->      193.026  -> "Heating and cooling - homes"
    -    "Pumped heat"               ->      70.672   -> "Heating and cooling - commercial"
    -    "Solar PV"                  ->      59.901   -> "Electricity grid"
    -    "Solar Thermal"             ->      19.263   -> "Heating and cooling - homes"
    -    "Solar"                     ->      19.263   -> "Solar Thermal"
    -    "Solar"                     ->      59.901   -> "Solar PV"
    -    "Solid"                     ->      0.882    -> "Agriculture"
    -    "Solid"                     ->      400.12   -> "Thermal generation"
    -    "Solid"                     ->      46.477   -> "Industry"
    -    "Thermal generation"        ->      525.531  -> "Electricity grid"
    -    "Thermal generation"        ->      787.129  -> "Losses"
    -    "Thermal generation"        ->      79.329   -> "District heating"
    -    "Tidal"                     ->      9.452    -> "Electricity grid"
    -    "UK land based bioenergy"   ->      182.01   -> "Bio-conversion"
    -    "Wave"                      ->      19.013   -> "Electricity grid"
    -    "Wind"                      ->      289.366  -> "Electricity grid"
    +Agricultural 'waste',Bio-conversion,124.729
    +Bio-conversion,Liquid,0.597
    +Bio-conversion,Losses,26.862
    +Bio-conversion,Solid,280.322
    +Bio-conversion,Gas,81.144
    +Biofuel imports,Liquid,35
    +Biomass imports,Solid,35
    +Coal imports,Coal,11.606
    +Coal reserves,Coal,63.965
    +Coal,Solid,75.571
    +District heating,Industry,10.639
    +District heating,Heating and cooling - commercial,22.505
    +District heating,Heating and cooling - homes,46.184
    +Electricity grid,Over generation / exports,104.453
    +Electricity grid,Heating and cooling - homes,113.726
    +Electricity grid,H2 conversion,27.14
    +Electricity grid,Industry,342.165
    +Electricity grid,Road transport,37.797
    +Electricity grid,Agriculture,4.412
    +Electricity grid,Heating and cooling - commercial,40.858
    +Electricity grid,Losses,56.691
    +Electricity grid,Rail transport,7.863
    +Electricity grid,Lighting & appliances - commercial,90.008
    +Electricity grid,Lighting & appliances - homes,93.494
    +Gas imports,Ngas,40.719
    +Gas reserves,Ngas,82.233
    +Gas,Heating and cooling - commercial,0.129
    +Gas,Losses,1.401
    +Gas,Thermal generation,151.891
    +Gas,Agriculture,2.096
    +Gas,Industry,48.58
    +Geothermal,Electricity grid,7.013
    +H2 conversion,H2,20.897
    +H2 conversion,Losses,6.242
    +H2,Road transport,20.897
    +Hydro,Electricity grid,6.995
    +Liquid,Industry,121.066
    +Liquid,International shipping,128.69
    +Liquid,Road transport,135.835
    +Liquid,Domestic aviation,14.458
    +Liquid,International aviation,206.267
    +Liquid,Agriculture,3.64
    +Liquid,National navigation,33.218
    +Liquid,Rail transport,4.413
    +Marine algae,Bio-conversion,4.375
    +Ngas,Gas,122.952
    +Nuclear,Thermal generation,839.978
    +Oil imports,Oil,504.287
    +Oil reserves,Oil,107.703
    +Oil,Liquid,611.99
    +Other waste,Solid,56.587
    +Other waste,Bio-conversion,77.81
    +Pumped heat,Heating and cooling - homes,193.026
    +Pumped heat,Heating and cooling - commercial,70.672
    +Solar PV,Electricity grid,59.901
    +Solar Thermal,Heating and cooling - homes,19.263
    +Solar,Solar Thermal,19.263
    +Solar,Solar PV,59.901
    +Solid,Agriculture,0.882
    +Solid,Thermal generation,400.12
    +Solid,Industry,46.477
    +Thermal generation,Electricity grid,525.531
    +Thermal generation,Losses,787.129
    +Thermal generation,District heating,79.329
    +Tidal,Electricity grid,9.452
    +UK land based bioenergy,Bio-conversion,182.01
    +Wave,Electricity grid,19.013
    +Wind,Electricity grid,289.366
       
    diff --git a/packages/mermaid/src/diagrams/sankey/energy.csv b/packages/mermaid/src/diagrams/sankey/parser/energy.csv similarity index 99% rename from packages/mermaid/src/diagrams/sankey/energy.csv rename to packages/mermaid/src/diagrams/sankey/parser/energy.csv index e5691f66c..36fd45d2a 100644 --- a/packages/mermaid/src/diagrams/sankey/energy.csv +++ b/packages/mermaid/src/diagrams/sankey/parser/energy.csv @@ -1,4 +1,3 @@ -source,target,value Agricultural 'waste',Bio-conversion,124.729 Bio-conversion,Liquid,0.597 Bio-conversion,Losses,26.862 diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.jison new file mode 100644 index 000000000..c981267a0 --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.jison @@ -0,0 +1,105 @@ +/** mermaid */ +%lex +TOKEN \w+ +NUM \d+(.\d+)? + +%options case-insensitive +%options easy_keword_rules + +%s link_value + +%x attributes +%x attr_value +%x string + +%% +//-------------------------------------------------------------- +// skip all whitespace EXCEPT newlines, but not within a string +//-------------------------------------------------------------- + +[^\S\r\n]+ {} + +//-------------- +// basic tokens +//-------------- + +(<>|[\n;])+ { return 'EOS'; } // end of statement is semicolon ; new line \n or end of file +"sankey" { return 'SANKEY'; } +{TOKEN} { return 'NODE_ID'; } +{NUM} { return 'AMOUNT'; } +"->" { + if(this.topState()!=='link_value') this.pushState('link_value'); + else this.popState(); + return 'ARROW'; + } +//------------ +// attributes +//------------ + +"[" { this.pushState('attributes'); return 'OPEN_ATTRIBUTES'; } +"]" { this.popState(); return 'CLOSE_ATTRIBUTES'; } +{TOKEN} { return 'ATTRIBUTE'; } +\= { this.pushState('attr_value'); return 'EQUAL'; } +{TOKEN} { this.popState(); return 'VALUE'; } + +//------------ +// strings +//------------ + +\" { this.pushState('string'); return 'OPEN_STRING'; } +(?!\\)\" { + if(this.topState()==='string') this.popState(); + if(this.topState()==='attr_value') this.popState(); + return 'CLOSE_STRING'; + } +([^"\\]|\\\"|\\\\)+ { return 'STRING'; } + +/lex + +%start start +%left ARROW + +%% // language grammar + +start + : EOS SANKEY document + | SANKEY document + ; + +document + : line document + | + ; + +line + : node optional_attributes EOS + | stream optional_attributes EOS + | EOS + ; + +optional_attributes: OPEN_ATTRIBUTES attributes CLOSE_ATTRIBUTES | ; + +attributes: attribute attributes | ; +attribute: ATTRIBUTE EQUAL value | ATTRIBUTE; + +value: VALUE | OPEN_STRING STRING CLOSE_STRING; + +stream + : node\[source] ARROW amount ARROW tail\[target] { + $$=$source; + yy.addLink($source, $target, $amount); + } + ; + +tail + : stream { $$ = $stream } + | node { $$ = $node; } + ; + +amount: AMOUNT { $$=parseFloat($AMOUNT); }; + +node + : NODE_ID { $$ = yy.findOrCreateNode($NODE_ID); } + | OPEN_STRING STRING\[node_label] CLOSE_STRING { $$ = yy.findOrCreateNode($node_label); } + ; + diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js b/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js similarity index 100% rename from packages/mermaid/src/diagrams/sankey/parser/sankey.spec.js rename to packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison index c981267a0..5ad25e811 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -1,105 +1,82 @@ /** mermaid */ + +//---------------------------------------------------- +// We support csv format as defined there +// CSV format // https://www.ietf.org/rfc/rfc4180.txt +//---------------------------------------------------- + %lex -TOKEN \w+ -NUM \d+(.\d+)? %options case-insensitive %options easy_keword_rules -%s link_value - -%x attributes -%x attr_value -%x string +// as per section 6.1 of RFC 2234 [2] +COMMA \u002C +CR \u000D +LF \u000A +CRLF \u000D\u000A +DQUOTE \u0022 +TEXTDATA [\u0020-\u0021\u0023-\u002B\u002D-\u007E] %% -//-------------------------------------------------------------- -// skip all whitespace EXCEPT newlines, but not within a string -//-------------------------------------------------------------- -[^\S\r\n]+ {} +<> { return 'EOF' } -//-------------- -// basic tokens -//-------------- - -(<>|[\n;])+ { return 'EOS'; } // end of statement is semicolon ; new line \n or end of file -"sankey" { return 'SANKEY'; } -{TOKEN} { return 'NODE_ID'; } -{NUM} { return 'AMOUNT'; } -"->" { - if(this.topState()!=='link_value') this.pushState('link_value'); - else this.popState(); - return 'ARROW'; - } -//------------ -// attributes -//------------ - -"[" { this.pushState('attributes'); return 'OPEN_ATTRIBUTES'; } -"]" { this.popState(); return 'CLOSE_ATTRIBUTES'; } -{TOKEN} { return 'ATTRIBUTE'; } -\= { this.pushState('attr_value'); return 'EQUAL'; } -{TOKEN} { this.popState(); return 'VALUE'; } - -//------------ -// strings -//------------ - -\" { this.pushState('string'); return 'OPEN_STRING'; } -(?!\\)\" { - if(this.topState()==='string') this.popState(); - if(this.topState()==='attr_value') this.popState(); - return 'CLOSE_STRING'; - } -([^"\\]|\\\"|\\\\)+ { return 'STRING'; } +"sankey" { return 'SANKEY' } +{COMMA} { return 'COMMA' } +{DQUOTE} { return 'DQUOTE' } +({CRLF}|{LF}) { return 'NEWLINE' } +{TEXTDATA}* { return 'NON_ESCAPED_TEXT' } +({TEXTDATA}|{COMMA}|{CR}|{LF}|{DQUOTE}{DQUOTE})* { return 'ESCAPED_TEXT' } /lex %start start -%left ARROW %% // language grammar start - : EOS SANKEY document - | SANKEY document + : SANKEY file opt_eof ; -document - : line document - | +file: csv opt_newline; + +csv + : record csv_tail ; -line - : node optional_attributes EOS - | stream optional_attributes EOS - | EOS +csv_tail + : NEWLINE csv + | // empty ; -optional_attributes: OPEN_ATTRIBUTES attributes CLOSE_ATTRIBUTES | ; - -attributes: attribute attributes | ; -attribute: ATTRIBUTE EQUAL value | ATTRIBUTE; - -value: VALUE | OPEN_STRING STRING CLOSE_STRING; - -stream - : node\[source] ARROW amount ARROW tail\[target] { - $$=$source; - yy.addLink($source, $target, $amount); - } +opt_newline + : NEWLINE + | // empty ; -tail - : stream { $$ = $stream } - | node { $$ = $node; } - ; - -amount: AMOUNT { $$=parseFloat($AMOUNT); }; - -node - : NODE_ID { $$ = yy.findOrCreateNode($NODE_ID); } - | OPEN_STRING STRING\[node_label] CLOSE_STRING { $$ = yy.findOrCreateNode($node_label); } +opt_eof + : EOF + | // empty ; +record + : field\[source] COMMA field\[target] COMMA field\[value] { + const source = yy.findOrCreateNode($source); + const target = yy.findOrCreateNode($target); + const value = parseFloat($value); + $$ = yy.addLink(source,target,value); + } // parse only 3 fields, this is not part of standard + | // allow empty record to handle empty lines, this is not part of csv standard either + ; + +field + : escaped { $$=$escaped; } + | non_escaped { $$=$non_escaped; } + ; + +escaped: DQUOTE ESCAPED_TEXT DQUOTE { $$=$ESCAPED_TEXT; }; + +non_escaped: NON_ESCAPED_TEXT { $$=$NON_ESCAPED_TEXT; }; + + diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts new file mode 100644 index 000000000..0c83df33f --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts @@ -0,0 +1,33 @@ +// @ts-ignore: jison doesn't export types +import diagram from './sankey.jison'; +// @ts-ignore: jison doesn't export types +import { parser } from './sankey.jison'; +import db from '../sankeyDB.js'; +// import { fail } from 'assert'; + + +describe('Sankey diagram', function () { + // TODO - these examples should be put into ./parser/stateDiagram.spec.js + describe('when parsing an info graph it', function () { + beforeEach(function () { + parser.yy = db; + diagram.parser.yy = db; + diagram.parser.yy.clear(); + }); + + it('parses csv', async () => { + + const fs = require('fs'); + const path = require('path').resolve(__dirname, "./energy.csv"); + await fs.readFile(path, 'utf8', (err: Error, data: string) => { + if (err) throw(err); + + const str = `sankey\\n${data}`; + + parser.parse(str); + }); + + }); + + }); +}); From 362648b74b53a951a5f40b7a70a036ec876a1398 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Wed, 21 Jun 2023 01:37:06 +0300 Subject: [PATCH 031/105] Trim trailing spaces --- demos/sankey.html | 137 +++++++++--------- .../src/diagrams/sankey/parser/energy.csv | 3 +- .../sankey/parser/sankey-arrow.spec.js | 4 +- .../src/diagrams/sankey/parser/sankey.jison | 6 +- 4 files changed, 76 insertions(+), 74 deletions(-) diff --git a/demos/sankey.html b/demos/sankey.html index d49bfc77e..4fc0993bf 100644 --- a/demos/sankey.html +++ b/demos/sankey.html @@ -19,74 +19,75 @@
         sankey
     
    -Agricultural 'waste',Bio-conversion,124.729
    -Bio-conversion,Liquid,0.597
    -Bio-conversion,Losses,26.862
    -Bio-conversion,Solid,280.322
    -Bio-conversion,Gas,81.144
    -Biofuel imports,Liquid,35
    -Biomass imports,Solid,35
    -Coal imports,Coal,11.606
    -Coal reserves,Coal,63.965
    -Coal,Solid,75.571
    -District heating,Industry,10.639
    -District heating,Heating and cooling - commercial,22.505
    -District heating,Heating and cooling - homes,46.184
    -Electricity grid,Over generation / exports,104.453
    -Electricity grid,Heating and cooling - homes,113.726
    -Electricity grid,H2 conversion,27.14
    -Electricity grid,Industry,342.165
    -Electricity grid,Road transport,37.797
    -Electricity grid,Agriculture,4.412
    -Electricity grid,Heating and cooling - commercial,40.858
    -Electricity grid,Losses,56.691
    -Electricity grid,Rail transport,7.863
    -Electricity grid,Lighting & appliances - commercial,90.008
    -Electricity grid,Lighting & appliances - homes,93.494
    -Gas imports,Ngas,40.719
    -Gas reserves,Ngas,82.233
    -Gas,Heating and cooling - commercial,0.129
    -Gas,Losses,1.401
    -Gas,Thermal generation,151.891
    -Gas,Agriculture,2.096
    -Gas,Industry,48.58
    -Geothermal,Electricity grid,7.013
    -H2 conversion,H2,20.897
    -H2 conversion,Losses,6.242
    -H2,Road transport,20.897
    -Hydro,Electricity grid,6.995
    -Liquid,Industry,121.066
    -Liquid,International shipping,128.69
    -Liquid,Road transport,135.835
    -Liquid,Domestic aviation,14.458
    -Liquid,International aviation,206.267
    -Liquid,Agriculture,3.64
    -Liquid,National navigation,33.218
    -Liquid,Rail transport,4.413
    -Marine algae,Bio-conversion,4.375
    -Ngas,Gas,122.952
    -Nuclear,Thermal generation,839.978
    -Oil imports,Oil,504.287
    -Oil reserves,Oil,107.703
    -Oil,Liquid,611.99
    -Other waste,Solid,56.587
    -Other waste,Bio-conversion,77.81
    -Pumped heat,Heating and cooling - homes,193.026
    -Pumped heat,Heating and cooling - commercial,70.672
    -Solar PV,Electricity grid,59.901
    -Solar Thermal,Heating and cooling - homes,19.263
    -Solar,Solar Thermal,19.263
    -Solar,Solar PV,59.901
    -Solid,Agriculture,0.882
    -Solid,Thermal generation,400.12
    -Solid,Industry,46.477
    -Thermal generation,Electricity grid,525.531
    -Thermal generation,Losses,787.129
    -Thermal generation,District heating,79.329
    -Tidal,Electricity grid,9.452
    -UK land based bioenergy,Bio-conversion,182.01
    -Wave,Electricity grid,19.013
    -Wind,Electricity grid,289.366
    +    Agricultural 'waste',Bio-conversion,124.729
    +    Bio-conversion,Liquid,0.597
    +    Bio-conversion,Losses,26.862
    +    Bio-conversion,Solid,280.322
    +    Bio-conversion,Gas,81.144
    +    Biofuel imports,Liquid,35
    +    Biomass imports,Solid,35
    +    Coal imports,Coal,11.606
    +    Coal reserves,Coal,63.965
    +    Coal,Solid,75.571
    +    District heating,Industry,10.639
    +    District heating,Heating and cooling - commercial,22.505
    +    District heating,Heating and cooling - homes,46.184
    +    Electricity grid,Over generation / exports,104.453
    +    Electricity grid,Heating and cooling - homes,113.726
    +    Electricity grid,H2 conversion,27.14
    +    Electricity grid,Industry,342.165
    +    Electricity grid,Road transport,37.797
    +    Electricity grid,Agriculture,4.412
    +    Electricity grid,Heating and cooling - commercial,40.858
    +    Electricity grid,Losses,56.691
    +    Electricity grid,Rail transport,7.863
    +    Electricity grid,Lighting & appliances - commercial,90.008
    +    Electricity grid,Lighting & appliances - homes,93.494
    +    Gas imports,Ngas,40.719
    +    Gas reserves,Ngas,82.233
    +    Gas,Heating and cooling - commercial,0.129
    +    Gas,Losses,1.401
    +    Gas,Thermal generation,151.891
    +    Gas,Agriculture,2.096
    +    Gas,Industry,48.58
    +    Geothermal,Electricity grid,7.013
    +    H2 conversion,H2,20.897
    +    H2 conversion,Losses,6.242
    +    H2,Road transport,20.897
    +    Hydro,Electricity grid,6.995
    +    Liquid,Industry,121.066
    +    Liquid,International shipping,128.69
    +    Liquid,Road transport,135.835
    +    Liquid,Domestic aviation,14.458
    +    Liquid,International aviation,206.267
    +    Liquid,Agriculture,3.64
    +    Liquid,National navigation,33.218
    +    Liquid,Rail transport,4.413
    +    Marine algae,Bio-conversion,4.375
    +    Ngas,Gas,122.952
    +    Nuclear,Thermal generation,839.978
    +    Oil imports,Oil,504.287
    +    Oil reserves,Oil,107.703
    +    Oil,Liquid,611.99
    +    Other waste,Solid,56.587
    +    Other waste,Bio-conversion,77.81
    +    Pumped heat,Heating and cooling - homes,193.026
    +    Pumped heat,Heating and cooling - commercial,70.672
    +    Solar PV,Electricity grid,59.901
    +    Solar Thermal,Heating and cooling - homes,19.263
    +    Solar,Solar Thermal,19.263
    +    Solar,Solar PV,59.901
    +    Solid,Agriculture,0.882
    +    Solid,Thermal generation,400.12
    +    Solid,Industry,46.477
    +    Thermal generation,Electricity grid,525.531
    +    Thermal generation,Losses,787.129
    +    Thermal generation,District heating,79.329
    +    Tidal,Electricity grid,9.452
    +    UK land based bioenergy,Bio-conversion,182.01
    +    Wave,Electricity grid,19.013
    +    Wind,Electricity grid,289.366
    +    
       
    diff --git a/packages/mermaid/src/diagrams/sankey/parser/energy.csv b/packages/mermaid/src/diagrams/sankey/parser/energy.csv index 36fd45d2a..ebddaca0c 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/energy.csv +++ b/packages/mermaid/src/diagrams/sankey/parser/energy.csv @@ -64,5 +64,6 @@ Thermal generation,Losses,787.129 Thermal generation,District heating,79.329 Tidal,Electricity grid,9.452 UK land based bioenergy,Bio-conversion,182.01 + Wave,Electricity grid,19.013 -Wind,Electricity grid,289.366 \ No newline at end of file +Wind,Electricity grid,289.366 diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js b/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js index 58fe31ab1..19f1ff357 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js @@ -1,5 +1,5 @@ -import diagram from './sankey.jison'; -import { parser } from './sankey.jison'; +import diagram from './sankey-arrow.jison'; +import { parser } from './sankey-arrow.jison'; import db from '../sankeyDB.js'; // import { fail } from 'assert'; diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison index 5ad25e811..c0294917c 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -62,9 +62,9 @@ opt_eof record : field\[source] COMMA field\[target] COMMA field\[value] { - const source = yy.findOrCreateNode($source); - const target = yy.findOrCreateNode($target); - const value = parseFloat($value); + const source = yy.findOrCreateNode($source.trim()); + const target = yy.findOrCreateNode($target.trim()); + const value = parseFloat($value.trim()); $$ = yy.addLink(source,target,value); } // parse only 3 fields, this is not part of standard | // allow empty record to handle empty lines, this is not part of csv standard either From 6c6efb24f406009a6c7fdd0fbd4476f1f3b279a1 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Wed, 21 Jun 2023 01:48:48 +0300 Subject: [PATCH 032/105] Fix graph width --- .../src/diagrams/sankey/parser/sankey.jison | 4 ++-- .../src/diagrams/sankey/parser/sankey.spec.ts | 12 ++++-------- .../mermaid/src/diagrams/sankey/sankeyRenderer.ts | 15 ++++++--------- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison index c0294917c..7f5d76554 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -66,8 +66,8 @@ record const target = yy.findOrCreateNode($target.trim()); const value = parseFloat($value.trim()); $$ = yy.addLink(source,target,value); - } // parse only 3 fields, this is not part of standard - | // allow empty record to handle empty lines, this is not part of csv standard either + } // parse only 3 fields, this is not part of CSV standard + | // allow empty record to handle empty lines, this is not part of CSV standard either ; field diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts index 0c83df33f..d25a1cdb6 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts @@ -5,7 +5,6 @@ import { parser } from './sankey.jison'; import db from '../sankeyDB.js'; // import { fail } from 'assert'; - describe('Sankey diagram', function () { // TODO - these examples should be put into ./parser/stateDiagram.spec.js describe('when parsing an info graph it', function () { @@ -14,20 +13,17 @@ describe('Sankey diagram', function () { diagram.parser.yy = db; diagram.parser.yy.clear(); }); - + it('parses csv', async () => { - const fs = require('fs'); - const path = require('path').resolve(__dirname, "./energy.csv"); + const path = require('path').resolve(__dirname, './energy.csv'); await fs.readFile(path, 'utf8', (err: Error, data: string) => { - if (err) throw(err); - + if (err) throw err; + const str = `sankey\\n${data}`; parser.parse(str); }); - }); - }); }); diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index c8a3edaa8..63abb2a6c 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -88,17 +88,19 @@ export const draw = function (text: string, id: string, _version: string, diagOb const graph = diagObj.db.getGraph(); + const nodeWidth = 10; // Construct and configure a Sankey generator // That will be a function that calculates nodes and links dimensions // const sankey = d3Sankey() .nodeId((d) => d.id) // we use 'id' property to identify node - .nodeWidth(10) + .nodeWidth(nodeWidth) .nodePadding(10) .nodeAlign(d3SankeyJustify) // d3.sankeyLeft, etc. - .size([width, height]); - // .extent([[5, 20], [width - 5, height - 20]]); alias for size - // paddings + .extent([ + [0, 0], + [width - nodeWidth, height], + ]); //["left", "sankeyLeft"], ["right", "sankeyRight"], ["center", "sankeyCenter"], ["justify", "sankeyJustify"] // .nodeWidth(15) @@ -169,11 +171,6 @@ export const draw = function (text: string, id: string, _version: string, diagOb .attr('d', d3SankeyLinkHorizontal()) .attr('stroke', (d) => color(d.source.id)) .attr('stroke-width', (d) => Math.max(1, d.width)); - - // const { nodes, links } = generator({ - // nodes: graph.nodes, - // links: graph.links, - // }); }; export default { From 24d9f59d6997ca192ba50e62ad277d0bf29a2e49 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Wed, 21 Jun 2023 02:52:21 +0300 Subject: [PATCH 033/105] Fix specs --- .../mermaid/src/diagrams/sankey/parser/sankey.spec.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts index d25a1cdb6..31f02a4a0 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts @@ -15,10 +15,12 @@ describe('Sankey diagram', function () { }); it('parses csv', async () => { - const fs = require('fs'); - const path = require('path').resolve(__dirname, './energy.csv'); + const fs = await import('fs'); + const path = await require('path').resolve(__dirname, './energy.csv'); await fs.readFile(path, 'utf8', (err: Error, data: string) => { - if (err) throw err; + if (err) { + throw err; + } const str = `sankey\\n${data}`; From 7bc5c1930ee75ede54141caf5728f615bd4b6021 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Wed, 21 Jun 2023 02:58:25 +0300 Subject: [PATCH 034/105] Fix specs --- packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts index 31f02a4a0..60d70013d 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts @@ -16,8 +16,9 @@ describe('Sankey diagram', function () { it('parses csv', async () => { const fs = await import('fs'); - const path = await require('path').resolve(__dirname, './energy.csv'); - await fs.readFile(path, 'utf8', (err: Error, data: string) => { + const path = await import('path'); + const csv = path.resolve(__dirname, './energy.csv'); + fs.readFile(csv, 'utf8', (err: NodeJS.ErrnoException | null, data: string) => { if (err) { throw err; } From 272615e580382b724117faae502841ad27c723bb Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Wed, 21 Jun 2023 03:54:55 +0300 Subject: [PATCH 035/105] Fixed tests and added node alignment --- cypress/integration/rendering/sankey.spec.js | 2 +- demos/sankey.html | 2 +- .../src/diagrams/sankey/parser/energy.csv | 3 +-- .../src/diagrams/sankey/parser/sankey.jison | 19 ++++++++++--------- .../mermaid/src/diagrams/sankey/sankeyDB.ts | 11 +++++++++++ .../src/diagrams/sankey/sankeyRenderer.ts | 16 ++++++++-------- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/cypress/integration/rendering/sankey.spec.js b/cypress/integration/rendering/sankey.spec.js index 5b9bcf870..3ed242724 100644 --- a/cypress/integration/rendering/sankey.spec.js +++ b/cypress/integration/rendering/sankey.spec.js @@ -5,7 +5,7 @@ describe('Sankey Diagram', () => { imgSnapshotTest( ` sankey - a -> 30 -> b + a,b,10 `, {} ); diff --git a/demos/sankey.html b/demos/sankey.html index 4fc0993bf..3afc83371 100644 --- a/demos/sankey.html +++ b/demos/sankey.html @@ -18,7 +18,7 @@

    Energy flow

         sankey
    -
    +    
         Agricultural 'waste',Bio-conversion,124.729
         Bio-conversion,Liquid,0.597
         Bio-conversion,Losses,26.862
    diff --git a/packages/mermaid/src/diagrams/sankey/parser/energy.csv b/packages/mermaid/src/diagrams/sankey/parser/energy.csv
    index ebddaca0c..36fd45d2a 100644
    --- a/packages/mermaid/src/diagrams/sankey/parser/energy.csv
    +++ b/packages/mermaid/src/diagrams/sankey/parser/energy.csv
    @@ -64,6 +64,5 @@ Thermal generation,Losses,787.129
     Thermal generation,District heating,79.329
     Tidal,Electricity grid,9.452
     UK land based bioenergy,Bio-conversion,182.01
    -
     Wave,Electricity grid,19.013
    -Wind,Electricity grid,289.366
    +Wind,Electricity grid,289.366
    \ No newline at end of file
    diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison
    index 7f5d76554..649d2dbd8 100644
    --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison
    +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison
    @@ -1,9 +1,12 @@
     /** mermaid */
     
    -//----------------------------------------------------
    -// We support csv format as defined there
    -// CSV format // https://www.ietf.org/rfc/rfc4180.txt
    -//----------------------------------------------------
    +//---------------------------------------------------------
    +// We support csv format as defined here:
    +// https://www.ietf.org/rfc/rfc4180.txt
    +// There are some minor changes for compliance with jison
    +// We also parse only 3 columns: source,target,value
    +// And allow blank lines for visual purposes
    +//---------------------------------------------------------
     
     %lex
     
    @@ -23,9 +26,9 @@ TEXTDATA [\u0020-\u0021\u0023-\u002B\u002D-\u007E]
     <> { return 'EOF' }
     
     "sankey" { return 'SANKEY' }
    +({CRLF}|{LF})+ { return 'NEWLINE' } // let newline to be multiple lines
     {COMMA} { return 'COMMA' }
     {DQUOTE} { return 'DQUOTE' }
    -({CRLF}|{LF}) { return 'NEWLINE' }
     {TEXTDATA}* { return 'NON_ESCAPED_TEXT' }
     ({TEXTDATA}|{COMMA}|{CR}|{LF}|{DQUOTE}{DQUOTE})* { return 'ESCAPED_TEXT' }
     
    @@ -36,11 +39,9 @@ TEXTDATA [\u0020-\u0021\u0023-\u002B\u002D-\u007E]
     %% // language grammar
     
     start
    -  : SANKEY file opt_eof
    +  : SANKEY csv opt_eof
       ;
     
    -file: csv opt_newline;
    -
     csv
       : record csv_tail 
       ;
    @@ -67,7 +68,7 @@ record
           const value = parseFloat($value.trim());
           $$ = yy.addLink(source,target,value);
         } // parse only 3 fields, this is not part of CSV standard
    -  | // allow empty record to handle empty lines, this is not part of CSV standard either
    +  | {} // allow empty record to handle empty lines, this is not part of CSV standard either
       ;
     
     field
    diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts
    index d44f3e6e8..f3faf9c7a 100644
    --- a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts
    +++ b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts
    @@ -19,11 +19,20 @@ import {
     let links: Array = [];
     let nodes: Array = [];
     let nodesHash: Record = {};
    +let nodeAlign: string = 'justify';
    +
    +const setNodeAlign = function (alignment: string): void {
    +  const nodeAlignments: string[] = ['left', 'right', 'center', 'justify'];
    +  if (nodeAlignments.includes(alignment)) nodeAlign = alignment;
    +};
    +
    +const getNodeAlign = () => nodeAlign;
     
     const clear = function () {
       links = [];
       nodes = [];
       nodesHash = {};
    +  nodeAlign = 'justify';
       commonClear();
     };
     
    @@ -85,6 +94,8 @@ export default {
       getGraph,
       addLink,
       findOrCreateNode,
    +  setNodeAlign,
    +  getNodeAlign,
       // TODO: If this is a must this probably should be an interface
       getAccTitle,
       setAccTitle,
    diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts
    index 63abb2a6c..4956b6cf2 100644
    --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts
    +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts
    @@ -87,6 +87,13 @@ export const draw = function (text: string, id: string, _version: string, diagOb
       //
     
       const graph = diagObj.db.getGraph();
    +  const nodeAligns = {
    +    left: d3SankeyLeft,
    +    right: d3SankeyRight,
    +    center: d3SankeyCenter,
    +    justify: d3SankeyJustify,
    +  };
    +  const nodeAlign = nodeAligns[diagObj.db.getNodeAlign()];
     
       const nodeWidth = 10;
       // Construct and configure a Sankey generator
    @@ -96,19 +103,12 @@ export const draw = function (text: string, id: string, _version: string, diagOb
         .nodeId((d) => d.id) // we use 'id' property to identify node
         .nodeWidth(nodeWidth)
         .nodePadding(10)
    -    .nodeAlign(d3SankeyJustify) // d3.sankeyLeft, etc.
    +    .nodeAlign(nodeAlign) // d3.sankeyLeft, etc.
         .extent([
           [0, 0],
           [width - nodeWidth, height],
         ]);
     
    -  //["left", "sankeyLeft"], ["right", "sankeyRight"], ["center", "sankeyCenter"], ["justify", "sankeyJustify"]
    -  // .nodeWidth(15)
    -  //   .nodePadding(10)
    -  //   .extent([[1, 5], [width - 1, height - 5]]);
    -  // .nodeId(d => d['id'])
    -  //
    -
       // Compute the Sankey layout
       // Namely calculate nodes and links positions
       // Our `graph` object will be mutated by this
    
    From be9cd480aa0a9815a899a5b158850e6a8292b6fa Mon Sep 17 00:00:00 2001
    From: Nikolay Rozhkov 
    Date: Thu, 22 Jun 2023 15:56:56 +0300
    Subject: [PATCH 036/105] Sankey syntax has beed reduced
    
    ---
     cypress/integration/rendering/sankey.spec.js  |  1 +
     .../src/diagrams/sankey/parser/energy.csv     | 12 ++++++--
     .../src/diagrams/sankey/parser/sankey.jison   | 30 ++++---------------
     .../src/diagrams/sankey/parser/sankey.spec.ts | 18 ++++++-----
     .../src/diagrams/sankey/sankeyDiagram.ts      | 12 ++++++++
     5 files changed, 39 insertions(+), 34 deletions(-)
    
    diff --git a/cypress/integration/rendering/sankey.spec.js b/cypress/integration/rendering/sankey.spec.js
    index 3ed242724..435ef8184 100644
    --- a/cypress/integration/rendering/sankey.spec.js
    +++ b/cypress/integration/rendering/sankey.spec.js
    @@ -5,6 +5,7 @@ describe('Sankey Diagram', () => {
         imgSnapshotTest(
           `
           sankey
    +      
           a,b,10
           `,
           {}
    diff --git a/packages/mermaid/src/diagrams/sankey/parser/energy.csv b/packages/mermaid/src/diagrams/sankey/parser/energy.csv
    index 36fd45d2a..7f4dbc153 100644
    --- a/packages/mermaid/src/diagrams/sankey/parser/energy.csv
    +++ b/packages/mermaid/src/diagrams/sankey/parser/energy.csv
    @@ -1,5 +1,7 @@
    -Agricultural 'waste',Bio-conversion,124.729
    +%% there are leading and trailing spaces, do not remove
    +    Agricultural 'waste',Bio-conversion,124.729   
     Bio-conversion,Liquid,0.597
    +%% line with comment
     Bio-conversion,Losses,26.862
     Bio-conversion,Solid,280.322
     Bio-conversion,Gas,81.144
    @@ -50,6 +52,9 @@ Oil reserves,Oil,107.703
     Oil,Liquid,611.99
     Other waste,Solid,56.587
     Other waste,Bio-conversion,77.81
    +%% blank lines in the middle
    +
    +
     Pumped heat,Heating and cooling - homes,193.026
     Pumped heat,Heating and cooling - commercial,70.672
     Solar PV,Electricity grid,59.901
    @@ -65,4 +70,7 @@ Thermal generation,District heating,79.329
     Tidal,Electricity grid,9.452
     UK land based bioenergy,Bio-conversion,182.01
     Wave,Electricity grid,19.013
    -Wind,Electricity grid,289.366
    \ No newline at end of file
    +Wind,Electricity grid,289.366
    +
    +%% lines at the end, do not remove
    +
    diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison
    index 649d2dbd8..b9d5854ed 100644
    --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.jison
    +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison
    @@ -23,10 +23,10 @@ TEXTDATA [\u0020-\u0021\u0023-\u002B\u002D-\u007E]
     
     %%
     
    -<> { return 'EOF' }
    +<> { return 'EOF' } // match end of file
     
     "sankey" { return 'SANKEY' }
    -({CRLF}|{LF})+ { return 'NEWLINE' } // let newline to be multiple lines
    +({CRLF}|{LF}) { return 'NEWLINE' }
     {COMMA} { return 'COMMA' }
     {DQUOTE} { return 'DQUOTE' }
     {TEXTDATA}* { return 'NON_ESCAPED_TEXT' }
    @@ -38,28 +38,11 @@ TEXTDATA [\u0020-\u0021\u0023-\u002B\u002D-\u007E]
     
     %% // language grammar
     
    -start
    -  : SANKEY csv opt_eof
    -  ;
    +start: SANKEY NEWLINE csv opt_eof;
     
    -csv
    -  : record csv_tail 
    -  ;
    -
    -csv_tail
    -  : NEWLINE csv
    -  | // empty
    -  ;
    -
    -opt_newline
    -  : NEWLINE
    -  | // empty
    -  ;
    -
    -opt_eof
    -  : EOF
    -  | // empty
    -  ;
    +csv: record csv_tail;
    +csv_tail: NEWLINE csv | ;
    +opt_eof: EOF | ;
     
     record
       : field\[source] COMMA field\[target] COMMA field\[value] {
    @@ -68,7 +51,6 @@ record
           const value = parseFloat($value.trim());
           $$ = yy.addLink(source,target,value);
         } // parse only 3 fields, this is not part of CSV standard
    -  | {} // allow empty record to handle empty lines, this is not part of CSV standard either
       ;
     
     field
    diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts
    index 60d70013d..873e99cb1 100644
    --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts
    +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts
    @@ -3,7 +3,8 @@ import diagram from './sankey.jison';
     // @ts-ignore: jison doesn't export types
     import { parser } from './sankey.jison';
     import db from '../sankeyDB.js';
    -// import { fail } from 'assert';
    +import { cleanupComments } from '../../../diagram-api/comments.js';
    +import { prepareTextForParsing } from '../sankeyDiagram.js';
     
     describe('Sankey diagram', function () {
       // TODO - these examples should be put into ./parser/stateDiagram.spec.js
    @@ -18,15 +19,16 @@ describe('Sankey diagram', function () {
           const fs = await import('fs');
           const path = await import('path');
           const csv = path.resolve(__dirname, './energy.csv');
    -      fs.readFile(csv, 'utf8', (err: NodeJS.ErrnoException | null, data: string) => {
    -        if (err) {
    -          throw err;
    -        }
    +      const data = fs.readFileSync(csv, 'utf8');
     
    -        const str = `sankey\\n${data}`;
    +      // Add \n\n + space to emulate possible possible imperfections
    +      const graphDefinition = prepareTextForParsing(cleanupComments('sankey\n\n ' + data));
    +      // const textToParse = graphDefinition
    +      //   .replaceAll(/^[^\S\r\n]+|[^\S\r\n]+$/g, '')
    +      //   .replaceAll(/([\n\r])+/g, "\n")
    +      //   .trim();
     
    -        parser.parse(str);
    -      });
    +      parser.parse(graphDefinition);
         });
       });
     });
    diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts
    index 9c8fbaa2d..7ec305db7 100644
    --- a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts
    +++ b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts
    @@ -5,6 +5,18 @@ import db from './sankeyDB.js';
     import styles from './styles.js';
     import renderer from './sankeyRenderer.js';
     
    +export const prepareTextForParsing = (text: string): string => {
    +  const textToParse = text
    +    .replaceAll(/^[^\S\r\n]+|[^\S\r\n]+$/g, '') // remove all trailing spaces for each row
    +    .replaceAll(/([\n\r])+/g, '\n') // remove empty lines duplicated
    +    .trim();
    +
    +  return textToParse;
    +};
    +
    +const originalParse = parser.parse.bind(parser);
    +parser.parse = (text: string) => originalParse(prepareTextForParsing(text));
    +
     export const diagram: DiagramDefinition = {
       parser,
       db,
    
    From 9a29066426d27b6a751203b0f2b4fcb469aff5d8 Mon Sep 17 00:00:00 2001
    From: Nikolay Rozhkov 
    Date: Thu, 22 Jun 2023 16:53:44 +0300
    Subject: [PATCH 037/105] Ensure that sankey keyword does not intefere with csv
    
    ---
     demos/sankey.html                             | 145 +++++++++---------
     .../src/diagrams/sankey/parser/energy.csv     |  45 ++++--
     .../src/diagrams/sankey/parser/sankey.jison   |  20 ++-
     3 files changed, 118 insertions(+), 92 deletions(-)
    
    diff --git a/demos/sankey.html b/demos/sankey.html
    index 3afc83371..a7d4d5537 100644
    --- a/demos/sankey.html
    +++ b/demos/sankey.html
    @@ -17,79 +17,78 @@
         

    Sankey diagram demos

    Energy flow

    -    sankey
    -    
    -    Agricultural 'waste',Bio-conversion,124.729
    -    Bio-conversion,Liquid,0.597
    -    Bio-conversion,Losses,26.862
    -    Bio-conversion,Solid,280.322
    -    Bio-conversion,Gas,81.144
    -    Biofuel imports,Liquid,35
    -    Biomass imports,Solid,35
    -    Coal imports,Coal,11.606
    -    Coal reserves,Coal,63.965
    -    Coal,Solid,75.571
    -    District heating,Industry,10.639
    -    District heating,Heating and cooling - commercial,22.505
    -    District heating,Heating and cooling - homes,46.184
    -    Electricity grid,Over generation / exports,104.453
    -    Electricity grid,Heating and cooling - homes,113.726
    -    Electricity grid,H2 conversion,27.14
    -    Electricity grid,Industry,342.165
    -    Electricity grid,Road transport,37.797
    -    Electricity grid,Agriculture,4.412
    -    Electricity grid,Heating and cooling - commercial,40.858
    -    Electricity grid,Losses,56.691
    -    Electricity grid,Rail transport,7.863
    -    Electricity grid,Lighting & appliances - commercial,90.008
    -    Electricity grid,Lighting & appliances - homes,93.494
    -    Gas imports,Ngas,40.719
    -    Gas reserves,Ngas,82.233
    -    Gas,Heating and cooling - commercial,0.129
    -    Gas,Losses,1.401
    -    Gas,Thermal generation,151.891
    -    Gas,Agriculture,2.096
    -    Gas,Industry,48.58
    -    Geothermal,Electricity grid,7.013
    -    H2 conversion,H2,20.897
    -    H2 conversion,Losses,6.242
    -    H2,Road transport,20.897
    -    Hydro,Electricity grid,6.995
    -    Liquid,Industry,121.066
    -    Liquid,International shipping,128.69
    -    Liquid,Road transport,135.835
    -    Liquid,Domestic aviation,14.458
    -    Liquid,International aviation,206.267
    -    Liquid,Agriculture,3.64
    -    Liquid,National navigation,33.218
    -    Liquid,Rail transport,4.413
    -    Marine algae,Bio-conversion,4.375
    -    Ngas,Gas,122.952
    -    Nuclear,Thermal generation,839.978
    -    Oil imports,Oil,504.287
    -    Oil reserves,Oil,107.703
    -    Oil,Liquid,611.99
    -    Other waste,Solid,56.587
    -    Other waste,Bio-conversion,77.81
    -    Pumped heat,Heating and cooling - homes,193.026
    -    Pumped heat,Heating and cooling - commercial,70.672
    -    Solar PV,Electricity grid,59.901
    -    Solar Thermal,Heating and cooling - homes,19.263
    -    Solar,Solar Thermal,19.263
    -    Solar,Solar PV,59.901
    -    Solid,Agriculture,0.882
    -    Solid,Thermal generation,400.12
    -    Solid,Industry,46.477
    -    Thermal generation,Electricity grid,525.531
    -    Thermal generation,Losses,787.129
    -    Thermal generation,District heating,79.329
    -    Tidal,Electricity grid,9.452
    -    UK land based bioenergy,Bio-conversion,182.01
    -    Wave,Electricity grid,19.013
    -    Wind,Electricity grid,289.366
    -    
    -  
    + sankey + + Agricultural 'waste',Bio-conversion,124.729 + Bio-conversion,Liquid,0.597 + %% line with comment + Bio-conversion,Losses,26.862 + Bio-conversion,Solid,280.322 + Bio-conversion,Gas,81.144 + Biofuel imports,Liquid,35 + Biomass imports,Solid,35 + Coal imports,Coal,11.606 + Coal reserves,Coal,63.965 + Coal,Solid,75.571 + District heating,Industry,10.639 + District heating,Heating and cooling - commercial,22.505 + District heating,Heating and cooling - homes,46.184 + Electricity grid,Over generation / exports,104.453 + Electricity grid,Heating and cooling - homes,113.726 + Electricity grid,H2 conversion,27.14 + Electricity grid,Industry,342.165 + Electricity grid,Road transport,37.797 + Electricity grid,Agriculture,4.412 + Electricity grid,Heating and cooling - commercial,40.858 + Electricity grid,Losses,56.691 + Electricity grid,Rail transport,7.863 + Electricity grid,Lighting & appliances - commercial,90.008 + Electricity grid,Lighting & appliances - homes,93.494 + Gas imports,Ngas,40.719 + Gas reserves,Ngas,82.233 + Gas,Heating and cooling - commercial,0.129 + Gas,Losses,1.401 + Gas,Thermal generation,151.891 + Gas,Agriculture,2.096 + Gas,Industry,48.58 + Geothermal,Electricity grid,7.013 + H2 conversion,H2,20.897 + H2 conversion,Losses,6.242 + H2,Road transport,20.897 + Hydro,Electricity grid,6.995 + Liquid,Industry,121.066 + Liquid,International shipping,128.69 + Liquid,Road transport,135.835 + Liquid,Domestic aviation,14.458 + Liquid,International aviation,206.267 + Liquid,Agriculture,3.64 + Liquid,National navigation,33.218 + Liquid,Rail transport,4.413 + Marine algae,Bio-conversion,4.375 + Ngas,Gas,122.952 + Nuclear,Thermal generation,839.978 + Oil imports,Oil,504.287 + Oil reserves,Oil,107.703 + Oil,Liquid,611.99 + Other waste,Solid,56.587 + Other waste,Bio-conversion,77.81 + Pumped heat,Heating and cooling - homes,193.026 + Pumped heat,Heating and cooling - commercial,70.672 + Solar PV,Electricity grid,59.901 + Solar Thermal,Heating and cooling - homes,19.263 + Solar,Solar Thermal,19.263 + Solar,Solar PV,59.901 + Solid,Agriculture,0.882 + Solid,Thermal generation,400.12 + Solid,Industry,46.477 + Thermal generation,Electricity grid,525.531 + Thermal generation,Losses,787.129 + Thermal generation,District heating,79.329 + Tidal,Electricity grid,9.452 + UK land based bioenergy,Bio-conversion,182.01 + Wave,Electricity grid,19.013 + Wind,Electricity grid,289.366 +
    diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index f50a15d6b..7e0b67eb5 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -412,7 +412,17 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig { wrappingWidth?: number; } -export type SankeyDiagramConfig = BaseDiagramConfig; +export enum SankeyLinkColor { + source = 'source', + target = 'target', + gradient = 'gradient', +} + +export interface SankeyDiagramConfig extends BaseDiagramConfig { + width?: number; + height?: number; + linkColor?: SankeyLinkColor | string; +} export interface FontConfig { fontSize?: string | number; diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts index c9407828f..4ece25010 100644 --- a/packages/mermaid/src/defaultConfig.ts +++ b/packages/mermaid/src/defaultConfig.ts @@ -2270,6 +2270,11 @@ const config: Partial = { padding: 10, maxNodeWidth: 200, }, + sankey: { + width: 800, + height: 400, + linkColor: 'gradient', + }, fontSize: 16, }; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts index 04515cda4..098f3938c 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts @@ -20,7 +20,7 @@ let nodesMap: Record = {}; let nodeAlign = 'justify'; const setNodeAlign = (alignment: string): void => { - const nodeAlignments: Set= new Set(['left', 'right', 'center', 'justify']); + const nodeAlignments: Set = new Set(['left', 'right', 'center', 'justify']); if (nodeAlignments.has(alignment)) { nodeAlign = alignment; } diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 1a8eb5c7e..dd31e775e 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -20,9 +20,10 @@ import { } from 'd3-sankey'; import { configureSvgSize } from '../../setupGraphViewbox.js'; import { Uid } from './sankeyUtils.js'; +import { SankeyLinkColor } from '../../config.type.js'; /** - * Draws a sequenceDiagram in the tag with id: id based on the graph definition in text. + * Draws Sankey diagram. * * @param text - The text of the diagram * @param id - The id of the diagram which will be used as a DOM element id¨ @@ -30,10 +31,14 @@ import { Uid } from './sankeyUtils.js'; * @param diagObj - A standard diagram containing the db and the text and type etc of the diagram */ export const draw = function (text: string, id: string, _version: string, diagObj: Diagram): void { - // TODO: Figure out what is happening there + // Get Sankey config + const { securityLevel, sankey: conf } = configApi.getConfig(); + + // TODO: + // This code repeats for every diagram + // Figure out what is happening there, probably it should be separated // The main thing is svg object that is a d3 wrapper for svg operations // - const { securityLevel, sequence: conf } = configApi.getConfig(); let sandboxElement: any; if (securityLevel === 'sandbox') { sandboxElement = d3select('#i' + id); @@ -43,18 +48,17 @@ export const draw = function (text: string, id: string, _version: string, diagOb if (securityLevel === 'sandbox' && sandboxElement) { root = d3select(sandboxElement.nodes()[0].contentDocument.body); } - const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; const svg = securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : d3select(`[id="${id}"]`); // Establish svg dimensions and get width and height // - const elem = doc.getElementById(id); - const width = elem.parentElement.offsetWidth; - const height = 600; + const width = conf?.width || 800; + const height = conf?.height || 400; + const useMaxWidth = conf?.useMaxWidth || false; - // FIX: using max width prevents height from being set - configureSvgSize(svg, height, width, false); - // svg.attr('height', height); // that's why we need this line + // FIX: using max width prevents height from being set, is it intended? + // to add height directly one can use `svg.attr('height', height)` + configureSvgSize(svg, height, width, useMaxWidth); // Prepare data for construction based on diagObj.db // This must be a mutable object with `nodes` and `links` properties: @@ -142,27 +146,46 @@ export const draw = function (text: string, id: string, _version: string, diagOb .attr('class', 'link') .style('mix-blend-mode', 'multiply'); - const gradient = link - .append('linearGradient') - .attr('id', (d) => (d.uid = Uid.next('linearGradient-')).id) - .attr('gradientUnits', 'userSpaceOnUse') - .attr('x1', (d) => d.source.x1) - .attr('x2', (d) => d.target.x0); + const linkColor = conf?.linkColor || SankeyLinkColor.gradient; - gradient - .append('stop') - .attr('offset', '0%') - .attr('stop-color', (d) => colorScheme(d.source.id)); + if (linkColor === SankeyLinkColor.gradient) { + const gradient = link + .append('linearGradient') + .attr('id', (d) => (d.uid = Uid.next('linearGradient-')).id) + .attr('gradientUnits', 'userSpaceOnUse') + .attr('x1', (d) => d.source.x1) + .attr('x2', (d) => d.target.x0); - gradient - .append('stop') - .attr('offset', '100%') - .attr('stop-color', (d) => colorScheme(d.target.id)); + gradient + .append('stop') + .attr('offset', '0%') + .attr('stop-color', (d) => colorScheme(d.source.id)); + + gradient + .append('stop') + .attr('offset', '100%') + .attr('stop-color', (d) => colorScheme(d.target.id)); + } + + let coloring: any; + switch (linkColor) { + case SankeyLinkColor.gradient: + coloring = (d) => d.uid; + break; + case SankeyLinkColor.source: + coloring = (d) => d.source.id; + break; + case SankeyLinkColor.target: + coloring = (d) => d.target.id; + break; + default: + coloring = linkColor; + } link .append('path') .attr('d', d3SankeyLinkHorizontal()) - .attr('stroke', (d: any) => d.uid) + .attr('stroke', coloring) .attr('stroke-width', (d: any) => Math.max(1, d.width)); }; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyUtils.ts b/packages/mermaid/src/diagrams/sankey/sankeyUtils.ts index d88e528c5..08e4c547f 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyUtils.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyUtils.ts @@ -24,4 +24,4 @@ export class Uid { toString(): string { return 'url(' + this.href + ')'; } -} \ No newline at end of file +} From db2a556f626beb630629f9ddc39e604590eb2610 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 25 Jun 2023 00:23:42 +0300 Subject: [PATCH 049/105] Cleanup --- cypress/integration/rendering/sankey.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/rendering/sankey.spec.js b/cypress/integration/rendering/sankey.spec.js index 0a065e46c..7a3ffa5db 100644 --- a/cypress/integration/rendering/sankey.spec.js +++ b/cypress/integration/rendering/sankey.spec.js @@ -1,4 +1,4 @@ -import { imgSnapshotTest, renderGraph } from '../../helpers/util.js'; +import { imgSnapshotTest } from '../../helpers/util.js'; describe('Sankey Diagram', () => { it('should render a simple example', () => { From 1d6074dbfa6e7f0c0c0ca59987d55846d062965f Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 25 Jun 2023 00:42:06 +0300 Subject: [PATCH 050/105] Sankey: Use [] instead of Array --- packages/mermaid/src/diagrams/sankey/sankeyDB.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts index 098f3938c..807c07cd1 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts @@ -14,8 +14,8 @@ import { // Sankey diagram represented by nodes and links between those nodes // We have to track nodes uniqueness (by ID), thats why we need a mapping also // -let links: Array = []; -let nodes: Array = []; +let links: SankeyLink[] = []; +let nodes: SankeyNode[] = []; let nodesMap: Record = {}; let nodeAlign = 'justify'; From 6a893a758b9784c6e493c0697c04ad33f339e721 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 25 Jun 2023 01:07:11 +0300 Subject: [PATCH 051/105] Styles are optional --- packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts | 2 -- packages/mermaid/src/diagrams/sankey/styles.js | 5 ----- packages/mermaid/src/styles.spec.ts | 2 -- 3 files changed, 9 deletions(-) delete mode 100644 packages/mermaid/src/diagrams/sankey/styles.js diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts index 4fce38f97..d5b62122e 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts @@ -2,7 +2,6 @@ import { DiagramDefinition } from '../../diagram-api/types.js'; // @ts-ignore: jison doesn't export types import parser from './parser/sankey.jison'; import db from './sankeyDB.js'; -import styles from './styles.js'; import renderer from './sankeyRenderer.js'; import { prepareTextForParsing } from './sankeyUtils.js'; @@ -13,5 +12,4 @@ export const diagram: DiagramDefinition = { parser, db, renderer, - styles, }; diff --git a/packages/mermaid/src/diagrams/sankey/styles.js b/packages/mermaid/src/diagrams/sankey/styles.js deleted file mode 100644 index 54881fe6f..000000000 --- a/packages/mermaid/src/diagrams/sankey/styles.js +++ /dev/null @@ -1,5 +0,0 @@ -const getStyles = (options) => - ` -`; - -export default getStyles; diff --git a/packages/mermaid/src/styles.spec.ts b/packages/mermaid/src/styles.spec.ts index d1edf4111..51951f190 100644 --- a/packages/mermaid/src/styles.spec.ts +++ b/packages/mermaid/src/styles.spec.ts @@ -29,7 +29,6 @@ import state from './diagrams/state/styles.js'; import journey from './diagrams/user-journey/styles.js'; import timeline from './diagrams/timeline/styles.js'; import mindmap from './diagrams/mindmap/styles.js'; -import sankey from './diagrams/sankey/styles.js'; import themes from './themes/index.js'; async function checkValidStylisCSSStyleSheet(stylisString: string) { @@ -99,7 +98,6 @@ describe('styles', () => { sequence, state, timeline, - sankey, })) { test(`should return a valid style for diagram ${diagramId} and theme ${themeId}`, async () => { const { default: getStyles, addStylesForDiagram } = await import('./styles.js'); From 61e5dbeaa6900488ed7cbc5124663439c87f4d64 Mon Sep 17 00:00:00 2001 From: Tom PERRILLAT-COLLOMB Date: Sun, 25 Jun 2023 00:15:35 +0200 Subject: [PATCH 052/105] fix(class): keep members in namespace classes --- demos/classchart.html | 23 ++++++ .../mermaid/src/diagrams/class/classDb.ts | 3 +- .../src/diagrams/class/classDiagram.spec.ts | 45 ++++++++++ .../src/diagrams/class/classRenderer-v2.ts | 82 ++++++++++--------- .../mermaid/src/diagrams/class/classTypes.ts | 1 + 5 files changed, 112 insertions(+), 42 deletions(-) diff --git a/demos/classchart.html b/demos/classchart.html index b20dda2a3..508bb1066 100644 --- a/demos/classchart.html +++ b/demos/classchart.html @@ -154,6 +154,29 @@
    +
    +    classDiagram
    +      A1 --> B1
    +      namespace A {
    +        class A1 {
    +          +foo : string
    +        }
    +        class A2 {
    +          +bar : int
    +        }
    +      }
    +      namespace B {
    +        class B1 {
    +          +foo : bool
    +        }
    +        class B2 {
    +          +bar : float
    +        }
    +      }
    +      A2 --> B2
    +    
    +
    + +``` \ No newline at end of file diff --git a/run b/run index ecc6bb02b..88af16418 100755 --- a/run +++ b/run @@ -24,6 +24,8 @@ $RUN --service-ports mermaid sh -c "cd packages/mermaid/src/docs && npx pnpm pre ;; help) + +# Alignment of help message must be as it is, it will be nice looking when printed usage=$( cat <' - # git diff --name-only develop | xargs run pnpm prettier --write + # git diff --name-only develop | xargs run pnpm prettier --write $name pnpm test # Run unit tests $name pnpm vitest # Run watcher for unit tests $name pnpm e2e # Run integration tests From 9177350a390e2cc06910580e42a0afd1fbe15887 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Thu, 29 Jun 2023 20:16:25 +0300 Subject: [PATCH 086/105] Fixed double quotes, docs, demo and added more examples to run script --- demos/sankey.html | 50 +++++++++++++++---- .../src/diagrams/sankey/parser/energy.csv | 2 +- .../src/diagrams/sankey/parser/sankey.jison | 4 +- packages/mermaid/src/docs/syntax/sankey.md | 2 +- run | 17 ++++--- 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/demos/sankey.html b/demos/sankey.html index 72ca84e44..22e34cad3 100644 --- a/demos/sankey.html +++ b/demos/sankey.html @@ -18,20 +18,48 @@

    Energy flow

           sankey-beta
    -      
    -      Agricultural 'waste',Bio-conversion,124.729
    +
    +      %% There are leading and trailing spaces, do not crop
    +          Agricultural 'waste',Bio-conversion,124.729   
    +      %% line with a comment
    +
    +      %% Normal line
           Bio-conversion,Liquid,0.597
    +
    +      %% Line with unquoted sankey keyword
    +      sankey,target,10
    +
    +      %% Quoted sankey keyword
    +      "sankey",target,10
    +
    +      %% Another normal line
           Bio-conversion,Losses,26.862
    -      Bio-conversion,Solid,280.322
    +
    +      %% Line with integer amount
    +      Bio-conversion,Solid,280
    +
    +      %% Some blank lines in the middle of CSV
    +
    +
    +      %% Another normal line
           Bio-conversion,Gas,81.144
    -      Biofuel imports,Liquid,35
    -      Biomass imports,Solid,35
    +
    +      %% Quoted line
    +      "Biofuel imports",Liquid,35
    +
    +      %% Quoted line with escaped quotes inside
    +      """Biomass imports""",Solid,35
    +
    +      %% Lines containing commas inside
    +      %% Quoted and unquoted values should be equal in terms of graph
    +      "District heating","Heating and cooling, commercial",22.505
    +      District heating,"Heating and cooling, homes",46.184
    +
    +      %% A bunch of lines, normal CSV
           Coal imports,Coal,11.606
           Coal reserves,Coal,63.965
           Coal,Solid,75.571
           District heating,Industry,10.639
    -      District heating,Heating and cooling - commercial,22.505
    -      District heating,Heating and cooling - homes,46.184
           Electricity grid,Over generation / exports,104.453
           Electricity grid,Heating and cooling - homes,113.726
           Electricity grid,H2 conversion,27.14
    @@ -85,8 +113,12 @@
           Thermal generation,District heating,79.329
           Tidal,Electricity grid,9.452
           UK land based bioenergy,Bio-conversion,182.01
    -      Wave,Electricity grid,19.013
    -      Wind,Electricity grid,289.366
    +      """Wave""",Electricity grid,19.013
    +      """Wind""",Electricity grid,289.366
    +
    +      %% lines at the end, do not remove
    +
    +
         
    -``` \ No newline at end of file + +``` From 36df7260cc73e53e28d06768dfd8e2c2bebf8d39 Mon Sep 17 00:00:00 2001 From: nirname Date: Thu, 29 Jun 2023 17:32:39 +0000 Subject: [PATCH 089/105] Update docs --- docs/syntax/sankey.md | 464 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 docs/syntax/sankey.md diff --git a/docs/syntax/sankey.md b/docs/syntax/sankey.md new file mode 100644 index 000000000..2eaf9b8cc --- /dev/null +++ b/docs/syntax/sankey.md @@ -0,0 +1,464 @@ +> **Warning** +> +> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. +> +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/sankey.md](../../packages/mermaid/src/docs/syntax/sankey.md). + +# Sankey diagrams + +> A sankey diagram is a visualization used to depict a flow from one set of values to another. + +::: warning +This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future. +::: + +The things being connected are called nodes and the connections are called links. + +## Example + +This example taken from [observable](https://observablehq.com/@d3/sankey/2?collection=@d3/d3-sankey). It may be rendered a little bit differently, though, in terms of size and colors. + +```mermaid-example +sankey-beta + +Agricultural 'waste',Bio-conversion,124.729 +Bio-conversion,Liquid,0.597 +Bio-conversion,Losses,26.862 +Bio-conversion,Solid,280.322 +Bio-conversion,Gas,81.144 +Biofuel imports,Liquid,35 +Biomass imports,Solid,35 +Coal imports,Coal,11.606 +Coal reserves,Coal,63.965 +Coal,Solid,75.571 +District heating,Industry,10.639 +District heating,Heating and cooling - commercial,22.505 +District heating,Heating and cooling - homes,46.184 +Electricity grid,Over generation / exports,104.453 +Electricity grid,Heating and cooling - homes,113.726 +Electricity grid,H2 conversion,27.14 +Electricity grid,Industry,342.165 +Electricity grid,Road transport,37.797 +Electricity grid,Agriculture,4.412 +Electricity grid,Heating and cooling - commercial,40.858 +Electricity grid,Losses,56.691 +Electricity grid,Rail transport,7.863 +Electricity grid,Lighting & appliances - commercial,90.008 +Electricity grid,Lighting & appliances - homes,93.494 +Gas imports,Ngas,40.719 +Gas reserves,Ngas,82.233 +Gas,Heating and cooling - commercial,0.129 +Gas,Losses,1.401 +Gas,Thermal generation,151.891 +Gas,Agriculture,2.096 +Gas,Industry,48.58 +Geothermal,Electricity grid,7.013 +H2 conversion,H2,20.897 +H2 conversion,Losses,6.242 +H2,Road transport,20.897 +Hydro,Electricity grid,6.995 +Liquid,Industry,121.066 +Liquid,International shipping,128.69 +Liquid,Road transport,135.835 +Liquid,Domestic aviation,14.458 +Liquid,International aviation,206.267 +Liquid,Agriculture,3.64 +Liquid,National navigation,33.218 +Liquid,Rail transport,4.413 +Marine algae,Bio-conversion,4.375 +Ngas,Gas,122.952 +Nuclear,Thermal generation,839.978 +Oil imports,Oil,504.287 +Oil reserves,Oil,107.703 +Oil,Liquid,611.99 +Other waste,Solid,56.587 +Other waste,Bio-conversion,77.81 +Pumped heat,Heating and cooling - homes,193.026 +Pumped heat,Heating and cooling - commercial,70.672 +Solar PV,Electricity grid,59.901 +Solar Thermal,Heating and cooling - homes,19.263 +Solar,Solar Thermal,19.263 +Solar,Solar PV,59.901 +Solid,Agriculture,0.882 +Solid,Thermal generation,400.12 +Solid,Industry,46.477 +Thermal generation,Electricity grid,525.531 +Thermal generation,Losses,787.129 +Thermal generation,District heating,79.329 +Tidal,Electricity grid,9.452 +UK land based bioenergy,Bio-conversion,182.01 +Wave,Electricity grid,19.013 +Wind,Electricity grid,289.366 +``` + +```mermaid +sankey-beta + +Agricultural 'waste',Bio-conversion,124.729 +Bio-conversion,Liquid,0.597 +Bio-conversion,Losses,26.862 +Bio-conversion,Solid,280.322 +Bio-conversion,Gas,81.144 +Biofuel imports,Liquid,35 +Biomass imports,Solid,35 +Coal imports,Coal,11.606 +Coal reserves,Coal,63.965 +Coal,Solid,75.571 +District heating,Industry,10.639 +District heating,Heating and cooling - commercial,22.505 +District heating,Heating and cooling - homes,46.184 +Electricity grid,Over generation / exports,104.453 +Electricity grid,Heating and cooling - homes,113.726 +Electricity grid,H2 conversion,27.14 +Electricity grid,Industry,342.165 +Electricity grid,Road transport,37.797 +Electricity grid,Agriculture,4.412 +Electricity grid,Heating and cooling - commercial,40.858 +Electricity grid,Losses,56.691 +Electricity grid,Rail transport,7.863 +Electricity grid,Lighting & appliances - commercial,90.008 +Electricity grid,Lighting & appliances - homes,93.494 +Gas imports,Ngas,40.719 +Gas reserves,Ngas,82.233 +Gas,Heating and cooling - commercial,0.129 +Gas,Losses,1.401 +Gas,Thermal generation,151.891 +Gas,Agriculture,2.096 +Gas,Industry,48.58 +Geothermal,Electricity grid,7.013 +H2 conversion,H2,20.897 +H2 conversion,Losses,6.242 +H2,Road transport,20.897 +Hydro,Electricity grid,6.995 +Liquid,Industry,121.066 +Liquid,International shipping,128.69 +Liquid,Road transport,135.835 +Liquid,Domestic aviation,14.458 +Liquid,International aviation,206.267 +Liquid,Agriculture,3.64 +Liquid,National navigation,33.218 +Liquid,Rail transport,4.413 +Marine algae,Bio-conversion,4.375 +Ngas,Gas,122.952 +Nuclear,Thermal generation,839.978 +Oil imports,Oil,504.287 +Oil reserves,Oil,107.703 +Oil,Liquid,611.99 +Other waste,Solid,56.587 +Other waste,Bio-conversion,77.81 +Pumped heat,Heating and cooling - homes,193.026 +Pumped heat,Heating and cooling - commercial,70.672 +Solar PV,Electricity grid,59.901 +Solar Thermal,Heating and cooling - homes,19.263 +Solar,Solar Thermal,19.263 +Solar,Solar PV,59.901 +Solid,Agriculture,0.882 +Solid,Thermal generation,400.12 +Solid,Industry,46.477 +Thermal generation,Electricity grid,525.531 +Thermal generation,Losses,787.129 +Thermal generation,District heating,79.329 +Tidal,Electricity grid,9.452 +UK land based bioenergy,Bio-conversion,182.01 +Wave,Electricity grid,19.013 +Wind,Electricity grid,289.366 +``` + +::: details + +```mermaid-example +sankey-beta + +Agricultural 'waste',Bio-conversion,124.729 +Bio-conversion,Liquid,0.597 +Bio-conversion,Losses,26.862 +Bio-conversion,Solid,280.322 +Bio-conversion,Gas,81.144 +Biofuel imports,Liquid,35 +Biomass imports,Solid,35 +Coal imports,Coal,11.606 +Coal reserves,Coal,63.965 +Coal,Solid,75.571 +District heating,Industry,10.639 +District heating,Heating and cooling - commercial,22.505 +District heating,Heating and cooling - homes,46.184 +Electricity grid,Over generation / exports,104.453 +Electricity grid,Heating and cooling - homes,113.726 +Electricity grid,H2 conversion,27.14 +Electricity grid,Industry,342.165 +Electricity grid,Road transport,37.797 +Electricity grid,Agriculture,4.412 +Electricity grid,Heating and cooling - commercial,40.858 +Electricity grid,Losses,56.691 +Electricity grid,Rail transport,7.863 +Electricity grid,Lighting & appliances - commercial,90.008 +Electricity grid,Lighting & appliances - homes,93.494 +Gas imports,Ngas,40.719 +Gas reserves,Ngas,82.233 +Gas,Heating and cooling - commercial,0.129 +Gas,Losses,1.401 +Gas,Thermal generation,151.891 +Gas,Agriculture,2.096 +Gas,Industry,48.58 +Geothermal,Electricity grid,7.013 +H2 conversion,H2,20.897 +H2 conversion,Losses,6.242 +H2,Road transport,20.897 +Hydro,Electricity grid,6.995 +Liquid,Industry,121.066 +Liquid,International shipping,128.69 +Liquid,Road transport,135.835 +Liquid,Domestic aviation,14.458 +Liquid,International aviation,206.267 +Liquid,Agriculture,3.64 +Liquid,National navigation,33.218 +Liquid,Rail transport,4.413 +Marine algae,Bio-conversion,4.375 +Ngas,Gas,122.952 +Nuclear,Thermal generation,839.978 +Oil imports,Oil,504.287 +Oil reserves,Oil,107.703 +Oil,Liquid,611.99 +Other waste,Solid,56.587 +Other waste,Bio-conversion,77.81 +Pumped heat,Heating and cooling - homes,193.026 +Pumped heat,Heating and cooling - commercial,70.672 +Solar PV,Electricity grid,59.901 +Solar Thermal,Heating and cooling - homes,19.263 +Solar,Solar Thermal,19.263 +Solar,Solar PV,59.901 +Solid,Agriculture,0.882 +Solid,Thermal generation,400.12 +Solid,Industry,46.477 +Thermal generation,Electricity grid,525.531 +Thermal generation,Losses,787.129 +Thermal generation,District heating,79.329 +Tidal,Electricity grid,9.452 +UK land based bioenergy,Bio-conversion,182.01 +Wave,Electricity grid,19.013 +Wind,Electricity grid,289.366 +``` + +```mermaid +sankey-beta + +Agricultural 'waste',Bio-conversion,124.729 +Bio-conversion,Liquid,0.597 +Bio-conversion,Losses,26.862 +Bio-conversion,Solid,280.322 +Bio-conversion,Gas,81.144 +Biofuel imports,Liquid,35 +Biomass imports,Solid,35 +Coal imports,Coal,11.606 +Coal reserves,Coal,63.965 +Coal,Solid,75.571 +District heating,Industry,10.639 +District heating,Heating and cooling - commercial,22.505 +District heating,Heating and cooling - homes,46.184 +Electricity grid,Over generation / exports,104.453 +Electricity grid,Heating and cooling - homes,113.726 +Electricity grid,H2 conversion,27.14 +Electricity grid,Industry,342.165 +Electricity grid,Road transport,37.797 +Electricity grid,Agriculture,4.412 +Electricity grid,Heating and cooling - commercial,40.858 +Electricity grid,Losses,56.691 +Electricity grid,Rail transport,7.863 +Electricity grid,Lighting & appliances - commercial,90.008 +Electricity grid,Lighting & appliances - homes,93.494 +Gas imports,Ngas,40.719 +Gas reserves,Ngas,82.233 +Gas,Heating and cooling - commercial,0.129 +Gas,Losses,1.401 +Gas,Thermal generation,151.891 +Gas,Agriculture,2.096 +Gas,Industry,48.58 +Geothermal,Electricity grid,7.013 +H2 conversion,H2,20.897 +H2 conversion,Losses,6.242 +H2,Road transport,20.897 +Hydro,Electricity grid,6.995 +Liquid,Industry,121.066 +Liquid,International shipping,128.69 +Liquid,Road transport,135.835 +Liquid,Domestic aviation,14.458 +Liquid,International aviation,206.267 +Liquid,Agriculture,3.64 +Liquid,National navigation,33.218 +Liquid,Rail transport,4.413 +Marine algae,Bio-conversion,4.375 +Ngas,Gas,122.952 +Nuclear,Thermal generation,839.978 +Oil imports,Oil,504.287 +Oil reserves,Oil,107.703 +Oil,Liquid,611.99 +Other waste,Solid,56.587 +Other waste,Bio-conversion,77.81 +Pumped heat,Heating and cooling - homes,193.026 +Pumped heat,Heating and cooling - commercial,70.672 +Solar PV,Electricity grid,59.901 +Solar Thermal,Heating and cooling - homes,19.263 +Solar,Solar Thermal,19.263 +Solar,Solar PV,59.901 +Solid,Agriculture,0.882 +Solid,Thermal generation,400.12 +Solid,Industry,46.477 +Thermal generation,Electricity grid,525.531 +Thermal generation,Losses,787.129 +Thermal generation,District heating,79.329 +Tidal,Electricity grid,9.452 +UK land based bioenergy,Bio-conversion,182.01 +Wave,Electricity grid,19.013 +Wind,Electricity grid,289.366 +``` + +::: + +## Syntax + +The idea behind syntax is that a user types `sankey-beta` keyword first, then pastes raw CSV below and get the result. + +It is implements standard as [described here](https://www.ietf.org/rfc/rfc4180.txt) with subtle **differences**: + +- CSV must contain **3 columns only** +- It is **allowed** to have **empty lines** without comma separators for visual purposes + +### Empty lines + +CSV does not support empty lines (without comma delimeters) by default. But you can add them if needed. + +```mermaid-example +sankey-beta + +Bio-conversion,Liquid,0.597 +Bio-conversion,Losses,26.862 + +Bio-conversion,Solid,280.322 +Bio-conversion,Gas,81.144 +``` + +```mermaid +sankey-beta + +Bio-conversion,Liquid,0.597 +Bio-conversion,Losses,26.862 + +Bio-conversion,Solid,280.322 +Bio-conversion,Gas,81.144 +``` + +```mermaid-example +sankey-beta + +Bio-conversion,Liquid,0.597 +Bio-conversion,Losses,26.862 + +Bio-conversion,Solid,280.322 +Bio-conversion,Gas,81.144 +``` + +```mermaid +sankey-beta + +Bio-conversion,Liquid,0.597 +Bio-conversion,Losses,26.862 + +Bio-conversion,Solid,280.322 +Bio-conversion,Gas,81.144 +``` + +### Commas + +If you need to have a comma, wrap it in double quotes: + +```mermaid-example +sankey-beta + +Pumped heat,"Heating and cooling, homes",193.026 +Pumped heat,"Heating and cooling, commercial",70.672 +``` + +```mermaid +sankey-beta + +Pumped heat,"Heating and cooling, homes",193.026 +Pumped heat,"Heating and cooling, commercial",70.672 +``` + +```mermaid-example +sankey-beta + +Pumped heat,"Heating and cooling, homes",193.026 +Pumped heat,"Heating and cooling, commercial",70.672 +``` + +```mermaid +sankey-beta + +Pumped heat,"Heating and cooling, homes",193.026 +Pumped heat,"Heating and cooling, commercial",70.672 +``` + +### Double quotes + +If you need to have double quote, put a pair of them: + +```mermaid-example +sankey-beta + +Pumped heat,"Heating and cooling ""homes""",193.026 +Pumped heat,"Heating and cooling, ""commercial""",70.672 +``` + +```mermaid +sankey-beta + +Pumped heat,"Heating and cooling ""homes""",193.026 +Pumped heat,"Heating and cooling, ""commercial""",70.672 +``` + +```mermaid-example +sankey-beta + +Pumped heat,"Heating and cooling ""homes""",193.026 +Pumped heat,"Heating and cooling, ""commercial""",70.672 +``` + +```mermaid +sankey-beta + +Pumped heat,"Heating and cooling ""homes""",193.026 +Pumped heat,"Heating and cooling, ""commercial""",70.672 +``` + +## Configuration + +### Coloring and Alignment + +You can change graph layout by setting `nodeAlignment` to: + +- `justify` +- `center` +- `left` +- `right` + +And adjust coloring of the links by setting `linkColor` to one of those: + +- `source` +- `target` +- `gradient` +- hex code of color, like `#a1a1a1` + +```html + +``` From 163a4b819b3402f17babffa2c73e3eadb1ba5ef8 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Thu, 29 Jun 2023 21:00:41 +0300 Subject: [PATCH 090/105] Trigger build in GA From a1dfc3dac994525fd06b6452759c24c3e5e0dc5f Mon Sep 17 00:00:00 2001 From: Pascal Knecht Date: Fri, 30 Jun 2023 11:59:40 +0200 Subject: [PATCH 091/105] Fix Typo --- packages/mermaid/src/docs/syntax/stateDiagram.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/docs/syntax/stateDiagram.md b/packages/mermaid/src/docs/syntax/stateDiagram.md index 248146993..f35796b13 100644 --- a/packages/mermaid/src/docs/syntax/stateDiagram.md +++ b/packages/mermaid/src/docs/syntax/stateDiagram.md @@ -304,7 +304,7 @@ where - the second _property_ is `color` and its _value_ is `white` - the third _property_ is `font-weight` and its _value_ is `bold` - the fourth _property_ is `stroke-width` and its _value_ is `2px` -- the fifth _property_ is `stroke` and its _value_ is `yello` +- the fifth _property_ is `stroke` and its _value_ is `yellow` ### Apply classDef styles to states From 453b337e6e0e6f02357293764c27c29c236fe848 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Fri, 30 Jun 2023 13:10:22 +0200 Subject: [PATCH 092/105] Updated mermaid version to 10.2.4 --- package.json | 2 +- packages/mermaid/package.json | 2 +- pnpm-lock.yaml | 143 +--------------------------------- 3 files changed, 6 insertions(+), 141 deletions(-) diff --git a/package.json b/package.json index e6bd516a9..738d1796a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mermaid-monorepo", "private": true, - "version": "10.2.3", + "version": "10.2.4", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", "packageManager": "pnpm@8.6.5", diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 8e7e9696d..033359ee0 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -1,6 +1,6 @@ { "name": "mermaid", - "version": "10.2.3", + "version": "10.2.4", "description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", "module": "./dist/mermaid.core.mjs", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a316dd7f..10daa4cb6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - importers: .: @@ -451,31 +447,6 @@ importers: specifier: ^7.0.0 version: 7.0.0 - packages/mermaid/src/vitepress: - dependencies: - '@vueuse/core': - specifier: ^10.1.0 - version: 10.1.0(vue@3.2.47) - vue: - specifier: ^3.2.47 - version: 3.2.47 - devDependencies: - pathe: - specifier: ^1.1.0 - version: 1.1.0 - unocss: - specifier: ^0.53.0 - version: 0.53.0(postcss@8.4.23)(vite@4.3.9) - unplugin-vue-components: - specifier: ^0.25.0 - version: 0.25.0(rollup@2.79.1)(vue@3.2.47) - vite: - specifier: ^4.3.3 - version: 4.3.9(@types/node@18.16.0) - vitepress: - specifier: 1.0.0-beta.2 - version: 1.0.0-beta.2(@algolia/client-search@4.14.2)(@types/node@18.16.0)(search-insights@2.6.0) - tests/webpack: dependencies: '@mermaid-js/mermaid-example-diagram': @@ -4685,17 +4656,6 @@ packages: - vite dev: true - /@unocss/astro@0.53.0(vite@4.3.9): - resolution: {integrity: sha512-8bR7ysIMZEOpcjd/cVmogcABSFDYPjUqMnbflv44p1A2/deemo9CIkpRARoq/96NQuzWJsKhKodcQodExZcqiA==} - dependencies: - '@unocss/core': 0.53.0 - '@unocss/reset': 0.53.0 - '@unocss/vite': 0.53.0(vite@4.3.9) - transitivePeerDependencies: - - rollup - - vite - dev: true - /@unocss/cli@0.53.0(rollup@2.79.1): resolution: {integrity: sha512-9WNBHy8m8tMqwcp7mUhebRUBvHQfbx01CMe5cAFLmUYtJULM+8IjJxqERkaAZyyoOXf1TNO2v1dFAmCwhMRCLQ==} engines: {node: '>=14'} @@ -4874,26 +4834,6 @@ packages: - rollup dev: true - /@unocss/vite@0.53.0(vite@4.3.9): - resolution: {integrity: sha512-JoZhKVNruRjfySMVg/zNJbLEn/NTXj29Wf0SN4++xnGKrSapkPzYC46psL5bm5N5v4SHdpepTCoonC3FWCY6Fw==} - peerDependencies: - vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 - dependencies: - '@ampproject/remapping': 2.2.1 - '@rollup/pluginutils': 5.0.2(rollup@2.79.1) - '@unocss/config': 0.53.0 - '@unocss/core': 0.53.0 - '@unocss/inspector': 0.53.0 - '@unocss/scope': 0.53.0 - '@unocss/transformer-directives': 0.53.0 - chokidar: 3.5.3 - fast-glob: 3.2.12 - magic-string: 0.30.0 - vite: 4.3.9(@types/node@18.16.0) - transitivePeerDependencies: - - rollup - dev: true - /@vite-pwa/vitepress@0.2.0(vite-plugin-pwa@0.16.0): resolution: {integrity: sha512-dVQVaP6NB9woCFe4UASUqRp7uwBQJOVXlJlqK4krqXcbb3NuXIXIWOnU7HLpJnHqZj5U/81gKtLN6gs5gJBwiQ==} peerDependencies: @@ -14636,42 +14576,6 @@ packages: - vite dev: true - /unocss@0.53.0(postcss@8.4.23)(vite@4.3.9): - resolution: {integrity: sha512-kY4h5ERiDYlSnL2X+hbDfh+uaF7QNouy7j51GOTUr3Q0aaWehaNd05b15SjHrab559dEC0mYfrSEdh/DnCK1cw==} - engines: {node: '>=14'} - peerDependencies: - '@unocss/webpack': 0.53.0 - peerDependenciesMeta: - '@unocss/webpack': - optional: true - dependencies: - '@unocss/astro': 0.53.0(vite@4.3.9) - '@unocss/cli': 0.53.0(rollup@2.79.1) - '@unocss/core': 0.53.0 - '@unocss/extractor-arbitrary-variants': 0.53.0 - '@unocss/postcss': 0.53.0(postcss@8.4.23) - '@unocss/preset-attributify': 0.53.0 - '@unocss/preset-icons': 0.53.0 - '@unocss/preset-mini': 0.53.0 - '@unocss/preset-tagify': 0.53.0 - '@unocss/preset-typography': 0.53.0 - '@unocss/preset-uno': 0.53.0 - '@unocss/preset-web-fonts': 0.53.0 - '@unocss/preset-wind': 0.53.0 - '@unocss/reset': 0.53.0 - '@unocss/transformer-attributify-jsx': 0.53.0 - '@unocss/transformer-attributify-jsx-babel': 0.53.0 - '@unocss/transformer-compile-class': 0.53.0 - '@unocss/transformer-directives': 0.53.0 - '@unocss/transformer-variant-group': 0.53.0 - '@unocss/vite': 0.53.0(vite@4.3.9) - transitivePeerDependencies: - - postcss - - rollup - - supports-color - - vite - dev: true - /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -15041,49 +14945,6 @@ packages: - terser dev: true - /vitepress@1.0.0-beta.2(@algolia/client-search@4.14.2)(@types/node@18.16.0)(search-insights@2.6.0): - resolution: {integrity: sha512-DBXYjtYbm3W1IPPJ2TiCaK/XK+o/2XmL2+jslOGKm+txcbmG0kbeB+vadC5tCUZA9NdA+9Ywj3M4548c7t/SDg==} - hasBin: true - dependencies: - '@docsearch/css': 3.5.1 - '@docsearch/js': 3.5.1(@algolia/client-search@4.14.2)(search-insights@2.6.0) - '@vitejs/plugin-vue': 4.2.3(vite@4.3.9)(vue@3.3.4) - '@vue/devtools-api': 6.5.0 - '@vueuse/core': 10.1.2(vue@3.3.4) - '@vueuse/integrations': 10.1.2(focus-trap@7.4.3)(vue@3.3.4) - body-scroll-lock: 4.0.0-beta.0 - focus-trap: 7.4.3 - mark.js: 8.11.1 - minisearch: 6.1.0 - shiki: 0.14.2 - vite: 4.3.9(@types/node@18.16.0) - vue: 3.3.4 - transitivePeerDependencies: - - '@algolia/client-search' - - '@types/node' - - '@types/react' - - '@vue/composition-api' - - async-validator - - axios - - change-case - - drauu - - fuse.js - - idb-keyval - - jwt-decode - - less - - nprogress - - qrcode - - react - - react-dom - - sass - - search-insights - - sortablejs - - stylus - - sugarss - - terser - - universal-cookie - dev: true - /vitepress@1.0.0-beta.3(@algolia/client-search@4.14.2)(@types/node@18.16.0)(search-insights@2.6.0): resolution: {integrity: sha512-GR5Pvr/o343NN1M4Na1shhDYZRrQbjmLq7WE0lla0H8iDPAsHE8agTHLWfu3FWx+3q2KA29sv16+0O9RQKGjlA==} hasBin: true @@ -16045,3 +15906,7 @@ packages: /zwitch@2.0.2: resolution: {integrity: sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==} dev: true + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false From 959a773b6409f67ab2958a4b73b2ac541ad59495 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sat, 1 Jul 2023 00:28:37 +0530 Subject: [PATCH 093/105] Add threshold --- codecov.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/codecov.yaml b/codecov.yaml index b268d6680..91ae0771b 100644 --- a/codecov.yaml +++ b/codecov.yaml @@ -4,3 +4,9 @@ comment: require_changes: false # if true: only post the comment if coverage changes require_base: no # [yes :: must have a base report to post] require_head: yes # [yes :: must have a head report to post] + +coverage: + status: + project: + default: + threshold: 1% From 64f5d344657ae0ac3bdf62e45616243b99fb9341 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sat, 1 Jul 2023 00:30:27 +0530 Subject: [PATCH 094/105] Fix docs --- docs/syntax/stateDiagram.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/syntax/stateDiagram.md b/docs/syntax/stateDiagram.md index 807d6149a..4c28c82b3 100644 --- a/docs/syntax/stateDiagram.md +++ b/docs/syntax/stateDiagram.md @@ -487,7 +487,7 @@ where - the second _property_ is `color` and its _value_ is `white` - the third _property_ is `font-weight` and its _value_ is `bold` - the fourth _property_ is `stroke-width` and its _value_ is `2px` -- the fifth _property_ is `stroke` and its _value_ is `yello` +- the fifth _property_ is `stroke` and its _value_ is `yellow` ### Apply classDef styles to states From b90c8402cb617380c773df0d270a919dda0b6ee8 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sat, 1 Jul 2023 00:38:10 +0530 Subject: [PATCH 095/105] Set default branch in codecov --- codecov.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codecov.yaml b/codecov.yaml index 91ae0771b..d33925afc 100644 --- a/codecov.yaml +++ b/codecov.yaml @@ -1,3 +1,6 @@ +codecov: + branch: develop + comment: layout: 'reach, diff, flags, files' behavior: default From ae4d81cdfdff748408bfd04d1eb382f8698d402a Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sat, 1 Jul 2023 00:39:01 +0530 Subject: [PATCH 096/105] move codecov --- codecov.yaml => .github/codecov.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename codecov.yaml => .github/codecov.yaml (100%) diff --git a/codecov.yaml b/.github/codecov.yaml similarity index 100% rename from codecov.yaml rename to .github/codecov.yaml From a62719826a3a125e005be5e2749906275c84cb64 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sat, 1 Jul 2023 16:17:25 +0300 Subject: [PATCH 097/105] Docker+Cypress, better run, removed unused syntax Added cypress container (from their official image), may be reconsidered later to choose our own The only minor problem is node JS version mistmatch package.json 18.16.0 docker-compose mermaid 18.16.1 docker-compose cypress 18.16.0 Host option in cypress docker container must be removed in favor of possible configuration option. http://localhost:9000 are currently hard-coded, that is bad Updated ./run script with better documentation and added some styles too it as well Started sankey.spec.js as an example --- cypress/integration/rendering/sankey.spec.js | 12 ++ docker-compose.yml | 15 +++ .../diagrams/sankey/parser/sankey-arrow.jison | 105 ------------------ .../sankey/parser/sankey-arrow.spec.js | 101 ----------------- run | 59 ++++++---- 5 files changed, 65 insertions(+), 227 deletions(-) delete mode 100644 packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.jison delete mode 100644 packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js diff --git a/cypress/integration/rendering/sankey.spec.js b/cypress/integration/rendering/sankey.spec.js index eb25edf0f..f411f6819 100644 --- a/cypress/integration/rendering/sankey.spec.js +++ b/cypress/integration/rendering/sankey.spec.js @@ -11,4 +11,16 @@ describe('Sankey Diagram', () => { {} ); }); + + describe('Changes link color', function () { + beforeAll(() => { + let conf = { + sankey: { + linkColor: 'source', + } + }; + + mermaidAPI.initialize(conf); + }); + }); }); diff --git a/docker-compose.yml b/docker-compose.yml index 49fe0f3e8..8a60d829b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,21 @@ services: ports: - 9000:9000 - 3333:3333 + cypress: + image: cypress/base:18.16.0 + stdin_open: true + tty: true + working_dir: /mermaid + mem_limit: '2G' + environment: + - NODE_OPTIONS=--max_old_space_size=2048 + volumes: + - ./:/mermaid + - root_cache:/root/.cache + - root_local:/root/.local + - root_npm:/root/.npm + network_mode: host + volumes: root_cache: root_local: diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.jison b/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.jison deleted file mode 100644 index 181953d95..000000000 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.jison +++ /dev/null @@ -1,105 +0,0 @@ -/** mermaid */ -%lex -TOKEN \w+ -NUM \d+(.\d+)? - -%options case-insensitive -%options easy_keword_rules - -%s link_value - -%x attributes -%x attr_value -%x string - -%% -//-------------------------------------------------------------- -// skip all whitespace EXCEPT newlines, but not within a string -//-------------------------------------------------------------- - -[^\S\r\n]+ {} - -//-------------- -// basic tokens -//-------------- - -(<>|[\n;])+ { return 'EOS'; } // end of statement is semicolon ; new line \n or end of file -"sankey-beta" { return 'SANKEY'; } -{TOKEN} { return 'NODE_ID'; } -{NUM} { return 'AMOUNT'; } -"->" { - if(this.topState()!=='link_value') this.pushState('link_value'); - else this.popState(); - return 'ARROW'; - } -//------------ -// attributes -//------------ - -"[" { this.pushState('attributes'); return 'OPEN_ATTRIBUTES'; } -"]" { this.popState(); return 'CLOSE_ATTRIBUTES'; } -{TOKEN} { return 'ATTRIBUTE'; } -\= { this.pushState('attr_value'); return 'EQUAL'; } -{TOKEN} { this.popState(); return 'VALUE'; } - -//------------ -// strings -//------------ - -\" { this.pushState('string'); return 'OPEN_STRING'; } -(?!\\)\" { - if(this.topState()==='string') this.popState(); - if(this.topState()==='attr_value') this.popState(); - return 'CLOSE_STRING'; - } -([^"\\]|\\\"|\\\\)+ { return 'STRING'; } - -/lex - -%start start -%left ARROW - -%% // language grammar - -start - : EOS SANKEY document - | SANKEY document - ; - -document - : line document - | - ; - -line - : node optional_attributes EOS - | stream optional_attributes EOS - | EOS - ; - -optional_attributes: OPEN_ATTRIBUTES attributes CLOSE_ATTRIBUTES | ; - -attributes: attribute attributes | ; -attribute: ATTRIBUTE EQUAL value | ATTRIBUTE; - -value: VALUE | OPEN_STRING STRING CLOSE_STRING; - -stream - : node\[source] ARROW amount ARROW tail\[target] { - $$=$source; - yy.addLink($source, $target, $amount); - } - ; - -tail - : stream { $$ = $stream } - | node { $$ = $node; } - ; - -amount: AMOUNT { $$=parseFloat($AMOUNT); }; - -node - : NODE_ID { $$ = yy.findOrCreateNode($NODE_ID); } - | OPEN_STRING STRING\[node_label] CLOSE_STRING { $$ = yy.findOrCreateNode($node_label); } - ; - diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js b/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js deleted file mode 100644 index a180b9a28..000000000 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js +++ /dev/null @@ -1,101 +0,0 @@ -// @ts-ignore: jison doesn't export types -import sankey from './sankey-arrow.jison'; -import db from '../sankeyDB.js'; - -describe('sankey-beta diagram', function () { - describe('when parsing an info graph it', function () { - beforeEach(function () { - sankey.parser.yy = db; - sankey.parser.yy.clear(); - }); - - it('recognizes its type', () => { - const str = `sankey-beta`; - - sankey.parser.parse(str); - }); - - it('recognizes one flow', () => { - const str = ` - sankey-beta - node_a -> 30 -> node_b -> 20 -> node_c - `; - - sankey.parser.parse(str); - }); - - it('recognizes multiple flows', () => { - const str = ` - sankey-beta - node_a -> 30 -> node_b -> 12 -> node_e - node_c -> 30 -> node_d -> 12 -> node_e - node_c -> 40 -> node_e -> 12 -> node_q - `; - - sankey.parser.parse(str); - }); - - it('parses node as a string', () => { - const str = ` - sankey-beta - "node a" -> 30 -> "node b" -> 12 -> "node e" - "node c" -> 30 -> "node d" -> 12 -> "node e" - "node c" -> 40 -> "node e" -> 12 -> "node q" - `; - - sankey.parser.parse(str); - }); - - describe('while attributes parsing', () => { - it('recognized node and attribute ids starting with numbers', () => { - const str = ` - sankey-beta - 1st -> 200 -> 2nd -> 180 -> 3rd; - `; - - sankey.parser.parse(str); - }); - - it('parses different quotless variations', () => { - const str = ` - sankey-beta - node[] - - node[attr=1] - node_a -> 30 -> node_b - node[attrWithoutValue] - node[attr = 3] - node[attr1 = 23413 attr2=1234] - node[x1dfqowie attr1 = 23413 attr2] - `; - - sankey.parser.parse(str); - }); - - it('parses strings as values', () => { - const str = ` - sankey-beta - node[title="hello, how are you?"] - node[title="hello, mister \\"sankey-beta\\", backslash for you \\\\"] - `; - - sankey.parser.parse(str); - }); - - it('parses real example', () => { - const str = ` - sankey-beta - - "Agricultural 'waste'" -> 124.729 -> "Bio-conversion" - "Bio-conversion" -> 0.597 -> "Liquid" - "Bio-conversion" -> 26.862 -> "Losses" - "Bio-conversion" -> 280.322 -> "Solid" - "Bio-conversion" -> 81.144 -> "Gas" - "Biofuel imports" -> 35 -> "Liquid" - `; - - sankey.parser.parse(str); - }); - }); - }); -}); diff --git a/run b/run index 3509be7a7..55a0a849e 100755 --- a/run +++ b/run @@ -1,6 +1,13 @@ #!/bin/bash RUN="docker-compose run --rm" +ansi() { echo -e "\e[${1}m${*:2}\e[0m"; } +bold() { ansi 1 "$@"; } +# italic() { ansi 3 "$@"; } +underline() { ansi 4 "$@"; } +# strikethrough() { ansi 9 "$@"; } +# red() { ansi 31 "$@"; } + name=$(basename $0) command=$1 args=${@:2} @@ -12,7 +19,7 @@ $RUN mermaid sh -c "npx $args" ;; pnpm) -$RUN mermaid sh -c "npx $command $args" +$RUN mermaid sh -c "npx pnpm $args" ;; dev) @@ -23,43 +30,53 @@ docs:dev) $RUN --service-ports mermaid sh -c "cd packages/mermaid/src/docs && npx pnpm prefetch && npx vitepress --port 3333 --host" ;; +cypress) +$RUN cypress npm run cypress $args +;; + help) # Alignment of help message must be as it is, it will be nice looking when printed usage=$( cat <', e.g.: - # 'git diff --name-only develop | xargs run pnpm prettier --write' -$name pnpm test # Run unit tests -$name pnpm vitest # Run watcher for unit tests -$name pnpm e2e # Run integration tests +$(bold ./$name pnpm add --filter mermaid) $(underline package) + Add package to mermaid + +$(bold git diff --name-only develop \| xargs ./$name pnpm prettier --write) + Prettify everything you added so far + +$(bold ./$name cypress run -- --spec cypress/integration/rendering/)$(underline test.ts) + Run specific test in cypress\n + EOF ) From 4570f824f7e6fb37d35ee0667f9c14d7ce870914 Mon Sep 17 00:00:00 2001 From: Ibrahim Wassouf Date: Sat, 1 Jul 2023 15:45:48 -0300 Subject: [PATCH 098/105] Fix relative link to theme variables list --- docs/config/theming.md | 6 +++--- packages/mermaid/src/docs/config/theming.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/config/theming.md b/docs/config/theming.md index 580afb488..63271a39b 100644 --- a/docs/config/theming.md +++ b/docs/config/theming.md @@ -73,9 +73,9 @@ To make a custom theme, modify `themeVariables` via `init`. You will need to use the [base](#available-themes) theme as it is the only modifiable theme. -| Parameter | Description | Type | Properties | -| -------------- | ------------------------------------ | ------ | --------------------------------------------------------------------------------------------------- | -| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables-reference-table)) | +| Parameter | Description | Type | Properties | +| -------------- | ------------------------------------ | ------ | ----------------------------------------------------------------------------------- | +| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables)) | Example of modifying `themeVariables` using the `init` directive: diff --git a/packages/mermaid/src/docs/config/theming.md b/packages/mermaid/src/docs/config/theming.md index 0e4571d15..0e0853283 100644 --- a/packages/mermaid/src/docs/config/theming.md +++ b/packages/mermaid/src/docs/config/theming.md @@ -55,9 +55,9 @@ To make a custom theme, modify `themeVariables` via `init`. You will need to use the [base](#available-themes) theme as it is the only modifiable theme. -| Parameter | Description | Type | Properties | -| -------------- | ------------------------------------ | ------ | --------------------------------------------------------------------------------------------------- | -| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables-reference-table)) | +| Parameter | Description | Type | Properties | +| -------------- | ------------------------------------ | ------ | ----------------------------------------------------------------------------------- | +| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables)) | Example of modifying `themeVariables` using the `init` directive: From 084b765e9f89abe7171768f9649f0485861fbeee Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 2 Jul 2023 01:10:06 +0300 Subject: [PATCH 099/105] Added tests for colors and fully setup cypress in Docker --- cypress/integration/rendering/sankey.spec.js | 26 ------ cypress/integration/rendering/sankey.spec.ts | 85 +++++++++++++++++++ demos/sankey.html | 48 ++--------- docker-compose.yml | 9 +- .../src/diagrams/sankey/sankeyRenderer.ts | 5 +- run | 9 +- 6 files changed, 106 insertions(+), 76 deletions(-) delete mode 100644 cypress/integration/rendering/sankey.spec.js create mode 100644 cypress/integration/rendering/sankey.spec.ts diff --git a/cypress/integration/rendering/sankey.spec.js b/cypress/integration/rendering/sankey.spec.js deleted file mode 100644 index f411f6819..000000000 --- a/cypress/integration/rendering/sankey.spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import { imgSnapshotTest } from '../../helpers/util.js'; - -describe('Sankey Diagram', () => { - it('should render a simple example', () => { - imgSnapshotTest( - ` - sankey-beta - - a,b,10 - `, - {} - ); - }); - - describe('Changes link color', function () { - beforeAll(() => { - let conf = { - sankey: { - linkColor: 'source', - } - }; - - mermaidAPI.initialize(conf); - }); - }); -}); diff --git a/cypress/integration/rendering/sankey.spec.ts b/cypress/integration/rendering/sankey.spec.ts new file mode 100644 index 000000000..de10338e8 --- /dev/null +++ b/cypress/integration/rendering/sankey.spec.ts @@ -0,0 +1,85 @@ +import { imgSnapshotTest, renderGraph } from '../../helpers/util.js'; + +describe('Sankey Diagram', () => { + it('should render a simple example', () => { + imgSnapshotTest( + ` + sankey-beta + + sourceNode,targetNode,10 + `, + {} + ); + }); + + describe('when given a linkColor', function () { + it('links should be the same color as source node', () => { + renderGraph( + ` + sankey-beta + + sourceNode,targetNode,10 + `, + { + sankey: { linkColor: 'source' }, + } + ); + + cy.get('.link path').then((link) => { + cy.get('.node[id="node-1"] rect').should(node => + expect(link.attr('stroke')).to.equal(node.attr('fill')) + ); + }); + }); + + it('should change link color to hex code', () => { + renderGraph( + ` + sankey-beta + a,b,10 + `, + { + sankey: { linkColor: '#636465' }, + } + ); + + cy.get('.link path').should((link) => { + expect(link.attr('stroke')).to.equal('#636465'); + }); + }); + + it('links should be the same color as target node', () => { + renderGraph( + ` + sankey-beta + sourceNode,targetNode,10 + `, + { + sankey: { linkColor: 'target' }, + } + ); + + cy.get('.link path').then((link) => { + cy.get('.node[id="node-2"] rect').should(node => + expect(link.attr('stroke')).to.equal(node.attr('fill')) + ); + }); + }); + + it('links must be gradient', () => { + renderGraph( + ` + sankey-beta + sourceNode,targetNode,10 + `, + { + sankey: { linkColor: 'gradient' }, + } + ); + + cy.get('.link path').should((link) => { + expect(link.attr('stroke')).to.equal('url(#linearGradient-3)'); + }); + }); + }); +}); diff --git a/demos/sankey.html b/demos/sankey.html index 22e34cad3..c8cdc1e7e 100644 --- a/demos/sankey.html +++ b/demos/sankey.html @@ -19,47 +19,19 @@
           sankey-beta
     
    -      %% There are leading and trailing spaces, do not crop
    -          Agricultural 'waste',Bio-conversion,124.729   
    -      %% line with a comment
    -
    -      %% Normal line
    +      Agricultural 'waste',Bio-conversion,124.729
           Bio-conversion,Liquid,0.597
    -
    -      %% Line with unquoted sankey keyword
    -      sankey,target,10
    -
    -      %% Quoted sankey keyword
    -      "sankey",target,10
    -
    -      %% Another normal line
           Bio-conversion,Losses,26.862
    -
    -      %% Line with integer amount
    -      Bio-conversion,Solid,280
    -
    -      %% Some blank lines in the middle of CSV
    -
    -
    -      %% Another normal line
    +      Bio-conversion,Solid,280.322
           Bio-conversion,Gas,81.144
    -
    -      %% Quoted line
    -      "Biofuel imports",Liquid,35
    -
    -      %% Quoted line with escaped quotes inside
    -      """Biomass imports""",Solid,35
    -
    -      %% Lines containing commas inside
    -      %% Quoted and unquoted values should be equal in terms of graph
    -      "District heating","Heating and cooling, commercial",22.505
    -      District heating,"Heating and cooling, homes",46.184
    -
    -      %% A bunch of lines, normal CSV
    +      Biofuel imports,Liquid,35
    +      Biomass imports,Solid,35
           Coal imports,Coal,11.606
           Coal reserves,Coal,63.965
           Coal,Solid,75.571
           District heating,Industry,10.639
    +      District heating,Heating and cooling - commercial,22.505
    +      District heating,Heating and cooling - homes,46.184
           Electricity grid,Over generation / exports,104.453
           Electricity grid,Heating and cooling - homes,113.726
           Electricity grid,H2 conversion,27.14
    @@ -113,12 +85,8 @@
           Thermal generation,District heating,79.329
           Tidal,Electricity grid,9.452
           UK land based bioenergy,Bio-conversion,182.01
    -      """Wave""",Electricity grid,19.013
    -      """Wind""",Electricity grid,289.366
    -
    -      %% lines at the end, do not remove
    -
    -
    +      Wave,Electricity grid,19.013
    +      Wind,Electricity grid,289.366
         
    From abb7bb23d623f1717b220b75fdd27d5d6d5e8d32 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 2 Jul 2023 02:34:22 +0300 Subject: [PATCH 101/105] More detailed sankey docs --- cypress/integration/rendering/sankey.spec.ts | 2 +- packages/mermaid/src/docs/syntax/sankey.md | 76 +++++++++++++------- 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/cypress/integration/rendering/sankey.spec.ts b/cypress/integration/rendering/sankey.spec.ts index ded23a045..e334b898b 100644 --- a/cypress/integration/rendering/sankey.spec.ts +++ b/cypress/integration/rendering/sankey.spec.ts @@ -128,7 +128,7 @@ describe('Sankey Diagram', () => { expect(node.attr('x')).to.equal('400'); }); }); - + it('should center nodes', function () { renderGraph(this.graph, { sankey: { nodeAlignment: 'center', width: 410, useMaxWidth: false }, diff --git a/packages/mermaid/src/docs/syntax/sankey.md b/packages/mermaid/src/docs/syntax/sankey.md index 97806d725..32f3663b9 100644 --- a/packages/mermaid/src/docs/syntax/sankey.md +++ b/packages/mermaid/src/docs/syntax/sankey.md @@ -166,32 +166,54 @@ Wind,Electricity grid,289.366 The idea behind syntax is that a user types `sankey-beta` keyword first, then pastes raw CSV below and get the result. -It is implements standard as [described here](https://www.ietf.org/rfc/rfc4180.txt) with subtle **differences**: +It implements CSV standard as [described here](https://www.ietf.org/rfc/rfc4180.txt) with subtle **differences**: - CSV must contain **3 columns only** - It is **allowed** to have **empty lines** without comma separators for visual purposes -### Empty lines +### Basic -CSV does not support empty lines (without comma delimeters) by default. But you can add them if needed. +It is implied that 3 columns inside CSV should represent `source`, `target` and `value` accordingly: + +```mermaid-example +sankey-beta + +%% source,target,value +Electricity grid,Over generation / exports,104.453 +Electricity grid,Heating and cooling - homes,113.726 +Electricity grid,H2 conversion,27.14 +``` + +```mermaid +sankey-beta + +%% source,target,value +Electricity grid,Over generation / exports,104.453 +Electricity grid,Heating and cooling - homes,113.726 +Electricity grid,H2 conversion,27.14 +``` + +### Empty Lines + +CSV does not support empty lines without comma delimeters by default. But you can add them if needed: ```mermaid-example sankey-beta -Bio-conversion,Liquid,0.597 Bio-conversion,Losses,26.862 Bio-conversion,Solid,280.322 + Bio-conversion,Gas,81.144 ``` ```mermaid sankey-beta -Bio-conversion,Liquid,0.597 Bio-conversion,Losses,26.862 Bio-conversion,Solid,280.322 + Bio-conversion,Gas,81.144 ``` @@ -213,41 +235,27 @@ Pumped heat,"Heating and cooling, homes",193.026 Pumped heat,"Heating and cooling, commercial",70.672 ``` -### Double quotes +### Double Quotes -If you need to have double quote, put a pair of them: +If you need to have double quote, put a pair of them inside quoted string: ```mermaid-example sankey-beta -Pumped heat,"Heating and cooling ""homes""",193.026 +Pumped heat,"Heating and cooling, ""homes""",193.026 Pumped heat,"Heating and cooling, ""commercial""",70.672 ``` ```mermaid sankey-beta -Pumped heat,"Heating and cooling ""homes""",193.026 +Pumped heat,"Heating and cooling, ""homes""",193.026 Pumped heat,"Heating and cooling, ""commercial""",70.672 ``` ## Configuration -### Coloring and Alignment - -You can change graph layout by setting `nodeAlignment` to: - -- `justify` -- `center` -- `left` -- `right` - -And adjust coloring of the links by setting `linkColor` to one of those: - -- `source` -- `target` -- `gradient` -- hex code of color, like `#a1a1a1` +You can customize link colors, node alignments and diagram dimensions. ```html ``` + +### Links Coloring + +You can adjust links' color by setting `linkColor` to one of those: + +- `source` - link will be of a source node color +- `target` - link will be of a target node color +- `gradient` - link color will be smoothly transient between source and target node colors +- hex code of color, like `#a1a1a1` + +### Node Alignment + +Graph layout can be changed by setting `nodeAlignment` to: + +- `justify` +- `center` +- `left` +- `right` From c327ea9f2194832760a00bdd1a0e72a64435cc8b Mon Sep 17 00:00:00 2001 From: nirname Date: Sat, 1 Jul 2023 23:38:02 +0000 Subject: [PATCH 102/105] Update docs --- docs/syntax/sankey.md | 102 ++++++++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 28 deletions(-) diff --git a/docs/syntax/sankey.md b/docs/syntax/sankey.md index 2eaf9b8cc..bfbfc3621 100644 --- a/docs/syntax/sankey.md +++ b/docs/syntax/sankey.md @@ -318,52 +318,92 @@ Wind,Electricity grid,289.366 The idea behind syntax is that a user types `sankey-beta` keyword first, then pastes raw CSV below and get the result. -It is implements standard as [described here](https://www.ietf.org/rfc/rfc4180.txt) with subtle **differences**: +It implements CSV standard as [described here](https://www.ietf.org/rfc/rfc4180.txt) with subtle **differences**: - CSV must contain **3 columns only** - It is **allowed** to have **empty lines** without comma separators for visual purposes -### Empty lines +### Basic -CSV does not support empty lines (without comma delimeters) by default. But you can add them if needed. +It is implied that 3 columns inside CSV should represent `source`, `target` and `value` accordingly: + +```mermaid-example +sankey-beta + +%% source,target,value +Electricity grid,Over generation / exports,104.453 +Electricity grid,Heating and cooling - homes,113.726 +Electricity grid,H2 conversion,27.14 +``` + +```mermaid +sankey-beta + +%% source,target,value +Electricity grid,Over generation / exports,104.453 +Electricity grid,Heating and cooling - homes,113.726 +Electricity grid,H2 conversion,27.14 +``` + +```mermaid-example +sankey-beta + +%% source,target,value +Electricity grid,Over generation / exports,104.453 +Electricity grid,Heating and cooling - homes,113.726 +Electricity grid,H2 conversion,27.14 +``` + +```mermaid +sankey-beta + +%% source,target,value +Electricity grid,Over generation / exports,104.453 +Electricity grid,Heating and cooling - homes,113.726 +Electricity grid,H2 conversion,27.14 +``` + +### Empty Lines + +CSV does not support empty lines without comma delimeters by default. But you can add them if needed: ```mermaid-example sankey-beta -Bio-conversion,Liquid,0.597 Bio-conversion,Losses,26.862 Bio-conversion,Solid,280.322 + Bio-conversion,Gas,81.144 ``` ```mermaid sankey-beta -Bio-conversion,Liquid,0.597 Bio-conversion,Losses,26.862 Bio-conversion,Solid,280.322 + Bio-conversion,Gas,81.144 ``` ```mermaid-example sankey-beta -Bio-conversion,Liquid,0.597 Bio-conversion,Losses,26.862 Bio-conversion,Solid,280.322 + Bio-conversion,Gas,81.144 ``` ```mermaid sankey-beta -Bio-conversion,Liquid,0.597 Bio-conversion,Losses,26.862 Bio-conversion,Solid,280.322 + Bio-conversion,Gas,81.144 ``` @@ -399,55 +439,41 @@ Pumped heat,"Heating and cooling, homes",193.026 Pumped heat,"Heating and cooling, commercial",70.672 ``` -### Double quotes +### Double Quotes -If you need to have double quote, put a pair of them: +If you need to have double quote, put a pair of them inside quoted string: ```mermaid-example sankey-beta -Pumped heat,"Heating and cooling ""homes""",193.026 +Pumped heat,"Heating and cooling, ""homes""",193.026 Pumped heat,"Heating and cooling, ""commercial""",70.672 ``` ```mermaid sankey-beta -Pumped heat,"Heating and cooling ""homes""",193.026 +Pumped heat,"Heating and cooling, ""homes""",193.026 Pumped heat,"Heating and cooling, ""commercial""",70.672 ``` ```mermaid-example sankey-beta -Pumped heat,"Heating and cooling ""homes""",193.026 +Pumped heat,"Heating and cooling, ""homes""",193.026 Pumped heat,"Heating and cooling, ""commercial""",70.672 ``` ```mermaid sankey-beta -Pumped heat,"Heating and cooling ""homes""",193.026 +Pumped heat,"Heating and cooling, ""homes""",193.026 Pumped heat,"Heating and cooling, ""commercial""",70.672 ``` ## Configuration -### Coloring and Alignment - -You can change graph layout by setting `nodeAlignment` to: - -- `justify` -- `center` -- `left` -- `right` - -And adjust coloring of the links by setting `linkColor` to one of those: - -- `source` -- `target` -- `gradient` -- hex code of color, like `#a1a1a1` +You can customize link colors, node alignments and diagram dimensions. ```html ``` + +### Links Coloring + +You can adjust links' color by setting `linkColor` to one of those: + +- `source` - link will be of a source node color +- `target` - link will be of a target node color +- `gradient` - link color will be smoothly transient between source and target node colors +- hex code of color, like `#a1a1a1` + +### Node Alignment + +Graph layout can be changed by setting `nodeAlignment` to: + +- `justify` +- `center` +- `left` +- `right` From 4a95589b5aaa370793f7462856f5d4fe973c8814 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sun, 2 Jul 2023 10:03:31 +0530 Subject: [PATCH 103/105] Add `merge_group` to CI triggers --- .github/workflows/build-docs.yml | 1 + .github/workflows/build.yml | 1 + .github/workflows/checks.yml | 3 ++- .github/workflows/e2e.yml | 5 ++++- .github/workflows/lint.yml | 3 ++- .github/workflows/test.yml | 2 +- 6 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index f3e440fce..b9e542cf8 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -2,6 +2,7 @@ name: Build Vitepress docs on: pull_request: + merge_group: permissions: contents: read diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2a70b5901..49be6364a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,7 @@ name: Build on: push: {} + merge_group: pull_request: types: - opened diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 396ff4e6e..df761325c 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -1,5 +1,6 @@ on: - push: {} + push: + merge_group: pull_request: types: - opened diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 64637c5fb..c27425beb 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,6 +1,9 @@ name: E2E -on: [push, pull_request] +on: + push: + pull_request: + merge_group: permissions: contents: read diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d4bf4afe8..53b7f484d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,7 +1,8 @@ name: Lint on: - push: {} + push: + merge_group: pull_request: types: - opened diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6bae6b71f..2d8c96165 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,6 @@ name: Unit Tests -on: [push, pull_request] +on: [push, pull_request, merge_group] permissions: contents: read From 78377daf2f383c268a390f186935c28b7bc155fa Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sun, 2 Jul 2023 10:14:35 +0530 Subject: [PATCH 104/105] Rename workflow jobs --- .github/workflows/build-docs.yml | 3 +-- .github/workflows/build.yml | 2 +- .github/workflows/check-readme-in-sync.yml | 2 +- .github/workflows/checks.yml | 4 ++-- .github/workflows/e2e-applitools.yml | 2 +- .github/workflows/e2e.yml | 2 +- .github/workflows/publish-docs.yml | 6 +++--- .github/workflows/release-preview-publish.yml | 2 +- .github/workflows/test.yml | 8 +------- .github/workflows/update-browserlist.yml | 2 +- 10 files changed, 13 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index b9e542cf8..b25ff89cc 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -8,8 +8,7 @@ permissions: contents: read jobs: - # Build job - build: + build-docs: runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 49be6364a..eeb557ebb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ permissions: contents: read jobs: - build: + build-mermaid: runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/check-readme-in-sync.yml b/.github/workflows/check-readme-in-sync.yml index 13912e5b9..5a8ca319b 100644 --- a/.github/workflows/check-readme-in-sync.yml +++ b/.github/workflows/check-readme-in-sync.yml @@ -14,7 +14,7 @@ permissions: contents: read jobs: - check: + check-readme: runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index df761325c..9f9f316c4 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -7,10 +7,10 @@ on: - synchronize - ready_for_review -name: Static analysis +name: Static analysis on Test files jobs: - test: + check-tests: runs-on: ubuntu-latest name: check tests if: github.repository_owner == 'mermaid-js' diff --git a/.github/workflows/e2e-applitools.yml b/.github/workflows/e2e-applitools.yml index 92f2f80b1..5b1943142 100644 --- a/.github/workflows/e2e-applitools.yml +++ b/.github/workflows/e2e-applitools.yml @@ -19,7 +19,7 @@ env: USE_APPLI: ${{ secrets.APPLITOOLS_API_KEY && 'true' || '' }} jobs: - test: + e2e-applitools: runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index c27425beb..9bcb1898c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -9,7 +9,7 @@ permissions: contents: read jobs: - build: + e2e: runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index b556c1b1d..f63e58750 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -19,7 +19,7 @@ concurrency: jobs: # Build job - build: + build-docs: runs-on: ubuntu-latest steps: - name: Checkout @@ -48,11 +48,11 @@ jobs: path: packages/mermaid/src/vitepress/.vitepress/dist # Deployment job - deploy: + deploy-docs: environment: name: github-pages runs-on: ubuntu-latest - needs: build + needs: build-docs steps: - name: Deploy to GitHub Pages id: deployment diff --git a/.github/workflows/release-preview-publish.yml b/.github/workflows/release-preview-publish.yml index 5f4936ab6..221e3836e 100644 --- a/.github/workflows/release-preview-publish.yml +++ b/.github/workflows/release-preview-publish.yml @@ -6,7 +6,7 @@ on: - 'release/**' jobs: - publish: + publish-preview: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2d8c96165..e25502ee4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ permissions: contents: read jobs: - build: + unit-test: runs-on: ubuntu-latest strategy: matrix: @@ -49,9 +49,3 @@ jobs: name: mermaid-codecov fail_ci_if_error: true verbose: true - # Coveralls is throwing 500. Disabled for now. - # - name: Upload Coverage to Coveralls - # uses: coverallsapp/github-action@v2 - # with: - # github-token: ${{ secrets.GITHUB_TOKEN }} - # flag-name: unit diff --git a/.github/workflows/update-browserlist.yml b/.github/workflows/update-browserlist.yml index 4155ec988..813a400b3 100644 --- a/.github/workflows/update-browserlist.yml +++ b/.github/workflows/update-browserlist.yml @@ -5,7 +5,7 @@ on: workflow_dispatch: jobs: - build: + update-browser-list: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From b55a9328f490ffa865858e8ce73607ec68f30710 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 2 Jul 2023 05:07:56 +0000 Subject: [PATCH 105/105] Update all patch dependencies --- packages/mermaid/package.json | 2 +- pnpm-lock.yaml | 160 ++-------------------------------- 2 files changed, 9 insertions(+), 153 deletions(-) diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 4ee118cef..62efec380 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -62,7 +62,7 @@ "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.10", "dayjs": "^1.11.7", - "dompurify": "3.0.3", + "dompurify": "3.0.4", "elkjs": "^0.8.2", "khroma": "^2.0.0", "lodash-es": "^4.17.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3967ce135..9d28e9e50 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + importers: .: @@ -218,8 +222,8 @@ importers: specifier: ^1.11.7 version: 1.11.7 dompurify: - specifier: 3.0.3 - version: 3.0.3 + specifier: 3.0.4 + version: 3.0.4 elkjs: specifier: ^0.8.2 version: 0.8.2 @@ -459,58 +463,6 @@ importers: specifier: ^7.0.0 version: 7.0.0 - packages/mermaid/src/vitepress: - dependencies: - '@vueuse/core': - specifier: ^10.1.0 - version: 10.1.0(vue@3.2.47) - jiti: - specifier: ^1.18.2 - version: 1.18.2 - vue: - specifier: ^3.2.47 - version: 3.2.47 - devDependencies: - '@iconify-json/carbon': - specifier: ^1.1.16 - version: 1.1.16 - '@unocss/reset': - specifier: ^0.53.0 - version: 0.53.0 - '@vite-pwa/vitepress': - specifier: ^0.2.0 - version: 0.2.0(vite-plugin-pwa@0.16.0) - '@vitejs/plugin-vue': - specifier: ^4.2.1 - version: 4.2.1(vite@4.3.9)(vue@3.2.47) - fast-glob: - specifier: ^3.2.12 - version: 3.2.12 - https-localhost: - specifier: ^4.7.1 - version: 4.7.1 - pathe: - specifier: ^1.1.0 - version: 1.1.0 - unocss: - specifier: ^0.53.0 - version: 0.53.0(postcss@8.4.23)(rollup@2.79.1)(vite@4.3.9) - unplugin-vue-components: - specifier: ^0.25.0 - version: 0.25.0(rollup@2.79.1)(vue@3.2.47) - vite: - specifier: ^4.3.3 - version: 4.3.9(@types/node@18.16.0) - vite-plugin-pwa: - specifier: ^0.16.0 - version: 0.16.0(vite@4.3.9)(workbox-build@7.0.0)(workbox-window@7.0.0) - vitepress: - specifier: 1.0.0-beta.3 - version: 1.0.0-beta.3(@algolia/client-search@4.14.2)(@types/node@18.16.0)(search-insights@2.6.0) - workbox-window: - specifier: ^7.0.0 - version: 7.0.0 - tests/webpack: dependencies: '@mermaid-js/mermaid-example-diagram': @@ -4733,17 +4685,6 @@ packages: - vite dev: true - /@unocss/astro@0.53.0(rollup@2.79.1)(vite@4.3.9): - resolution: {integrity: sha512-8bR7ysIMZEOpcjd/cVmogcABSFDYPjUqMnbflv44p1A2/deemo9CIkpRARoq/96NQuzWJsKhKodcQodExZcqiA==} - dependencies: - '@unocss/core': 0.53.0 - '@unocss/reset': 0.53.0 - '@unocss/vite': 0.53.0(rollup@2.79.1)(vite@4.3.9) - transitivePeerDependencies: - - rollup - - vite - dev: true - /@unocss/cli@0.53.0(rollup@2.79.1): resolution: {integrity: sha512-9WNBHy8m8tMqwcp7mUhebRUBvHQfbx01CMe5cAFLmUYtJULM+8IjJxqERkaAZyyoOXf1TNO2v1dFAmCwhMRCLQ==} engines: {node: '>=14'} @@ -4922,26 +4863,6 @@ packages: - rollup dev: true - /@unocss/vite@0.53.0(rollup@2.79.1)(vite@4.3.9): - resolution: {integrity: sha512-JoZhKVNruRjfySMVg/zNJbLEn/NTXj29Wf0SN4++xnGKrSapkPzYC46psL5bm5N5v4SHdpepTCoonC3FWCY6Fw==} - peerDependencies: - vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 - dependencies: - '@ampproject/remapping': 2.2.1 - '@rollup/pluginutils': 5.0.2(rollup@2.79.1) - '@unocss/config': 0.53.0 - '@unocss/core': 0.53.0 - '@unocss/inspector': 0.53.0 - '@unocss/scope': 0.53.0 - '@unocss/transformer-directives': 0.53.0 - chokidar: 3.5.3 - fast-glob: 3.2.12 - magic-string: 0.30.0 - vite: 4.3.9(@types/node@18.16.0) - transitivePeerDependencies: - - rollup - dev: true - /@vite-pwa/vitepress@0.2.0(vite-plugin-pwa@0.16.0): resolution: {integrity: sha512-dVQVaP6NB9woCFe4UASUqRp7uwBQJOVXlJlqK4krqXcbb3NuXIXIWOnU7HLpJnHqZj5U/81gKtLN6gs5gJBwiQ==} peerDependencies: @@ -4961,17 +4882,6 @@ packages: vue: 3.2.47 dev: true - /@vitejs/plugin-vue@4.2.1(vite@4.3.9)(vue@3.2.47): - resolution: {integrity: sha512-ZTZjzo7bmxTRTkb8GSTwkPOYDIP7pwuyV+RV53c9PYUouwcbkIZIvWvNWlX2b1dYZqtOv7D6iUAnJLVNGcLrSw==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.0.0 - vue: ^3.2.25 - dependencies: - vite: 4.3.9(@types/node@18.16.0) - vue: 3.2.47 - dev: true - /@vitejs/plugin-vue@4.2.3(vite@4.3.8)(vue@3.3.4): resolution: {integrity: sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -7847,8 +7757,8 @@ packages: domelementtype: 2.3.0 dev: true - /dompurify@3.0.3: - resolution: {integrity: sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ==} + /dompurify@3.0.4: + resolution: {integrity: sha512-ae0mA+Qiqp6C29pqZX3fQgK+F91+F7wobM/v8DRzDqJdZJELXiFUx4PP4pK/mzUS0xkiSEx3Ncd9gr69jg3YsQ==} dev: false /domutils@3.0.1: @@ -14722,42 +14632,6 @@ packages: - vite dev: true - /unocss@0.53.0(postcss@8.4.23)(rollup@2.79.1)(vite@4.3.9): - resolution: {integrity: sha512-kY4h5ERiDYlSnL2X+hbDfh+uaF7QNouy7j51GOTUr3Q0aaWehaNd05b15SjHrab559dEC0mYfrSEdh/DnCK1cw==} - engines: {node: '>=14'} - peerDependencies: - '@unocss/webpack': 0.53.0 - peerDependenciesMeta: - '@unocss/webpack': - optional: true - dependencies: - '@unocss/astro': 0.53.0(rollup@2.79.1)(vite@4.3.9) - '@unocss/cli': 0.53.0(rollup@2.79.1) - '@unocss/core': 0.53.0 - '@unocss/extractor-arbitrary-variants': 0.53.0 - '@unocss/postcss': 0.53.0(postcss@8.4.23) - '@unocss/preset-attributify': 0.53.0 - '@unocss/preset-icons': 0.53.0 - '@unocss/preset-mini': 0.53.0 - '@unocss/preset-tagify': 0.53.0 - '@unocss/preset-typography': 0.53.0 - '@unocss/preset-uno': 0.53.0 - '@unocss/preset-web-fonts': 0.53.0 - '@unocss/preset-wind': 0.53.0 - '@unocss/reset': 0.53.0 - '@unocss/transformer-attributify-jsx': 0.53.0 - '@unocss/transformer-attributify-jsx-babel': 0.53.0 - '@unocss/transformer-compile-class': 0.53.0 - '@unocss/transformer-directives': 0.53.0 - '@unocss/transformer-variant-group': 0.53.0 - '@unocss/vite': 0.53.0(rollup@2.79.1)(vite@4.3.9) - transitivePeerDependencies: - - postcss - - rollup - - supports-color - - vite - dev: true - /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -14982,24 +14856,6 @@ packages: - supports-color dev: true - /vite-plugin-pwa@0.16.0(vite@4.3.9)(workbox-build@7.0.0)(workbox-window@7.0.0): - resolution: {integrity: sha512-E+AQRzHxqNU4ZhEeR8X37/foZB+ezJEhXauE/mcf1UITY6k2Pa1dtlFl+BQu57fTdiVlWim5S0Qy44Yap93Dkg==} - engines: {node: '>=16.0.0'} - peerDependencies: - vite: ^3.1.0 || ^4.0.0 - workbox-build: ^7.0.0 - workbox-window: ^7.0.0 - dependencies: - debug: 4.3.4(supports-color@8.1.1) - fast-glob: 3.2.12 - pretty-bytes: 6.1.0 - vite: 4.3.9(@types/node@18.16.0) - workbox-build: 7.0.0 - workbox-window: 7.0.0 - transitivePeerDependencies: - - supports-color - dev: true - /vite@4.3.3(@types/node@18.16.0): resolution: {integrity: sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA==} engines: {node: ^14.18.0 || >=16.0.0}