Merge branch 'develop' into sidv/tinyMermaid

* develop: (52 commits)
  docs: Add quadrant point styling
  feat: Change precedence of styling
  chore(deps): update all minor dependencies
  chore(deps): update all patch dependencies
  fix: eslint ignore, type definition
  chore(deps): update all patch dependencies
  fix: Remove `ImperativeState` type restriction.
  📝🐛 fix schema link
  update latest news section
  Changes to rendering/gitGraph.spec.js - Added additional rendering test functionality for recognizing 'switch' as an alias to 'checkout'.
  1. Changes to gitGraph.jison - Updated the regex to allow either 'checkout' or 'switch' 2. Changes to gitGraphParser.spec.js - Additional test coverage added for the changes made to the parser. 3. Changes to gitGraphParserV2.spec.js - Additional test coverafe added for the changes made to the parser. 4. Changes to gitgraph.md - Updated documentation to let users know that checkout/switch can be used interchangeably.
  revert from and to type to object
  add eslint rule consistent-type-definations
  Update createText.ts
  chore(deps): update all patch dependencies
  revert lock file
  simplify message type from and to
  move types to separate file
  use interfaces instead of types
  feat: create utils func + test cases
  ...
This commit is contained in:
Sidharth Vinod 2024-04-16 12:34:42 +05:30
commit 70038e90ce
No known key found for this signature in database
GPG Key ID: FB5CCD378D3907CD
93 changed files with 2596 additions and 2762 deletions

View File

