Merge branch 'develop' into next

* develop: (124 commits)
  chore(deps): update all patch dependencies
  fix typo cutomers => customers
  chore(deps): update all minor dependencies
  Fix macOS onboarding issues
  Bump @zenuml/core and update render options in mermaid-zenuml (#5257)
  Fixed Typo in ErrorRenderer.ts (#5256)
  #3358 Removing redundant file
  #3358 Fix after review
  Fix selector
  #3358 Renaming of IOperation to ActionFun
  chore: Add interface naming
  build(deps-dev): bump vite from 4.4.12 to 4.5.2
  Update container
  Update container
  Remove pnpm cache
  3358 Adding types for blockArrowHelper
  #3358n Updated lockfile
  Update docs
  #3358 Another set of review changes
  Use `.node-version` file in workflows
  ...
This commit is contained in:
Sidharth Vinod 2024-02-11 19:56:55 +05:30
commit e047755a21
No known key found for this signature in database
GPG Key ID: FB5CCD378D3907CD
99 changed files with 5560 additions and 1368 deletions

View File

@ -1,7 +1,6 @@
import { load, JSON_SCHEMA } from 'js-yaml';
import assert from 'node:assert';
import Ajv2019, { type JSONSchemaType } from 'ajv/dist/2019.js';
import type { MermaidConfig, BaseDiagramConfig } from '../packages/mermaid/src/config.type.js';
/**
@ -24,6 +23,7 @@ const MERMAID_CONFIG_DIAGRAM_KEYS = [
'gitGraph',
'c4',
'sankey',
'block',
'packet',
] as const;

View File

@ -63,6 +63,17 @@ module.exports = {
minimumDescriptionLength: 10,
},
],
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'typeLike',
format: ['PascalCase'],
custom: {
regex: '^I[A-Z]',
match: false,
},
},
],
'json/*': ['error', 'allowComments'],
'@cspell/spellchecker': [
'error',

View File

@ -4,7 +4,7 @@ contact_links:
url: https://github.com/mermaid-js/mermaid/discussions
about: Ask the Community questions or share your own graphs in our discussions.
- name: Discord
url: https://discord.gg/wwtabKgp8y
url: https://discord.gg/AgrbSrBer3
about: Join our Community on Discord for Help and a casual chat.
- name: Documentation
url: https://mermaid.js.org

View File

@ -24,7 +24,7 @@ jobs:
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: 18
node-version-file: '.node-version'
- name: Install Packages
run: pnpm install --frozen-lockfile

View File

@ -15,20 +15,17 @@ permissions:
jobs:
build-mermaid:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
# uses version from "packageManager" field in package.json
- name: Setup Node.js ${{ matrix.node-version }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ matrix.node-version }}
node-version-file: '.node-version'
- name: Install Packages
run: |

View File

@ -21,9 +21,9 @@ env:
jobs:
e2e-applitools:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
container:
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
options: --user 1001
steps:
- if: ${{ ! env.USE_APPLI }}
name: Warn if not using Applitools
@ -35,10 +35,10 @@ jobs:
- uses: pnpm/action-setup@v2
# uses version from "packageManager" field in package.json
- name: Setup Node.js ${{ matrix.node-version }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
node-version-file: '.node-version'
- if: ${{ env.USE_APPLI }}
name: Notify applitools of new batch

View File

@ -23,13 +23,16 @@ env:
jobs:
cache:
runs-on: ubuntu-latest
container:
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
options: --user 1001
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18.x
node-version-file: '.node-version'
- name: Cache snapshots
id: cache-snapshot
uses: actions/cache@v4
@ -56,11 +59,13 @@ jobs:
e2e:
runs-on: ubuntu-latest
container:
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
options: --user 1001
needs: cache
strategy:
fail-fast: false
matrix:
node-version: [18.x]
containers: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
@ -68,10 +73,10 @@ jobs:
- uses: pnpm/action-setup@v2
# uses version from "packageManager" field in package.json
- name: Setup Node.js ${{ matrix.node-version }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
node-version-file: '.node-version'
# These cached snapshots are downloaded, providing the reference snapshots.
- name: Cache snapshots

View File

@ -36,7 +36,7 @@ jobs:
restore-keys: cache-lychee-
- name: Link Checker
uses: lycheeverse/lychee-action@v1.9.1
uses: lycheeverse/lychee-action@v1.9.3
with:
args: >-
--config .github/lychee.toml

View File

@ -16,20 +16,17 @@ permissions:
jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
# uses version from "packageManager" field in package.json
- name: Setup Node.js ${{ matrix.node-version }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ matrix.node-version }}
node-version-file: '.node-version'
- name: Install Packages
run: |

View File

@ -31,7 +31,7 @@ jobs:
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: 18
node-version-file: '.node-version'
- name: Install Packages
run: pnpm install --frozen-lockfile

View File

@ -19,7 +19,7 @@ jobs:
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: 18.x
node-version-file: '.node-version'
- name: Install Packages
run: |

View File

@ -14,11 +14,11 @@ jobs:
- uses: pnpm/action-setup@v2
# uses version from "packageManager" field in package.json
- name: Setup Node.js v18
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: 18.x
node-version-file: '.node-version'
- name: Install Packages
run: |

View File

@ -8,20 +8,17 @@ permissions:
jobs:
unit-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
# uses version from "packageManager" field in package.json
- name: Setup Node.js ${{ matrix.node-version }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ matrix.node-version }}
node-version-file: '.node-version'
- name: Install Packages
run: |

1
.node-version Normal file
View File

@ -0,0 +1 @@
v20.11.0

1
.npmrc
View File

@ -1,3 +1,4 @@
registry=https://registry.npmjs.org
auto-install-peers=true
strict-peer-dependencies=false
package-import-method=clone-or-copy

View File

@ -1,6 +1,7 @@
dist
cypress/platform/xss3.html
.cache
.pnpm-store
coverage
# Autogenerated by PNPM
pnpm-lock.yaml

View File

@ -1,2 +1,2 @@
FROM node:18.19.0-alpine3.18 AS base
FROM node:20.11.0-alpine3.19 AS base
RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" sh -

View File

@ -15,7 +15,7 @@ Generate diagrams from markdown-like text.
<a href="https://mermaid.live/"><b>Live Editor!</b></a>
</p>
<p align="center">
<a href="https://mermaid.js.org">📖 Documentation</a> | <a href="https://mermaid.js.org/intro/">🚀 Getting Started</a> | <a href="https://www.jsdelivr.com/package/npm/mermaid">🌐 CDN</a> | <a href="https://discord.gg/wwtabKgp8y" title="Discord invite">🙌 Join Us</a>
<a href="https://mermaid.js.org">📖 Documentation</a> | <a href="https://mermaid.js.org/intro/">🚀 Getting Started</a> | <a href="https://www.jsdelivr.com/package/npm/mermaid">🌐 CDN</a> | <a href="https://discord.gg/AgrbSrBer3" title="Discord invite">🙌 Join Us</a>
</p>
<p align="center">
<a href="./README.zh-CN.md">简体中文</a>
@ -33,7 +33,7 @@ Try Live Editor previews of future releases: <a href="https://develop.git.mermai
[![Coverage Status](https://codecov.io/github/mermaid-js/mermaid/branch/develop/graph/badge.svg)](https://app.codecov.io/github/mermaid-js/mermaid/tree/develop)
[![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid)
[![NPM Downloads](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid)
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/wwtabKgp8y)
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/AgrbSrBer3)
[![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=X)](https://twitter.com/mermaidjs_)
<img src="./img/header.png" alt="" />

View File

@ -15,7 +15,7 @@ Mermaid
<a href="https://mermaid.live/"><b>实时编辑器!</b></a>
</p>
<p align="center">
<a href="https://mermaid.js.org">📖 文档</a> | <a href="https://mermaid.js.org/intro/">🚀 入门</a> | <a href="https://www.jsdelivr.com/package/npm/mermaid">🌐 CDN</a> | <a href="https://discord.gg/wwtabKgp8y" title="Discord invite">🙌 加入我们</a>
<a href="https://mermaid.js.org">📖 文档</a> | <a href="https://mermaid.js.org/intro/">🚀 入门</a> | <a href="https://www.jsdelivr.com/package/npm/mermaid">🌐 CDN</a> | <a href="https://discord.gg/AgrbSrBer3" title="Discord invite">🙌 加入我们</a>
</p>
<p align="center">
<a href="./README.md">English</a>
@ -34,7 +34,7 @@ Mermaid
[![Coverage Status](https://codecov.io/github/mermaid-js/mermaid/branch/develop/graph/badge.svg)](https://app.codecov.io/github/mermaid-js/mermaid/tree/develop)
[![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid)
[![NPM Downloads](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid)
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/wwtabKgp8y)
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/AgrbSrBer3)
[![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=X)](https://twitter.com/mermaidjs_)
<img src="./img/header.png" alt="" />

View File

@ -28,6 +28,7 @@
"codedoc",
"codemia",
"colour",
"colours",
"commitlint",
"cpettitt",
"customizability",

View File

@ -0,0 +1,386 @@
import { imgSnapshotTest } from '../../helpers/util';
/* eslint-disable no-useless-escape */
describe('Block diagram', () => {
it('BL1: should calculate the block widths', () => {
imgSnapshotTest(
`block-beta
columns 2
block
id2["I am a wide one"]
id1
end
id["Next row"]
`
);
});
it('BL2: should handle colums statement in sub-blocks', () => {
imgSnapshotTest(
`block-beta
id1["Hello"]
block
columns 3
id2["to"]
id3["the"]
id4["World"]
id5["World"]
end
`,
{}
);
});
it('BL3: should align block widths and handle colums statement in sub-blocks', () => {
imgSnapshotTest(
`block-beta
block
columns 1
id1
id2
id2.1
end
id3
id4
`,
{}
);
});
it('BL4: should align block widths and handle colums statements in deeper sub-blocks then 1 level', () => {
imgSnapshotTest(
`block-beta
columns 1
block
columns 1
block
columns 3
id1
id2
id2.1(("XYZ"))
end
id48
end
id3
`,
{}
);
});
it('BL5: should align block widths and handle colums statements in deeper sub-blocks then 1 level (alt)', () => {
imgSnapshotTest(
`block-beta
columns 1
block
id1
id2
block
columns 1
id3("Wider then")
id5(("id5"))
end
end
id4
`,
{}
);
});
it('BL6: should handle block arrows and spece statements', () => {
imgSnapshotTest(
`block-beta
columns 3
space:3
ida idb idc
id1 id2
blockArrowId<["Label"]>(right)
blockArrowId2<["Label"]>(left)
blockArrowId3<["Label"]>(up)
blockArrowId4<["Label"]>(down)
blockArrowId5<["Label"]>(x)
blockArrowId6<["Label"]>(y)
blockArrowId6<["Label"]>(x, down)
`,
{}
);
});
it('BL7: should handle different types of edges', () => {
imgSnapshotTest(
`block-beta
columns 3
A space:5
A --o B
A --> C
A --x D
`,
{}
);
});
it('BL8: should handle sub-blocks without columns statements', () => {
imgSnapshotTest(
`block-beta
columns 2
C A B
block
D
E
end
`,
{}
);
});
it('BL9: should handle edges from blocks in sub blocks to other blocks', () => {
imgSnapshotTest(
`block-beta
columns 3
B space
block
D
end
D --> B
`,
{}
);
});
it('BL10: should handle edges from composite blocks', () => {
imgSnapshotTest(
`block-beta
columns 3
B space
block BL
D
end
BL --> B
`,
{}
);
});
it('BL11: should handle edges to composite blocks', () => {
imgSnapshotTest(
`block-beta
columns 3
B space
block BL
D
end
B --> BL
`,
{}
);
});
it('BL12: edges should handle labels', () => {
imgSnapshotTest(
`block-beta
A
space
A -- "apa" --> E
`,
{}
);
});
it('BL13: should handle block arrows in different directions', () => {
imgSnapshotTest(
`block-beta
columns 3
space blockArrowId1<["down"]>(down) space
blockArrowId2<["right"]>(right) blockArrowId3<["Sync"]>(x, y) blockArrowId4<["left"]>(left)
space blockArrowId5<["up"]>(up) space
blockArrowId6<["x"]>(x) space blockArrowId7<["y"]>(y)
`,
{}
);
});
it('BL14: should style statements and class statements', () => {
imgSnapshotTest(
`block-beta
A
B
classDef blue fill:#66f,stroke:#333,stroke-width:2px;
class A blue
style B fill:#f9F,stroke:#333,stroke-width:4px
`,
{}
);
});
it('BL15: width alignment - D and E should share available space', () => {
imgSnapshotTest(
`block-beta
block
D
E
end
db("This is the text in the box")
`,
{}
);
});
it('BL16: width alignment - C should be as wide as the composite block', () => {
imgSnapshotTest(
`block-beta
block
A("This is the text")
B
end
C
`,
{}
);
});
it('BL16: width alignment - blocks shold be equal in width', () => {
imgSnapshotTest(
`block-beta
A("This is the text")
B
C
`,
{}
);
});
it('BL17: block types 1 - square, rounded and circle', () => {
imgSnapshotTest(
`block-beta
A["square"]
B("rounded")
C(("circle"))
`,
{}
);
});
it('BL18: block types 2 - odd, diamond and hexagon', () => {
imgSnapshotTest(
`block-beta
A>"rect_left_inv_arrow"]
B{"diamond"}
C{{"hexagon"}}
`,
{}
);
});
it('BL19: block types 3 - stadium', () => {
imgSnapshotTest(
`block-beta
A(["stadium"])
`,
{}
);
});
it('BL20: block types 4 - lean right, lean left, trapezoid and inv trapezoid', () => {
imgSnapshotTest(
`block-beta
A[/"lean right"/]
B[\"lean left"\]
C[/"trapezoid"\]
D[\"trapezoid alt"/]
`,
{}
);
});
it('BL21: block types 1 - square, rounded and circle', () => {
imgSnapshotTest(
`block-beta
A["square"]
B("rounded")
C(("circle"))
`,
{}
);
});
it('BL22: sizing - it should be possible to make a block wider', () => {
imgSnapshotTest(
`block-beta
A("rounded"):2
B:2
C
`,
{}
);
});
it('BL23: sizing - it should be possible to make a composite block wider', () => {
imgSnapshotTest(
`block-beta
block:2
A
end
B
`,
{}
);
});
it('BL24: block in the middle with space on each side', () => {
imgSnapshotTest(
`block-beta
columns 3
space
middle["In the middle"]
space
`,
{}
);
});
it('BL25: space and an edge', () => {
imgSnapshotTest(
`block-beta
columns 5
A space B
A --x B
`,
{}
);
});
it('BL26: block sizes for regular blocks', () => {
imgSnapshotTest(
`block-beta
columns 3
a["A wide one"] b:2 c:2 d
`,
{}
);
});
it('BL27: composite block with a set width - f should use the available space', () => {
imgSnapshotTest(
`block-beta
columns 3
a:3
block:e:3
f
end
g
`,
{}
);
});
it('BL23: composite block with a set width - f and g should split the available space', () => {
imgSnapshotTest(
`block-beta
columns 3
a:3
block:e:3
f
g
end
h
i
j
`,
{}
);
});
});

View File

@ -583,4 +583,106 @@ describe('Gantt diagram', () => {
{}
);
});
it("should render when there's a semicolon in the title", () => {
imgSnapshotTest(
`
gantt
title ;Gantt With a Semicolon in the Title
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :after a1 , 20d
section Another
Task in sec :2014-01-12 , 12d
another task : 24d
`,
{}
);
});
it("should render when there's a semicolon in a section is true", () => {
imgSnapshotTest(
`
gantt
title Gantt Digram
dateFormat YYYY-MM-DD
section ;Section With a Semicolon
A task :a1, 2014-01-01, 30d
Another task :after a1 , 20d
section Another
Task in sec :2014-01-12 , 12d
another task : 24d
`,
{}
);
});
it("should render when there's a semicolon in the task data", () => {
imgSnapshotTest(
`
gantt
title Gantt Digram
dateFormat YYYY-MM-DD
section Section
;A task with a semiclon :a1, 2014-01-01, 30d
Another task :after a1 , 20d
section Another
Task in sec :2014-01-12 , 12d
another task : 24d
`,
{}
);
});
it("should render when there's a hashtag in the title", () => {
imgSnapshotTest(
`
gantt
title #Gantt With a Hashtag in the Title
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :after a1 , 20d
section Another
Task in sec :2014-01-12 , 12d
another task : 24d
`,
{}
);
});
it("should render when there's a hashtag in a section is true", () => {
imgSnapshotTest(
`
gantt
title Gantt Digram
dateFormat YYYY-MM-DD
section #Section With a Hashtag
A task :a1, 2014-01-01, 30d
Another task :after a1 , 20d
section Another
Task in sec :2014-01-12 , 12d
another task : 24d
`,
{}
);
});
it("should render when there's a hashtag in the task data", () => {
imgSnapshotTest(
`
gantt
title Gantt Digram
dateFormat YYYY-MM-DD
section Section
#A task with a hashtag :a1, 2014-01-01, 30d
Another task :after a1 , 20d
section Another
Task in sec :2014-01-12 , 12d
another task : 24d
`,
{}
);
});
});

View File

