From 4018fad4167227087f447de112c221770ff8168a Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 18 Jun 2023 01:32:45 +0300 Subject: [PATCH 01/82] 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 02/82] 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 03/82] 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 04/82] 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 05/82] 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 06/82] 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 07/82] 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 08/82] 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 09/82] 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 10/82] 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 11/82] 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 12/82] 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 13/82] 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 14/82] 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 15/82] 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 18/82] 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 19/82] 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 20/82] 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 21/82] 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 22/82] 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 24/82] 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 25/82] 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 28/82] 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 29/82] 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 30/82] 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 31/82] 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 32/82] 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 33/82] 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 34/82] 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 35/82] 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 36/82] 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 48/82] 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 49/82] 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 50/82] 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 2a22bae1f93b3595ba45452ba2d817576bef9a4b Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 25 Jun 2023 01:18:38 +0300 Subject: [PATCH 51/82] Update docs --- .../interfaces/mermaidAPI.ParseOptions.md | 2 +- .../interfaces/mermaidAPI.RenderResult.md | 4 ++-- docs/config/setup/modules/config.md | 22 +++++++++---------- docs/config/setup/modules/defaultConfig.md | 4 ++-- docs/config/setup/modules/mermaidAPI.md | 22 +++++++++---------- run | 10 +++++---- 6 files changed, 33 insertions(+), 31 deletions(-) diff --git a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md index 8ab259885..0875af71d 100644 --- a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md +++ b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md @@ -16,4 +16,4 @@ #### Defined in -[mermaidAPI.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L77) +mermaidAPI.ts:77 diff --git a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md index 527b46d09..ce4c7d52d 100644 --- a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md +++ b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md @@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. #### Defined in -[mermaidAPI.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L97) +mermaidAPI.ts:97 --- @@ -51,4 +51,4 @@ The svg code for the rendered graph. #### Defined in -[mermaidAPI.ts:87](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L87) +mermaidAPI.ts:87 diff --git a/docs/config/setup/modules/config.md b/docs/config/setup/modules/config.md index 8381dc8c7..c69958ea7 100644 --- a/docs/config/setup/modules/config.md +++ b/docs/config/setup/modules/config.md @@ -14,7 +14,7 @@ #### Defined in -[config.ts:7](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L7) +config.ts:7 ## Functions @@ -36,7 +36,7 @@ Pushes in a directive to the configuration #### Defined in -[config.ts:191](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L191) +config.ts:191 --- @@ -60,7 +60,7 @@ The currentConfig #### Defined in -[config.ts:137](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L137) +config.ts:137 --- @@ -84,7 +84,7 @@ The siteConfig #### Defined in -[config.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L96) +config.ts:96 --- @@ -118,7 +118,7 @@ The siteConfig #### Defined in -[config.ts:223](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L223) +config.ts:223 --- @@ -147,7 +147,7 @@ options in-place #### Defined in -[config.ts:152](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L152) +config.ts:152 --- @@ -167,7 +167,7 @@ options in-place #### Defined in -[config.ts:75](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L75) +config.ts:75 --- @@ -199,7 +199,7 @@ The currentConfig merged with the sanitized conf #### Defined in -[config.ts:113](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L113) +config.ts:113 --- @@ -232,7 +232,7 @@ The new siteConfig #### Defined in -[config.ts:61](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L61) +config.ts:61 --- @@ -253,7 +253,7 @@ The new siteConfig #### Defined in -[config.ts:14](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L14) +config.ts:14 --- @@ -273,4 +273,4 @@ The new siteConfig #### Defined in -[config.ts:79](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L79) +config.ts:79 diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index d95ec4e92..649e03db8 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -14,7 +14,7 @@ #### Defined in -[defaultConfig.ts:2293](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2293) +defaultConfig.ts:2298 --- @@ -53,4 +53,4 @@ A description of each option follows below. #### Defined in -[defaultConfig.ts:33](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L33) +defaultConfig.ts:33 diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index 591b6841a..aa3043713 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -25,7 +25,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) #### Defined in -[mermaidAPI.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L81) +mermaidAPI.ts:81 ## Variables @@ -96,7 +96,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:663](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L663) +mermaidAPI.ts:663 ## Functions @@ -127,7 +127,7 @@ Return the last node appended #### Defined in -[mermaidAPI.ts:308](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L308) +mermaidAPI.ts:308 --- @@ -153,7 +153,7 @@ the cleaned up svgCode #### Defined in -[mermaidAPI.ts:256](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L256) +mermaidAPI.ts:256 --- @@ -179,7 +179,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L185) +mermaidAPI.ts:185 --- @@ -202,7 +202,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:233](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L233) +mermaidAPI.ts:233 --- @@ -229,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169) +mermaidAPI.ts:169 --- @@ -249,7 +249,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155) +mermaidAPI.ts:155 --- @@ -269,7 +269,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L126) +mermaidAPI.ts:126 --- @@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code #### Defined in -[mermaidAPI.ts:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287) +mermaidAPI.ts:287 --- @@ -320,4 +320,4 @@ Remove any existing elements from the given document #### Defined in -[mermaidAPI.ts:358](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L358) +mermaidAPI.ts:358 diff --git a/run b/run index 6a9b0e5ce..6aee10dc2 100755 --- a/run +++ b/run @@ -1,16 +1,14 @@ #!/bin/bash RUN="docker-compose run --rm" -# UP="docker-compose up" name=$(basename $0) command=$1 args=${@:2} - case $command in sh) -$RUN mermaid sh $args +$RUN mermaid sh -c "npx $args" ;; i | install) @@ -35,7 +33,10 @@ $RUN mermaid sh -c "npx pnpm -w run lint:fix $args" dev) $RUN --service-ports mermaid sh -c "npx pnpm run dev" -# $UP --rm mermaid sh -c "npx pnpm run dev" +;; + +docs:dev) +$RUN --service-ports mermaid sh -c "npx pnpm --filter mermaid run docs:dev" ;; help) @@ -45,6 +46,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 +\033[1m$name docs:dev\033[0m # For docs contributions, open http://localhost:3333 $name add # Add package, 'run add d3-sankey' $name prettier # Prettify a file 'run prettier ' From 95ab598ea4451d0c01386d1bc1f9023c9133d54e Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 25 Jun 2023 01:31:53 +0300 Subject: [PATCH 52/82] Fix docs --- .../interfaces/mermaidAPI.ParseOptions.md | 2 +- .../interfaces/mermaidAPI.RenderResult.md | 4 ++-- docs/config/setup/modules/config.md | 22 +++++++++---------- docs/config/setup/modules/defaultConfig.md | 4 ++-- docs/config/setup/modules/mermaidAPI.md | 22 +++++++++---------- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md index 0875af71d..8ab259885 100644 --- a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md +++ b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md @@ -16,4 +16,4 @@ #### Defined in -mermaidAPI.ts:77 +[mermaidAPI.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L77) diff --git a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md index ce4c7d52d..527b46d09 100644 --- a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md +++ b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md @@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. #### Defined in -mermaidAPI.ts:97 +[mermaidAPI.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L97) --- @@ -51,4 +51,4 @@ The svg code for the rendered graph. #### Defined in -mermaidAPI.ts:87 +[mermaidAPI.ts:87](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L87) diff --git a/docs/config/setup/modules/config.md b/docs/config/setup/modules/config.md index c69958ea7..8381dc8c7 100644 --- a/docs/config/setup/modules/config.md +++ b/docs/config/setup/modules/config.md @@ -14,7 +14,7 @@ #### Defined in -config.ts:7 +[config.ts:7](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L7) ## Functions @@ -36,7 +36,7 @@ Pushes in a directive to the configuration #### Defined in -config.ts:191 +[config.ts:191](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L191) --- @@ -60,7 +60,7 @@ The currentConfig #### Defined in -config.ts:137 +[config.ts:137](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L137) --- @@ -84,7 +84,7 @@ The siteConfig #### Defined in -config.ts:96 +[config.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L96) --- @@ -118,7 +118,7 @@ config.ts:96 #### Defined in -config.ts:223 +[config.ts:223](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L223) --- @@ -147,7 +147,7 @@ options in-place #### Defined in -config.ts:152 +[config.ts:152](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L152) --- @@ -167,7 +167,7 @@ config.ts:152 #### Defined in -config.ts:75 +[config.ts:75](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L75) --- @@ -199,7 +199,7 @@ The currentConfig merged with the sanitized conf #### Defined in -config.ts:113 +[config.ts:113](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L113) --- @@ -232,7 +232,7 @@ The new siteConfig #### Defined in -config.ts:61 +[config.ts:61](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L61) --- @@ -253,7 +253,7 @@ config.ts:61 #### Defined in -config.ts:14 +[config.ts:14](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L14) --- @@ -273,4 +273,4 @@ config.ts:14 #### Defined in -config.ts:79 +[config.ts:79](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L79) diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index 649e03db8..d95ec4e92 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -14,7 +14,7 @@ #### Defined in -defaultConfig.ts:2298 +[defaultConfig.ts:2293](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2293) --- @@ -53,4 +53,4 @@ A description of each option follows below. #### Defined in -defaultConfig.ts:33 +[defaultConfig.ts:33](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L33) diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index aa3043713..591b6841a 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -25,7 +25,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) #### Defined in -mermaidAPI.ts:81 +[mermaidAPI.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L81) ## Variables @@ -96,7 +96,7 @@ mermaid.initialize(config); #### Defined in -mermaidAPI.ts:663 +[mermaidAPI.ts:663](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L663) ## Functions @@ -127,7 +127,7 @@ Return the last node appended #### Defined in -mermaidAPI.ts:308 +[mermaidAPI.ts:308](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L308) --- @@ -153,7 +153,7 @@ the cleaned up svgCode #### Defined in -mermaidAPI.ts:256 +[mermaidAPI.ts:256](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L256) --- @@ -179,7 +179,7 @@ the string with all the user styles #### Defined in -mermaidAPI.ts:185 +[mermaidAPI.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L185) --- @@ -202,7 +202,7 @@ mermaidAPI.ts:185 #### Defined in -mermaidAPI.ts:233 +[mermaidAPI.ts:233](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L233) --- @@ -229,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -mermaidAPI.ts:169 +[mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169) --- @@ -249,7 +249,7 @@ mermaidAPI.ts:169 #### Defined in -mermaidAPI.ts:155 +[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155) --- @@ -269,7 +269,7 @@ mermaidAPI.ts:155 #### Defined in -mermaidAPI.ts:126 +[mermaidAPI.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L126) --- @@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code #### Defined in -mermaidAPI.ts:287 +[mermaidAPI.ts:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287) --- @@ -320,4 +320,4 @@ Remove any existing elements from the given document #### Defined in -mermaidAPI.ts:358 +[mermaidAPI.ts:358](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L358) From c5b89d127d03bb50995e87abe3294e4358a7348c Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 25 Jun 2023 01:38:31 +0300 Subject: [PATCH 53/82] Update docs --- docs/config/setup/modules/defaultConfig.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index d95ec4e92..d74c9b2b1 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -14,7 +14,7 @@ #### Defined in -[defaultConfig.ts:2293](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2293) +[defaultConfig.ts:2298](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2298) --- From a38733346f01c443e46a2b81def9cf567aa325f8 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 25 Jun 2023 21:56:14 +0300 Subject: [PATCH 54/82] Removed unnecessary configs and extra code --- cypress/integration/rendering/sankey.spec.js | 1 - demos/sankey.html | 3 --- packages/mermaid/src/diagrams/sankey/sankeyDetector.ts | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/cypress/integration/rendering/sankey.spec.js b/cypress/integration/rendering/sankey.spec.js index 7a3ffa5db..eb25edf0f 100644 --- a/cypress/integration/rendering/sankey.spec.js +++ b/cypress/integration/rendering/sankey.spec.js @@ -10,6 +10,5 @@ describe('Sankey Diagram', () => { `, {} ); - cy.get('svg'); }); }); diff --git a/demos/sankey.html b/demos/sankey.html index 3dc7a2332..72ca84e44 100644 --- a/demos/sankey.html +++ b/demos/sankey.html @@ -95,9 +95,6 @@ theme: 'default', logLevel: 3, securityLevel: 'loose', - flowchart: { curve: 'basis' }, - gantt: { axisFormat: '%m/%d/%Y' }, - sequence: { actorMargin: 50 }, sankey: { title: 'Hey, this is Sankey-Beta', width: 1200, diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDetector.ts b/packages/mermaid/src/diagrams/sankey/sankeyDetector.ts index 1045d7021..73c4d1428 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDetector.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDetector.ts @@ -3,7 +3,7 @@ import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-a const id = 'sankey'; const detector: DiagramDetector = (txt) => { - return txt.match(/^\s*sankey-beta/) !== null; + return /^\s*sankey-beta/.test(txt); }; const loader = async () => { From b0825ebb35ed001ae86a996e7f622901fa3e850e Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Mon, 26 Jun 2023 18:37:41 +0300 Subject: [PATCH 55/82] Import at the top --- .../mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js | 1 - packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) 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 a6efe62c1..1fae77c15 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js @@ -1,7 +1,6 @@ import diagram from './sankey-arrow.jison'; import { parser } from './sankey-arrow.jison'; import db from '../sankeyDB.js'; -// import { fail } from 'assert'; describe('sankey-beta diagram', function () { // TODO - these examples should be put into ./parser/stateDiagram.spec.js diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts index 1df607b63..eef8ae3c6 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts @@ -5,6 +5,8 @@ import { parser } from './sankey.jison'; import db from '../sankeyDB.js'; import { cleanupComments } from '../../../diagram-api/comments.js'; import { prepareTextForParsing } from '../sankeyUtils.js'; +import * as fs from 'fs'; +import * as path from 'path'; describe('Sankey diagram', function () { describe('when parsing an info graph it', function () { @@ -15,8 +17,6 @@ describe('Sankey diagram', function () { }); it('parses csv', async () => { - const fs = await import('fs'); - const path = await import('path'); const csv = path.resolve(__dirname, './energy.csv'); const data = fs.readFileSync(csv, 'utf8'); const graphDefinition = prepareTextForParsing(cleanupComments('sankey-beta\n\n ' + data)); From 605e8d4a922b8c2620b6abe47a8d13154e8a7839 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Tue, 27 Jun 2023 14:11:06 +0300 Subject: [PATCH 56/82] Sankey refactoring --- packages/mermaid/src/config.type.ts | 8 +++ packages/mermaid/src/defaultConfig.ts | 5 +- .../mermaid/src/diagrams/sankey/sankeyDB.ts | 27 +++------- .../src/diagrams/sankey/sankeyRenderer.ts | 22 ++++---- run | 52 ++++++++----------- 5 files changed, 50 insertions(+), 64 deletions(-) diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 7e0b67eb5..4d40b33c8 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -418,10 +418,18 @@ export enum SankeyLinkColor { gradient = 'gradient', } +export enum SankeyNodeAlignment { + left = 'left', + right = 'right', + center = 'center', + justify = 'justify', +} + export interface SankeyDiagramConfig extends BaseDiagramConfig { width?: number; height?: number; linkColor?: SankeyLinkColor | string; + nodeAlignment?: SankeyNodeAlignment; } export interface FontConfig { diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts index 4ece25010..39dcd0721 100644 --- a/packages/mermaid/src/defaultConfig.ts +++ b/packages/mermaid/src/defaultConfig.ts @@ -1,5 +1,5 @@ import theme from './themes/index.js'; -import { MermaidConfig } from './config.type.js'; +import { MermaidConfig, SankeyLinkColor, SankeyNodeAlignment } from './config.type.js'; /** * **Configuration methods in Mermaid version 8.6.0 have been updated, to learn more[[click * here](8.6.0_docs.md)].** @@ -2273,7 +2273,8 @@ const config: Partial = { sankey: { width: 800, height: 400, - linkColor: 'gradient', + linkColor: SankeyLinkColor.gradient, + nodeAlignment: SankeyNodeAlignment.justify, }, fontSize: 16, }; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts index 807c07cd1..eda443871 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts @@ -17,22 +17,11 @@ import { let links: SankeyLink[] = []; let nodes: SankeyNode[] = []; let nodesMap: Record = {}; -let nodeAlign = 'justify'; - -const setNodeAlign = (alignment: string): void => { - const nodeAlignments: Set = new Set(['left', 'right', 'center', 'justify']); - if (nodeAlignments.has(alignment)) { - nodeAlign = alignment; - } -}; - -const getNodeAlign = (): string => nodeAlign; const clear = (): void => { links = []; nodes = []; nodesMap = {}; - nodeAlign = 'justify'; commonClear(); }; @@ -58,15 +47,13 @@ class SankeyNode { */ const findOrCreateNode = (ID: string): SankeyNode => { ID = common.sanitizeText(ID, configApi.getConfig()); - let node: SankeyNode; - if (nodesMap[ID] === undefined) { - node = new SankeyNode(ID); - nodesMap[ID] = node; - nodes.push(node); - } else { - node = nodesMap[ID]; + if (nodesMap[ID]) { + return nodesMap[ID]; } - return node; + + nodesMap[ID] = new SankeyNode(ID); + nodes.push(nodesMap[ID]); + return nodesMap[ID]; }; const getNodes = () => nodes; @@ -89,8 +76,6 @@ export default { getGraph, addLink, findOrCreateNode, - setNodeAlign, - getNodeAlign, getAccTitle, setAccTitle, getAccDescription, diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index dd31e775e..a480b4b3e 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -1,12 +1,9 @@ -// @ts-nocheck TODO: fix file import { Diagram } from '../../Diagram.js'; import * as configApi from '../../config.js'; import { select as d3select, - // @ts-ignore TODO: make proper import scaleOrdinal as d3scaleOrdinal, - // @ts-ignore TODO: make proper import schemeTableau10 as d3schemeTableau10, } from 'd3'; @@ -20,7 +17,7 @@ import { } from 'd3-sankey'; import { configureSvgSize } from '../../setupGraphViewbox.js'; import { Uid } from './sankeyUtils.js'; -import { SankeyLinkColor } from '../../config.type.js'; +import { SankeyLinkColor, SankeyNodeAlignment } from '../../config.type.js'; /** * Draws Sankey diagram. @@ -69,13 +66,14 @@ 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 alignmentsMap: Record, n: number) => number> = new Map([ + [SankeyNodeAlignment.left, d3SankeyLeft], + [SankeyNodeAlignment.right, d3SankeyRight], + [SankeyNodeAlignment.center, d3SankeyCenter], + [SankeyNodeAlignment.justify, d3SankeyJustify], + ]); + const nodeAlignment = alignmentsMap[conf?.nodeAlignment]; // Construct and configure a Sankey generator // That will be a function that calculates nodes and links dimensions @@ -85,7 +83,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb .nodeId((d: any) => d.id) // we use 'id' property to identify node .nodeWidth(nodeWidth) .nodePadding(10) - .nodeAlign(nodeAlign) + .nodeAlign(nodeAlignment) .extent([ [0, 0], [width, height], diff --git a/run b/run index 6aee10dc2..b5e589e6e 100755 --- a/run +++ b/run @@ -11,24 +11,8 @@ sh) $RUN mermaid sh -c "npx $args" ;; -i | install) -$RUN mermaid sh -c "npx pnpm install $args" -;; - -add) -$RUN mermaid sh -c "npx pnpm -w add $args" -;; - -test | vitest | e2e ) -$RUN mermaid sh -c "npx pnpm $command $args" -;; - -prettier) -$RUN mermaid sh -c "npx pnpm prettier --write $args" -;; - -lint) -$RUN mermaid sh -c "npx pnpm -w run lint:fix $args" +pnpm) +$RUN mermaid sh -c "npx $command $args" ;; dev) @@ -42,22 +26,32 @@ $RUN --service-ports mermaid sh -c "npx pnpm --filter mermaid run docs:dev" help) usage=$( cat <' -$name lint # Equvalent of pnpm -w run lint:fix -$name test # Run unit tests -$name vitest # Run watcher for unit tests -$name e2e # Run integration tests +\033[1m$name pnpm install\033[0m # Install packages +\033[1m$name dev\033[0m # Run dev server with examples, open http://localhost:9000 +\033[1m$name docs:dev\033[0m # For docs contributions, open http://localhost:3333 -$name sh # Open sh inside docker container for development +Commands: + +$name pnpm # Run any 'pnpm' command +$name dev # Run dev server with examples, open http://localhost:9000 +$name docs:dev # For docs contributions, open http://localhost:3333 + +$name sh # Open 'sh' inside docker container for development $name help # Show this help + +Examples of frequiently used commands: + +$name pnpm add # Add package, 'run add d3-sankey' +$name pnpm prettier # Prettify a file 'run prettier ' +$name pnpm test # Run unit tests +$name pnpm vitest # Run watcher for unit tests +$name pnpm e2e # Run integration tests +$name pnpm -w run lint:fix EOF ) From 616c5e6914c063af5d6c1db5696cc031ab44568c Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Tue, 27 Jun 2023 14:43:35 +0300 Subject: [PATCH 57/82] Move UID to separate file, refine run sh --- .../src/diagrams/sankey/sankeyRenderer.ts | 12 +++++---- .../src/diagrams/sankey/sankeyUtils.ts | 19 ------------- packages/mermaid/src/rendering-util/uid.ts | 18 +++++++++++++ run | 27 ++++++++++--------- 4 files changed, 39 insertions(+), 37 deletions(-) create mode 100644 packages/mermaid/src/rendering-util/uid.ts diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index a480b4b3e..6a4c5fdcf 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -16,7 +16,7 @@ import { sankeyJustify as d3SankeyJustify, } from 'd3-sankey'; import { configureSvgSize } from '../../setupGraphViewbox.js'; -import { Uid } from './sankeyUtils.js'; +import { Uid } from '../../rendering-util/uid.js'; import { SankeyLinkColor, SankeyNodeAlignment } from '../../config.type.js'; /** @@ -30,7 +30,7 @@ import { SankeyLinkColor, SankeyNodeAlignment } from '../../config.type.js'; export const draw = function (text: string, id: string, _version: string, diagObj: Diagram): void { // 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 @@ -66,14 +66,16 @@ export const draw = function (text: string, id: string, _version: string, diagOb // } // const graph = diagObj.db.getGraph(); - - const alignmentsMap: Record, n: number) => number> = new Map([ + const alignmentsMap: Map< + SankeyNodeAlignment, + (node: SankeyNode<{}, {}>, n: number) => number + > = new Map([ [SankeyNodeAlignment.left, d3SankeyLeft], [SankeyNodeAlignment.right, d3SankeyRight], [SankeyNodeAlignment.center, d3SankeyCenter], [SankeyNodeAlignment.justify, d3SankeyJustify], ]); - const nodeAlignment = alignmentsMap[conf?.nodeAlignment]; + const nodeAlignment = alignmentsMap.get(conf?.nodeAlignment || SankeyNodeAlignment.justify); // Construct and configure a Sankey generator // That will be a function that calculates nodes and links dimensions diff --git a/packages/mermaid/src/diagrams/sankey/sankeyUtils.ts b/packages/mermaid/src/diagrams/sankey/sankeyUtils.ts index 08e4c547f..45ecf21dd 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyUtils.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyUtils.ts @@ -6,22 +6,3 @@ export const prepareTextForParsing = (text: string): string => { return textToParse; }; - -export class Uid { - private static count = 0; - id: string; - href: string; - - public static next(name: string): Uid { - return new Uid(name + ++Uid.count); - } - - constructor(id: string) { - this.id = id; - this.href = `#${id}`; - } - - toString(): string { - return 'url(' + this.href + ')'; - } -} diff --git a/packages/mermaid/src/rendering-util/uid.ts b/packages/mermaid/src/rendering-util/uid.ts new file mode 100644 index 000000000..9f581faa7 --- /dev/null +++ b/packages/mermaid/src/rendering-util/uid.ts @@ -0,0 +1,18 @@ +export class Uid { + private static count = 0; + id: string; + href: string; + + public static next(name: string): Uid { + return new Uid(name + ++Uid.count); + } + + constructor(id: string) { + this.id = id; + this.href = `#${id}`; + } + + toString(): string { + return 'url(' + this.href + ')'; + } +} diff --git a/run b/run index b5e589e6e..18a544312 100755 --- a/run +++ b/run @@ -30,27 +30,28 @@ Run commands within docker containers. Development quick start guide: -\033[1m$name pnpm install\033[0m # Install packages -\033[1m$name dev\033[0m # Run dev server with examples, open http://localhost:9000 -\033[1m$name docs:dev\033[0m # For docs contributions, open http://localhost:3333 +\033[1m$name pnpm install\033[0m # Install packages +\033[1m$name dev\033[0m # Run dev server with examples, open http://localhost:9000 +\033[1m$name docs:dev\033[0m # For docs contributions, open http://localhost:3333 Commands: -$name pnpm # Run any 'pnpm' command -$name dev # Run dev server with examples, open http://localhost:9000 -$name docs:dev # For docs contributions, open http://localhost:3333 +$name pnpm # Run any 'pnpm' command +$name dev # Run dev server with examples, open http://localhost:9000 +$name docs:dev # For docs contributions, open http://localhost:3333 -$name sh # Open 'sh' inside docker container for development +$name sh # Open 'sh' inside docker container for development -$name help # Show this help +$name help # Show this help Examples of frequiently used commands: -$name pnpm add # Add package, 'run add d3-sankey' -$name pnpm prettier # Prettify a file 'run prettier ' -$name pnpm test # Run unit tests -$name pnpm vitest # Run watcher for unit tests -$name pnpm e2e # Run integration tests +$name pnpm add # Add package, 'run add d3-sankey' +$name pnpm prettier --write # Prettify a file 'run prettier ' + # 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 $name pnpm -w run lint:fix EOF ) From 32c85243172fd4162ae3ba1b0c036ac51e50eecf Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Tue, 27 Jun 2023 15:15:34 +0300 Subject: [PATCH 58/82] Put d3-sankey under mermaid package --- package.json | 1 - packages/mermaid/package.json | 3 + .../mermaid/src/diagrams/sankey/sankeyDB.ts | 3 - .../src/diagrams/sankey/sankeyRenderer.ts | 1 + pnpm-lock.yaml | 179 ++++++++++++++++-- 5 files changed, 169 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index f1ab3d4ec..b1a5b5c4b 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "coveralls": "^3.1.1", "cypress": "^12.10.0", "cypress-image-snapshot": "^4.0.1", - "d3-sankey": "^0.12.3", "esbuild": "^0.18.0", "eslint": "^8.39.0", "eslint-config-prettier": "^8.8.0", diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index d0c67ee58..97d658deb 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -53,10 +53,13 @@ }, "dependencies": { "@braintree/sanitize-url": "^6.0.2", + "@types/d3-scale": "^4.0.3", + "@types/d3-scale-chromatic": "^3.0.0", "cytoscape": "^3.23.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.1.0", "d3": "^7.4.0", + "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.10", "dayjs": "^1.11.7", "dompurify": "3.0.3", diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts index eda443871..913fcb410 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts @@ -42,9 +42,6 @@ class SankeyNode { constructor(public ID: string) {} } -/** - * @param ID - The id of the node - */ const findOrCreateNode = (ID: string): SankeyNode => { ID = common.sanitizeText(ID, configApi.getConfig()); if (nodesMap[ID]) { diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 6a4c5fdcf..83d61dec7 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { Diagram } from '../../Diagram.js'; import * as configApi from '../../config.js'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 219cecd96..4d91dd5de 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,9 +88,6 @@ 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 @@ -199,6 +196,12 @@ importers: '@braintree/sanitize-url': specifier: ^6.0.2 version: 6.0.2 + '@types/d3-scale': + specifier: ^4.0.3 + version: 4.0.3 + '@types/d3-scale-chromatic': + specifier: ^3.0.0 + version: 3.0.0 cytoscape: specifier: ^3.23.0 version: 3.23.0 @@ -211,6 +214,9 @@ importers: d3: specifier: ^7.4.0 version: 7.8.2 + d3-sankey: + specifier: ^0.12.3 + version: 0.12.3 dagre-d3-es: specifier: 7.0.10 version: 7.0.10 @@ -456,6 +462,58 @@ importers: specifier: ^6.5.4 version: 6.5.4 + 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@6.5.4) + 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) + workbox-window: + specifier: ^6.5.4 + version: 6.5.4 + tests/webpack: dependencies: '@mermaid-js/mermaid-example-diagram': @@ -4017,13 +4075,11 @@ packages: /@types/d3-scale-chromatic@3.0.0: resolution: {integrity: sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==} - dev: true - /@types/d3-scale@4.0.2: - resolution: {integrity: sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==} + /@types/d3-scale@4.0.3: + resolution: {integrity: sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==} dependencies: '@types/d3-time': 3.0.0 - dev: true /@types/d3-selection@3.0.5: resolution: {integrity: sha512-xCB0z3Hi8eFIqyja3vW8iV01+OHGYR2di/+e+AiOcXIOrY82lcvWW8Ke1DYE/EUVMsBl4Db9RppSBS3X1U6J0w==} @@ -4047,7 +4103,6 @@ packages: /@types/d3-time@3.0.0: resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==} - dev: true /@types/d3-timer@3.0.0: resolution: {integrity: sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==} @@ -4090,7 +4145,7 @@ packages: '@types/d3-polygon': 3.0.0 '@types/d3-quadtree': 3.0.2 '@types/d3-random': 3.0.1 - '@types/d3-scale': 4.0.2 + '@types/d3-scale': 4.0.3 '@types/d3-scale-chromatic': 3.0.0 '@types/d3-selection': 3.0.5 '@types/d3-shape': 3.1.0 @@ -4681,6 +4736,17 @@ 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'} @@ -4859,6 +4925,26 @@ 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: @@ -4878,6 +4964,17 @@ 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} @@ -7166,7 +7263,7 @@ packages: resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} dependencies: internmap: 1.0.1 - dev: true + dev: false /d3-array@3.2.0: resolution: {integrity: sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==} @@ -7287,7 +7384,7 @@ packages: /d3-path@1.0.9: resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} - dev: true + dev: false /d3-path@3.0.1: resolution: {integrity: sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==} @@ -7314,7 +7411,7 @@ packages: dependencies: d3-array: 2.12.1 d3-shape: 1.3.7 - dev: true + dev: false /d3-scale-chromatic@3.0.0: resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==} @@ -7344,7 +7441,7 @@ packages: resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} dependencies: d3-path: 1.0.9 - dev: true + dev: false /d3-shape@3.1.0: resolution: {integrity: sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==} @@ -9583,7 +9680,7 @@ packages: /internmap@1.0.1: resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} - dev: true + dev: false /internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} @@ -14618,6 +14715,42 @@ 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'} @@ -14842,6 +14975,24 @@ packages: - supports-color dev: true + /vite-plugin-pwa@0.16.0(vite@4.3.9)(workbox-build@7.0.0)(workbox-window@6.5.4): + 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: 6.5.4 + 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} From 830319e3db6df0cb86ac7dd3e3b80d3328792602 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Tue, 27 Jun 2023 15:21:36 +0300 Subject: [PATCH 59/82] Removed unnecessary TODO --- packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js | 1 - 1 file changed, 1 deletion(-) 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 1fae77c15..2172801dc 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js @@ -3,7 +3,6 @@ import { parser } from './sankey-arrow.jison'; import db from '../sankeyDB.js'; describe('sankey-beta 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; From bbba64328879ed31bc3fa294ebaca508df03eee4 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Tue, 27 Jun 2023 15:30:22 +0300 Subject: [PATCH 60/82] Rmoved unnecessary imports in tests --- .../sankey/parser/sankey-arrow.spec.js | 25 +++++++++---------- .../src/diagrams/sankey/parser/sankey.spec.ts | 11 +++----- 2 files changed, 16 insertions(+), 20 deletions(-) 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 2172801dc..a180b9a28 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey-arrow.spec.js @@ -1,19 +1,18 @@ -import diagram from './sankey-arrow.jison'; -import { parser } from './sankey-arrow.jison'; +// @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 () { - parser.yy = db; - diagram.parser.yy = db; - diagram.parser.yy.clear(); + sankey.parser.yy = db; + sankey.parser.yy.clear(); }); it('recognizes its type', () => { const str = `sankey-beta`; - parser.parse(str); + sankey.parser.parse(str); }); it('recognizes one flow', () => { @@ -22,7 +21,7 @@ describe('sankey-beta diagram', function () { node_a -> 30 -> node_b -> 20 -> node_c `; - parser.parse(str); + sankey.parser.parse(str); }); it('recognizes multiple flows', () => { @@ -33,7 +32,7 @@ describe('sankey-beta diagram', function () { node_c -> 40 -> node_e -> 12 -> node_q `; - parser.parse(str); + sankey.parser.parse(str); }); it('parses node as a string', () => { @@ -44,7 +43,7 @@ describe('sankey-beta diagram', function () { "node c" -> 40 -> "node e" -> 12 -> "node q" `; - parser.parse(str); + sankey.parser.parse(str); }); describe('while attributes parsing', () => { @@ -54,7 +53,7 @@ describe('sankey-beta diagram', function () { 1st -> 200 -> 2nd -> 180 -> 3rd; `; - parser.parse(str); + sankey.parser.parse(str); }); it('parses different quotless variations', () => { @@ -70,7 +69,7 @@ describe('sankey-beta diagram', function () { node[x1dfqowie attr1 = 23413 attr2] `; - parser.parse(str); + sankey.parser.parse(str); }); it('parses strings as values', () => { @@ -80,7 +79,7 @@ describe('sankey-beta diagram', function () { node[title="hello, mister \\"sankey-beta\\", backslash for you \\\\"] `; - parser.parse(str); + sankey.parser.parse(str); }); it('parses real example', () => { @@ -95,7 +94,7 @@ describe('sankey-beta diagram', function () { "Biofuel imports" -> 35 -> "Liquid" `; - parser.parse(str); + sankey.parser.parse(str); }); }); }); diff --git a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts index eef8ae3c6..4517ca01f 100644 --- a/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts @@ -1,7 +1,5 @@ // @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 sankey from './sankey.jison'; import db from '../sankeyDB.js'; import { cleanupComments } from '../../../diagram-api/comments.js'; import { prepareTextForParsing } from '../sankeyUtils.js'; @@ -11,9 +9,8 @@ import * as path from 'path'; describe('Sankey diagram', function () { describe('when parsing an info graph it', function () { beforeEach(function () { - parser.yy = db; - diagram.parser.yy = db; - diagram.parser.yy.clear(); + sankey.parser.yy = db; + sankey.parser.yy.clear(); }); it('parses csv', async () => { @@ -21,7 +18,7 @@ describe('Sankey diagram', function () { const data = fs.readFileSync(csv, 'utf8'); const graphDefinition = prepareTextForParsing(cleanupComments('sankey-beta\n\n ' + data)); - parser.parse(graphDefinition); + sankey.parser.parse(graphDefinition); }); }); }); From d4d7ce5a4b7c352fa2c45a65aad5f99a914d83de Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Tue, 27 Jun 2023 16:12:41 +0300 Subject: [PATCH 61/82] Fixed majority of ts errors for sankey --- .../src/diagrams/sankey/sankeyRenderer.ts | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 83d61dec7..525c23e1f 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import { Diagram } from '../../Diagram.js'; import * as configApi from '../../config.js'; @@ -15,6 +14,7 @@ import { sankeyRight as d3SankeyRight, sankeyCenter as d3SankeyCenter, sankeyJustify as d3SankeyJustify, + SankeyNode as d3SankeyNode, } from 'd3-sankey'; import { configureSvgSize } from '../../setupGraphViewbox.js'; import { Uid } from '../../rendering-util/uid.js'; @@ -41,11 +41,11 @@ export const draw = function (text: string, id: string, _version: string, diagOb if (securityLevel === 'sandbox') { sandboxElement = d3select('#i' + id); } - let root = d3select('body'); - - if (securityLevel === 'sandbox' && sandboxElement) { - root = d3select(sandboxElement.nodes()[0].contentDocument.body); - } + const root = + securityLevel === 'sandbox' + ? d3select(sandboxElement.nodes()[0].contentDocument.body) + : d3select('body'); + // @ts-ignore TODO root.select is not callable const svg = securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : d3select(`[id="${id}"]`); // Establish svg dimensions and get width and height @@ -53,9 +53,12 @@ export const draw = function (text: string, id: string, _version: string, diagOb const width = conf?.width || 800; const height = conf?.height || 400; const useMaxWidth = conf?.useMaxWidth || false; + const nodeAlignment = conf?.nodeAlignment || SankeyNodeAlignment.justify; // FIX: using max width prevents height from being set, is it intended? // to add height directly one can use `svg.attr('height', height)` + // + // @ts-ignore TODO: svg type vs selection mismatch configureSvgSize(svg, height, width, useMaxWidth); // Prepare data for construction based on diagObj.db @@ -66,17 +69,21 @@ export const draw = function (text: string, id: string, _version: string, diagOb // "links": [ { "source": "Alice", "target": "Bob", "value": 23 }, { "source": "Bob", "target": "Carol", "value": 43 } ] // } // + // @ts-ignore TODO: db should be coerced to sankey DB type const graph = diagObj.db.getGraph(); + + // Map config options to alignment functions const alignmentsMap: Map< SankeyNodeAlignment, - (node: SankeyNode<{}, {}>, n: number) => number + (node: d3SankeyNode<{}, {}>, n: number) => number > = new Map([ [SankeyNodeAlignment.left, d3SankeyLeft], [SankeyNodeAlignment.right, d3SankeyRight], [SankeyNodeAlignment.center, d3SankeyCenter], [SankeyNodeAlignment.justify, d3SankeyJustify], - ]); - const nodeAlignment = alignmentsMap.get(conf?.nodeAlignment || SankeyNodeAlignment.justify); + ]) + // We need fallback because typescript thinks that `get` can result in undefined + const nodeAlign = alignmentsMap.get(nodeAlignment) || d3SankeyJustify; // Construct and configure a Sankey generator // That will be a function that calculates nodes and links dimensions @@ -86,7 +93,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb .nodeId((d: any) => d.id) // we use 'id' property to identify node .nodeWidth(nodeWidth) .nodePadding(10) - .nodeAlign(nodeAlignment) + .nodeAlign(nodeAlign) .extent([ [0, 0], [width, height], @@ -152,32 +159,32 @@ export const draw = function (text: string, id: string, _version: string, diagOb if (linkColor === SankeyLinkColor.gradient) { const gradient = link .append('linearGradient') - .attr('id', (d) => (d.uid = Uid.next('linearGradient-')).id) + .attr('id', (d: any) => (d.uid = Uid.next('linearGradient-')).id) .attr('gradientUnits', 'userSpaceOnUse') - .attr('x1', (d) => d.source.x1) - .attr('x2', (d) => d.target.x0); + .attr('x1', (d: any) => d.source.x1) + .attr('x2', (d: any) => d.target.x0); gradient .append('stop') .attr('offset', '0%') - .attr('stop-color', (d) => colorScheme(d.source.id)); + .attr('stop-color', (d: any) => colorScheme(d.source.id)); gradient .append('stop') .attr('offset', '100%') - .attr('stop-color', (d) => colorScheme(d.target.id)); + .attr('stop-color', (d: any) => colorScheme(d.target.id)); } let coloring: any; switch (linkColor) { case SankeyLinkColor.gradient: - coloring = (d) => d.uid; + coloring = (d: any) => d.uid; break; case SankeyLinkColor.source: - coloring = (d) => d.source.id; + coloring = (d: any) => d.source.id; break; case SankeyLinkColor.target: - coloring = (d) => d.target.id; + coloring = (d: any) => d.target.id; break; default: coloring = linkColor; From 0bbf0555d3ac7fe3dee453945dcd8fb2dbd63a18 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Tue, 27 Jun 2023 16:17:55 +0300 Subject: [PATCH 62/82] Fix linters --- packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 525c23e1f..ee78f95bd 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -75,7 +75,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb // Map config options to alignment functions const alignmentsMap: Map< SankeyNodeAlignment, - (node: d3SankeyNode<{}, {}>, n: number) => number + (node: d3SankeyNode, n: number) => number > = new Map([ [SankeyNodeAlignment.left, d3SankeyLeft], [SankeyNodeAlignment.right, d3SankeyRight], From f0231ad9b1c51697655f32ef391ff2c3355e1c5f Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Tue, 27 Jun 2023 16:22:12 +0300 Subject: [PATCH 63/82] Lint --- packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index ee78f95bd..e80fe657d 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -31,7 +31,7 @@ import { SankeyLinkColor, SankeyNodeAlignment } from '../../config.type.js'; export const draw = function (text: string, id: string, _version: string, diagObj: Diagram): void { // 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 @@ -81,7 +81,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb [SankeyNodeAlignment.right, d3SankeyRight], [SankeyNodeAlignment.center, d3SankeyCenter], [SankeyNodeAlignment.justify, d3SankeyJustify], - ]) + ]); // We need fallback because typescript thinks that `get` can result in undefined const nodeAlign = alignmentsMap.get(nodeAlignment) || d3SankeyJustify; From 149cc44c39f1e7c06382dd27adc498654c92b1a0 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Tue, 27 Jun 2023 16:46:20 +0300 Subject: [PATCH 64/82] Empty From 301073a60d602687aa96d2943030bff4ee14db58 Mon Sep 17 00:00:00 2001 From: nirname Date: Tue, 27 Jun 2023 13:51:44 +0000 Subject: [PATCH 65/82] Update docs --- docs/config/setup/modules/defaultConfig.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index d74c9b2b1..8cc503ae6 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -14,7 +14,7 @@ #### Defined in -[defaultConfig.ts:2298](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2298) +[defaultConfig.ts:2299](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2299) --- From 8b111760b15b699ff4e024aa3f85d7a120f2e5f9 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Tue, 27 Jun 2023 17:50:51 +0300 Subject: [PATCH 66/82] Updates after review --- packages/mermaid/src/defaultConfig.ts | 1 + .../mermaid/src/diagrams/sankey/sankeyDB.ts | 5 +- .../src/diagrams/sankey/sankeyRenderer.ts | 26 ++++----- pnpm-lock.yaml | 54 +++++++++++++++++++ 4 files changed, 70 insertions(+), 16 deletions(-) diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts index 39dcd0721..6e24b6391 100644 --- a/packages/mermaid/src/defaultConfig.ts +++ b/packages/mermaid/src/defaultConfig.ts @@ -2275,6 +2275,7 @@ const config: Partial = { height: 400, linkColor: SankeyLinkColor.gradient, nodeAlignment: SankeyNodeAlignment.justify, + useMaxWidth: false, }, fontSize: 16, }; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts index 913fcb410..1b6864af9 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts @@ -10,12 +10,11 @@ import { clear as commonClear, } from '../../commonDb.js'; -// 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 a mapping also -// let links: SankeyLink[] = []; +// Array of nodes guarantees their order let nodes: SankeyNode[] = []; +// We also have to track nodes uniqueness (by ID) let nodesMap: Record = {}; const clear = (): void => { diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index e80fe657d..cb3092f55 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -31,6 +31,7 @@ import { SankeyLinkColor, SankeyNodeAlignment } from '../../config.type.js'; export const draw = function (text: string, id: string, _version: string, diagObj: Diagram): void { // Get Sankey config const { securityLevel, sankey: conf } = configApi.getConfig(); + const defaultSankeyConfig = configApi!.defaultConfig!.sankey!; // TODO: // This code repeats for every diagram @@ -50,10 +51,10 @@ export const draw = function (text: string, id: string, _version: string, diagOb // Establish svg dimensions and get width and height // - const width = conf?.width || 800; - const height = conf?.height || 400; - const useMaxWidth = conf?.useMaxWidth || false; - const nodeAlignment = conf?.nodeAlignment || SankeyNodeAlignment.justify; + const width = conf?.width || defaultSankeyConfig.width!; + const height = conf?.height || defaultSankeyConfig.width!; + const useMaxWidth = conf?.useMaxWidth || defaultSankeyConfig.useMaxWidth!; + const nodeAlignment = conf?.nodeAlignment || defaultSankeyConfig.nodeAlignment!; // FIX: using max width prevents height from being set, is it intended? // to add height directly one can use `svg.attr('height', height)` @@ -73,17 +74,16 @@ export const draw = function (text: string, id: string, _version: string, diagOb const graph = diagObj.db.getGraph(); // Map config options to alignment functions - const alignmentsMap: Map< + const alignmentsMap: Record< SankeyNodeAlignment, (node: d3SankeyNode, n: number) => number - > = new Map([ - [SankeyNodeAlignment.left, d3SankeyLeft], - [SankeyNodeAlignment.right, d3SankeyRight], - [SankeyNodeAlignment.center, d3SankeyCenter], - [SankeyNodeAlignment.justify, d3SankeyJustify], - ]); - // We need fallback because typescript thinks that `get` can result in undefined - const nodeAlign = alignmentsMap.get(nodeAlignment) || d3SankeyJustify; + > = { + [SankeyNodeAlignment.left]: d3SankeyLeft, + [SankeyNodeAlignment.right]: d3SankeyRight, + [SankeyNodeAlignment.center]: d3SankeyCenter, + [SankeyNodeAlignment.justify]: d3SankeyJustify, + }; + const nodeAlign = alignmentsMap[nodeAlignment]; // Construct and configure a Sankey generator // That will be a function that calculates nodes and links dimensions diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d13daf1b2..052e5eec9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15143,6 +15143,49 @@ 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 @@ -15808,6 +15851,10 @@ packages: workbox-core: 7.0.0 dev: true + /workbox-core@6.5.4: + resolution: {integrity: sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==} + dev: true + /workbox-core@7.0.0: resolution: {integrity: sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ==} dev: true @@ -15882,6 +15929,13 @@ packages: resolution: {integrity: sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA==} dev: true + /workbox-window@6.5.4: + resolution: {integrity: sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==} + dependencies: + '@types/trusted-types': 2.0.2 + workbox-core: 6.5.4 + dev: true + /workbox-window@7.0.0: resolution: {integrity: sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA==} dependencies: From 19bea109830c1e4986a02cfcccecf21c2e3d1f4d Mon Sep 17 00:00:00 2001 From: nirname Date: Tue, 27 Jun 2023 14:54:57 +0000 Subject: [PATCH 67/82] Update docs --- docs/config/setup/modules/defaultConfig.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index 8cc503ae6..793dfffae 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -14,7 +14,7 @@ #### Defined in -[defaultConfig.ts:2299](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2299) +[defaultConfig.ts:2300](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2300) --- From a712e618886d2a46668f21c6afe189e40bcc596b Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Thu, 29 Jun 2023 14:06:08 +0300 Subject: [PATCH 68/82] Loading avatars in parallel & started sankey documentation --- docker-compose.yml | 1 + .../docs/.vitepress/scripts/fetch-avatars.ts | 11 +- packages/mermaid/src/docs/package.json | 2 +- packages/mermaid/src/docs/syntax/sankey.md | 184 ++++++++++++++++++ 4 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 packages/mermaid/src/docs/syntax/sankey.md diff --git a/docker-compose.yml b/docker-compose.yml index 43fa5485f..49fe0f3e8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: - root_npm:/root/.npm ports: - 9000:9000 + - 3333:3333 volumes: root_cache: root_local: diff --git a/packages/mermaid/src/docs/.vitepress/scripts/fetch-avatars.ts b/packages/mermaid/src/docs/.vitepress/scripts/fetch-avatars.ts index bbea31bc1..47f371fee 100644 --- a/packages/mermaid/src/docs/.vitepress/scripts/fetch-avatars.ts +++ b/packages/mermaid/src/docs/.vitepress/scripts/fetch-avatars.ts @@ -18,7 +18,7 @@ async function download(url: string, fileName: URL) { const image = await fetch(url); await writeFile(fileName, Buffer.from(await image.arrayBuffer())); } catch (error) { - console.error(error); + console.error('failed to load', url, error); } } @@ -26,10 +26,13 @@ async function fetchAvatars() { await mkdir(fileURLToPath(new URL(getAvatarPath('none'))).replace('none.png', ''), { recursive: true, }); + contributors = JSON.parse(await readFile(pathContributors, { encoding: 'utf-8' })); - for (const name of contributors) { - await download(`https://github.com/${name}.png?size=100`, getAvatarPath(name)); - } + let avatars = contributors.map((name) => { + download(`https://github.com/${name}.png?size=100`, getAvatarPath(name)); + }); + + await Promise.all(avatars); } fetchAvatars(); diff --git a/packages/mermaid/src/docs/package.json b/packages/mermaid/src/docs/package.json index f24f9f4bc..34be89bd2 100644 --- a/packages/mermaid/src/docs/package.json +++ b/packages/mermaid/src/docs/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "dev": "vitepress --port 3333 --open", + "dev": "pnpm prefetch && vitepress --port 3333 --host", "build": "pnpm prefetch && vitepress build", "build-no-prefetch": "vitepress build", "serve": "vitepress serve", diff --git a/packages/mermaid/src/docs/syntax/sankey.md b/packages/mermaid/src/docs/syntax/sankey.md new file mode 100644 index 000000000..a6aa8e5a9 --- /dev/null +++ b/packages/mermaid/src/docs/syntax/sankey.md @@ -0,0 +1,184 @@ +# Sankey diagrams + +> A sankey diagram is a visualization used to depict a flow from one set of values to another. +> This is an experimental diagram. Its syntax are very close to plain CSV, but highly likely +> to be extended in the nearest future. + +The things being connected are called nodes and the connections are called links. + +## Example + +Code: + +```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 it is that a user types `sankey-beta` first, then pastes raw CSV below and get the result. + +It is implemented according 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 + +```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 +``` + +If you need your title to have a comma, wrap it in double quotes + + +"Hello, Mister Anderson" + From 408c32781e458b26cbeb482ae49f55e609eccd12 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Thu, 29 Jun 2023 15:24:09 +0300 Subject: [PATCH 69/82] Contributors fetching failure must not hinder docs development --- .../.vitepress/scripts/fetch-contributors.ts | 36 +++++----- pnpm-lock.yaml | 68 ++----------------- run | 2 +- 3 files changed, 27 insertions(+), 79 deletions(-) diff --git a/packages/mermaid/src/docs/.vitepress/scripts/fetch-contributors.ts b/packages/mermaid/src/docs/.vitepress/scripts/fetch-contributors.ts index fd5409d0f..a29ef3324 100644 --- a/packages/mermaid/src/docs/.vitepress/scripts/fetch-contributors.ts +++ b/packages/mermaid/src/docs/.vitepress/scripts/fetch-contributors.ts @@ -10,23 +10,25 @@ interface Contributor { async function fetchContributors() { const collaborators: string[] = []; - let page = 1; - let data: Contributor[] = []; - do { - const response = await fetch( - `https://api.github.com/repos/mermaid-js/mermaid/contributors?per_page=100&page=${page}`, - { - method: 'GET', - headers: { - 'content-type': 'application/json', - }, - } - ); - data = await response.json(); - collaborators.push(...data.map((i) => i.login)); - console.log(`Fetched page ${page}`); - page++; - } while (data.length === 100); + try { + let page = 1; + let data: Contributor[] = []; + do { + const response = await fetch( + `https://api.github.com/repos/mermaid-js/mermaid/contributors?per_page=100&page=${page}`, + { + method: 'GET', + headers: { + 'content-type': 'application/json', + }, + } + ); + data = await response.json(); + collaborators.push(...data.map((i) => i.login)); + console.log(`Fetched page ${page}`); + page++; + } while (data.length === 100); + } catch (e) { /* contributors fetching failure must not hinder docs development */ } return collaborators.filter((name) => !name.includes('[bot]')); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 052e5eec9..200e6115a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -506,13 +506,13 @@ importers: 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@6.5.4) + version: 0.16.0(vite@4.3.9)(workbox-build@7.0.0)(workbox-window@7.0.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) + 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: ^6.5.4 - version: 6.5.4 + specifier: ^7.0.0 + version: 7.0.0 tests/webpack: dependencies: @@ -14980,7 +14980,7 @@ packages: - supports-color dev: true - /vite-plugin-pwa@0.16.0(vite@4.3.9)(workbox-build@7.0.0)(workbox-window@6.5.4): + /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: @@ -14993,7 +14993,7 @@ packages: pretty-bytes: 6.1.0 vite: 4.3.9(@types/node@18.16.0) workbox-build: 7.0.0 - workbox-window: 6.5.4 + workbox-window: 7.0.0 transitivePeerDependencies: - supports-color dev: true @@ -15143,49 +15143,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 @@ -15851,10 +15808,6 @@ packages: workbox-core: 7.0.0 dev: true - /workbox-core@6.5.4: - resolution: {integrity: sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==} - dev: true - /workbox-core@7.0.0: resolution: {integrity: sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ==} dev: true @@ -15929,13 +15882,6 @@ packages: resolution: {integrity: sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA==} dev: true - /workbox-window@6.5.4: - resolution: {integrity: sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==} - dependencies: - '@types/trusted-types': 2.0.2 - workbox-core: 6.5.4 - dev: true - /workbox-window@7.0.0: resolution: {integrity: sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA==} dependencies: diff --git a/run b/run index 18a544312..ecc6bb02b 100755 --- a/run +++ b/run @@ -20,7 +20,7 @@ $RUN --service-ports mermaid sh -c "npx pnpm run dev" ;; docs:dev) -$RUN --service-ports mermaid sh -c "npx pnpm --filter mermaid run docs:dev" +$RUN --service-ports mermaid sh -c "cd packages/mermaid/src/docs && npx pnpm prefetch && npx vitepress --port 3333 --host" ;; help) From 8e3f8b1a57b5fb4ab277ff235961a9ef768871ca Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Thu, 29 Jun 2023 15:39:50 +0300 Subject: [PATCH 70/82] Fixes after review --- .../mermaid/src/diagrams/sankey/sankeyDB.ts | 9 ++++---- .../src/diagrams/sankey/sankeyRenderer.ts | 22 ++++++++++--------- .../mermaid/src/docs/.vitepress/config.ts | 1 + 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts index 1b6864af9..1921e1b85 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts @@ -43,12 +43,11 @@ class SankeyNode { const findOrCreateNode = (ID: string): SankeyNode => { ID = common.sanitizeText(ID, configApi.getConfig()); - if (nodesMap[ID]) { - return nodesMap[ID]; - } - nodesMap[ID] = new SankeyNode(ID); - nodes.push(nodesMap[ID]); + if (!nodesMap[ID]) { + nodesMap[ID] = new SankeyNode(ID); + nodes.push(nodesMap[ID]); + } return nodesMap[ID]; }; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index cb3092f55..780144be0 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -20,6 +20,17 @@ import { configureSvgSize } from '../../setupGraphViewbox.js'; import { Uid } from '../../rendering-util/uid.js'; import { SankeyLinkColor, SankeyNodeAlignment } from '../../config.type.js'; +// Map config options to alignment functions +const alignmentsMap: Record< + SankeyNodeAlignment, + (node: d3SankeyNode, n: number) => number +> = { + [SankeyNodeAlignment.left]: d3SankeyLeft, + [SankeyNodeAlignment.right]: d3SankeyRight, + [SankeyNodeAlignment.center]: d3SankeyCenter, + [SankeyNodeAlignment.justify]: d3SankeyJustify, +}; + /** * Draws Sankey diagram. * @@ -73,16 +84,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb // @ts-ignore TODO: db should be coerced to sankey DB type const graph = diagObj.db.getGraph(); - // Map config options to alignment functions - const alignmentsMap: Record< - SankeyNodeAlignment, - (node: d3SankeyNode, n: number) => number - > = { - [SankeyNodeAlignment.left]: d3SankeyLeft, - [SankeyNodeAlignment.right]: d3SankeyRight, - [SankeyNodeAlignment.center]: d3SankeyCenter, - [SankeyNodeAlignment.justify]: d3SankeyJustify, - }; + // Get alignment function const nodeAlign = alignmentsMap[nodeAlignment]; // Construct and configure a Sankey generator diff --git a/packages/mermaid/src/docs/.vitepress/config.ts b/packages/mermaid/src/docs/.vitepress/config.ts index 8fceb810b..2b2914ca3 100644 --- a/packages/mermaid/src/docs/.vitepress/config.ts +++ b/packages/mermaid/src/docs/.vitepress/config.ts @@ -138,6 +138,7 @@ function sidebarSyntax() { { text: 'Mindmaps 🔥', link: '/syntax/mindmap' }, { text: 'Timeline 🔥', link: '/syntax/timeline' }, { text: 'Zenuml 🔥', link: '/syntax/zenuml' }, + { text: 'Sankey 🔥', link: '/syntax/sankey' }, { text: 'Other Examples', link: '/syntax/examples' }, ], }, From 95a436595ee6e18900cedd980ccde80d4b47de92 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Thu, 29 Jun 2023 16:44:33 +0300 Subject: [PATCH 71/82] Let it be as it was in dev branch --- packages/mermaid/src/docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/docs/package.json b/packages/mermaid/src/docs/package.json index 34be89bd2..f24f9f4bc 100644 --- a/packages/mermaid/src/docs/package.json +++ b/packages/mermaid/src/docs/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "dev": "pnpm prefetch && vitepress --port 3333 --host", + "dev": "vitepress --port 3333 --open", "build": "pnpm prefetch && vitepress build", "build-no-prefetch": "vitepress build", "serve": "vitepress serve", From e24dce10a293571f57618677ec90956e8a2e2731 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Thu, 29 Jun 2023 19:51:46 +0300 Subject: [PATCH 72/82] Added sankey documentation --- packages/mermaid/src/docs/syntax/sankey.md | 253 ++++++++++++++------- run | 4 +- 2 files changed, 168 insertions(+), 89 deletions(-) diff --git a/packages/mermaid/src/docs/syntax/sankey.md b/packages/mermaid/src/docs/syntax/sankey.md index a6aa8e5a9..66c8a742f 100644 --- a/packages/mermaid/src/docs/syntax/sankey.md +++ b/packages/mermaid/src/docs/syntax/sankey.md @@ -1,87 +1,16 @@ # Sankey diagrams > A sankey diagram is a visualization used to depict a flow from one set of values to another. -> This is an experimental diagram. Its syntax are very close to plain CSV, but highly likely -> to be extended in the nearest future. + +::: 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 -Code: - -```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 -``` +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 sankey-beta @@ -156,29 +85,177 @@ Wave,Electricity grid,19.013 Wind,Electricity grid,289.366 ``` -## Syntax - -The idea behind it is that a user types `sankey-beta` first, then pastes raw CSV below and get the result. - -It is implemented according 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 +::: 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 +``` +::: + + +## 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 ``` -If you need your title to have a comma, wrap it in double quotes +```mermaid +sankey-beta +Bio-conversion,Liquid,0.597 +Bio-conversion,Losses,26.862 -"Hello, Mister Anderson" +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 +``` + +### 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 +``` + +### Coloring and Фlignment + +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 + +``` \ 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 73/82] 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 76/82] 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 77/82] Trigger build in GA From a62719826a3a125e005be5e2749906275c84cb64 Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sat, 1 Jul 2023 16:17:25 +0300 Subject: [PATCH 78/82] 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 084b765e9f89abe7171768f9649f0485861fbeee Mon Sep 17 00:00:00 2001 From: Nikolay Rozhkov Date: Sun, 2 Jul 2023 01:10:06 +0300 Subject: [PATCH 79/82] 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 81/82] 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 82/82] 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`