@ -1,11 +0,0 @@
dist/**
.github/**
docs/Setup.md
cypress.config.js
cypress/plugins/index.js
coverage
*.json
node_modules
# autogenereated by langium-cli
generated/

1
.eslintignore Symbolic link
View File

@ -0,0 +1 @@
.gitignore

View File

@ -53,6 +53,7 @@ module.exports = {
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/consistent-type-definitions': 'error',
'@typescript-eslint/ban-ts-comment': [
'error',
{

View File

@ -33,7 +33,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
config-file: ./.github/codeql/codeql-config.yml
languages: ${{ matrix.language }}
@ -45,7 +45,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@ -59,4 +59,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View File

@ -17,4 +17,4 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@v4
- name: 'Dependency Review'
uses: actions/dependency-review-action@v3
uses: actions/dependency-review-action@v4

View File

@ -106,7 +106,7 @@ jobs:
# These cached snapshots are downloaded, providing the reference snapshots.
- name: Cache snapshots
id: cache-snapshot
uses: actions/cache/restore@v3
uses: actions/cache/restore@v4
with:
path: ./cypress/snapshots
key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
@ -148,7 +148,7 @@ jobs:
CYPRESS_COMMIT: ${{ github.sha }}
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
# Run step only pushes to develop and pull_requests
if: ${{ steps.cypress.conclusion == 'success' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop')}}
with:
@ -185,7 +185,7 @@ jobs:
- name: Save snapshots cache
id: cache-upload
if: ${{ github.event_name == 'push' && needs.e2e.result != 'failure' }}
uses: actions/cache/save@v3
uses: actions/cache/save@v4
with:
path: ./snapshots
key: ${{ runner.os }}-snapshots-${{ github.event.after }}

View File

@ -29,7 +29,7 @@ jobs:
- uses: actions/checkout@v4
- name: Restore lychee cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: .lycheecache
key: cache-lychee-${{ github.sha }}

View File

@ -22,7 +22,7 @@ jobs:
pull-requests: write # write permission is required to label PRs
steps:
- name: Label PR
uses: release-drafter/release-drafter@v5
uses: release-drafter/release-drafter@v6
with:
config-name: pr-labeler.yml
disable-autolabeler: false

View File

@ -37,13 +37,13 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Setup Pages
uses: actions/configure-pages@v3
uses: actions/configure-pages@v4
- name: Run Build
run: pnpm --filter mermaid run docs:build:vitepress
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
uses: actions/upload-pages-artifact@v3
with:
path: packages/mermaid/src/vitepress/.vitepress/dist
@ -56,4 +56,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
uses: actions/deploy-pages@v4

View File

@ -16,7 +16,7 @@ jobs:
pull-requests: read # required to read PR titles/labels
steps:
- name: Draft Release
uses: release-drafter/release-drafter@v5
uses: release-drafter/release-drafter@v6
with:
disable-autolabeler: true
env:

View File

@ -39,7 +39,7 @@ jobs:
pnpm exec vitest run ./packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts --coverage
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
# Run step only pushes to develop and pull_requests
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }}
with:

View File

@ -19,7 +19,7 @@ jobs:
message: 'chore: update browsers list'
push: false
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
uses: peter-evans/create-pull-request@v6
with:
branch: update-browserslist
title: Update Browserslist

View File

@ -1 +1 @@
v20.11.1
20.12.2

View File

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

View File

@ -1458,5 +1458,79 @@ gitGraph TB:
{ gitGraph: { parallelCommits: true } }
);
});
it('73: should render a simple gitgraph with three branches and tagged merge commit using switch instead of checkout', () => {
imgSnapshotTest(
`gitGraph
commit id: "1"
commit id: "2"
branch nice_feature
switch nice_feature
commit id: "3"
switch main
commit id: "4"
switch nice_feature
branch very_nice_feature
switch very_nice_feature
commit id: "5"
switch main
commit id: "6"
switch nice_feature
commit id: "7"
switch main
merge nice_feature id: "12345" tag: "my merge commit"
switch very_nice_feature
commit id: "8"
switch main
commit id: "9"
`,
{}
);
});
it('74: should render commits for more than 8 branches using switch instead of checkout', () => {
imgSnapshotTest(
`
gitGraph
switch main
%% Make sure to manually set the ID of all commits, for consistent visual tests
commit id: "1-abcdefg"
switch main
branch branch1
commit id: "2-abcdefg"
switch main
merge branch1
branch branch2
commit id: "3-abcdefg"
switch main
merge branch2
branch branch3
commit id: "4-abcdefg"
switch main
merge branch3
branch branch4
commit id: "5-abcdefg"
switch main
merge branch4
branch branch5
commit id: "6-abcdefg"
switch main
merge branch5
branch branch6
commit id: "7-abcdefg"
switch main
merge branch6
branch branch7
commit id: "8-abcdefg"
switch main
merge branch7
branch branch8
commit id: "9-abcdefg"
switch main
merge branch8
branch branch9
commit id: "10-abcdefg"
`,
{}
);
});
});
});

View File

@ -1,4 +1,4 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
import { imgSnapshotTest } from '../../helpers/util.ts';
describe('Quadrant Chart', () => {
it('should render if only chart type is provided', () => {
@ -226,4 +226,52 @@ describe('Quadrant Chart', () => {
);
cy.get('svg');
});
it('it should render data points with styles', () => {
imgSnapshotTest(
`
quadrantChart
title Reach and engagement of campaigns
x-axis Reach -->
y-axis Engagement -->
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6] radius: 20
Campaign B: [0.45, 0.23] color: #ff0000
Campaign C: [0.57, 0.69] stroke-color: #ff00ff
Campaign D: [0.78, 0.34] stroke-width: 3px
Campaign E: [0.40, 0.34] radius: 20, color: #ff0000 , stroke-color : #ff00ff, stroke-width : 3px
Campaign F: [0.35, 0.78] stroke-width: 3px , color: #ff0000, radius: 20, stroke-color: #ff00ff
Campaign G: [0.22, 0.22] stroke-width: 3px , color: #309708 , radius : 20 , stroke-color: #5060ff
Campaign H: [0.22, 0.44]
`,
{}
);
cy.get('svg');
});
it('it should render data points with styles + classes', () => {
imgSnapshotTest(
`
quadrantChart
title Reach and engagement of campaigns
x-axis Reach -->
y-axis Engagement -->
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A:::class1: [0.3, 0.6] radius: 20
Campaign B: [0.45, 0.23] color: #ff0000
Campaign C: [0.57, 0.69] stroke-color: #ff00ff
Campaign D:::class2: [0.78, 0.34] stroke-width: 3px
Campaign E:::class2: [0.40, 0.34] radius: 20, color: #ff0000, stroke-color: #ff00ff, stroke-width: 3px
Campaign F:::class1: [0.35, 0.78]
classDef class1 color: #908342, radius : 10, stroke-color: #310085, stroke-width: 10px
classDef class2 color: #f00fff, radius : 10
`
);
});
});

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"

View File

@ -3,7 +3,7 @@
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -3,7 +3,7 @@
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -3,7 +3,7 @@
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -3,7 +3,7 @@
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -20,7 +20,7 @@ services:
- 9000:9000
- 3333:3333
cypress:
image: cypress/included:12.17.4
image: cypress/included:13.7.3
stdin_open: true
tty: true
working_dir: /mermaid

View File

@ -28,13 +28,23 @@ Thank you for being part of our story. Here's to creating, innovating, and colla
Knut Sveidqvist 🧜‍♂️✨
## Mermaid Chart's Visual Editor for Flowcharts
## Mermaid Chart's Visual Editor for Flowcharts and Sequence diagrams
The Mermaid Chart team is excited to introduce a new Visual Editor for flowcharts, enabling users of all skill levels to create diagrams easily and efficiently, with both GUI and code-based editing options.
The Mermaid Chart team is excited to introduce a new Visual Editor for Flowcharts and Sequence diagrams, enabling users of all skill levels to create diagrams easily and efficiently, with both GUI and code-based editing options.
Create flowchart nodes, connect them with edges, update shapes, change colors, and edit labels with just a few clicks that automatically reflect in your diagrams code for easy customizability.
Learn more:
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.
- Visual Editor For Flowcharts
- [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-releases-new-visual-editor-for-flowcharts)
- [Demo video](https://www.youtube.com/watch?v=5aja0gijoO0)
- Visual Editor For Sequence diagrams
- [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-unveils-visual-editor-for-sequence-diagrams)
- [Demo video](https://youtu.be/imc2u5_N6Dc)
## 📖 Blog posts

View File

@ -6,6 +6,18 @@
# Blog
## [Mermaid Chart Unveils Visual Editor for Sequence Diagrams](https://www.mermaidchart.com/blog/posts/mermaid-chart-unveils-visual-editor-for-sequence-diagrams/)
8 April 2024 · 5 mins
Sequence diagrams are excellent tools for communication and documentation.
## [Modeling system states: It starts with a Turing machine](https://www.mermaidchart.com/blog/posts/modeling-system-states/)
27 March 2024 · 12 mins
In computer science, there are a few fundamental papers that, without exaggeration, changed everything.
## [Mermaid Chart Raises $7.5M to Reinvent Visual Collaboration for Enterprises](https://www.mermaidchart.com/blog/posts/mermaid-chart-raises-7.5m-to-reinvent-visual-collaoration-for-enterprises/)
20 March 2024 · 4 mins

View File

@ -6,7 +6,7 @@
# Entity Relationship Diagrams
> An entityrelationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types). Wikipedia.
> An entityrelationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types) [Wikipedia](https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model).
Note that practitioners of ER modelling almost always refer to _entity types_ simply as _entities_. For example the `CUSTOMER` entity _type_ would be referred to simply as the `CUSTOMER` entity. This is so common it would be inadvisable to do anything else, but technically an entity is an abstract _instance_ of an entity type, and this is what an ER diagram shows - abstract instances, and the relationships between them. This is why entities are always named using singular nouns.
@ -111,7 +111,7 @@ Only the `first-entity` part of a statement is mandatory. This makes it possible
The `relationship` part of each statement can be broken down into three sub-components:
- the cardinality of the first entity with respect to the second,
- the cardinality of the first entity with respect to the second
- whether the relationship confers identity on a 'child' entity
- the cardinality of the second entity with respect to the first
@ -232,7 +232,7 @@ erDiagram
#### Attribute Keys and Comments
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`).. A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`). A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
```mermaid-example
erDiagram

View File

@ -1178,6 +1178,36 @@ Adding this snippet in the `<head>` would add support for Font Awesome v6.5.1
/>
```
### Custom icons
It is possible to use custom icons served from Font Awesome as long as the website imports the corresponding kit.
Note that this is currently a paid feature from Font Awesome.
For custom icons, you need to use the `fak` prefix.
**Example**
```
flowchart TD
B[fa:fa-twitter] %% standard icon
B-->E(fak:fa-custom-icon-name) %% custom icon
```
And trying to render it
```mermaid-example
flowchart TD
B["fa:fa-twitter for peace"]
B-->C["fab:fa-truck-bold a custom icon"]
```
```mermaid
flowchart TD
B["fa:fa-twitter for peace"]
B-->C["fab:fa-truck-bold a custom icon"]
```
## Graph declarations with spaces between vertices and link and without semicolon
- In graph declarations, the statements also can now end without a semicolon. After release 0.2.16, ending a graph statement with semicolon is just optional. So the below graph declaration is also valid along with the old declarations of the graph.

View File

@ -56,6 +56,8 @@ In Mermaid, we support the basic git operations like:
With the help of these key git commands, you will be able to draw a gitgraph in Mermaid very easily and quickly.
Entity names are often capitalized, although there is no accepted standard on this, and it is not required in Mermaid.
**NOTE**: `checkout` and `switch` can be used interchangeably.
## Syntax
Mermaid syntax for a gitgraph is very straight-forward and simple. It follows a declarative-approach, where each commit is drawn on the timeline in the diagram, in order of its occurrences/presence in code. Basically, it follows the insertion order for each command.

View File

@ -168,3 +168,86 @@ quadrantChart
quadrant-3 Delegate
quadrant-4 Delete
```
### Point styling
Points can either be styled directly or with defined shared classes
1. Direct styling
```md
Point A: [0.9, 0.0] radius: 12
Point B: [0.8, 0.1] color: #ff3300, radius: 10
Point C: [0.7, 0.2] radius: 25, color: #00ff33, stroke-color: #10f0f0
Point D: [0.6, 0.3] radius: 15, stroke-color: #00ff0f, stroke-width: 5px ,color: #ff33f0
```
2. Classes styling
```md
Point A:::class1: [0.9, 0.0]
Point B:::class2: [0.8, 0.1]
Point C:::class3: [0.7, 0.2]
Point D:::class3: [0.7, 0.2]
classDef class1 color: #109060
classDef class2 color: #908342, radius : 10, stroke-color: #310085, stroke-width: 10px
classDef class3 color: #f00fff, radius : 10
```
#### Available styles:
| Parameter | Description |
| ------------ | ---------------------------------------------------------------------- |
| color | Fill color of the point |
| radius | Radius of the point |
| stroke-width | Border width of the point |
| stroke-color | Border color of the point (useless when stroke-width is not specified) |
> **Note**
> Order of preference:
>
> 1. Direct styles
> 2. Class styles
> 3. Theme styles
## Example on styling
```mermaid-example
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.9, 0.0] radius: 12
Campaign B:::class1: [0.8, 0.1] color: #ff3300, radius: 10
Campaign C: [0.7, 0.2] radius: 25, color: #00ff33, stroke-color: #10f0f0
Campaign D: [0.6, 0.3] radius: 15, stroke-color: #00ff0f, stroke-width: 5px ,color: #ff33f0
Campaign E:::class2: [0.5, 0.4]
Campaign F:::class3: [0.4, 0.5] color: #0000ff
classDef class1 color: #109060
classDef class2 color: #908342, radius : 10, stroke-color: #310085, stroke-width: 10px
classDef class3 color: #f00fff, radius : 10
```
```mermaid
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.9, 0.0] radius: 12
Campaign B:::class1: [0.8, 0.1] color: #ff3300, radius: 10
Campaign C: [0.7, 0.2] radius: 25, color: #00ff33, stroke-color: #10f0f0
Campaign D: [0.6, 0.3] radius: 15, stroke-color: #00ff0f, stroke-width: 5px ,color: #ff33f0
Campaign E:::class2: [0.5, 0.4]
Campaign F:::class3: [0.4, 0.5] color: #0000ff
classDef class1 color: #109060
classDef class2 color: #908342, radius : 10, stroke-color: #310085, stroke-width: 10px
classDef class3 color: #f00fff, radius : 10
```

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.15.4",
"packageManager": "pnpm@8.15.7",
"keywords": [
"diagram",
"markdown",
@ -25,8 +25,8 @@
"dev:vite": "tsx .vite/server.ts",
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev:vite",
"release": "pnpm build",
"lint": "cross-env NODE_OPTIONS=--max_old_space_size=8192 eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .",
"lint:fix": "cross-env NODE_OPTIONS=--max_old_space_size=8192 eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && tsx scripts/fixCSpell.ts",
"lint": "cross-env NODE_OPTIONS=--max_old_space_size=8192 eslint --cache --cache-strategy content . && pnpm lint:jison && prettier --cache --check .",
"lint:fix": "cross-env NODE_OPTIONS=--max_old_space_size=8192 eslint --cache --cache-strategy content --fix . && prettier --write . && tsx scripts/fixCSpell.ts",
"lint:jison": "tsx ./scripts/jison/lint.mts",
"contributors": "tsx scripts/updateContributors.ts",
"cypress": "cypress run",

View File

@ -38,7 +38,7 @@
]
},
"dependencies": {
"@braintree/sanitize-url": "^6.0.4",
"@braintree/sanitize-url": "^7.0.0",
"d3": "^7.9.0",
"khroma": "^2.1.0"
},

View File

@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "11.0.0-alpha.6",
"version": "11.0.0-alpha.7",
"description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
"type": "module",
"module": "./dist/mermaid.core.mjs",
@ -87,7 +87,7 @@
"uuid": "^9.0.1"
},
"devDependencies": {
"@adobe/jsonschema2md": "^7.1.5",
"@adobe/jsonschema2md": "^8.0.0",
"@types/cytoscape": "^3.19.16",
"@types/d3": "^7.4.3",
"@types/d3-sankey": "^0.12.4",

View File

@ -3,6 +3,7 @@ import { log } from '../logger.js';
import { getConfig } from '../diagram-api/diagramAPI.js';
import { evaluate } from '../diagrams/common/common.js';
import { decodeEntities } from '../utils.js';
import { replaceIconSubstring } from '../rendering-util/createText.js';
/**
* @param dom
@ -59,10 +60,7 @@ const createLabel = (_vertexText, style, isTitle, isNode) => {
log.debug('vertexText' + vertexText);
const node = {
isNode,
label: decodeEntities(vertexText).replace(
/fa[blrs]?:fa-[\w-]+/g, // cspell: disable-line
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
label: replaceIconSubstring(decodeEntities(vertexText)),
labelStyle: style.replace('fill:', 'color:'),
};
let vertexNode = addHtmlLabel(node);

View File

@ -138,7 +138,7 @@ export interface ClassNote {
text: string;
}
export type ClassRelation = {
export interface ClassRelation {
id1: string;
id2: string;
relationTitle1: string;
@ -152,7 +152,7 @@ export type ClassRelation = {
type2: number;
lineType: number;
};
};
}
export interface NamespaceNode {
id: string;

View File

@ -9,6 +9,7 @@ import common, { evaluate, renderKatex } from '../common/common.js';
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import flowChartShapes from './flowChartShapes.js';
import { replaceIconSubstring } from '../../rendering-util/createText.js';
const conf = {};
export const setConf = function (cnf) {
@ -56,14 +57,9 @@ export const addVertices = async function (vert, g, svgId, root, _doc, diagObj)
let vertexNode;
if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const replacedVertexText = replaceIconSubstring(vertexText);
const node = {
label: await renderKatex(
vertexText.replace(
/fa[blrs]?:fa-[\w-]+/g, // cspell:disable-line
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
getConfig()
),
label: await renderKatex(replacedVertexText, getConfig()),
};
vertexNode = addHtmlLabel(svg, node).node();
vertexNode.parentNode.removeChild(vertexNode);
@ -242,13 +238,7 @@ export const addEdges = async function (edges, g, diagObj) {
edgeData.labelType = 'html';
edgeData.label = `<span id="L-${linkId}" class="edgeLabel L-${linkNameStart}' L-${linkNameEnd}" style="${
edgeData.labelStyle
}">${await renderKatex(
edge.text.replace(
/fa[blrs]?:fa-[\w-]+/g, // cspell:disable-line
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
getConfig()
)}</span>`;
}">${await renderKatex(replaceIconSubstring(edge.text), getConfig())}</span>`;
} else {
edgeData.labelType = 'text';
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');

View File

@ -88,6 +88,16 @@ describe('when parsing a gitGraph', function () {
expect(parser.yy.getCurrentBranch()).toBe('new');
});
it('should switch a branch', function () {
const str = 'gitGraph:\n' + 'branch new\n' + 'switch new\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(Object.keys(commits).length).toBe(0);
expect(parser.yy.getCurrentBranch()).toBe('new');
});
it('should add commits to checked out branch', function () {
const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n' + 'commit\n' + 'commit\n';

View File

@ -520,6 +520,78 @@ describe('when parsing a gitGraph', function () {
]);
});
it('should handle new branch switch', function () {
const str = `gitGraph:
commit
branch testBranch
switch testBranch
`;
parser.parse(str);
const commits = parser.yy.getCommits();
expect(Object.keys(commits).length).toBe(1);
expect(parser.yy.getCurrentBranch()).toBe('testBranch');
expect(parser.yy.getDirection()).toBe('LR');
expect(Object.keys(parser.yy.getBranches()).length).toBe(2);
});
it('should handle new branch switch & commit', function () {
const str = `gitGraph:
commit
branch testBranch
switch testBranch
commit
`;
parser.parse(str);
const commits = parser.yy.getCommits();
expect(Object.keys(commits).length).toBe(2);
expect(parser.yy.getCurrentBranch()).toBe('testBranch');
expect(parser.yy.getDirection()).toBe('LR');
expect(Object.keys(parser.yy.getBranches()).length).toBe(2);
const commit1 = Object.keys(commits)[0];
const commit2 = Object.keys(commits)[1];
expect(commits[commit1].branch).toBe('main');
expect(commits[commit1].parents).toStrictEqual([]);
expect(commits[commit2].branch).toBe('testBranch');
expect(commits[commit2].parents).toStrictEqual([commit1]);
});
it('should handle new branch switch & commit and merge', function () {
const str = `gitGraph:
commit
branch testBranch
switch testBranch
commit
commit
switch main
merge testBranch
`;
parser.parse(str);
const commits = parser.yy.getCommits();
expect(Object.keys(commits).length).toBe(4);
expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getDirection()).toBe('LR');
expect(Object.keys(parser.yy.getBranches()).length).toBe(2);
const commit1 = Object.keys(commits)[0];
const commit2 = Object.keys(commits)[1];
const commit3 = Object.keys(commits)[2];
const commit4 = Object.keys(commits)[3];
expect(commits[commit1].branch).toBe('main');
expect(commits[commit1].parents).toStrictEqual([]);
expect(commits[commit2].branch).toBe('testBranch');
expect(commits[commit2].parents).toStrictEqual([commits[commit1].id]);
expect(commits[commit3].branch).toBe('testBranch');
expect(commits[commit3].parents).toStrictEqual([commits[commit2].id]);
expect(commits[commit4].branch).toBe('main');
expect(commits[commit4].parents).toStrictEqual([commits[commit1].id, commits[commit3].id]);
expect(parser.yy.getBranchesAsObjArray()).toStrictEqual([
{ name: 'main' },
{ name: 'testBranch' },
]);
});
it('should handle merge tags', function () {
const str = `gitGraph:
commit

View File

@ -41,7 +41,7 @@ merge(?=\s|$) return 'MERGE';
cherry\-pick(?=\s|$) return 'CHERRY_PICK';
"parent:" return 'PARENT_COMMIT'
// "reset" return 'RESET';
checkout(?=\s|$) return 'CHECKOUT';
\b(checkout|switch)(?=\s|$) return 'CHECKOUT';
"LR" return 'DIR';
"TB" return 'DIR';
"BT" return 'DIR';

View File

@ -11,6 +11,7 @@
%x point_start
%x point_x
%x point_y
%x class_name
%%
\%\%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
@ -35,6 +36,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
" "*"quadrant-2"" "* return 'QUADRANT_2';
" "*"quadrant-3"" "* return 'QUADRANT_3';
" "*"quadrant-4"" "* return 'QUADRANT_4';
"classDef" return 'CLASSDEF';
["][`] { this.begin("md_string");}
<md_string>[^`"]+ { return "MD_STR";}
@ -43,6 +45,9 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
<string>["] this.popState();
<string>[^"]* return "STR";
\:\:\: {this.begin('class_name')}
<class_name>^\w+ {this.popState(); return 'class_name';}
\s*\:\s*\[\s* {this.begin("point_start"); return 'point_start';}
<point_start>(1)|(0(.\d+)?) {this.begin('point_x'); return 'point_x';}
<point_start>\s*\]" "* {this.popState();}
@ -75,6 +80,31 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
%% /* language grammar */
idStringToken : ALPHA | NUM | NODE_STRING | DOWN | MINUS | DEFAULT | COMMA | COLON | AMP | BRKT | MULT | UNICODE_TEXT;
styleComponent: ALPHA | NUM | NODE_STRING | COLON | UNIT | SPACE | BRKT | STYLE | PCT | MINUS ;
idString
:idStringToken
{$$=$idStringToken}
| idString idStringToken
{$$=$idString+''+$idStringToken}
;
style: styleComponent
|style styleComponent
{$$ = $style + $styleComponent;}
;
stylesOpt: style
{$$ = [$style.trim()]}
| stylesOpt COMMA style
{$stylesOpt.push($style.trim());$$ = $stylesOpt;}
;
classDefStatement
: CLASSDEF SPACE idString SPACE stylesOpt {$$ = $CLASSDEF;yy.addClass($idString,$stylesOpt);}
;
start
: eol start
| SPACE start
@ -92,6 +122,7 @@ line
statement
:
| classDefStatement {$$=[];}
| SPACE statement
| axisDetails
| quadrantDetails
@ -103,7 +134,10 @@ statement
;
points
: text point_start point_x point_y {yy.addPoint($1, $3, $4);}
: text point_start point_x point_y {yy.addPoint($1, "", $3, $4, []);}
| text class_name point_start point_x point_y {yy.addPoint($1, $2, $4, $5, []);}
| text point_start point_x point_y stylesOpt {yy.addPoint($1, "", $3, $4, $stylesOpt);}
| text class_name point_start point_x point_y stylesOpt {yy.addPoint($1, $2, $4, $5, $stylesOpt);}
;
axisDetails