@ -375,6 +375,26 @@ context('Sequence diagram', () => {
{}
);
});
it('should have actor-top and actor-bottom classes on top and bottom actor box and symbol', () => {
imgSnapshotTest(
`
sequenceDiagram
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
`,
{}
);
cy.get('.actor').should('have.class', 'actor-top');
cy.get('.actor-man').should('have.class', 'actor-top');
cy.get('.actor.actor-top').should('not.have.class', 'actor-bottom');
cy.get('.actor-man.actor-top').should('not.have.class', 'actor-bottom');
cy.get('.actor').should('have.class', 'actor-bottom');
cy.get('.actor-man').should('have.class', 'actor-bottom');
cy.get('.actor.actor-bottom').should('not.have.class', 'actor-top');
cy.get('.actor-man.actor-bottom').should('not.have.class', 'actor-top');
});
it('should render long notes left of actor', () => {
imgSnapshotTest(
`
@ -792,6 +812,34 @@ context('Sequence diagram', () => {
});
});
context('links', () => {
it('should support actor links', () => {
renderGraph(
`
sequenceDiagram
link Alice: Dashboard @ https://dashboard.contoso.com/alice
link Alice: Wiki @ https://wiki.contoso.com/alice
link John: Dashboard @ https://dashboard.contoso.com/john
link John: Wiki @ https://wiki.contoso.com/john
Alice->>John: Hello John<br/>
John-->>Alice: Great<br/><br/>day!
`,
{ securityLevel: 'loose' }
);
cy.get('#actor0_popup').should((popupMenu) => {
const style = popupMenu.attr('style');
expect(style).to.undefined;
});
cy.get('#root-0').click();
cy.get('#actor0_popup').should((popupMenu) => {
const style = popupMenu.attr('style');
expect(style).to.match(/^display: block;$/);
});
cy.get('#root-0').click();
cy.get('#actor0_popup').should((popupMenu) => {
const style = popupMenu.attr('style');
expect(style).to.match(/^display: none;$/);
});
});
it('should support actor links and properties EXPERIMENTAL: USE WITH CAUTION', () => {
//Be aware that the syntax for "properties" is likely to be changed.
imgSnapshotTest(

View File

@ -17,24 +17,30 @@
<style>
body {
/* background: rgb(221, 208, 208); */
/* background:#333; */
background: #333;
font-family: 'Arial';
/* font-size: 18px !important; */
}
h1 {
color: grey;
}
.mermaid {
border: 1px solid #ddd;
margin: 10px;
}
.mermaid2 {
display: none;
}
.mermaid svg {
/* font-size: 18px !important; */
background-color: #efefef;
background-image: radial-gradient(#fff 51%, transparent 91%),
radial-gradient(#fff 51%, transparent 91%);
/* background-color: #efefef; */
background-color: #333;
background-image: radial-gradient(#333 51%, transparent 91%),
radial-gradient(#333 51%, transparent 91%);
background-size: 20px 20px;
background-position: 0 0, 10px 10px;
background-repeat: repeat;
border: 2px solid rgb(131, 142, 205);
}
.malware {
position: fixed;
@ -58,46 +64,192 @@
</head>
<body>
<pre id="diagram" class="mermaid">
flowchart TB
C & D & E & F & G & H & I & J & K & L & M & N & O & P & Q & R & S & T & U & V & W & X & Y & Z & A1 & A2 & A3 & A4 & A5 & A6 & A7 & A8
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
C & D & E & F & G & H & I & J & K & L & M & N & O & P & Q & R & S & T & U & V & W & X & Y & Z & A1 & A2 & A3 & A4 & A5 & A6 & A7 & A8
block-beta
blockArrowId<["Label"]>(right)
blockArrowId2<["Label"]>(left)
blockArrowId3<["Label"]>(up)
blockArrowId4<["Label"]>(down)
blockArrowId5<["Label"]>(x)
blockArrowId6<["Label"]>(y)
blockArrowId6<["Label"]>(x, down)
</pre>
<pre id="diagram" class="mermaid">
block-beta
block:e:4
columns 2
f
g
end
</pre>
<pre id="diagram" class="mermaid">
block-beta
block:e:4
columns 2
f
g
h
end
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 4
a b c d
block:e:4
columns 2
f
g
h
end
i:4
</pre>
<pre id="diagram" class="mermaid2">
flowchart TB
A & A & A & A & A & A & A & A ---> C & D & E & F & G & H & I & J & K & L & M & N & O & P & Q & R & S & T & U & V & W & X & Y & Z
flowchart LR
X-- "y" -->z
</pre>
<pre id="diagram" class="mermaid2">
flowchart TB
A1 & A2 & A3 & A4 & A5 & A6 & A7 & A8 --> C & D & E & F & G & H & I & J & K & L & M & N & O & P & Q & R & S & T & U & V & W & X & Y & Z
block-beta
columns 5
A space B
A --x B
</pre>
<pre id="diagram" class="mermaid2">
block-beta
columns 3
a["A wide one"] b:2 c:2 d
</pre>
<pre id="diagram" class="mermaid2">
block-beta
block:e
f
end
</pre>
<pre id="diagram" class="mermaid2">
block-beta
columns 3
a:3
block:e:3
f
end
g
</pre>
<pre id="diagram" class="mermaid2">
block-beta
columns 3
a:3
block:e:3
f
g
end
h
i
j
</pre>
<pre id="diagram" class="mermaid2">
block-beta
columns 3
a b:2
block:e:3
f
end
g h i
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 3
a b c
e:3
f g h
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#f9F,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 5
A1:3
A2:1
A3
B1 B2 B3:3
</pre>
<pre id="diagram" class="mermaid2">
block-beta
block
D
E
end
db("This is the text in the box")
</pre>
<pre id="diagram" class="mermaid2">
block-beta
block
D
end
A["A: I am a wide one"]
</pre>
<pre id="diagram" class="mermaid2">
block-beta
A["square"]
B("rounded")
C(("circle"))
</pre>
<pre id="diagram" class="mermaid2">
block-beta
A>"rect_left_inv_arrow"]
B{"diamond"}
C{{"hexagon"}}
</pre>
<pre id="diagram" class="mermaid2">
block-beta
A(["stadium"])
</pre>
<pre id="diagram" class="mermaid2">
block-beta
%% A[["subroutine"]]
%% B[("cylinder")]
C>"surprise"]
</pre>
<pre id="diagram" class="mermaid2">
block-beta
A[/"lean right"/]
B[\"lean left"\]
C[/"trapezoid"\]
D[\"trapezoid"/]
</pre>
<pre id="diagram" class="mermaid2">
flowchart
Node1:::class1 --> Node2:::class2
Node1:::class1 --> Node3:::class2
Node3 --> Node4((I am a circle)):::larger
B
style B fill:#f9F,stroke:#333,stroke-width:4px
</pre>
classDef class1 fill:lightblue
classDef class2 fill:pink
classDef larger font-size:30px,fill:yellow
</pre
>
<pre id="diagram" class="mermaid2">
stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*] </pre
>
flowchart LR
a1 -- apa --> b1
</pre>
<pre id="diagram" class="mermaid2">
flowchart RL
subgraph "`one`"
a1 -- l1 --> a2
a1 -- l2 --> a2
end
subgraph "`one`"
id
end
</pre>
<pre id="diagram" class="mermaid2">
flowchart RL
@ -442,14 +594,8 @@ mindmap
// useMaxWidth: false,
// });
mermaid.initialize({
flowchart: { titleTopMargin: 10 },
fontFamily: 'courier',
sequence: {
actorFontFamily: 'courier',
noteFontFamily: 'courier',
messageFontFamily: 'courier',
},
fontSize: 16,
theme: 'dark',
startOnLoad: true,
logLevel: 0,
});
function callback() {

139
demos/block.html Normal file
View File

@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Mermaid Block diagram demo page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
</head>
<body>
<h1>Block diagram demos</h1>
<pre id="diagram" class="mermaid">
block-beta
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#f9F,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram" class="mermaid">
block-beta
A1["square"]
B1("rounded")
C1(("circle"))
A2>"rect_left_inv_arrow"]
B2{"diamond"}
C2{{"hexagon"}}
</pre>
<pre id="diagram" class="mermaid">
block-beta
A1(["stadium"])
A2[["subroutine"]]
B1[("cylinder")]
C1>"surprise"]
A3[/"lean right"/]
B2[\"lean left"\]
C2[/"trapezoid"\]
D2[\"trapezoid"/]
</pre>
<pre id="diagram" class="mermaid">
block-beta
block:e:4
columns 2
f
g
end
</pre>
<pre id="diagram" class="mermaid">
block-beta
block:e:4
columns 2
f
g
h
end
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 3
a:3
block:e:3
f
g
end
h
i
j
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 4
a b c d
block:e:4
columns 2
f
g
h
end
i:4
</pre>
<pre id="diagram" class="mermaid">
flowchart LR
X-- "a label" -->z
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 5
A space B
A --x B
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 3
a["A wide one"] b:2 c:2 d
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 3
a b c
e:3
f g h
</pre>
<pre id="diagram" class="mermaid">
block-beta
A1:3
A2:1
A3
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
theme: 'default',
logLevel: 3,
securityLevel: 'loose',
block: {
padding: 10,
},
});
</script>
</body>
</html>

View File

@ -30,6 +30,21 @@
</pre>
<hr />
<pre class="mermaid">
gantt
title #; Gantt Diagrams Allow Semicolons and Hashtags #;!
accTitle: A simple sample gantt diagram
accDescr: 2 sections with 2 tasks each, from 2014
dateFormat YYYY-MM-DD
section #;Section
#;A task :a1, 2014-01-01, 30d
#;Another task :after a1 , 20d
section #;Another
Task in sec :2014-01-12 , 12d
another task : 24d
</pre>
<hr />
<pre class="mermaid">
gantt
title Airworks roadmap

View File

@ -84,6 +84,9 @@
<li>
<h2><a href="./packet.html">Packet</a></h2>
</li>
<li>
<h2><a href="./block.html">Layered Blocks</a></h2>
</li>
</ul>
</body>
</html>

View File

@ -15,7 +15,7 @@
<body>
<h1>Pie chart demos</h1>
<pre class="mermaid">
pie title Pets adopted by volunteers
pie title Default text position: Animal adoption
accTitle: simple pie char demo
accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
"Dogs": 386
@ -27,7 +27,7 @@
<pre class="mermaid">
%%{init: {"pie": {"textPosition": 0.9}, "themeVariables": {"pieOuterStrokeWidth": "5px"}}}%%
pie
title Key elements in Product X
title Offset labels close to border: Product X
accTitle: Key elements in Product X
accDescr: This is a pie chart showing the key elements in Product X.
"Calcium": 42.96
@ -36,6 +36,19 @@
"Iron": 5
</pre>
<pre class="mermaid">
%%{init: {"pie": {"textPosition": 0.45}, "themeVariables": {"pieOuterStrokeWidth": "5px"}}}%%
pie
title Centralized labels: Languages
accTitle: Key elements in Product X
accDescr: This is a pie chart showing the key elements in Product X.
"JavaScript": 30
"Python": 25
"Java": 20
"C#": 15
"Others": 10
</pre>
<script type="module">
import mermaid from '/mermaid.esm.mjs';
mermaid.initialize({

View File

@ -23,6 +23,10 @@
participant Alice
participant Bob
participant John as John<br />Second Line
link Alice: Dashboard @ https://dashboard.contoso.com/alice
link Alice: Wiki @ https://wiki.contoso.com/alice
link John: Dashboard @ https://dashboard.contoso.com/john
link John: Wiki @ https://wiki.contoso.com/john
autonumber 10 10
rect rgb(200, 220, 100)
rect rgb(200, 255, 200)
@ -62,6 +66,26 @@
</pre>
<hr />
<pre class="mermaid">
---
title: With forced menus
config:
sequence:
forceMenus: true
---
sequenceDiagram
participant Alice
participant John
link Alice: Dashboard @ https://dashboard.contoso.com/alice
link Alice: Wiki @ https://wiki.contoso.com/alice
link John: Dashboard @ https://dashboard.contoso.com/john
link John: Wiki @ https://wiki.contoso.com/john
Alice->>John: Hello John, how are you?
John-->>Alice: Great!
Alice-)John: See you later!
</pre
>
<hr />
<pre class="mermaid">
sequenceDiagram
accTitle: Sequence diagram title is here
accDescr: Hello friends

View File

@ -24,7 +24,7 @@ Currently pending [IANA](https://www.iana.org/) recognition.
### Mermaid Discord workspace
We would love to see what you create with Mermaid. Please share your creations with us in our [Discord](https://discord.gg/wwtabKgp8y) server [#showcase](https://discord.com/channels/1079455296289788015/1079502635054399649) channel.
We would love to see what you create with Mermaid. Please share your creations with us in our [Discord](https://discord.gg/AgrbSrBer3) server [#showcase](https://discord.com/channels/1079455296289788015/1079502635054399649) channel.
### Add to Mermaid Ecosystem

View File

@ -22,7 +22,7 @@ It is a JavaScript based diagramming and charting tool that renders Markdown-ins
[![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master)
[![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid)
[![NPM](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid)
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/wwtabKgp8y)
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/AgrbSrBer3)
[![Twitter Follow](https://img.shields.io/twitter/follow/mermaidjs_?style=social)](https://twitter.com/mermaidjs_)
</div>

View File

@ -14,19 +14,6 @@ Create flowchart nodes, connect them with edges, update shapes, change colors, a
Read more about it in our latest [BLOG POST](https://www.mermaidchart.com/blog/posts/mermaid-chart-releases-new-visual-editor-for-flowcharts) and watch a [DEMO VIDEO](https://www.youtube.com/watch?v=5aja0gijoO0) on our YouTube page.
## 🎉 Mermaid Chart is running a Holiday promotion
### Use <span class="text-[#FE3470]">HOLIDAYS2023</span> to get a 14-day free trial and 25% off a Pro subscription
With a Pro subscription, you get access to:
- AI functionality
- Team collaboration and multi-user editing
- Unlimited diagrams and presentations
- And more!
Redeem the promo code on the [Mermaid Chart website](https://www.mermaidchart.com/app/user/billing/checkout?coupon=HOLIDAYS2023).
## 📖 Blog posts
Visit our [Blog](./blog.md) to see the latest blog posts.

View File

@ -6,6 +6,12 @@
# Blog
## [How one data scientist uses Mermaid Chart to quickly and easily build flowcharts](https://www.mermaidchart.com/blog/posts/customer-spotlight-ari-tal/)
23 January 2024 · 4 mins
Read about how Ari Tal, a data scientist and founder of Leveling Up with XAI, utilizes Mermaid Chart for its easy-to-use flowchart creation capabilities to enhance his work in explainable AI (XAI).
## [Introducing Mermaid Charts JetBrains IDE Extension](https://www.mermaidchart.com/blog/posts/introducing-mermaid-charts-jetbrains-ide-extension/)
20 December 2023 · 5 mins

706
docs/syntax/block.md Normal file
View File

@ -0,0 +1,706 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/block.md](../../packages/mermaid/src/docs/syntax/block.md).
# Block Diagrams Documentation
## Introduction to Block Diagrams
```mermaid-example
block-beta
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#969,stroke:#333,stroke-width:4px
```
```mermaid
block-beta
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#969,stroke:#333,stroke-width:4px
```
### Definition and Purpose
Block diagrams are an intuitive and efficient way to represent complex systems, processes, or architectures visually. They are composed of blocks and connectors, where blocks represent the fundamental components or functions, and connectors show the relationship or flow between these components. This method of diagramming is essential in various fields such as engineering, software development, and process management.
The primary purpose of block diagrams is to provide a high-level view of a system, allowing for easy understanding and analysis without delving into the intricate details of each component. This makes them particularly useful for simplifying complex systems and for explaining the overall structure and interaction of components within a system.
Many people use mermaid flowcharts for this purpose. A side-effect of this is that the automatic layout sometimes move shapes to positions that the diagram maker does not want. Block diagrams use a different approach. In this diagram we give the author full control over where the shapes are positioned.
### General Use Cases
Block diagrams have a wide range of applications across various industries and disciplines. Some of the key use cases include:
- **Software Architecture**: In software development, block diagrams can be used to illustrate the architecture of a software application. This includes showing how different modules or services interact, data flow, and high-level component interaction.
- **Network Diagrams**: Block diagrams are ideal for representing network architectures in IT and telecommunications. They can depict how different network devices and services are interconnected, including routers, switches, firewalls, and the flow of data across the network.
- **Process Flowcharts**: In business and manufacturing, block diagrams can be employed to create process flowcharts. These flowcharts represent various stages of a business or manufacturing process, helping to visualize the sequence of steps, decision points, and the flow of control.
- **Electrical Systems**: Engineers use block diagrams to represent electrical systems and circuitry. They can illustrate the high-level structure of an electrical system, the interaction between different electrical components, and the flow of electrical currents.
- **Educational Purposes**: Block diagrams are also extensively used in educational materials to explain complex concepts and systems in a simplified manner. They help in breaking down and visualizing scientific theories, engineering principles, and technological systems.
These examples demonstrate the versatility of block diagrams in providing clear and concise representations of complex systems. Their simplicity and clarity make them a valuable tool for professionals across various fields to communicate complex ideas effectively.
In the following sections, we will delve into the specifics of creating and manipulating block diagrams using Mermaid, covering everything from basic syntax to advanced configurations and styling.
Creating block diagrams with Mermaid is straightforward and accessible. This section introduces the basic syntax and structure needed to start building simple diagrams. Understanding these foundational concepts is key to efficiently utilizing Mermaid for more complex diagramming tasks.
### Simple Block Diagrams
#### Basic Structure
At its core, a block diagram consists of blocks representing different entities or components. In Mermaid, these blocks are easily created using simple text labels. The most basic form of a block diagram can be a series of blocks without any connectors.
**Example - Simple Block Diagram**:
To create a simple block diagram with three blocks labeled 'a', 'b', and 'c', the syntax is as follows:
```mermaid-example
block-beta
a b c
```
```mermaid
block-beta
a b c
```
This example will produce a horizontal sequence of three blocks. Each block is automatically spaced and aligned for optimal readability.
### Defining the number of columns to use
#### Column Usage
While simple block diagrams are linear and straightforward, more complex systems may require a structured layout. Mermaid allows for the organization of blocks into multiple columns, facilitating the creation of more intricate and detailed diagrams.
**Example - Multi-Column Diagram:**
In scenarios where you need to distribute blocks across multiple columns, you can specify the number of columns and arrange the blocks accordingly. Here's how to create a block diagram with three columns and four blocks, where the fourth block appears in a second row:
```mermaid-example
block-beta
columns 3
a b c d
```
```mermaid
block-beta
columns 3
a b c d
```
This syntax instructs Mermaid to arrange the blocks 'a', 'b', 'c', and 'd' across three columns, wrapping to the next row as needed. This feature is particularly useful for representing layered or multi-tiered systems, such as network layers or hierarchical structures.
These basic building blocks of Mermaid's block diagrams provide a foundation for more complex diagramming. The simplicity of the syntax allows for quick creation and iteration of diagrams, making it an efficient tool for visualizing ideas and concepts. In the next section, we'll explore advanced block configuration options, including setting block widths and creating composite blocks.
## 3. Advanced Block Configuration
Building upon the basics, this section delves into more advanced features of block diagramming in Mermaid. These features allow for greater flexibility and complexity in diagram design, accommodating a wider range of use cases and scenarios.
### Setting Block Width
#### Spanning Multiple Columns
In more complex diagrams, you may need blocks that span multiple columns to emphasize certain components or to represent larger entities. Mermaid allows for the adjustment of block widths to cover multiple columns, enhancing the diagram's readability and structure.
**Example - Block Spanning Multiple Columns**:
To create a block diagram where one block spans across two columns, you can specify the desired width for each block:
```mermaid-example
block-beta
columns 3
a["A label"] b:2 c:2 d
```
```mermaid
block-beta
columns 3
a["A label"] b:2 c:2 d
```
In this example, the block labeled "A wide one" spans two columns, while blocks 'b', 'c', and 'd' are allocated their own columns. This flexibility in block sizing is crucial for accurately representing systems with components of varying significance or size.
### Creating Composite Blocks
#### Nested Blocks
Composite blocks, or blocks within blocks, are an advanced feature in Mermaid's block diagram syntax. They allow for the representation of nested or hierarchical systems, where one component encompasses several subcomponents.
**Example - Composite Blocks:**
Creating a composite block involves defining a parent block and then nesting other blocks within it. Here's how to define a composite block with nested elements:
```mermaid-example
block-beta
block
D
end
A["A: I am a wide one"]
```
```mermaid
block-beta
block
D
end
A["A: I am a wide one"]
```
In this syntax, 'D' is a nested block within a larger parent block. This feature is particularly useful for depicting complex structures, such as a server with multiple services or a department within a larger organizational framework.
### Column Width Dynamics
#### Adjusting Widths
Mermaid also allows for dynamic adjustment of column widths based on the content of the blocks. The width of the columns is determined by the widest block in the column, ensuring that the diagram remains balanced and readable.
**Example - Dynamic Column Widths:**
In diagrams with varying block sizes, Mermaid automatically adjusts the column widths to fit the largest block in each column. Here's an example:
```mermaid-example
block-beta
columns 3
a:3
block:group1:2
columns 2
h i j k
end
g
block:group2:3
%% columns auto (default)
l m n o p q r
end
```
```mermaid
block-beta
columns 3
a:3
block:group1:2
columns 2
h i j k
end
g
block:group2:3
%% columns auto (default)
l m n o p q r
end
```
This example demonstrates how Mermaid dynamically adjusts the width of the columns to accommodate the widest block, in this case, 'a' and the composite block 'e'. This dynamic adjustment is essential for creating visually balanced and easy-to-understand diagrams.
With these advanced configuration options, Mermaid's block diagrams can be tailored to represent a wide array of complex systems and structures. The flexibility offered by these features enables users to create diagrams that are both informative and visually appealing. In the following sections, we will explore further capabilities, including different block shapes and linking options.
## 4. Block Varieties and Shapes
Mermaid's block diagrams are not limited to standard rectangular shapes. A variety of block shapes are available, allowing for a more nuanced and tailored representation of different types of information or entities. This section outlines the different block shapes you can use in Mermaid and their specific applications.
### Standard and Special Block Shapes
Mermaid supports a range of block shapes to suit different diagramming needs, from basic geometric shapes to more specialized forms.
#### Example - Round Edged Block
To create a block with round edges, which can be used to represent a softer or more flexible component:
```mermaid-example
block-beta
id1("This is the text in the box")
```
```mermaid
block-beta
id1("This is the text in the box")
```
#### Example - Stadium-Shaped Block
A stadium-shaped block, resembling an elongated circle, can be used for components that are process-oriented:
```mermaid-example
block-beta
id1(["This is the text in the box"])
```
```mermaid
block-beta
id1(["This is the text in the box"])
```
#### Example - Subroutine Shape
For representing subroutines or contained processes, a block with double vertical lines is useful:
```mermaid-example
block-beta
id1[["This is the text in the box"]]
```
```mermaid
block-beta
id1[["This is the text in the box"]]
```
#### Example - Cylindrical Shape
The cylindrical shape is ideal for representing databases or storage components:
```mermaid-example
block-beta
id1[("Database")]
```
```mermaid
block-beta
id1[("Database")]
```
#### Example - Circle Shape
A circle can be used for centralized or pivotal components:
```mermaid-example
block-beta
id1(("This is the text in the circle"))
```
```mermaid
block-beta
id1(("This is the text in the circle"))
```
#### Example - Asymmetric, Rhombus, and Hexagon Shapes
For decision points, use a rhombus, and for unique or specialized processes, asymmetric and hexagon shapes can be utilized:
**Asymmetric**
```mermaid-example
block-beta
id1>"This is the text in the box"]
```
```mermaid
block-beta
id1>"This is the text in the box"]
```
**Rhombus**
```mermaid-example
block-beta
id1{"This is the text in the box"}
```
```mermaid
block-beta
id1{"This is the text in the box"}
```
**Hexagon**
```mermaid-example
block-beta
id1{{"This is the text in the box"}}
```
```mermaid
block-beta
id1{{"This is the text in the box"}}
```
#### Example - Parallelogram and Trapezoid Shapes
Parallelogram and trapezoid shapes are perfect for inputs/outputs and transitional processes:
```mermaid-example
block-beta
id1[/"This is the text in the box"/]
id2[\"This is the text in the box"\]
A[/"Christmas"\]
B[\"Go shopping"/]
```
```mermaid
block-beta
id1[/"This is the text in the box"/]
id2[\"This is the text in the box"\]
A[/"Christmas"\]
B[\"Go shopping"/]
```
#### Example - Double Circle
For highlighting critical or high-priority components, a double circle can be effective:
```mermaid-example
block-beta
id1((("This is the text in the circle")))
```
```mermaid
block-beta
id1((("This is the text in the circle")))
```
### Block Arrows and Space Blocks
Mermaid also offers unique shapes like block arrows and space blocks for directional flow and spacing.
#### Example - Block Arrows
Block arrows can visually indicate direction or flow within a process:
```mermaid-example
block-beta
blockArrowId<["Label"]>(right)
blockArrowId2<["Label"]>(left)
blockArrowId3<["Label"]>(up)
blockArrowId4<["Label"]>(down)
blockArrowId5<["Label"]>(x)
blockArrowId6<["Label"]>(y)
blockArrowId6<["Label"]>(x, down)
```
```mermaid
block-beta
blockArrowId<["Label"]>(right)
blockArrowId2<["Label"]>(left)
blockArrowId3<["Label"]>(up)
blockArrowId4<["Label"]>(down)
blockArrowId5<["Label"]>(x)
blockArrowId6<["Label"]>(y)
blockArrowId6<["Label"]>(x, down)
```
#### Example - Space Blocks
Space blocks can be used to create intentional empty spaces in the diagram, which is useful for layout and readability:
```mermaid-example
block-beta
columns 3
a space b
c d e
```
```mermaid
block-beta
columns 3
a space b
c d e
```
or
```mermaid-example
block-beta
ida space:3 idb idc
```
```mermaid
block-beta
ida space:3 idb idc
```
Note that you can set how many columns the spece block occupied using the number notaion `space:num` where num is a number indicating the num columns width. You can alsio use `space` which defaults to one column.
The variety of shapes and special blocks in Mermaid enhances the expressive power of block diagrams, allowing for more accurate and context-specific representations. These options give users the flexibility to create diagrams that are both informative and visually appealing. In the next sections, we will explore the ways to connect these blocks and customize their appearance.
### Standard and Special Block Shapes
Discuss the various shapes available for blocks, including standard shapes and special forms like block arrows and space blocks.
## 5. Connecting Blocks with Edges
One of the key features of block diagrams in Mermaid is the ability to connect blocks using various types of edges or links. This section explores the different ways blocks can be interconnected to represent relationships and flows between components.
### Basic Linking and Arrow Types
The most fundamental aspect of connecting blocks is the use of arrows or links. These connectors depict the relationships or the flow of information between the blocks. Mermaid offers a range of arrow types to suit different diagramming needs.
**Example - Basic Links**
A simple link with an arrow can be created to show direction or flow from one block to another:
```mermaid-example
block-beta
A space B
A-->B
```
```mermaid
block-beta
A space B
A-->B
```
This example illustrates a direct connection from block 'A' to block 'B', using a straightforward arrow.
This syntax creates a line connecting 'A' and 'B', implying a relationship or connection without indicating a specific direction.
### Text on Links
In addition to connecting blocks, it's often necessary to describe or label the relationship. Mermaid allows for the inclusion of text on links, providing context to the connections.
Example - Text with Links
To add text to a link, the syntax includes the text within the link definition:
```mermaid-example
block-beta
A space:2 B
A-- "X" -->B
```
```mermaid
block-beta
A space:2 B
A-- "X" -->B
```
This example show how to add descriptive text to the links, enhancing the information conveyed by the diagram.
Example - Edges and Styles:
```mermaid-example
block-beta
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#939,stroke:#333,stroke-width:4px
```
```mermaid
block-beta
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#939,stroke:#333,stroke-width:4px
```
## 6. Styling and Customization
Beyond the structure and layout of block diagrams, Mermaid offers extensive styling options. These customization features allow for the creation of more visually distinctive and informative diagrams. This section covers how to apply individual styles to blocks and how to use classes for consistent styling across multiple elements.
### Individual Block Styling
Mermaid enables detailed styling of individual blocks, allowing you to apply various CSS properties such as color, stroke, and border thickness. This feature is especially useful for highlighting specific parts of a diagram or for adhering to certain visual themes.
#### Example - Styling a Single Block
To apply custom styles to a block, you can use the `style` keyword followed by the block identifier and the desired CSS properties:
```mermaid-example
block-beta
id1 space id2
id1("Start")-->id2("Stop")
style id1 fill:#636,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
```
```mermaid
block-beta
id1 space id2
id1("Start")-->id2("Stop")
style id1 fill:#636,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
```
In this example, a class named 'blue' is defined and applied to block 'A', while block 'B' receives individual styling. This demonstrates the flexibility of Mermaid in applying both shared and unique styles within the same diagram.
The ability to style blocks individually or through classes provides a powerful tool for enhancing the visual impact and clarity of block diagrams. Whether emphasizing certain elements or maintaining a cohesive design across the diagram, these styling capabilities are central to effective diagramming. The next sections will present practical examples and use cases, followed by tips for troubleshooting common issues.
### 7. Practical Examples and Use Cases
The versatility of Mermaid's block diagrams becomes evident when applied to real-world scenarios. This section provides practical examples demonstrating the application of various features discussed in previous sections. These examples showcase how block diagrams can be used to represent complex systems and processes in an accessible and informative manner.
### Detailed Examples Illustrating Various Features
Combining the elements of structure, linking, and styling, we can create comprehensive diagrams that serve specific purposes in different contexts.
#### Example - System Architecture
Illustrating a simple software system architecture with interconnected components:
```mermaid-example
block-beta
columns 3
Frontend blockArrowId6<[" "]>(right) Backend
space:2 down<[" "]>(down)
Disk left<[" "]>(left) Database[("Database")]
classDef front fill:#696,stroke:#333;
classDef back fill:#969,stroke:#333;
class Frontend front
class Backend,Database back
```
```mermaid
block-beta
columns 3
Frontend blockArrowId6<[" "]>(right) Backend
space:2 down<[" "]>(down)
Disk left<[" "]>(left) Database[("Database")]
classDef front fill:#696,stroke:#333;
classDef back fill:#969,stroke:#333;
class Frontend front
class Backend,Database back
```
This example shows a basic architecture with a frontend, backend, and database. The blocks are styled to differentiate between types of components.
#### Example - Business Process Flow
Representing a business process flow with decision points and multiple stages:
```mermaid-example
block-beta
columns 3
Start(("Start")) space:2
down<[" "]>(down) space:2
Decision{{"Make Decision"}} right<["Yes"]>(right) Process1["Process A"]
downAgain<["No"]>(down) space r3<["Done"]>(down)
Process2["Process B"] r2<["Done"]>(right) End(("End"))
style Start fill:#969;
style End fill:#696;
```
```mermaid
block-beta
columns 3
Start(("Start")) space:2
down<[" "]>(down) space:2
Decision{{"Make Decision"}} right<["Yes"]>(right) Process1["Process A"]
downAgain<["No"]>(down) space r3<["Done"]>(down)
Process2["Process B"] r2<["Done"]>(right) End(("End"))
style Start fill:#969;
style End fill:#696;
```
These practical examples and scenarios underscore the utility of Mermaid block diagrams in simplifying and effectively communicating complex information across various domains.
The next section, 'Troubleshooting and Common Issues', will provide insights into resolving common challenges encountered when working with Mermaid block diagrams, ensuring a smooth diagramming experience.
## 8. Troubleshooting and Common Issues
Working with Mermaid block diagrams can sometimes present challenges, especially as the complexity of the diagrams increases. This section aims to provide guidance on resolving common issues and offers tips for managing more intricate diagram structures.
### Common Syntax Errors
Understanding and avoiding common syntax errors is key to a smooth experience with Mermaid diagrams.
#### Example - Incorrect Linking
A common mistake is incorrect linking syntax, which can lead to unexpected results or broken diagrams:
block-beta
A - B
**Correction**:
Ensure that links between blocks are correctly specified with arrows (--> or ---) to define the direction and type of connection. Also rememeber that one of the fundaments for block diagram is to give the author full control of where the boxes are positioned so in the example you need to add a space between the boxes:
```mermaid-example
block-beta
A space B
A --> B
```
```mermaid
block-beta
A space B
A --> B
```
#### Example - Misplaced Styling
Applying styles in the wrong context or with incorrect syntax can lead to blocks not being styled as intended:
```mermaid-example
block-beta
A
style A fill#969;
```
```mermaid
block-beta
A
style A fill#969;
```
**Correction:**
Correct the syntax by ensuring proper separation of style properties with commas and using the correct CSS property format:
```mermaid-example
block-beta
A
style A fill:#969,stroke:#333;
```
```mermaid
block-beta
A
style A fill:#969,stroke:#333;
```
### Tips for Complex Diagram Structures
Managing complexity in Mermaid diagrams involves planning and employing best practices.
#### Modular Design
Break down complex diagrams into smaller, more manageable components. This approach not only makes the diagram easier to understand but also simplifies the creation and maintenance process.
#### Consistent Styling
Use classes to maintain consistent styling across similar elements. This not only saves time but also ensures a cohesive and professional appearance.
#### Comments and Documentation
Use comments with `%%` within the Mermaid syntax to document the purpose of various parts of the diagram. This practice is invaluable for maintaining clarity, especially when working in teams or returning to a diagram after some time.
With these troubleshooting tips and best practices, you can effectively manage and resolve common issues in Mermaid block diagrams. The final section, 'Conclusion', will summarize the key points covered in this documentation and invite user feedback for continuous improvement.

View File

@ -320,7 +320,7 @@ UpdateRelStyle(customerA, bankA, $offsetY="60")
Person(customer, Customer, "A customer of the bank, with personal bank accounts", $tags="v1.0")
Container_Boundary(c1, "Internet Banking") {
Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to cutomers via their web browser")
Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to customers via their web browser")
Container_Ext(mobile_app, "Mobile App", "C#, Xamarin", "Provides a limited subset of the Internet banking functionality to customers via their mobile device")
Container(web_app, "Web Application", "Java, Spring MVC", "Delivers the static content and the Internet banking SPA")
ContainerDb(database, "Database", "SQL Database", "Stores user registration information, hashed auth credentials, access logs, etc.")
@ -360,7 +360,7 @@ UpdateRelStyle(customerA, bankA, $offsetY="60")
Person(customer, Customer, "A customer of the bank, with personal bank accounts", $tags="v1.0")
Container_Boundary(c1, "Internet Banking") {
Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to cutomers via their web browser")
Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to customers via their web browser")
Container_Ext(mobile_app, "Mobile App", "C#, Xamarin", "Provides a limited subset of the Internet banking functionality to customers via their mobile device")
Container(web_app, "Web Application", "Java, Spring MVC", "Delivers the static content and the Internet banking SPA")
ContainerDb(database, "Database", "SQL Database", "Stores user registration information, hashed auth credentials, access logs, etc.")

View File

@ -740,20 +740,22 @@ Styling of a sequence diagram is done by defining a number of css classes. Durin
### Classes used
| Class | Description |
| ------------ | ----------------------------------------------------------- |
| actor | Style for the actor box at the top of the diagram. |
| text.actor | Styles for text in the actor box at the top of the diagram. |
| actor-line | The vertical line for an actor. |
| messageLine0 | Styles for the solid message line. |
| messageLine1 | Styles for the dotted message line. |
| messageText | Defines styles for the text on the message arrows. |
| labelBox | Defines styles label to left in a loop. |
| labelText | Styles for the text in label for loops. |
| loopText | Styles for the text in the loop box. |
| loopLine | Defines styles for the lines in the loop box. |
| note | Styles for the note box. |
| noteText | Styles for the text on in the note boxes. |
| Class | Description |
| ------------ | -------------------------------------------------------------- |
| actor | Styles for the actor box. |
| actor-top | Styles for the actor figure/ box at the top of the diagram. |
| actor-bottom | Styles for the actor figure/ box at the bottom of the diagram. |
| text.actor | Styles for text in the actor box. |
| actor-line | The vertical line for an actor. |
| messageLine0 | Styles for the solid message line. |
| messageLine1 | Styles for the dotted message line. |
| messageText | Defines styles for the text on the message arrows. |
| labelBox | Defines styles label to left in a loop. |
| labelText | Styles for the text in label for loops. |
| loopText | Styles for the text in the loop box. |
| loopLine | Defines styles for the lines in the loop box. |
| note | Styles for the note box. |
| noteText | Styles for the text on in the note boxes. |
### Sample stylesheet

View File

@ -163,11 +163,11 @@ timeline
timeline
title MermaidChart 2023 Timeline
section 2023 Q1 <br> Release Personal Tier
Buttet 1 : sub-point 1a : sub-point 1b
Bullet 1 : sub-point 1a : sub-point 1b
: sub-point 1c
Bullet 2 : sub-point 2a : sub-point 2b
section 2023 Q2 <br> Release XYZ Tier
Buttet 3 : sub-point <br> 3a : sub-point 3b
Bullet 3 : sub-point <br> 3a : sub-point 3b
: sub-point 3c
Bullet 4 : sub-point 4a : sub-point 4b
```
@ -176,11 +176,11 @@ timeline
timeline
title MermaidChart 2023 Timeline
section 2023 Q1 <br> Release Personal Tier
Buttet 1 : sub-point 1a : sub-point 1b
Bullet 1 : sub-point 1a : sub-point 1b
: sub-point 1c
Bullet 2 : sub-point 2a : sub-point 2b
section 2023 Q2 <br> Release XYZ Tier
Buttet 3 : sub-point <br> 3a : sub-point 3b
Bullet 3 : sub-point <br> 3a : sub-point 3b
: sub-point 3c
Bullet 4 : sub-point 4a : sub-point 4b
```

View File

@ -4,7 +4,7 @@
"version": "10.2.4",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
"packageManager": "pnpm@8.14.1",
"packageManager": "pnpm@8.15.1",
"keywords": [
"diagram",
"markdown",
@ -74,7 +74,7 @@
"@types/jsdom": "^21.1.1",
"@types/lodash": "^4.14.194",
"@types/mdast": "^3.0.11",
"@types/node": "^18.16.0",
"@types/node": "^20.11.10",
"@types/prettier": "^2.7.2",
"@types/rollup-plugin-visualizer": "^4.2.1",
"@typescript-eslint/eslint-plugin": "^6.7.2",
@ -88,7 +88,7 @@
"cors": "^2.8.5",
"cypress": "^12.17.4",
"cypress-image-snapshot": "^4.0.1",
"esbuild": "^0.19.0",
"esbuild": "^0.20.0",
"eslint": "^8.47.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-cypress": "^2.13.2",
@ -120,14 +120,16 @@
"start-server-and-test": "^2.0.0",
"tsx": "^4.6.2",
"typescript": "^5.1.3",
"vite": "^4.4.12",
"vite": "^4.5.2",
"vite-plugin-istanbul": "^4.1.0",
"vitest": "^0.34.0"
},
"volta": {
"node": "18.19.0"
},
"nyc": {
"report-dir": "coverage/cypress"
},
"pnpm": {
"patchedDependencies": {
"cytoscape@3.28.1": "patches/cytoscape@3.28.1.patch"
}
}
}

View File

@ -39,14 +39,10 @@
},
"dependencies": {
"@braintree/sanitize-url": "^6.0.1",
"cytoscape": "^3.23.0",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.1.0",
"d3": "^7.0.0",
"khroma": "^2.0.0"
},
"devDependencies": {
"@types/cytoscape": "^3.19.9",
"concurrently": "^8.0.0",
"rimraf": "^5.0.0",
"mermaid": "workspace:*"

View File

@ -309,13 +309,12 @@ const getNextPosition = (position, edgeDirection, graphDirection) => {
},
};
portPos.TD = portPos.TB;
log.info('abc88', graphDirection, edgeDirection, position);
return portPos[graphDirection][edgeDirection][position];
// return 'south';
};
const getNextPort = (node, edgeDirection, graphDirection) => {
log.info('getNextPort abc88', { node, edgeDirection, graphDirection });
log.info('getNextPort', { node, edgeDirection, graphDirection });
if (!portPos[node]) {
switch (graphDirection) {
case 'TB':

View File

@ -34,7 +34,7 @@
],
"license": "MIT",
"dependencies": {
"@zenuml/core": "^3.0.6"
"@zenuml/core": "^3.17.2"
},
"devDependencies": {
"mermaid": "workspace:^"

View File

@ -56,7 +56,7 @@ export const draw = async function (text: string, id: string) {
// @ts-expect-error @zenuml/core@3.0.0 exports the wrong type for ZenUml
const zenuml = new ZenUml(app);
// default is a theme name. More themes to be added and will be configurable in the future
await zenuml.render(text, 'theme-mermaid');
await zenuml.render(text, { theme: 'default', mode: 'static' });
const { width, height } = window.getComputedStyle(container);
log.debug('zenuml diagram size', width, height);

View File

@ -61,9 +61,8 @@
"dependencies": {
"@braintree/sanitize-url": "^6.0.1",
"@mermaid-js/parser": "workspace:^",
"cytoscape": "^3.23.0",
"cytoscape": "^3.28.1",
"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",
@ -119,8 +118,8 @@
"typescript": "^5.0.4",
"unist-util-flatmap": "^1.0.0",
"unist-util-visit": "^4.1.2",
"vitepress": "^1.0.0-alpha.72",
"vitepress-plugin-search": "^1.0.4-alpha.20"
"vitepress": "^1.0.0-rc.40",
"vitepress-plugin-search": "^1.0.4-alpha.22"
},
"files": [
"dist/",

View File

@ -147,10 +147,12 @@ export interface MermaidConfig {
c4?: C4DiagramConfig;
sankey?: SankeyDiagramConfig;
packet?: PacketDiagramConfig;
block?: BlockDiagramConfig;
dompurifyConfig?: DOMPurifyConfiguration;
wrap?: boolean;
fontSize?: number;
}
/**
* The object containing configurations specific for packet diagrams.
*
@ -183,6 +185,16 @@ export interface PacketDiagramConfig extends BaseDiagramConfig {
*/
paddingY?: number;
}
/**
* The object containing configurations specific for block diagrams.
*
* This interface was referenced by `MermaidConfig`'s JSON-Schema
* via the `definition` "BlockDiagramConfig".
*/
export interface BlockDiagramConfig extends BaseDiagramConfig {
padding?: number;
}
/**
* This interface was referenced by `MermaidConfig`'s JSON-Schema
* via the `definition` "BaseDiagramConfig".

View File

@ -0,0 +1,248 @@
import type { Direction } from '../../src/diagrams/block/blockTypes.js';
const expandAndDeduplicateDirections = (directions: Direction[]) => {
const uniqueDirections = new Set();
for (const direction of directions) {
switch (direction) {
case 'x':
uniqueDirections.add('right');
uniqueDirections.add('left');
break;
case 'y':
uniqueDirections.add('up');
uniqueDirections.add('down');
break;
default:
uniqueDirections.add(direction);
break;
}
}
return uniqueDirections;
};
export const getArrowPoints = (
duplicatedDirections: Direction[],
bbox: { width: number; height: number },
node: any
) => {
// Expand and deduplicate the provided directions.
// for instance: x, right => right, left
const directions = expandAndDeduplicateDirections(duplicatedDirections);
// Factor to divide height for some calculations.
const f = 2;
// Calculated height of the bounding box, accounting for node padding.
const height = bbox.height + 2 * node.padding;
// Midpoint calculation based on height.
const midpoint = height / f;
// Calculated width of the bounding box, accounting for additional width and node padding.
const width = bbox.width + 2 * midpoint + node.padding;
// Padding to use, half of the node padding.
const padding = node.padding / 2;
// Initialize an empty array to store points for the arrow.
const points = [];
if (
directions.has('right') &&
directions.has('left') &&
directions.has('up') &&
directions.has('down')
) {
// SQUARE
return [
// Bottom
{ x: 0, y: 0 },
{ x: midpoint, y: 0 },
{ x: width / 2, y: 2 * padding },
{ x: width - midpoint, y: 0 },
{ x: width, y: 0 },
// Right
{ x: width, y: -height / 3 },
{ x: width + 2 * padding, y: -height / 2 },
{ x: width, y: (-2 * height) / 3 },
{ x: width, y: -height },
// Top
{ x: width - midpoint, y: -height },
{ x: width / 2, y: -height - 2 * padding },
{ x: midpoint, y: -height },
// Left
{ x: 0, y: -height },
{ x: 0, y: (-2 * height) / 3 },
{ x: -2 * padding, y: -height / 2 },
{ x: 0, y: -height / 3 },
];
}
if (directions.has('right') && directions.has('left') && directions.has('up')) {
// RECTANGLE_VERTICAL (Top Open)
return [
{ x: midpoint, y: 0 },
{ x: width - midpoint, y: 0 },
{ x: width, y: -height / 2 },
{ x: width - midpoint, y: -height },
{ x: midpoint, y: -height },
{ x: 0, y: -height / 2 },
];
}
if (directions.has('right') && directions.has('left') && directions.has('down')) {
// RECTANGLE_VERTICAL (Bottom Open)
return [
{ x: 0, y: 0 },
{ x: midpoint, y: -height },
{ x: width - midpoint, y: -height },
{ x: width, y: 0 },
];
}
if (directions.has('right') && directions.has('up') && directions.has('down')) {
// RECTANGLE_HORIZONTAL (Right Open)
return [
{ x: 0, y: 0 },
{ x: width, y: -midpoint },
{ x: width, y: -height + midpoint },
{ x: 0, y: -height },
];
}
if (directions.has('left') && directions.has('up') && directions.has('down')) {
// RECTANGLE_HORIZONTAL (Left Open)
return [
{ x: width, y: 0 },
{ x: 0, y: -midpoint },
{ x: 0, y: -height + midpoint },
{ x: width, y: -height },
];
}
if (directions.has('right') && directions.has('left')) {
// HORIZONTAL_LINE
return [
{ x: midpoint, y: 0 },
{ x: midpoint, y: -padding },
{ x: width - midpoint, y: -padding },
{ x: width - midpoint, y: 0 },
{ x: width, y: -height / 2 },
{ x: width - midpoint, y: -height },
{ x: width - midpoint, y: -height + padding },
{ x: midpoint, y: -height + padding },
{ x: midpoint, y: -height },
{ x: 0, y: -height / 2 },
];
}
if (directions.has('up') && directions.has('down')) {
// VERTICAL_LINE
return [
// Bottom center
{ x: width / 2, y: 0 },
// Left pont of bottom arrow
{ x: 0, y: -padding },
{ x: midpoint, y: -padding },
// Left top over vertical section
{ x: midpoint, y: -height + padding },
{ x: 0, y: -height + padding },
// Top of arrow
{ x: width / 2, y: -height },
{ x: width, y: -height + padding },
// Top of right vertical bar
{ x: width - midpoint, y: -height + padding },
{ x: width - midpoint, y: -padding },
{ x: width, y: -padding },
];
}
if (directions.has('right') && directions.has('up')) {
// ANGLE_RT
return [
{ x: 0, y: 0 },
{ x: width, y: -midpoint },
{ x: 0, y: -height },
];
}
if (directions.has('right') && directions.has('down')) {
// ANGLE_RB
return [
{ x: 0, y: 0 },
{ x: width, y: 0 },
{ x: 0, y: -height },
];
}
if (directions.has('left') && directions.has('up')) {
// ANGLE_LT
return [
{ x: width, y: 0 },
{ x: 0, y: -midpoint },
{ x: width, y: -height },
];
}
if (directions.has('left') && directions.has('down')) {
// ANGLE_LB
return [
{ x: width, y: 0 },
{ x: 0, y: 0 },
{ x: width, y: -height },
];
}
if (directions.has('right')) {
// ARROW_RIGHT
return [
{ x: midpoint, y: -padding },
{ x: midpoint, y: -padding },
{ x: width - midpoint, y: -padding },
{ x: width - midpoint, y: 0 },
{ x: width, y: -height / 2 },
{ x: width - midpoint, y: -height },
{ x: width - midpoint, y: -height + padding },
// top left corner of arrow
{ x: midpoint, y: -height + padding },
{ x: midpoint, y: -height + padding },
];
}
if (directions.has('left')) {
// ARROW_LEFT
return [
{ x: midpoint, y: 0 },
{ x: midpoint, y: -padding },
// Two points, the right corners
{ x: width - midpoint, y: -padding },
{ x: width - midpoint, y: -height + padding },
{ x: midpoint, y: -height + padding },
{ x: midpoint, y: -height },
{ x: 0, y: -height / 2 },
];
}
if (directions.has('up')) {
// ARROW_TOP
return [
// Bottom center
{ x: midpoint, y: -padding },
// Left top over vertical section
{ x: midpoint, y: -height + padding },
{ x: 0, y: -height + padding },
// Top of arrow
{ x: width / 2, y: -height },
{ x: width, y: -height + padding },
// Top of right vertical bar
{ x: width - midpoint, y: -height + padding },
{ x: width - midpoint, y: -padding },
];
}
if (directions.has('down')) {
// ARROW_BOTTOM
return [
// Bottom center
{ x: width / 2, y: 0 },
// Left pont of bottom arrow
{ x: 0, y: -padding },
{ x: midpoint, y: -padding },
// Left top over vertical section
{ x: midpoint, y: -height + padding },
{ x: width - midpoint, y: -height + padding },
{ x: width - midpoint, y: -padding },
{ x: width, y: -padding },
];
}
// POINT
return [{ x: 0, y: 0 }];
};

View File

@ -56,7 +56,7 @@ const createLabel = (_vertexText, style, isTitle, isNode) => {
if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
vertexText = vertexText.replace(/\\n|\n/g, '<br />');
log.info('vertexText' + vertexText);
log.debug('vertexText' + vertexText);
const node = {
isNode,
label: decodeEntities(vertexText).replace(

View File

@ -28,7 +28,6 @@ export const insertEdgeLabel = (elem, edge) => {
addSvgBackground: true,
})
: createLabel(edge.label, edge.labelStyle);
log.info('abc82', edge, edge.labelType);
// Create outer g, edgeLabel, this will be positioned after graph layout
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
@ -135,7 +134,7 @@ function setTerminalWidth(fo, value) {
}
export const positionEdgeLabel = (edge, paths) => {
log.info('Moving label abc78 ', edge.id, edge.label, edgeLabels[edge.id]);
log.debug('Moving label abc88 ', edge.id, edge.label, edgeLabels[edge.id], paths);
let path = paths.updatedPath ? paths.updatedPath : paths.originalPath;
const siteConfig = getConfig();
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
@ -146,7 +145,7 @@ export const positionEdgeLabel = (edge, paths) => {
if (path) {
// // debugger;
const pos = utils.calcLabelPosition(path);
log.info(
log.debug(
'Moving label ' + edge.label + ' from (',
x,
',',
@ -155,7 +154,7 @@ export const positionEdgeLabel = (edge, paths) => {
pos.x,
',',
pos.y,
') abc78'
') abc88'
);
if (paths.updatedPath) {
x = pos.x;
@ -221,7 +220,6 @@ export const positionEdgeLabel = (edge, paths) => {
};
const outsideNode = (node, point) => {
// log.warn('Checking bounds ', node, point);
const x = node.x;
const y = node.y;
const dx = Math.abs(point.x - x);
@ -235,7 +233,7 @@ const outsideNode = (node, point) => {
};
export const intersection = (node, outsidePoint, insidePoint) => {
log.warn(`intersection calc abc89:
log.debug(`intersection calc abc89:
outsidePoint: ${JSON.stringify(outsidePoint)}
insidePoint : ${JSON.stringify(insidePoint)}
node : x:${node.x} y:${node.y} w:${node.width} h:${node.height}`);
@ -248,29 +246,11 @@ export const intersection = (node, outsidePoint, insidePoint) => {
let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx;
const h = node.height / 2;
// const edges = {
// x1: x - w,
// x2: x + w,
// y1: y - h,
// y2: y + h
// };
// if (
// outsidePoint.x === edges.x1 ||
// outsidePoint.x === edges.x2 ||
// outsidePoint.y === edges.y1 ||
// outsidePoint.y === edges.y2
// ) {
// log.warn('abc89 calc equals on edge', outsidePoint, edges);
// return outsidePoint;
// }
const Q = Math.abs(outsidePoint.y - insidePoint.y);
const R = Math.abs(outsidePoint.x - insidePoint.x);
// log.warn();
if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h) {
// Intersection is top or bottom of rect.
// let q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y;
let q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y;
r = (R * q) / Q;
const res = {
@ -289,7 +269,7 @@ export const intersection = (node, outsidePoint, insidePoint) => {
res.y = outsidePoint.y;
}
log.warn(`abc89 topp/bott calc, Q ${Q}, q ${q}, R ${R}, r ${r}`, res);
log.debug(`abc89 topp/bott calc, Q ${Q}, q ${q}, R ${R}, r ${r}`, res);
return res;
} else {
@ -306,7 +286,7 @@ export const intersection = (node, outsidePoint, insidePoint) => {
let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - R + r;
// let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : outsidePoint.x + r;
let _y = insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q;
log.warn(`sides calc abc89, Q ${Q}, q ${q}, R ${R}, r ${r}`, { _x, _y });
log.debug(`sides calc abc89, Q ${Q}, q ${q}, R ${R}, r ${r}`, { _x, _y });
if (r === 0) {
_x = outsidePoint.x;
_y = outsidePoint.y;
@ -330,21 +310,16 @@ export const intersection = (node, outsidePoint, insidePoint) => {
* @returns {Array} Points
*/
const cutPathAtIntersect = (_points, boundryNode) => {
log.warn('abc88 cutPathAtIntersect', _points, boundryNode);
log.debug('abc88 cutPathAtIntersect', _points, boundryNode);
let points = [];
let lastPointOutside = _points[0];
let isInside = false;
_points.forEach((point) => {
// const node = clusterDb[edge.toCluster].node;
log.info('abc88 checking point', point, boundryNode);
// check if point is inside the boundary rect
if (!outsideNode(boundryNode, point) && !isInside) {
// First point inside the rect found
// Calc the intersection coord between the point anf the last point outside the rect
const inter = intersection(boundryNode, lastPointOutside, point);
log.warn('abc88 inside', point, lastPointOutside, inter);
log.warn('abc88 intersection', inter);
// // Check case where the intersection is the same as the last point
let pointPresent = false;
@ -354,14 +329,11 @@ const cutPathAtIntersect = (_points, boundryNode) => {
// // if (!pointPresent) {
if (!points.some((e) => e.x === inter.x && e.y === inter.y)) {
points.push(inter);
} else {
log.warn('abc88 no intersect', inter, points);
}
// points.push(inter);
isInside = true;
} else {
// Outside
log.warn('abc88 outside', point, lastPointOutside);
lastPointOutside = point;
// points.push(point);
if (!isInside) {
@ -369,67 +341,31 @@ const cutPathAtIntersect = (_points, boundryNode) => {
}
}
});
log.warn('abc88 returning points', points);
return points;
};
export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph, id) {
let points = edge.points;
log.debug('abc88 InsertEdge: edge=', edge, 'e=', e);
let pointsHasChanged = false;
const tail = graph.node(e.v);
var head = graph.node(e.w);
log.info('abc88 InsertEdge: ', edge);
if (head.intersect && tail.intersect) {
if (head?.intersect && tail?.intersect) {
points = points.slice(1, edge.points.length - 1);
points.unshift(tail.intersect(points[0]));
log.info(
'Last point',
points[points.length - 1],
head,
head.intersect(points[points.length - 1])
);
points.push(head.intersect(points[points.length - 1]));
}
if (edge.toCluster) {
log.info('to cluster abc88', clusterDb[edge.toCluster]);
log.debug('to cluster abc88', clusterDb[edge.toCluster]);
points = cutPathAtIntersect(edge.points, clusterDb[edge.toCluster].node);
// log.trace('edge', edge);
// points = [];
// let lastPointOutside; // = edge.points[0];
// let isInside = false;
// edge.points.forEach(point => {
// const node = clusterDb[edge.toCluster].node;
// log.warn('checking from', edge.fromCluster, point, node);
// if (!outsideNode(node, point) && !isInside) {
// log.trace('inside', edge.toCluster, point, lastPointOutside);
// // First point inside the rect
// const inter = intersection(node, lastPointOutside, point);
// let pointPresent = false;
// points.forEach(p => {
// pointPresent = pointPresent || (p.x === inter.x && p.y === inter.y);
// });
// // if (!pointPresent) {
// if (!points.find(e => e.x === inter.x && e.y === inter.y)) {
// points.push(inter);
// } else {
// log.warn('no intersect', inter, points);
// }
// isInside = true;
// } else {
// // outside
// lastPointOutside = point;
// if (!isInside) points.push(point);
// }
// });
pointsHasChanged = true;
}
if (edge.fromCluster) {
log.info('from cluster abc88', clusterDb[edge.fromCluster]);
log.debug('from cluster abc88', clusterDb[edge.fromCluster]);
points = cutPathAtIntersect(points.reverse(), clusterDb[edge.fromCluster].node).reverse();
pointsHasChanged = true;
@ -507,8 +443,6 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
}
log.info('arrowTypeStart', edge.arrowTypeStart);
log.info('arrowTypeEnd', edge.arrowTypeEnd);
addEdgeMarkers(svgPath, edge, url, id, diagramType);

View File

@ -6,6 +6,7 @@ import intersect from './intersect/index.js';
import createLabel from './createLabel.js';
import note from './shapes/note.js';
import { evaluate } from '../diagrams/common/common.js';
import { getArrowPoints } from './blockArrowHelper.js';
const formatClass = (str) => {
if (str) {
@ -30,6 +31,7 @@ const question = async (parent, node) => {
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
const s = w + h;
const points = [
{ x: s / 2, y: 0 },
{ x: s, y: -s / 2 },
@ -117,6 +119,27 @@ const hexagon = async (parent, node) => {
return shapeSvg;
};
const block_arrow = async (parent, node) => {
const { shapeSvg, bbox } = await labelHelper(parent, node, undefined, true);
const f = 2;
const h = bbox.height + 2 * node.padding;
const m = h / f;
const w = bbox.width + 2 * m + node.padding;
const points = getArrowPoints(node.directions, bbox, node);
const blockArrow = insertPolygonShape(shapeSvg, w, h, points);
blockArrow.attr('style', node.style);
updateNodeBounds(node, blockArrow);
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};
return shapeSvg;
};
const rect_left_inv_arrow = async (parent, node) => {
const { shapeSvg, bbox } = await labelHelper(
parent,
@ -374,17 +397,64 @@ const rect = async (parent, node) => {
// const totalWidth = bbox.width + node.padding * 2;
// const totalHeight = bbox.height + node.padding * 2;
const totalWidth = bbox.width + node.padding;
const totalHeight = bbox.height + node.padding;
const totalWidth = node.positioned ? node.width : bbox.width + node.padding;
const totalHeight = node.positioned ? node.height : bbox.height + node.padding;
const x = node.positioned ? -totalWidth / 2 : -bbox.width / 2 - halfPadding;
const y = node.positioned ? -totalHeight / 2 : -bbox.height / 2 - halfPadding;
rect
.attr('class', 'basic label-container')
.attr('style', node.style)
.attr('rx', node.rx)
.attr('ry', node.ry)
// .attr('x', -bbox.width / 2 - node.padding)
// .attr('y', -bbox.height / 2 - node.padding)
.attr('x', -bbox.width / 2 - halfPadding)
.attr('y', -bbox.height / 2 - halfPadding)
.attr('x', x)
.attr('y', y)
.attr('width', totalWidth)
.attr('height', totalHeight);
if (node.props) {
const propKeys = new Set(Object.keys(node.props));
if (node.props.borders) {
applyNodePropertyBorders(rect, node.props.borders, totalWidth, totalHeight);
propKeys.delete('borders');
}
propKeys.forEach((propKey) => {
log.warn(`Unknown node property ${propKey}`);
});
}
updateNodeBounds(node, rect);
node.intersect = function (point) {
return intersect.rect(node, point);
};
return shapeSvg;
};
const composite = async (parent, node) => {
const { shapeSvg, bbox, halfPadding } = await labelHelper(
parent,
node,
'node ' + node.classes,
true
);
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
// const totalWidth = bbox.width + node.padding * 2;
// const totalHeight = bbox.height + node.padding * 2;
const totalWidth = node.positioned ? node.width : bbox.width + node.padding;
const totalHeight = node.positioned ? node.height : bbox.height + node.padding;
const x = node.positioned ? -totalWidth / 2 : -bbox.width / 2 - halfPadding;
const y = node.positioned ? -totalHeight / 2 : -bbox.height / 2 - halfPadding;
rect
.attr('class', 'basic cluster composite label-container')
.attr('style', node.style)
.attr('rx', node.rx)
.attr('ry', node.ry)
.attr('x', x)
.attr('y', y)
.attr('width', totalWidth)
.attr('height', totalHeight);
@ -1031,6 +1101,7 @@ const class_box = (parent, node) => {
const shapes = {
rhombus: question,
composite,
question,
rect,
labelRect,
@ -1040,6 +1111,7 @@ const shapes = {
doublecircle,
stadium,
hexagon,
block_arrow,
rect_left_inv_arrow,
lean_right,
lean_left,
@ -1082,6 +1154,9 @@ export const insertNode = async (elem, node, dir) => {
if (node.class) {
el.attr('class', 'node default ' + node.class);
}
// MC Special
newEl.attr('data-node', 'true');
newEl.attr('data-id', node.id);
nodeElems[node.id] = newEl;
@ -1099,7 +1174,6 @@ export const clear = () => {
export const positionNode = (node) => {
const el = nodeElems[node.id];
log.trace(
'Transforming node',
node.diff,

View File

@ -115,6 +115,7 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
}
label.insert('rect', ':first-child');
return { shapeSvg, bbox, halfPadding, label };
};

View File

@ -21,6 +21,7 @@ import timeline from '../diagrams/timeline/detector.js';
import mindmap from '../diagrams/mindmap/detector.js';
import sankey from '../diagrams/sankey/sankeyDetector.js';
import { packet } from '../diagrams/packet/detector.js';
import block from '../diagrams/block/blockDetector.js';
import { registerLazyLoadedDiagrams } from './detectType.js';
import { registerDiagram } from './diagramAPI.js';
@ -88,6 +89,7 @@ export const addDiagrams = () => {
quadrantChart,
sankey,
packet,
xychart
xychart,
block
);
};

View File

@ -0,0 +1,311 @@
import type { DiagramDB } from '../../diagram-api/types.js';
import type { BlockConfig, BlockType, Block, ClassDef } from './blockTypes.js';
import * as configApi from '../../config.js';
import { clear as commonClear } from '../common/commonDb.js';
import { log } from '../../logger.js';
import clone from 'lodash-es/clone.js';
// Initialize the node database for simple lookups
let blockDatabase: Record<string, Block> = {};
let edgeList: Block[] = [];
let edgeCount: Record<string, number> = {};
const COLOR_KEYWORD = 'color';
const FILL_KEYWORD = 'fill';
const BG_FILL = 'bgFill';
const STYLECLASS_SEP = ',';
let classes = {} as Record<string, ClassDef>;
/**
* Called when the parser comes across a (style) class definition
* @example classDef my-style fill:#f96;
*
* @param id - the id of this (style) class
* @param styleAttributes - the string with 1 or more style attributes (each separated by a comma)
*/
export const addStyleClass = function (id: string, styleAttributes = '') {
// create a new style class object with this id
if (classes[id] === undefined) {
classes[id] = { id: id, styles: [], textStyles: [] }; // This is a classDef
}
const foundClass = classes[id];
if (styleAttributes !== undefined && styleAttributes !== null) {
styleAttributes.split(STYLECLASS_SEP).forEach((attrib) => {
// remove any trailing ;
const fixedAttrib = attrib.replace(/([^;]*);/, '$1').trim();
// replace some style keywords
if (attrib.match(COLOR_KEYWORD)) {
const newStyle1 = fixedAttrib.replace(FILL_KEYWORD, BG_FILL);
const newStyle2 = newStyle1.replace(COLOR_KEYWORD, FILL_KEYWORD);
foundClass.textStyles.push(newStyle2);
}
foundClass.styles.push(fixedAttrib);
});
}
};
/**
* Called when the parser comes across a style definition
* @example style my-block-id fill:#f96;
*
* @param id - the id of the block to style
* @param styles - the string with 1 or more style attributes (each separated by a comma)
*/
export const addStyle2Node = function (id: string, styles = '') {
const foundBlock = blockDatabase[id];
if (styles !== undefined && styles !== null) {
foundBlock.styles = styles.split(STYLECLASS_SEP);
}
};
/**
* Add a CSS/style class to the block with the given id.
* If the block isn't already in the list of known blocks, add it.
* Might be called by parser when a CSS/style class should be applied to a block
*
* @param itemIds - The id or a list of ids of the item(s) to apply the css class to
* @param cssClassName - CSS class name
*/
export const setCssClass = function (itemIds: string, cssClassName: string) {
itemIds.split(',').forEach(function (id: string) {
let foundBlock = blockDatabase[id];
if (foundBlock === undefined) {
const trimmedId = id.trim();
blockDatabase[trimmedId] = { id: trimmedId, type: 'na', children: [] } as Block;
foundBlock = blockDatabase[trimmedId];
}
if (!foundBlock.classes) {
foundBlock.classes = [];
}
foundBlock.classes.push(cssClassName);
});
};
const populateBlockDatabase = (_blockList: Block[] | Block[][], parent: Block): void => {
const blockList = _blockList.flat();
const children = [];
for (const block of blockList) {
if (block.type === 'classDef') {
addStyleClass(block.id, block.css);
continue;
}
if (block.type === 'applyClass') {
setCssClass(block.id, block?.styleClass || '');
continue;
}
if (block.type === 'applyStyles') {
if (block?.stylesStr) {
addStyle2Node(block.id, block?.stylesStr);
}
continue;
}
if (block.type === 'column-setting') {
parent.columns = block.columns || -1;
} else if (block.type === 'edge') {
if (edgeCount[block.id]) {
edgeCount[block.id]++;
} else {
edgeCount[block.id] = 1;
}
block.id = edgeCount[block.id] + '-' + block.id;
edgeList.push(block);
} else {
if (!block.label) {
if (block.type === 'composite') {
block.label = '';
// log.debug('abc89 composite', block);
} else {
block.label = block.id;
}
}
const newBlock = !blockDatabase[block.id];
if (newBlock) {
blockDatabase[block.id] = block;
} else {
// Add newer relevant data to aggregated node
if (block.type !== 'na') {
blockDatabase[block.id].type = block.type;
}
if (block.label !== block.id) {
blockDatabase[block.id].label = block.label;
}
}
if (block.children) {
populateBlockDatabase(block.children, block);
}
if (block.type === 'space') {
// log.debug('abc95 space', block);
const w = block.width || 1;
for (let j = 0; j < w; j++) {
const newBlock = clone(block);
newBlock.id = newBlock.id + '-' + j;
blockDatabase[newBlock.id] = newBlock;
children.push(newBlock);
}
} else if (newBlock) {
children.push(block);
}
}
}
parent.children = children;
};
let blocks: Block[] = [];
let rootBlock = { id: 'root', type: 'composite', children: [], columns: -1 } as Block;
const clear = (): void => {
log.debug('Clear called');
commonClear();
rootBlock = { id: 'root', type: 'composite', children: [], columns: -1 } as Block;
blockDatabase = { root: rootBlock };
blocks = [] as Block[];
classes = {} as Record<string, ClassDef>;
edgeList = [];
edgeCount = {};
};
export function typeStr2Type(typeStr: string) {
log.debug('typeStr2Type', typeStr);
switch (typeStr) {
case '[]':
return 'square';
case '()':
log.debug('we have a round');
return 'round';
case '(())':
return 'circle';
case '>]':
return 'rect_left_inv_arrow';
case '{}':
return 'diamond';
case '{{}}':
return 'hexagon';
case '([])':
return 'stadium';
case '[[]]':
return 'subroutine';
case '[()]':
return 'cylinder';
case '((()))':
return 'doublecircle';
case '[//]':
return 'lean_right';
case '[\\\\]':
return 'lean_left';
case '[/\\]':
return 'trapezoid';
case '[\\/]':
return 'inv_trapezoid';
case '<[]>':
return 'block_arrow';
default:
return 'na';
}
}
export function edgeTypeStr2Type(typeStr: string): string {
log.debug('typeStr2Type', typeStr);
switch (typeStr) {
case '==':
return 'thick';
default:
return 'normal';
}
}
export function edgeStrToEdgeData(typeStr: string): string {
switch (typeStr.trim()) {
case '--x':
return 'arrow_cross';
case '--o':
return 'arrow_circle';
default:
return 'arrow_point';
}
}
let cnt = 0;
export const generateId = () => {
cnt++;
return 'id-' + Math.random().toString(36).substr(2, 12) + '-' + cnt;
};
const setHierarchy = (block: Block[]): void => {
rootBlock.children = block;
populateBlockDatabase(block, rootBlock);
blocks = rootBlock.children;
};
const getColumns = (blockid: string): number => {
const block = blockDatabase[blockid];
if (!block) {
return -1;
}
if (block.columns) {
return block.columns;
}
if (!block.children) {
return -1;
}
return block.children.length;
};
/**
* Returns all the blocks as a flat array
* @returns
*/
const getBlocksFlat = () => {
return [...Object.values(blockDatabase)];
};
/**
* Returns the the hierarchy of blocks
* @returns
*/
const getBlocks = () => {
return blocks || [];
};
const getEdges = () => {
return edgeList;
};
const getBlock = (id: string) => {
return blockDatabase[id];
};
const setBlock = (block: Block) => {
blockDatabase[block.id] = block;
};
const getLogger = () => console;
/**
* Return all of the style classes
*/
export const getClasses = function () {
return classes;
};
const db = {
getConfig: () => configApi.getConfig().block,
typeStr2Type: typeStr2Type,
edgeTypeStr2Type: edgeTypeStr2Type,
edgeStrToEdgeData,
getLogger,
getBlocksFlat,
getBlocks,
getEdges,
setHierarchy,
getBlock,
setBlock,
getColumns,
getClasses,
clear,
generateId,
} as const;
export type BlockDB = typeof db & DiagramDB;
export default db;

View File

@ -0,0 +1,20 @@
import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types.js';
const id = 'block';
const detector: DiagramDetector = (txt) => {
return /^\s*block-beta/.test(txt);
};
const loader = async () => {
const { diagram } = await import('./blockDiagram.js');
return { id, diagram };
};
const plugin: ExternalDiagramDefinition = {
id,
detector,
loader,
};
export default plugin;

View File

@ -0,0 +1,13 @@
import type { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: jison doesn't export types
import parser from './parser/block.jison';
import db from './blockDB.js';
import flowStyles from './styles.js';
import renderer from './blockRenderer.js';
export const diagram: DiagramDefinition = {
parser,
db,
renderer,
styles: flowStyles,
};

View File

@ -0,0 +1,96 @@
import type { Diagram } from '../../Diagram.js';
import * as configApi from '../../config.js';
import { calculateBlockSizes, insertBlocks, insertEdges } from './renderHelpers.js';
import { layout } from './layout.js';
import type { MermaidConfig, BaseDiagramConfig } from '../../config.type.js';
import insertMarkers from '../../dagre-wrapper/markers.js';
import {
select as d3select,
scaleOrdinal as d3scaleOrdinal,
schemeTableau10 as d3schemeTableau10,
} from 'd3';
import type { ContainerElement } from 'd3';
import { log } from '../../logger.js';
import type { BlockDB } from './blockDB.js';
import type { Block } from './blockTypes.js';
import { configureSvgSize } from '../../setupGraphViewbox.js';
/**
* Returns the all the styles from classDef statements in the graph definition.
*
* @param text - The text with the classes
* @param diagObj - The diagram object
* @returns ClassDef - The styles
*/
export const getClasses = function (text: any, diagObj: any) {
return diagObj.db.getClasses();
};
export const draw = async function (
text: string,
id: string,
_version: string,
diagObj: Diagram
): Promise<void> {
const { securityLevel, block: conf } = configApi.getConfig();
const db = diagObj.db as BlockDB;
let sandboxElement: any;
if (securityLevel === 'sandbox') {
sandboxElement = d3select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? d3select<HTMLBodyElement, unknown>(sandboxElement.nodes()[0].contentDocument.body)
: d3select<HTMLBodyElement, unknown>('body');
const svg =
securityLevel === 'sandbox'
? root.select<SVGSVGElement>(`[id="${id}"]`)
: d3select<SVGSVGElement, unknown>(`[id="${id}"]`);
// Define the supported markers for the diagram
const markers = ['point', 'circle', 'cross'];
// Add the marker definitions to the svg as marker tags
// insertMarkers(svg, markers, diagObj.type, diagObj.arrowMarkerAbsolute);
// insertMarkers(svg, markers, diagObj.type, true);
insertMarkers(svg, markers, diagObj.type, id);
const bl = db.getBlocks();
const blArr = db.getBlocksFlat();
const edges = db.getEdges();
const nodes = svg.insert('g').attr('class', 'block');
await calculateBlockSizes(nodes, bl, db);
const bounds = layout(db);
await insertBlocks(nodes, bl, db);
await insertEdges(nodes, edges, blArr, db, id);
// log.debug('Here', bl);
// Establish svg dimensions and get width and height
//
// const bounds2 = nodes.node().getBoundingClientRect();
// Why, oh why ????
if (bounds) {
const bounds2 = bounds;
const magicFactor = Math.max(1, Math.round(0.125 * (bounds2.width / bounds2.height)));
const height = bounds2.height + magicFactor + 10;
const width = bounds2.width + 10;
const { useMaxWidth } = conf as Exclude<MermaidConfig['block'], undefined>;
configureSvgSize(svg, height, width, !!useMaxWidth);
log.debug('Here Bounds', bounds, bounds2);
svg.attr(
'viewBox',
`${bounds2.x - 5} ${bounds2.y - 5} ${bounds2.width + 10} ${bounds2.height + 10}`
);
}
// Get color scheme for the graph
const colorScheme = d3scaleOrdinal(d3schemeTableau10);
};
export default {
draw,
getClasses,
};

View File

@ -0,0 +1,68 @@
export type { BlockDiagramConfig as BlockConfig } from '../../config.type.js';
export type BlockType =
| 'na'
| 'column-setting'
| 'edge'
| 'round'
| 'block_arrow'
| 'space'
| 'square'
| 'diamond'
| 'hexagon'
| 'odd'
| 'lean_right'
| 'lean_left'
| 'trapezoid'
| 'inv_trapezoid'
| 'rect_left_inv_arrow'
| 'odd_right'
| 'circle'
| 'ellipse'
| 'stadium'
| 'subroutine'
| 'cylinder'
| 'group'
| 'doublecircle'
| 'classDef'
| 'applyClass'
| 'applyStyles'
| 'composite';
export interface Block {
// When the block have the type edge, the start and end are the id of the source and target blocks
start?: string;
end?: string;
arrowTypeEnd?: string;
arrowTypeStart?: string;
width?: number;
id: string;
label?: string;
intersect?: any;
parent?: Block;
type?: BlockType;
children: Block[];
size?: {
width: number;
height: number;
x: number;
y: number;
};
node?: any;
columns?: number; // | TBlockColumnsDefaultValue;
classes?: string[];
directions?: string[];
css?: string;
styleClass?: string;
styles?: string[];
stylesStr?: string;
widthInColumns?: number;
}
export interface ClassDef {
id: string;
textStyles: string[];
styles: string[];
}
export type Direction = 'up' | 'down' | 'left' | 'right' | 'x' | 'y';

View File

@ -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;
};

View File

@ -0,0 +1,12 @@
import { calculateBlockPosition } from './layout.js';
describe('Layout', function () {
it('should calculate position correctly', () => {
expect(calculateBlockPosition(2, 0)).toEqual({ px: 0, py: 0 });
expect(calculateBlockPosition(2, 1)).toEqual({ px: 1, py: 0 });
expect(calculateBlockPosition(2, 2)).toEqual({ px: 0, py: 1 });
expect(calculateBlockPosition(2, 3)).toEqual({ px: 1, py: 1 });
expect(calculateBlockPosition(2, 4)).toEqual({ px: 0, py: 2 });
expect(calculateBlockPosition(1, 3)).toEqual({ px: 0, py: 3 });
});
});

View File

@ -0,0 +1,324 @@
import type { BlockDB } from './blockDB.js';
import type { Block } from './blockTypes.js';
import { log } from '../../logger.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
const padding = getConfig()?.block?.padding || 8;
interface BlockPosition {
px: number;
py: number;
}
export function calculateBlockPosition(columns: number, position: number): BlockPosition {
// log.debug('calculateBlockPosition abc89', columns, position);
// Ensure that columns is a positive integer
if (columns === 0 || !Number.isInteger(columns)) {
throw new Error('Columns must be an integer !== 0.');
}
// Ensure that position is a non-negative integer
if (position < 0 || !Number.isInteger(position)) {
throw new Error('Position must be a non-negative integer.' + position);
}
if (columns < 0) {
// Auto coulumns is set
return { px: position, py: 0 };
}
if (columns === 1) {
// Auto coulumns is set
return { px: 0, py: position };
}
// Calculate posX and posY
const px = position % columns;
const py = Math.floor(position / columns);
// log.debug('calculateBlockPosition abc89', columns, position, '=> (', px, py, ')');
return { px, py };
}
const getMaxChildSize = (block: Block) => {
let maxWidth = 0;
let maxHeight = 0;
// find max width of children
// log.debug('getMaxChildSize abc95 (start) parent:', block.id);
for (const child of block.children) {
const { width, height, x, y } = child.size || { width: 0, height: 0, x: 0, y: 0 };
log.debug(
'getMaxChildSize abc95 child:',
child.id,
'width:',
width,
'height:',
height,
'x:',
x,
'y:',
y,
child.type
);
if (child.type === 'space') {
continue;
}
if (width > maxWidth) {
maxWidth = width / (block.widthInColumns || 1);
}
if (height > maxHeight) {
maxHeight = height;
}
}
return { width: maxWidth, height: maxHeight };
};
function setBlockSizes(block: Block, db: BlockDB, siblingWidth = 0, siblingHeight = 0) {
log.debug(
'setBlockSizes abc95 (start)',
block.id,
block?.size?.x,
'block width =',
block?.size,
'sieblingWidth',
siblingWidth
);
if (!block?.size?.width) {
block.size = {
width: siblingWidth,
height: siblingHeight,
x: 0,
y: 0,
};
}
let maxWidth = 0;
let maxHeight = 0;
if (block.children?.length > 0) {
for (const child of block.children) {
setBlockSizes(child, db);
}
// find max width of children
const childSize = getMaxChildSize(block);
maxWidth = childSize.width;
maxHeight = childSize.height;
log.debug('setBlockSizes abc95 maxWidth of', block.id, ':s children is ', maxWidth, maxHeight);
// set width of block to max width of children
for (const child of block.children) {
if (child.size) {
log.debug(
`abc95 Setting size of children of ${block.id} id=${child.id} ${maxWidth} ${maxHeight} ${child.size}`
);
child.size.width =
maxWidth * (child.widthInColumns || 1) + padding * ((child.widthInColumns || 1) - 1);
child.size.height = maxHeight;
child.size.x = 0;
child.size.y = 0;
log.debug(
`abc95 updating size of ${block.id} children child:${child.id} maxWidth:${maxWidth} maxHeight:${maxHeight}`
);
}
}
for (const child of block.children) {
setBlockSizes(child, db, maxWidth, maxHeight);
}
const columns = block.columns || -1;
const numItems = block.children.length;
// The width and height in number blocks
let xSize = block.children.length;
if (columns > 0 && columns < numItems) {
xSize = columns;
}
const w = block.widthInColumns || 1;
const ySize = Math.ceil(numItems / xSize);
let width = xSize * (maxWidth + padding) + padding;
let height = ySize * (maxHeight + padding) + padding;
// If maxWidth
if (width < siblingWidth) {
log.debug(
`Detected to small siebling: abc95 ${block.id} sieblingWidth ${siblingWidth} sieblingHeight ${siblingHeight} width ${width}`
);
width = siblingWidth;
height = siblingHeight;
const childWidth = (siblingWidth - xSize * padding - padding) / xSize;
const childHeight = (siblingHeight - ySize * padding - padding) / ySize;
log.debug('Size indata abc88', block.id, 'childWidth', childWidth, 'maxWidth', maxWidth);
log.debug('Size indata abc88', block.id, 'childHeight', childHeight, 'maxHeight', maxHeight);
log.debug('Size indata abc88 xSize', xSize, 'paddiong', padding);
// set width of block to max width of children
for (const child of block.children) {
if (child.size) {
child.size.width = childWidth;
child.size.height = childHeight;
child.size.x = 0;
child.size.y = 0;
}
}
}
log.debug(
`abc95 (finale calc) ${block.id} xSize ${xSize} ySize ${ySize} columns ${columns}${
block.children.length
} width=${Math.max(width, block.size?.width || 0)}`
);
if (width < (block?.size?.width || 0)) {
width = block?.size?.width || 0;
// Grow children to fit
const num = columns > 0 ? Math.min(block.children.length, columns) : block.children.length;
if (num > 0) {
const childWidth = (width - num * padding - padding) / num;
log.debug('abc95 (growing to fit) width', block.id, width, block.size?.width, childWidth);
for (const child of block.children) {
if (child.size) {
child.size.width = childWidth;
}
}
}
}
block.size = {
width,
height,
x: 0,
y: 0,
};
}
log.debug(
'setBlockSizes abc94 (done)',
block.id,
block?.size?.x,
block?.size?.width,
block?.size?.y,
block?.size?.height
);
}
function layoutBlocks(block: Block, db: BlockDB) {
log.debug(
`abc85 layout blocks (=>layoutBlocks) ${block.id} x: ${block?.size?.x} y: ${block?.size?.y} width: ${block?.size?.width}`
);
const columns = block.columns || -1;
log.debug('layoutBlocks columns abc95', block.id, '=>', columns, block);
if (
block.children && // find max width of children
block.children.length > 0
) {
const width = block?.children[0]?.size?.width || 0;
const widthOfChildren = block.children.length * width + (block.children.length - 1) * padding;
log.debug('widthOfChildren 88', widthOfChildren, 'posX');
// let first = true;
let columnPos = 0;
log.debug('abc91 block?.size?.x', block.id, block?.size?.x);
let startingPosX = block?.size?.x ? block?.size?.x + (-block?.size?.width / 2 || 0) : -padding;
let rowPos = 0;
for (const child of block.children) {
const parent = block;
if (!child.size) {
continue;
}
const { width, height } = child.size;
const { px, py } = calculateBlockPosition(columns, columnPos);
if (py != rowPos) {
rowPos = py;
startingPosX = block?.size?.x ? block?.size?.x + (-block?.size?.width / 2 || 0) : -padding;
log.debug('New row in layout for block', block.id, ' and child ', child.id, rowPos);
}
log.debug(
`abc89 layout blocks (child) id: ${child.id} Pos: ${columnPos} (px, py) ${px},${py} (${parent?.size?.x},${parent?.size?.y}) parent: ${parent.id} width: ${width}${padding}`
);
if (parent.size) {
const halfWidth = width / 2;
child.size.x = startingPosX + padding + halfWidth;
log.debug(
`abc91 layout blocks (calc) px, pyid:${
child.id
} startingPos=X${startingPosX} new startingPosX${
child.size.x
} ${halfWidth} padding=${padding} width=${width} halfWidth=${halfWidth} => x:${
child.size.x
} y:${child.size.y} ${child.widthInColumns} (width * (child?.w || 1)) / 2 ${
(width * (child?.widthInColumns || 1)) / 2
}`
);
startingPosX = child.size.x + halfWidth;
child.size.y =
parent.size.y - parent.size.height / 2 + py * (height + padding) + height / 2 + padding;
log.debug(
`abc88 layout blocks (calc) px, pyid:${
child.id
}startingPosX${startingPosX}${padding}${halfWidth}=>x:${child.size.x}y:${child.size.y}${
child.widthInColumns
}(width * (child?.w || 1)) / 2${(width * (child?.widthInColumns || 1)) / 2}`
);
}
// posY += height + padding;
if (child.children) {
layoutBlocks(child, db);
}
columnPos += child?.widthInColumns || 1;
log.debug('abc88 columnsPos', child, columnPos);
}
}
log.debug(
`layout blocks (<==layoutBlocks) ${block.id} x: ${block?.size?.x} y: ${block?.size?.y} width: ${block?.size?.width}`
);
}
function findBounds(
block: Block,
{ minX, minY, maxX, maxY } = { minX: 0, minY: 0, maxX: 0, maxY: 0 }
) {
if (block.size && block.id !== 'root') {
const { x, y, width, height } = block.size;
if (x - width / 2 < minX) {
minX = x - width / 2;
}
if (y - height / 2 < minY) {
minY = y - height / 2;
}
if (x + width / 2 > maxX) {
maxX = x + width / 2;
}
if (y + height / 2 > maxY) {
maxY = y + height / 2;
}
}
if (block.children) {
for (const child of block.children) {
({ minX, minY, maxX, maxY } = findBounds(child, { minX, minY, maxX, maxY }));
}
}
return { minX, minY, maxX, maxY };
}
export function layout(db: BlockDB) {
const root = db.getBlock('root');
if (!root) {
return;
}
setBlockSizes(root, db, 0, 0);
layoutBlocks(root, db);
// Position blocks relative to parents
// positionBlock(root, root, db);
log.debug('getBlocks', JSON.stringify(root, null, 2));
const { minX, minY, maxX, maxY } = findBounds(root);
const height = maxY - minY;
const width = maxX - minX;
return { x: minX, y: minY, width, height };
}

View File

@ -0,0 +1,290 @@
/** 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
%x acc_title
%x acc_descr
%x acc_descr_multiline
%x string
%x space
%x md_string
%x NODE
%x BLOCK_ARROW
%x ARROW_DIR
%x LLABEL
%x CLASS
%x CLASS_STYLE
%x CLASSDEF
%x CLASSDEFID
%x STYLE_STMNT
%x STYLE_DEFINITION
// as per section 6.1 of RFC 2234 [2]
COMMA \u002C
CR \u000D
LF \u000A
CRLF \u000D\u000A
%%
"block-beta" { return 'BLOCK_DIAGRAM_KEY'; }
"block"\s+ { yy.getLogger().debug('Found space-block'); return 'block';}
"block"\n+ { yy.getLogger().debug('Found nl-block'); return 'block';}
"block:" { yy.getLogger().debug('Found space-block'); return 'id-block';}
// \s*\%\%.* { yy.getLogger().debug('Found comment',yytext); }
[\s]+ { yy.getLogger().debug('.', yytext); /* skip all whitespace */ }
[\n]+ {yy.getLogger().debug('_', yytext); /* skip all whitespace */ }
// [\n] return 'NL';
<INITIAL>({CRLF}|{LF}) { return 'NL' }
"columns"\s+"auto" { yytext=-1; return 'COLUMNS'; }
"columns"\s+[\d]+ { yytext = yytext.replace(/columns\s+/,''); yy.getLogger().debug('COLUMNS (LEX)', yytext); return 'COLUMNS'; }
["][`] { this.pushState("md_string");}
<md_string>[^`"]+ { return "MD_STR";}
<md_string>[`]["] { this.popState();}
["] this.pushState("string");
<string>["] { yy.getLogger().debug('LEX: POPPING STR:', yytext);this.popState();}
<string>[^"]* { yy.getLogger().debug('LEX: STR end:', yytext); return "STR";}
space[:]\d+ { yytext = yytext.replace(/space\:/,'');yy.getLogger().debug('SPACE NUM (LEX)', yytext); return 'SPACE_BLOCK'; }
space { yytext = '1'; yy.getLogger().debug('COLUMNS (LEX)', yytext); return 'SPACE_BLOCK'; }
"default" return 'DEFAULT';
"linkStyle" return 'LINKSTYLE';
"interpolate" return 'INTERPOLATE';
"classDef"\s+ { this.pushState('CLASSDEF'); return 'classDef'; }
<CLASSDEF>DEFAULT\s+ { this.popState(); this.pushState('CLASSDEFID'); return 'DEFAULT_CLASSDEF_ID' }
<CLASSDEF>\w+\s+ { this.popState(); this.pushState('CLASSDEFID'); return 'CLASSDEF_ID' }
<CLASSDEFID>[^\n]* { this.popState(); return 'CLASSDEF_STYLEOPTS' }
"class"\s+ { this.pushState('CLASS'); return 'class'; }
<CLASS>(\w+)+((","\s*\w+)*) { this.popState(); this.pushState('CLASS_STYLE'); return 'CLASSENTITY_IDS' }
<CLASS_STYLE>[^\n]* { this.popState(); return 'STYLECLASS' }
"style"\s+ { this.pushState('STYLE_STMNT'); return 'style'; }
<STYLE_STMNT>(\w+)+((","\s*\w+)*) { this.popState(); this.pushState('STYLE_DEFINITION'); return 'STYLE_ENTITY_IDS' }
<STYLE_DEFINITION>[^\n]* { this.popState(); return 'STYLE_DEFINITION_DATA' }
accTitle\s*":"\s* { this.pushState("acc_title");return 'acc_title'; }
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
accDescr\s*":"\s* { this.pushState("acc_descr");return 'acc_descr'; }
<acc_descr>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; }
accDescr\s*"{"\s* { this.pushState("acc_descr_multiline");}
<acc_descr_multiline>[\}] { this.popState(); }
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
"end"\b\s* return 'end';
// Node end of shape
<NODE>"(((" { this.popState();yy.getLogger().debug('Lex: (('); return "NODE_DEND"; }
<NODE>")))" { this.popState();yy.getLogger().debug('Lex: (('); return "NODE_DEND"; }
<NODE>[\)]\) { this.popState();yy.getLogger().debug('Lex: ))'); return "NODE_DEND"; }
<NODE>"}}" { this.popState();yy.getLogger().debug('Lex: (('); return "NODE_DEND"; }
<NODE>"}" { this.popState();yy.getLogger().debug('Lex: (('); return "NODE_DEND"; }
<NODE>"(-" { this.popState();yy.getLogger().debug('Lex: (-'); return "NODE_DEND"; }
<NODE>"-)" { this.popState();yy.getLogger().debug('Lex: -)'); return "NODE_DEND"; }
<NODE>"((" { this.popState();yy.getLogger().debug('Lex: (('); return "NODE_DEND"; }
<NODE>"]]" { this.popState();yy.getLogger().debug('Lex: ]]'); return "NODE_DEND"; }
<NODE>"(" { this.popState();yy.getLogger().debug('Lex: ('); return "NODE_DEND"; }
<NODE>"])" { this.popState();yy.getLogger().debug('Lex: ])'); return "NODE_DEND"; }
<NODE>"\\]" { this.popState();yy.getLogger().debug('Lex: /]'); return "NODE_DEND"; }
<NODE>"/]" { this.popState();yy.getLogger().debug('Lex: /]'); return "NODE_DEND"; }
<NODE>")]" { this.popState();yy.getLogger().debug('Lex: )]'); return "NODE_DEND"; }
<NODE>[\)] { this.popState();yy.getLogger().debug('Lex: )'); return "NODE_DEND"; }
<NODE>\]\> { this.popState();yy.getLogger().debug('Lex: ]>'); return "NODE_DEND"; }
<NODE>[\]] { this.popState();yy.getLogger().debug('Lex: ]'); return "NODE_DEND"; }
// Start of nodes with shapes and description
"-)" { yy.getLogger().debug('Lexa: -)'); this.pushState('NODE');return 'NODE_DSTART'; }
"(-" { yy.getLogger().debug('Lexa: (-'); this.pushState('NODE');return 'NODE_DSTART'; }
"))" { yy.getLogger().debug('Lexa: ))'); this.pushState('NODE');return 'NODE_DSTART'; }
")" { yy.getLogger().debug('Lexa: )'); this.pushState('NODE');return 'NODE_DSTART'; }
"(((" { yy.getLogger().debug('Lex: ((('); this.pushState('NODE');return 'NODE_DSTART'; }
"((" { yy.getLogger().debug('Lexa: )'); this.pushState('NODE');return 'NODE_DSTART'; }
"{{" { yy.getLogger().debug('Lexa: )'); this.pushState('NODE');return 'NODE_DSTART'; }
"{" { yy.getLogger().debug('Lexa: )'); this.pushState('NODE');return 'NODE_DSTART'; }
">" { yy.getLogger().debug('Lexc: >'); this.pushState('NODE');return 'NODE_DSTART'; }
"([" { yy.getLogger().debug('Lexa: (['); this.pushState('NODE');return 'NODE_DSTART'; }
"(" { yy.getLogger().debug('Lexa: )'); this.pushState('NODE');return 'NODE_DSTART'; }
"[[" { this.pushState('NODE');return 'NODE_DSTART'; }
"[|" { this.pushState('NODE');return 'NODE_DSTART'; }
"[(" { this.pushState('NODE');return 'NODE_DSTART'; }
")))" { this.pushState('NODE');return 'NODE_DSTART'; }
"[\\" { this.pushState('NODE');return 'NODE_DSTART'; }
"[/" { this.pushState('NODE');return 'NODE_DSTART'; }
"[\\" { this.pushState('NODE');return 'NODE_DSTART'; }
"[" { yy.getLogger().debug('Lexa: ['); this.pushState('NODE');return 'NODE_DSTART'; }
"<[" { this.pushState('BLOCK_ARROW');yy.getLogger().debug('LEX ARR START');return 'BLOCK_ARROW_START'; }
[^\(\[\n\-\)\{\}\s\<\>:]+ { yy.getLogger().debug('Lex: NODE_ID', yytext);return 'NODE_ID'; }
<<EOF>> { yy.getLogger().debug('Lex: EOF', yytext);return 'EOF'; }
// Handling of strings in node
<BLOCK_ARROW>["][`] { this.pushState("md_string");}
<NODE>["][`] { this.pushState("md_string");}
<md_string>[^`"]+ { return "NODE_DESCR";}
<md_string>[`]["] { this.popState();}
<NODE>["] { yy.getLogger().debug('Lex: Starting string');this.pushState("string");}
<BLOCK_ARROW>["] { yy.getLogger().debug('LEX ARR: Starting string');this.pushState("string");}
<string>[^"]+ { yy.getLogger().debug('LEX: NODE_DESCR:', yytext); return "NODE_DESCR";}
<string>["] {yy.getLogger().debug('LEX POPPING');this.popState();}
<BLOCK_ARROW>"]>"\s*"(" { yy.getLogger().debug('Lex: =>BAE'); this.pushState('ARROW_DIR'); }
<ARROW_DIR>","?\s*right\s* { yytext = yytext.replace(/^,\s*/, ''); yy.getLogger().debug('Lex (right): dir:',yytext);return "DIR"; }
<ARROW_DIR>","?\s*left\s* { yytext = yytext.replace(/^,\s*/, ''); yy.getLogger().debug('Lex (left):',yytext);return "DIR"; }
<ARROW_DIR>","?\s*x\s* { yytext = yytext.replace(/^,\s*/, ''); yy.getLogger().debug('Lex (x):',yytext); return "DIR"; }
<ARROW_DIR>","?\s*y\s* { yytext = yytext.replace(/^,\s*/, ''); yy.getLogger().debug('Lex (y):',yytext); return "DIR"; }
<ARROW_DIR>","?\s*up\s* { yytext = yytext.replace(/^,\s*/, ''); yy.getLogger().debug('Lex (up):',yytext); return "DIR"; }
<ARROW_DIR>","?\s*down\s* { yytext = yytext.replace(/^,\s*/, ''); yy.getLogger().debug('Lex (down):',yytext); return "DIR"; }
<ARROW_DIR>")"\s* { yytext=']>';yy.getLogger().debug('Lex (ARROW_DIR end):',yytext);this.popState();this.popState();return "BLOCK_ARROW_END"; }
// Edges
\s*[xo<]?\-\-+[-xo>]\s* { yy.getLogger().debug('Lex: LINK', '#'+yytext+'#'); return 'LINK'; }
\s*[xo<]?\=\=+[=xo>]\s* { yy.getLogger().debug('Lex: LINK', yytext); return 'LINK'; }
\s*[xo<]?\-?\.+\-[xo>]?\s* { yy.getLogger().debug('Lex: LINK', yytext); return 'LINK'; }
\s*\~\~[\~]+\s* { yy.getLogger().debug('Lex: LINK', yytext); return 'LINK'; }
\s*[xo<]?\-\-\s* { yy.getLogger().debug('Lex: START_LINK', yytext);this.pushState("LLABEL");return 'START_LINK'; }
\s*[xo<]?\=\=\s* { yy.getLogger().debug('Lex: START_LINK', yytext);this.pushState("LLABEL");return 'START_LINK'; }
\s*[xo<]?\-\.\s* { yy.getLogger().debug('Lex: START_LINK', yytext);this.pushState("LLABEL");return 'START_LINK'; }
<LLABEL>["][`] { this.pushState("md_string");}
<LLABEL>["] { yy.getLogger().debug('Lex: Starting string');this.pushState("string"); return "LINK_LABEL";}
<LLABEL>\s*[xo<]?\-\-+[-xo>]\s* { this.popState(); yy.getLogger().debug('Lex: LINK', '#'+yytext+'#'); return 'LINK'; }
<LLABEL>\s*[xo<]?\=\=+[=xo>]\s* { this.popState(); yy.getLogger().debug('Lex: LINK', yytext); return 'LINK'; }
<LLABEL>\s*[xo<]?\-?\.+\-[xo>]?\s* { this.popState(); yy.getLogger().debug('Lex: LINK', yytext); return 'LINK'; }
':'\d+ { yy.getLogger().debug('Lex: COLON', yytext); yytext=yytext.slice(1);return 'SIZE'; }
/lex
%left '^'
%start start
%% // language grammar
spaceLines
: SPACELINE
| spaceLines SPACELINE
| spaceLines NL
;
seperator
: NL
{yy.getLogger().debug('Rule: seperator (NL) ');}
| SPACE
{yy.getLogger().debug('Rule: seperator (Space) ');}
| EOF
{yy.getLogger().debug('Rule: seperator (EOF) ');}
;
start: BLOCK_DIAGRAM_KEY document EOF
{ yy.getLogger().debug("Rule: hierarchy: ", $2); yy.setHierarchy($2); }
;
stop
: NL {yy.getLogger().debug('Stop NL ');}
| EOF {yy.getLogger().debug('Stop EOF ');}
// | SPACELINE
| stop NL {yy.getLogger().debug('Stop NL2 ');}
| stop EOF {yy.getLogger().debug('Stop EOF2 ');}
;
//array of statements
document
: statement { yy.getLogger().debug("Rule: statement: ", $1); typeof $1.length === 'number'?$$ = $1:$$ = [$1]; }
| statement document { yy.getLogger().debug("Rule: statement #2: ", $1); $$ = [$1].concat($2); }
;
link
: LINK
{ yy.getLogger().debug("Rule: link: ", $1, yytext); $$={edgeTypeStr: $1, label:''}; }
| START_LINK LINK_LABEL STR LINK
{ yy.getLogger().debug("Rule: LABEL link: ", $1, $3, $4); $$={edgeTypeStr: $4, label:$3}; }
;
statement
: nodeStatement
| columnsStatement
| SPACE_BLOCK
{ const num=parseInt($1); const spaceId = yy.generateId(); $$ = { id: spaceId, type:'space', label:'', width: num, children: [] }}
| blockStatement
| classDefStatement
| cssClassStatement
| styleStatement
;
nodeStatement
: nodeStatement link node {
yy.getLogger().debug('Rule: (nodeStatement link node) ', $1, $2, $3, ' typestr: ',$2.edgeTypeStr);
const edgeData = yy.edgeStrToEdgeData($2.edgeTypeStr)
$$ = [
{id: $1.id, label: $1.label, type:$1.type, directions: $1.directions},
{id: $1.id + '-' + $3.id, start: $1.id, end: $3.id, label: $2.label, type: 'edge', directions: $3.directions, arrowTypeEnd: edgeData, arrowTypeStart: 'arrow_open' },
{id: $3.id, label: $3.label, type: yy.typeStr2Type($3.typeStr), directions: $3.directions}
];
}
| node SIZE { yy.getLogger().debug('Rule: nodeStatement (abc88 node size) ', $1, $2); $$ = {id: $1.id, label: $1.label, type: yy.typeStr2Type($1.typeStr), directions: $1.directions, widthInColumns: parseInt($2,10)}; }
| node { yy.getLogger().debug('Rule: nodeStatement (node) ', $1); $$ = {id: $1.id, label: $1.label, type: yy.typeStr2Type($1.typeStr), directions: $1.directions, widthInColumns:1}; }
;
columnsStatement
: COLUMNS { yy.getLogger().debug('APA123', this? this:'na'); yy.getLogger().debug("COLUMNS: ", $1); $$ = {type: 'column-setting', columns: $1 === 'auto'?-1:parseInt($1) } }
;
blockStatement
: id-block nodeStatement document end { yy.getLogger().debug('Rule: id-block statement : ', $2, $3); const id2 = yy.generateId(); $$ = { ...$2, type:'composite', children: $3 }; }
| block document end { yy.getLogger().debug('Rule: blockStatement : ', $1, $2, $3); const id = yy.generateId(); $$ = { id, type:'composite', label:'', children: $2 }; }
;
node
: NODE_ID
{ yy.getLogger().debug("Rule: node (NODE_ID seperator): ", $1); $$ = { id: $1 }; }
| NODE_ID nodeShapeNLabel
{
yy.getLogger().debug("Rule: node (NODE_ID nodeShapeNLabel seperator): ", $1, $2);
$$ = { id: $1, label: $2.label, typeStr: $2.typeStr, directions: $2.directions };
}
;
dirList: DIR { yy.getLogger().debug("Rule: dirList: ", $1); $$ = [$1]; }
| DIR dirList { yy.getLogger().debug("Rule: dirList: ", $1, $2); $$ = [$1].concat($2); }
;
nodeShapeNLabel
: NODE_DSTART STR NODE_DEND
{ yy.getLogger().debug("Rule: nodeShapeNLabel: ", $1, $2, $3); $$ = { typeStr: $1 + $3, label: $2 }; }
| BLOCK_ARROW_START STR dirList BLOCK_ARROW_END
{ yy.getLogger().debug("Rule: BLOCK_ARROW nodeShapeNLabel: ", $1, $2, " #3:",$3, $4); $$ = { typeStr: $1 + $4, label: $2, directions: $3}; }
;
classDefStatement
: classDef CLASSDEF_ID CLASSDEF_STYLEOPTS {
$$ = { type: 'classDef', id: $2.trim(), css: $3.trim() };
}
| classDef DEFAULT CLASSDEF_STYLEOPTS {
$$ = { type: 'classDef', id: $2.trim(), css: $3.trim() };
}
;
cssClassStatement
: class CLASSENTITY_IDS STYLECLASS {
//log.debug('apply class: id(s): ',$2, ' style class: ', $3);
$$={ type: 'applyClass', id: $2.trim(), styleClass: $3.trim() };
}
;
styleStatement
: style STYLE_ENTITY_IDS STYLE_DEFINITION_DATA {
$$={ type: 'applyStyles', id: $2.trim(), stylesStr: $3.trim() };
}
;
%%

View File

@ -0,0 +1,409 @@
// @ts-ignore: jison doesn't export types
import block from './block.jison';
import db from '../blockDB.js';
import { cleanupComments } from '../../../diagram-api/comments.js';
import { prepareTextForParsing } from '../blockUtils.js';
import { setConfig } from '../../../config.js';
describe('Block diagram', function () {
describe('when parsing an block diagram graph it should handle > ', function () {
beforeEach(function () {
block.parser.yy = db;
block.parser.yy.clear();
block.parser.yy.getLogger = () => console;
});
it('a diagram with a node', async () => {
const str = `block-beta
id
`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(1);
expect(blocks[0].id).toBe('id');
expect(blocks[0].label).toBe('id');
});
it('a node with a square shape and a label', async () => {
const str = `block-beta
id["A label"]
`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(1);
expect(blocks[0].id).toBe('id');
expect(blocks[0].label).toBe('A label');
expect(blocks[0].type).toBe('square');
});
it('a diagram with multiple nodes', async () => {
const str = `block-beta
id1
id2
`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(2);
expect(blocks[0].id).toBe('id1');
expect(blocks[0].label).toBe('id1');
expect(blocks[0].type).toBe('na');
expect(blocks[1].id).toBe('id2');
expect(blocks[1].label).toBe('id2');
expect(blocks[1].type).toBe('na');
});
it('a diagram with multiple nodes', async () => {
const str = `block-beta
id1
id2
id3
`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(3);
expect(blocks[0].id).toBe('id1');
expect(blocks[0].label).toBe('id1');
expect(blocks[0].type).toBe('na');
expect(blocks[1].id).toBe('id2');
expect(blocks[1].label).toBe('id2');
expect(blocks[1].type).toBe('na');
expect(blocks[2].id).toBe('id3');
expect(blocks[2].label).toBe('id3');
expect(blocks[2].type).toBe('na');
});
it('a node with a square shape and a label', async () => {
const str = `block-beta
id["A label"]
id2`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(2);
expect(blocks[0].id).toBe('id');
expect(blocks[0].label).toBe('A label');
expect(blocks[0].type).toBe('square');
expect(blocks[1].id).toBe('id2');
expect(blocks[1].label).toBe('id2');
expect(blocks[1].type).toBe('na');
});
it('a diagram with multiple nodes with edges abc123', async () => {
const str = `block-beta
id1["first"] --> id2["second"]
`;
block.parse(str);
const blocks = db.getBlocks();
const edges = db.getEdges();
expect(blocks.length).toBe(2);
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('id1');
expect(edges[0].end).toBe('id2');
expect(edges[0].arrowTypeEnd).toBe('arrow_point');
});
it('a diagram with multiple nodes with edges abc123', async () => {
const str = `block-beta
id1["first"] -- "a label" --> id2["second"]
`;
block.parse(str);
const blocks = db.getBlocks();
const edges = db.getEdges();
expect(blocks.length).toBe(2);
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('id1');
expect(edges[0].end).toBe('id2');
expect(edges[0].arrowTypeEnd).toBe('arrow_point');
expect(edges[0].label).toBe('a label');
});
it('a diagram with column statements', async () => {
const str = `block-beta
columns 2
block1["Block 1"]
`;
block.parse(str);
expect(db.getColumns('root')).toBe(2);
const blocks = db.getBlocks();
expect(blocks.length).toBe(1);
});
it('a diagram withput column statements', async () => {
const str = `block-beta
block1["Block 1"]
`;
block.parse(str);
expect(db.getColumns('root')).toBe(-1);
const blocks = db.getBlocks();
expect(blocks.length).toBe(1);
});
it('a diagram with auto column statements', async () => {
const str = `block-beta
columns auto
block1["Block 1"]
`;
block.parse(str);
expect(db.getColumns('root')).toBe(-1);
const blocks = db.getBlocks();
expect(blocks.length).toBe(1);
});
it('blocks next to each other', async () => {
const str = `block-beta
columns 2
block1["Block 1"]
block2["Block 2"]
`;
block.parse(str);
const blocks = db.getBlocks();
expect(db.getColumns('root')).toBe(2);
expect(blocks.length).toBe(2);
});
it('blocks on top of each other', async () => {
const str = `block-beta
columns 1
block1["Block 1"]
block2["Block 2"]
`;
block.parse(str);
const blocks = db.getBlocks();
expect(db.getColumns('root')).toBe(1);
expect(blocks.length).toBe(2);
});
it('compound blocks 2', async () => {
const str = `block-beta
block
aBlock["ABlock"]
bBlock["BBlock"]
end
`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(1);
expect(blocks[0].children.length).toBe(2);
expect(blocks[0].id).not.toBe(undefined);
expect(blocks[0].label).toBe('');
expect(blocks[0].type).toBe('composite');
const aBlock = blocks[0].children[0];
expect(aBlock.id).not.toBe(aBlock);
expect(aBlock.label).toBe('ABlock');
expect(aBlock.type).toBe('square');
const bBlock = blocks[0].children[1];
expect(bBlock.id).not.toBe(bBlock);
expect(bBlock.label).toBe('BBlock');
expect(bBlock.type).toBe('square');
});
it('compound blocks of compound blocks', async () => {
const str = `block-beta
block
aBlock["ABlock"]
block
bBlock["BBlock"]
end
end
`;
block.parse(str);
const blocks = db.getBlocks();
const aBlock = blocks[0].children[0];
const secondComposite = blocks[0].children[1];
const bBlock = blocks[0].children[1].children[0];
expect(blocks[0].children.length).toBe(2);
expect(blocks[0].id).not.toBe(undefined);
expect(blocks[0].label).toBe('');
expect(blocks[0].type).toBe('composite');
expect(secondComposite.children.length).toBe(1);
expect(secondComposite.id).not.toBe(undefined);
expect(secondComposite.label).toBe('');
expect(secondComposite.type).toBe('composite');
expect(aBlock.id).not.toBe(aBlock);
expect(aBlock.label).toBe('ABlock');
expect(aBlock.type).toBe('square');
expect(bBlock.id).not.toBe(bBlock);
expect(bBlock.label).toBe('BBlock');
expect(bBlock.type).toBe('square');
});
it('compound blocks with title', async () => {
const str = `block-beta
block:compoundBlock["Compound block"]
columns 1
block2["Block 2"]
end
`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(1);
const compoundBlock = blocks[0];
const block2 = compoundBlock.children[0];
expect(compoundBlock.children.length).toBe(1);
expect(compoundBlock.id).toBe('compoundBlock');
expect(compoundBlock.label).toBe('Compound block');
expect(compoundBlock.type).toBe('composite');
expect(block2.id).toBe('block2');
expect(block2.label).toBe('Block 2');
expect(block2.type).toBe('square');
});
it('blocks mixed with compound blocks', async () => {
const str = `block-beta
columns 1
block1["Block 1"]
block
columns 2
block2["Block 2"]
block3["Block 3"]
end
`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(2);
const compoundBlock = blocks[1];
const block2 = compoundBlock.children[0];
expect(compoundBlock.children.length).toBe(2);
expect(block2.id).toBe('block2');
expect(block2.label).toBe('Block 2');
expect(block2.type).toBe('square');
});
it('Arrow blocks', async () => {
const str = `block-beta
columns 3
block1["Block 1"]
blockArrow<["&nbsp;&nbsp;&nbsp;"]>(right)
block2["Block 2"]`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(3);
const block1 = blocks[0];
const blockArrow = blocks[1];
const block2 = blocks[2];
expect(block1.id).toBe('block1');
expect(blockArrow.id).toBe('blockArrow');
expect(block2.id).toBe('block2');
expect(block2.label).toBe('Block 2');
expect(block2.type).toBe('square');
expect(blockArrow.type).toBe('block_arrow');
expect(blockArrow.directions).toContain('right');
});
it('Arrow blocks with multiple points', async () => {
const str = `block-beta
columns 1
A
blockArrow<["&nbsp;&nbsp;&nbsp;"]>(up, down)
block
columns 3
B
C
D
end`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(3);
const blockArrow = blocks[1];
expect(blockArrow.type).toBe('block_arrow');
expect(blockArrow.directions).toContain('up');
expect(blockArrow.directions).toContain('down');
expect(blockArrow.directions).not.toContain('right');
});
it('blocks with different widths', async () => {
const str = `block-beta
columns 3
one["One Slot"]
two["Two slots"]:2
`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(2);
const one = blocks[0];
const two = blocks[1];
expect(two.widthInColumns).toBe(2);
});
it('empty blocks', async () => {
const str = `block-beta
columns 3
space
middle["In the middle"]
space
`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(3);
const sp1 = blocks[0];
const middle = blocks[1];
const sp2 = blocks[2];
expect(sp1.type).toBe('space');
expect(sp2.type).toBe('space');
expect(middle.label).toBe('In the middle');
});
it('classDef statements applied to a block', async () => {
const str = `block-beta
classDef black color:#ffffff, fill:#000000;
mc["Memcache"]
class mc black
`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(1);
const mc = blocks[0];
expect(mc.classes).toContain('black');
const classes = db.getClasses();
const black = classes.black;
expect(black.id).toBe('black');
expect(black.styles[0]).toEqual('color:#ffffff');
});
it('style statements applied to a block', async () => {
const str = `block-beta
columns 1
B["A wide one in the middle"]
style B fill:#f9F,stroke:#333,stroke-width:4px
`;
block.parse(str);
const blocks = db.getBlocks();
expect(blocks.length).toBe(1);
const B = blocks[0];
expect(B.styles).toContain('fill:#f9F');
});
});
});

View File

@ -0,0 +1,261 @@
import { getStylesFromArray } from '../../utils.js';
import { insertNode, positionNode } from '../../dagre-wrapper/nodes.js';
import { insertEdge, insertEdgeLabel, positionEdgeLabel } from '../../dagre-wrapper/edges.js';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { getConfig } from '../../config.js';
import type { ContainerElement } from 'd3';
import type { Block } from './blockTypes.js';
import type { BlockDB } from './blockDB.js';
interface Node {
classes: string;
}
function getNodeFromBlock(block: Block, db: BlockDB, positioned = false) {
const vertex = block;
let classStr = 'default';
if ((vertex?.classes?.length || 0) > 0) {
classStr = (vertex?.classes || []).join(' ');
}
classStr = classStr + ' flowchart-label';
// We create a SVG label, either by delegating to addHtmlLabel or manually
let radius = 0;
let shape = '';
let layoutOptions = {};
let padding;
// Set the shape based parameters
switch (vertex.type) {
case 'round':
radius = 5;
shape = 'rect';
break;
case 'composite':
radius = 0;
shape = 'composite';
padding = 0;
break;
case 'square':
shape = 'rect';
break;
case 'diamond':
shape = 'question';
layoutOptions = {
portConstraints: 'FIXED_SIDE',
};
break;
case 'hexagon':
shape = 'hexagon';
break;
case 'block_arrow':
shape = 'block_arrow';
break;
case 'odd':
shape = 'rect_left_inv_arrow';
break;
case 'lean_right':
shape = 'lean_right';
break;
case 'lean_left':
shape = 'lean_left';
break;
case 'trapezoid':
shape = 'trapezoid';
break;
case 'inv_trapezoid':
shape = 'inv_trapezoid';
break;
case 'rect_left_inv_arrow':
shape = 'rect_left_inv_arrow';
break;
case 'circle':
shape = 'circle';
break;
case 'ellipse':
shape = 'ellipse';
break;
case 'stadium':
shape = 'stadium';
break;
case 'subroutine':
shape = 'subroutine';
break;
case 'cylinder':
shape = 'cylinder';
break;
case 'group':
shape = 'rect';
break;
case 'doublecircle':
shape = 'doublecircle';
break;
default:
shape = 'rect';
}
const styles = getStylesFromArray(vertex?.styles || []);
// Use vertex id as text in the box if no text is provided by the graph definition
const vertexText = vertex.label;
const bounds = vertex.size || { width: 0, height: 0, x: 0, y: 0 };
// Add the node
const node = {
labelStyle: styles.labelStyle,
shape: shape,
labelText: vertexText,
rx: radius,
ry: radius,
class: classStr,
style: styles.style,
id: vertex.id,
directions: vertex.directions,
width: bounds.width,
height: bounds.height,
x: bounds.x,
y: bounds.y,
positioned,
intersect: undefined,
type: vertex.type,
padding: padding ?? (getConfig()?.block?.padding || 0),
};
return node;
}
async function calculateBlockSize(
elem: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
block: any,
db: any
) {
const node = getNodeFromBlock(block, db, false);
if (node.type === 'group') {
return;
}
// Add the element to the DOM to size it
const nodeEl = await insertNode(elem, node);
const boundingBox = nodeEl.node().getBBox();
const obj = db.getBlock(node.id);
obj.size = { width: boundingBox.width, height: boundingBox.height, x: 0, y: 0, node: nodeEl };
db.setBlock(obj);
nodeEl.remove();
}
type ActionFun = typeof calculateBlockSize;
export async function insertBlockPositioned(elem: any, block: Block, db: any) {
const node = getNodeFromBlock(block, db, true);
// Add the element to the DOM to size it
const obj = db.getBlock(node.id);
if (obj.type !== 'space') {
const nodeEl = await insertNode(elem, node);
block.intersect = node?.intersect;
positionNode(node);
}
}
export async function performOperations(
elem: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
blocks: Block[],
db: BlockDB,
operation: ActionFun
) {
for (const block of blocks) {
await operation(elem, block, db);
if (block.children) {
await performOperations(elem, block.children, db, operation);
}
}
}
export async function calculateBlockSizes(elem: any, blocks: Block[], db: BlockDB) {
await performOperations(elem, blocks, db, calculateBlockSize);
}
export async function insertBlocks(
elem: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
blocks: Block[],
db: BlockDB
) {
await performOperations(elem, blocks, db, insertBlockPositioned);
}
export async function insertEdges(
elem: any,
edges: Block[],
blocks: Block[],
db: BlockDB,
id: string
) {
const g = new graphlib.Graph({
multigraph: true,
compound: true,
});
g.setGraph({
rankdir: 'TB',
nodesep: 10,
ranksep: 10,
marginx: 8,
marginy: 8,
});
for (const block of blocks) {
if (block.size) {
g.setNode(block.id, {
width: block.size.width,
height: block.size.height,
intersect: block.intersect,
});
}
}
for (const edge of edges) {
// elem, e, edge, clusterDb, diagramType, graph;
if (edge.start && edge.end) {
const startBlock = db.getBlock(edge.start);
const endBlock = db.getBlock(edge.end);
if (startBlock?.size && endBlock?.size) {
const start = startBlock.size;
const end = endBlock.size;
const points = [
{ x: start.x, y: start.y },
{ x: start.x + (end.x - start.x) / 2, y: start.y + (end.y - start.y) / 2 },
{ x: end.x, y: end.y },
];
// edge.points = points;
await insertEdge(
elem,
{ v: edge.start, w: edge.end, name: edge.id },
{
...edge,
arrowTypeEnd: edge.arrowTypeEnd,
arrowTypeStart: edge.arrowTypeStart,
points,
classes: 'edge-thickness-normal edge-pattern-solid flowchart-link LS-a1 LE-b1',
},
undefined,
'block',
g,
id
);
if (edge.label) {
await insertEdgeLabel(elem, {
...edge,
label: edge.label,
labelStyle: 'stroke: #333; stroke-width: 1.5px;fill:none;',
arrowTypeEnd: edge.arrowTypeEnd,
arrowTypeStart: edge.arrowTypeStart,
points,
classes: 'edge-thickness-normal edge-pattern-solid flowchart-link LS-a1 LE-b1',
});
await positionEdgeLabel(
{ ...edge, x: points[1].x, y: points[1].y },
{
originalPath: points,
}
);
}
}
}
}
}

View File

@ -0,0 +1,147 @@
import * as khroma from 'khroma';
/** Returns the styles given options */
export interface BlockChartStyleOptions {
arrowheadColor: string;
border2: string;
clusterBkg: string;
clusterBorder: string;
edgeLabelBackground: string;
fontFamily: string;
lineColor: string;
mainBkg: string;
nodeBorder: string;
nodeTextColor: string;
tertiaryColor: string;
textColor: string;
titleColor: string;
}
const fade = (color: string, opacity: number) => {
// @ts-ignore TODO: incorrect types from khroma
const channel = khroma.channel;
const r = channel(color, 'r');
const g = channel(color, 'g');
const b = channel(color, 'b');
// @ts-ignore incorrect types from khroma
return khroma.rgba(r, g, b, opacity);
};
const getStyles = (options: BlockChartStyleOptions) =>
`.label {
font-family: ${options.fontFamily};
color: ${options.nodeTextColor || options.textColor};
}
.cluster-label text {
fill: ${options.titleColor};
}
.cluster-label span,p {
color: ${options.titleColor};
}
.label text,span,p {
fill: ${options.nodeTextColor || options.textColor};
color: ${options.nodeTextColor || options.textColor};
}
.node rect,
.node circle,
.node ellipse,
.node polygon,
.node path {
fill: ${options.mainBkg};
stroke: ${options.nodeBorder};
stroke-width: 1px;
}
.flowchart-label text {
text-anchor: middle;
}
// .flowchart-label .text-outer-tspan {
// text-anchor: middle;
// }
// .flowchart-label .text-inner-tspan {
// text-anchor: start;
// }
.node .label {
text-align: center;
}
.node.clickable {
cursor: pointer;
}
.arrowheadPath {
fill: ${options.arrowheadColor};
}
.edgePath .path {
stroke: ${options.lineColor};
stroke-width: 2.0px;
}
.flowchart-link {
stroke: ${options.lineColor};
fill: none;
}
.edgeLabel {
background-color: ${options.edgeLabelBackground};
rect {
opacity: 0.5;
background-color: ${options.edgeLabelBackground};
fill: ${options.edgeLabelBackground};
}
text-align: center;
}
/* For html labels only */
.labelBkg {
background-color: ${fade(options.edgeLabelBackground, 0.5)};
// background-color:
}
.node .cluster {
// fill: ${fade(options.mainBkg, 0.5)};
fill: ${fade(options.clusterBkg, 0.5)};
stroke: ${fade(options.clusterBorder, 0.2)};
box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
stroke-width: 1px;
}
.cluster text {
fill: ${options.titleColor};
}
.cluster span,p {
color: ${options.titleColor};
}
/* .cluster div {
color: ${options.titleColor};
} */
div.mermaidTooltip {
position: absolute;
text-align: center;
max-width: 200px;
padding: 2px;
font-family: ${options.fontFamily};
font-size: 12px;
background: ${options.tertiaryColor};
border: 1px solid ${options.border2};
border-radius: 2px;
pointer-events: none;
z-index: 100;
}
.flowchartTitleText {
text-anchor: middle;
font-size: 18px;
fill: ${options.textColor};
}
`;
export default getStyles;

View File

@ -18,13 +18,18 @@ export const getRows = (s?: string): string[] => {
return str.split('#br#');
};
/**
* Removes script tags from a text
*
* @param txt - The text to sanitize
* @returns The safer text
*/
export const removeScript = (txt: string): string => {
const setupDompurifyHooksIfNotSetup = (() => {
let setup = false;
return () => {
if (!setup) {
setupDompurifyHooks();
setup = true;
}
};
})();
function setupDompurifyHooks() {
const TEMPORARY_ATTRIBUTE = 'data-temp-href-target';
DOMPurify.addHook('beforeSanitizeAttributes', (node: Element) => {
@ -33,8 +38,6 @@ export const removeScript = (txt: string): string => {
}
});
const sanitizedText = DOMPurify.sanitize(txt);
DOMPurify.addHook('afterSanitizeAttributes', (node: Element) => {
if (node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) {
node.setAttribute('target', node.getAttribute(TEMPORARY_ATTRIBUTE) || '');
@ -44,6 +47,18 @@ export const removeScript = (txt: string): string => {
}
}
});
}
/**
* Removes script tags from a text
*
* @param txt - The text to sanitize
* @returns The safer text
*/
export const removeScript = (txt: string): string => {
setupDompurifyHooksIfNotSetup();
const sanitizedText = DOMPurify.sanitize(txt);
return sanitizedText;
};

View File

@ -11,7 +11,7 @@ import { configureSvgSize } from '../../setupGraphViewbox.js';
* @param version - The version
*/
export const draw = (_text: string, id: string, version: string) => {
log.debug('renering svg for syntax error\n');
log.debug('rendering svg for syntax error\n');
const svg: SVG = selectSvgElement(id);
svg.attr('viewBox', '0 0 2412 512');

View File

@ -349,6 +349,8 @@ export const getClasses = function (text, diagObj) {
*
* @param text
* @param id
* @param _version
* @param diagObj
*/
export const draw = async function (text, id, _version, diagObj) {

View File

@ -1,7 +1,7 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { setConfig } from '../../../config.js';
import { cleanupComments } from '../../../diagram-api/comments.js';
import { setConfig } from '../../../config.js';
setConfig({
securityLevel: 'strict',

View File

@ -27,11 +27,10 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
\%\%(?!\{)*[^\n]* /* skip comments */
[^\}]\%\%*[^\n]* /* skip comments */
\%\%*[^\n]*[\n]* /* do nothing */
\%\%*[^\n]*[\n]* /* do nothing */
[\n]+ return 'NL';
\s+ /* skip whitespace */
\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
/*
@ -86,10 +85,10 @@ weekday\s+friday return 'weekday_friday'
weekday\s+saturday return 'weekday_saturday'
weekday\s+sunday return 'weekday_sunday'
\d\d\d\d"-"\d\d"-"\d\d return 'date';
"title"\s[^#\n;]+ return 'title';
"title"\s[^\n]+ return 'title';
"accDescription"\s[^#\n;]+ return 'accDescription'
"section"\s[^#:\n;]+ return 'section';
[^#:\n;]+ return 'taskTxt';
"section"\s[^\n]+ return 'section';
[^:\n]+ return 'taskTxt';
":"[^#\n;]+ return 'taskData';
":" return ':';
<<EOF>> return 'EOF';

View File

@ -28,8 +28,12 @@ describe('when parsing a gantt diagram it', function () {
});
it('should handle a title definition', function () {
const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid';
const semi = 'gantt\ndateFormat yyyy-mm-dd\ntitle ;Gantt diagram titles support semicolons';
const hash = 'gantt\ndateFormat yyyy-mm-dd\ntitle #Gantt diagram titles support hashtags';
expect(parserFnConstructor(str)).not.toThrow();
expect(parserFnConstructor(semi)).not.toThrow();
expect(parserFnConstructor(hash)).not.toThrow();
});
it('should handle an excludes definition', function () {
const str =
@ -53,7 +57,23 @@ describe('when parsing a gantt diagram it', function () {
'excludes weekdays 2019-02-01\n' +
'section Documentation';
const semi =
'gantt\n' +
'dateFormat yyyy-mm-dd\n' +
'title Adding gantt diagram functionality to mermaid\n' +
'excludes weekdays 2019-02-01\n' +
'section ;Documentation';
const hash =
'gantt\n' +
'dateFormat yyyy-mm-dd\n' +
'title Adding gantt diagram functionality to mermaid\n' +
'excludes weekdays 2019-02-01\n' +
'section #Documentation';
expect(parserFnConstructor(str)).not.toThrow();
expect(parserFnConstructor(semi)).not.toThrow();
expect(parserFnConstructor(hash)).not.toThrow();
});
it('should handle multiline section titles with different line breaks', function () {
const str =
@ -73,7 +93,23 @@ describe('when parsing a gantt diagram it', function () {
'section Documentation\n' +
'Design jison grammar:des1, 2014-01-01, 2014-01-04';
const semi =
'gantt\n' +
'dateFormat YYYY-MM-DD\n' +
'title Adding gantt diagram functionality to mermaid\n' +
'section Documentation\n' +
';Design jison grammar:des1, 2014-01-01, 2014-01-04';
const hash =
'gantt\n' +
'dateFormat YYYY-MM-DD\n' +
'title Adding gantt diagram functionality to mermaid\n' +
'section Documentation\n' +
'#Design jison grammar:des1, 2014-01-01, 2014-01-04';
expect(parserFnConstructor(str)).not.toThrow();
expect(parserFnConstructor(semi)).not.toThrow();
expect(parserFnConstructor(hash)).not.toThrow();
const tasks = parser.yy.getTasks();

View File

@ -1,12 +1,13 @@
// @ts-ignore: JISON doesn't support types
import mindmapParser from './parser/mindmap.jison';
import * as mindmapDb from './mindmapDb.js';
import mindmapRenderer from './mindmapRenderer.js';
import mindmapStyles from './styles.js';
import parser from './parser/mindmap.jison';
import db from './mindmapDb.js';
import renderer from './mindmapRenderer.js';
import styles from './styles.js';
import type { DiagramDefinition } from '../../diagram-api/types.js';
export const diagram = {
db: mindmapDb,
renderer: mindmapRenderer,
parser: mindmapParser,
styles: mindmapStyles,
export const diagram: DiagramDefinition = {
db,
renderer,
parser,
styles,
};

View File

@ -1,5 +1,6 @@
// @ts-expect-error No types available for JISON
import { parser as mindmap } from './parser/mindmap.jison';
import * as mindmapDB from './mindmapDb.js';
import mindmapDB from './mindmapDb.js';
// Todo fix utils functions for tests
import { setLogLevel } from '../../diagram-api/diagramAPI.js';
@ -11,7 +12,7 @@ describe('when parsing a mindmap ', function () {
});
describe('hiearchy', function () {
it('MMP-1 should handle a simple root definition abc122', function () {
let str = `mindmap
const str = `mindmap
root`;
mindmap.parse(str);
@ -19,7 +20,7 @@ describe('when parsing a mindmap ', function () {
expect(mindmap.yy.getMindmap().descr).toEqual('root');
});
it('MMP-2 should handle a hierachial mindmap definition', function () {
let str = `mindmap
const str = `mindmap
root
child1
child2
@ -34,7 +35,7 @@ describe('when parsing a mindmap ', function () {
});
it('3 should handle a simple root definition with a shape and without an id abc123', function () {
let str = `mindmap
const str = `mindmap
(root)`;
mindmap.parse(str);
@ -43,7 +44,7 @@ describe('when parsing a mindmap ', function () {
});
it('MMP-4 should handle a deeper hierachial mindmap definition', function () {
let str = `mindmap
const str = `mindmap
root
child1
leaf1
@ -58,40 +59,27 @@ describe('when parsing a mindmap ', function () {
expect(mm.children[1].descr).toEqual('child2');
});
it('5 Multiple roots are illegal', function () {
let str = `mindmap
const str = `mindmap
root
fakeRoot`;
try {
mindmap.parse(str);
// Fail test if above expression doesn't throw anything.
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe(
'There can be only one root. No parent could be found for ("fakeRoot")'
);
}
expect(() => mindmap.parse(str)).toThrow(
'There can be only one root. No parent could be found for ("fakeRoot")'
);
});
it('MMP-6 real root in wrong place', function () {
let str = `mindmap
const str = `mindmap
root
fakeRoot
realRootWrongPlace`;
try {
mindmap.parse(str);
// Fail test if above expression doesn't throw anything.
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe(
'There can be only one root. No parent could be found for ("fakeRoot")'
);
}
expect(() => mindmap.parse(str)).toThrow(
'There can be only one root. No parent could be found for ("fakeRoot")'
);
});
});
describe('nodes', function () {
it('MMP-7 should handle an id and type for a node definition', function () {
let str = `mindmap
const str = `mindmap
root[The root]
`;
@ -102,7 +90,7 @@ describe('when parsing a mindmap ', function () {
expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
});
it('MMP-8 should handle an id and type for a node definition', function () {
let str = `mindmap
const str = `mindmap
root
theId(child1)`;
@ -116,7 +104,7 @@ describe('when parsing a mindmap ', function () {
expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT);
});
it('MMP-9 should handle an id and type for a node definition', function () {
let str = `mindmap
const str = `mindmap
root
theId(child1)`;
@ -130,7 +118,7 @@ root
expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT);
});
it('MMP-10 multiple types (circle)', function () {
let str = `mindmap
const str = `mindmap
root((the root))
`;
@ -142,7 +130,7 @@ root
});
it('MMP-11 multiple types (cloud)', function () {
let str = `mindmap
const str = `mindmap
root)the root(
`;
@ -153,7 +141,7 @@ root
expect(mm.type).toEqual(mindmap.yy.nodeType.CLOUD);
});
it('MMP-12 multiple types (bang)', function () {
let str = `mindmap
const str = `mindmap
root))the root((
`;
@ -165,7 +153,7 @@ root
});
it('MMP-12-a multiple types (hexagon)', function () {
let str = `mindmap
const str = `mindmap
root{{the root}}
`;
@ -178,7 +166,7 @@ root
});
describe('decorations', function () {
it('MMP-13 should be possible to set an icon for the node', function () {
let str = `mindmap
const str = `mindmap
root[The root]
::icon(bomb)
`;
@ -192,7 +180,7 @@ root
expect(mm.icon).toEqual('bomb');
});
it('MMP-14 should be possible to set classes for the node', function () {
let str = `mindmap
const str = `mindmap
root[The root]
:::m-4 p-8
`;
@ -206,7 +194,7 @@ root
expect(mm.class).toEqual('m-4 p-8');
});
it('MMP-15 should be possible to set both classes and icon for the node', function () {
let str = `mindmap
const str = `mindmap
root[The root]
:::m-4 p-8
::icon(bomb)
@ -222,7 +210,7 @@ root
expect(mm.icon).toEqual('bomb');
});
it('MMP-16 should be possible to set both classes and icon for the node', function () {
let str = `mindmap
const str = `mindmap
root[The root]
::icon(bomb)
:::m-4 p-8
@ -240,7 +228,7 @@ root
});
describe('descriptions', function () {
it('MMP-17 should be possible to use node syntax in the descriptions', function () {
let str = `mindmap
const str = `mindmap
root["String containing []"]
`;
mindmap.parse(str);
@ -249,7 +237,7 @@ root
expect(mm.descr).toEqual('String containing []');
});
it('MMP-18 should be possible to use node syntax in the descriptions in children', function () {
let str = `mindmap
const str = `mindmap
root["String containing []"]
child1["String containing ()"]
`;
@ -261,7 +249,7 @@ root
expect(mm.children[0].descr).toEqual('String containing ()');
});
it('MMP-19 should be possible to have a child after a class assignment', function () {
let str = `mindmap
const str = `mindmap
root(Root)
Child(Child)
:::hot
@ -281,7 +269,7 @@ root
});
});
it('MMP-20 should be possible to have meaningless empty rows in a mindmap abc124', function () {
let str = `mindmap
const str = `mindmap
root(Root)
Child(Child)
a(a)
@ -300,7 +288,7 @@ root
expect(child.children[1].nodeId).toEqual('b');
});
it('MMP-21 should be possible to have comments in a mindmap', function () {
let str = `mindmap
const str = `mindmap
root(Root)
Child(Child)
a(a)
@ -321,7 +309,7 @@ root
});
it('MMP-22 should be possible to have comments at the end of a line', function () {
let str = `mindmap
const str = `mindmap
root(Root)
Child(Child)
a(a) %% This is a comment
@ -339,7 +327,7 @@ root
expect(child.children[1].nodeId).toEqual('b');
});
it('MMP-23 Rows with only spaces should not interfere', function () {
let str = 'mindmap\nroot\n A\n \n\n B';
const str = 'mindmap\nroot\n A\n \n\n B';
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
@ -351,7 +339,7 @@ root
expect(child2.nodeId).toEqual('B');
});
it('MMP-24 Handle rows above the mindmap declarations', function () {
let str = '\n \nmindmap\nroot\n A\n \n\n B';
const str = '\n \nmindmap\nroot\n A\n \n\n B';
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
@ -363,7 +351,7 @@ root
expect(child2.nodeId).toEqual('B');
});
it('MMP-25 Handle rows above the mindmap declarations, no space', function () {
let str = '\n\n\nmindmap\nroot\n A\n \n\n B';
const str = '\n\n\nmindmap\nroot\n A\n \n\n B';
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');

View File

@ -1,19 +1,21 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { sanitizeText as _sanitizeText } from '../../diagrams/common/common.js';
import type { D3Element } from '../../mermaidAPI.js';
import { sanitizeText } from '../../diagrams/common/common.js';
import { log } from '../../logger.js';
import type { MindmapNode } from './mindmapTypes.js';
import defaultConfig from '../../defaultConfig.js';
export const sanitizeText = (text) => _sanitizeText(text, getConfig());
let nodes = [];
let nodes: MindmapNode[] = [];
let cnt = 0;
let elements = {};
export const clear = () => {
let elements: Record<number, D3Element> = {};
const clear = () => {
nodes = [];
cnt = 0;
elements = {};
};
const getParent = function (level) {
const getParent = function (level: number) {
for (let i = nodes.length - 1; i >= 0; i--) {
if (nodes[i].level < level) {
return nodes[i];
@ -23,34 +25,32 @@ const getParent = function (level) {
return null;
};
export const getMindmap = () => {
const getMindmap = () => {
return nodes.length > 0 ? nodes[0] : null;
};
export const addNode = (level, id, descr, type) => {
const addNode = (level: number, id: string, descr: string, type: number) => {
log.info('addNode', level, id, descr, type);
const conf = getConfig();
let padding: number = conf.mindmap?.padding ?? defaultConfig.mindmap.padding;
switch (type) {
case nodeType.ROUNDED_RECT:
case nodeType.RECT:
case nodeType.HEXAGON:
padding *= 2;
}
const node = {
id: cnt++,
nodeId: sanitizeText(id),
nodeId: sanitizeText(id, conf),
level,
descr: sanitizeText(descr),
descr: sanitizeText(descr, conf),
type,
children: [],
width: getConfig().mindmap.maxNodeWidth,
};
switch (node.type) {
case nodeType.ROUNDED_RECT:
node.padding = 2 * conf.mindmap.padding;
break;
case nodeType.RECT:
node.padding = 2 * conf.mindmap.padding;
break;
case nodeType.HEXAGON:
node.padding = 2 * conf.mindmap.padding;
break;
default:
node.padding = conf.mindmap.padding;
}
width: conf.mindmap?.maxNodeWidth ?? defaultConfig.mindmap.maxNodeWidth,
padding,
} satisfies MindmapNode;
const parent = getParent(level);
if (parent) {
parent.children.push(node);
@ -62,22 +62,14 @@ export const addNode = (level, id, descr, type) => {
nodes.push(node);
} else {
// Syntax error ... there can only bee one root
let error = new Error(
throw new Error(
'There can be only one root. No parent could be found for ("' + node.descr + '")'
);
error.hash = {
text: 'branch ' + name,
token: 'branch ' + name,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['"checkout ' + name + '"'],
};
throw error;
}
}
};
export const nodeType = {
const nodeType = {
DEFAULT: 0,
NO_BORDER: 0,
ROUNDED_RECT: 1,
@ -88,7 +80,7 @@ export const nodeType = {
HEXAGON: 6,
};
export const getType = (startStr, endStr) => {
const getType = (startStr: string, endStr: string): number => {
log.debug('In get type', startStr, endStr);
switch (startStr) {
case '[':
@ -108,21 +100,25 @@ export const getType = (startStr, endStr) => {
}
};
export const setElementForId = (id, element) => {
const setElementForId = (id: number, element: D3Element) => {
elements[id] = element;
};
export const decorateNode = (decoration) => {
const node = nodes[nodes.length - 1];
if (decoration && decoration.icon) {
node.icon = sanitizeText(decoration.icon);
const decorateNode = (decoration?: { class?: string; icon?: string }) => {
if (!decoration) {
return;
}
if (decoration && decoration.class) {
node.class = sanitizeText(decoration.class);
const config = getConfig();
const node = nodes[nodes.length - 1];
if (decoration.icon) {
node.icon = sanitizeText(decoration.icon, config);
}
if (decoration.class) {
node.class = sanitizeText(decoration.class, config);
}
};
export const type2Str = (type) => {
const type2Str = (type: number) => {
switch (type) {
case nodeType.DEFAULT:
return 'no-border';
@ -143,13 +139,21 @@ export const type2Str = (type) => {
}
};
export let parseError;
export const setErrorHandler = (handler) => {
parseError = handler;
};
// Expose logger to grammar
export const getLogger = () => log;
const getLogger = () => log;
const getElementById = (id: number) => elements[id];
export const getNodeById = (id) => nodes[id];
export const getElementById = (id) => elements[id];
const db = {
clear,
addNode,
getMindmap,
nodeType,
getType,
setElementForId,
decorateNode,
type2Str,
getLogger,
getElementById,
} as const;
export default db;

View File

@ -1,36 +1,53 @@
/** Created by knut on 14-12-11. */
import { select } from 'd3';
import { log } from '../../logger.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import svgDraw from './svgDraw.js';
import cytoscape from 'cytoscape/dist/cytoscape.umd.js';
import cytoscape from 'cytoscape';
// @ts-expect-error No types available
import coseBilkent from 'cytoscape-cose-bilkent';
import * as db from './mindmapDb.js';
import { select } from 'd3';
import type { MermaidConfig } from '../../config.type.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import type { DrawDefinition } from '../../diagram-api/types.js';
import { log } from '../../logger.js';
import type { D3Element } from '../../mermaidAPI.js';
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import type { FilledMindMapNode, MindmapDB, MindmapNode } from './mindmapTypes.js';
import { drawNode, positionNode } from './svgDraw.js';
import defaultConfig from '../../defaultConfig.js';
// Inject the layout algorithm into cytoscape
cytoscape.use(coseBilkent);
/**
* @param {any} svg The svg element to draw the diagram onto
* @param {object} mindmap The mindmap data and hierarchy
* @param section
* @param {object} conf The configuration object
*/
function drawNodes(svg, mindmap, section, conf) {
svgDraw.drawNode(svg, mindmap, section, conf);
function drawNodes(
db: MindmapDB,
svg: D3Element,
mindmap: FilledMindMapNode,
section: number,
conf: MermaidConfig
) {
drawNode(db, svg, mindmap, section, conf);
if (mindmap.children) {
mindmap.children.forEach((child, index) => {
drawNodes(svg, child, section < 0 ? index : section, conf);
drawNodes(db, svg, child, section < 0 ? index : section, conf);
});
}
}
/**
* @param edgesEl
* @param cy
*/
function drawEdges(edgesEl, cy) {
declare module 'cytoscape' {
interface EdgeSingular {
_private: {
bodyBounds: unknown;
rscratch: {
startX: number;
startY: number;
midX: number;
midY: number;
endX: number;
endY: number;
};
};
}
}
function drawEdges(edgesEl: D3Element, cy: cytoscape.Core) {
cy.edges().map((edge, id) => {
const data = edge.data();
if (edge[0]._private.bodyBounds) {
@ -47,17 +64,11 @@ function drawEdges(edgesEl, cy) {
});
}
/**
* @param mindmap The mindmap data and hierarchy
* @param cy
* @param conf The configuration object
* @param level
*/
function addNodes(mindmap, cy, conf, level) {
function addNodes(mindmap: MindmapNode, cy: cytoscape.Core, conf: MermaidConfig, level: number) {
cy.add({
group: 'nodes',
data: {
id: mindmap.id,
id: mindmap.id.toString(),
labelText: mindmap.descr,
height: mindmap.height,
width: mindmap.width,
@ -67,8 +78,8 @@ function addNodes(mindmap, cy, conf, level) {
type: mindmap.type,
},
position: {
x: mindmap.x,
y: mindmap.y,
x: mindmap.x!,
y: mindmap.y!,
},
});
if (mindmap.children) {
@ -88,12 +99,7 @@ function addNodes(mindmap, cy, conf, level) {
}
}
/**
* @param node
* @param conf
* @param cy
*/
function layoutMindmap(node, conf) {
function layoutMindmap(node: MindmapNode, conf: MermaidConfig): Promise<cytoscape.Core> {
return new Promise((resolve) => {
// Add temporary render element
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
@ -122,8 +128,8 @@ function layoutMindmap(node, conf) {
cy.layout({
name: 'cose-bilkent',
// @ts-ignore Types for cose-bilkent are not correct?
quality: 'proof',
// headless: true,
styleEnabled: false,
animate: false,
}).run();
@ -133,18 +139,13 @@ function layoutMindmap(node, conf) {
});
});
}
/**
* @param node
* @param cy
* @param positionedMindmap
* @param conf
*/
function positionNodes(cy) {
function positionNodes(db: MindmapDB, cy: cytoscape.Core) {
cy.nodes().map((node, id) => {
const data = node.data();
data.x = node.position().x;
data.y = node.position().y;
svgDraw.positionNode(data);
positionNode(db, data);
const el = db.getElementById(data.nodeId);
log.info('Id:', id, 'Position: (', node.position().x, ', ', node.position().y, ')', data);
el.attr(
@ -155,38 +156,19 @@ function positionNodes(cy) {
});
}
/**
* Draws a an info picture in the tag with id: id based on the graph definition in text.
*
* @param {any} text
* @param {any} id
* @param {any} version
* @param diagObj
*/
export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
log.debug('Rendering mindmap diagram\n' + text);
const db = diagObj.db as MindmapDB;
const mm = db.getMindmap();
if (!mm) {
return;
}
export const draw = async (text, id, version, diagObj) => {
const conf = getConfig();
conf.htmlLabels = false;
log.debug('Rendering mindmap diagram\n' + text, diagObj.parser);
const securityLevel = getConfig().securityLevel;
// Handle root and Document for when rendering in sandbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
// Parse the graph definition
const svg = root.select('#' + id);
svg.append('g');
const mm = diagObj.db.getMindmap();
const svg = selectSvgElement(id);
// Draw the graph and start with drawing the nodes without proper position
// this gives us the size of the nodes and we can set the positions later
@ -195,18 +177,23 @@ export const draw = async (text, id, version, diagObj) => {
edgesElem.attr('class', 'mindmap-edges');
const nodesElem = svg.append('g');
nodesElem.attr('class', 'mindmap-nodes');
drawNodes(nodesElem, mm, -1, conf);
drawNodes(db, nodesElem, mm as FilledMindMapNode, -1, conf);
// Next step is to layout the mindmap, giving each node a position
const cy = await layoutMindmap(mm, conf);
// // After this we can draw, first the edges and the then nodes with the correct position
drawEdges(edgesElem, cy, conf);
positionNodes(cy, conf);
// After this we can draw, first the edges and the then nodes with the correct position
drawEdges(edgesElem, cy);
positionNodes(db, cy);
// Setup the view box and size of the svg element
setupGraphViewbox(undefined, svg, conf.mindmap.padding, conf.mindmap.useMaxWidth);
setupGraphViewbox(
undefined,
svg,
conf.mindmap?.padding ?? defaultConfig.mindmap.padding,
conf.mindmap?.useMaxWidth ?? defaultConfig.mindmap.useMaxWidth
);
};
export default {

View File

@ -0,0 +1,22 @@
import type { RequiredDeep } from 'type-fest';
import type mindmapDb from './mindmapDb.js';
export interface MindmapNode {
id: number;
nodeId: string;
level: number;
descr: string;
type: number;
children: MindmapNode[];
width: number;
padding: number;
section?: number;
height?: number;
class?: string;
icon?: string;
x?: number;
y?: number;
}
export type FilledMindMapNode = RequiredDeep<MindmapNode>;
export type MindmapDB = typeof mindmapDb;

View File

@ -1,6 +1,8 @@
// @ts-expect-error Incorrect khroma types
import { darken, lighten, isDark } from 'khroma';
import type { DiagramStylesProvider } from '../../diagram-api/types.js';
const genSections = (options) => {
const genSections: DiagramStylesProvider = (options) => {
let sections = '';
for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) {
@ -49,7 +51,8 @@ const genSections = (options) => {
return sections;
};
const getStyles = (options) =>
// TODO: These options seem incorrect.
const getStyles: DiagramStylesProvider = (options) =>
`
.edge {
stroke-width: 3;

View File

@ -1,55 +1,20 @@
import { select } from 'd3';
import * as db from './mindmapDb.js';
import type { D3Element } from '../../mermaidAPI.js';
import { createText } from '../../rendering-util/createText.js';
import type { FilledMindMapNode, MindmapDB } from './mindmapTypes.js';
import type { Point } from '../../types.js';
import { parseFontSize } from '../../utils.js';
import type { MermaidConfig } from '../../config.type.js';
const MAX_SECTIONS = 12;
/**
* @param {string} text The text to be wrapped
* @param {number} width The max width of the text
*/
function wrap(text, width) {
text.each(function () {
var text = select(this),
words = text
.text()
.split(/(\s+|<br\/>)/)
.reverse(),
word,
line = [],
lineHeight = 1.1, // ems
y = text.attr('y'),
dy = parseFloat(text.attr('dy')),
tspan = text
.text(null)
.append('tspan')
.attr('x', 0)
.attr('y', y)
.attr('dy', dy + 'em');
for (let j = 0; j < words.length; j++) {
word = words[words.length - 1 - j];
line.push(word);
tspan.text(line.join(' ').trim());
if (tspan.node().getComputedTextLength() > width || word === '<br/>') {
line.pop();
tspan.text(line.join(' ').trim());
if (word === '<br/>') {
line = [''];
} else {
line = [word];
}
type ShapeFunction = (
db: MindmapDB,
elem: D3Element,
node: FilledMindMapNode,
section?: number
) => void;
tspan = text
.append('tspan')
.attr('x', 0)
.attr('y', y)
.attr('dy', lineHeight + 'em')
.text(word);
}
}
});
}
const defaultBkg = function (elem, node, section) {
const defaultBkg: ShapeFunction = function (db, elem, node, section) {
const rd = 5;
elem
.append('path')
@ -71,7 +36,7 @@ const defaultBkg = function (elem, node, section) {
.attr('y2', node.height);
};
const rectBkg = function (elem, node) {
const rectBkg: ShapeFunction = function (db, elem, node) {
elem
.append('rect')
.attr('id', 'node-' + node.id)
@ -80,7 +45,7 @@ const rectBkg = function (elem, node) {
.attr('width', node.width);
};
const cloudBkg = function (elem, node) {
const cloudBkg: ShapeFunction = function (db, elem, node) {
const w = node.width;
const h = node.height;
const r1 = 0.15 * w;
@ -111,7 +76,7 @@ const cloudBkg = function (elem, node) {
);
};
const bangBkg = function (elem, node) {
const bangBkg: ShapeFunction = function (db, elem, node) {
const w = node.width;
const h = node.height;
const r = 0.15 * w;
@ -143,7 +108,7 @@ const bangBkg = function (elem, node) {
);
};
const circleBkg = function (elem, node) {
const circleBkg: ShapeFunction = function (db, elem, node) {
elem
.append('circle')
.attr('id', 'node-' + node.id)
@ -151,15 +116,13 @@ const circleBkg = function (elem, node) {
.attr('r', node.width / 2);
};
/**
*
* @param parent
* @param w
* @param h
* @param points
* @param node
*/
function insertPolygonShape(parent, w, h, points, node) {
function insertPolygonShape(
parent: D3Element,
w: number,
h: number,
points: Point[],
node: FilledMindMapNode
) {
return parent
.insert('polygon', ':first-child')
.attr(
@ -173,12 +136,16 @@ function insertPolygonShape(parent, w, h, points, node) {
.attr('transform', 'translate(' + (node.width - w) / 2 + ', ' + h + ')');
}
const hexagonBkg = function (elem, node) {
const hexagonBkg: ShapeFunction = function (
_db: MindmapDB,
elem: D3Element,
node: FilledMindMapNode
) {
const h = node.height;
const f = 4;
const m = h / f;
const w = node.width - node.padding + 2 * m;
const points = [
const points: Point[] = [
{ x: m, y: 0 },
{ x: w - m, y: 0 },
{ x: w, y: -h / 2 },
@ -186,10 +153,10 @@ const hexagonBkg = function (elem, node) {
{ x: m, y: -h },
{ x: 0, y: -h / 2 },
];
const shapeSvg = insertPolygonShape(elem, w, h, points, node);
insertPolygonShape(elem, w, h, points, node);
};
const roundedRectBkg = function (elem, node) {
const roundedRectBkg: ShapeFunction = function (db, elem, node) {
elem
.append('rect')
.attr('id', 'node-' + node.id)
@ -201,13 +168,20 @@ const roundedRectBkg = function (elem, node) {
};
/**
* @param {object} elem The D3 dom element in which the node is to be added
* @param {object} node The node to be added
* @param fullSection
* @param {object} conf The configuration object
* @returns {number} The height nodes dom element
* @param db - The database
* @param elem - The D3 dom element in which the node is to be added
* @param node - The node to be added
* @param fullSection - ?
* @param conf - The configuration object
* @returns The height nodes dom element
*/
export const drawNode = function (elem, node, fullSection, conf) {
export const drawNode = function (
db: MindmapDB,
elem: D3Element,
node: FilledMindMapNode,
fullSection: number,
conf: MermaidConfig
): number {
const htmlLabels = conf.htmlLabels;
const section = fullSection % (MAX_SECTIONS - 1);
const nodeElem = elem.append('g');
@ -235,10 +209,9 @@ export const drawNode = function (elem, node, fullSection, conf) {
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle');
}
// .call(wrap, node.width);
const bbox = textElem.node().getBBox();
const fontSize = conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
const [fontSize] = parseFontSize(conf.fontSize);
node.height = bbox.height + fontSize! * 1.1 * 0.5 + node.padding;
node.width = bbox.width + 2 * node.padding;
if (node.icon) {
if (node.type === db.nodeType.CIRCLE) {
@ -294,60 +267,34 @@ export const drawNode = function (elem, node, fullSection, conf) {
switch (node.type) {
case db.nodeType.DEFAULT:
defaultBkg(bkgElem, node, section, conf);
defaultBkg(db, bkgElem, node, section);
break;
case db.nodeType.ROUNDED_RECT:
roundedRectBkg(bkgElem, node, section, conf);
roundedRectBkg(db, bkgElem, node, section);
break;
case db.nodeType.RECT:
rectBkg(bkgElem, node, section, conf);
rectBkg(db, bkgElem, node, section);
break;
case db.nodeType.CIRCLE:
bkgElem.attr('transform', 'translate(' + node.width / 2 + ', ' + +node.height / 2 + ')');
circleBkg(bkgElem, node, section, conf);
circleBkg(db, bkgElem, node, section);
break;
case db.nodeType.CLOUD:
cloudBkg(bkgElem, node, section, conf);
cloudBkg(db, bkgElem, node, section);
break;
case db.nodeType.BANG:
bangBkg(bkgElem, node, section, conf);
bangBkg(db, bkgElem, node, section);
break;
case db.nodeType.HEXAGON:
hexagonBkg(bkgElem, node, section, conf);
hexagonBkg(db, bkgElem, node, section);
break;
}
// Position the node to its coordinate
// if (typeof node.x !== 'undefined' && typeof node.y !== 'undefined') {
// nodeElem.attr('transform', 'translate(' + node.x + ',' + node.y + ')');
// }
db.setElementForId(node.id, nodeElem);
return node.height;
};
export const drawEdge = function drawEdge(edgesElem, mindmap, parent, depth, fullSection) {
const section = fullSection % (MAX_SECTIONS - 1);
const sx = parent.x + parent.width / 2;
const sy = parent.y + parent.height / 2;
const ex = mindmap.x + mindmap.width / 2;
const ey = mindmap.y + mindmap.height / 2;
const mx = ex > sx ? sx + Math.abs(sx - ex) / 2 : sx - Math.abs(sx - ex) / 2;
const my = ey > sy ? sy + Math.abs(sy - ey) / 2 : sy - Math.abs(sy - ey) / 2;
const qx = ex > sx ? Math.abs(sx - mx) / 2 + sx : -Math.abs(sx - mx) / 2 + sx;
const qy = ey > sy ? Math.abs(sy - my) / 2 + sy : -Math.abs(sy - my) / 2 + sy;
edgesElem
.append('path')
.attr(
'd',
parent.direction === 'TB' || parent.direction === 'BT'
? `M${sx},${sy} Q${sx},${qy} ${mx},${my} T${ex},${ey}`
: `M${sx},${sy} Q${qx},${sy} ${mx},${my} T${ex},${ey}`
)
.attr('class', 'edge section-edge-' + section + ' edge-depth-' + depth);
};
export const positionNode = function (node) {
export const positionNode = function (db: MindmapDB, node: FilledMindMapNode) {
const nodeElem = db.getElementById(node.id);
const x = node.x || 0;
@ -355,5 +302,3 @@ export const positionNode = function (node) {
// Position the node to its coordinate
nodeElem.attr('transform', 'translate(' + x + ',' + y + ')');
};
export default { drawNode, positionNode, drawEdge };

View File

@ -53,7 +53,7 @@ export interface QuadrantBuildType {
borderLines?: QuadrantLineType[];
}
export interface quadrantBuilderData {
export interface QuadrantBuilderData {
titleText: string;
quadrant1Text: string;
quadrant2Text: string;
@ -116,7 +116,7 @@ interface CalculateSpaceData {
export class QuadrantBuilder {
private config: QuadrantBuilderConfig;
private themeConfig: QuadrantBuilderThemeConfig;
private data: quadrantBuilderData;
private data: QuadrantBuilderData;
constructor() {
this.config = this.getDefaultConfig();
@ -124,7 +124,7 @@ export class QuadrantBuilder {
this.data = this.getDefaultData();
}
getDefaultData(): quadrantBuilderData {
getDefaultData(): QuadrantBuilderData {
return {
titleText: '',
quadrant1Text: '',
@ -194,7 +194,7 @@ export class QuadrantBuilder {
log.info('clear called');
}
setData(data: Partial<quadrantBuilderData>) {
setData(data: Partial<QuadrantBuilderData>) {
this.data = { ...this.data, ...data };
}

View File

@ -1,6 +1,5 @@
import type { Diagram } from '../../Diagram.js';
import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js';
import {
select as d3select,
scaleOrdinal as d3scaleOrdinal,

View File

@ -5,27 +5,13 @@ import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
import { sanitizeUrl } from '@braintree/sanitize-url';
export const ACTOR_TYPE_WIDTH = 18 * 2;
const TOP_ACTOR_CLASS = 'actor-top';
const BOTTOM_ACTOR_CLASS = 'actor-bottom';
export const drawRect = function (elem, rectData) {
return svgDrawCommon.drawRect(elem, rectData);
};
const addPopupInteraction = (id, actorCnt) => {
addFunction(() => {
const arr = document.querySelectorAll(id);
// This will be the case when running in sandboxed mode
if (arr.length === 0) {
return;
}
arr[0].addEventListener('mouseover', function () {
popupMenuUpFunc('actor' + actorCnt + '_popup');
});
arr[0].addEventListener('mouseout', function () {
popupMenuDownFunc('actor' + actorCnt + '_popup');
});
});
};
export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMenus) {
if (actor.links === undefined || actor.links === null || Object.keys(actor.links).length === 0) {
return { height: 0, width: 0 };
@ -44,7 +30,6 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe
g.attr('id', 'actor' + actorCnt + '_popup');
g.attr('class', 'actorPopupMenu');
g.attr('display', displayValue);
addPopupInteraction('#actor' + actorCnt + '_popup', actorCnt);
var actorClass = '';
if (rectData.class !== undefined) {
actorClass = ' ' + rectData.class;
@ -90,36 +75,14 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe
return { height: rectData.height + linkY, width: menuWidth };
};
export const popupMenu = function (popid) {
const popupMenuToggle = function (popid) {
return (
"var pu = document.getElementById('" +
popid +
"'); if (pu != null) { pu.style.display = 'block'; }"
"'); if (pu != null) { pu.style.display = pu.style.display == 'block' ? 'none' : 'block'; }"
);
};
export const popdownMenu = function (popid) {
return (
"var pu = document.getElementById('" +
popid +
"'); if (pu != null) { pu.style.display = 'none'; }"
);
};
const popupMenuUpFunc = function (popupId) {
var pu = document.getElementById(popupId);
if (pu != null) {
pu.style.display = 'block';
}
};
const popupMenuDownFunc = function (popupId) {
var pu = document.getElementById(popupId);
if (pu != null) {
pu.style.display = 'none';
}
};
export const drawText = function (elem, textData) {
let prevTextHeight = 0;
let textHeight = 0;
@ -329,6 +292,9 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
if (!isFooter) {
actorCnt++;
if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
}
g.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
@ -345,7 +311,6 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
if (actor.links != null) {
g.attr('id', 'root-' + actorCnt);
addPopupInteraction('#root-' + actorCnt, actorCnt);
}
}
@ -356,6 +321,11 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
} else {
rect.fill = '#eaeaea';
}
if (isFooter) {
cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssclass += ` ${TOP_ACTOR_CLASS}`;
}
rect.x = actor.x;
rect.y = actorY;
rect.width = actor.width;
@ -420,7 +390,13 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
actor.actorCnt = actorCnt;
}
const actElem = elem.append('g');
actElem.attr('class', 'actor-man');
let cssClass = 'actor-man';
if (isFooter) {
cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssClass += ` ${TOP_ACTOR_CLASS}`;
}
actElem.attr('class', cssClass);
const rect = svgDrawCommon.getNoteRect();
rect.x = actor.x;
@ -1053,8 +1029,6 @@ export default {
insertClockIcon,
getTextObj,
getNoteRect,
popupMenu,
popdownMenu,
fixLifeLineHeights,
sanitizeUrl,
};

View File

@ -16,7 +16,7 @@ import { teamMembers } from '../contributors';
<p text-lg max-w-200 text-center leading-7>
<Contributors />
<br />
<a href="https://discord.gg/wwtabKgp8y" rel="noopener noreferrer">Join the community</a>
<a href="https://discord.gg/AgrbSrBer3" rel="noopener noreferrer">Join the community</a>
and get involved!
</p>
</div>

View File

@ -56,7 +56,7 @@ export default defineConfig({
{ icon: 'github', link: 'https://github.com/mermaid-js/mermaid' },
{
icon: 'discord',
link: 'https://discord.gg/wwtabKgp8y',
link: 'https://discord.gg/AgrbSrBer3',
},
{
icon: {
@ -153,6 +153,7 @@ function sidebarSyntax() {
{ text: 'Zenuml', link: '/syntax/zenuml' },
{ text: 'Sankey 🔥', link: '/syntax/sankey' },
{ text: 'XYChart 🔥', link: '/syntax/xyChart' },
{ text: 'Block Diagram 🔥', link: '/syntax/block' },
{ text: 'Packet 🔥', link: '/syntax/packet' },
{ text: 'Other Examples', link: '/syntax/examples' },
],

View File

@ -8,14 +8,12 @@ import Contributors from '../components/Contributors.vue';
import HomePage from '../components/HomePage.vue';
// @ts-ignore
import TopBar from '../components/TopBar.vue';
import { getRedirect } from './redirect.js';
import { h } from 'vue';
import Theme from 'vitepress/theme';
import '../style/main.css';
import 'uno.css';
import type { EnhanceAppContext } from 'vitepress';
export default {
...DefaultTheme,
@ -26,19 +24,22 @@ export default {
'home-features-after': () => h(HomePage),
});
},
enhanceApp({ app, router }) {
enhanceApp({ app, router }: EnhanceAppContext) {
// register global components
app.component('Mermaid', Mermaid);
app.component('Contributors', Contributors);
router.onBeforeRouteChange = (to) => {
try {
const newPath = getRedirect(to);
const url = new URL(window.location.origin + to);
const newPath = getRedirect(url);
if (newPath) {
console.log(`Redirecting to ${newPath} from ${window.location}`);
// router.go isn't loading the ID properly.
window.location.href = `/${newPath}`;
}
} catch (e) {}
} catch (e) {
console.error(e);
}
};
},
};

View File

@ -57,5 +57,5 @@ test.each([
'configure/faq.html#frequently-asked-questions',
], // with hash
])('should process url %s to %s', (link: string, path: string) => {
expect(getRedirect(link)).toBe(path);
expect(getRedirect(new URL(link))).toBe(path);
});

View File

@ -104,8 +104,7 @@ const urlRedirectMap: Record<string, string> = {
* @param link - The old documentation URL.
* @returns The new documentation path.
*/
export const getRedirect = (link: string): string | undefined => {
const url = new URL(link);
export const getRedirect = (url: URL): string | undefined => {
// Redirects for deprecated vitepress URLs
if (url.pathname in urlRedirectMap) {
return `${urlRedirectMap[url.pathname]}${url.hash}`;

View File

@ -18,7 +18,7 @@ Currently pending [IANA](https://www.iana.org/) recognition.
### Mermaid Discord workspace
We would love to see what you create with Mermaid. Please share your creations with us in our [Discord](https://discord.gg/wwtabKgp8y) server [#showcase](https://discord.com/channels/1079455296289788015/1079502635054399649) channel.
We would love to see what you create with Mermaid. Please share your creations with us in our [Discord](https://discord.gg/AgrbSrBer3) server [#showcase](https://discord.com/channels/1079455296289788015/1079502635054399649) channel.
### Add to Mermaid Ecosystem

View File

@ -16,7 +16,7 @@ It is a JavaScript based diagramming and charting tool that renders Markdown-ins
[![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master)
[![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid)
[![NPM](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid)
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/wwtabKgp8y)
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/AgrbSrBer3)
[![Twitter Follow](https://img.shields.io/twitter/follow/mermaidjs_?style=social)](https://twitter.com/mermaidjs_)
</div>

View File

@ -12,19 +12,6 @@ Create flowchart nodes, connect them with edges, update shapes, change colors, a
Read more about it in our latest [BLOG POST](https://www.mermaidchart.com/blog/posts/mermaid-chart-releases-new-visual-editor-for-flowcharts) and watch a [DEMO VIDEO](https://www.youtube.com/watch?v=5aja0gijoO0) on our YouTube page.
## 🎉 Mermaid Chart is running a Holiday promotion
### Use <span class="text-[#FE3470]">HOLIDAYS2023</span> to get a 14-day free trial and 25% off a Pro subscription
With a Pro subscription, you get access to:
- AI functionality
- Team collaboration and multi-user editing
- Unlimited diagrams and presentations
- And more!
Redeem the promo code on the [Mermaid Chart website](https://www.mermaidchart.com/app/user/billing/checkout?coupon=HOLIDAYS2023).
## 📖 Blog posts
Visit our [Blog](./blog.md) to see the latest blog posts.

View File

@ -1,5 +1,11 @@
# Blog
## [How one data scientist uses Mermaid Chart to quickly and easily build flowcharts](https://www.mermaidchart.com/blog/posts/customer-spotlight-ari-tal/)
23 January 2024 · 4 mins
Read about how Ari Tal, a data scientist and founder of Leveling Up with XAI, utilizes Mermaid Chart for its easy-to-use flowchart creation capabilities to enhance his work in explainable AI (XAI).
## [Introducing Mermaid Charts JetBrains IDE Extension](https://www.mermaidchart.com/blog/posts/introducing-mermaid-charts-jetbrains-ide-extension/)
20 December 2023 · 5 mins

View File

@ -30,9 +30,9 @@
"pathe": "^1.1.0",
"unocss": "^0.58.0",
"unplugin-vue-components": "^0.26.0",
"vite": "^4.4.12",
"vite-plugin-pwa": "^0.17.0",
"vitepress": "1.0.0-rc.39",
"vite": "^4.5.2",
"vite-plugin-pwa": "^0.17.5",
"vitepress": "1.0.0-rc.41",
"workbox-window": "^7.0.0"
}
}

View File

@ -0,0 +1,492 @@
---
title: Block Diagram Syntax
outline: 'deep' # shows all h3 headings in outline in Vitepress
---
# Block Diagrams Documentation
## Introduction to Block Diagrams
```mermaid
block-beta
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#969,stroke:#333,stroke-width:4px
```
### Definition and Purpose
Block diagrams are an intuitive and efficient way to represent complex systems, processes, or architectures visually. They are composed of blocks and connectors, where blocks represent the fundamental components or functions, and connectors show the relationship or flow between these components. This method of diagramming is essential in various fields such as engineering, software development, and process management.
The primary purpose of block diagrams is to provide a high-level view of a system, allowing for easy understanding and analysis without delving into the intricate details of each component. This makes them particularly useful for simplifying complex systems and for explaining the overall structure and interaction of components within a system.
Many people use mermaid flowcharts for this purpose. A side-effect of this is that the automatic layout sometimes move shapes to positions that the diagram maker does not want. Block diagrams use a different approach. In this diagram we give the author full control over where the shapes are positioned.
### General Use Cases
Block diagrams have a wide range of applications across various industries and disciplines. Some of the key use cases include:
- **Software Architecture**: In software development, block diagrams can be used to illustrate the architecture of a software application. This includes showing how different modules or services interact, data flow, and high-level component interaction.
- **Network Diagrams**: Block diagrams are ideal for representing network architectures in IT and telecommunications. They can depict how different network devices and services are interconnected, including routers, switches, firewalls, and the flow of data across the network.
- **Process Flowcharts**: In business and manufacturing, block diagrams can be employed to create process flowcharts. These flowcharts represent various stages of a business or manufacturing process, helping to visualize the sequence of steps, decision points, and the flow of control.
- **Electrical Systems**: Engineers use block diagrams to represent electrical systems and circuitry. They can illustrate the high-level structure of an electrical system, the interaction between different electrical components, and the flow of electrical currents.
- **Educational Purposes**: Block diagrams are also extensively used in educational materials to explain complex concepts and systems in a simplified manner. They help in breaking down and visualizing scientific theories, engineering principles, and technological systems.
These examples demonstrate the versatility of block diagrams in providing clear and concise representations of complex systems. Their simplicity and clarity make them a valuable tool for professionals across various fields to communicate complex ideas effectively.
In the following sections, we will delve into the specifics of creating and manipulating block diagrams using Mermaid, covering everything from basic syntax to advanced configurations and styling.
Creating block diagrams with Mermaid is straightforward and accessible. This section introduces the basic syntax and structure needed to start building simple diagrams. Understanding these foundational concepts is key to efficiently utilizing Mermaid for more complex diagramming tasks.
### Simple Block Diagrams
#### Basic Structure
At its core, a block diagram consists of blocks representing different entities or components. In Mermaid, these blocks are easily created using simple text labels. The most basic form of a block diagram can be a series of blocks without any connectors.
**Example - Simple Block Diagram**:
To create a simple block diagram with three blocks labeled 'a', 'b', and 'c', the syntax is as follows:
```mermaid-example
block-beta
a b c
```
This example will produce a horizontal sequence of three blocks. Each block is automatically spaced and aligned for optimal readability.
### Defining the number of columns to use
#### Column Usage
While simple block diagrams are linear and straightforward, more complex systems may require a structured layout. Mermaid allows for the organization of blocks into multiple columns, facilitating the creation of more intricate and detailed diagrams.
**Example - Multi-Column Diagram:**
In scenarios where you need to distribute blocks across multiple columns, you can specify the number of columns and arrange the blocks accordingly. Here's how to create a block diagram with three columns and four blocks, where the fourth block appears in a second row:
```mermaid-example
block-beta
columns 3
a b c d
```
This syntax instructs Mermaid to arrange the blocks 'a', 'b', 'c', and 'd' across three columns, wrapping to the next row as needed. This feature is particularly useful for representing layered or multi-tiered systems, such as network layers or hierarchical structures.
These basic building blocks of Mermaid's block diagrams provide a foundation for more complex diagramming. The simplicity of the syntax allows for quick creation and iteration of diagrams, making it an efficient tool for visualizing ideas and concepts. In the next section, we'll explore advanced block configuration options, including setting block widths and creating composite blocks.
## 3. Advanced Block Configuration
Building upon the basics, this section delves into more advanced features of block diagramming in Mermaid. These features allow for greater flexibility and complexity in diagram design, accommodating a wider range of use cases and scenarios.
### Setting Block Width
#### Spanning Multiple Columns
In more complex diagrams, you may need blocks that span multiple columns to emphasize certain components or to represent larger entities. Mermaid allows for the adjustment of block widths to cover multiple columns, enhancing the diagram's readability and structure.
**Example - Block Spanning Multiple Columns**:
To create a block diagram where one block spans across two columns, you can specify the desired width for each block:
```mermaid-example
block-beta
columns 3
a["A label"] b:2 c:2 d
```
In this example, the block labeled "A wide one" spans two columns, while blocks 'b', 'c', and 'd' are allocated their own columns. This flexibility in block sizing is crucial for accurately representing systems with components of varying significance or size.
### Creating Composite Blocks
#### Nested Blocks
Composite blocks, or blocks within blocks, are an advanced feature in Mermaid's block diagram syntax. They allow for the representation of nested or hierarchical systems, where one component encompasses several subcomponents.
**Example - Composite Blocks:**
Creating a composite block involves defining a parent block and then nesting other blocks within it. Here's how to define a composite block with nested elements:
```mermaid-example
block-beta
block
D
end
A["A: I am a wide one"]
```
In this syntax, 'D' is a nested block within a larger parent block. This feature is particularly useful for depicting complex structures, such as a server with multiple services or a department within a larger organizational framework.
### Column Width Dynamics
#### Adjusting Widths
Mermaid also allows for dynamic adjustment of column widths based on the content of the blocks. The width of the columns is determined by the widest block in the column, ensuring that the diagram remains balanced and readable.
**Example - Dynamic Column Widths:**
In diagrams with varying block sizes, Mermaid automatically adjusts the column widths to fit the largest block in each column. Here's an example:
```mermaid-example
block-beta
columns 3
a:3
block:group1:2
columns 2
h i j k
end
g
block:group2:3
%% columns auto (default)
l m n o p q r
end
```
This example demonstrates how Mermaid dynamically adjusts the width of the columns to accommodate the widest block, in this case, 'a' and the composite block 'e'. This dynamic adjustment is essential for creating visually balanced and easy-to-understand diagrams.
With these advanced configuration options, Mermaid's block diagrams can be tailored to represent a wide array of complex systems and structures. The flexibility offered by these features enables users to create diagrams that are both informative and visually appealing. In the following sections, we will explore further capabilities, including different block shapes and linking options.
## 4. Block Varieties and Shapes
Mermaid's block diagrams are not limited to standard rectangular shapes. A variety of block shapes are available, allowing for a more nuanced and tailored representation of different types of information or entities. This section outlines the different block shapes you can use in Mermaid and their specific applications.
### Standard and Special Block Shapes
Mermaid supports a range of block shapes to suit different diagramming needs, from basic geometric shapes to more specialized forms.
#### Example - Round Edged Block
To create a block with round edges, which can be used to represent a softer or more flexible component:
```mermaid-example
block-beta
id1("This is the text in the box")
```
#### Example - Stadium-Shaped Block
A stadium-shaped block, resembling an elongated circle, can be used for components that are process-oriented:
```mermaid-example
block-beta
id1(["This is the text in the box"])
```
#### Example - Subroutine Shape
For representing subroutines or contained processes, a block with double vertical lines is useful:
```mermaid-example
block-beta
id1[["This is the text in the box"]]
```
#### Example - Cylindrical Shape
The cylindrical shape is ideal for representing databases or storage components:
```mermaid-example
block-beta
id1[("Database")]
```
#### Example - Circle Shape
A circle can be used for centralized or pivotal components:
```mermaid-example
block-beta
id1(("This is the text in the circle"))
```
#### Example - Asymmetric, Rhombus, and Hexagon Shapes
For decision points, use a rhombus, and for unique or specialized processes, asymmetric and hexagon shapes can be utilized:
**Asymmetric**
```mermaid-example
block-beta
id1>"This is the text in the box"]
```
**Rhombus**
```mermaid-example
block-beta
id1{"This is the text in the box"}
```
**Hexagon**
```mermaid-example
block-beta
id1{{"This is the text in the box"}}
```
#### Example - Parallelogram and Trapezoid Shapes
Parallelogram and trapezoid shapes are perfect for inputs/outputs and transitional processes:
```mermaid-example
block-beta
id1[/"This is the text in the box"/]
id2[\"This is the text in the box"\]
A[/"Christmas"\]
B[\"Go shopping"/]
```
#### Example - Double Circle
For highlighting critical or high-priority components, a double circle can be effective:
```mermaid-example
block-beta
id1((("This is the text in the circle")))
```
### Block Arrows and Space Blocks
Mermaid also offers unique shapes like block arrows and space blocks for directional flow and spacing.
#### Example - Block Arrows
Block arrows can visually indicate direction or flow within a process:
```mermaid-example
block-beta
blockArrowId<["Label"]>(right)
blockArrowId2<["Label"]>(left)
blockArrowId3<["Label"]>(up)
blockArrowId4<["Label"]>(down)
blockArrowId5<["Label"]>(x)
blockArrowId6<["Label"]>(y)
blockArrowId6<["Label"]>(x, down)
```
#### Example - Space Blocks
Space blocks can be used to create intentional empty spaces in the diagram, which is useful for layout and readability:
```mermaid-example
block-beta
columns 3
a space b
c d e
```
or
```mermaid-example
block-beta
ida space:3 idb idc
```
Note that you can set how many columns the spece block occupied using the number notaion `space:num` where num is a number indicating the num columns width. You can alsio use `space` which defaults to one column.
The variety of shapes and special blocks in Mermaid enhances the expressive power of block diagrams, allowing for more accurate and context-specific representations. These options give users the flexibility to create diagrams that are both informative and visually appealing. In the next sections, we will explore the ways to connect these blocks and customize their appearance.
### Standard and Special Block Shapes
Discuss the various shapes available for blocks, including standard shapes and special forms like block arrows and space blocks.
## 5. Connecting Blocks with Edges
One of the key features of block diagrams in Mermaid is the ability to connect blocks using various types of edges or links. This section explores the different ways blocks can be interconnected to represent relationships and flows between components.
### Basic Linking and Arrow Types
The most fundamental aspect of connecting blocks is the use of arrows or links. These connectors depict the relationships or the flow of information between the blocks. Mermaid offers a range of arrow types to suit different diagramming needs.
**Example - Basic Links**
A simple link with an arrow can be created to show direction or flow from one block to another:
```mermaid-example
block-beta
A space B
A-->B
```
This example illustrates a direct connection from block 'A' to block 'B', using a straightforward arrow.
This syntax creates a line connecting 'A' and 'B', implying a relationship or connection without indicating a specific direction.
### Text on Links
In addition to connecting blocks, it's often necessary to describe or label the relationship. Mermaid allows for the inclusion of text on links, providing context to the connections.
Example - Text with Links
To add text to a link, the syntax includes the text within the link definition:
```mermaid-example
block-beta
A space:2 B
A-- "X" -->B
```
This example show how to add descriptive text to the links, enhancing the information conveyed by the diagram.
Example - Edges and Styles:
```mermaid-example
block-beta
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#939,stroke:#333,stroke-width:4px
```
## 6. Styling and Customization
Beyond the structure and layout of block diagrams, Mermaid offers extensive styling options. These customization features allow for the creation of more visually distinctive and informative diagrams. This section covers how to apply individual styles to blocks and how to use classes for consistent styling across multiple elements.
### Individual Block Styling
Mermaid enables detailed styling of individual blocks, allowing you to apply various CSS properties such as color, stroke, and border thickness. This feature is especially useful for highlighting specific parts of a diagram or for adhering to certain visual themes.
#### Example - Styling a Single Block
To apply custom styles to a block, you can use the `style` keyword followed by the block identifier and the desired CSS properties:
```mermaid-example
block-beta
id1 space id2
id1("Start")-->id2("Stop")
style id1 fill:#636,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
```
In this example, a class named 'blue' is defined and applied to block 'A', while block 'B' receives individual styling. This demonstrates the flexibility of Mermaid in applying both shared and unique styles within the same diagram.
The ability to style blocks individually or through classes provides a powerful tool for enhancing the visual impact and clarity of block diagrams. Whether emphasizing certain elements or maintaining a cohesive design across the diagram, these styling capabilities are central to effective diagramming. The next sections will present practical examples and use cases, followed by tips for troubleshooting common issues.
### 7. Practical Examples and Use Cases
The versatility of Mermaid's block diagrams becomes evident when applied to real-world scenarios. This section provides practical examples demonstrating the application of various features discussed in previous sections. These examples showcase how block diagrams can be used to represent complex systems and processes in an accessible and informative manner.
### Detailed Examples Illustrating Various Features
Combining the elements of structure, linking, and styling, we can create comprehensive diagrams that serve specific purposes in different contexts.
#### Example - System Architecture
Illustrating a simple software system architecture with interconnected components:
```mermaid
block-beta
columns 3
Frontend blockArrowId6<[" "]>(right) Backend
space:2 down<[" "]>(down)
Disk left<[" "]>(left) Database[("Database")]
classDef front fill:#696,stroke:#333;
classDef back fill:#969,stroke:#333;
class Frontend front
class Backend,Database back
```
This example shows a basic architecture with a frontend, backend, and database. The blocks are styled to differentiate between types of components.
#### Example - Business Process Flow
Representing a business process flow with decision points and multiple stages:
```mermaid-example
block-beta
columns 3
Start(("Start")) space:2
down<[" "]>(down) space:2
Decision{{"Make Decision"}} right<["Yes"]>(right) Process1["Process A"]
downAgain<["No"]>(down) space r3<["Done"]>(down)
Process2["Process B"] r2<["Done"]>(right) End(("End"))
style Start fill:#969;
style End fill:#696;
```
These practical examples and scenarios underscore the utility of Mermaid block diagrams in simplifying and effectively communicating complex information across various domains.
The next section, 'Troubleshooting and Common Issues', will provide insights into resolving common challenges encountered when working with Mermaid block diagrams, ensuring a smooth diagramming experience.
## 8. Troubleshooting and Common Issues
Working with Mermaid block diagrams can sometimes present challenges, especially as the complexity of the diagrams increases. This section aims to provide guidance on resolving common issues and offers tips for managing more intricate diagram structures.
### Common Syntax Errors
Understanding and avoiding common syntax errors is key to a smooth experience with Mermaid diagrams.
#### Example - Incorrect Linking
A common mistake is incorrect linking syntax, which can lead to unexpected results or broken diagrams:
```
block-beta
A - B
```
**Correction**:
Ensure that links between blocks are correctly specified with arrows (--> or ---) to define the direction and type of connection. Also rememeber that one of the fundaments for block diagram is to give the author full control of where the boxes are positioned so in the example you need to add a space between the boxes:
```mermaid-example
block-beta
A space B
A --> B
```
#### Example - Misplaced Styling
Applying styles in the wrong context or with incorrect syntax can lead to blocks not being styled as intended:
```mermaid-example
block-beta
A
style A fill#969;
```
**Correction:**
Correct the syntax by ensuring proper separation of style properties with commas and using the correct CSS property format:
```mermaid-example
block-beta
A
style A fill:#969,stroke:#333;
```
### Tips for Complex Diagram Structures
Managing complexity in Mermaid diagrams involves planning and employing best practices.
#### Modular Design
Break down complex diagrams into smaller, more manageable components. This approach not only makes the diagram easier to understand but also simplifies the creation and maintenance process.
#### Consistent Styling
Use classes to maintain consistent styling across similar elements. This not only saves time but also ensures a cohesive and professional appearance.
#### Comments and Documentation
Use comments with `%%` within the Mermaid syntax to document the purpose of various parts of the diagram. This practice is invaluable for maintaining clarity, especially when working in teams or returning to a diagram after some time.
With these troubleshooting tips and best practices, you can effectively manage and resolve common issues in Mermaid block diagrams. The final section, 'Conclusion', will summarize the key points covered in this documentation and invite user feedback for continuous improvement.

View File

@ -218,7 +218,7 @@ UpdateRelStyle(customerA, bankA, $offsetY="60")
Person(customer, Customer, "A customer of the bank, with personal bank accounts", $tags="v1.0")
Container_Boundary(c1, "Internet Banking") {
Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to cutomers via their web browser")
Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to customers via their web browser")
Container_Ext(mobile_app, "Mobile App", "C#, Xamarin", "Provides a limited subset of the Internet banking functionality to customers via their mobile device")
Container(web_app, "Web Application", "Java, Spring MVC", "Delivers the static content and the Internet banking SPA")
ContainerDb(database, "Database", "SQL Database", "Stores user registration information, hashed auth credentials, access logs, etc.")

View File

@ -518,20 +518,22 @@ Styling of a sequence diagram is done by defining a number of css classes. Durin
### Classes used
| Class | Description |
| ------------ | ----------------------------------------------------------- |
| actor | Style for the actor box at the top of the diagram. |
| text.actor | Styles for text in the actor box at the top of the diagram. |
| actor-line | The vertical line for an actor. |
| messageLine0 | Styles for the solid message line. |
| messageLine1 | Styles for the dotted message line. |
| messageText | Defines styles for the text on the message arrows. |
| labelBox | Defines styles label to left in a loop. |
| labelText | Styles for the text in label for loops. |
| loopText | Styles for the text in the loop box. |
| loopLine | Defines styles for the lines in the loop box. |
| note | Styles for the note box. |
| noteText | Styles for the text on in the note boxes. |
| Class | Description |
| ------------ | -------------------------------------------------------------- |
| actor | Styles for the actor box. |
| actor-top | Styles for the actor figure/ box at the top of the diagram. |
| actor-bottom | Styles for the actor figure/ box at the bottom of the diagram. |
| text.actor | Styles for text in the actor box. |
| actor-line | The vertical line for an actor. |
| messageLine0 | Styles for the solid message line. |
| messageLine1 | Styles for the dotted message line. |
| messageText | Defines styles for the text on the message arrows. |
| labelBox | Defines styles label to left in a loop. |
| labelText | Styles for the text in label for loops. |
| loopText | Styles for the text in the loop box. |
| loopLine | Defines styles for the lines in the loop box. |
| note | Styles for the note box. |
| noteText | Styles for the text on in the note boxes. |
### Sample stylesheet

View File

@ -112,11 +112,11 @@ timeline
timeline
title MermaidChart 2023 Timeline
section 2023 Q1 <br> Release Personal Tier
Buttet 1 : sub-point 1a : sub-point 1b
Bullet 1 : sub-point 1a : sub-point 1b
: sub-point 1c
Bullet 2 : sub-point 2a : sub-point 2b
section 2023 Q2 <br> Release XYZ Tier
Buttet 3 : sub-point <br> 3a : sub-point 3b
Bullet 3 : sub-point <br> 3a : sub-point 3b
: sub-point 3c
Bullet 4 : sub-point 4a : sub-point 4b
```

View File

@ -460,7 +460,6 @@ const render = async function (
const a11yTitle: string | undefined = diag.db.getAccTitle?.();
const a11yDescr: string | undefined = diag.db.getAccDescription?.();
addA11yInfo(diagramType, svgNode, a11yTitle, a11yDescr);
// -------------------------------------------------------------------------------
// Clean up SVG code
root.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', XMLNS_XHTML_STD);

View File

@ -51,6 +51,7 @@ required:
- c4
- sankey
- packet
- block
properties:
theme:
description: |
@ -214,6 +215,8 @@ properties:
$ref: '#/$defs/SankeyDiagramConfig'
packet:
$ref: '#/$defs/PacketDiagramConfig'
block:
$ref: '#/$defs/BlockDiagramConfig'
dompurifyConfig:
title: DOM Purify Configuration
description: Configuration options to pass to the `dompurify` library.
@ -2049,6 +2052,16 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
minimum: 0
default: 5
BlockDiagramConfig:
title: Block Diagram Config
allOf: [{ $ref: '#/$defs/BaseDiagramConfig' }]
description: The object containing configurations specific for block diagrams.
type: object
unevaluatedProperties: false
properties:
padding:
default: 8
FontCalculator:
title: Font Calculator
description: |

View File

@ -28,6 +28,7 @@ import journey from './diagrams/user-journey/styles.js';
import timeline from './diagrams/timeline/styles.js';
import mindmap from './diagrams/mindmap/styles.js';
import packet from './diagrams/packet/styles.js';
import block from './diagrams/block/styles.js';
import themes from './themes/index.js';
async function checkValidStylisCSSStyleSheet(stylisString: string) {
@ -94,6 +95,7 @@ describe('styles', () => {
requirement,
sequence,
state,
block,
timeline,
packet,
})) {

View File

@ -925,3 +925,7 @@ export const encodeEntities = function (text: string): string {
export const decodeEntities = function (text: string): string {
return text.replace(/fl°°/g, '&#').replace(/fl°/g, '&').replace(/¶ß/g, ';');
};
export const isString = (value: unknown): value is string => {
return typeof value === 'string';
};

View File

@ -0,0 +1,20 @@
diff --git a/package.json b/package.json
index f2f77fa79c99382b079f4051ed51eafe8d2379c8..0bfddf55394e86f3a386eb7ab681369d410bae07 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,15 @@
"engines": {
"node": ">=0.10"
},
+ "exports": {
+ ".": {
+ "import": "./dist/cytoscape.umd.js",
+ "default": "./dist/cytoscape.cjs.js"
+ },
+ "./*": "./*"
+ },
"main": "dist/cytoscape.cjs.js",
+ "module": "dist/cytoscape.umd.js",
"unpkg": "dist/cytoscape.min.js",
"jsdelivr": "dist/cytoscape.min.js",
"scripts": {

File diff suppressed because it is too large Load Diff

11
run
View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
RUN="docker compose run --rm"
ansi() { echo -e "\e[${1}m${*:2}\e[0m"; }
@ -6,7 +6,7 @@ bold() { ansi 1 "$@"; }
# italic() { ansi 3 "$@"; }
underline() { ansi 4 "$@"; }
# strikethrough() { ansi 9 "$@"; }
# red() { ansi 31 "$@"; }
red() { ansi 31 "$@"; }
name=$(basename $0)
command=$1
@ -38,7 +38,7 @@ cypress)
$RUN cypress $args
;;
help)
help|"")
# Alignment of help message must be as it is, it will be nice looking when printed
usage=$(
@ -99,7 +99,10 @@ echo -n -e "$usage"
;;
*)
$name help
message="$(red Unknown command: $command). See $(bold ./$name help) for available commands."
echo -n -e "$message\n" >&2
$0 help
exit 1
;;
esac