diff --git a/codecov.yaml b/.github/codecov.yaml similarity index 73% rename from codecov.yaml rename to .github/codecov.yaml index b268d6680..3db0811ad 100644 --- a/codecov.yaml +++ b/.github/codecov.yaml @@ -1,6 +1,15 @@ +codecov: + branch: develop + comment: layout: 'reach, diff, flags, files' behavior: default require_changes: false # if true: only post the comment if coverage changes require_base: no # [yes :: must have a base report to post] require_head: yes # [yes :: must have a head report to post] + +coverage: + status: + project: + default: + threshold: 2% diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index f3e440fce..b25ff89cc 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -2,13 +2,13 @@ name: Build Vitepress docs on: pull_request: + merge_group: permissions: contents: read jobs: - # Build job - build: + build-docs: runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2a70b5901..eeb557ebb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,7 @@ name: Build on: push: {} + merge_group: pull_request: types: - opened @@ -12,7 +13,7 @@ permissions: contents: read jobs: - build: + build-mermaid: runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/check-readme-in-sync.yml b/.github/workflows/check-readme-in-sync.yml index 13912e5b9..5a8ca319b 100644 --- a/.github/workflows/check-readme-in-sync.yml +++ b/.github/workflows/check-readme-in-sync.yml @@ -14,7 +14,7 @@ permissions: contents: read jobs: - check: + check-readme: runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 396ff4e6e..9f9f316c4 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -1,15 +1,16 @@ on: - push: {} + push: + merge_group: pull_request: types: - opened - synchronize - ready_for_review -name: Static analysis +name: Static analysis on Test files jobs: - test: + check-tests: runs-on: ubuntu-latest name: check tests if: github.repository_owner == 'mermaid-js' diff --git a/.github/workflows/e2e-applitools.yml b/.github/workflows/e2e-applitools.yml index 92f2f80b1..5b1943142 100644 --- a/.github/workflows/e2e-applitools.yml +++ b/.github/workflows/e2e-applitools.yml @@ -19,7 +19,7 @@ env: USE_APPLI: ${{ secrets.APPLITOOLS_API_KEY && 'true' || '' }} jobs: - test: + e2e-applitools: runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 64637c5fb..c204d5872 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,12 +1,15 @@ name: E2E -on: [push, pull_request] +on: + push: + pull_request: + merge_group: permissions: contents: read jobs: - build: + e2e: runs-on: ubuntu-latest strategy: fail-fast: false @@ -49,7 +52,7 @@ jobs: files: coverage/cypress/lcov.info flags: e2e name: mermaid-codecov - fail_ci_if_error: true + fail_ci_if_error: false verbose: true - name: Upload Artifacts uses: actions/upload-artifact@v3 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d4bf4afe8..53b7f484d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,7 +1,8 @@ name: Lint on: - push: {} + push: + merge_group: pull_request: types: - opened diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index b556c1b1d..f63e58750 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -19,7 +19,7 @@ concurrency: jobs: # Build job - build: + build-docs: runs-on: ubuntu-latest steps: - name: Checkout @@ -48,11 +48,11 @@ jobs: path: packages/mermaid/src/vitepress/.vitepress/dist # Deployment job - deploy: + deploy-docs: environment: name: github-pages runs-on: ubuntu-latest - needs: build + needs: build-docs steps: - name: Deploy to GitHub Pages id: deployment diff --git a/.github/workflows/release-preview-publish.yml b/.github/workflows/release-preview-publish.yml index 5f4936ab6..221e3836e 100644 --- a/.github/workflows/release-preview-publish.yml +++ b/.github/workflows/release-preview-publish.yml @@ -6,7 +6,7 @@ on: - 'release/**' jobs: - publish: + publish-preview: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6bae6b71f..47b726bd1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,12 +1,12 @@ name: Unit Tests -on: [push, pull_request] +on: [push, pull_request, merge_group] permissions: contents: read jobs: - build: + unit-test: runs-on: ubuntu-latest strategy: matrix: @@ -47,11 +47,5 @@ jobs: files: ./coverage/vitest/lcov.info flags: unit name: mermaid-codecov - fail_ci_if_error: true + fail_ci_if_error: false verbose: true - # Coveralls is throwing 500. Disabled for now. - # - name: Upload Coverage to Coveralls - # uses: coverallsapp/github-action@v2 - # with: - # github-token: ${{ secrets.GITHUB_TOKEN }} - # flag-name: unit diff --git a/.github/workflows/update-browserlist.yml b/.github/workflows/update-browserlist.yml index 4155ec988..813a400b3 100644 --- a/.github/workflows/update-browserlist.yml +++ b/.github/workflows/update-browserlist.yml @@ -5,7 +5,7 @@ on: workflow_dispatch: jobs: - build: + update-browser-list: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 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/cSpell.json b/cSpell.json index ce761b884..ad225d1bc 100644 --- a/cSpell.json +++ b/cSpell.json @@ -109,6 +109,7 @@ "rehype", "roledescription", "sandboxed", + "sankey", "setupgraphviewbox", "shiki", "sidharth", diff --git a/cypress/integration/rendering/sankey.spec.ts b/cypress/integration/rendering/sankey.spec.ts new file mode 100644 index 000000000..e334b898b --- /dev/null +++ b/cypress/integration/rendering/sankey.spec.ts @@ -0,0 +1,144 @@ +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 () { + this.beforeAll(() => { + cy.wrap( + `sankey-beta + a,b,10 + ` + ).as('graph'); + }); + + it('links should use hex color', function () { + renderGraph(this.graph, { sankey: { linkColor: '#636465' } }); + + cy.get('.link path').should((link) => { + expect(link.attr('stroke')).to.equal('#636465'); + }); + }); + + it('links should be the same color as source node', function () { + renderGraph(this.graph, { 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('links should be the same color as target node', function () { + renderGraph(this.graph, { 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', function () { + renderGraph(this.graph, { sankey: { linkColor: 'gradient' } }); + + cy.get('.link path').should((link) => { + expect(link.attr('stroke')).to.equal('url(#linearGradient-3)'); + }); + }); + }); + + describe('when given a nodeAlignment', function () { + this.beforeAll(() => { + cy.wrap( + ` + sankey-beta + + a,b,8 + b,c,8 + c,d,8 + d,e,8 + + x,c,4 + c,y,4 + ` + ).as('graph'); + }); + + this.afterEach(() => { + cy.get('.node[id="node-1"]').should((node) => { + expect(node.attr('x')).to.equal('0'); + }); + cy.get('.node[id="node-2"]').should((node) => { + expect(node.attr('x')).to.equal('100'); + }); + cy.get('.node[id="node-3"]').should((node) => { + expect(node.attr('x')).to.equal('200'); + }); + cy.get('.node[id="node-4"]').should((node) => { + expect(node.attr('x')).to.equal('300'); + }); + cy.get('.node[id="node-5"]').should((node) => { + expect(node.attr('x')).to.equal('400'); + }); + }); + + it('should justify nodes', function () { + renderGraph(this.graph, { + sankey: { nodeAlignment: 'justify', width: 410, useMaxWidth: false }, + }); + cy.get('.node[id="node-6"]').should((node) => { + expect(node.attr('x')).to.equal('0'); + }); + cy.get('.node[id="node-7"]').should((node) => { + expect(node.attr('x')).to.equal('400'); + }); + }); + + it('should align nodes left', function () { + renderGraph(this.graph, { + sankey: { nodeAlignment: 'left', width: 410, useMaxWidth: false }, + }); + cy.get('.node[id="node-6"]').should((node) => { + expect(node.attr('x')).to.equal('0'); + }); + cy.get('.node[id="node-7"]').should((node) => { + expect(node.attr('x')).to.equal('300'); + }); + }); + + it('should align nodes right', function () { + renderGraph(this.graph, { + sankey: { nodeAlignment: 'right', width: 410, useMaxWidth: false }, + }); + cy.get('.node[id="node-6"]').should((node) => { + expect(node.attr('x')).to.equal('100'); + }); + cy.get('.node[id="node-7"]').should((node) => { + expect(node.attr('x')).to.equal('400'); + }); + }); + + it('should center nodes', function () { + renderGraph(this.graph, { + sankey: { nodeAlignment: 'center', width: 410, useMaxWidth: false }, + }); + cy.get('.node[id="node-6"]').should((node) => { + expect(node.attr('x')).to.equal('100'); + }); + cy.get('.node[id="node-7"]').should((node) => { + expect(node.attr('x')).to.equal('300'); + }); + }); + }); +}); diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js index 185cc4133..7d36c1ff1 100644 --- a/cypress/integration/rendering/sequencediagram.spec.js +++ b/cypress/integration/rendering/sequencediagram.spec.js @@ -156,6 +156,81 @@ context('Sequence diagram', () => { ` ); }); + it('should render a sequence diagram with basic actor creation and destruction', () => { + imgSnapshotTest( + ` + sequenceDiagram + Alice ->> Bob: Hello Bob, how are you ? + Bob ->> Alice: Fine, thank you. And you? + create participant Polo + Alice ->> Polo: Hi Polo! + create actor Ola1 as Ola + Polo ->> Ola1: Hiii + Ola1 ->> Alice: Hi too + destroy Ola1 + Alice --x Ola1: Bye! + Alice ->> Bob: And now? + create participant Ola2 as Ola + Alice ->> Ola2: Hello again + destroy Alice + Alice --x Ola2: Bye for me! + destroy Bob + Ola2 --> Bob: The end + ` + ); + }); + it('should render a sequence diagram with actor creation and destruction coupled with backgrounds, loops and notes', () => { + imgSnapshotTest( + ` + sequenceDiagram + accTitle: test the accTitle + accDescr: Test a description + + participant Alice + participant Bob + autonumber 10 10 + rect rgb(200, 220, 100) + rect rgb(200, 255, 200) + + Alice ->> Bob: Hello Bob, how are you? + create participant John as John
Second Line + Bob-->>John: How about you John? + end + + Bob--x Alice: I am good thanks! + Bob-x John: I am good thanks! + Note right of John: John thinks a long
long time, so long
that the text does
not fit on a row. + + Bob-->Alice: Checking with John... + Note over John:wrap: John looks like he's still thinking, so Bob prods him a bit. + Bob-x John: Hey John - we're still waiting to know
how you're doing + Note over John:nowrap: John's trying hard not to break his train of thought. + destroy John + Bob-x John: John! Cmon! + Note over John: After a few more moments, John
finally snaps out of it. + end + + autonumber off + alt either this + create actor Lola + Alice->>+Lola: Yes + Lola-->>-Alice: OK + else or this + autonumber + Alice->>Lola: No + else or this will happen + Alice->Lola: Maybe + end + autonumber 200 + par this happens in parallel + destroy Bob + Alice -->> Bob: Parallel message 1 + and + Alice -->> Lola: Parallel message 2 + end + ` + ); + }); context('font settings', () => { it('should render different note fonts when configured', () => { imgSnapshotTest( 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 new file mode 100644 index 000000000..0c9679c65 --- /dev/null +++ b/demos/sankey.html @@ -0,0 +1,108 @@ + + + + + + States Mermaid Quick Test Page + + + + + +

    Sankey diagram demos

    +

    Energy flow

    +
    +      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
    +    
    + + + + diff --git a/docker-compose.yml b/docker-compose.yml index a2773b8a5..c881d0473 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,36 @@ version: '3.9' services: mermaid: - image: node:20.3.1-alpine3.18 + image: node:18.16.1-alpine3.18 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 + ports: + - 9000:9000 + - 3333:3333 + cypress: + image: cypress/included:12.16.0 + stdin_open: true + tty: true + working_dir: /mermaid + mem_limit: '2G' + entrypoint: cypress + environment: + - DISPLAY + volumes: + - ./:/mermaid + - /tmp/.X11-unix:/tmp/.X11-unix + network_mode: host + +volumes: + root_cache: + root_local: + root_npm: diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index d95ec4e92..793dfffae 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:2300](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2300) --- diff --git a/docs/config/theming.md b/docs/config/theming.md index 580afb488..63271a39b 100644 --- a/docs/config/theming.md +++ b/docs/config/theming.md @@ -73,9 +73,9 @@ To make a custom theme, modify `themeVariables` via `init`. You will need to use the [base](#available-themes) theme as it is the only modifiable theme. -| Parameter | Description | Type | Properties | -| -------------- | ------------------------------------ | ------ | --------------------------------------------------------------------------------------------------- | -| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables-reference-table)) | +| Parameter | Description | Type | Properties | +| -------------- | ------------------------------------ | ------ | ----------------------------------------------------------------------------------- | +| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables)) | Example of modifying `themeVariables` using the `init` directive: diff --git a/docs/news/announcements.md b/docs/news/announcements.md index a843ae419..1837b33db 100644 --- a/docs/news/announcements.md +++ b/docs/news/announcements.md @@ -6,8 +6,8 @@ # Announcements -## [subhash-halder contributed quadrant charts so you can show your manager what to select - just like the strategy consultants at BCG do](https://www.mermaidchart.com/blog/posts/subhash-halder-contributed-quadrant-charts-so-you-can-show-your-manager-what-to-select-just-like-the-strategy-consultants-at-bcg-do/) +## [Sequence diagrams, the only good thing UML brought to software development](https://www.mermaidchart.com/blog/posts/sequence-diagrams-the-good-thing-uml-brought-to-software-development/) -8 June 2023 · 7 mins +15 June 2023 · 12 mins -A quadrant chart is a useful diagram that helps users visualize data and identify patterns in a data set. +Sequence diagrams really shine when you’re documenting different parts of a system and the various ways these parts interact with each other. diff --git a/docs/news/blog.md b/docs/news/blog.md index 48b669d79..eb9ec6c47 100644 --- a/docs/news/blog.md +++ b/docs/news/blog.md @@ -6,6 +6,12 @@ # Blog +## [Sequence diagrams, the only good thing UML brought to software development](https://www.mermaidchart.com/blog/posts/sequence-diagrams-the-good-thing-uml-brought-to-software-development/) + +15 June 2023 · 12 mins + +Sequence diagrams really shine when you’re documenting different parts of a system and the various ways these parts interact with each other. + ## [subhash-halder contributed quadrant charts so you can show your manager what to select - just like the strategy consultants at BCG do](https://www.mermaidchart.com/blog/posts/subhash-halder-contributed-quadrant-charts-so-you-can-show-your-manager-what-to-select-just-like-the-strategy-consultants-at-bcg-do/) 8 June 2023 · 7 mins diff --git a/docs/syntax/sankey.md b/docs/syntax/sankey.md new file mode 100644 index 000000000..bfbfc3621 --- /dev/null +++ b/docs/syntax/sankey.md @@ -0,0 +1,510 @@ +> **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 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 + +### Basic + +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,Losses,26.862 + +Bio-conversion,Solid,280.322 + +Bio-conversion,Gas,81.144 +``` + +```mermaid +sankey-beta + +Bio-conversion,Losses,26.862 + +Bio-conversion,Solid,280.322 + +Bio-conversion,Gas,81.144 +``` + +```mermaid-example +sankey-beta + +Bio-conversion,Losses,26.862 + +Bio-conversion,Solid,280.322 + +Bio-conversion,Gas,81.144 +``` + +```mermaid +sankey-beta + +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 inside quoted string: + +```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 + +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` diff --git a/docs/syntax/sequenceDiagram.md b/docs/syntax/sequenceDiagram.md index 26f81452d..89c232ea6 100644 --- a/docs/syntax/sequenceDiagram.md +++ b/docs/syntax/sequenceDiagram.md @@ -94,6 +94,43 @@ sequenceDiagram J->>A: Great! ``` +### Actor Creation and Destruction + +It is possible to create and destroy actors by messages. To do so, add a create or destroy directive before the message. + + create participant B + A --> B: Hello + +Create directives support actor/participant distinction and aliases. The sender or the recipient of a message can be destroyed but only the recipient can be created. + +```mermaid-example +sequenceDiagram + Alice->>Bob: Hello Bob, how are you ? + Bob->>Alice: Fine, thank you. And you? + create participant Carl + Alice->>Carl: Hi Carl! + create actor D as Donald + Carl->>D: Hi! + destroy Carl + Alice-xCarl: We are too many + destroy Bob + Bob->>Alice: I agree +``` + +```mermaid +sequenceDiagram + Alice->>Bob: Hello Bob, how are you ? + Bob->>Alice: Fine, thank you. And you? + create participant Carl + Alice->>Carl: Hi Carl! + create actor D as Donald + Carl->>D: Hi! + destroy Carl + Alice-xCarl: We are too many + destroy Bob + Bob->>Alice: I agree +``` + ### Grouping / Box The actor(s) can be grouped in vertical boxes. You can define a color (if not, it will be transparent) and/or a descriptive label using the following notation: diff --git a/docs/syntax/stateDiagram.md b/docs/syntax/stateDiagram.md index 807d6149a..4c28c82b3 100644 --- a/docs/syntax/stateDiagram.md +++ b/docs/syntax/stateDiagram.md @@ -487,7 +487,7 @@ where - the second _property_ is `color` and its _value_ is `white` - the third _property_ is `font-weight` and its _value_ is `bold` - the fourth _property_ is `stroke-width` and its _value_ is `2px` -- the fifth _property_ is `stroke` and its _value_ is `yello` +- the fifth _property_ is `stroke` and its _value_ is `yellow` ### Apply classDef styles to states diff --git a/package.json b/package.json index e6bd516a9..738d1796a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mermaid-monorepo", "private": true, - "version": "10.2.3", + "version": "10.2.4", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", "packageManager": "pnpm@8.6.5", diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 8e7e9696d..62efec380 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -1,6 +1,6 @@ { "name": "mermaid", - "version": "10.2.3", + "version": "10.2.4", "description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", "module": "./dist/mermaid.core.mjs", @@ -53,13 +53,16 @@ }, "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", + "dompurify": "3.0.4", "elkjs": "^0.8.2", "khroma": "^2.0.0", "lodash-es": "^4.17.21", @@ -73,6 +76,7 @@ "devDependencies": { "@types/cytoscape": "^3.19.9", "@types/d3": "^7.4.0", + "@types/d3-sankey": "^0.12.1", "@types/d3-selection": "^3.0.5", "@types/dompurify": "^3.0.2", "@types/jsdom": "^21.1.1", diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 140c200fb..4d40b33c8 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,26 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig { wrappingWidth?: number; } +export enum SankeyLinkColor { + source = 'source', + target = 'target', + 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 { fontSize?: string | number; fontFamily?: string; diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts index c9407828f..6e24b6391 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)].** @@ -2270,6 +2270,13 @@ const config: Partial = { padding: 10, maxNodeWidth: 200, }, + sankey: { + width: 800, + height: 400, + linkColor: SankeyLinkColor.gradient, + nodeAlignment: SankeyNodeAlignment.justify, + useMaxWidth: false, + }, fontSize: 16, }; diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index 0253be45d..9c03e27f3 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/sankeyDetector.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/parser/energy.csv b/packages/mermaid/src/diagrams/sankey/parser/energy.csv new file mode 100644 index 000000000..d9a8fac9a --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/parser/energy.csv @@ -0,0 +1,99 @@ +%% 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 + +%% Line with integer amount +Bio-conversion,Solid,280 + +%% Some blank lines in the middle of CSV + + +%% Another normal line +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 +Coal imports,Coal,11.606 +Coal reserves,Coal,63.965 +Coal,Solid,75.571 +District heating,Industry,10.639 +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 + +%% 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 new file mode 100644 index 000000000..b11f8a87b --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.jison @@ -0,0 +1,69 @@ +/** mermaid */ + +//--------------------------------------------------------- +// 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 + +%options case-insensitive +%options easy_keword_rules + +%x escaped_text +%x csv + +// as per section 6.1 of RFC 2234 [2] +COMMA \u002C +CR \u000D +LF \u000A +CRLF \u000D\u000A +ESCAPED_QUOTE \u0022 +DQUOTE \u0022 +TEXTDATA [\u0020-\u0021\u0023-\u002B\u002D-\u007E] + +%% + +"sankey-beta" { this.pushState('csv'); return 'SANKEY'; } +<> { return 'EOF' } // match end of file +({CRLF}|{LF}) { return 'NEWLINE' } +{COMMA} { return 'COMMA' } +{DQUOTE} { this.pushState('escaped_text'); return 'DQUOTE'; } +{TEXTDATA}* { return 'NON_ESCAPED_TEXT' } +{DQUOTE}(?!{DQUOTE}) {this.popState('escaped_text'); return 'DQUOTE'; } // unescaped DQUOTE closes string +({TEXTDATA}|{COMMA}|{CR}|{LF}|{DQUOTE}{DQUOTE})* { return 'ESCAPED_TEXT'; } + +/lex + +%start start + +%% // language grammar + +start: SANKEY NEWLINE csv opt_eof; + +csv: record csv_tail; +csv_tail: NEWLINE csv | ; +opt_eof: EOF | ; + +record + : field\[source] COMMA field\[target] COMMA field\[value] { + const source = yy.findOrCreateNode($source.trim().replaceAll('""', '"')); + const target = yy.findOrCreateNode($target.trim().replaceAll('""', '"')); + const value = parseFloat($value.trim()); + yy.addLink(source,target,value); + } // parse only 3 fields, this is not part of CSV standard + ; + +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..4517ca01f --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts @@ -0,0 +1,24 @@ +// @ts-ignore: jison doesn't export types +import sankey 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 () { + beforeEach(function () { + sankey.parser.yy = db; + sankey.parser.yy.clear(); + }); + + it('parses csv', async () => { + const csv = path.resolve(__dirname, './energy.csv'); + const data = fs.readFileSync(csv, 'utf8'); + const graphDefinition = prepareTextForParsing(cleanupComments('sankey-beta\n\n ' + data)); + + sankey.parser.parse(graphDefinition); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDB.ts b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts new file mode 100644 index 000000000..1921e1b85 --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/sankeyDB.ts @@ -0,0 +1,81 @@ +import * as configApi from '../../config.js'; +import common from '../common/common.js'; +import { + setAccTitle, + getAccTitle, + getAccDescription, + setAccDescription, + setDiagramTitle, + getDiagramTitle, + clear as commonClear, +} from '../../commonDb.js'; + +// Sankey diagram represented by nodes and links between those nodes +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 => { + links = []; + nodes = []; + nodesMap = {}; + commonClear(); +}; + +class SankeyLink { + constructor(public source: SankeyNode, public target: SankeyNode, public value: number = 0) {} +} + +/** + * @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: SankeyNode, target: SankeyNode, value: number): void => { + links.push(new SankeyLink(source, target, value)); +}; + +class SankeyNode { + constructor(public ID: string) {} +} + +const findOrCreateNode = (ID: string): SankeyNode => { + ID = common.sanitizeText(ID, configApi.getConfig()); + + if (!nodesMap[ID]) { + nodesMap[ID] = new SankeyNode(ID); + nodes.push(nodesMap[ID]); + } + return nodesMap[ID]; +}; + +const getNodes = () => nodes; +const getLinks = () => links; + +const getGraph = () => ({ + nodes: nodes.map((node) => ({ id: node.ID })), + links: links.map((link) => ({ + source: link.source.ID, + target: link.target.ID, + value: link.value, + })), +}); + +export default { + nodesMap, + getConfig: () => configApi.getConfig().sankey, + getNodes, + getLinks, + getGraph, + addLink, + findOrCreateNode, + getAccTitle, + setAccTitle, + getAccDescription, + setAccDescription, + getDiagramTitle, + setDiagramTitle, + clear, +}; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDetector.ts b/packages/mermaid/src/diagrams/sankey/sankeyDetector.ts new file mode 100644 index 000000000..73c4d1428 --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/sankeyDetector.ts @@ -0,0 +1,20 @@ +import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types.js'; + +const id = 'sankey'; + +const detector: DiagramDetector = (txt) => { + return /^\s*sankey-beta/.test(txt); +}; + +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/diagrams/sankey/sankeyDiagram.ts b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts new file mode 100644 index 000000000..d5b62122e --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts @@ -0,0 +1,15 @@ +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 renderer from './sankeyRenderer.js'; +import { prepareTextForParsing } from './sankeyUtils.js'; + +const originalParse = parser.parse.bind(parser); +parser.parse = (text: string) => originalParse(prepareTextForParsing(text)); + +export const diagram: DiagramDefinition = { + parser, + db, + renderer, +}; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts new file mode 100644 index 000000000..e6e18f113 --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -0,0 +1,205 @@ +import { Diagram } from '../../Diagram.js'; +import * as configApi from '../../config.js'; + +import { + select as d3select, + scaleOrdinal as d3scaleOrdinal, + schemeTableau10 as d3schemeTableau10, +} from 'd3'; + +import { + sankey as d3Sankey, + sankeyLinkHorizontal as d3SankeyLinkHorizontal, + sankeyLeft as d3SankeyLeft, + 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'; +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. + * + * @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): void { + // Get Sankey config + const { securityLevel, sankey: conf } = configApi.getConfig(); + const defaultSankeyConfig = configApi!.defaultConfig!.sankey!; + + // 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 + // + let sandboxElement: any; + if (securityLevel === 'sandbox') { + sandboxElement = d3select('#i' + id); + } + 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 + // + 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)` + // + // @ts-ignore TODO: svg type vs selection mismatch + configureSvgSize(svg, height, width, useMaxWidth); + + // Prepare data for construction based on diagObj.db + // This must be a mutable object with `nodes` and `links` properties: + // + // { + // "nodes": [ { "id": "Alice" }, { "id": "Bob" }, { "id": "Carol" } ], + // "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(); + + // Get alignment function + const nodeAlign = alignmentsMap[nodeAlignment]; + + // Construct and configure a Sankey generator + // That will be a function that calculates nodes and links dimensions + // + const nodeWidth = 10; + const sankey = d3Sankey() + .nodeId((d: any) => d.id) // we use 'id' property to identify node + .nodeWidth(nodeWidth) + .nodePadding(10) + .nodeAlign(nodeAlign) + .extent([ + [0, 0], + [width, height], + ]); + + // Compute the Sankey layout: calculate nodes and links positions + // Our `graph` object will be mutated by this and enriched with other properties + // + sankey(graph); + + // Get color scheme for the graph + const colorScheme = d3scaleOrdinal(d3schemeTableau10); + + // Create rectangles for nodes + svg + .append('g') + .attr('class', 'nodes') + .selectAll('.node') + .data(graph.nodes) + .join('g') + .attr('class', 'node') + .attr('id', (d: any) => (d.uid = Uid.next('node-')).id) + .attr('transform', function (d: any) { + return 'translate(' + d.x0 + ',' + d.y0 + ')'; + }) + .attr('x', (d: any) => d.x0) + .attr('y', (d: any) => d.y0) + .append('rect') + .attr('height', (d: any) => { + return d.y1 - d.y0; + }) + .attr('width', (d: any) => d.x1 - d.x0) + .attr('fill', (d: any) => colorScheme(d.id)); + + // Create labels for nodes + svg + .append('g') + .attr('class', 'node-labels') + .attr('font-family', 'sans-serif') + .attr('font-size', 14) + .selectAll('text') + .data(graph.nodes) + .join('text') + .attr('x', (d: any) => (d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)) + .attr('y', (d: any) => (d.y1 + d.y0) / 2) + .attr('dy', '0.35em') + .attr('text-anchor', (d: any) => (d.x0 < width / 2 ? 'start' : 'end')) + .text((d: any) => d.id); + + // Creates the paths that represent the links. + const link = 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'); + + const linkColor = conf?.linkColor || SankeyLinkColor.gradient; + + if (linkColor === SankeyLinkColor.gradient) { + const gradient = link + .append('linearGradient') + .attr('id', (d: any) => (d.uid = Uid.next('linearGradient-')).id) + .attr('gradientUnits', 'userSpaceOnUse') + .attr('x1', (d: any) => d.source.x1) + .attr('x2', (d: any) => d.target.x0); + + gradient + .append('stop') + .attr('offset', '0%') + .attr('stop-color', (d: any) => colorScheme(d.source.id)); + + gradient + .append('stop') + .attr('offset', '100%') + .attr('stop-color', (d: any) => colorScheme(d.target.id)); + } + + let coloring: any; + switch (linkColor) { + case SankeyLinkColor.gradient: + coloring = (d: any) => d.uid; + break; + case SankeyLinkColor.source: + coloring = (d: any) => colorScheme(d.source.id); + break; + case SankeyLinkColor.target: + coloring = (d: any) => colorScheme(d.target.id); + break; + default: + coloring = linkColor; + } + + link + .append('path') + .attr('d', d3SankeyLinkHorizontal()) + .attr('stroke', coloring) + .attr('stroke-width', (d: any) => Math.max(1, d.width)); +}; + +export default { + draw, +}; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyUtils.ts b/packages/mermaid/src/diagrams/sankey/sankeyUtils.ts new file mode 100644 index 000000000..45ecf21dd --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/sankeyUtils.ts @@ -0,0 +1,8 @@ +export const prepareTextForParsing = (text: string): string => { + const textToParse = text + .replaceAll(/^[^\S\n\r]+|[^\S\n\r]+$/g, '') // remove all trailing spaces for each row + .replaceAll(/([\n\r])+/g, '\n') // remove empty lines duplicated + .trim(); + + return textToParse; +}; diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 074cd5975..04f0de20e 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -38,6 +38,8 @@ "box" { this.begin('LINE'); return 'box'; } "participant" { this.begin('ID'); return 'participant'; } "actor" { this.begin('ID'); return 'participant_actor'; } +"create" return 'create'; +"destroy" { this.begin('ID'); return 'destroy'; } [^\->:\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'; } @@ -138,6 +140,7 @@ directive statement : participant_statement + | 'create' participant_statement {$2.type='createParticipant'; $$=$2;} | 'box' restOfLine box_section end { $3.unshift({type: 'boxStart', boxData:yy.parseBoxData($2) }); @@ -234,10 +237,11 @@ else_sections ; 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;} + : 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant' actor 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$$=$2;} + | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;} + | 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;} ; note_statement diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.js b/packages/mermaid/src/diagrams/sequence/sequenceDb.js index 89869b64f..b5d68fb9f 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.js @@ -14,12 +14,16 @@ import { let prevActor = undefined; let actors = {}; +let createdActors = {}; +let destroyedActors = {}; let boxes = []; let messages = []; const notes = []; let sequenceNumbersEnabled = false; let wrapEnabled; let currentBox = undefined; +let lastCreated = undefined; +let lastDestroyed = undefined; export const parseDirective = function (statement, context, type) { mermaidAPI.parseDirective(this, statement, context, type); @@ -165,6 +169,12 @@ export const getBoxes = function () { export const getActors = function () { return actors; }; +export const getCreatedActors = function () { + return createdActors; +}; +export const getDestroyedActors = function () { + return destroyedActors; +}; export const getActor = function (id) { return actors[id]; }; @@ -194,6 +204,8 @@ export const autoWrap = () => { export const clear = function () { actors = {}; + createdActors = {}; + destroyedActors = {}; boxes = []; messages = []; sequenceNumbersEnabled = false; @@ -459,10 +471,21 @@ export const apply = function (param) { }); break; case 'addParticipant': - addActor(param.actor, param.actor, param.description, 'participant'); + addActor(param.actor, param.actor, param.description, param.draw); break; - case 'addActor': - addActor(param.actor, param.actor, param.description, 'actor'); + case 'createParticipant': + if (actors[param.actor]) { + throw new Error( + "It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior" + ); + } + lastCreated = param.actor; + addActor(param.actor, param.actor, param.description, param.draw); + createdActors[param.actor] = messages.length; + break; + case 'destroyParticipant': + lastDestroyed = param.actor; + destroyedActors[param.actor] = messages.length; break; case 'activeStart': addSignal(param.actor, undefined, undefined, param.signalType); @@ -486,6 +509,27 @@ export const apply = function (param) { addDetails(param.actor, param.text); break; case 'addMessage': + if (lastCreated) { + if (param.to !== lastCreated) { + throw new Error( + 'The created participant ' + + lastCreated + + ' does not have an associated creating message after its declaration. Please check the sequence diagram.' + ); + } else { + lastCreated = undefined; + } + } else if (lastDestroyed) { + if (param.to !== lastDestroyed && param.from !== lastDestroyed) { + throw new Error( + 'The destroyed participant ' + + lastDestroyed + + ' does not have an associated destroying message after its declaration. Please check the sequence diagram.' + ); + } else { + lastDestroyed = undefined; + } + } addSignal(param.from, param.to, param.msg, param.signalType); break; case 'boxStart': @@ -566,6 +610,8 @@ export default { showSequenceNumbers, getMessages, getActors, + getCreatedActors, + getDestroyedActors, getActor, getActorKeys, getActorProperty, diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index baa5d2fcf..8b7d6f8d0 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -1404,6 +1404,62 @@ link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com expect(boxes[0].actorKeys).toEqual(['a', 'b']); expect(boxes[0].fill).toEqual('Aqua'); }); + + it('should handle simple actor creation', async () => { + const str = ` + sequenceDiagram + participant a as Alice + a ->>b: Hello Bob? + create participant c + b-->>c: Hello c! + c ->> b: Hello b? + create actor d as Donald + a ->> d: Hello Donald? + `; + await mermaidAPI.parse(str); + const actors = diagram.db.getActors(); + const createdActors = diagram.db.getCreatedActors(); + expect(actors['c'].name).toEqual('c'); + expect(actors['c'].description).toEqual('c'); + expect(actors['c'].type).toEqual('participant'); + expect(createdActors['c']).toEqual(1); + expect(actors['d'].name).toEqual('d'); + expect(actors['d'].description).toEqual('Donald'); + expect(actors['d'].type).toEqual('actor'); + expect(createdActors['d']).toEqual(3); + }); + it('should handle simple actor destruction', async () => { + const str = ` + sequenceDiagram + participant a as Alice + a ->>b: Hello Bob? + destroy a + b-->>a: Hello Alice! + b ->> c: Where is Alice? + destroy c + b ->> c: Where are you? + `; + await mermaidAPI.parse(str); + const destroyedActors = diagram.db.getDestroyedActors(); + expect(destroyedActors['a']).toEqual(1); + expect(destroyedActors['c']).toEqual(3); + }); + it('should handle the creation and destruction of the same actor', async () => { + const str = ` + sequenceDiagram + a ->>b: Hello Bob? + create participant c + b ->>c: Hello c! + c ->> b: Hello b? + destroy c + b ->> c : Bye c ! + `; + await mermaidAPI.parse(str); + const createdActors = diagram.db.getCreatedActors(); + const destroyedActors = diagram.db.getDestroyedActors(); + expect(createdActors['c']).toEqual(1); + expect(destroyedActors['c']).toEqual(3); + }); }); describe('when checking the bounds in a sequenceDiagram', function () { beforeAll(() => { @@ -1973,7 +2029,9 @@ participant Alice`; expect(bounds.startx).toBe(0); expect(bounds.starty).toBe(0); expect(bounds.stopx).toBe(conf.width); - expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height + conf.boxMargin); + expect(bounds.stopy).toBe( + models.lastActor().stopy + models.lastActor().height + conf.boxMargin + ); }); }); }); @@ -2025,7 +2083,7 @@ participant Alice expect(bounds.startx).toBe(0); expect(bounds.starty).toBe(0); expect(bounds.stopy).toBe( - models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin + models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin ); }); it('should handle one actor, when logLevel is 3 (dfg0)', async () => { @@ -2045,7 +2103,7 @@ participant Alice expect(bounds.startx).toBe(0); expect(bounds.starty).toBe(0); expect(bounds.stopy).toBe( - models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin + models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin ); }); it('should hide sequence numbers when autonumber is removed when autonumber is enabled', async () => { diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index 16a3d21fc..4f8b1889b 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -1,6 +1,6 @@ // @ts-nocheck TODO: fix file import { select, selectAll } from 'd3'; -import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw.js'; +import svgDraw, { ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js'; import { log } from '../../logger.js'; import common from '../common/common.js'; import * as svgDrawCommon from '../common/svgDrawCommon'; @@ -478,29 +478,19 @@ const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Di } }; -export const drawActors = function ( +const addActorRenderingData = function ( diagram, actors, + createdActors, 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; + let maxHeight = 0; for (const actorKey of actorKeys) { const actor = actors[actorKey]; @@ -528,12 +518,16 @@ export const drawActors = function ( actor.height = common.getMax(actor.height || conf.height, conf.height); actor.margin = actor.margin || conf.actorMargin; - actor.x = prevWidth + prevMargin; - actor.y = bounds.getVerticalPos(); + maxHeight = common.getMax(maxHeight, actor.height); + + // if the actor is created by a message, widen margin + if (createdActors[actor.name]) { + prevMargin += actor.width / 2; + } + + actor.x = prevWidth + prevMargin; + actor.starty = 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; @@ -554,6 +548,28 @@ export const drawActors = function ( bounds.bumpVerticalPos(maxHeight); }; +export const drawActors = function (diagram, actors, actorKeys, isFooter) { + if (!isFooter) { + for (const actorKey of actorKeys) { + const actor = actors[actorKey]; + // Draw the box with the attached line + svgDraw.drawActor(diagram, actor, conf, false); + } + } else { + let maxHeight = 0; + bounds.bumpVerticalPos(conf.boxMargin * 2); + for (const actorKey of actorKeys) { + const actor = actors[actorKey]; + if (!actor.stopy) { + actor.stopy = bounds.getVerticalPos(); + } + const height = svgDraw.drawActor(diagram, actor, conf, true); + maxHeight = common.getMax(maxHeight, height); + } + bounds.bumpVerticalPos(maxHeight + conf.boxMargin); + } +}; + export const drawActorsPopup = function (diagram, actors, actorKeys, doc) { let maxHeight = 0; let maxWidth = 0; @@ -633,6 +649,95 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop bounds.bumpVerticalPos(heightAdjust); } +/** + * Adjust the msgModel and the actor for the rendering in case the latter is created or destroyed by the msg + * @param msg - the potentially creating or destroying message + * @param msgModel - the model associated with the message + * @param lineStartY - the y position of the message line + * @param index - the index of the current actor under consideration + * @param actors - the array of all actors + * @param createdActors - the array of actors created in the diagram + * @param destroyedActors - the array of actors destroyed in the diagram + */ +function adjustCreatedDestroyedData( + msg, + msgModel, + lineStartY, + index, + actors, + createdActors, + destroyedActors +) { + function receiverAdjustment(actor, adjustment) { + if (actor.x < actors[msg.from].x) { + bounds.insert( + msgModel.stopx - adjustment, + msgModel.starty, + msgModel.startx, + msgModel.stopy + actor.height / 2 + conf.noteMargin + ); + msgModel.stopx = msgModel.stopx + adjustment; + } else { + bounds.insert( + msgModel.startx, + msgModel.starty, + msgModel.stopx + adjustment, + msgModel.stopy + actor.height / 2 + conf.noteMargin + ); + msgModel.stopx = msgModel.stopx - adjustment; + } + } + + function senderAdjustment(actor, adjustment) { + if (actor.x < actors[msg.to].x) { + bounds.insert( + msgModel.startx - adjustment, + msgModel.starty, + msgModel.stopx, + msgModel.stopy + actor.height / 2 + conf.noteMargin + ); + msgModel.startx = msgModel.startx + adjustment; + } else { + bounds.insert( + msgModel.stopx, + msgModel.starty, + msgModel.startx + adjustment, + msgModel.stopy + actor.height / 2 + conf.noteMargin + ); + msgModel.startx = msgModel.startx - adjustment; + } + } + + // if it is a create message + if (createdActors[msg.to] == index) { + const actor = actors[msg.to]; + const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3; + receiverAdjustment(actor, adjustment); + actor.starty = lineStartY - actor.height / 2; + bounds.bumpVerticalPos(actor.height / 2); + } + // if it is a destroy sender message + else if (destroyedActors[msg.from] == index) { + const actor = actors[msg.from]; + if (conf.mirrorActors) { + const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2; + senderAdjustment(actor, adjustment); + } + actor.stopy = lineStartY - actor.height / 2; + bounds.bumpVerticalPos(actor.height / 2); + } + // if it is a destroy receiver message + else if (destroyedActors[msg.to] == index) { + const actor = actors[msg.to]; + if (conf.mirrorActors) { + const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3; + receiverAdjustment(actor, adjustment); + } + actor.stopy = lineStartY - actor.height / 2; + bounds.bumpVerticalPos(actor.height / 2); + } +} + /** * Draws a sequenceDiagram in the tag with id: id based on the graph definition in text. * @@ -666,8 +771,10 @@ export const draw = function (_text: string, id: string, _version: string, diagO // Fetch data from the parsing const actors = diagObj.db.getActors(); + const createdActors = diagObj.db.getCreatedActors(); + const destroyedActors = diagObj.db.getDestroyedActors(); const boxes = diagObj.db.getBoxes(); - const actorKeys = diagObj.db.getActorKeys(); + let actorKeys = diagObj.db.getActorKeys(); const messages = diagObj.db.getMessages(); const title = diagObj.db.getDiagramTitle(); const hasBoxes = diagObj.db.hasAtLeastOneBox(); @@ -686,7 +793,16 @@ export const draw = function (_text: string, id: string, _version: string, diagO } } - drawActors(diagram, actors, actorKeys, 0, conf, messages, false); + if (conf.hideUnusedParticipants === true) { + const newActors = new Set(); + messages.forEach((message) => { + newActors.add(message.from); + newActors.add(message.to); + }); + actorKeys = actorKeys.filter((actorKey) => newActors.has(actorKey)); + } + + addActorRenderingData(diagram, actors, createdActors, actorKeys, 0, messages, false); const loopWidths = calculateLoopBounds(messages, actors, maxMessageWidthPerActor, diagObj); // The arrow head definition is attached to the svg once @@ -720,7 +836,8 @@ export const draw = function (_text: string, id: string, _version: string, diagO let sequenceIndex = 1; let sequenceIndexStep = 1; const messagesToDraw = []; - messages.forEach(function (msg) { + const backgrounds = []; + messages.forEach(function (msg, index) { let loopModel, noteModel, msgModel; switch (msg.type) { @@ -757,7 +874,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO break; case diagObj.db.LINETYPE.RECT_END: loopModel = bounds.endLoop(); - svgDraw.drawBackgroundRect(diagram, loopModel); + backgrounds.push(loopModel); bounds.models.addLoop(loopModel); bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); break; @@ -876,13 +993,20 @@ export const draw = function (_text: string, id: string, _version: string, diagO 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); + adjustCreatedDestroyedData( + msg, + msgModel, + lineStartY, + index, + actors, + createdActors, + destroyedActors + ); messagesToDraw.push({ messageModel: msgModel, lineStartY: lineStartY }); bounds.models.addMessage(msgModel); } catch (e) { @@ -907,15 +1031,16 @@ export const draw = function (_text: string, id: string, _version: string, diagO } }); - messagesToDraw.forEach((e) => drawMessage(diagram, e.messageModel, e.lineStartY, diagObj)); + log.debug('createdActors', createdActors); + log.debug('destroyedActors', destroyedActors); + drawActors(diagram, actors, actorKeys, false); + 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()); + drawActors(diagram, actors, actorKeys, true); } + backgrounds.forEach((e) => svgDraw.drawBackgroundRect(diagram, e)); + fixLifeLineHeights(diagram, actors, actorKeys, conf); bounds.models.boxes.forEach(function (box) { box.height = bounds.getVerticalPos() - box.y; @@ -937,11 +1062,6 @@ export const draw = function (_text: string, id: string, _version: string, diagO 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) { diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index a3f7514f6..0c7bc6421 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -4,6 +4,8 @@ import { addFunction } from '../../interactionDb.js'; import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js'; import { sanitizeUrl } from '@braintree/sanitize-url'; +export const ACTOR_TYPE_WIDTH = 18 * 2; + export const drawRect = function (elem, rectData) { return svgDrawCommon.drawRect(elem, rectData); }; @@ -294,14 +296,19 @@ export const drawLabel = function (elem, txtObject) { let actorCnt = -1; -export const fixLifeLineHeights = (diagram, bounds) => { - if (!diagram.selectAll) { +export const fixLifeLineHeights = (diagram, actors, actorKeys, conf) => { + if (!diagram.select) { return; } - diagram - .selectAll('.actor-line') - .attr('class', '200') - .attr('y2', bounds - 55); + actorKeys.forEach((actorKey) => { + const actor = actors[actorKey]; + const actorDOM = diagram.select('#actor' + actor.actorCnt); + if (!conf.mirrorActors && actor.stopy) { + actorDOM.attr('y2', actor.stopy + actor.height / 2); + } else if (conf.mirrorActors) { + actorDOM.attr('y2', actor.stopy); + } + }); }; /** @@ -313,10 +320,11 @@ export const fixLifeLineHeights = (diagram, bounds) => { * @param {boolean} isFooter - If the actor is the footer one */ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; const center = actor.x + actor.width / 2; - const centerY = actor.y + 5; + const centerY = actorY + 5; - const boxpluslineGroup = elem.append('g'); + const boxpluslineGroup = elem.append('g').lower(); var g = boxpluslineGroup; if (!isFooter) { @@ -328,6 +336,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { .attr('x2', center) .attr('y2', 2000) .attr('class', 'actor-line') + .attr('class', '200') .attr('stroke-width', '0.5px') .attr('stroke', '#999'); @@ -348,7 +357,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { rect.fill = '#eaeaea'; } rect.x = actor.x; - rect.y = actor.y; + rect.y = actorY; rect.width = actor.width; rect.height = actor.height; rect.class = cssclass; @@ -388,8 +397,11 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { }; const drawActorTypeActor = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; const center = actor.x + actor.width / 2; - const centerY = actor.y + 80; + const centerY = actorY + 80; + + elem.lower(); if (!isFooter) { actorCnt++; @@ -401,15 +413,18 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) { .attr('x2', center) .attr('y2', 2000) .attr('class', 'actor-line') + .attr('class', '200') .attr('stroke-width', '0.5px') .attr('stroke', '#999'); + + actor.actorCnt = actorCnt; } const actElem = elem.append('g'); actElem.attr('class', 'actor-man'); const rect = svgDrawCommon.getNoteRect(); rect.x = actor.x; - rect.y = actor.y; + rect.y = actorY; rect.fill = '#eaeaea'; rect.width = actor.width; rect.height = actor.height; @@ -421,33 +436,33 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) { .append('line') .attr('id', 'actor-man-torso' + actorCnt) .attr('x1', center) - .attr('y1', actor.y + 25) + .attr('y1', actorY + 25) .attr('x2', center) - .attr('y2', actor.y + 45); + .attr('y2', actorY + 45); actElem .append('line') .attr('id', 'actor-man-arms' + actorCnt) - .attr('x1', center - 18) - .attr('y1', actor.y + 33) - .attr('x2', center + 18) - .attr('y2', actor.y + 33); + .attr('x1', center - ACTOR_TYPE_WIDTH / 2) + .attr('y1', actorY + 33) + .attr('x2', center + ACTOR_TYPE_WIDTH / 2) + .attr('y2', actorY + 33); actElem .append('line') - .attr('x1', center - 18) - .attr('y1', actor.y + 60) + .attr('x1', center - ACTOR_TYPE_WIDTH / 2) + .attr('y1', actorY + 60) .attr('x2', center) - .attr('y2', actor.y + 45); + .attr('y2', actorY + 45); actElem .append('line') .attr('x1', center) - .attr('y1', actor.y + 45) - .attr('x2', center + 16) - .attr('y2', actor.y + 60); + .attr('y1', actorY + 45) + .attr('x2', center + ACTOR_TYPE_WIDTH / 2 - 2) + .attr('y2', actorY + 60); const circle = actElem.append('circle'); circle.attr('cx', actor.x + actor.width / 2); - circle.attr('cy', actor.y + 10); + circle.attr('cy', actorY + 10); circle.attr('r', 15); circle.attr('width', actor.width); circle.attr('height', actor.height); 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' }, ], }, 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/.vitepress/scripts/fetch-contributors.ts b/packages/mermaid/src/docs/.vitepress/scripts/fetch-contributors.ts index fd5409d0f..a810fc30f 100644 --- a/packages/mermaid/src/docs/.vitepress/scripts/fetch-contributors.ts +++ b/packages/mermaid/src/docs/.vitepress/scripts/fetch-contributors.ts @@ -10,23 +10,27 @@ 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/packages/mermaid/src/docs/config/theming.md b/packages/mermaid/src/docs/config/theming.md index 0e4571d15..0e0853283 100644 --- a/packages/mermaid/src/docs/config/theming.md +++ b/packages/mermaid/src/docs/config/theming.md @@ -55,9 +55,9 @@ To make a custom theme, modify `themeVariables` via `init`. You will need to use the [base](#available-themes) theme as it is the only modifiable theme. -| Parameter | Description | Type | Properties | -| -------------- | ------------------------------------ | ------ | --------------------------------------------------------------------------------------------------- | -| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables-reference-table)) | +| Parameter | Description | Type | Properties | +| -------------- | ------------------------------------ | ------ | ----------------------------------------------------------------------------------- | +| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables)) | Example of modifying `themeVariables` using the `init` directive: diff --git a/packages/mermaid/src/docs/news/announcements.md b/packages/mermaid/src/docs/news/announcements.md index b752d3d57..83dafaab6 100644 --- a/packages/mermaid/src/docs/news/announcements.md +++ b/packages/mermaid/src/docs/news/announcements.md @@ -1,7 +1,7 @@ # Announcements -## [subhash-halder contributed quadrant charts so you can show your manager what to select - just like the strategy consultants at BCG do](https://www.mermaidchart.com/blog/posts/subhash-halder-contributed-quadrant-charts-so-you-can-show-your-manager-what-to-select-just-like-the-strategy-consultants-at-bcg-do/) +## [Sequence diagrams, the only good thing UML brought to software development](https://www.mermaidchart.com/blog/posts/sequence-diagrams-the-good-thing-uml-brought-to-software-development/) -8 June 2023 · 7 mins +15 June 2023 · 12 mins -A quadrant chart is a useful diagram that helps users visualize data and identify patterns in a data set. +Sequence diagrams really shine when you’re documenting different parts of a system and the various ways these parts interact with each other. diff --git a/packages/mermaid/src/docs/news/blog.md b/packages/mermaid/src/docs/news/blog.md index e62a327b4..61d53656d 100644 --- a/packages/mermaid/src/docs/news/blog.md +++ b/packages/mermaid/src/docs/news/blog.md @@ -1,5 +1,11 @@ # Blog +## [Sequence diagrams, the only good thing UML brought to software development](https://www.mermaidchart.com/blog/posts/sequence-diagrams-the-good-thing-uml-brought-to-software-development/) + +15 June 2023 · 12 mins + +Sequence diagrams really shine when you’re documenting different parts of a system and the various ways these parts interact with each other. + ## [subhash-halder contributed quadrant charts so you can show your manager what to select - just like the strategy consultants at BCG do](https://www.mermaidchart.com/blog/posts/subhash-halder-contributed-quadrant-charts-so-you-can-show-your-manager-what-to-select-just-like-the-strategy-consultants-at-bcg-do/) 8 June 2023 · 7 mins diff --git a/packages/mermaid/src/docs/syntax/sankey.md b/packages/mermaid/src/docs/syntax/sankey.md new file mode 100644 index 000000000..32f3663b9 --- /dev/null +++ b/packages/mermaid/src/docs/syntax/sankey.md @@ -0,0 +1,292 @@ +# 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 +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 +``` + +::: + +## Syntax + +The idea behind syntax is that a user types `sankey-beta` keyword first, then pastes raw CSV below and get the result. + +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 + +### Basic + +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,Losses,26.862 + +Bio-conversion,Solid,280.322 + +Bio-conversion,Gas,81.144 +``` + +```mermaid +sankey-beta + +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 +``` + +### Double Quotes + +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, ""commercial""",70.672 +``` + +```mermaid +sankey-beta + +Pumped heat,"Heating and cooling, ""homes""",193.026 +Pumped heat,"Heating and cooling, ""commercial""",70.672 +``` + +## Configuration + +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` diff --git a/packages/mermaid/src/docs/syntax/sequenceDiagram.md b/packages/mermaid/src/docs/syntax/sequenceDiagram.md index 0d5442129..e061a192e 100644 --- a/packages/mermaid/src/docs/syntax/sequenceDiagram.md +++ b/packages/mermaid/src/docs/syntax/sequenceDiagram.md @@ -58,6 +58,31 @@ sequenceDiagram J->>A: Great! ``` +### Actor Creation and Destruction + +It is possible to create and destroy actors by messages. To do so, add a create or destroy directive before the message. + +``` +create participant B +A --> B: Hello +``` + +Create directives support actor/participant distinction and aliases. The sender or the recipient of a message can be destroyed but only the recipient can be created. + +```mermaid-example +sequenceDiagram + Alice->>Bob: Hello Bob, how are you ? + Bob->>Alice: Fine, thank you. And you? + create participant Carl + Alice->>Carl: Hi Carl! + create actor D as Donald + Carl->>D: Hi! + destroy Carl + Alice-xCarl: We are too many + destroy Bob + Bob->>Alice: I agree +``` + ### Grouping / Box The actor(s) can be grouped in vertical boxes. You can define a color (if not, it will be transparent) and/or a descriptive label using the following notation: diff --git a/packages/mermaid/src/docs/syntax/stateDiagram.md b/packages/mermaid/src/docs/syntax/stateDiagram.md index 248146993..f35796b13 100644 --- a/packages/mermaid/src/docs/syntax/stateDiagram.md +++ b/packages/mermaid/src/docs/syntax/stateDiagram.md @@ -304,7 +304,7 @@ where - the second _property_ is `color` and its _value_ is `white` - the third _property_ is `font-weight` and its _value_ is `bold` - the fourth _property_ is `stroke-width` and its _value_ is `2px` -- the fifth _property_ is `stroke` and its _value_ is `yello` +- the fifth _property_ is `stroke` and its _value_ is `yellow` ### Apply classDef styles to states 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/pnpm-lock.yaml b/pnpm-lock.yaml index 1a316dd7f..9d28e9e50 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -194,6 +194,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 @@ -206,6 +212,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 @@ -213,8 +222,8 @@ importers: specifier: ^1.11.7 version: 1.11.7 dompurify: - specifier: 3.0.3 - version: 3.0.3 + specifier: 3.0.4 + version: 3.0.4 elkjs: specifier: ^0.8.2 version: 0.8.2 @@ -249,6 +258,9 @@ importers: '@types/d3': specifier: ^7.4.0 version: 7.4.0 + '@types/d3-sankey': + specifier: ^0.12.1 + version: 0.12.1 '@types/d3-selection': specifier: ^3.0.5 version: 3.0.5 @@ -451,31 +463,6 @@ importers: specifier: ^7.0.0 version: 7.0.0 - packages/mermaid/src/vitepress: - dependencies: - '@vueuse/core': - specifier: ^10.1.0 - version: 10.1.0(vue@3.2.47) - vue: - specifier: ^3.2.47 - version: 3.2.47 - devDependencies: - pathe: - specifier: ^1.1.0 - version: 1.1.0 - unocss: - specifier: ^0.53.0 - version: 0.53.0(postcss@8.4.23)(vite@4.3.9) - unplugin-vue-components: - specifier: ^0.25.0 - version: 0.25.0(rollup@2.79.1)(vue@3.2.47) - vite: - specifier: ^4.3.3 - version: 4.3.9(@types/node@18.16.0) - vitepress: - specifier: 1.0.0-beta.2 - version: 1.0.0-beta.2(@algolia/client-search@4.14.2)(@types/node@18.16.0)(search-insights@2.6.0) - tests/webpack: dependencies: '@mermaid-js/mermaid-example-diagram': @@ -4009,6 +3996,10 @@ packages: '@types/d3-color': 3.1.0 dev: true + /@types/d3-path@1.0.9: + resolution: {integrity: sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==} + dev: true + /@types/d3-path@3.0.0: resolution: {integrity: sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==} dev: true @@ -4025,20 +4016,30 @@ packages: resolution: {integrity: sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==} dev: true - /@types/d3-scale-chromatic@3.0.0: - resolution: {integrity: sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==} + /@types/d3-sankey@0.12.1: + resolution: {integrity: sha512-10X6l6lXB42udBNX9/fDN+kJuooifSMk7+x4U9815eobavldqis4wDdFQUQjMazh+qlzsUZsGzXKxfWFUVt+3w==} + dependencies: + '@types/d3-shape': 1.3.8 dev: true - /@types/d3-scale@4.0.2: - resolution: {integrity: sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==} + /@types/d3-scale-chromatic@3.0.0: + resolution: {integrity: sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==} + + /@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==} dev: true + /@types/d3-shape@1.3.8: + resolution: {integrity: sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg==} + dependencies: + '@types/d3-path': 1.0.9 + dev: true + /@types/d3-shape@3.1.0: resolution: {integrity: sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==} dependencies: @@ -4051,7 +4052,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==} @@ -4094,7 +4094,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 @@ -4685,17 +4685,6 @@ packages: - vite dev: true - /@unocss/astro@0.53.0(vite@4.3.9): - resolution: {integrity: sha512-8bR7ysIMZEOpcjd/cVmogcABSFDYPjUqMnbflv44p1A2/deemo9CIkpRARoq/96NQuzWJsKhKodcQodExZcqiA==} - dependencies: - '@unocss/core': 0.53.0 - '@unocss/reset': 0.53.0 - '@unocss/vite': 0.53.0(vite@4.3.9) - transitivePeerDependencies: - - rollup - - vite - dev: true - /@unocss/cli@0.53.0(rollup@2.79.1): resolution: {integrity: sha512-9WNBHy8m8tMqwcp7mUhebRUBvHQfbx01CMe5cAFLmUYtJULM+8IjJxqERkaAZyyoOXf1TNO2v1dFAmCwhMRCLQ==} engines: {node: '>=14'} @@ -4874,26 +4863,6 @@ packages: - rollup dev: true - /@unocss/vite@0.53.0(vite@4.3.9): - resolution: {integrity: sha512-JoZhKVNruRjfySMVg/zNJbLEn/NTXj29Wf0SN4++xnGKrSapkPzYC46psL5bm5N5v4SHdpepTCoonC3FWCY6Fw==} - peerDependencies: - vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 - dependencies: - '@ampproject/remapping': 2.2.1 - '@rollup/pluginutils': 5.0.2(rollup@2.79.1) - '@unocss/config': 0.53.0 - '@unocss/core': 0.53.0 - '@unocss/inspector': 0.53.0 - '@unocss/scope': 0.53.0 - '@unocss/transformer-directives': 0.53.0 - chokidar: 3.5.3 - fast-glob: 3.2.12 - magic-string: 0.30.0 - vite: 4.3.9(@types/node@18.16.0) - transitivePeerDependencies: - - rollup - dev: true - /@vite-pwa/vitepress@0.2.0(vite-plugin-pwa@0.16.0): resolution: {integrity: sha512-dVQVaP6NB9woCFe4UASUqRp7uwBQJOVXlJlqK4krqXcbb3NuXIXIWOnU7HLpJnHqZj5U/81gKtLN6gs5gJBwiQ==} peerDependencies: @@ -7200,6 +7169,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: false + /d3-array@3.2.0: resolution: {integrity: sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==} engines: {node: '>=12'} @@ -7317,6 +7292,10 @@ packages: d3-color: 3.1.0 dev: false + /d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + dev: false + /d3-path@3.0.1: resolution: {integrity: sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==} engines: {node: '>=12'} @@ -7337,6 +7316,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: false + /d3-scale-chromatic@3.0.0: resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==} engines: {node: '>=12'} @@ -7361,6 +7347,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: false + /d3-shape@3.1.0: resolution: {integrity: sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==} engines: {node: '>=12'} @@ -7765,8 +7757,8 @@ packages: domelementtype: 2.3.0 dev: true - /dompurify@3.0.3: - resolution: {integrity: sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ==} + /dompurify@3.0.4: + resolution: {integrity: sha512-ae0mA+Qiqp6C29pqZX3fQgK+F91+F7wobM/v8DRzDqJdZJELXiFUx4PP4pK/mzUS0xkiSEx3Ncd9gr69jg3YsQ==} dev: false /domutils@3.0.1: @@ -9044,7 +9036,7 @@ packages: dependencies: foreground-child: 3.1.1 jackspeak: 2.1.1 - minimatch: 9.0.1 + minimatch: 9.0.0 minipass: 5.0.0 path-scurry: 1.7.0 dev: true @@ -9596,6 +9588,10 @@ packages: side-channel: 1.0.4 dev: true + /internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + dev: false + /internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} @@ -14636,42 +14632,6 @@ packages: - vite dev: true - /unocss@0.53.0(postcss@8.4.23)(vite@4.3.9): - resolution: {integrity: sha512-kY4h5ERiDYlSnL2X+hbDfh+uaF7QNouy7j51GOTUr3Q0aaWehaNd05b15SjHrab559dEC0mYfrSEdh/DnCK1cw==} - engines: {node: '>=14'} - peerDependencies: - '@unocss/webpack': 0.53.0 - peerDependenciesMeta: - '@unocss/webpack': - optional: true - dependencies: - '@unocss/astro': 0.53.0(vite@4.3.9) - '@unocss/cli': 0.53.0(rollup@2.79.1) - '@unocss/core': 0.53.0 - '@unocss/extractor-arbitrary-variants': 0.53.0 - '@unocss/postcss': 0.53.0(postcss@8.4.23) - '@unocss/preset-attributify': 0.53.0 - '@unocss/preset-icons': 0.53.0 - '@unocss/preset-mini': 0.53.0 - '@unocss/preset-tagify': 0.53.0 - '@unocss/preset-typography': 0.53.0 - '@unocss/preset-uno': 0.53.0 - '@unocss/preset-web-fonts': 0.53.0 - '@unocss/preset-wind': 0.53.0 - '@unocss/reset': 0.53.0 - '@unocss/transformer-attributify-jsx': 0.53.0 - '@unocss/transformer-attributify-jsx-babel': 0.53.0 - '@unocss/transformer-compile-class': 0.53.0 - '@unocss/transformer-directives': 0.53.0 - '@unocss/transformer-variant-group': 0.53.0 - '@unocss/vite': 0.53.0(vite@4.3.9) - transitivePeerDependencies: - - postcss - - rollup - - supports-color - - vite - dev: true - /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -15041,49 +15001,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 diff --git a/run b/run index 6afe76eee..d9f669cab 100755 --- a/run +++ b/run @@ -1,40 +1,93 @@ #!/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} case $command in sh) -$RUN mermaid sh $args +$RUN mermaid sh -c "npx $args" ;; -install) -$RUN mermaid sh -c "npx pnpm install" +pnpm) +$RUN mermaid sh -c "npx pnpm $args" ;; -test) -$RUN mermaid sh -c "npx pnpm test" +dev) +$RUN --service-ports mermaid sh -c "npx pnpm run dev" ;; -lint) -$RUN mermaid sh -c "npx pnpm -w run lint:fix" +docs:dev) +$RUN --service-ports mermaid sh -c "cd packages/mermaid/src/docs && npx pnpm prefetch && npx vitepress --port 3333 --host" +;; + +cypress) +$RUN cypress $args ;; help) + +# Alignment of help message must be as it is, it will be nice looking when printed +usage=$( cat <