View File

@ -212,20 +212,34 @@ describe('Testing quadrantChart jison file', () => {
it('should be able to parse points', () => {
let str = 'quadrantChart\npoint1: [0.1, 0.4]';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'point1', type: 'text' }, '0.1', '0.4');
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'point1', type: 'text' },
'',
'0.1',
'0.4',
[]
);
clearMocks();
str = 'QuadRantChart \n Point1 : [0.1, 0.4] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'Point1', type: 'text' }, '0.1', '0.4');
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Point1', type: 'text' },
'',
'0.1',
'0.4',
[]
);
clearMocks();
str = 'QuadRantChart \n "Point1 : (* +=[❤": [1, 0] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Point1 : (* +=[❤', type: 'text' },
'',
'1',
'0'
'0',
[]
);
clearMocks();
@ -264,15 +278,149 @@ describe('Testing quadrantChart jison file', () => {
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'Visionaries', type: 'text' });
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Microsoft', type: 'text' },
'',
'0.75',
'0.75'
'0.75',
[]
);
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Salesforce', type: 'text' },
'',
'0.55',
'0.60'
'0.60',
[]
);
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'IBM', type: 'text' },
'',
'0.51',
'0.40',
[]
);
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Incorta', type: 'text' },
'',
'0.20',
'0.30',
[]
);
});
it('should be able to parse the whole chart with point styling with all params or some params', () => {
const str = `quadrantChart
title Analytics and Business Intelligence Platforms
x-axis "Completeness of Vision ❤" --> "x-axis-2"
y-axis Ability to Execute --> "y-axis-2"
quadrant-1 Leaders
quadrant-2 Challengers
quadrant-3 Niche
quadrant-4 Visionaries
Microsoft: [0.75, 0.75] radius: 10
Salesforce: [0.55, 0.60] radius: 10, color: #ff0000
IBM: [0.51, 0.40] radius: 10, color: #ff0000, stroke-color: #ff00ff
Incorta: [0.20, 0.30] radius: 10 ,color: #ff0000 ,stroke-color: #ff00ff ,stroke-width: 10px`;
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
text: 'Completeness of Vision ❤',
type: 'text',
});
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({ text: 'x-axis-2', type: 'text' });
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({ text: 'y-axis-2', type: 'text' });
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({
text: 'Ability to Execute',
type: 'text',
});
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Leaders', type: 'text' });
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'Challengers', type: 'text' });
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'Niche', type: 'text' });
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'Visionaries', type: 'text' });
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Microsoft', type: 'text' },
'',
'0.75',
'0.75',
['radius: 10']
);
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Salesforce', type: 'text' },
'',
'0.55',
'0.60',
['radius: 10', 'color: #ff0000']
);
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'IBM', type: 'text' },
'',
'0.51',
'0.40',
['radius: 10', 'color: #ff0000', 'stroke-color: #ff00ff']
);
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Incorta', type: 'text' },
'',
'0.20',
'0.30',
['radius: 10', 'color: #ff0000', 'stroke-color: #ff00ff', 'stroke-width: 10px']
);
});
it('should be able to parse the whole chart with point styling with params in a random order + class names', () => {
const str = `quadrantChart
title Analytics and Business Intelligence Platforms
x-axis "Completeness of Vision ❤" --> "x-axis-2"
y-axis Ability to Execute --> "y-axis-2"
quadrant-1 Leaders
quadrant-2 Challengers
quadrant-3 Niche
quadrant-4 Visionaries
Microsoft: [0.75, 0.75] stroke-color: #ff00ff ,stroke-width: 10px, color: #ff0000, radius: 10
Salesforce:::class1: [0.55, 0.60] radius: 10, color: #ff0000
IBM: [0.51, 0.40] stroke-color: #ff00ff ,stroke-width: 10px
Incorta: [0.20, 0.30] stroke-width: 10px`;
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
text: 'Completeness of Vision ❤',
type: 'text',
});
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({ text: 'x-axis-2', type: 'text' });
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({ text: 'y-axis-2', type: 'text' });
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({
text: 'Ability to Execute',
type: 'text',
});
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Leaders', type: 'text' });
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'Challengers', type: 'text' });
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'Niche', type: 'text' });
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'Visionaries', type: 'text' });
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Microsoft', type: 'text' },
'',
'0.75',
'0.75',
['stroke-color: #ff00ff', 'stroke-width: 10px', 'color: #ff0000', 'radius: 10']
);
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Salesforce', type: 'text' },
'class1',
'0.55',
'0.60',
['radius: 10', 'color: #ff0000']
);
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'IBM', type: 'text' },
'',
'0.51',
'0.40',
['stroke-color: #ff00ff', 'stroke-width: 10px']
);
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Incorta', type: 'text' },
'',
'0.20',
'0.30',
['stroke-width: 10px']
);
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'IBM', type: 'text' }, '0.51', '0.40');
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'Incorta', type: 'text' }, '0.20', '0.30');
});
});

View File

@ -1,7 +1,7 @@
import { scaleLinear } from 'd3';
import { log } from '../../logger.js';
import type { BaseDiagramConfig, QuadrantChartConfig } from '../../config.type.js';
import defaultConfig from '../../defaultConfig.js';
import { log } from '../../logger.js';
import { getThemeVariables } from '../../themes/theme-default.js';
import type { Point } from '../../types.js';
@ -10,7 +10,15 @@ const defaultThemeVariables = getThemeVariables();
export type TextVerticalPos = 'left' | 'center' | 'right';
export type TextHorizontalPos = 'top' | 'middle' | 'bottom';
export interface QuadrantPointInputType extends Point {
export interface StylesObject {
className?: string;
radius?: number;
color?: string;
strokeColor?: string;
strokeWidth?: string;
}
export interface QuadrantPointInputType extends Point, StylesObject {
text: string;
}
@ -23,7 +31,9 @@ export interface QuadrantTextType extends Point {
rotation: number;
}
export interface QuadrantPointType extends Point {
export interface QuadrantPointType
extends Point,
Pick<StylesObject, 'strokeColor' | 'strokeWidth'> {
fill: string;
radius: number;
text: QuadrantTextType;
@ -117,6 +127,7 @@ export class QuadrantBuilder {
private config: QuadrantBuilderConfig;
private themeConfig: QuadrantBuilderThemeConfig;
private data: QuadrantBuilderData;
private classes: Record<string, StylesObject> = {};
constructor() {
this.config = this.getDefaultConfig();
@ -191,6 +202,7 @@ export class QuadrantBuilder {
this.config = this.getDefaultConfig();
this.themeConfig = this.getDefaultThemeConfig();
this.data = this.getDefaultData();
this.classes = {};
log.info('clear called');
}
@ -202,6 +214,10 @@ export class QuadrantBuilder {
this.data.points = [...points, ...this.data.points];
}
addClass(className: string, styles: StylesObject) {
this.classes[className] = styles;
}
setConfig(config: Partial<QuadrantBuilderConfig>) {
log.trace('setConfig called with: ', config);
this.config = { ...this.config, ...config };
@ -470,11 +486,15 @@ export class QuadrantBuilder {
.range([quadrantHeight + quadrantTop, quadrantTop]);
const points: QuadrantPointType[] = this.data.points.map((point) => {
const classStyles = this.classes[point.className as keyof typeof this.classes];
if (classStyles) {
point = { ...classStyles, ...point };
}
const props: QuadrantPointType = {
x: xAxis(point.x),
y: yAxis(point.y),
fill: this.themeConfig.quadrantPointFill,
radius: this.config.pointRadius,
fill: point.color || this.themeConfig.quadrantPointFill,
radius: point.radius || this.config.pointRadius,
text: {
text: point.text,
fill: this.themeConfig.quadrantPointTextFill,
@ -485,6 +505,8 @@ export class QuadrantBuilder {
fontSize: this.config.pointLabelFontSize,
rotation: 0,
},
strokeColor: point.strokeColor || this.themeConfig.quadrantPointFill,
strokeWidth: point.strokeWidth || '0px',
};
return props;
});

View File

@ -0,0 +1,50 @@
import quadrantDb from './quadrantDb.js';
describe('quadrant unit tests', () => {
it('should parse the styles array and return a StylesObject', () => {
const styles = ['radius: 10', 'color: #ff0000', 'stroke-color: #ff00ff', 'stroke-width: 10px'];
const result = quadrantDb.parseStyles(styles);
expect(result).toEqual({
radius: 10,
color: '#ff0000',
strokeColor: '#ff00ff',
strokeWidth: '10px',
});
});
it('should throw an error for non supported style name', () => {
const styles: string[] = ['test_name: value'];
expect(() => quadrantDb.parseStyles(styles)).toThrowError(
'style named test_name is not supported.'
);
});
it('should return an empty StylesObject for an empty input array', () => {
const styles: string[] = [];
const result = quadrantDb.parseStyles(styles);
expect(result).toEqual({});
});
it('should throw an error for non supported style value', () => {
let styles: string[] = ['radius: f'];
expect(() => quadrantDb.parseStyles(styles)).toThrowError(
'value for radius f is invalid, please use a valid number'
);
styles = ['color: ffaa'];
expect(() => quadrantDb.parseStyles(styles)).toThrowError(
'value for color ffaa is invalid, please use a valid hex code'
);
styles = ['stroke-color: #f677779'];
expect(() => quadrantDb.parseStyles(styles)).toThrowError(
'value for stroke-color #f677779 is invalid, please use a valid hex code'
);
styles = ['stroke-width: 30'];
expect(() => quadrantDb.parseStyles(styles)).toThrowError(
'value for stroke-width 30 is invalid, please use a valid number of pixels (eg. 10px)'
);
});
});

View File

@ -9,7 +9,14 @@ import {
setAccDescription,
clear as commonClear,
} from '../common/commonDb.js';
import type { StylesObject } from './quadrantBuilder.js';
import { QuadrantBuilder } from './quadrantBuilder.js';
import {
validateHexCode,
validateSizeInPixels,
validateNumber,
InvalidStyleError,
} from './utils.js';
const config = getConfig();
@ -17,7 +24,10 @@ function textSanitizer(text: string) {
return sanitizeText(text.trim(), config);
}
type LexTextObj = { text: string; type: 'text' | 'markdown' };
interface LexTextObj {
text: string;
type: 'text' | 'markdown';
}
const quadrantBuilder = new QuadrantBuilder();
@ -53,8 +63,52 @@ function setYAxisBottomText(textObj: LexTextObj) {
quadrantBuilder.setData({ yAxisBottomText: textSanitizer(textObj.text) });
}
function addPoint(textObj: LexTextObj, x: number, y: number) {
quadrantBuilder.addPoints([{ x, y, text: textSanitizer(textObj.text) }]);
function parseStyles(styles: string[]): StylesObject {
const stylesObject: StylesObject = {};
for (const style of styles) {
const [key, value] = style.trim().split(/\s*:\s*/);
if (key === 'radius') {
if (validateNumber(value)) {
throw new InvalidStyleError(key, value, 'number');
}
stylesObject.radius = parseInt(value);
} else if (key === 'color') {
if (validateHexCode(value)) {
throw new InvalidStyleError(key, value, 'hex code');
}
stylesObject.color = value;
} else if (key === 'stroke-color') {
if (validateHexCode(value)) {
throw new InvalidStyleError(key, value, 'hex code');
}
stylesObject.strokeColor = value;
} else if (key === 'stroke-width') {
if (validateSizeInPixels(value)) {
throw new InvalidStyleError(key, value, 'number of pixels (eg. 10px)');
}
stylesObject.strokeWidth = value;
} else {
throw new Error(`style named ${key} is not supported.`);
}
}
return stylesObject;
}
function addPoint(textObj: LexTextObj, className: string, x: number, y: number, styles: string[]) {
const stylesObject = parseStyles(styles);
quadrantBuilder.addPoints([
{
x,
y,
text: textSanitizer(textObj.text),
className,
...stylesObject,
},
]);
}
function addClass(className: string, styles: string[]) {
quadrantBuilder.addClass(className, parseStyles(styles));
}
function setWidth(width: number) {
@ -108,7 +162,9 @@ export default {
setXAxisRightText,
setYAxisTopText,
setYAxisBottomText,
parseStyles,
addPoint,
addClass,
getQuadrantData,
clear,
setAccTitle,

View File

@ -152,7 +152,9 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
.attr('cx', (data: QuadrantPointType) => data.x)
.attr('cy', (data: QuadrantPointType) => data.y)
.attr('r', (data: QuadrantPointType) => data.radius)
.attr('fill', (data: QuadrantPointType) => data.fill);
.attr('fill', (data: QuadrantPointType) => data.fill)
.attr('stroke', (data: QuadrantPointType) => data.strokeColor)
.attr('stroke-width', (data: QuadrantPointType) => data.strokeWidth);
dataPoints
.append('text')

View File

@ -0,0 +1,20 @@
class InvalidStyleError extends Error {
constructor(style: string, value: string, type: string) {
super(`value for ${style} ${value} is invalid, please use a valid ${type}`);
this.name = 'InvalidStyleError';
}
}
function validateHexCode(value: string): boolean {
return !/^#?([\dA-Fa-f]{6}|[\dA-Fa-f]{3})$/.test(value);
}
function validateNumber(value: string): boolean {
return !/^\d+$/.test(value);
}
function validateSizeInPixels(value: string): boolean {
return !/^\d+px$/.test(value);
}
export { validateHexCode, validateNumber, validateSizeInPixels, InvalidStyleError };

View File

@ -1,5 +1,6 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js';
import { ImperativeState } from '../../utils/imperativeState.js';
import { sanitizeText } from '../common/common.js';
import {
clear as commonClear,
@ -10,9 +11,24 @@ import {
setAccTitle,
setDiagramTitle,
} from '../common/commonDb.js';
import { ImperativeState } from '../../utils/imperativeState.js';
import type { Actor, AddMessageParams, Box, Message, Note } from './types.js';
const state = new ImperativeState(() => ({
interface SequenceState {
prevActor?: string;
actors: Record<string, Actor>;
createdActors: Record<string, number>;
destroyedActors: Record<string, number>;
boxes: Box[];
messages: Message[];
notes: Note[];
sequenceNumbersEnabled: boolean;
wrapEnabled?: boolean;
currentBox?: Box;
lastCreated?: Actor;
lastDestroyed?: Actor;
}
const state = new ImperativeState<SequenceState>(() => ({
prevActor: undefined,
actors: {},
createdActors: {},
@ -27,7 +43,7 @@ const state = new ImperativeState(() => ({
lastDestroyed: undefined,
}));
export const addBox = function (data) {
export const addBox = function (data: { text: string; color: string; wrap: boolean }) {
state.records.boxes.push({
name: data.text,
wrap: (data.wrap === undefined && autoWrap()) || !!data.wrap,
@ -37,20 +53,19 @@ export const addBox = function (data) {
state.records.currentBox = state.records.boxes.slice(-1)[0];
};
export const addActor = function (id, name, description, type) {
export const addActor = function (
id: string,
name: string,
description: { text: string; wrap?: boolean | null; type: string },
type: string
) {
let assignedBox = state.records.currentBox;
const old = state.records.actors[id];
if (old) {
// If already set and trying to set to a new one throw error
if (state.records.currentBox && old.box && state.records.currentBox !== old.box) {
throw new Error(
'A same participant should only be defined in one Box: ' +
old.name +
" can't be in '" +
old.box.name +
"' and in '" +
state.records.currentBox.name +
"' at the same time."
`A same participant should only be defined in one Box: ${old.name} can't be in '${old.box.name}' and in '${state.records.currentBox.name}' at the same time.`
);
}
@ -82,7 +97,7 @@ export const addActor = function (id, name, description, type) {
properties: {},
actorCnt: null,
rectData: null,
type: type || 'participant',
type: type ?? 'participant',
};
if (state.records.prevActor && state.records.actors[state.records.prevActor]) {
state.records.actors[state.records.prevActor].nextActor = id;
@ -94,19 +109,23 @@ export const addActor = function (id, name, description, type) {
state.records.prevActor = id;
};
const activationCount = (part) => {
const activationCount = (part: string) => {
let i;
let count = 0;
if (!part) {
return 0;
}
for (i = 0; i < state.records.messages.length; i++) {
if (
state.records.messages[i].type === LINETYPE.ACTIVE_START &&
state.records.messages[i].from.actor === part
state.records.messages[i].from?.actor === part
) {
count++;
}
if (
state.records.messages[i].type === LINETYPE.ACTIVE_END &&
state.records.messages[i].from.actor === part
state.records.messages[i].from?.actor === part
) {
count--;
}
@ -114,7 +133,12 @@ const activationCount = (part) => {
return count;
};
export const addMessage = function (idFrom, idTo, message, answer) {
export const addMessage = function (
idFrom: Message['from'],
idTo: Message['to'],
message: { text: string; wrap?: boolean },
answer: Message['answer']
) {
state.records.messages.push({
from: idFrom,
to: idTo,
@ -125,17 +149,21 @@ export const addMessage = function (idFrom, idTo, message, answer) {
};
export const addSignal = function (
idFrom,
idTo,
message = { text: undefined, wrap: undefined },
messageType,
activate = false
idFrom?: Message['from'],
idTo?: Message['to'],
message?: { text: string; wrap: boolean },
messageType?: number,
activate: boolean = false
) {
if (messageType === LINETYPE.ACTIVE_END) {
const cnt = activationCount(idFrom.actor);
const cnt = activationCount(idFrom?.actor || '');
if (cnt < 1) {
// Bail out as there is an activation signal from an inactive participant
let error = new Error('Trying to inactivate an inactive participant (' + idFrom.actor + ')');
const error = new Error(
'Trying to inactivate an inactive participant (' + idFrom?.actor + ')'
);
// @ts-ignore: we are passing hash param to the error object, however we should define our own custom error class to make it type safe
error.hash = {
text: '->>-',
token: '->>-',
@ -149,8 +177,8 @@ export const addSignal = function (
state.records.messages.push({
from: idFrom,
to: idTo,
message: message.text,
wrap: (message.wrap === undefined && autoWrap()) || !!message.wrap,
message: message?.text ?? '',
wrap: (message?.wrap === undefined && autoWrap()) || !!message?.wrap,
type: messageType,
activate,
});
@ -181,7 +209,7 @@ export const getCreatedActors = function () {
export const getDestroyedActors = function () {
return state.records.destroyedActors;
};
export const getActor = function (id) {
export const getActor = function (id: string) {
return state.records.actors[id];
};
export const getActorKeys = function () {
@ -195,7 +223,7 @@ export const disableSequenceNumbers = function () {
};
export const showSequenceNumbers = () => state.records.sequenceNumbersEnabled;
export const setWrap = function (wrapSetting) {
export const setWrap = function (wrapSetting?: boolean) {
state.records.wrapEnabled = wrapSetting;
};
@ -205,7 +233,7 @@ export const autoWrap = () => {
if (state.records.wrapEnabled !== undefined) {
return state.records.wrapEnabled;
}
return getConfig().sequence.wrap;
return getConfig()?.sequence?.wrap;
};
export const clear = function () {
@ -213,25 +241,25 @@ export const clear = function () {
commonClear();
};
export const parseMessage = function (str) {
const _str = str.trim();
export const parseMessage = function (str: string) {
const trimmedStr = str.trim();
const message = {
text: _str.replace(/^:?(?:no)?wrap:/, '').trim(),
text: trimmedStr.replace(/^:?(?:no)?wrap:/, '').trim(),
wrap:
_str.match(/^:?wrap:/) !== null
trimmedStr.match(/^:?wrap:/) !== null
? true
: _str.match(/^:?nowrap:/) !== null
: trimmedStr.match(/^:?nowrap:/) !== null
? false
: undefined,
};
log.debug('parseMessage:', message);
log.debug(`parseMessage: ${message}`);
return message;
};
// We expect the box statement to be color first then description
// The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled
// We extract first segment as color, the rest of the line is considered as text
export const parseBoxData = function (str) {
export const parseBoxData = function (str: string) {
const match = str.match(/^((?:rgba?|hsla?)\s*\(.*\)|\w*)(.*)$/);
let color = match != null && match[1] ? match[1].trim() : 'transparent';
let title = match != null && match[2] ? match[2].trim() : undefined;
@ -312,18 +340,21 @@ export const PLACEMENT = {
OVER: 2,
};
export const addNote = function (actor, placement, message) {
const note = {
export const addNote = function (
actor: { actor: string },
placement: Message['placement'],
message: { text: string; wrap?: boolean }
) {
const note: Note = {
actor: actor,
placement: placement,
message: message.text,
wrap: (message.wrap === undefined && autoWrap()) || !!message.wrap,
};
// Coerce actor into a [to, from, ...] array
//@ts-ignore: Coerce actor into a [to, from, ...] array
// eslint-disable-next-line unicorn/prefer-spread
const actors = [].concat(actor, actor);
state.records.notes.push(note);
state.records.messages.push({
from: actors[0],
@ -335,7 +366,7 @@ export const addNote = function (actor, placement, message) {
});
};
export const addLinks = function (actorId, text) {
export const addLinks = function (actorId: string, text: { text: string }) {
// find the actor
const actor = getActor(actorId);
// JSON.parse the text
@ -351,17 +382,17 @@ export const addLinks = function (actorId, text) {
}
};
export const addALink = function (actorId, text) {
export const addALink = function (actorId: string, text: { text: string }) {
// find the actor
const actor = getActor(actorId);
try {
const links = {};
const links: Record<string, string> = {};
let sanitizedText = sanitizeText(text.text, getConfig());
var sep = sanitizedText.indexOf('@');
const sep = sanitizedText.indexOf('@');
sanitizedText = sanitizedText.replace(/&amp;/g, '&');
sanitizedText = sanitizedText.replace(/&equals;/g, '=');
var label = sanitizedText.slice(0, sep - 1).trim();
var link = sanitizedText.slice(sep + 1).trim();
const label = sanitizedText.slice(0, sep - 1).trim();
const link = sanitizedText.slice(sep + 1).trim();
links[label] = link;
// add the deserialized text to the actor's links field.
@ -372,26 +403,26 @@ export const addALink = function (actorId, text) {
};
/**
* @param {any} actor
* @param {any} links
* @param actor - the actor to add the links to
* @param links - the links to add to the actor
*/
function insertLinks(actor, links) {
function insertLinks(actor: Actor, links: Record<string, string>) {
if (actor.links == null) {
actor.links = links;
} else {
for (let key in links) {
for (const key in links) {
actor.links[key] = links[key];
}
}
}
export const addProperties = function (actorId, text) {
export const addProperties = function (actorId: string, text: { text: string }) {
// find the actor
const actor = getActor(actorId);
// JSON.parse the text
try {
let sanitizedText = sanitizeText(text.text, getConfig());
const properties = JSON.parse(sanitizedText);
const sanitizedText = sanitizeText(text.text, getConfig());
const properties: Record<string, unknown> = JSON.parse(sanitizedText);
// add the deserialized text to the actor's property field.
insertProperties(actor, properties);
} catch (e) {
@ -400,30 +431,27 @@ export const addProperties = function (actorId, text) {
};
/**
* @param {any} actor
* @param {any} properties
* @param actor - the actor to add the properties to
* @param properties - the properties to add to the actor's properties
*/
function insertProperties(actor, properties) {
function insertProperties(actor: Actor, properties: Record<string, unknown>) {
if (actor.properties == null) {
actor.properties = properties;
} else {
for (let key in properties) {
for (const key in properties) {
actor.properties[key] = properties[key];
}
}
}
/**
*
*/
function boxEnd() {
state.records.currentBox = undefined;
}
export const addDetails = function (actorId, text) {
export const addDetails = function (actorId: string, text: { text: string }) {
// find the actor
const actor = getActor(actorId);
const elem = document.getElementById(text.text);
const elem = document.getElementById(text.text)!;
// JSON.parse the text
try {
@ -442,7 +470,7 @@ export const addDetails = function (actorId, text) {
}
};
export const getActorProperty = function (actor, key) {
export const getActorProperty = function (actor: Actor, key: string) {
if (actor !== undefined && actor.properties !== undefined) {
return actor.properties[key];
}
@ -450,20 +478,7 @@ export const getActorProperty = function (actor, key) {
return undefined;
};
/**
* @typedef {object} AddMessageParams A message from one actor to another.
* @property {string} from - The id of the actor sending the message.
* @property {string} to - The id of the actor receiving the message.
* @property {string} msg - The message text.
* @property {number} signalType - The type of signal.
* @property {"addMessage"} type - Set to `"addMessage"` if this is an `AddMessageParams`.
* @property {boolean} [activate] - If `true`, this signal starts an activation.
*/
/**
* @param {object | object[] | AddMessageParams} param - Object of parameters.
*/
export const apply = function (param) {
export const apply = function (param: any | AddMessageParams | AddMessageParams[]) {
if (Array.isArray(param)) {
param.forEach(function (item) {
apply(item);

View File

@ -1,6 +1,5 @@
import common, { calculateMathMLDimensions, hasKatex, renderKatex } from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon.js';
import { addFunction } from '../../interactionDb.js';
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
import { sanitizeUrl } from '@braintree/sanitize-url';
import * as configApi from '../../config.js';

View File

@ -0,0 +1,92 @@
export interface Box {
name: string;
wrap: boolean;
fill: string;
actorKeys: string[];
}
export interface Actor {
box?: Box;
name: string;
description: string;
wrap: boolean;
prevActor?: string;
nextActor?: string;
links: Record<string, unknown>;
properties: Record<string, unknown>;
actorCnt: number | null;
rectData: unknown;
type: string;
}
export interface Message {
from?: { actor: string };
to?: { actor: string };
message:
| string
| {
start: number;
step: number;
visible: boolean;
};
wrap: boolean;
answer?: unknown;
type?: number;
activate?: boolean;
placement?: string;
}
export interface AddMessageParams {
from: string;
to: string;
msg: string;
signalType: number;
type:
| 'addMessage'
| 'sequenceIndex'
| 'addParticipant'
| 'createParticipant'
| 'destroyParticipant'
| 'activeStart'
| 'activeEnd'
| 'addNote'
| 'addLinks'
| 'addALink'
| 'addProperties'
| 'addDetails'
| 'boxStart'
| 'boxEnd'
| 'loopStart'
| 'loopEnd'
| 'rectStart'
| 'rectEnd'
| 'optStart'
| 'optEnd'
| 'altStart'
| 'else'
| 'altEnd'
| 'setAccTitle'
| 'parStart'
| 'parAnd'
| 'parEnd'
| 'and'
| 'criticalStart'
| 'criticalOption'
| 'option'
| 'criticalEnd'
| 'breakStart'
| 'breakEnd'
| 'parOverStart'
| 'parOverEnd'
| 'parOverAnd'
| 'parOverEnd';
activate: boolean;
}
export interface Note {
actor: { actor: string };
placement: Message['placement'];
message: string;
wrap: boolean;
}

View File

@ -26,13 +26,23 @@ Thank you for being part of our story. Here's to creating, innovating, and colla
Knut Sveidqvist 🧜‍♂️✨
## Mermaid Chart's Visual Editor for Flowcharts
## Mermaid Chart's Visual Editor for Flowcharts and Sequence diagrams
The Mermaid Chart team is excited to introduce a new Visual Editor for flowcharts, enabling users of all skill levels to create diagrams easily and efficiently, with both GUI and code-based editing options.
The Mermaid Chart team is excited to introduce a new Visual Editor for Flowcharts and Sequence diagrams, enabling users of all skill levels to create diagrams easily and efficiently, with both GUI and code-based editing options.
Create flowchart nodes, connect them with edges, update shapes, change colors, and edit labels with just a few clicks that automatically reflect in your diagrams code for easy customizability.
Learn more:
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.
- Visual Editor For Flowcharts
- [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-releases-new-visual-editor-for-flowcharts)
- [Demo video](https://www.youtube.com/watch?v=5aja0gijoO0)
- Visual Editor For Sequence diagrams
- [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-unveils-visual-editor-for-sequence-diagrams)
- [Demo video](https://youtu.be/imc2u5_N6Dc)
## 📖 Blog posts

View File

@ -1,5 +1,17 @@
# Blog
## [Mermaid Chart Unveils Visual Editor for Sequence Diagrams](https://www.mermaidchart.com/blog/posts/mermaid-chart-unveils-visual-editor-for-sequence-diagrams/)
8 April 2024 · 5 mins
Sequence diagrams are excellent tools for communication and documentation.
## [Modeling system states: It starts with a Turing machine](https://www.mermaidchart.com/blog/posts/modeling-system-states/)
27 March 2024 · 12 mins
In computer science, there are a few fundamental papers that, without exaggeration, changed everything.
## [Mermaid Chart Raises $7.5M to Reinvent Visual Collaboration for Enterprises](https://www.mermaidchart.com/blog/posts/mermaid-chart-raises-7.5m-to-reinvent-visual-collaoration-for-enterprises/)
20 March 2024 · 4 mins

View File

@ -15,7 +15,7 @@
"fetch-contributors": "tsx .vitepress/scripts/fetch-contributors.ts"
},
"dependencies": {
"@mdi/font": "^6.9.96",
"@mdi/font": "^7.0.0",
"@vueuse/core": "^10.9.0",
"font-awesome": "^4.7.0",
"jiti": "^1.21.0",
@ -24,17 +24,17 @@
},
"devDependencies": {
"@iconify-json/carbon": "^1.1.31",
"@unocss/reset": "^0.58.6",
"@unocss/reset": "^0.59.0",
"@vite-pwa/vitepress": "^0.4.0",
"@vitejs/plugin-vue": "^4.6.2",
"@vitejs/plugin-vue": "^5.0.0",
"fast-glob": "^3.3.2",
"https-localhost": "^4.7.1",
"pathe": "^1.1.2",
"unocss": "^0.58.6",
"unocss": "^0.59.0",
"unplugin-vue-components": "^0.26.0",
"vite": "^4.5.2",
"vite": "^5.0.0",
"vite-plugin-pwa": "^0.19.7",
"vitepress": "1.0.0-rc.45",
"vitepress": "1.1.0",
"workbox-window": "^7.0.0"
}
}

View File

@ -1,6 +1,6 @@
# Entity Relationship Diagrams
> An entityrelationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types). Wikipedia.
> An entityrelationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types) [Wikipedia](https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model).
Note that practitioners of ER modelling almost always refer to _entity types_ simply as _entities_. For example the `CUSTOMER` entity _type_ would be referred to simply as the `CUSTOMER` entity. This is so common it would be inadvisable to do anything else, but technically an entity is an abstract _instance_ of an entity type, and this is what an ER diagram shows - abstract instances, and the relationships between them. This is why entities are always named using singular nouns.
@ -75,7 +75,7 @@ Only the `first-entity` part of a statement is mandatory. This makes it possible
The `relationship` part of each statement can be broken down into three sub-components:
- the cardinality of the first entity with respect to the second,
- the cardinality of the first entity with respect to the second
- whether the relationship confers identity on a 'child' entity
- the cardinality of the second entity with respect to the first
@ -162,7 +162,7 @@ erDiagram
#### Attribute Keys and Comments
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`).. A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`). A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
```mermaid-example
erDiagram

View File

@ -799,6 +799,30 @@ Adding this snippet in the `<head>` would add support for Font Awesome v6.5.1
/>
```
### Custom icons
It is possible to use custom icons served from Font Awesome as long as the website imports the corresponding kit.
Note that this is currently a paid feature from Font Awesome.
For custom icons, you need to use the `fak` prefix.
**Example**
```
flowchart TD
B[fa:fa-twitter] %% standard icon
B-->E(fak:fa-custom-icon-name) %% custom icon
```
And trying to render it
```mermaid-example
flowchart TD
B["fa:fa-twitter for peace"]
B-->C["fab:fa-truck-bold a custom icon"]
```
## Graph declarations with spaces between vertices and link and without semicolon
- In graph declarations, the statements also can now end without a semicolon. After release 0.2.16, ending a graph statement with semicolon is just optional. So the below graph declaration is also valid along with the old declarations of the graph.

View File

@ -33,6 +33,8 @@ In Mermaid, we support the basic git operations like:
With the help of these key git commands, you will be able to draw a gitgraph in Mermaid very easily and quickly.
Entity names are often capitalized, although there is no accepted standard on this, and it is not required in Mermaid.
**NOTE**: `checkout` and `switch` can be used interchangeably.
## Syntax
Mermaid syntax for a gitgraph is very straight-forward and simple. It follows a declarative-approach, where each commit is drawn on the timeline in the diagram, in order of its occurrences/presence in code. Basically, it follows the insertion order for each command.

View File

@ -136,3 +136,66 @@ quadrantChart
quadrant-3 Delegate
quadrant-4 Delete
```
### Point styling
Points can either be styled directly or with defined shared classes
1. Direct styling
```md
Point A: [0.9, 0.0] radius: 12
Point B: [0.8, 0.1] color: #ff3300, radius: 10
Point C: [0.7, 0.2] radius: 25, color: #00ff33, stroke-color: #10f0f0
Point D: [0.6, 0.3] radius: 15, stroke-color: #00ff0f, stroke-width: 5px ,color: #ff33f0
```
2. Classes styling
```md
Point A:::class1: [0.9, 0.0]
Point B:::class2: [0.8, 0.1]
Point C:::class3: [0.7, 0.2]
Point D:::class3: [0.7, 0.2]
classDef class1 color: #109060
classDef class2 color: #908342, radius : 10, stroke-color: #310085, stroke-width: 10px
classDef class3 color: #f00fff, radius : 10
```
#### Available styles:
| Parameter | Description |
| ------------ | ---------------------------------------------------------------------- |
| color | Fill color of the point |
| radius | Radius of the point |
| stroke-width | Border width of the point |
| stroke-color | Border color of the point (useless when stroke-width is not specified) |
```note
Order of preference:
1. Direct styles
2. Class styles
3. Theme styles
```
## Example on styling
```mermaid-example
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.9, 0.0] radius: 12
Campaign B:::class1: [0.8, 0.1] color: #ff3300, radius: 10
Campaign C: [0.7, 0.2] radius: 25, color: #00ff33, stroke-color: #10f0f0
Campaign D: [0.6, 0.3] radius: 15, stroke-color: #00ff0f, stroke-width: 5px ,color: #ff33f0
Campaign E:::class2: [0.5, 0.4]
Campaign F:::class3: [0.4, 0.5] color: #0000ff
classDef class1 color: #109060
classDef class2 color: #908342, radius : 10, stroke-color: #310085, stroke-width: 10px
classDef class3 color: #f00fff, radius : 10
```

View File

@ -0,0 +1,34 @@
import { describe, it, expect } from 'vitest';
import { replaceIconSubstring } from './createText.js';
describe('replaceIconSubstring', () => {
it('converts FontAwesome icon notations to HTML tags', () => {
const input = 'This is an icon: fa:fa-user and fab:fa-github';
const output = replaceIconSubstring(input);
const expected =
"This is an icon: <i class='fa fa-user'></i> and <i class='fab fa-github'></i>";
expect(output).toEqual(expected);
});
it('handles strings without FontAwesome icon notations', () => {
const input = 'This string has no icons';
const output = replaceIconSubstring(input);
expect(output).toEqual(input); // No change expected
});
it('correctly processes multiple FontAwesome icon notations in one string', () => {
const input = 'Icons galore: fa:fa-arrow-right, fak:fa-truck, fas:fa-home';
const output = replaceIconSubstring(input);
const expected =
"Icons galore: <i class='fa fa-arrow-right'></i>, <i class='fak fa-truck'></i>, <i class='fas fa-home'></i>";
expect(output).toEqual(expected);
});
it('correctly replaces a very long icon name with the fak prefix', () => {
const input = 'Here is a long icon: fak:fa-truck-driving-long-winding-road in use';
const output = replaceIconSubstring(input);
const expected =
"Here is a long icon: <i class='fak fa-truck-driving-long-winding-road'></i> in use";
expect(output).toEqual(expected);
});
});

View File

@ -168,6 +168,19 @@ function updateTextContentAndStyles(tspan: any, wrappedLine: MarkdownWord[]) {
});
}
/**
* Convert fontawesome labels into fontawesome icons by using a regex pattern
* @param text - The raw string to convert
* @returns string with fontawesome icons as i tags
*/
export function replaceIconSubstring(text: string) {
// The letters 'bklrs' stand for possible endings of the fontawesome prefix (e.g. 'fab' for brands, 'fak' for fa-kit) // cspell: disable-line
return text.replace(
/fa[bklrs]?:fa-[\w-]+/g, // cspell: disable-line
(s) => `<i class='${s.replace(':', ' ')}'></i>`
);
}
// Note when using from flowcharts converting the API isNode means classes should be set accordingly. When using htmlLabels => to sett classes to'nodeLabel' when isNode=true otherwise 'edgeLabel'
// When not using htmlLabels => to set classes to 'title-row' when isTitle=true otherwise 'title-row'
export const createText = (
@ -189,12 +202,10 @@ export const createText = (
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const htmlText = markdownToHTML(text, config);
const decodedReplacedText = replaceIconSubstring(decodeEntities(htmlText));
const node = {
isNode,
label: decodeEntities(htmlText).replace(
/fa[blrs]?:fa-[\w-]+/g, // cspell: disable-line
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
label: decodedReplacedText,
labelStyle: style.replace('fill:', 'color:'),
};
const vertexNode = addHtmlSpan(el, node, width, classes, addSvgBackground);

View File

@ -24,7 +24,7 @@
# you may need to add it to `.build/jsonSchema.ts`, `src/docs.mts`
# and `scripts/create-types-from-json-schema.mjs`
# to get the default values/docs/types to generate properly.
$id: https://mermaid-js.github.io/schemas/config.schema.json
$id: https://mermaid.js.org/schemas/config.schema.json
$schema: https://json-schema.org/draft/2019-09/schema
title: Mermaid Config
type: object

View File

@ -2,11 +2,11 @@
* Resettable state storage.
* @example
* ```
* const state = new ImperativeState(() => {
* const state = new ImperativeState(() => ({
* foo: undefined as string | undefined,
* bar: [] as number[],
* baz: 1 as number | undefined,
* });
* }));
*
* state.records.foo = "hi";
* console.log(state.records.foo); // prints "hi";
@ -21,7 +21,7 @@
* // }
* ```
*/
export class ImperativeState<S extends Record<string, unknown>> {
export class ImperativeState<S> {
public records: S;
/**

View File

@ -19,12 +19,12 @@ import { InfoTokenBuilder } from './tokenBuilder.js';
/**
* Declaration of `Info` services.
*/
type InfoAddedServices = {
interface InfoAddedServices {
parser: {
TokenBuilder: InfoTokenBuilder;
ValueConverter: CommonValueConverter;
};
};
}
/**
* Union of Langium default services and `Info` services.

View File

@ -19,12 +19,12 @@ import { PacketTokenBuilder } from './tokenBuilder.js';
/**
* Declaration of `Packet` services.
*/
type PacketAddedServices = {
interface PacketAddedServices {
parser: {
TokenBuilder: PacketTokenBuilder;
ValueConverter: CommonValueConverter;
};
};
}
/**
* Union of Langium default services and `Packet` services.

View File

@ -19,12 +19,12 @@ import { PieValueConverter } from './valueConverter.js';
/**
* Declaration of `Pie` services.
*/
type PieAddedServices = {
interface PieAddedServices {
parser: {
TokenBuilder: PieTokenBuilder;
ValueConverter: PieValueConverter;
};
};
}
/**
* Union of Langium default services and `Pie` services.

File diff suppressed because it is too large Load Diff