mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-21 06:53:17 +08:00
Merge branch 'develop' into fix/pie-chart-viewbox
This commit is contained in:
commit
bb16e50233
@ -38,6 +38,10 @@ module.exports = {
|
|||||||
'lodash',
|
'lodash',
|
||||||
'unicorn',
|
'unicorn',
|
||||||
],
|
],
|
||||||
|
ignorePatterns: [
|
||||||
|
// this file is automatically generated by `pnpm run --filter mermaid types:build-config`
|
||||||
|
'packages/mermaid/src/config.type.ts',
|
||||||
|
],
|
||||||
rules: {
|
rules: {
|
||||||
curly: 'error',
|
curly: 'error',
|
||||||
'no-console': 'error',
|
'no-console': 'error',
|
||||||
@ -123,6 +127,14 @@ module.exports = {
|
|||||||
files: ['*.{ts,tsx}'],
|
files: ['*.{ts,tsx}'],
|
||||||
plugins: ['tsdoc'],
|
plugins: ['tsdoc'],
|
||||||
rules: {
|
rules: {
|
||||||
|
'no-restricted-syntax': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
selector: 'TSEnumDeclaration',
|
||||||
|
message:
|
||||||
|
'Prefer using TypeScript union types over TypeScript enum, since TypeScript enums have a bunch of issues, see https://dev.to/dvddpl/whats-the-problem-with-typescript-enums-2okj',
|
||||||
|
},
|
||||||
|
],
|
||||||
'tsdoc/syntax': 'error',
|
'tsdoc/syntax': 'error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
11
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
11
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -53,8 +53,17 @@ body:
|
|||||||
Please fill out the info below.
|
Please fill out the info below.
|
||||||
Note that you only need to fill out the relevant section
|
Note that you only need to fill out the relevant section
|
||||||
value: |-
|
value: |-
|
||||||
- Mermaid version:
|
- Mermaid version:
|
||||||
- Browser and Version: [Chrome, Edge, Firefox]
|
- Browser and Version: [Chrome, Edge, Firefox]
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Suggested Solutions
|
||||||
|
description: >
|
||||||
|
If applicable, suggest solutions that could resolve the bug.
|
||||||
|
It would help maintainers/contributors to not waste time looking for the solution. Even pointing the line causing the bug would be great!
|
||||||
|
placeholder: |-
|
||||||
|
- Variable `parser` in file <filepath> is not initialised ...
|
||||||
|
- Add a new type for ...
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Additional Context
|
label: Additional Context
|
||||||
|
11
codecov.yaml → .github/codecov.yaml
vendored
11
codecov.yaml → .github/codecov.yaml
vendored
@ -1,6 +1,17 @@
|
|||||||
|
codecov:
|
||||||
|
branch: develop
|
||||||
|
|
||||||
comment:
|
comment:
|
||||||
layout: 'reach, diff, flags, files'
|
layout: 'reach, diff, flags, files'
|
||||||
behavior: default
|
behavior: default
|
||||||
require_changes: false # if true: only post the comment if coverage changes
|
require_changes: false # if true: only post the comment if coverage changes
|
||||||
require_base: no # [yes :: must have a base report to post]
|
require_base: no # [yes :: must have a base report to post]
|
||||||
require_head: yes # [yes :: must have a head report to post]
|
require_head: yes # [yes :: must have a head report to post]
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
off
|
||||||
|
# Turing off for now as code coverage isn't stable and causes unnecessary build failures.
|
||||||
|
# default:
|
||||||
|
# threshold: 2%
|
4
.github/workflows/build-docs.yml
vendored
4
.github/workflows/build-docs.yml
vendored
@ -2,13 +2,13 @@ name: Build Vitepress docs
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
merge_group:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Build job
|
build-docs:
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -2,6 +2,7 @@ name: Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push: {}
|
push: {}
|
||||||
|
merge_group:
|
||||||
pull_request:
|
pull_request:
|
||||||
types:
|
types:
|
||||||
- opened
|
- opened
|
||||||
@ -12,7 +13,7 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build-mermaid:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
2
.github/workflows/check-readme-in-sync.yml
vendored
2
.github/workflows/check-readme-in-sync.yml
vendored
@ -14,7 +14,7 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check-readme:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
|
7
.github/workflows/checks.yml
vendored
7
.github/workflows/checks.yml
vendored
@ -1,15 +1,16 @@
|
|||||||
on:
|
on:
|
||||||
push: {}
|
push:
|
||||||
|
merge_group:
|
||||||
pull_request:
|
pull_request:
|
||||||
types:
|
types:
|
||||||
- opened
|
- opened
|
||||||
- synchronize
|
- synchronize
|
||||||
- ready_for_review
|
- ready_for_review
|
||||||
|
|
||||||
name: Static analysis
|
name: Static analysis on Test files
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
check-tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: check tests
|
name: check tests
|
||||||
if: github.repository_owner == 'mermaid-js'
|
if: github.repository_owner == 'mermaid-js'
|
||||||
|
2
.github/workflows/e2e-applitools.yml
vendored
2
.github/workflows/e2e-applitools.yml
vendored
@ -19,7 +19,7 @@ env:
|
|||||||
USE_APPLI: ${{ secrets.APPLITOOLS_API_KEY && 'true' || '' }}
|
USE_APPLI: ${{ secrets.APPLITOOLS_API_KEY && 'true' || '' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
e2e-applitools:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
13
.github/workflows/e2e.yml
vendored
13
.github/workflows/e2e.yml
vendored
@ -1,12 +1,15 @@
|
|||||||
name: E2E
|
name: E2E
|
||||||
|
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
merge_group:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
e2e:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -44,13 +47,15 @@ jobs:
|
|||||||
VITEST_COVERAGE: true
|
VITEST_COVERAGE: true
|
||||||
- name: Upload Coverage to Codecov
|
- name: Upload Coverage to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
if: steps.cypress.conclusion == 'success'
|
# 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:
|
with:
|
||||||
files: coverage/cypress/lcov.info
|
files: coverage/cypress/lcov.info
|
||||||
flags: e2e
|
flags: e2e
|
||||||
name: mermaid-codecov
|
name: mermaid-codecov
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: false
|
||||||
verbose: true
|
verbose: true
|
||||||
|
token: 6845cc80-77ee-4e17-85a1-026cd95e0766
|
||||||
- name: Upload Artifacts
|
- name: Upload Artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
if: ${{ failure() && steps.cypress.conclusion == 'failure' }}
|
if: ${{ failure() && steps.cypress.conclusion == 'failure' }}
|
||||||
|
16
.github/workflows/lint.yml
vendored
16
.github/workflows/lint.yml
vendored
@ -1,7 +1,8 @@
|
|||||||
name: Lint
|
name: Lint
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push: {}
|
push:
|
||||||
|
merge_group:
|
||||||
pull_request:
|
pull_request:
|
||||||
types:
|
types:
|
||||||
- opened
|
- opened
|
||||||
@ -52,6 +53,19 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Verify `./src/config.type.ts` is in sync with `./src/schemas/config.schema.yaml`
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if ! pnpm run --filter mermaid types:verify-config; then
|
||||||
|
ERROR_MESSAGE='Running `pnpm run --filter mermaid types:verify-config` failed.'
|
||||||
|
ERROR_MESSAGE+=' This should be fixed by running'
|
||||||
|
ERROR_MESSAGE+=' `pnpm run --filter mermaid types:build-config`'
|
||||||
|
ERROR_MESSAGE+=' on your local machine.'
|
||||||
|
echo "::error title=Lint failure::${ERROR_MESSAGE}"
|
||||||
|
# make sure to return an error exitcode so that GitHub actions shows a red-cross
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Verify Docs
|
- name: Verify Docs
|
||||||
id: verifyDocs
|
id: verifyDocs
|
||||||
working-directory: ./packages/mermaid
|
working-directory: ./packages/mermaid
|
||||||
|
6
.github/workflows/publish-docs.yml
vendored
6
.github/workflows/publish-docs.yml
vendored
@ -19,7 +19,7 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Build job
|
# Build job
|
||||||
build:
|
build-docs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@ -48,11 +48,11 @@ jobs:
|
|||||||
path: packages/mermaid/src/vitepress/.vitepress/dist
|
path: packages/mermaid/src/vitepress/.vitepress/dist
|
||||||
|
|
||||||
# Deployment job
|
# Deployment job
|
||||||
deploy:
|
deploy-docs:
|
||||||
environment:
|
environment:
|
||||||
name: github-pages
|
name: github-pages
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
needs: build-docs
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
|
@ -6,7 +6,7 @@ on:
|
|||||||
- 'release/**'
|
- 'release/**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish-preview:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
15
.github/workflows/test.yml
vendored
15
.github/workflows/test.yml
vendored
@ -1,12 +1,12 @@
|
|||||||
name: Unit Tests
|
name: Unit Tests
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request, merge_group]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
unit-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@ -43,15 +43,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Coverage to Codecov
|
- name: Upload Coverage to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
|
# Run step only pushes to develop and pull_requests
|
||||||
|
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }}
|
||||||
with:
|
with:
|
||||||
files: ./coverage/vitest/lcov.info
|
files: ./coverage/vitest/lcov.info
|
||||||
flags: unit
|
flags: unit
|
||||||
name: mermaid-codecov
|
name: mermaid-codecov
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: false
|
||||||
verbose: true
|
verbose: true
|
||||||
# Coveralls is throwing 500. Disabled for now.
|
token: 6845cc80-77ee-4e17-85a1-026cd95e0766
|
||||||
# - name: Upload Coverage to Coveralls
|
|
||||||
# uses: coverallsapp/github-action@v2
|
|
||||||
# with:
|
|
||||||
# github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
# flag-name: unit
|
|
||||||
|
2
.github/workflows/update-browserlist.yml
vendored
2
.github/workflows/update-browserlist.yml
vendored
@ -5,7 +5,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
update-browser-list:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
@ -5,4 +5,8 @@ coverage
|
|||||||
# Autogenerated by PNPM
|
# Autogenerated by PNPM
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
stats
|
stats
|
||||||
packages/mermaid/src/docs/.vitepress/components.d.ts
|
**/.vitepress/components.d.ts
|
||||||
|
**/.vitepress/cache
|
||||||
|
.nyc_output
|
||||||
|
# Autogenerated by `pnpm run --filter mermaid types:build-config`
|
||||||
|
packages/mermaid/src/config.type.ts
|
||||||
|
@ -2,6 +2,7 @@ import { build, InlineConfig, type PluginOption } from 'vite';
|
|||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import jisonPlugin from './jisonPlugin.js';
|
import jisonPlugin from './jisonPlugin.js';
|
||||||
|
import jsonSchemaPlugin from './jsonSchemaPlugin.js';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import typescript from '@rollup/plugin-typescript';
|
import typescript from '@rollup/plugin-typescript';
|
||||||
import { visualizer } from 'rollup-plugin-visualizer';
|
import { visualizer } from 'rollup-plugin-visualizer';
|
||||||
@ -121,6 +122,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
jisonPlugin(),
|
jisonPlugin(),
|
||||||
|
jsonSchemaPlugin(), // handles `.schema.yaml` files
|
||||||
// @ts-expect-error According to the type definitions, rollup plugins are incompatible with vite
|
// @ts-expect-error According to the type definitions, rollup plugins are incompatible with vite
|
||||||
typescript({ compilerOptions: { declaration: false } }),
|
typescript({ compilerOptions: { declaration: false } }),
|
||||||
istanbul({
|
istanbul({
|
||||||
|
150
.vite/jsonSchemaPlugin.ts
Normal file
150
.vite/jsonSchemaPlugin.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { load, JSON_SCHEMA } from 'js-yaml';
|
||||||
|
import assert from 'node:assert';
|
||||||
|
import Ajv2019, { type JSONSchemaType } from 'ajv/dist/2019.js';
|
||||||
|
import { PluginOption } from 'vite';
|
||||||
|
|
||||||
|
import type { MermaidConfig, BaseDiagramConfig } from '../packages/mermaid/src/config.type.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All of the keys in the mermaid config that have a mermaid diagram config.
|
||||||
|
*/
|
||||||
|
const MERMAID_CONFIG_DIAGRAM_KEYS = [
|
||||||
|
'flowchart',
|
||||||
|
'sequence',
|
||||||
|
'gantt',
|
||||||
|
'journey',
|
||||||
|
'class',
|
||||||
|
'state',
|
||||||
|
'er',
|
||||||
|
'pie',
|
||||||
|
'quadrantChart',
|
||||||
|
'requirement',
|
||||||
|
'mindmap',
|
||||||
|
'timeline',
|
||||||
|
'gitGraph',
|
||||||
|
'c4',
|
||||||
|
'sankey',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate default values from the JSON Schema.
|
||||||
|
*
|
||||||
|
* AJV does not support nested default values yet (or default values with $ref),
|
||||||
|
* so we need to manually find them (this may be fixed in ajv v9).
|
||||||
|
*
|
||||||
|
* @param mermaidConfigSchema - The Mermaid JSON Schema to use.
|
||||||
|
* @returns The default mermaid config object.
|
||||||
|
*/
|
||||||
|
function generateDefaults(mermaidConfigSchema: JSONSchemaType<MermaidConfig>) {
|
||||||
|
const ajv = new Ajv2019({
|
||||||
|
useDefaults: true,
|
||||||
|
allowUnionTypes: true,
|
||||||
|
strict: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
ajv.addKeyword({
|
||||||
|
keyword: 'meta:enum', // used by jsonschema2md
|
||||||
|
errors: false,
|
||||||
|
});
|
||||||
|
ajv.addKeyword({
|
||||||
|
keyword: 'tsType', // used by json-schema-to-typescript
|
||||||
|
errors: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ajv currently doesn't support nested default values, see https://github.com/ajv-validator/ajv/issues/1718
|
||||||
|
// (may be fixed in v9) so we need to manually use sub-schemas
|
||||||
|
const mermaidDefaultConfig = {};
|
||||||
|
|
||||||
|
assert.ok(mermaidConfigSchema.$defs);
|
||||||
|
const baseDiagramConfig = mermaidConfigSchema.$defs.BaseDiagramConfig;
|
||||||
|
|
||||||
|
for (const key of MERMAID_CONFIG_DIAGRAM_KEYS) {
|
||||||
|
const subSchemaRef = mermaidConfigSchema.properties[key].$ref;
|
||||||
|
const [root, defs, defName] = subSchemaRef.split('/');
|
||||||
|
assert.strictEqual(root, '#');
|
||||||
|
assert.strictEqual(defs, '$defs');
|
||||||
|
const subSchema = {
|
||||||
|
$schema: mermaidConfigSchema.$schema,
|
||||||
|
$defs: mermaidConfigSchema.$defs,
|
||||||
|
...mermaidConfigSchema.$defs[defName],
|
||||||
|
} as JSONSchemaType<BaseDiagramConfig>;
|
||||||
|
|
||||||
|
const validate = ajv.compile(subSchema);
|
||||||
|
|
||||||
|
mermaidDefaultConfig[key] = {};
|
||||||
|
|
||||||
|
for (const required of subSchema.required ?? []) {
|
||||||
|
if (subSchema.properties[required] === undefined && baseDiagramConfig.properties[required]) {
|
||||||
|
mermaidDefaultConfig[key][required] = baseDiagramConfig.properties[required].default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!validate(mermaidDefaultConfig[key])) {
|
||||||
|
throw new Error(
|
||||||
|
`schema for subconfig ${key} does not have valid defaults! Errors were ${JSON.stringify(
|
||||||
|
validate.errors,
|
||||||
|
undefined,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = ajv.compile(mermaidConfigSchema);
|
||||||
|
|
||||||
|
if (!validate(mermaidDefaultConfig)) {
|
||||||
|
throw new Error(
|
||||||
|
`Mermaid config JSON Schema does not have valid defaults! Errors were ${JSON.stringify(
|
||||||
|
validate.errors,
|
||||||
|
undefined,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mermaidDefaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vite plugin that handles JSON Schemas saved as a `.schema.yaml` file.
|
||||||
|
*
|
||||||
|
* Use `my-example.schema.yaml?only-defaults=true` to only load the default values.
|
||||||
|
*/
|
||||||
|
export default function jsonSchemaPlugin(): PluginOption {
|
||||||
|
return {
|
||||||
|
name: 'json-schema-plugin',
|
||||||
|
transform(src: string, id: string) {
|
||||||
|
const idAsUrl = new URL(id, 'file:///');
|
||||||
|
|
||||||
|
if (!idAsUrl.pathname.endsWith('schema.yaml')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idAsUrl.searchParams.get('only-defaults')) {
|
||||||
|
const jsonSchema = load(src, {
|
||||||
|
filename: idAsUrl.pathname,
|
||||||
|
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
|
||||||
|
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
|
||||||
|
schema: JSON_SCHEMA,
|
||||||
|
}) as JSONSchemaType<MermaidConfig>;
|
||||||
|
return {
|
||||||
|
code: `export default ${JSON.stringify(generateDefaults(jsonSchema), undefined, 2)};`,
|
||||||
|
map: null, // no source map
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
code: `export default ${JSON.stringify(
|
||||||
|
load(src, {
|
||||||
|
filename: idAsUrl.pathname,
|
||||||
|
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
|
||||||
|
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
|
||||||
|
schema: JSON_SCHEMA,
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
2
|
||||||
|
)};`,
|
||||||
|
map: null, // provide source map if available
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
16
CITATION.cff
Normal file
16
CITATION.cff
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
cff-version: 1.2.0
|
||||||
|
title: 'Mermaid: Generate diagrams from markdown-like text'
|
||||||
|
message: >-
|
||||||
|
If you use this software, please cite it using the metadata from this file.
|
||||||
|
type: software
|
||||||
|
authors:
|
||||||
|
- family-names: Sveidqvist
|
||||||
|
given-names: Knut
|
||||||
|
- name: 'Contributors to Mermaid'
|
||||||
|
repository-code: 'https://github.com/mermaid-js/mermaid'
|
||||||
|
date-released: 2014-12-02
|
||||||
|
url: 'https://mermaid.js.org/'
|
||||||
|
abstract: >-
|
||||||
|
JavaScript based diagramming and charting tool that renders Markdown-inspired
|
||||||
|
text definitions to create and modify diagrams dynamically.
|
||||||
|
license: MIT
|
13
__mocks__/sankeyRenderer.js
Normal file
13
__mocks__/sankeyRenderer.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Mocked Sankey diagram renderer
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
|
||||||
|
export const draw = vi.fn().mockImplementation(() => {
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
draw,
|
||||||
|
};
|
@ -40,6 +40,7 @@
|
|||||||
"dompurify",
|
"dompurify",
|
||||||
"edgechromium",
|
"edgechromium",
|
||||||
"elkjs",
|
"elkjs",
|
||||||
|
"elle",
|
||||||
"faber",
|
"faber",
|
||||||
"flatmap",
|
"flatmap",
|
||||||
"foswiki",
|
"foswiki",
|
||||||
@ -86,7 +87,10 @@
|
|||||||
"mkdocs",
|
"mkdocs",
|
||||||
"mmorel",
|
"mmorel",
|
||||||
"mult",
|
"mult",
|
||||||
|
"neurodiverse",
|
||||||
"nextra",
|
"nextra",
|
||||||
|
"nikolay",
|
||||||
|
"nirname",
|
||||||
"orlandoni",
|
"orlandoni",
|
||||||
"pathe",
|
"pathe",
|
||||||
"pbrolin",
|
"pbrolin",
|
||||||
@ -100,10 +104,13 @@
|
|||||||
"ranksep",
|
"ranksep",
|
||||||
"rect",
|
"rect",
|
||||||
"rects",
|
"rects",
|
||||||
|
"reda",
|
||||||
"redmine",
|
"redmine",
|
||||||
"rehype",
|
"rehype",
|
||||||
"roledescription",
|
"roledescription",
|
||||||
|
"rozhkov",
|
||||||
"sandboxed",
|
"sandboxed",
|
||||||
|
"sankey",
|
||||||
"setupgraphviewbox",
|
"setupgraphviewbox",
|
||||||
"shiki",
|
"shiki",
|
||||||
"sidharth",
|
"sidharth",
|
||||||
@ -118,6 +125,7 @@
|
|||||||
"stylis",
|
"stylis",
|
||||||
"subhash-halder",
|
"subhash-halder",
|
||||||
"substate",
|
"substate",
|
||||||
|
"sulais",
|
||||||
"sveidqvist",
|
"sveidqvist",
|
||||||
"swimm",
|
"swimm",
|
||||||
"techn",
|
"techn",
|
||||||
@ -141,6 +149,7 @@
|
|||||||
"vueuse",
|
"vueuse",
|
||||||
"xlink",
|
"xlink",
|
||||||
"yash",
|
"yash",
|
||||||
|
"yokozuna",
|
||||||
"zenuml"
|
"zenuml"
|
||||||
],
|
],
|
||||||
"patterns": [
|
"patterns": [
|
||||||
|
144
cypress/integration/rendering/sankey.spec.ts
Normal file
144
cypress/integration/rendering/sankey.spec.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||||
|
|
||||||
|
describe('Sankey Diagram', () => {
|
||||||
|
it('should render a simple example', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
sourceNode,targetNode,10
|
||||||
|
`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when given a linkColor', function () {
|
||||||
|
this.beforeAll(() => {
|
||||||
|
cy.wrap(
|
||||||
|
`sankey-beta
|
||||||
|
a,b,10
|
||||||
|
`
|
||||||
|
).as('graph');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('links should use hex color', function () {
|
||||||
|
renderGraph(this.graph, { sankey: { linkColor: '#636465' } });
|
||||||
|
|
||||||
|
cy.get('.link path').should((link) => {
|
||||||
|
expect(link.attr('stroke')).to.equal('#636465');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('links should be the same color as source node', function () {
|
||||||
|
renderGraph(this.graph, { sankey: { linkColor: 'source' } });
|
||||||
|
|
||||||
|
cy.get('.link path').then((link) => {
|
||||||
|
cy.get('.node[id="node-1"] rect').should((node) =>
|
||||||
|
expect(link.attr('stroke')).to.equal(node.attr('fill'))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('links should be the same color as target node', function () {
|
||||||
|
renderGraph(this.graph, { sankey: { linkColor: 'target' } });
|
||||||
|
|
||||||
|
cy.get('.link path').then((link) => {
|
||||||
|
cy.get('.node[id="node-2"] rect').should((node) =>
|
||||||
|
expect(link.attr('stroke')).to.equal(node.attr('fill'))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('links must be gradient', function () {
|
||||||
|
renderGraph(this.graph, { sankey: { linkColor: 'gradient' } });
|
||||||
|
|
||||||
|
cy.get('.link path').should((link) => {
|
||||||
|
expect(link.attr('stroke')).to.equal('url(#linearGradient-3)');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when given a nodeAlignment', function () {
|
||||||
|
this.beforeAll(() => {
|
||||||
|
cy.wrap(
|
||||||
|
`
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
a,b,8
|
||||||
|
b,c,8
|
||||||
|
c,d,8
|
||||||
|
d,e,8
|
||||||
|
|
||||||
|
x,c,4
|
||||||
|
c,y,4
|
||||||
|
`
|
||||||
|
).as('graph');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.afterEach(() => {
|
||||||
|
cy.get('.node[id="node-1"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('0');
|
||||||
|
});
|
||||||
|
cy.get('.node[id="node-2"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('100');
|
||||||
|
});
|
||||||
|
cy.get('.node[id="node-3"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('200');
|
||||||
|
});
|
||||||
|
cy.get('.node[id="node-4"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('300');
|
||||||
|
});
|
||||||
|
cy.get('.node[id="node-5"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('400');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should justify nodes', function () {
|
||||||
|
renderGraph(this.graph, {
|
||||||
|
sankey: { nodeAlignment: 'justify', width: 410, useMaxWidth: false },
|
||||||
|
});
|
||||||
|
cy.get('.node[id="node-6"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('0');
|
||||||
|
});
|
||||||
|
cy.get('.node[id="node-7"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('400');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should align nodes left', function () {
|
||||||
|
renderGraph(this.graph, {
|
||||||
|
sankey: { nodeAlignment: 'left', width: 410, useMaxWidth: false },
|
||||||
|
});
|
||||||
|
cy.get('.node[id="node-6"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('0');
|
||||||
|
});
|
||||||
|
cy.get('.node[id="node-7"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('300');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should align nodes right', function () {
|
||||||
|
renderGraph(this.graph, {
|
||||||
|
sankey: { nodeAlignment: 'right', width: 410, useMaxWidth: false },
|
||||||
|
});
|
||||||
|
cy.get('.node[id="node-6"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('100');
|
||||||
|
});
|
||||||
|
cy.get('.node[id="node-7"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('400');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should center nodes', function () {
|
||||||
|
renderGraph(this.graph, {
|
||||||
|
sankey: { nodeAlignment: 'center', width: 410, useMaxWidth: false },
|
||||||
|
});
|
||||||
|
cy.get('.node[id="node-6"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('100');
|
||||||
|
});
|
||||||
|
cy.get('.node[id="node-7"]').should((node) => {
|
||||||
|
expect(node.attr('x')).to.equal('300');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -156,6 +156,81 @@ context('Sequence diagram', () => {
|
|||||||
`
|
`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('should render a sequence diagram with basic actor creation and destruction', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
Alice ->> Bob: Hello Bob, how are you ?
|
||||||
|
Bob ->> Alice: Fine, thank you. And you?
|
||||||
|
create participant Polo
|
||||||
|
Alice ->> Polo: Hi Polo!
|
||||||
|
create actor Ola1 as Ola
|
||||||
|
Polo ->> Ola1: Hiii
|
||||||
|
Ola1 ->> Alice: Hi too
|
||||||
|
destroy Ola1
|
||||||
|
Alice --x Ola1: Bye!
|
||||||
|
Alice ->> Bob: And now?
|
||||||
|
create participant Ola2 as Ola
|
||||||
|
Alice ->> Ola2: Hello again
|
||||||
|
destroy Alice
|
||||||
|
Alice --x Ola2: Bye for me!
|
||||||
|
destroy Bob
|
||||||
|
Ola2 --> Bob: The end
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should render a sequence diagram with actor creation and destruction coupled with backgrounds, loops and notes', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
accTitle: test the accTitle
|
||||||
|
accDescr: Test a description
|
||||||
|
|
||||||
|
participant Alice
|
||||||
|
participant Bob
|
||||||
|
autonumber 10 10
|
||||||
|
rect rgb(200, 220, 100)
|
||||||
|
rect rgb(200, 255, 200)
|
||||||
|
|
||||||
|
Alice ->> Bob: Hello Bob, how are you?
|
||||||
|
create participant John as John<br />Second Line
|
||||||
|
Bob-->>John: How about you John?
|
||||||
|
end
|
||||||
|
|
||||||
|
Bob--x Alice: I am good thanks!
|
||||||
|
Bob-x John: I am good thanks!
|
||||||
|
Note right of John: John thinks a long<br />long time, so long<br />that the text does<br />not fit on a row.
|
||||||
|
|
||||||
|
Bob-->Alice: Checking with John...
|
||||||
|
Note over John:wrap: John looks like he's still thinking, so Bob prods him a bit.
|
||||||
|
Bob-x John: Hey John - we're still waiting to know<br />how you're doing
|
||||||
|
Note over John:nowrap: John's trying hard not to break his train of thought.
|
||||||
|
destroy John
|
||||||
|
Bob-x John: John! Cmon!
|
||||||
|
Note over John: After a few more moments, John<br />finally snaps out of it.
|
||||||
|
end
|
||||||
|
|
||||||
|
autonumber off
|
||||||
|
alt either this
|
||||||
|
create actor Lola
|
||||||
|
Alice->>+Lola: Yes
|
||||||
|
Lola-->>-Alice: OK
|
||||||
|
else or this
|
||||||
|
autonumber
|
||||||
|
Alice->>Lola: No
|
||||||
|
else or this will happen
|
||||||
|
Alice->Lola: Maybe
|
||||||
|
end
|
||||||
|
autonumber 200
|
||||||
|
par this happens in parallel
|
||||||
|
destroy Bob
|
||||||
|
Alice -->> Bob: Parallel message 1
|
||||||
|
and
|
||||||
|
Alice -->> Lola: Parallel message 2
|
||||||
|
end
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
context('font settings', () => {
|
context('font settings', () => {
|
||||||
it('should render different note fonts when configured', () => {
|
it('should render different note fonts when configured', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
|
@ -154,6 +154,29 @@
|
|||||||
</pre>
|
</pre>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
A1 --> B1
|
||||||
|
namespace A {
|
||||||
|
class A1 {
|
||||||
|
+foo : string
|
||||||
|
}
|
||||||
|
class A2 {
|
||||||
|
+bar : int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace B {
|
||||||
|
class B1 {
|
||||||
|
+foo : bool
|
||||||
|
}
|
||||||
|
class B2 {
|
||||||
|
+bar : float
|
||||||
|
}
|
||||||
|
}
|
||||||
|
A2 --> B2
|
||||||
|
</pre>
|
||||||
|
<hr />
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import mermaid from './mermaid.esm.mjs';
|
import mermaid from './mermaid.esm.mjs';
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
|
@ -75,6 +75,9 @@
|
|||||||
<li>
|
<li>
|
||||||
<h2><a href="./zenuml.html">ZenUML</a></h2>
|
<h2><a href="./zenuml.html">ZenUML</a></h2>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<h2><a href="./sankey.html">Sankey</a></h2>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
108
demos/sankey.html
Normal file
108
demos/sankey.html
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<!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>States Mermaid Quick Test Page</title>
|
||||||
|
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||||
|
<style>
|
||||||
|
div.mermaid {
|
||||||
|
/* font-family: 'trebuchet ms', verdana, arial; */
|
||||||
|
font-family: 'Courier New', Courier, monospace !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Sankey diagram demos</h1>
|
||||||
|
<h2>Energy flow</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
Agricultural 'waste',Bio-conversion,124.729
|
||||||
|
Bio-conversion,Liquid,0.597
|
||||||
|
Bio-conversion,Losses,26.862
|
||||||
|
Bio-conversion,Solid,280.322
|
||||||
|
Bio-conversion,Gas,81.144
|
||||||
|
Biofuel imports,Liquid,35
|
||||||
|
Biomass imports,Solid,35
|
||||||
|
Coal imports,Coal,11.606
|
||||||
|
Coal reserves,Coal,63.965
|
||||||
|
Coal,Solid,75.571
|
||||||
|
District heating,Industry,10.639
|
||||||
|
District heating,Heating and cooling - commercial,22.505
|
||||||
|
District heating,Heating and cooling - homes,46.184
|
||||||
|
Electricity grid,Over generation / exports,104.453
|
||||||
|
Electricity grid,Heating and cooling - homes,113.726
|
||||||
|
Electricity grid,H2 conversion,27.14
|
||||||
|
Electricity grid,Industry,342.165
|
||||||
|
Electricity grid,Road transport,37.797
|
||||||
|
Electricity grid,Agriculture,4.412
|
||||||
|
Electricity grid,Heating and cooling - commercial,40.858
|
||||||
|
Electricity grid,Losses,56.691
|
||||||
|
Electricity grid,Rail transport,7.863
|
||||||
|
Electricity grid,Lighting & appliances - commercial,90.008
|
||||||
|
Electricity grid,Lighting & appliances - homes,93.494
|
||||||
|
Gas imports,Ngas,40.719
|
||||||
|
Gas reserves,Ngas,82.233
|
||||||
|
Gas,Heating and cooling - commercial,0.129
|
||||||
|
Gas,Losses,1.401
|
||||||
|
Gas,Thermal generation,151.891
|
||||||
|
Gas,Agriculture,2.096
|
||||||
|
Gas,Industry,48.58
|
||||||
|
Geothermal,Electricity grid,7.013
|
||||||
|
H2 conversion,H2,20.897
|
||||||
|
H2 conversion,Losses,6.242
|
||||||
|
H2,Road transport,20.897
|
||||||
|
Hydro,Electricity grid,6.995
|
||||||
|
Liquid,Industry,121.066
|
||||||
|
Liquid,International shipping,128.69
|
||||||
|
Liquid,Road transport,135.835
|
||||||
|
Liquid,Domestic aviation,14.458
|
||||||
|
Liquid,International aviation,206.267
|
||||||
|
Liquid,Agriculture,3.64
|
||||||
|
Liquid,National navigation,33.218
|
||||||
|
Liquid,Rail transport,4.413
|
||||||
|
Marine algae,Bio-conversion,4.375
|
||||||
|
Ngas,Gas,122.952
|
||||||
|
Nuclear,Thermal generation,839.978
|
||||||
|
Oil imports,Oil,504.287
|
||||||
|
Oil reserves,Oil,107.703
|
||||||
|
Oil,Liquid,611.99
|
||||||
|
Other waste,Solid,56.587
|
||||||
|
Other waste,Bio-conversion,77.81
|
||||||
|
Pumped heat,Heating and cooling - homes,193.026
|
||||||
|
Pumped heat,Heating and cooling - commercial,70.672
|
||||||
|
Solar PV,Electricity grid,59.901
|
||||||
|
Solar Thermal,Heating and cooling - homes,19.263
|
||||||
|
Solar,Solar Thermal,19.263
|
||||||
|
Solar,Solar PV,59.901
|
||||||
|
Solid,Agriculture,0.882
|
||||||
|
Solid,Thermal generation,400.12
|
||||||
|
Solid,Industry,46.477
|
||||||
|
Thermal generation,Electricity grid,525.531
|
||||||
|
Thermal generation,Losses,787.129
|
||||||
|
Thermal generation,District heating,79.329
|
||||||
|
Tidal,Electricity grid,9.452
|
||||||
|
UK land based bioenergy,Bio-conversion,182.01
|
||||||
|
Wave,Electricity grid,19.013
|
||||||
|
Wind,Electricity grid,289.366
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from './mermaid.esm.mjs';
|
||||||
|
mermaid.initialize({
|
||||||
|
theme: 'default',
|
||||||
|
logLevel: 3,
|
||||||
|
securityLevel: 'loose',
|
||||||
|
sankey: {
|
||||||
|
title: 'Hey, this is Sankey-Beta',
|
||||||
|
width: 1200,
|
||||||
|
height: 600,
|
||||||
|
linkColor: 'gradient',
|
||||||
|
nodeAlignment: 'justify',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -5,5 +5,32 @@ services:
|
|||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
working_dir: /mermaid
|
working_dir: /mermaid
|
||||||
|
mem_limit: '2G'
|
||||||
|
environment:
|
||||||
|
- NODE_OPTIONS=--max_old_space_size=2048
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/mermaid
|
- ./:/mermaid
|
||||||
|
- root_cache:/root/.cache
|
||||||
|
- root_local:/root/.local
|
||||||
|
- root_npm:/root/.npm
|
||||||
|
ports:
|
||||||
|
- 9000:9000
|
||||||
|
- 3333:3333
|
||||||
|
cypress:
|
||||||
|
image: cypress/included:12.16.0
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
working_dir: /mermaid
|
||||||
|
mem_limit: '2G'
|
||||||
|
entrypoint: cypress
|
||||||
|
environment:
|
||||||
|
- DISPLAY
|
||||||
|
volumes:
|
||||||
|
- ./:/mermaid
|
||||||
|
- /tmp/.X11-unix:/tmp/.X11-unix
|
||||||
|
network_mode: host
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
root_cache:
|
||||||
|
root_local:
|
||||||
|
root_npm:
|
||||||
|
@ -26,6 +26,10 @@ The definitions that can be generated the Live-Editor are also backwards-compati
|
|||||||
|
|
||||||
[Eddie Jaoude: Can you code your diagrams?](https://www.youtube.com/watch?v=9HZzKkAqrX8)
|
[Eddie Jaoude: Can you code your diagrams?](https://www.youtube.com/watch?v=9HZzKkAqrX8)
|
||||||
|
|
||||||
|
## Mermaid with OpenAI
|
||||||
|
|
||||||
|
[Elle Neal: Mind Mapping with AI: An Accessible Approach for Neurodiverse Learners Tutorial:](https://medium.com/@elle.neal_71064/mind-mapping-with-ai-an-accessible-approach-for-neurodiverse-learners-1a74767359ff), [Demo:](https://databutton.com/v/jk9vrghc)
|
||||||
|
|
||||||
## Mermaid with HTML
|
## Mermaid with HTML
|
||||||
|
|
||||||
Examples are provided in [Getting Started](../intro/n00b-gettingStarted.md)
|
Examples are provided in [Getting Started](../intro/n00b-gettingStarted.md)
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[defaultConfig.ts:2293](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2293)
|
[defaultConfig.ts:266](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L266)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -22,35 +22,12 @@
|
|||||||
|
|
||||||
• `Const` **default**: `Partial`<`MermaidConfig`>
|
• `Const` **default**: `Partial`<`MermaidConfig`>
|
||||||
|
|
||||||
**Configuration methods in Mermaid version 8.6.0 have been updated, to learn more\[[click
|
Default mermaid configuration options.
|
||||||
here](8.6.0_docs.md)].**
|
|
||||||
|
|
||||||
## **What follows are config instructions for older versions**
|
Please see the Mermaid config JSON Schema for the default JSON values.
|
||||||
|
Non-JSON JS default values are listed in this file, e.g. functions, or
|
||||||
These are the default options which can be overridden with the initialization call like so:
|
`undefined` (explicitly set so that `configKeys` finds them).
|
||||||
|
|
||||||
**Example 1:**
|
|
||||||
|
|
||||||
```js
|
|
||||||
mermaid.initialize({ flowchart: { htmlLabels: false } });
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example 2:**
|
|
||||||
|
|
||||||
```html
|
|
||||||
<script>
|
|
||||||
const config = {
|
|
||||||
startOnLoad: true,
|
|
||||||
flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'cardinal' },
|
|
||||||
securityLevel: 'loose',
|
|
||||||
};
|
|
||||||
mermaid.initialize(config);
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
A summary of all options and their defaults is found [here](#mermaidapi-configuration-defaults).
|
|
||||||
A description of each option follows below.
|
|
||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[defaultConfig.ts:33](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L33)
|
[defaultConfig.ts:16](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L16)
|
||||||
|
@ -96,7 +96,7 @@ mermaid.initialize(config);
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:663](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L663)
|
[mermaidAPI.ts:667](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L667)
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
|
@ -73,9 +73,9 @@ To make a custom theme, modify `themeVariables` via `init`.
|
|||||||
|
|
||||||
You will need to use the [base](#available-themes) theme as it is the only modifiable theme.
|
You will need to use the [base](#available-themes) theme as it is the only modifiable theme.
|
||||||
|
|
||||||
| Parameter | Description | Type | Properties |
|
| Parameter | Description | Type | Properties |
|
||||||
| -------------- | ------------------------------------ | ------ | --------------------------------------------------------------------------------------------------- |
|
| -------------- | ------------------------------------ | ------ | ----------------------------------------------------------------------------------- |
|
||||||
| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables-reference-table)) |
|
| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables)) |
|
||||||
|
|
||||||
Example of modifying `themeVariables` using the `init` directive:
|
Example of modifying `themeVariables` using the `init` directive:
|
||||||
|
|
||||||
|
@ -161,6 +161,8 @@ They also serve as proof of concept, for the variety of things that can be built
|
|||||||
- [Nano Mermaid](https://github.com/Yash-Singh1/nano-mermaid)
|
- [Nano Mermaid](https://github.com/Yash-Singh1/nano-mermaid)
|
||||||
- [CKEditor](https://github.com/ckeditor/ckeditor5)
|
- [CKEditor](https://github.com/ckeditor/ckeditor5)
|
||||||
- [CKEditor 5 Mermaid plugin](https://github.com/ckeditor/ckeditor5-mermaid)
|
- [CKEditor 5 Mermaid plugin](https://github.com/ckeditor/ckeditor5-mermaid)
|
||||||
|
- [Standard Notes](https://standardnotes.com/)
|
||||||
|
- [sn-mermaid](https://github.com/nienow/sn-mermaid)
|
||||||
|
|
||||||
## Document Generation
|
## Document Generation
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
# Announcements
|
# Announcements
|
||||||
|
|
||||||
## [subhash-halder contributed quadrant charts so you can show your manager what to select - just like the strategy consultants at BCG do](https://www.mermaidchart.com/blog/posts/subhash-halder-contributed-quadrant-charts-so-you-can-show-your-manager-what-to-select-just-like-the-strategy-consultants-at-bcg-do/)
|
## [Mermaid Chart’s ChatGPT Plugin Combines Generative AI and Smart Diagramming For Users](https://www.mermaidchart.com/blog/posts/mermaid-chart-chatgpt-plugin-combines-generative-ai-and-smart-diagramming)
|
||||||
|
|
||||||
8 June 2023 · 7 mins
|
29 June 2023 · 4 mins
|
||||||
|
|
||||||
A quadrant chart is a useful diagram that helps users visualize data and identify patterns in a data set.
|
Mermaid Chart’s new ChatGPT plugin integrates AI-powered text prompts with Mermaid’s intuitive diagramming editor, enabling users to generate, edit, and share complex diagrams with ease and efficiency.
|
||||||
|
@ -6,6 +6,18 @@
|
|||||||
|
|
||||||
# Blog
|
# Blog
|
||||||
|
|
||||||
|
## [Mermaid Chart’s ChatGPT Plugin Combines Generative AI and Smart Diagramming For Users](https://www.mermaidchart.com/blog/posts/mermaid-chart-chatgpt-plugin-combines-generative-ai-and-smart-diagramming)
|
||||||
|
|
||||||
|
29 June 2023 · 4 mins
|
||||||
|
|
||||||
|
Mermaid Chart’s new ChatGPT plugin integrates AI-powered text prompts with Mermaid’s intuitive diagramming editor, enabling users to generate, edit, and share complex diagrams with ease and efficiency.
|
||||||
|
|
||||||
|
## [Sequence diagrams, the only good thing UML brought to software development](https://www.mermaidchart.com/blog/posts/sequence-diagrams-the-good-thing-uml-brought-to-software-development/)
|
||||||
|
|
||||||
|
15 June 2023 · 12 mins
|
||||||
|
|
||||||
|
Sequence diagrams really shine when you’re documenting different parts of a system and the various ways these parts interact with each other.
|
||||||
|
|
||||||
## [subhash-halder contributed quadrant charts so you can show your manager what to select - just like the strategy consultants at BCG do](https://www.mermaidchart.com/blog/posts/subhash-halder-contributed-quadrant-charts-so-you-can-show-your-manager-what-to-select-just-like-the-strategy-consultants-at-bcg-do/)
|
## [subhash-halder contributed quadrant charts so you can show your manager what to select - just like the strategy consultants at BCG do](https://www.mermaidchart.com/blog/posts/subhash-halder-contributed-quadrant-charts-so-you-can-show-your-manager-what-to-select-just-like-the-strategy-consultants-at-bcg-do/)
|
||||||
|
|
||||||
8 June 2023 · 7 mins
|
8 June 2023 · 7 mins
|
||||||
|
@ -991,6 +991,24 @@ flowchart LR
|
|||||||
classDef someclass fill:#f96
|
classDef someclass fill:#f96
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This form can be used when declaring multiple links between nodes:
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
flowchart LR
|
||||||
|
A:::foo & B:::bar --> C:::foobar
|
||||||
|
classDef foo stroke:#f00
|
||||||
|
classDef bar stroke:#0f0
|
||||||
|
classDef foobar stroke:#00f
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A:::foo & B:::bar --> C:::foobar
|
||||||
|
classDef foo stroke:#f00
|
||||||
|
classDef bar stroke:#0f0
|
||||||
|
classDef foobar stroke:#00f
|
||||||
|
```
|
||||||
|
|
||||||
### Css classes
|
### Css classes
|
||||||
|
|
||||||
It is also possible to predefine classes in css styles that can be applied from the graph definition as in the example
|
It is also possible to predefine classes in css styles that can be applied from the graph definition as in the example
|
||||||
|
@ -25,25 +25,25 @@ Mermaid can render Gantt diagrams as SVG, PNG or a MarkDown link that can be pas
|
|||||||
```mermaid-example
|
```mermaid-example
|
||||||
gantt
|
gantt
|
||||||
title A Gantt Diagram
|
title A Gantt Diagram
|
||||||
dateFormat YYYY-MM-DD
|
dateFormat YYYY-MM-DD
|
||||||
section Section
|
section Section
|
||||||
A task :a1, 2014-01-01, 30d
|
A task :a1, 2014-01-01, 30d
|
||||||
Another task :after a1 , 20d
|
Another task :after a1, 20d
|
||||||
section Another
|
section Another
|
||||||
Task in sec :2014-01-12 , 12d
|
Task in Another :2014-01-12, 12d
|
||||||
another task : 24d
|
another task :24d
|
||||||
```
|
```
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
gantt
|
gantt
|
||||||
title A Gantt Diagram
|
title A Gantt Diagram
|
||||||
dateFormat YYYY-MM-DD
|
dateFormat YYYY-MM-DD
|
||||||
section Section
|
section Section
|
||||||
A task :a1, 2014-01-01, 30d
|
A task :a1, 2014-01-01, 30d
|
||||||
Another task :after a1 , 20d
|
Another task :after a1, 20d
|
||||||
section Another
|
section Another
|
||||||
Task in sec :2014-01-12 , 12d
|
Task in Another :2014-01-12, 12d
|
||||||
another task : 24d
|
another task :24d
|
||||||
```
|
```
|
||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
@ -117,17 +117,17 @@ gantt
|
|||||||
It is possible to set multiple dependencies separated by space:
|
It is possible to set multiple dependencies separated by space:
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
gantt
|
gantt
|
||||||
apple :a, 2017-07-20, 1w
|
apple :a, 2017-07-20, 1w
|
||||||
banana :crit, b, 2017-07-23, 1d
|
banana :crit, b, 2017-07-23, 1d
|
||||||
cherry :active, c, after b a, 1d
|
cherry :active, c, after b a, 1d
|
||||||
```
|
```
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
gantt
|
gantt
|
||||||
apple :a, 2017-07-20, 1w
|
apple :a, 2017-07-20, 1w
|
||||||
banana :crit, b, 2017-07-23, 1d
|
banana :crit, b, 2017-07-23, 1d
|
||||||
cherry :active, c, after b a, 1d
|
cherry :active, c, after b a, 1d
|
||||||
```
|
```
|
||||||
|
|
||||||
### Title
|
### Title
|
||||||
@ -146,22 +146,22 @@ You can add milestones to the diagrams. Milestones differ from tasks as they rep
|
|||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
gantt
|
gantt
|
||||||
dateFormat HH:mm
|
dateFormat HH:mm
|
||||||
axisFormat %H:%M
|
axisFormat %H:%M
|
||||||
Initial milestone : milestone, m1, 17:49,2min
|
Initial milestone : milestone, m1, 17:49, 2m
|
||||||
taska2 : 10min
|
Task A : 10m
|
||||||
taska3 : 5min
|
Task B : 5m
|
||||||
Final milestone : milestone, m2, 18:14, 2min
|
Final milestone : milestone, m2, 18:08, 4m
|
||||||
```
|
```
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
gantt
|
gantt
|
||||||
dateFormat HH:mm
|
dateFormat HH:mm
|
||||||
axisFormat %H:%M
|
axisFormat %H:%M
|
||||||
Initial milestone : milestone, m1, 17:49,2min
|
Initial milestone : milestone, m1, 17:49, 2m
|
||||||
taska2 : 10min
|
Task A : 10m
|
||||||
taska3 : 5min
|
Task B : 5m
|
||||||
Final milestone : milestone, m2, 18:14, 2min
|
Final milestone : milestone, m2, 18:08, 4m
|
||||||
```
|
```
|
||||||
|
|
||||||
## Setting dates
|
## Setting dates
|
||||||
@ -296,29 +296,27 @@ Comments can be entered within a gantt chart, which will be ignored by the parse
|
|||||||
```mermaid-example
|
```mermaid-example
|
||||||
gantt
|
gantt
|
||||||
title A Gantt Diagram
|
title A Gantt Diagram
|
||||||
%% this is a comment
|
%% This is a comment
|
||||||
dateFormat YYYY-MM-DD
|
dateFormat YYYY-MM-DD
|
||||||
section Section
|
section Section
|
||||||
A task :a1, 2014-01-01, 30d
|
A task :a1, 2014-01-01, 30d
|
||||||
Another task :after a1 , 20d
|
Another task :after a1, 20d
|
||||||
section Another
|
section Another
|
||||||
Task in sec :2014-01-12 , 12d
|
Task in Another :2014-01-12, 12d
|
||||||
another task : 24d
|
another task :24d
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
gantt
|
gantt
|
||||||
title A Gantt Diagram
|
title A Gantt Diagram
|
||||||
%% this is a comment
|
%% This is a comment
|
||||||
dateFormat YYYY-MM-DD
|
dateFormat YYYY-MM-DD
|
||||||
section Section
|
section Section
|
||||||
A task :a1, 2014-01-01, 30d
|
A task :a1, 2014-01-01, 30d
|
||||||
Another task :after a1 , 20d
|
Another task :after a1, 20d
|
||||||
section Another
|
section Another
|
||||||
Task in sec :2014-01-12 , 12d
|
Task in Another :2014-01-12, 12d
|
||||||
another task : 24d
|
another task :24d
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
@ -440,7 +438,7 @@ Beginner's tip—a full example using interactive links in an html context:
|
|||||||
dateFormat YYYY-MM-DD
|
dateFormat YYYY-MM-DD
|
||||||
|
|
||||||
section Clickable
|
section Clickable
|
||||||
Visit mermaidjs :active, cl1, 2014-01-07, 3d
|
Visit mermaidjs :active, cl1, 2014-01-07, 3d
|
||||||
Print arguments :cl2, after cl1, 3d
|
Print arguments :cl2, after cl1, 3d
|
||||||
Print task :cl3, after cl2, 3d
|
Print task :cl3, after cl2, 3d
|
||||||
|
|
||||||
|
294
docs/syntax/sankey.md
Normal file
294
docs/syntax/sankey.md
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
> **Warning**
|
||||||
|
>
|
||||||
|
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||||
|
>
|
||||||
|
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/sankey.md](../../packages/mermaid/src/docs/syntax/sankey.md).
|
||||||
|
|
||||||
|
# Sankey diagrams
|
||||||
|
|
||||||
|
> A sankey diagram is a visualization used to depict a flow from one set of values to another.
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future.
|
||||||
|
:::
|
||||||
|
|
||||||
|
The things being connected are called nodes and the connections are called links.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
This example taken from [observable](https://observablehq.com/@d3/sankey/2?collection=@d3/d3-sankey). It may be rendered a little bit differently, though, in terms of size and colors.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
Agricultural 'waste',Bio-conversion,124.729
|
||||||
|
Bio-conversion,Liquid,0.597
|
||||||
|
Bio-conversion,Losses,26.862
|
||||||
|
Bio-conversion,Solid,280.322
|
||||||
|
Bio-conversion,Gas,81.144
|
||||||
|
Biofuel imports,Liquid,35
|
||||||
|
Biomass imports,Solid,35
|
||||||
|
Coal imports,Coal,11.606
|
||||||
|
Coal reserves,Coal,63.965
|
||||||
|
Coal,Solid,75.571
|
||||||
|
District heating,Industry,10.639
|
||||||
|
District heating,Heating and cooling - commercial,22.505
|
||||||
|
District heating,Heating and cooling - homes,46.184
|
||||||
|
Electricity grid,Over generation / exports,104.453
|
||||||
|
Electricity grid,Heating and cooling - homes,113.726
|
||||||
|
Electricity grid,H2 conversion,27.14
|
||||||
|
Electricity grid,Industry,342.165
|
||||||
|
Electricity grid,Road transport,37.797
|
||||||
|
Electricity grid,Agriculture,4.412
|
||||||
|
Electricity grid,Heating and cooling - commercial,40.858
|
||||||
|
Electricity grid,Losses,56.691
|
||||||
|
Electricity grid,Rail transport,7.863
|
||||||
|
Electricity grid,Lighting & appliances - commercial,90.008
|
||||||
|
Electricity grid,Lighting & appliances - homes,93.494
|
||||||
|
Gas imports,Ngas,40.719
|
||||||
|
Gas reserves,Ngas,82.233
|
||||||
|
Gas,Heating and cooling - commercial,0.129
|
||||||
|
Gas,Losses,1.401
|
||||||
|
Gas,Thermal generation,151.891
|
||||||
|
Gas,Agriculture,2.096
|
||||||
|
Gas,Industry,48.58
|
||||||
|
Geothermal,Electricity grid,7.013
|
||||||
|
H2 conversion,H2,20.897
|
||||||
|
H2 conversion,Losses,6.242
|
||||||
|
H2,Road transport,20.897
|
||||||
|
Hydro,Electricity grid,6.995
|
||||||
|
Liquid,Industry,121.066
|
||||||
|
Liquid,International shipping,128.69
|
||||||
|
Liquid,Road transport,135.835
|
||||||
|
Liquid,Domestic aviation,14.458
|
||||||
|
Liquid,International aviation,206.267
|
||||||
|
Liquid,Agriculture,3.64
|
||||||
|
Liquid,National navigation,33.218
|
||||||
|
Liquid,Rail transport,4.413
|
||||||
|
Marine algae,Bio-conversion,4.375
|
||||||
|
Ngas,Gas,122.952
|
||||||
|
Nuclear,Thermal generation,839.978
|
||||||
|
Oil imports,Oil,504.287
|
||||||
|
Oil reserves,Oil,107.703
|
||||||
|
Oil,Liquid,611.99
|
||||||
|
Other waste,Solid,56.587
|
||||||
|
Other waste,Bio-conversion,77.81
|
||||||
|
Pumped heat,Heating and cooling - homes,193.026
|
||||||
|
Pumped heat,Heating and cooling - commercial,70.672
|
||||||
|
Solar PV,Electricity grid,59.901
|
||||||
|
Solar Thermal,Heating and cooling - homes,19.263
|
||||||
|
Solar,Solar Thermal,19.263
|
||||||
|
Solar,Solar PV,59.901
|
||||||
|
Solid,Agriculture,0.882
|
||||||
|
Solid,Thermal generation,400.12
|
||||||
|
Solid,Industry,46.477
|
||||||
|
Thermal generation,Electricity grid,525.531
|
||||||
|
Thermal generation,Losses,787.129
|
||||||
|
Thermal generation,District heating,79.329
|
||||||
|
Tidal,Electricity grid,9.452
|
||||||
|
UK land based bioenergy,Bio-conversion,182.01
|
||||||
|
Wave,Electricity grid,19.013
|
||||||
|
Wind,Electricity grid,289.366
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
Agricultural 'waste',Bio-conversion,124.729
|
||||||
|
Bio-conversion,Liquid,0.597
|
||||||
|
Bio-conversion,Losses,26.862
|
||||||
|
Bio-conversion,Solid,280.322
|
||||||
|
Bio-conversion,Gas,81.144
|
||||||
|
Biofuel imports,Liquid,35
|
||||||
|
Biomass imports,Solid,35
|
||||||
|
Coal imports,Coal,11.606
|
||||||
|
Coal reserves,Coal,63.965
|
||||||
|
Coal,Solid,75.571
|
||||||
|
District heating,Industry,10.639
|
||||||
|
District heating,Heating and cooling - commercial,22.505
|
||||||
|
District heating,Heating and cooling - homes,46.184
|
||||||
|
Electricity grid,Over generation / exports,104.453
|
||||||
|
Electricity grid,Heating and cooling - homes,113.726
|
||||||
|
Electricity grid,H2 conversion,27.14
|
||||||
|
Electricity grid,Industry,342.165
|
||||||
|
Electricity grid,Road transport,37.797
|
||||||
|
Electricity grid,Agriculture,4.412
|
||||||
|
Electricity grid,Heating and cooling - commercial,40.858
|
||||||
|
Electricity grid,Losses,56.691
|
||||||
|
Electricity grid,Rail transport,7.863
|
||||||
|
Electricity grid,Lighting & appliances - commercial,90.008
|
||||||
|
Electricity grid,Lighting & appliances - homes,93.494
|
||||||
|
Gas imports,Ngas,40.719
|
||||||
|
Gas reserves,Ngas,82.233
|
||||||
|
Gas,Heating and cooling - commercial,0.129
|
||||||
|
Gas,Losses,1.401
|
||||||
|
Gas,Thermal generation,151.891
|
||||||
|
Gas,Agriculture,2.096
|
||||||
|
Gas,Industry,48.58
|
||||||
|
Geothermal,Electricity grid,7.013
|
||||||
|
H2 conversion,H2,20.897
|
||||||
|
H2 conversion,Losses,6.242
|
||||||
|
H2,Road transport,20.897
|
||||||
|
Hydro,Electricity grid,6.995
|
||||||
|
Liquid,Industry,121.066
|
||||||
|
Liquid,International shipping,128.69
|
||||||
|
Liquid,Road transport,135.835
|
||||||
|
Liquid,Domestic aviation,14.458
|
||||||
|
Liquid,International aviation,206.267
|
||||||
|
Liquid,Agriculture,3.64
|
||||||
|
Liquid,National navigation,33.218
|
||||||
|
Liquid,Rail transport,4.413
|
||||||
|
Marine algae,Bio-conversion,4.375
|
||||||
|
Ngas,Gas,122.952
|
||||||
|
Nuclear,Thermal generation,839.978
|
||||||
|
Oil imports,Oil,504.287
|
||||||
|
Oil reserves,Oil,107.703
|
||||||
|
Oil,Liquid,611.99
|
||||||
|
Other waste,Solid,56.587
|
||||||
|
Other waste,Bio-conversion,77.81
|
||||||
|
Pumped heat,Heating and cooling - homes,193.026
|
||||||
|
Pumped heat,Heating and cooling - commercial,70.672
|
||||||
|
Solar PV,Electricity grid,59.901
|
||||||
|
Solar Thermal,Heating and cooling - homes,19.263
|
||||||
|
Solar,Solar Thermal,19.263
|
||||||
|
Solar,Solar PV,59.901
|
||||||
|
Solid,Agriculture,0.882
|
||||||
|
Solid,Thermal generation,400.12
|
||||||
|
Solid,Industry,46.477
|
||||||
|
Thermal generation,Electricity grid,525.531
|
||||||
|
Thermal generation,Losses,787.129
|
||||||
|
Thermal generation,District heating,79.329
|
||||||
|
Tidal,Electricity grid,9.452
|
||||||
|
UK land based bioenergy,Bio-conversion,182.01
|
||||||
|
Wave,Electricity grid,19.013
|
||||||
|
Wind,Electricity grid,289.366
|
||||||
|
```
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
The idea behind syntax is that a user types `sankey-beta` keyword first, then pastes raw CSV below and get the result.
|
||||||
|
|
||||||
|
It implements CSV standard as [described here](https://www.ietf.org/rfc/rfc4180.txt) with subtle **differences**:
|
||||||
|
|
||||||
|
- CSV must contain **3 columns only**
|
||||||
|
- It is **allowed** to have **empty lines** without comma separators for visual purposes
|
||||||
|
|
||||||
|
### Basic
|
||||||
|
|
||||||
|
It is implied that 3 columns inside CSV should represent `source`, `target` and `value` accordingly:
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
%% source,target,value
|
||||||
|
Electricity grid,Over generation / exports,104.453
|
||||||
|
Electricity grid,Heating and cooling - homes,113.726
|
||||||
|
Electricity grid,H2 conversion,27.14
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
%% source,target,value
|
||||||
|
Electricity grid,Over generation / exports,104.453
|
||||||
|
Electricity grid,Heating and cooling - homes,113.726
|
||||||
|
Electricity grid,H2 conversion,27.14
|
||||||
|
```
|
||||||
|
|
||||||
|
### Empty Lines
|
||||||
|
|
||||||
|
CSV does not support empty lines without comma delimeters by default. But you can add them if needed:
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
Bio-conversion,Losses,26.862
|
||||||
|
|
||||||
|
Bio-conversion,Solid,280.322
|
||||||
|
|
||||||
|
Bio-conversion,Gas,81.144
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
Bio-conversion,Losses,26.862
|
||||||
|
|
||||||
|
Bio-conversion,Solid,280.322
|
||||||
|
|
||||||
|
Bio-conversion,Gas,81.144
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commas
|
||||||
|
|
||||||
|
If you need to have a comma, wrap it in double quotes:
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
Pumped heat,"Heating and cooling, homes",193.026
|
||||||
|
Pumped heat,"Heating and cooling, commercial",70.672
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
Pumped heat,"Heating and cooling, homes",193.026
|
||||||
|
Pumped heat,"Heating and cooling, commercial",70.672
|
||||||
|
```
|
||||||
|
|
||||||
|
### Double Quotes
|
||||||
|
|
||||||
|
If you need to have double quote, put a pair of them inside quoted string:
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
Pumped heat,"Heating and cooling, ""homes""",193.026
|
||||||
|
Pumped heat,"Heating and cooling, ""commercial""",70.672
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sankey-beta
|
||||||
|
|
||||||
|
Pumped heat,"Heating and cooling, ""homes""",193.026
|
||||||
|
Pumped heat,"Heating and cooling, ""commercial""",70.672
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
You can customize link colors, node alignments and diagram dimensions.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
const config = {
|
||||||
|
startOnLoad: true,
|
||||||
|
securityLevel: 'loose',
|
||||||
|
sankey: {
|
||||||
|
width: 800,
|
||||||
|
height: 400,
|
||||||
|
linkColor: 'source',
|
||||||
|
nodeAlignment: 'left',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mermaid.initialize(config);
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Links Coloring
|
||||||
|
|
||||||
|
You can adjust links' color by setting `linkColor` to one of those:
|
||||||
|
|
||||||
|
- `source` - link will be of a source node color
|
||||||
|
- `target` - link will be of a target node color
|
||||||
|
- `gradient` - link color will be smoothly transient between source and target node colors
|
||||||
|
- hex code of color, like `#a1a1a1`
|
||||||
|
|
||||||
|
### Node Alignment
|
||||||
|
|
||||||
|
Graph layout can be changed by setting `nodeAlignment` to:
|
||||||
|
|
||||||
|
- `justify`
|
||||||
|
- `center`
|
||||||
|
- `left`
|
||||||
|
- `right`
|
@ -94,6 +94,43 @@ sequenceDiagram
|
|||||||
J->>A: Great!
|
J->>A: Great!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Actor Creation and Destruction
|
||||||
|
|
||||||
|
It is possible to create and destroy actors by messages. To do so, add a create or destroy directive before the message.
|
||||||
|
|
||||||
|
create participant B
|
||||||
|
A --> B: Hello
|
||||||
|
|
||||||
|
Create directives support actor/participant distinction and aliases. The sender or the recipient of a message can be destroyed but only the recipient can be created.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
Alice->>Bob: Hello Bob, how are you ?
|
||||||
|
Bob->>Alice: Fine, thank you. And you?
|
||||||
|
create participant Carl
|
||||||
|
Alice->>Carl: Hi Carl!
|
||||||
|
create actor D as Donald
|
||||||
|
Carl->>D: Hi!
|
||||||
|
destroy Carl
|
||||||
|
Alice-xCarl: We are too many
|
||||||
|
destroy Bob
|
||||||
|
Bob->>Alice: I agree
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
Alice->>Bob: Hello Bob, how are you ?
|
||||||
|
Bob->>Alice: Fine, thank you. And you?
|
||||||
|
create participant Carl
|
||||||
|
Alice->>Carl: Hi Carl!
|
||||||
|
create actor D as Donald
|
||||||
|
Carl->>D: Hi!
|
||||||
|
destroy Carl
|
||||||
|
Alice-xCarl: We are too many
|
||||||
|
destroy Bob
|
||||||
|
Bob->>Alice: I agree
|
||||||
|
```
|
||||||
|
|
||||||
### Grouping / Box
|
### Grouping / Box
|
||||||
|
|
||||||
The actor(s) can be grouped in vertical boxes. You can define a color (if not, it will be transparent) and/or a descriptive label using the following notation:
|
The actor(s) can be grouped in vertical boxes. You can define a color (if not, it will be transparent) and/or a descriptive label using the following notation:
|
||||||
|
@ -487,7 +487,7 @@ where
|
|||||||
- the second _property_ is `color` and its _value_ is `white`
|
- the second _property_ is `color` and its _value_ is `white`
|
||||||
- the third _property_ is `font-weight` and its _value_ is `bold`
|
- the third _property_ is `font-weight` and its _value_ is `bold`
|
||||||
- the fourth _property_ is `stroke-width` and its _value_ is `2px`
|
- the fourth _property_ is `stroke-width` and its _value_ is `2px`
|
||||||
- the fifth _property_ is `stroke` and its _value_ is `yello`
|
- the fifth _property_ is `stroke` and its _value_ is `yellow`
|
||||||
|
|
||||||
### Apply classDef styles to states
|
### Apply classDef styles to states
|
||||||
|
|
||||||
|
@ -257,9 +257,11 @@ let us look at same example, where we have disabled the multiColor option.
|
|||||||
|
|
||||||
### Customizing Color scheme
|
### Customizing Color scheme
|
||||||
|
|
||||||
You can customize the color scheme using the `cScale0` to `cScale11` theme variables. Mermaid allows you to set unique colors for up-to 12 sections, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on.
|
You can customize the color scheme using the `cScale0` to `cScale11` theme variables, which will change the background colors. Mermaid allows you to set unique colors for up-to 12 sections, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on.
|
||||||
In case you have more than 12 sections, the color scheme will start to repeat.
|
In case you have more than 12 sections, the color scheme will start to repeat.
|
||||||
|
|
||||||
|
If you also want to change the foreground color of a section, you can do so use theme variables corresponding `cScaleLabel0` to `cScaleLabel11` variables.
|
||||||
|
|
||||||
NOTE: Default values for these theme variables are picked from the selected theme. If you want to override the default values, you can use the `initialize` call to add your custom theme variable values.
|
NOTE: Default values for these theme variables are picked from the selected theme. If you want to override the default values, you can use the `initialize` call to add your custom theme variable values.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@ -268,9 +270,9 @@ Now let's override the default values for the `cScale0` to `cScale2` variables:
|
|||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': {
|
%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': {
|
||||||
'cScale0': '#ff0000',
|
'cScale0': '#ff0000', 'cScaleLabel0': '#ffffff',
|
||||||
'cScale1': '#00ff00',
|
'cScale1': '#00ff00',
|
||||||
'cScale2': '#0000ff'
|
'cScale2': '#0000ff', 'cScaleLabel2': '#ffffff'
|
||||||
} } }%%
|
} } }%%
|
||||||
timeline
|
timeline
|
||||||
title History of Social Media Platform
|
title History of Social Media Platform
|
||||||
@ -286,9 +288,9 @@ Now let's override the default values for the `cScale0` to `cScale2` variables:
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': {
|
%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': {
|
||||||
'cScale0': '#ff0000',
|
'cScale0': '#ff0000', 'cScaleLabel0': '#ffffff',
|
||||||
'cScale1': '#00ff00',
|
'cScale1': '#00ff00',
|
||||||
'cScale2': '#0000ff'
|
'cScale2': '#0000ff', 'cScaleLabel2': '#ffffff'
|
||||||
} } }%%
|
} } }%%
|
||||||
timeline
|
timeline
|
||||||
title History of Social Media Platform
|
title History of Social Media Platform
|
||||||
|
18
package.json
18
package.json
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "mermaid-monorepo",
|
"name": "mermaid-monorepo",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "10.2.3",
|
"version": "10.2.4",
|
||||||
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@8.6.4",
|
"packageManager": "pnpm@8.6.5",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"diagram",
|
"diagram",
|
||||||
"markdown",
|
"markdown",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"build:watch": "pnpm build:vite --watch",
|
"build:watch": "pnpm build:vite --watch",
|
||||||
"build": "pnpm run -r clean && pnpm build:types && pnpm build:vite",
|
"build": "pnpm run -r clean && pnpm build:types && pnpm build:vite",
|
||||||
"dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"",
|
"dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"",
|
||||||
"dev:coverage": "VITE_COVERAGE=true pnpm dev",
|
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev",
|
||||||
"release": "pnpm build",
|
"release": "pnpm build",
|
||||||
"lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .",
|
"lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .",
|
||||||
"lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts",
|
"lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts",
|
||||||
@ -31,7 +31,8 @@
|
|||||||
"cypress": "cypress run",
|
"cypress": "cypress run",
|
||||||
"cypress:open": "cypress open",
|
"cypress:open": "cypress open",
|
||||||
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
|
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
|
||||||
"e2e:coverage": "VITE_COVERAGE=true pnpm e2e",
|
"coverage:cypress:clean": "rimraf .nyc_output coverage/cypress",
|
||||||
|
"e2e:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm e2e",
|
||||||
"coverage:merge": "ts-node-esm scripts/coverage.ts",
|
"coverage:merge": "ts-node-esm scripts/coverage.ts",
|
||||||
"coverage": "pnpm test:coverage --run && pnpm e2e:coverage && pnpm coverage:merge",
|
"coverage": "pnpm test:coverage --run && pnpm e2e:coverage && pnpm coverage:merge",
|
||||||
"ci": "vitest run",
|
"ci": "vitest run",
|
||||||
@ -77,9 +78,10 @@
|
|||||||
"@types/rollup-plugin-visualizer": "^4.2.1",
|
"@types/rollup-plugin-visualizer": "^4.2.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||||
"@typescript-eslint/parser": "^5.59.0",
|
"@typescript-eslint/parser": "^5.59.0",
|
||||||
"@vitest/coverage-istanbul": "^0.32.2",
|
"@vitest/coverage-v8": "^0.32.2",
|
||||||
"@vitest/spy": "^0.32.2",
|
"@vitest/spy": "^0.32.2",
|
||||||
"@vitest/ui": "^0.32.2",
|
"@vitest/ui": "^0.32.2",
|
||||||
|
"ajv": "^8.12.0",
|
||||||
"concurrently": "^8.0.1",
|
"concurrently": "^8.0.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"coveralls": "^3.1.1",
|
"coveralls": "^3.1.1",
|
||||||
@ -91,20 +93,20 @@
|
|||||||
"eslint-plugin-cypress": "^2.13.2",
|
"eslint-plugin-cypress": "^2.13.2",
|
||||||
"eslint-plugin-html": "^7.1.0",
|
"eslint-plugin-html": "^7.1.0",
|
||||||
"eslint-plugin-jest": "^27.2.1",
|
"eslint-plugin-jest": "^27.2.1",
|
||||||
"eslint-plugin-jsdoc": "^43.0.7",
|
"eslint-plugin-jsdoc": "^46.0.0",
|
||||||
"eslint-plugin-json": "^3.1.0",
|
"eslint-plugin-json": "^3.1.0",
|
||||||
"eslint-plugin-lodash": "^7.4.0",
|
"eslint-plugin-lodash": "^7.4.0",
|
||||||
"eslint-plugin-markdown": "^3.0.0",
|
"eslint-plugin-markdown": "^3.0.0",
|
||||||
"eslint-plugin-no-only-tests": "^3.1.0",
|
"eslint-plugin-no-only-tests": "^3.1.0",
|
||||||
"eslint-plugin-tsdoc": "^0.2.17",
|
"eslint-plugin-tsdoc": "^0.2.17",
|
||||||
"eslint-plugin-unicorn": "^46.0.0",
|
"eslint-plugin-unicorn": "^47.0.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"globby": "^13.1.4",
|
"globby": "^13.1.4",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"jison": "^0.4.18",
|
"jison": "^0.4.18",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsdom": "^21.1.1",
|
"jsdom": "^22.0.0",
|
||||||
"lint-staged": "^13.2.1",
|
"lint-staged": "^13.2.1",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
|
@ -22,7 +22,6 @@ export const setInfo = (inf) => {
|
|||||||
info = inf;
|
info = inf;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @returns Returns the info flag */
|
|
||||||
export const getInfo = () => {
|
export const getInfo = () => {
|
||||||
return info;
|
return info;
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,6 @@ import { log, getConfig, setupGraphViewbox } from './mermaidUtils.js';
|
|||||||
* @param {any} text
|
* @param {any} text
|
||||||
* @param {any} id
|
* @param {any} id
|
||||||
* @param {any} version
|
* @param {any} version
|
||||||
* @param diagObj
|
|
||||||
*/
|
*/
|
||||||
export const draw = (text, id, version) => {
|
export const draw = (text, id, version) => {
|
||||||
try {
|
try {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const warning = (s: string) => {
|
const warning = (s: string) => {
|
||||||
// Todo remove debug code
|
// Todo remove debug code
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@ -28,7 +29,6 @@ export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
|
|||||||
export let getConfig: () => object;
|
export let getConfig: () => object;
|
||||||
export let sanitizeText: (str: string) => string;
|
export let sanitizeText: (str: string) => string;
|
||||||
export let commonDb: () => object;
|
export let commonDb: () => object;
|
||||||
// eslint-disable @typescript-eslint/no-explicit-any
|
|
||||||
export let setupGraphViewbox: (
|
export let setupGraphViewbox: (
|
||||||
graph: any,
|
graph: any,
|
||||||
svgElem: any,
|
svgElem: any,
|
||||||
|
@ -4,4 +4,5 @@ export default {
|
|||||||
'src/docs/**': ['pnpm --filter mermaid run docs:build --git'],
|
'src/docs/**': ['pnpm --filter mermaid run docs:build --git'],
|
||||||
'src/docs.mts': ['pnpm --filter mermaid run docs:build --git'],
|
'src/docs.mts': ['pnpm --filter mermaid run docs:build --git'],
|
||||||
'src/(defaultConfig|config|mermaidAPI).ts': ['pnpm --filter mermaid run docs:build --git'],
|
'src/(defaultConfig|config|mermaidAPI).ts': ['pnpm --filter mermaid run docs:build --git'],
|
||||||
|
'src/schemas/config.schema.yaml': ['pnpm --filter mermaid run types:build-config --git'],
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mermaid",
|
"name": "mermaid",
|
||||||
"version": "10.2.3",
|
"version": "10.2.4",
|
||||||
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "./dist/mermaid.core.mjs",
|
"module": "./dist/mermaid.core.mjs",
|
||||||
@ -27,11 +27,14 @@
|
|||||||
"docs:code": "typedoc src/defaultConfig.ts src/config.ts src/mermaidAPI.ts && prettier --write ./src/docs/config/setup",
|
"docs:code": "typedoc src/defaultConfig.ts src/config.ts src/mermaidAPI.ts && prettier --write ./src/docs/config/setup",
|
||||||
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts",
|
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts",
|
||||||
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts --verify",
|
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts --verify",
|
||||||
"docs:pre:vitepress": "rimraf src/vitepress && pnpm docs:code && ts-node-esm src/docs.mts --vitepress",
|
"docs:pre:vitepress": "pnpm --filter ./src/docs prefetch && rimraf src/vitepress && pnpm docs:code && ts-node-esm src/docs.mts --vitepress && pnpm --filter ./src/vitepress install --no-frozen-lockfile --ignore-scripts",
|
||||||
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm --filter ./ install --no-frozen-lockfile --ignore-scripts && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
||||||
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./ src/vitepress dev\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
|
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
|
||||||
|
"docs:dev:docker": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev:docker\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
|
||||||
"docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress",
|
"docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress",
|
||||||
"docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"",
|
"docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"",
|
||||||
|
"types:build-config": "ts-node-esm --transpileOnly scripts/create-types-from-json-schema.mts",
|
||||||
|
"types:verify-config": "ts-node-esm scripts/create-types-from-json-schema.mts --verify",
|
||||||
"release": "pnpm build",
|
"release": "pnpm build",
|
||||||
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm -w run build"
|
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm -w run build"
|
||||||
},
|
},
|
||||||
@ -53,13 +56,16 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
|
"@types/d3-scale": "^4.0.3",
|
||||||
|
"@types/d3-scale-chromatic": "^3.0.0",
|
||||||
"cytoscape": "^3.23.0",
|
"cytoscape": "^3.23.0",
|
||||||
"cytoscape-cose-bilkent": "^4.1.0",
|
"cytoscape-cose-bilkent": "^4.1.0",
|
||||||
"cytoscape-fcose": "^2.1.0",
|
"cytoscape-fcose": "^2.1.0",
|
||||||
"d3": "^7.4.0",
|
"d3": "^7.4.0",
|
||||||
|
"d3-sankey": "^0.12.3",
|
||||||
"dagre-d3-es": "7.0.10",
|
"dagre-d3-es": "7.0.10",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"dompurify": "3.0.3",
|
"dompurify": "3.0.4",
|
||||||
"elkjs": "^0.8.2",
|
"elkjs": "^0.8.2",
|
||||||
"khroma": "^2.0.0",
|
"khroma": "^2.0.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
@ -71,8 +77,10 @@
|
|||||||
"web-worker": "^1.2.0"
|
"web-worker": "^1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@adobe/jsonschema2md": "^7.1.4",
|
||||||
"@types/cytoscape": "^3.19.9",
|
"@types/cytoscape": "^3.19.9",
|
||||||
"@types/d3": "^7.4.0",
|
"@types/d3": "^7.4.0",
|
||||||
|
"@types/d3-sankey": "^0.12.1",
|
||||||
"@types/d3-selection": "^3.0.5",
|
"@types/d3-selection": "^3.0.5",
|
||||||
"@types/dompurify": "^3.0.2",
|
"@types/dompurify": "^3.0.2",
|
||||||
"@types/jsdom": "^21.1.1",
|
"@types/jsdom": "^21.1.1",
|
||||||
@ -83,6 +91,7 @@
|
|||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||||
"@typescript-eslint/parser": "^5.59.0",
|
"@typescript-eslint/parser": "^5.59.0",
|
||||||
|
"ajv": "^8.11.2",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"concurrently": "^8.0.1",
|
"concurrently": "^8.0.1",
|
||||||
"coveralls": "^3.1.1",
|
"coveralls": "^3.1.1",
|
||||||
@ -92,7 +101,8 @@
|
|||||||
"globby": "^13.1.4",
|
"globby": "^13.1.4",
|
||||||
"jison": "^0.4.18",
|
"jison": "^0.4.18",
|
||||||
"js-base64": "^3.7.5",
|
"js-base64": "^3.7.5",
|
||||||
"jsdom": "^21.1.1",
|
"jsdom": "^22.0.0",
|
||||||
|
"json-schema-to-typescript": "^11.0.3",
|
||||||
"micromatch": "^4.0.5",
|
"micromatch": "^4.0.5",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
@ -105,6 +115,7 @@
|
|||||||
"typedoc-plugin-markdown": "^3.15.2",
|
"typedoc-plugin-markdown": "^3.15.2",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"unist-util-flatmap": "^1.0.0",
|
"unist-util-flatmap": "^1.0.0",
|
||||||
|
"unist-util-visit": "^4.1.2",
|
||||||
"vitepress": "^1.0.0-alpha.72",
|
"vitepress": "^1.0.0-alpha.72",
|
||||||
"vitepress-plugin-search": "^1.0.4-alpha.20"
|
"vitepress-plugin-search": "^1.0.4-alpha.20"
|
||||||
},
|
},
|
||||||
|
252
packages/mermaid/scripts/create-types-from-json-schema.mts
Normal file
252
packages/mermaid/scripts/create-types-from-json-schema.mts
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
/**
|
||||||
|
* Script to load Mermaid Config JSON Schema from YAML and to:
|
||||||
|
*
|
||||||
|
* - Validate JSON Schema
|
||||||
|
*
|
||||||
|
* Then to generate:
|
||||||
|
*
|
||||||
|
* - config.types.ts TypeScript file
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
import { readFile, writeFile } from 'node:fs/promises';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import assert from 'node:assert';
|
||||||
|
import { execFile } from 'node:child_process';
|
||||||
|
import { promisify } from 'node:util';
|
||||||
|
|
||||||
|
import { load, JSON_SCHEMA } from 'js-yaml';
|
||||||
|
import { compile, type JSONSchema } from 'json-schema-to-typescript';
|
||||||
|
|
||||||
|
import _Ajv2019, { type JSONSchemaType } from 'ajv/dist/2019.js';
|
||||||
|
|
||||||
|
// Workaround for wrong AJV types, see
|
||||||
|
// https://github.com/ajv-validator/ajv/issues/2132#issuecomment-1290409907
|
||||||
|
const Ajv2019 = _Ajv2019 as unknown as typeof _Ajv2019.default;
|
||||||
|
|
||||||
|
// !!! -- The config.type.js file is created by this script -- !!!
|
||||||
|
import type { MermaidConfig } from '../src/config.type.js';
|
||||||
|
|
||||||
|
// options for running the main command
|
||||||
|
const verifyOnly = process.argv.includes('--verify');
|
||||||
|
/** If `true`, automatically `git add` any changes (i.e. during `pnpm run pre-commit`)*/
|
||||||
|
const git = process.argv.includes('--git');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All of the keys in the mermaid config that have a mermaid diagram config.
|
||||||
|
*/
|
||||||
|
const MERMAID_CONFIG_DIAGRAM_KEYS = [
|
||||||
|
'flowchart',
|
||||||
|
'sequence',
|
||||||
|
'gantt',
|
||||||
|
'journey',
|
||||||
|
'class',
|
||||||
|
'state',
|
||||||
|
'er',
|
||||||
|
'pie',
|
||||||
|
'quadrantChart',
|
||||||
|
'requirement',
|
||||||
|
'mindmap',
|
||||||
|
'timeline',
|
||||||
|
'gitGraph',
|
||||||
|
'c4',
|
||||||
|
'sankey',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the MermaidConfig JSON schema YAML file.
|
||||||
|
*
|
||||||
|
* @returns The loaded JSON Schema, use {@link validateSchema} to confirm it is a valid JSON Schema.
|
||||||
|
*/
|
||||||
|
async function loadJsonSchemaFromYaml() {
|
||||||
|
const configSchemaFile = join('src', 'schemas', 'config.schema.yaml');
|
||||||
|
const contentsYaml = await readFile(configSchemaFile, { encoding: 'utf8' });
|
||||||
|
const jsonSchema = load(contentsYaml, {
|
||||||
|
filename: configSchemaFile,
|
||||||
|
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
|
||||||
|
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
|
||||||
|
schema: JSON_SCHEMA,
|
||||||
|
});
|
||||||
|
return jsonSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given value is a valid JSON Schema object.
|
||||||
|
*
|
||||||
|
* @param jsonSchema - The value to validate as JSON Schema 2019-09
|
||||||
|
* @throws {Error} if the given object is invalid.
|
||||||
|
*/
|
||||||
|
function validateSchema(jsonSchema: unknown): asserts jsonSchema is JSONSchemaType<MermaidConfig> {
|
||||||
|
if (typeof jsonSchema !== 'object') {
|
||||||
|
throw new Error(`jsonSchema param is not an object: actual type is ${typeof jsonSchema}`);
|
||||||
|
}
|
||||||
|
if (jsonSchema === null) {
|
||||||
|
throw new Error('jsonSchema param must not be null');
|
||||||
|
}
|
||||||
|
const ajv = new Ajv2019({
|
||||||
|
allErrors: true,
|
||||||
|
allowUnionTypes: true,
|
||||||
|
strict: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
ajv.addKeyword({
|
||||||
|
keyword: 'meta:enum', // used by jsonschema2md (in docs.mts script)
|
||||||
|
errors: false,
|
||||||
|
});
|
||||||
|
ajv.addKeyword({
|
||||||
|
keyword: 'tsType', // used by json-schema-to-typescript
|
||||||
|
errors: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
ajv.compile(jsonSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a typescript definition from a JSON Schema using json-schema-to-typescript.
|
||||||
|
*
|
||||||
|
* @param mermaidConfigSchema - The input JSON Schema.
|
||||||
|
*/
|
||||||
|
async function generateTypescript(mermaidConfigSchema: JSONSchemaType<MermaidConfig>) {
|
||||||
|
/**
|
||||||
|
* Replace all usages of `allOf` with `extends`.
|
||||||
|
*
|
||||||
|
* `extends` is only valid JSON Schema in very old versions of JSON Schema.
|
||||||
|
* However, json-schema-to-typescript creates much nicer types when using
|
||||||
|
* `extends`, so we should use them instead when possible.
|
||||||
|
*
|
||||||
|
* @param schema - The input schema.
|
||||||
|
* @returns The schema with `allOf` replaced with `extends`.
|
||||||
|
*/
|
||||||
|
function replaceAllOfWithExtends(schema: JSONSchemaType<Record<string, any>>) {
|
||||||
|
if (schema['allOf']) {
|
||||||
|
const { allOf, ...schemaWithoutAllOf } = schema;
|
||||||
|
return {
|
||||||
|
...schemaWithoutAllOf,
|
||||||
|
extends: allOf,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For backwards compatibility with older Mermaid Typescript defs,
|
||||||
|
* we need to make all value optional instead of required.
|
||||||
|
*
|
||||||
|
* This is because the `MermaidConfig` type is used as an input, and everything is optional,
|
||||||
|
* since all the required values have default values.s
|
||||||
|
*
|
||||||
|
* In the future, we should make make the input to Mermaid `Partial<MermaidConfig>`.
|
||||||
|
*
|
||||||
|
* @todo TODO: Remove this function when Mermaid releases a new breaking change.
|
||||||
|
* @param schema - The input schema.
|
||||||
|
* @returns The schema with all required values removed.
|
||||||
|
*/
|
||||||
|
function removeRequired(schema: JSONSchemaType<Record<string, any>>) {
|
||||||
|
return { ...schema, required: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a temporary hack to control the order the types are generated in.
|
||||||
|
*
|
||||||
|
* By default, json-schema-to-typescript outputs the $defs in the order they
|
||||||
|
* are used, then any unused schemas at the end.
|
||||||
|
*
|
||||||
|
* **The only purpose of this function is to make the `git diff` simpler**
|
||||||
|
* **We should remove this later to simplify the code**
|
||||||
|
*
|
||||||
|
* @todo TODO: Remove this function in a future PR.
|
||||||
|
* @param schema - The input schema.
|
||||||
|
* @returns The schema with all `$ref`s removed.
|
||||||
|
*/
|
||||||
|
function unrefSubschemas(schema: JSONSchemaType<Record<string, any>>) {
|
||||||
|
return {
|
||||||
|
...schema,
|
||||||
|
properties: Object.fromEntries(
|
||||||
|
Object.entries(schema.properties).map(([key, propertySchema]) => {
|
||||||
|
if (MERMAID_CONFIG_DIAGRAM_KEYS.includes(key)) {
|
||||||
|
const { $ref, ...propertySchemaWithoutRef } = propertySchema as JSONSchemaType<unknown>;
|
||||||
|
if ($ref === undefined) {
|
||||||
|
throw Error(
|
||||||
|
`subSchema ${key} is in MERMAID_CONFIG_DIAGRAM_KEYS but does not have a $ref field`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const [
|
||||||
|
_root, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
_defs, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
defName,
|
||||||
|
] = $ref.split('/');
|
||||||
|
return [
|
||||||
|
key,
|
||||||
|
{
|
||||||
|
...propertySchemaWithoutRef,
|
||||||
|
tsType: defName,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [key, propertySchema];
|
||||||
|
})
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.ok(mermaidConfigSchema.$defs);
|
||||||
|
const modifiedSchema = {
|
||||||
|
...unrefSubschemas(removeRequired(mermaidConfigSchema)),
|
||||||
|
|
||||||
|
$defs: Object.fromEntries(
|
||||||
|
Object.entries(mermaidConfigSchema.$defs).map(([key, subSchema]) => {
|
||||||
|
return [key, removeRequired(replaceAllOfWithExtends(subSchema))];
|
||||||
|
})
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const typescriptFile = await compile(
|
||||||
|
modifiedSchema as JSONSchema, // json-schema-to-typescript only allows JSON Schema 4 as input type
|
||||||
|
'MermaidConfig',
|
||||||
|
{
|
||||||
|
additionalProperties: false, // in JSON Schema 2019-09, these are called `unevaluatedProperties`
|
||||||
|
unreachableDefinitions: true, // definition for FontConfig is unreachable
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO, should we somehow use the functions from `docs.mts` instead?
|
||||||
|
if (verifyOnly) {
|
||||||
|
const originalFile = await readFile('./src/config.type.ts', { encoding: 'utf-8' });
|
||||||
|
if (typescriptFile !== originalFile) {
|
||||||
|
console.error('❌ Error: ./src/config.type.ts will be changed.');
|
||||||
|
console.error("Please run 'pnpm run --filter mermaid types:build-config' to update this");
|
||||||
|
process.exitCode = 1;
|
||||||
|
} else {
|
||||||
|
console.log('✅ ./src/config.type.ts will be unchanged');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Writing typescript file to ./src/config.type.ts');
|
||||||
|
await writeFile('./src/config.type.ts', typescriptFile, { encoding: 'utf8' });
|
||||||
|
if (git) {
|
||||||
|
console.log('📧 Git: Adding ./src/config.type.ts changed');
|
||||||
|
await promisify(execFile)('git', ['add', './src/config.type.ts']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Main function */
|
||||||
|
async function main() {
|
||||||
|
if (verifyOnly) {
|
||||||
|
console.log(
|
||||||
|
'Verifying that ./src/config.type.ts is in sync with src/schemas/config.schema.yaml'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const configJsonSchema = await loadJsonSchemaFromYaml();
|
||||||
|
|
||||||
|
validateSchema(configJsonSchema);
|
||||||
|
|
||||||
|
// Generate types from JSON Schema
|
||||||
|
await generateTypescript(configJsonSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
@ -1,27 +1,24 @@
|
|||||||
import { MockedD3 } from './tests/MockedD3.js';
|
import { MockedD3 } from './tests/MockedD3.js';
|
||||||
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js';
|
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js';
|
||||||
import { D3Element } from './mermaidAPI.js';
|
import type { D3Element } from './mermaidAPI.js';
|
||||||
|
|
||||||
describe('accessibility', () => {
|
describe('accessibility', () => {
|
||||||
const fauxSvgNode = new MockedD3();
|
const fauxSvgNode: MockedD3 = new MockedD3();
|
||||||
|
|
||||||
describe('setA11yDiagramInfo', () => {
|
describe('setA11yDiagramInfo', () => {
|
||||||
it('sets the svg element role to "graphics-document document"', () => {
|
it('should set svg element role to "graphics-document document"', () => {
|
||||||
// @ts-ignore Required to easily handle the d3 select types
|
|
||||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||||
setA11yDiagramInfo(fauxSvgNode, 'flowchart');
|
setA11yDiagramInfo(fauxSvgNode, 'flowchart');
|
||||||
expect(svgAttrSpy).toHaveBeenCalledWith('role', 'graphics-document document');
|
expect(svgAttrSpy).toHaveBeenCalledWith('role', 'graphics-document document');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets the aria-roledescription to the diagram type', () => {
|
it('should set aria-roledescription to the diagram type', () => {
|
||||||
// @ts-ignore Required to easily handle the d3 select types
|
|
||||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||||
setA11yDiagramInfo(fauxSvgNode, 'flowchart');
|
setA11yDiagramInfo(fauxSvgNode, 'flowchart');
|
||||||
expect(svgAttrSpy).toHaveBeenCalledWith('aria-roledescription', 'flowchart');
|
expect(svgAttrSpy).toHaveBeenCalledWith('aria-roledescription', 'flowchart');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not set the aria-roledescription if the diagram type is empty', () => {
|
it('should not set aria-roledescription if the diagram type is empty', () => {
|
||||||
// @ts-ignore Required to easily handle the d3 select types
|
|
||||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||||
setA11yDiagramInfo(fauxSvgNode, '');
|
setA11yDiagramInfo(fauxSvgNode, '');
|
||||||
expect(svgAttrSpy).toHaveBeenCalledTimes(1);
|
expect(svgAttrSpy).toHaveBeenCalledTimes(1);
|
||||||
@ -32,8 +29,8 @@ describe('accessibility', () => {
|
|||||||
describe('addSVGa11yTitleDescription', () => {
|
describe('addSVGa11yTitleDescription', () => {
|
||||||
const givenId = 'theBaseId';
|
const givenId = 'theBaseId';
|
||||||
|
|
||||||
describe('with the given svg d3 object:', () => {
|
describe('with svg d3 object', () => {
|
||||||
it('does nothing if there is no insert defined', () => {
|
it('should do nothing if there is no insert defined', () => {
|
||||||
const noInsertSvg = {
|
const noInsertSvg = {
|
||||||
attr: vi.fn(),
|
attr: vi.fn(),
|
||||||
};
|
};
|
||||||
@ -42,26 +39,25 @@ describe('accessibility', () => {
|
|||||||
expect(noInsertAttrSpy).not.toHaveBeenCalled();
|
expect(noInsertAttrSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ----------------
|
// convenience functions to DRY up the spec
|
||||||
// Convenience functions to DRY up the spec
|
|
||||||
|
|
||||||
function expectAriaLabelledByIsTitleId(
|
function expectAriaLabelledByItTitleId(
|
||||||
svgD3Node: D3Element,
|
svgD3Node: D3Element,
|
||||||
title: string | null | undefined,
|
title: string | undefined,
|
||||||
desc: string | null | undefined,
|
desc: string | undefined,
|
||||||
givenId: string
|
givenId: string
|
||||||
) {
|
): void {
|
||||||
const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
|
const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
|
||||||
addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
|
addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
|
||||||
expect(svgAttrSpy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`);
|
expect(svgAttrSpy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectAriaDescribedByIsDescId(
|
function expectAriaDescribedByItDescId(
|
||||||
svgD3Node: D3Element,
|
svgD3Node: D3Element,
|
||||||
title: string | null | undefined,
|
title: string | undefined,
|
||||||
desc: string | null | undefined,
|
desc: string | undefined,
|
||||||
givenId: string
|
givenId: string
|
||||||
) {
|
): void {
|
||||||
const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
|
const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
|
||||||
addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
|
addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
|
||||||
expect(svgAttrSpy).toHaveBeenCalledWith('aria-describedby', `chart-desc-${givenId}`);
|
expect(svgAttrSpy).toHaveBeenCalledWith('aria-describedby', `chart-desc-${givenId}`);
|
||||||
@ -69,154 +65,148 @@ describe('accessibility', () => {
|
|||||||
|
|
||||||
function a11yTitleTagInserted(
|
function a11yTitleTagInserted(
|
||||||
svgD3Node: D3Element,
|
svgD3Node: D3Element,
|
||||||
title: string | null | undefined,
|
title: string | undefined,
|
||||||
desc: string | null | undefined,
|
desc: string | undefined,
|
||||||
givenId: string,
|
givenId: string,
|
||||||
callNumber: number
|
callNumber: number
|
||||||
) {
|
): void {
|
||||||
a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'title', title);
|
a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'title', title);
|
||||||
}
|
}
|
||||||
|
|
||||||
function a11yDescTagInserted(
|
function a11yDescTagInserted(
|
||||||
svgD3Node: D3Element,
|
svgD3Node: D3Element,
|
||||||
title: string | null | undefined,
|
title: string | undefined,
|
||||||
desc: string | null | undefined,
|
desc: string | undefined,
|
||||||
givenId: string,
|
givenId: string,
|
||||||
callNumber: number
|
callNumber: number
|
||||||
) {
|
): void {
|
||||||
a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'desc', desc);
|
a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'desc', desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
function a11yTagInserted(
|
function a11yTagInserted(
|
||||||
svgD3Node: D3Element,
|
_svgD3Node: D3Element,
|
||||||
title: string | null | undefined,
|
title: string | undefined,
|
||||||
desc: string | null | undefined,
|
desc: string | undefined,
|
||||||
givenId: string,
|
givenId: string,
|
||||||
callNumber: number,
|
callNumber: number,
|
||||||
expectedPrefix: string,
|
expectedPrefix: string,
|
||||||
expectedText: string | null | undefined
|
expectedText: string | undefined
|
||||||
) {
|
): void {
|
||||||
const fauxInsertedD3 = new MockedD3();
|
const fauxInsertedD3: MockedD3 = new MockedD3();
|
||||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxInsertedD3);
|
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxInsertedD3);
|
||||||
// @ts-ignore Required to easily handle the d3 select types
|
|
||||||
const titleAttrSpy = vi.spyOn(fauxInsertedD3, 'attr').mockReturnValue(fauxInsertedD3);
|
const titleAttrSpy = vi.spyOn(fauxInsertedD3, 'attr').mockReturnValue(fauxInsertedD3);
|
||||||
const titleTextSpy = vi.spyOn(fauxInsertedD3, 'text');
|
const titleTextSpy = vi.spyOn(fauxInsertedD3, 'text');
|
||||||
|
|
||||||
addSVGa11yTitleDescription(fauxSvgNode, title, desc, givenId);
|
addSVGa11yTitleDescription(fauxSvgNode, title, desc, givenId);
|
||||||
expect(svgInsertSpy).toHaveBeenCalledWith(expectedPrefix, ':first-child');
|
expect(svginsertpy).toHaveBeenCalledWith(expectedPrefix, ':first-child');
|
||||||
expect(titleAttrSpy).toHaveBeenCalledWith('id', `chart-${expectedPrefix}-${givenId}`);
|
expect(titleAttrSpy).toHaveBeenCalledWith('id', `chart-${expectedPrefix}-${givenId}`);
|
||||||
expect(titleTextSpy).toHaveBeenNthCalledWith(callNumber, expectedText);
|
expect(titleTextSpy).toHaveBeenNthCalledWith(callNumber, expectedText);
|
||||||
}
|
}
|
||||||
// ----------------
|
|
||||||
|
|
||||||
describe('given an a11y title', () => {
|
describe('with a11y title', () => {
|
||||||
const a11yTitle = 'a11y title';
|
const a11yTitle = 'a11y title';
|
||||||
|
|
||||||
describe('given an a11y description', () => {
|
describe('with a11y description', () => {
|
||||||
const a11yDesc = 'a11y description';
|
const a11yDesc = 'a11y description';
|
||||||
|
|
||||||
it('sets aria-labelledby to the title id inserted as a child', () => {
|
it('shold set aria-labelledby to the title id inserted as a child', () => {
|
||||||
expectAriaLabelledByIsTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
expectAriaLabelledByItTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets aria-describedby to the description id inserted as a child', () => {
|
it('should set aria-describedby to the description id inserted as a child', () => {
|
||||||
expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
expectAriaDescribedByItDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inserts a title tag as the first child with the text set to the accTitle given', () => {
|
it('should insert title tag as the first child with the text set to the accTitle given', () => {
|
||||||
a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 2);
|
a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => {
|
it('should insert desc tag as the 2nd child with the text set to accDescription given', () => {
|
||||||
a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
|
a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(`no a11y description`, () => {
|
describe(`without a11y description`, () => {
|
||||||
const a11yDesc = undefined;
|
const a11yDesc = undefined;
|
||||||
|
|
||||||
it('sets aria-labelledby to the title id inserted as a child', () => {
|
it('should set aria-labelledby to the title id inserted as a child', () => {
|
||||||
expectAriaLabelledByIsTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
expectAriaLabelledByItTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('no aria-describedby is set', () => {
|
it('should not set aria-describedby', () => {
|
||||||
// @ts-ignore Required to easily handle the d3 select types
|
|
||||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||||
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
|
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inserts a title tag as the first child with the text set to the accTitle given', () => {
|
it('should insert title tag as the first child with the text set to the accTitle given', () => {
|
||||||
a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
|
a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('no description tag is inserted', () => {
|
it('should not insert description tag', () => {
|
||||||
const fauxTitle = new MockedD3();
|
const fauxTitle: MockedD3 = new MockedD3();
|
||||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
||||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||||
expect(svgInsertSpy).not.toHaveBeenCalledWith('desc', ':first-child');
|
expect(svginsertpy).not.toHaveBeenCalledWith('desc', ':first-child');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('no a11y title', () => {
|
describe('without a11y title', () => {
|
||||||
const a11yTitle = undefined;
|
const a11yTitle = undefined;
|
||||||
|
|
||||||
describe('given an a11y description', () => {
|
describe('with a11y description', () => {
|
||||||
const a11yDesc = 'a11y description';
|
const a11yDesc = 'a11y description';
|
||||||
|
|
||||||
it('no aria-labelledby is set', () => {
|
it('should not set aria-labelledby', () => {
|
||||||
// @ts-ignore Required to easily handle the d3 select types
|
|
||||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||||
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
|
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('no title tag inserted', () => {
|
it('should not insert title tag', () => {
|
||||||
const fauxTitle = new MockedD3();
|
const fauxTitle: MockedD3 = new MockedD3();
|
||||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
||||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||||
expect(svgInsertSpy).not.toHaveBeenCalledWith('title', ':first-child');
|
expect(svginsertpy).not.toHaveBeenCalledWith('title', ':first-child');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets aria-describedby to the description id inserted as a child', () => {
|
it('should set aria-describedby to the description id inserted as a child', () => {
|
||||||
expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
expectAriaDescribedByItDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => {
|
it('should insert desc tag as the 2nd child with the text set to accDescription given', () => {
|
||||||
a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
|
a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('no a11y description', () => {
|
describe('without a11y description', () => {
|
||||||
const a11yDesc = undefined;
|
const a11yDesc = undefined;
|
||||||
|
|
||||||
it('no aria-labelledby is set', () => {
|
it('should not set aria-labelledby', () => {
|
||||||
// @ts-ignore Required to easily handle the d3 select types
|
|
||||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||||
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
|
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('no aria-describedby is set', () => {
|
it('should not set aria-describedby', () => {
|
||||||
// @ts-ignore Required to easily handle the d3 select types
|
|
||||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||||
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
|
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('no title tag inserted', () => {
|
it('should not insert title tag', () => {
|
||||||
const fauxTitle = new MockedD3();
|
const fauxTitle: MockedD3 = new MockedD3();
|
||||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
||||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||||
expect(svgInsertSpy).not.toHaveBeenCalledWith('title', ':first-child');
|
expect(svginsertpy).not.toHaveBeenCalledWith('title', ':first-child');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('no description tag inserted', () => {
|
it('should not insert description tag', () => {
|
||||||
const fauxDesc = new MockedD3();
|
const fauxDesc: MockedD3 = new MockedD3();
|
||||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxDesc);
|
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxDesc);
|
||||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||||
expect(svgInsertSpy).not.toHaveBeenCalledWith('desc', ':first-child');
|
expect(svginsertpy).not.toHaveBeenCalledWith('desc', ':first-child');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Accessibility (a11y) functions, types, helpers
|
* Accessibility (a11y) functions, types, helpers.
|
||||||
|
*
|
||||||
* @see https://www.w3.org/WAI/
|
* @see https://www.w3.org/WAI/
|
||||||
* @see https://www.w3.org/TR/wai-aria-1.1/
|
* @see https://www.w3.org/TR/wai-aria-1.1/
|
||||||
* @see https://www.w3.org/TR/svg-aam-1.0/
|
* @see https://www.w3.org/TR/svg-aam-1.0/
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
import { D3Element } from './mermaidAPI.js';
|
import type { D3Element } from './mermaidAPI.js';
|
||||||
|
|
||||||
import isEmpty from 'lodash-es/isEmpty.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SVG element role:
|
* SVG element role:
|
||||||
@ -21,50 +19,47 @@ import isEmpty from 'lodash-es/isEmpty.js';
|
|||||||
const SVG_ROLE = 'graphics-document document';
|
const SVG_ROLE = 'graphics-document document';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add role and aria-roledescription to the svg element
|
* Add role and aria-roledescription to the svg element.
|
||||||
*
|
*
|
||||||
* @param svg - d3 object that contains the SVG HTML element
|
* @param svg - d3 object that contains the SVG HTML element
|
||||||
* @param diagramType - diagram name for to the aria-roledescription
|
* @param diagramType - diagram name for to the aria-roledescription
|
||||||
*/
|
*/
|
||||||
export function setA11yDiagramInfo(svg: D3Element, diagramType: string | null | undefined) {
|
export function setA11yDiagramInfo(svg: D3Element, diagramType: string) {
|
||||||
svg.attr('role', SVG_ROLE);
|
svg.attr('role', SVG_ROLE);
|
||||||
if (!isEmpty(diagramType)) {
|
if (diagramType !== '') {
|
||||||
svg.attr('aria-roledescription', diagramType);
|
svg.attr('aria-roledescription', diagramType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an accessible title and/or description element to a chart.
|
* Add an accessible title and/or description element to a chart.
|
||||||
* The title is usually not displayed and the description is never displayed.
|
* The title is usually not displayed and the description is never displayed.
|
||||||
*
|
*
|
||||||
* The following charts display their title as a visual and accessibility element: gantt
|
* The following charts display their title as a visual and accessibility element: gantt.
|
||||||
*
|
*
|
||||||
* @param svg - d3 node to insert the a11y title and desc info
|
* @param svg - d3 node to insert the a11y title and desc info
|
||||||
* @param a11yTitle - a11y title. null and undefined are meaningful: means to skip it
|
* @param a11yTitle - a11y title. undefined or empty strings mean to skip them
|
||||||
* @param a11yDesc - a11y description. null and undefined are meaningful: means to skip it
|
* @param a11yDesc - a11y description. undefined or empty strings mean to skip them
|
||||||
* @param baseId - id used to construct the a11y title and description id
|
* @param baseId - id used to construct the a11y title and description id
|
||||||
*/
|
*/
|
||||||
export function addSVGa11yTitleDescription(
|
export function addSVGa11yTitleDescription(
|
||||||
svg: D3Element,
|
svg: D3Element,
|
||||||
a11yTitle: string | null | undefined,
|
a11yTitle: string | undefined,
|
||||||
a11yDesc: string | null | undefined,
|
a11yDesc: string | undefined,
|
||||||
baseId: string
|
baseId: string
|
||||||
) {
|
): void {
|
||||||
if (svg.insert === undefined) {
|
if (svg.insert === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a11yTitle || a11yDesc) {
|
if (a11yDesc) {
|
||||||
if (a11yDesc) {
|
const descId = `chart-desc-${baseId}`;
|
||||||
const descId = 'chart-desc-' + baseId;
|
svg.attr('aria-describedby', descId);
|
||||||
svg.attr('aria-describedby', descId);
|
svg.insert('desc', ':first-child').attr('id', descId).text(a11yDesc);
|
||||||
svg.insert('desc', ':first-child').attr('id', descId).text(a11yDesc);
|
}
|
||||||
}
|
if (a11yTitle) {
|
||||||
if (a11yTitle) {
|
const titleId = `chart-title-${baseId}`;
|
||||||
const titleId = 'chart-title-' + baseId;
|
svg.attr('aria-labelledby', titleId);
|
||||||
svg.attr('aria-labelledby', titleId);
|
svg.insert('title', ':first-child').attr('id', titleId).text(a11yTitle);
|
||||||
svg.insert('title', ':first-child').attr('id', titleId).text(a11yTitle);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* of src to dst in order.
|
* of src to dst in order.
|
||||||
* @param {any} dst - The destination of the merge
|
* @param {any} dst - The destination of the merge
|
||||||
* @param {any} src - The source object(s) to merge into destination
|
* @param {any} src - The source object(s) to merge into destination
|
||||||
* @param {{ depth: number; clobber: boolean }} [config={ depth: 2, clobber: false }] - Depth: depth
|
* @param {{ depth: number; clobber: boolean }} [config] - Depth: depth
|
||||||
* to traverse within src and dst for merging - clobber: should dissimilar types clobber (default:
|
* to traverse within src and dst for merging - clobber: should dissimilar types clobber (default:
|
||||||
* { depth: 2, clobber: false }). Default is `{ depth: 2, clobber: false }`
|
* { depth: 2, clobber: false }). Default is `{ depth: 2, clobber: false }`
|
||||||
* @returns {any}
|
* @returns {any}
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
import * as configApi from './config.js';
|
|
||||||
|
|
||||||
describe('when working with site config', function () {
|
|
||||||
beforeEach(() => {
|
|
||||||
// Resets the site config to default config
|
|
||||||
configApi.setSiteConfig({});
|
|
||||||
});
|
|
||||||
it('should set site config and config properly', function () {
|
|
||||||
let config_0 = { foo: 'bar', bar: 0 };
|
|
||||||
configApi.setSiteConfig(config_0);
|
|
||||||
let config_1 = configApi.getSiteConfig();
|
|
||||||
let config_2 = configApi.getConfig();
|
|
||||||
expect(config_1.foo).toEqual(config_0.foo);
|
|
||||||
expect(config_1.bar).toEqual(config_0.bar);
|
|
||||||
expect(config_1).toEqual(config_2);
|
|
||||||
});
|
|
||||||
it('should respect secure keys when applying directives', function () {
|
|
||||||
let config_0 = {
|
|
||||||
foo: 'bar',
|
|
||||||
bar: 'cant-be-changed',
|
|
||||||
secure: [...configApi.defaultConfig.secure, 'bar'],
|
|
||||||
};
|
|
||||||
configApi.setSiteConfig(config_0);
|
|
||||||
const directive = { foo: 'baf', bar: 'should-not-be-allowed' };
|
|
||||||
const cfg = configApi.updateCurrentConfig(config_0, [directive]);
|
|
||||||
expect(cfg.foo).toEqual(directive.foo);
|
|
||||||
expect(cfg.bar).toBe(config_0.bar);
|
|
||||||
});
|
|
||||||
it('should set reset config properly', function () {
|
|
||||||
let config_0 = { foo: 'bar', bar: 0 };
|
|
||||||
configApi.setSiteConfig(config_0);
|
|
||||||
let config_1 = { foo: 'baf' };
|
|
||||||
configApi.setConfig(config_1);
|
|
||||||
let config_2 = configApi.getConfig();
|
|
||||||
expect(config_2.foo).toEqual(config_1.foo);
|
|
||||||
configApi.reset();
|
|
||||||
let config_3 = configApi.getConfig();
|
|
||||||
expect(config_3.foo).toEqual(config_0.foo);
|
|
||||||
let config_4 = configApi.getSiteConfig();
|
|
||||||
expect(config_4.foo).toEqual(config_0.foo);
|
|
||||||
});
|
|
||||||
it('should set global reset config properly', function () {
|
|
||||||
let config_0 = { foo: 'bar', bar: 0 };
|
|
||||||
configApi.setSiteConfig(config_0);
|
|
||||||
let config_1 = configApi.getSiteConfig();
|
|
||||||
expect(config_1.foo).toEqual(config_0.foo);
|
|
||||||
let config_2 = configApi.getConfig();
|
|
||||||
expect(config_2.foo).toEqual(config_0.foo);
|
|
||||||
configApi.setConfig({ foobar: 'bar0' });
|
|
||||||
let config_3 = configApi.getConfig();
|
|
||||||
expect(config_3.foobar).toEqual('bar0');
|
|
||||||
configApi.reset();
|
|
||||||
let config_4 = configApi.getConfig();
|
|
||||||
expect(config_4.foobar).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
72
packages/mermaid/src/config.spec.ts
Normal file
72
packages/mermaid/src/config.spec.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import * as configApi from './config.js';
|
||||||
|
|
||||||
|
describe('when working with site config', function () {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Resets the site config to default config
|
||||||
|
configApi.setSiteConfig({});
|
||||||
|
});
|
||||||
|
it('should set site config and config properly', function () {
|
||||||
|
const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
|
||||||
|
configApi.setSiteConfig(config_0);
|
||||||
|
const config_1 = configApi.getSiteConfig();
|
||||||
|
const config_2 = configApi.getConfig();
|
||||||
|
expect(config_1.fontFamily).toEqual(config_0.fontFamily);
|
||||||
|
expect(config_1.fontSize).toEqual(config_0.fontSize);
|
||||||
|
expect(config_1).toEqual(config_2);
|
||||||
|
});
|
||||||
|
it('should respect secure keys when applying directives', function () {
|
||||||
|
const config_0 = {
|
||||||
|
fontFamily: 'foo-font',
|
||||||
|
fontSize: 12345, // can't be changed
|
||||||
|
secure: [...configApi.defaultConfig.secure!, 'fontSize'],
|
||||||
|
};
|
||||||
|
configApi.setSiteConfig(config_0);
|
||||||
|
const directive = { fontFamily: 'baf', fontSize: 54321 /* fontSize shouldn't be changed */ };
|
||||||
|
const cfg = configApi.updateCurrentConfig(config_0, [directive]);
|
||||||
|
expect(cfg.fontFamily).toEqual(directive.fontFamily);
|
||||||
|
expect(cfg.fontSize).toBe(config_0.fontSize);
|
||||||
|
});
|
||||||
|
it('should allow setting partial options', function () {
|
||||||
|
const defaultConfig = configApi.getConfig();
|
||||||
|
|
||||||
|
configApi.setConfig({
|
||||||
|
quadrantChart: {
|
||||||
|
chartHeight: 600,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedConfig = configApi.getConfig();
|
||||||
|
|
||||||
|
// deep options we didn't update should remain the same
|
||||||
|
expect(defaultConfig.quadrantChart!.chartWidth).toEqual(
|
||||||
|
updatedConfig.quadrantChart!.chartWidth
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should set reset config properly', function () {
|
||||||
|
const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
|
||||||
|
configApi.setSiteConfig(config_0);
|
||||||
|
const config_1 = { fontFamily: 'baf' };
|
||||||
|
configApi.setConfig(config_1);
|
||||||
|
const config_2 = configApi.getConfig();
|
||||||
|
expect(config_2.fontFamily).toEqual(config_1.fontFamily);
|
||||||
|
configApi.reset();
|
||||||
|
const config_3 = configApi.getConfig();
|
||||||
|
expect(config_3.fontFamily).toEqual(config_0.fontFamily);
|
||||||
|
const config_4 = configApi.getSiteConfig();
|
||||||
|
expect(config_4.fontFamily).toEqual(config_0.fontFamily);
|
||||||
|
});
|
||||||
|
it('should set global reset config properly', function () {
|
||||||
|
const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
|
||||||
|
configApi.setSiteConfig(config_0);
|
||||||
|
const config_1 = configApi.getSiteConfig();
|
||||||
|
expect(config_1.fontFamily).toEqual(config_0.fontFamily);
|
||||||
|
const config_2 = configApi.getConfig();
|
||||||
|
expect(config_2.fontFamily).toEqual(config_0.fontFamily);
|
||||||
|
configApi.setConfig({ altFontFamily: 'bar-font' });
|
||||||
|
const config_3 = configApi.getConfig();
|
||||||
|
expect(config_3.altFontFamily).toEqual('bar-font');
|
||||||
|
configApi.reset();
|
||||||
|
const config_4 = configApi.getConfig();
|
||||||
|
expect(config_4.altFontFamily).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
@ -226,9 +226,11 @@ export const reset = (config = siteConfig): void => {
|
|||||||
updateCurrentConfig(config, directives);
|
updateCurrentConfig(config, directives);
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ConfigWarning {
|
const ConfigWarning = {
|
||||||
'LAZY_LOAD_DEPRECATED' = 'The configuration options lazyLoadedDiagrams and loadExternalDiagramsAtStartup are deprecated. Please use registerExternalDiagrams instead.',
|
LAZY_LOAD_DEPRECATED:
|
||||||
}
|
'The configuration options lazyLoadedDiagrams and loadExternalDiagramsAtStartup are deprecated. Please use registerExternalDiagrams instead.',
|
||||||
|
} as const;
|
||||||
|
|
||||||
type ConfigWarningStrings = keyof typeof ConfigWarning;
|
type ConfigWarningStrings = keyof typeof ConfigWarning;
|
||||||
const issuedWarnings: { [key in ConfigWarningStrings]?: boolean } = {};
|
const issuedWarnings: { [key in ConfigWarningStrings]?: boolean } = {};
|
||||||
const issueWarning = (warning: ConfigWarningStrings) => {
|
const issueWarning = (warning: ConfigWarningStrings) => {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -602,6 +602,8 @@ const doublecircle = async (parent, node) => {
|
|||||||
const outerCircle = circleGroup.insert('circle');
|
const outerCircle = circleGroup.insert('circle');
|
||||||
const innerCircle = circleGroup.insert('circle');
|
const innerCircle = circleGroup.insert('circle');
|
||||||
|
|
||||||
|
circleGroup.attr('class', node.class);
|
||||||
|
|
||||||
// center the circle around its coordinate
|
// center the circle around its coordinate
|
||||||
outerCircle
|
outerCircle
|
||||||
.attr('style', node.style)
|
.attr('style', node.style)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,7 @@ import errorDiagram from '../diagrams/error/errorDiagram.js';
|
|||||||
import flowchartElk from '../diagrams/flowchart/elk/detector.js';
|
import flowchartElk from '../diagrams/flowchart/elk/detector.js';
|
||||||
import timeline from '../diagrams/timeline/detector.js';
|
import timeline from '../diagrams/timeline/detector.js';
|
||||||
import mindmap from '../diagrams/mindmap/detector.js';
|
import mindmap from '../diagrams/mindmap/detector.js';
|
||||||
|
import sankey from '../diagrams/sankey/sankeyDetector.js';
|
||||||
import { registerLazyLoadedDiagrams } from './detectType.js';
|
import { registerLazyLoadedDiagrams } from './detectType.js';
|
||||||
import { registerDiagram } from './diagramAPI.js';
|
import { registerDiagram } from './diagramAPI.js';
|
||||||
|
|
||||||
@ -79,6 +80,7 @@ export const addDiagrams = () => {
|
|||||||
stateV2,
|
stateV2,
|
||||||
state,
|
state,
|
||||||
journey,
|
journey,
|
||||||
quadrantChart
|
quadrantChart,
|
||||||
|
sankey
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,6 @@ import { addStylesForDiagram } from '../styles.js';
|
|||||||
import { DiagramDefinition, DiagramDetector } from './types.js';
|
import { DiagramDefinition, DiagramDetector } from './types.js';
|
||||||
import * as _commonDb from '../commonDb.js';
|
import * as _commonDb from '../commonDb.js';
|
||||||
import { parseDirective as _parseDirective } from '../directiveUtils.js';
|
import { parseDirective as _parseDirective } from '../directiveUtils.js';
|
||||||
import isEmpty from 'lodash-es/isEmpty.js';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Packaging and exposing resources for external diagrams so that they can import
|
Packaging and exposing resources for external diagrams so that they can import
|
||||||
@ -51,9 +50,7 @@ export const registerDiagram = (
|
|||||||
if (detector) {
|
if (detector) {
|
||||||
addDetector(id, detector);
|
addDetector(id, detector);
|
||||||
}
|
}
|
||||||
if (!isEmpty(diagram.styles)) {
|
addStylesForDiagram(id, diagram.styles);
|
||||||
addStylesForDiagram(id, diagram.styles);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diagram.injectUtils) {
|
if (diagram.injectUtils) {
|
||||||
diagram.injectUtils(
|
diagram.injectUtils(
|
||||||
|
@ -82,3 +82,5 @@ export type ParseDirectiveDefinition = (statement: string, context: string, type
|
|||||||
export type HTML = d3.Selection<HTMLIFrameElement, unknown, Element, unknown>;
|
export type HTML = d3.Selection<HTMLIFrameElement, unknown, Element, unknown>;
|
||||||
|
|
||||||
export type SVG = d3.Selection<SVGSVGElement, unknown, Element, unknown>;
|
export type SVG = d3.Selection<SVGSVGElement, unknown, Element, unknown>;
|
||||||
|
|
||||||
|
export type DiagramStylesProvider = (options?: any) => string;
|
||||||
|
@ -448,9 +448,8 @@ const getNamespaces = function (): NamespaceMap {
|
|||||||
export const addClassesToNamespace = function (id: string, classNames: string[]) {
|
export const addClassesToNamespace = function (id: string, classNames: string[]) {
|
||||||
if (namespaces[id] !== undefined) {
|
if (namespaces[id] !== undefined) {
|
||||||
classNames.map((className) => {
|
classNames.map((className) => {
|
||||||
|
classes[className].parent = id;
|
||||||
namespaces[id].classes[className] = classes[className];
|
namespaces[id].classes[className] = classes[className];
|
||||||
delete classes[className];
|
|
||||||
classCounter--;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1373,9 +1373,54 @@ class Class2
|
|||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
|
|
||||||
const testNamespace = parser.yy.getNamespace('Namespace1');
|
const testNamespace = parser.yy.getNamespace('Namespace1');
|
||||||
|
const testClasses = parser.yy.getClasses();
|
||||||
expect(Object.keys(testNamespace.classes).length).toBe(2);
|
expect(Object.keys(testNamespace.classes).length).toBe(2);
|
||||||
expect(Object.keys(testNamespace.children).length).toBe(0);
|
expect(Object.keys(testNamespace.children).length).toBe(0);
|
||||||
expect(testNamespace.classes['Class1'].id).toBe('Class1');
|
expect(testNamespace.classes['Class1'].id).toBe('Class1');
|
||||||
|
expect(Object.keys(testClasses).length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add relations between classes of different namespaces', function () {
|
||||||
|
const str = `classDiagram
|
||||||
|
A1 --> B1
|
||||||
|
namespace A {
|
||||||
|
class A1 {
|
||||||
|
+foo : string
|
||||||
|
}
|
||||||
|
class A2 {
|
||||||
|
+bar : int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace B {
|
||||||
|
class B1 {
|
||||||
|
+foo : bool
|
||||||
|
}
|
||||||
|
class B2 {
|
||||||
|
+bar : float
|
||||||
|
}
|
||||||
|
}
|
||||||
|
A2 --> B2`;
|
||||||
|
|
||||||
|
parser.parse(str);
|
||||||
|
const testNamespaceA = parser.yy.getNamespace('A');
|
||||||
|
const testNamespaceB = parser.yy.getNamespace('B');
|
||||||
|
const testClasses = parser.yy.getClasses();
|
||||||
|
const testRelations = parser.yy.getRelations();
|
||||||
|
expect(Object.keys(testNamespaceA.classes).length).toBe(2);
|
||||||
|
expect(testNamespaceA.classes['A1'].members[0]).toBe('+foo : string');
|
||||||
|
expect(testNamespaceA.classes['A2'].members[0]).toBe('+bar : int');
|
||||||
|
expect(Object.keys(testNamespaceB.classes).length).toBe(2);
|
||||||
|
expect(testNamespaceB.classes['B1'].members[0]).toBe('+foo : bool');
|
||||||
|
expect(testNamespaceB.classes['B2'].members[0]).toBe('+bar : float');
|
||||||
|
expect(Object.keys(testClasses).length).toBe(4);
|
||||||
|
expect(testClasses['A1'].parent).toBe('A');
|
||||||
|
expect(testClasses['A2'].parent).toBe('A');
|
||||||
|
expect(testClasses['B1'].parent).toBe('B');
|
||||||
|
expect(testClasses['B2'].parent).toBe('B');
|
||||||
|
expect(testRelations[0].id1).toBe('A1');
|
||||||
|
expect(testRelations[0].id2).toBe('B1');
|
||||||
|
expect(testRelations[1].id1).toBe('A2');
|
||||||
|
expect(testRelations[1].id2).toBe('B2');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -93,52 +93,51 @@ export const addClasses = function (
|
|||||||
log.info(classes);
|
log.info(classes);
|
||||||
|
|
||||||
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
|
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
|
||||||
keys.forEach(function (id) {
|
keys
|
||||||
const vertex = classes[id];
|
.filter((id) => classes[id].parent == parent)
|
||||||
|
.forEach(function (id) {
|
||||||
|
const vertex = classes[id];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable for storing the classes for the vertex
|
* Variable for storing the classes for the vertex
|
||||||
*/
|
*/
|
||||||
let cssClassStr = '';
|
const cssClassStr = vertex.cssClasses.join(' ');
|
||||||
if (vertex.cssClasses.length > 0) {
|
|
||||||
cssClassStr = cssClassStr + ' ' + vertex.cssClasses.join(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = { labelStyle: '', style: '' }; //getStylesFromArray(vertex.styles);
|
const styles = { labelStyle: '', style: '' }; //getStylesFromArray(vertex.styles);
|
||||||
|
|
||||||
// Use vertex id as text in the box if no text is provided by the graph definition
|
// Use vertex id as text in the box if no text is provided by the graph definition
|
||||||
const vertexText = vertex.label ?? vertex.id;
|
const vertexText = vertex.label ?? vertex.id;
|
||||||
const radius = 0;
|
const radius = 0;
|
||||||
const shape = 'class_box';
|
const shape = 'class_box';
|
||||||
|
|
||||||
// Add the node
|
// Add the node
|
||||||
const node = {
|
const node = {
|
||||||
labelStyle: styles.labelStyle,
|
labelStyle: styles.labelStyle,
|
||||||
shape: shape,
|
shape: shape,
|
||||||
labelText: sanitizeText(vertexText),
|
labelText: sanitizeText(vertexText),
|
||||||
classData: vertex,
|
classData: vertex,
|
||||||
rx: radius,
|
rx: radius,
|
||||||
ry: radius,
|
ry: radius,
|
||||||
class: cssClassStr,
|
class: cssClassStr,
|
||||||
style: styles.style,
|
style: styles.style,
|
||||||
id: vertex.id,
|
id: vertex.id,
|
||||||
domId: vertex.domId,
|
domId: vertex.domId,
|
||||||
tooltip: diagObj.db.getTooltip(vertex.id, parent) || '',
|
tooltip: diagObj.db.getTooltip(vertex.id, parent) || '',
|
||||||
haveCallback: vertex.haveCallback,
|
haveCallback: vertex.haveCallback,
|
||||||
link: vertex.link,
|
link: vertex.link,
|
||||||
width: vertex.type === 'group' ? 500 : undefined,
|
width: vertex.type === 'group' ? 500 : undefined,
|
||||||
type: vertex.type,
|
type: vertex.type,
|
||||||
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
|
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
|
||||||
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
|
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
|
||||||
};
|
};
|
||||||
g.setNode(vertex.id, node);
|
g.setNode(vertex.id, node);
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
g.setParent(vertex.id, parent);
|
g.setParent(vertex.id, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info('setNode', node);
|
log.info('setNode', node);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,8 +141,6 @@ const insertMarkers = function (elem) {
|
|||||||
export const draw = function (text, id, _version, diagObj) {
|
export const draw = function (text, id, _version, diagObj) {
|
||||||
const conf = getConfig().class;
|
const conf = getConfig().class;
|
||||||
idCache = {};
|
idCache = {};
|
||||||
// diagObj.db.clear();
|
|
||||||
// diagObj.parser.parse(text);
|
|
||||||
|
|
||||||
log.info('Rendering diagram ' + text);
|
log.info('Rendering diagram ' + text);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ export interface ClassNode {
|
|||||||
members: string[];
|
members: string[];
|
||||||
annotations: string[];
|
annotations: string[];
|
||||||
domId: string;
|
domId: string;
|
||||||
|
parent?: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
linkTarget?: string;
|
linkTarget?: string;
|
||||||
haveCallback?: boolean;
|
haveCallback?: boolean;
|
||||||
|
@ -568,13 +568,6 @@ export const draw = function (text, id, _version, diagObj) {
|
|||||||
: select('body');
|
: select('body');
|
||||||
// const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
// const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
||||||
|
|
||||||
// Parse the text to populate erDb
|
|
||||||
// try {
|
|
||||||
// parser.parse(text);
|
|
||||||
// } catch (err) {
|
|
||||||
// log.debug('Parsing failed');
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Get a reference to the svg node that contains the text
|
// Get a reference to the svg node that contains the text
|
||||||
const svg = root.select(`[id='${id}']`);
|
const svg = root.select(`[id='${id}']`);
|
||||||
|
|
||||||
|
@ -342,7 +342,10 @@ export const setLink = function (ids, linkStr, target) {
|
|||||||
setClass(ids, 'clickable');
|
setClass(ids, 'clickable');
|
||||||
};
|
};
|
||||||
export const getTooltip = function (id) {
|
export const getTooltip = function (id) {
|
||||||
return tooltips[id];
|
if (tooltips.hasOwnProperty(id)) {
|
||||||
|
return tooltips[id];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -443,7 +446,7 @@ export const clear = function (ver = 'gen-1') {
|
|||||||
subGraphs = [];
|
subGraphs = [];
|
||||||
subGraphLookup = {};
|
subGraphLookup = {};
|
||||||
subCount = 0;
|
subCount = 0;
|
||||||
tooltips = [];
|
tooltips = {};
|
||||||
firstGraphFlag = true;
|
firstGraphFlag = true;
|
||||||
version = ver;
|
version = ver;
|
||||||
commonClear();
|
commonClear();
|
||||||
|
@ -306,13 +306,6 @@ export const draw = function (text, id, _version, diagObj) {
|
|||||||
: select('body');
|
: select('body');
|
||||||
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
||||||
|
|
||||||
// Parse the graph definition
|
|
||||||
try {
|
|
||||||
diagObj.parser.parse(text);
|
|
||||||
} catch (err) {
|
|
||||||
log.debug('Parsing failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the default direction, use TD if none was found
|
// Fetch the default direction, use TD if none was found
|
||||||
let dir = diagObj.db.getDirection();
|
let dir = diagObj.db.getDirection();
|
||||||
if (dir === undefined) {
|
if (dir === undefined) {
|
||||||
|
@ -338,4 +338,20 @@ describe('[Style] when parsing', () => {
|
|||||||
|
|
||||||
expect(edges[0].type).toBe('arrow_point');
|
expect(edges[0].type).toBe('arrow_point');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle multiple vertices with style', function () {
|
||||||
|
const res = flow.parser.parse(`
|
||||||
|
graph TD
|
||||||
|
classDef C1 stroke-dasharray:4
|
||||||
|
classDef C2 stroke-dasharray:6
|
||||||
|
A & B:::C1 & D:::C1 --> E:::C2
|
||||||
|
`);
|
||||||
|
|
||||||
|
const vert = flow.parser.yy.getVertices();
|
||||||
|
|
||||||
|
expect(vert['A'].classes.length).toBe(0);
|
||||||
|
expect(vert['B'].classes[0]).toBe('C1');
|
||||||
|
expect(vert['D'].classes[0]).toBe('C1');
|
||||||
|
expect(vert['E'].classes[0]).toBe('C2');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -359,7 +359,7 @@ statement
|
|||||||
|
|
||||||
separator: NEWLINE | SEMI | EOF ;
|
separator: NEWLINE | SEMI | EOF ;
|
||||||
|
|
||||||
|
|
||||||
verticeStatement: verticeStatement link node
|
verticeStatement: verticeStatement link node
|
||||||
{ /* console.warn('vs',$1.stmt,$3); */ yy.addLink($1.stmt,$3,$2); $$ = { stmt: $3, nodes: $3.concat($1.nodes) } }
|
{ /* console.warn('vs',$1.stmt,$3); */ yy.addLink($1.stmt,$3,$2); $$ = { stmt: $3, nodes: $3.concat($1.nodes) } }
|
||||||
| verticeStatement link node spaceList
|
| verticeStatement link node spaceList
|
||||||
@ -368,12 +368,16 @@ verticeStatement: verticeStatement link node
|
|||||||
|node { /*console.warn('noda', $1);*/ $$ = {stmt: $1, nodes:$1 }}
|
|node { /*console.warn('noda', $1);*/ $$ = {stmt: $1, nodes:$1 }}
|
||||||
;
|
;
|
||||||
|
|
||||||
node: vertex
|
node: styledVertex
|
||||||
{ /* console.warn('nod', $1); */ $$ = [$1];}
|
{ /* console.warn('nod', $1); */ $$ = [$1];}
|
||||||
| node spaceList AMP spaceList vertex
|
| node spaceList AMP spaceList styledVertex
|
||||||
{ $$ = $1.concat($5); /* console.warn('pip', $1[0], $5, $$); */ }
|
{ $$ = $1.concat($5); /* console.warn('pip', $1[0], $5, $$); */ }
|
||||||
|
;
|
||||||
|
|
||||||
|
styledVertex: vertex
|
||||||
|
{ /* console.warn('nod', $1); */ $$ = $1;}
|
||||||
| vertex STYLE_SEPARATOR idString
|
| vertex STYLE_SEPARATOR idString
|
||||||
{$$ = [$1];yy.setClass($1,$3)}
|
{$$ = $1;yy.setClass($1,$3)}
|
||||||
;
|
;
|
||||||
|
|
||||||
vertex: idString SQS text SQE
|
vertex: idString SQS text SQE
|
||||||
|
@ -59,8 +59,6 @@ let w;
|
|||||||
export const draw = function (text, id, version, diagObj) {
|
export const draw = function (text, id, version, diagObj) {
|
||||||
const conf = getConfig().gantt;
|
const conf = getConfig().gantt;
|
||||||
|
|
||||||
// diagObj.db.clear();
|
|
||||||
// parser.parse(text);
|
|
||||||
const securityLevel = getConfig().securityLevel;
|
const securityLevel = getConfig().securityLevel;
|
||||||
// Handle root and Document for when rendering in sandbox mode
|
// Handle root and Document for when rendering in sandbox mode
|
||||||
let sandboxElement;
|
let sandboxElement;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { curveBasis, line, select } from 'd3';
|
import { curveBasis, line, select } from 'd3';
|
||||||
|
|
||||||
import db from './gitGraphAst.js';
|
import db from './gitGraphAst.js';
|
||||||
import gitGraphParser from './parser/gitGraph.js';
|
|
||||||
import { logger } from '../../logger.js';
|
import { logger } from '../../logger.js';
|
||||||
import { interpolateToCurve } from '../../utils.js';
|
import { interpolateToCurve } from '../../utils.js';
|
||||||
|
|
||||||
@ -328,13 +327,7 @@ function renderLines(svg, commit, direction, branchColor = 0) {
|
|||||||
|
|
||||||
export const draw = function (txt, id, ver) {
|
export const draw = function (txt, id, ver) {
|
||||||
try {
|
try {
|
||||||
const parser = gitGraphParser.parser;
|
|
||||||
parser.yy = db;
|
|
||||||
parser.yy.clear();
|
|
||||||
|
|
||||||
logger.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver);
|
logger.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver);
|
||||||
// Parse the graph definition
|
|
||||||
parser.parse(txt + '\n');
|
|
||||||
|
|
||||||
config = Object.assign(config, apiConfig, db.getOptions());
|
config = Object.assign(config, apiConfig, db.getOptions());
|
||||||
logger.debug('effective options', config);
|
logger.debug('effective options', config);
|
||||||
|
@ -167,14 +167,8 @@ function positionNodes(cy) {
|
|||||||
export const draw = async (text, id, version, diagObj) => {
|
export const draw = async (text, id, version, diagObj) => {
|
||||||
const conf = getConfig();
|
const conf = getConfig();
|
||||||
|
|
||||||
// console.log('Config: ', conf);
|
|
||||||
conf.htmlLabels = false;
|
conf.htmlLabels = false;
|
||||||
|
|
||||||
// This is done only for throwing the error if the text is not valid.
|
|
||||||
diagObj.db.clear();
|
|
||||||
// Parse the graph definition
|
|
||||||
diagObj.parser.parse(text);
|
|
||||||
|
|
||||||
log.debug('Rendering mindmap diagram\n' + text, diagObj.parser);
|
log.debug('Rendering mindmap diagram\n' + text, diagObj.parser);
|
||||||
|
|
||||||
const securityLevel = getConfig().securityLevel;
|
const securityLevel = getConfig().securityLevel;
|
||||||
|
@ -34,9 +34,6 @@ export const draw = (txt, id, _version, diagObj) => {
|
|||||||
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
||||||
|
|
||||||
// Parse the Pie Chart definition
|
// Parse the Pie Chart definition
|
||||||
diagObj.db.clear();
|
|
||||||
diagObj.parser.parse(txt);
|
|
||||||
log.debug('Parsed info diagram');
|
|
||||||
const elem = doc.getElementById(id);
|
const elem = doc.getElementById(id);
|
||||||
width = elem.parentElement.offsetWidth;
|
width = elem.parentElement.offsetWidth;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// @ts-ignore: TODO Fix ts errors
|
// @ts-ignore: TODO Fix ts errors
|
||||||
import { scaleLinear } from 'd3';
|
import { scaleLinear } from 'd3';
|
||||||
import { log } from '../../logger.js';
|
import { log } from '../../logger.js';
|
||||||
import { QuadrantChartConfig } from '../../config.type.js';
|
import type { BaseDiagramConfig, QuadrantChartConfig } from '../../config.type.js';
|
||||||
import defaultConfig from '../../defaultConfig.js';
|
import defaultConfig from '../../defaultConfig.js';
|
||||||
import { getThemeVariables } from '../../themes/theme-default.js';
|
import { getThemeVariables } from '../../themes/theme-default.js';
|
||||||
|
|
||||||
@ -71,7 +71,8 @@ export interface quadrantBuilderData {
|
|||||||
points: QuadrantPointInputType[];
|
points: QuadrantPointInputType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QuadrantBuilderConfig extends QuadrantChartConfig {
|
export interface QuadrantBuilderConfig
|
||||||
|
extends Required<Omit<QuadrantChartConfig, keyof BaseDiagramConfig>> {
|
||||||
showXAxis: boolean;
|
showXAxis: boolean;
|
||||||
showYAxis: boolean;
|
showYAxis: boolean;
|
||||||
showTitle: boolean;
|
showTitle: boolean;
|
||||||
|
@ -306,8 +306,6 @@ const elementString = (str) => {
|
|||||||
|
|
||||||
export const draw = (text, id, _version, diagObj) => {
|
export const draw = (text, id, _version, diagObj) => {
|
||||||
conf = getConfig().requirement;
|
conf = getConfig().requirement;
|
||||||
diagObj.db.clear();
|
|
||||||
diagObj.parser.parse(text);
|
|
||||||
|
|
||||||
const securityLevel = conf.securityLevel;
|
const securityLevel = conf.securityLevel;
|
||||||
// Handle root and Document for when rendering in sandbox mode
|
// Handle root and Document for when rendering in sandbox mode
|
||||||
|
99
packages/mermaid/src/diagrams/sankey/parser/energy.csv
Normal file
99
packages/mermaid/src/diagrams/sankey/parser/energy.csv
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
%% There are leading and trailing spaces, do not crop
|
||||||
|
Agricultural 'waste',Bio-conversion,124.729
|
||||||
|
%% line with a comment
|
||||||
|
|
||||||
|
%% Normal line
|
||||||
|
Bio-conversion,Liquid,0.597
|
||||||
|
|
||||||
|
%% Line with unquoted sankey keyword
|
||||||
|
sankey,target,10
|
||||||
|
|
||||||
|
%% Quoted sankey keyword
|
||||||
|
"sankey",target,10
|
||||||
|
|
||||||
|
%% Another normal line
|
||||||
|
Bio-conversion,Losses,26.862
|
||||||
|
|
||||||
|
%% Line with integer amount
|
||||||
|
Bio-conversion,Solid,280
|
||||||
|
|
||||||
|
%% Some blank lines in the middle of CSV
|
||||||
|
|
||||||
|
|
||||||
|
%% Another normal line
|
||||||
|
Bio-conversion,Gas,81.144
|
||||||
|
|
||||||
|
%% Quoted line
|
||||||
|
"Biofuel imports",Liquid,35
|
||||||
|
|
||||||
|
%% Quoted line with escaped quotes inside
|
||||||
|
"""Biomass imports""",Solid,35
|
||||||
|
|
||||||
|
%% Lines containing commas inside
|
||||||
|
%% Quoted and unquoted values should be equal in terms of graph
|
||||||
|
"District heating","Heating and cooling, commercial",22.505
|
||||||
|
District heating,"Heating and cooling, homes",46.184
|
||||||
|
|
||||||
|
%% A bunch of lines, normal CSV
|
||||||
|
Coal imports,Coal,11.606
|
||||||
|
Coal reserves,Coal,63.965
|
||||||
|
Coal,Solid,75.571
|
||||||
|
District heating,Industry,10.639
|
||||||
|
Electricity grid,Over generation / exports,104.453
|
||||||
|
Electricity grid,Heating and cooling - homes,113.726
|
||||||
|
Electricity grid,H2 conversion,27.14
|
||||||
|
Electricity grid,Industry,342.165
|
||||||
|
Electricity grid,Road transport,37.797
|
||||||
|
Electricity grid,Agriculture,4.412
|
||||||
|
Electricity grid,Heating and cooling - commercial,40.858
|
||||||
|
Electricity grid,Losses,56.691
|
||||||
|
Electricity grid,Rail transport,7.863
|
||||||
|
Electricity grid,Lighting & appliances - commercial,90.008
|
||||||
|
Electricity grid,Lighting & appliances - homes,93.494
|
||||||
|
Gas imports,Ngas,40.719
|
||||||
|
Gas reserves,Ngas,82.233
|
||||||
|
Gas,Heating and cooling - commercial,0.129
|
||||||
|
Gas,Losses,1.401
|
||||||
|
Gas,Thermal generation,151.891
|
||||||
|
Gas,Agriculture,2.096
|
||||||
|
Gas,Industry,48.58
|
||||||
|
Geothermal,Electricity grid,7.013
|
||||||
|
H2 conversion,H2,20.897
|
||||||
|
H2 conversion,Losses,6.242
|
||||||
|
H2,Road transport,20.897
|
||||||
|
Hydro,Electricity grid,6.995
|
||||||
|
Liquid,Industry,121.066
|
||||||
|
Liquid,International shipping,128.69
|
||||||
|
Liquid,Road transport,135.835
|
||||||
|
Liquid,Domestic aviation,14.458
|
||||||
|
Liquid,International aviation,206.267
|
||||||
|
Liquid,Agriculture,3.64
|
||||||
|
Liquid,National navigation,33.218
|
||||||
|
Liquid,Rail transport,4.413
|
||||||
|
Marine algae,Bio-conversion,4.375
|
||||||
|
Ngas,Gas,122.952
|
||||||
|
Nuclear,Thermal generation,839.978
|
||||||
|
Oil imports,Oil,504.287
|
||||||
|
Oil reserves,Oil,107.703
|
||||||
|
Oil,Liquid,611.99
|
||||||
|
Other waste,Solid,56.587
|
||||||
|
Other waste,Bio-conversion,77.81
|
||||||
|
Pumped heat,Heating and cooling - homes,193.026
|
||||||
|
Pumped heat,Heating and cooling - commercial,70.672
|
||||||
|
Solar PV,Electricity grid,59.901
|
||||||
|
Solar Thermal,Heating and cooling - homes,19.263
|
||||||
|
Solar,Solar Thermal,19.263
|
||||||
|
Solar,Solar PV,59.901
|
||||||
|
Solid,Agriculture,0.882
|
||||||
|
Solid,Thermal generation,400.12
|
||||||
|
Solid,Industry,46.477
|
||||||
|
Thermal generation,Electricity grid,525.531
|
||||||
|
Thermal generation,Losses,787.129
|
||||||
|
Thermal generation,District heating,79.329
|
||||||
|
Tidal,Electricity grid,9.452
|
||||||
|
UK land based bioenergy,Bio-conversion,182.01
|
||||||
|
"""Wave""",Electricity grid,19.013
|
||||||
|
"""Wind""",Electricity grid,289.366
|
||||||
|
|
||||||
|
%% lines at the end, do not remove
|
||||||
|
|
Can't render this file because it has a wrong number of fields in line 2.
|
69
packages/mermaid/src/diagrams/sankey/parser/sankey.jison
Normal file
69
packages/mermaid/src/diagrams/sankey/parser/sankey.jison
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/** mermaid */
|
||||||
|
|
||||||
|
//---------------------------------------------------------
|
||||||
|
// We support csv format as defined here:
|
||||||
|
// https://www.ietf.org/rfc/rfc4180.txt
|
||||||
|
// There are some minor changes for compliance with jison
|
||||||
|
// We also parse only 3 columns: source,target,value
|
||||||
|
// And allow blank lines for visual purposes
|
||||||
|
//---------------------------------------------------------
|
||||||
|
|
||||||
|
%lex
|
||||||
|
|
||||||
|
%options case-insensitive
|
||||||
|
%options easy_keword_rules
|
||||||
|
|
||||||
|
%x escaped_text
|
||||||
|
%x csv
|
||||||
|
|
||||||
|
// as per section 6.1 of RFC 2234 [2]
|
||||||
|
COMMA \u002C
|
||||||
|
CR \u000D
|
||||||
|
LF \u000A
|
||||||
|
CRLF \u000D\u000A
|
||||||
|
ESCAPED_QUOTE \u0022
|
||||||
|
DQUOTE \u0022
|
||||||
|
TEXTDATA [\u0020-\u0021\u0023-\u002B\u002D-\u007E]
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
<INITIAL>"sankey-beta" { this.pushState('csv'); return 'SANKEY'; }
|
||||||
|
<INITIAL,csv><<EOF>> { return 'EOF' } // match end of file
|
||||||
|
<INITIAL,csv>({CRLF}|{LF}) { return 'NEWLINE' }
|
||||||
|
<INITIAL,csv>{COMMA} { return 'COMMA' }
|
||||||
|
<INITIAL,csv>{DQUOTE} { this.pushState('escaped_text'); return 'DQUOTE'; }
|
||||||
|
<INITIAL,csv>{TEXTDATA}* { return 'NON_ESCAPED_TEXT' }
|
||||||
|
<INITIAL,csv,escaped_text>{DQUOTE}(?!{DQUOTE}) {this.popState('escaped_text'); return 'DQUOTE'; } // unescaped DQUOTE closes string
|
||||||
|
<INITIAL,csv,escaped_text>({TEXTDATA}|{COMMA}|{CR}|{LF}|{DQUOTE}{DQUOTE})* { return 'ESCAPED_TEXT'; }
|
||||||
|
|
||||||
|
/lex
|
||||||
|
|
||||||
|
%start start
|
||||||
|
|
||||||
|
%% // language grammar
|
||||||
|
|
||||||
|
start: SANKEY NEWLINE csv opt_eof;
|
||||||
|
|
||||||
|
csv: record csv_tail;
|
||||||
|
csv_tail: NEWLINE csv | ;
|
||||||
|
opt_eof: EOF | ;
|
||||||
|
|
||||||
|
record
|
||||||
|
: field\[source] COMMA field\[target] COMMA field\[value] {
|
||||||
|
const source = yy.findOrCreateNode($source.trim().replaceAll('""', '"'));
|
||||||
|
const target = yy.findOrCreateNode($target.trim().replaceAll('""', '"'));
|
||||||
|
const value = parseFloat($value.trim());
|
||||||
|
yy.addLink(source,target,value);
|
||||||
|
} // parse only 3 fields, this is not part of CSV standard
|
||||||
|
;
|
||||||
|
|
||||||
|
field
|
||||||
|
: escaped { $$=$escaped; }
|
||||||
|
| non_escaped { $$=$non_escaped; }
|
||||||
|
;
|
||||||
|
|
||||||
|
escaped: DQUOTE ESCAPED_TEXT DQUOTE { $$=$ESCAPED_TEXT; };
|
||||||
|
|
||||||
|
non_escaped: NON_ESCAPED_TEXT { $$=$NON_ESCAPED_TEXT; };
|
||||||
|
|
||||||
|
|
24
packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts
Normal file
24
packages/mermaid/src/diagrams/sankey/parser/sankey.spec.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// @ts-ignore: jison doesn't export types
|
||||||
|
import sankey from './sankey.jison';
|
||||||
|
import db from '../sankeyDB.js';
|
||||||
|
import { cleanupComments } from '../../../diagram-api/comments.js';
|
||||||
|
import { prepareTextForParsing } from '../sankeyUtils.js';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
describe('Sankey diagram', function () {
|
||||||
|
describe('when parsing an info graph it', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
sankey.parser.yy = db;
|
||||||
|
sankey.parser.yy.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses csv', async () => {
|
||||||
|
const csv = path.resolve(__dirname, './energy.csv');
|
||||||
|
const data = fs.readFileSync(csv, 'utf8');
|
||||||
|
const graphDefinition = prepareTextForParsing(cleanupComments('sankey-beta\n\n ' + data));
|
||||||
|
|
||||||
|
sankey.parser.parse(graphDefinition);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
81
packages/mermaid/src/diagrams/sankey/sankeyDB.ts
Normal file
81
packages/mermaid/src/diagrams/sankey/sankeyDB.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import * as configApi from '../../config.js';
|
||||||
|
import common from '../common/common.js';
|
||||||
|
import {
|
||||||
|
setAccTitle,
|
||||||
|
getAccTitle,
|
||||||
|
getAccDescription,
|
||||||
|
setAccDescription,
|
||||||
|
setDiagramTitle,
|
||||||
|
getDiagramTitle,
|
||||||
|
clear as commonClear,
|
||||||
|
} from '../../commonDb.js';
|
||||||
|
|
||||||
|
// Sankey diagram represented by nodes and links between those nodes
|
||||||
|
let links: SankeyLink[] = [];
|
||||||
|
// Array of nodes guarantees their order
|
||||||
|
let nodes: SankeyNode[] = [];
|
||||||
|
// We also have to track nodes uniqueness (by ID)
|
||||||
|
let nodesMap: Record<string, SankeyNode> = {};
|
||||||
|
|
||||||
|
const clear = (): void => {
|
||||||
|
links = [];
|
||||||
|
nodes = [];
|
||||||
|
nodesMap = {};
|
||||||
|
commonClear();
|
||||||
|
};
|
||||||
|
|
||||||
|
class SankeyLink {
|
||||||
|
constructor(public source: SankeyNode, public target: SankeyNode, public value: number = 0) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param source - Node where the link starts
|
||||||
|
* @param target - Node where the link ends
|
||||||
|
* @param value - number, float or integer, describes the amount to be passed
|
||||||
|
*/
|
||||||
|
const addLink = (source: SankeyNode, target: SankeyNode, value: number): void => {
|
||||||
|
links.push(new SankeyLink(source, target, value));
|
||||||
|
};
|
||||||
|
|
||||||
|
class SankeyNode {
|
||||||
|
constructor(public ID: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const findOrCreateNode = (ID: string): SankeyNode => {
|
||||||
|
ID = common.sanitizeText(ID, configApi.getConfig());
|
||||||
|
|
||||||
|
if (!nodesMap[ID]) {
|
||||||
|
nodesMap[ID] = new SankeyNode(ID);
|
||||||
|
nodes.push(nodesMap[ID]);
|
||||||
|
}
|
||||||
|
return nodesMap[ID];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNodes = () => nodes;
|
||||||
|
const getLinks = () => links;
|
||||||
|
|
||||||
|
const getGraph = () => ({
|
||||||
|
nodes: nodes.map((node) => ({ id: node.ID })),
|
||||||
|
links: links.map((link) => ({
|
||||||
|
source: link.source.ID,
|
||||||
|
target: link.target.ID,
|
||||||
|
value: link.value,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
nodesMap,
|
||||||
|
getConfig: () => configApi.getConfig().sankey,
|
||||||
|
getNodes,
|
||||||
|
getLinks,
|
||||||
|
getGraph,
|
||||||
|
addLink,
|
||||||
|
findOrCreateNode,
|
||||||
|
getAccTitle,
|
||||||
|
setAccTitle,
|
||||||
|
getAccDescription,
|
||||||
|
setAccDescription,
|
||||||
|
getDiagramTitle,
|
||||||
|
setDiagramTitle,
|
||||||
|
clear,
|
||||||
|
};
|
20
packages/mermaid/src/diagrams/sankey/sankeyDetector.ts
Normal file
20
packages/mermaid/src/diagrams/sankey/sankeyDetector.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types.js';
|
||||||
|
|
||||||
|
const id = 'sankey';
|
||||||
|
|
||||||
|
const detector: DiagramDetector = (txt) => {
|
||||||
|
return /^\s*sankey-beta/.test(txt);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loader = async () => {
|
||||||
|
const { diagram } = await import('./sankeyDiagram.js');
|
||||||
|
return { id, diagram };
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: ExternalDiagramDefinition = {
|
||||||
|
id,
|
||||||
|
detector,
|
||||||
|
loader,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
15
packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts
Normal file
15
packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { DiagramDefinition } from '../../diagram-api/types.js';
|
||||||
|
// @ts-ignore: jison doesn't export types
|
||||||
|
import parser from './parser/sankey.jison';
|
||||||
|
import db from './sankeyDB.js';
|
||||||
|
import renderer from './sankeyRenderer.js';
|
||||||
|
import { prepareTextForParsing } from './sankeyUtils.js';
|
||||||
|
|
||||||
|
const originalParse = parser.parse.bind(parser);
|
||||||
|
parser.parse = (text: string) => originalParse(prepareTextForParsing(text));
|
||||||
|
|
||||||
|
export const diagram: DiagramDefinition = {
|
||||||
|
parser,
|
||||||
|
db,
|
||||||
|
renderer,
|
||||||
|
};
|
205
packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts
Normal file
205
packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
import { Diagram } from '../../Diagram.js';
|
||||||
|
import * as configApi from '../../config.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
select as d3select,
|
||||||
|
scaleOrdinal as d3scaleOrdinal,
|
||||||
|
schemeTableau10 as d3schemeTableau10,
|
||||||
|
} from 'd3';
|
||||||
|
|
||||||
|
import {
|
||||||
|
sankey as d3Sankey,
|
||||||
|
sankeyLinkHorizontal as d3SankeyLinkHorizontal,
|
||||||
|
sankeyLeft as d3SankeyLeft,
|
||||||
|
sankeyRight as d3SankeyRight,
|
||||||
|
sankeyCenter as d3SankeyCenter,
|
||||||
|
sankeyJustify as d3SankeyJustify,
|
||||||
|
SankeyNode as d3SankeyNode,
|
||||||
|
} from 'd3-sankey';
|
||||||
|
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||||
|
import { Uid } from '../../rendering-util/uid.js';
|
||||||
|
import type { SankeyLinkColor, SankeyNodeAlignment } from '../../config.type.js';
|
||||||
|
|
||||||
|
// Map config options to alignment functions
|
||||||
|
const alignmentsMap: Record<
|
||||||
|
SankeyNodeAlignment,
|
||||||
|
(node: d3SankeyNode<object, object>, n: number) => number
|
||||||
|
> = {
|
||||||
|
left: d3SankeyLeft,
|
||||||
|
right: d3SankeyRight,
|
||||||
|
center: d3SankeyCenter,
|
||||||
|
justify: d3SankeyJustify,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws Sankey diagram.
|
||||||
|
*
|
||||||
|
* @param text - The text of the diagram
|
||||||
|
* @param id - The id of the diagram which will be used as a DOM element id¨
|
||||||
|
* @param _version - Mermaid version from package.json
|
||||||
|
* @param diagObj - A standard diagram containing the db and the text and type etc of the diagram
|
||||||
|
*/
|
||||||
|
export const draw = function (text: string, id: string, _version: string, diagObj: Diagram): void {
|
||||||
|
// Get Sankey config
|
||||||
|
const { securityLevel, sankey: conf } = configApi.getConfig();
|
||||||
|
const defaultSankeyConfig = configApi!.defaultConfig!.sankey!;
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// This code repeats for every diagram
|
||||||
|
// Figure out what is happening there, probably it should be separated
|
||||||
|
// The main thing is svg object that is a d3 wrapper for svg operations
|
||||||
|
//
|
||||||
|
let sandboxElement: any;
|
||||||
|
if (securityLevel === 'sandbox') {
|
||||||
|
sandboxElement = d3select('#i' + id);
|
||||||
|
}
|
||||||
|
const root =
|
||||||
|
securityLevel === 'sandbox'
|
||||||
|
? d3select(sandboxElement.nodes()[0].contentDocument.body)
|
||||||
|
: d3select('body');
|
||||||
|
// @ts-ignore TODO root.select is not callable
|
||||||
|
const svg = securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : d3select(`[id="${id}"]`);
|
||||||
|
|
||||||
|
// Establish svg dimensions and get width and height
|
||||||
|
//
|
||||||
|
const width = conf?.width || defaultSankeyConfig.width!;
|
||||||
|
const height = conf?.height || defaultSankeyConfig.width!;
|
||||||
|
const useMaxWidth = conf?.useMaxWidth || defaultSankeyConfig.useMaxWidth!;
|
||||||
|
const nodeAlignment = conf?.nodeAlignment || defaultSankeyConfig.nodeAlignment!;
|
||||||
|
|
||||||
|
// FIX: using max width prevents height from being set, is it intended?
|
||||||
|
// to add height directly one can use `svg.attr('height', height)`
|
||||||
|
//
|
||||||
|
// @ts-ignore TODO: svg type vs selection mismatch
|
||||||
|
configureSvgSize(svg, height, width, useMaxWidth);
|
||||||
|
|
||||||
|
// Prepare data for construction based on diagObj.db
|
||||||
|
// This must be a mutable object with `nodes` and `links` properties:
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "nodes": [ { "id": "Alice" }, { "id": "Bob" }, { "id": "Carol" } ],
|
||||||
|
// "links": [ { "source": "Alice", "target": "Bob", "value": 23 }, { "source": "Bob", "target": "Carol", "value": 43 } ]
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @ts-ignore TODO: db should be coerced to sankey DB type
|
||||||
|
const graph = diagObj.db.getGraph();
|
||||||
|
|
||||||
|
// Get alignment function
|
||||||
|
const nodeAlign = alignmentsMap[nodeAlignment];
|
||||||
|
|
||||||
|
// Construct and configure a Sankey generator
|
||||||
|
// That will be a function that calculates nodes and links dimensions
|
||||||
|
//
|
||||||
|
const nodeWidth = 10;
|
||||||
|
const sankey = d3Sankey()
|
||||||
|
.nodeId((d: any) => d.id) // we use 'id' property to identify node
|
||||||
|
.nodeWidth(nodeWidth)
|
||||||
|
.nodePadding(10)
|
||||||
|
.nodeAlign(nodeAlign)
|
||||||
|
.extent([
|
||||||
|
[0, 0],
|
||||||
|
[width, height],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Compute the Sankey layout: calculate nodes and links positions
|
||||||
|
// Our `graph` object will be mutated by this and enriched with other properties
|
||||||
|
//
|
||||||
|
sankey(graph);
|
||||||
|
|
||||||
|
// Get color scheme for the graph
|
||||||
|
const colorScheme = d3scaleOrdinal(d3schemeTableau10);
|
||||||
|
|
||||||
|
// Create rectangles for nodes
|
||||||
|
svg
|
||||||
|
.append('g')
|
||||||
|
.attr('class', 'nodes')
|
||||||
|
.selectAll('.node')
|
||||||
|
.data(graph.nodes)
|
||||||
|
.join('g')
|
||||||
|
.attr('class', 'node')
|
||||||
|
.attr('id', (d: any) => (d.uid = Uid.next('node-')).id)
|
||||||
|
.attr('transform', function (d: any) {
|
||||||
|
return 'translate(' + d.x0 + ',' + d.y0 + ')';
|
||||||
|
})
|
||||||
|
.attr('x', (d: any) => d.x0)
|
||||||
|
.attr('y', (d: any) => d.y0)
|
||||||
|
.append('rect')
|
||||||
|
.attr('height', (d: any) => {
|
||||||
|
return d.y1 - d.y0;
|
||||||
|
})
|
||||||
|
.attr('width', (d: any) => d.x1 - d.x0)
|
||||||
|
.attr('fill', (d: any) => colorScheme(d.id));
|
||||||
|
|
||||||
|
// Create labels for nodes
|
||||||
|
svg
|
||||||
|
.append('g')
|
||||||
|
.attr('class', 'node-labels')
|
||||||
|
.attr('font-family', 'sans-serif')
|
||||||
|
.attr('font-size', 14)
|
||||||
|
.selectAll('text')
|
||||||
|
.data(graph.nodes)
|
||||||
|
.join('text')
|
||||||
|
.attr('x', (d: any) => (d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6))
|
||||||
|
.attr('y', (d: any) => (d.y1 + d.y0) / 2)
|
||||||
|
.attr('dy', '0.35em')
|
||||||
|
.attr('text-anchor', (d: any) => (d.x0 < width / 2 ? 'start' : 'end'))
|
||||||
|
.text((d: any) => d.id);
|
||||||
|
|
||||||
|
// Creates the paths that represent the links.
|
||||||
|
const link = svg
|
||||||
|
.append('g')
|
||||||
|
.attr('class', 'links')
|
||||||
|
.attr('fill', 'none')
|
||||||
|
.attr('stroke-opacity', 0.5)
|
||||||
|
.selectAll('.link')
|
||||||
|
.data(graph.links)
|
||||||
|
.join('g')
|
||||||
|
.attr('class', 'link')
|
||||||
|
.style('mix-blend-mode', 'multiply');
|
||||||
|
|
||||||
|
const linkColor = conf?.linkColor || 'gradient';
|
||||||
|
|
||||||
|
if (linkColor === 'gradient') {
|
||||||
|
const gradient = link
|
||||||
|
.append('linearGradient')
|
||||||
|
.attr('id', (d: any) => (d.uid = Uid.next('linearGradient-')).id)
|
||||||
|
.attr('gradientUnits', 'userSpaceOnUse')
|
||||||
|
.attr('x1', (d: any) => d.source.x1)
|
||||||
|
.attr('x2', (d: any) => d.target.x0);
|
||||||
|
|
||||||
|
gradient
|
||||||
|
.append('stop')
|
||||||
|
.attr('offset', '0%')
|
||||||
|
.attr('stop-color', (d: any) => colorScheme(d.source.id));
|
||||||
|
|
||||||
|
gradient
|
||||||
|
.append('stop')
|
||||||
|
.attr('offset', '100%')
|
||||||
|
.attr('stop-color', (d: any) => colorScheme(d.target.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
let coloring: any;
|
||||||
|
switch (linkColor) {
|
||||||
|
case 'gradient':
|
||||||
|
coloring = (d: any) => d.uid;
|
||||||
|
break;
|
||||||
|
case 'source':
|
||||||
|
coloring = (d: any) => colorScheme(d.source.id);
|
||||||
|
break;
|
||||||
|
case 'target':
|
||||||
|
coloring = (d: any) => colorScheme(d.target.id);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
coloring = linkColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
link
|
||||||
|
.append('path')
|
||||||
|
.attr('d', d3SankeyLinkHorizontal())
|
||||||
|
.attr('stroke', coloring)
|
||||||
|
.attr('stroke-width', (d: any) => Math.max(1, d.width));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
draw,
|
||||||
|
};
|
8
packages/mermaid/src/diagrams/sankey/sankeyUtils.ts
Normal file
8
packages/mermaid/src/diagrams/sankey/sankeyUtils.ts
Normal 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;
|
||||||
|
};
|
@ -38,6 +38,8 @@
|
|||||||
"box" { this.begin('LINE'); return 'box'; }
|
"box" { this.begin('LINE'); return 'box'; }
|
||||||
"participant" { this.begin('ID'); return 'participant'; }
|
"participant" { this.begin('ID'); return 'participant'; }
|
||||||
"actor" { this.begin('ID'); return 'participant_actor'; }
|
"actor" { this.begin('ID'); return 'participant_actor'; }
|
||||||
|
"create" return 'create';
|
||||||
|
"destroy" { this.begin('ID'); return 'destroy'; }
|
||||||
<ID>[^\->:\n,;]+?([\-]*[^\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
|
<ID>[^\->:\n,;]+?([\-]*[^\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
|
||||||
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
|
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
|
||||||
<ALIAS>(?:) { this.popState(); this.popState(); return 'NEWLINE'; }
|
<ALIAS>(?:) { this.popState(); this.popState(); return 'NEWLINE'; }
|
||||||
@ -138,6 +140,7 @@ directive
|
|||||||
|
|
||||||
statement
|
statement
|
||||||
: participant_statement
|
: participant_statement
|
||||||
|
| 'create' participant_statement {$2.type='createParticipant'; $$=$2;}
|
||||||
| 'box' restOfLine box_section end
|
| 'box' restOfLine box_section end
|
||||||
{
|
{
|
||||||
$3.unshift({type: 'boxStart', boxData:yy.parseBoxData($2) });
|
$3.unshift({type: 'boxStart', boxData:yy.parseBoxData($2) });
|
||||||
@ -234,10 +237,11 @@ else_sections
|
|||||||
;
|
;
|
||||||
|
|
||||||
participant_statement
|
participant_statement
|
||||||
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
|
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
|
||||||
| 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;}
|
| 'participant' actor 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$$=$2;}
|
||||||
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;}
|
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
|
||||||
| 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;}
|
| 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;}
|
||||||
|
| 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;}
|
||||||
;
|
;
|
||||||
|
|
||||||
note_statement
|
note_statement
|
||||||
|
@ -14,12 +14,16 @@ import {
|
|||||||
|
|
||||||
let prevActor = undefined;
|
let prevActor = undefined;
|
||||||
let actors = {};
|
let actors = {};
|
||||||
|
let createdActors = {};
|
||||||
|
let destroyedActors = {};
|
||||||
let boxes = [];
|
let boxes = [];
|
||||||
let messages = [];
|
let messages = [];
|
||||||
const notes = [];
|
const notes = [];
|
||||||
let sequenceNumbersEnabled = false;
|
let sequenceNumbersEnabled = false;
|
||||||
let wrapEnabled;
|
let wrapEnabled;
|
||||||
let currentBox = undefined;
|
let currentBox = undefined;
|
||||||
|
let lastCreated = undefined;
|
||||||
|
let lastDestroyed = undefined;
|
||||||
|
|
||||||
export const parseDirective = function (statement, context, type) {
|
export const parseDirective = function (statement, context, type) {
|
||||||
mermaidAPI.parseDirective(this, statement, context, type);
|
mermaidAPI.parseDirective(this, statement, context, type);
|
||||||
@ -165,6 +169,12 @@ export const getBoxes = function () {
|
|||||||
export const getActors = function () {
|
export const getActors = function () {
|
||||||
return actors;
|
return actors;
|
||||||
};
|
};
|
||||||
|
export const getCreatedActors = function () {
|
||||||
|
return createdActors;
|
||||||
|
};
|
||||||
|
export const getDestroyedActors = function () {
|
||||||
|
return destroyedActors;
|
||||||
|
};
|
||||||
export const getActor = function (id) {
|
export const getActor = function (id) {
|
||||||
return actors[id];
|
return actors[id];
|
||||||
};
|
};
|
||||||
@ -194,6 +204,8 @@ export const autoWrap = () => {
|
|||||||
|
|
||||||
export const clear = function () {
|
export const clear = function () {
|
||||||
actors = {};
|
actors = {};
|
||||||
|
createdActors = {};
|
||||||
|
destroyedActors = {};
|
||||||
boxes = [];
|
boxes = [];
|
||||||
messages = [];
|
messages = [];
|
||||||
sequenceNumbersEnabled = false;
|
sequenceNumbersEnabled = false;
|
||||||
@ -459,10 +471,21 @@ export const apply = function (param) {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'addParticipant':
|
case 'addParticipant':
|
||||||
addActor(param.actor, param.actor, param.description, 'participant');
|
addActor(param.actor, param.actor, param.description, param.draw);
|
||||||
break;
|
break;
|
||||||
case 'addActor':
|
case 'createParticipant':
|
||||||
addActor(param.actor, param.actor, param.description, 'actor');
|
if (actors[param.actor]) {
|
||||||
|
throw new Error(
|
||||||
|
"It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lastCreated = param.actor;
|
||||||
|
addActor(param.actor, param.actor, param.description, param.draw);
|
||||||
|
createdActors[param.actor] = messages.length;
|
||||||
|
break;
|
||||||
|
case 'destroyParticipant':
|
||||||
|
lastDestroyed = param.actor;
|
||||||
|
destroyedActors[param.actor] = messages.length;
|
||||||
break;
|
break;
|
||||||
case 'activeStart':
|
case 'activeStart':
|
||||||
addSignal(param.actor, undefined, undefined, param.signalType);
|
addSignal(param.actor, undefined, undefined, param.signalType);
|
||||||
@ -486,6 +509,27 @@ export const apply = function (param) {
|
|||||||
addDetails(param.actor, param.text);
|
addDetails(param.actor, param.text);
|
||||||
break;
|
break;
|
||||||
case 'addMessage':
|
case 'addMessage':
|
||||||
|
if (lastCreated) {
|
||||||
|
if (param.to !== lastCreated) {
|
||||||
|
throw new Error(
|
||||||
|
'The created participant ' +
|
||||||
|
lastCreated +
|
||||||
|
' does not have an associated creating message after its declaration. Please check the sequence diagram.'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
lastCreated = undefined;
|
||||||
|
}
|
||||||
|
} else if (lastDestroyed) {
|
||||||
|
if (param.to !== lastDestroyed && param.from !== lastDestroyed) {
|
||||||
|
throw new Error(
|
||||||
|
'The destroyed participant ' +
|
||||||
|
lastDestroyed +
|
||||||
|
' does not have an associated destroying message after its declaration. Please check the sequence diagram.'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
lastDestroyed = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
addSignal(param.from, param.to, param.msg, param.signalType);
|
addSignal(param.from, param.to, param.msg, param.signalType);
|
||||||
break;
|
break;
|
||||||
case 'boxStart':
|
case 'boxStart':
|
||||||
@ -566,6 +610,8 @@ export default {
|
|||||||
showSequenceNumbers,
|
showSequenceNumbers,
|
||||||
getMessages,
|
getMessages,
|
||||||
getActors,
|
getActors,
|
||||||
|
getCreatedActors,
|
||||||
|
getDestroyedActors,
|
||||||
getActor,
|
getActor,
|
||||||
getActorKeys,
|
getActorKeys,
|
||||||
getActorProperty,
|
getActorProperty,
|
||||||
|
@ -1404,6 +1404,62 @@ link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com
|
|||||||
expect(boxes[0].actorKeys).toEqual(['a', 'b']);
|
expect(boxes[0].actorKeys).toEqual(['a', 'b']);
|
||||||
expect(boxes[0].fill).toEqual('Aqua');
|
expect(boxes[0].fill).toEqual('Aqua');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle simple actor creation', async () => {
|
||||||
|
const str = `
|
||||||
|
sequenceDiagram
|
||||||
|
participant a as Alice
|
||||||
|
a ->>b: Hello Bob?
|
||||||
|
create participant c
|
||||||
|
b-->>c: Hello c!
|
||||||
|
c ->> b: Hello b?
|
||||||
|
create actor d as Donald
|
||||||
|
a ->> d: Hello Donald?
|
||||||
|
`;
|
||||||
|
await mermaidAPI.parse(str);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
const createdActors = diagram.db.getCreatedActors();
|
||||||
|
expect(actors['c'].name).toEqual('c');
|
||||||
|
expect(actors['c'].description).toEqual('c');
|
||||||
|
expect(actors['c'].type).toEqual('participant');
|
||||||
|
expect(createdActors['c']).toEqual(1);
|
||||||
|
expect(actors['d'].name).toEqual('d');
|
||||||
|
expect(actors['d'].description).toEqual('Donald');
|
||||||
|
expect(actors['d'].type).toEqual('actor');
|
||||||
|
expect(createdActors['d']).toEqual(3);
|
||||||
|
});
|
||||||
|
it('should handle simple actor destruction', async () => {
|
||||||
|
const str = `
|
||||||
|
sequenceDiagram
|
||||||
|
participant a as Alice
|
||||||
|
a ->>b: Hello Bob?
|
||||||
|
destroy a
|
||||||
|
b-->>a: Hello Alice!
|
||||||
|
b ->> c: Where is Alice?
|
||||||
|
destroy c
|
||||||
|
b ->> c: Where are you?
|
||||||
|
`;
|
||||||
|
await mermaidAPI.parse(str);
|
||||||
|
const destroyedActors = diagram.db.getDestroyedActors();
|
||||||
|
expect(destroyedActors['a']).toEqual(1);
|
||||||
|
expect(destroyedActors['c']).toEqual(3);
|
||||||
|
});
|
||||||
|
it('should handle the creation and destruction of the same actor', async () => {
|
||||||
|
const str = `
|
||||||
|
sequenceDiagram
|
||||||
|
a ->>b: Hello Bob?
|
||||||
|
create participant c
|
||||||
|
b ->>c: Hello c!
|
||||||
|
c ->> b: Hello b?
|
||||||
|
destroy c
|
||||||
|
b ->> c : Bye c !
|
||||||
|
`;
|
||||||
|
await mermaidAPI.parse(str);
|
||||||
|
const createdActors = diagram.db.getCreatedActors();
|
||||||
|
const destroyedActors = diagram.db.getDestroyedActors();
|
||||||
|
expect(createdActors['c']).toEqual(1);
|
||||||
|
expect(destroyedActors['c']).toEqual(3);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('when checking the bounds in a sequenceDiagram', function () {
|
describe('when checking the bounds in a sequenceDiagram', function () {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@ -1973,7 +2029,9 @@ participant Alice`;
|
|||||||
expect(bounds.startx).toBe(0);
|
expect(bounds.startx).toBe(0);
|
||||||
expect(bounds.starty).toBe(0);
|
expect(bounds.starty).toBe(0);
|
||||||
expect(bounds.stopx).toBe(conf.width);
|
expect(bounds.stopx).toBe(conf.width);
|
||||||
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height + conf.boxMargin);
|
expect(bounds.stopy).toBe(
|
||||||
|
models.lastActor().stopy + models.lastActor().height + conf.boxMargin
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -2025,7 +2083,7 @@ participant Alice
|
|||||||
expect(bounds.startx).toBe(0);
|
expect(bounds.startx).toBe(0);
|
||||||
expect(bounds.starty).toBe(0);
|
expect(bounds.starty).toBe(0);
|
||||||
expect(bounds.stopy).toBe(
|
expect(bounds.stopy).toBe(
|
||||||
models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin
|
models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('should handle one actor, when logLevel is 3 (dfg0)', async () => {
|
it('should handle one actor, when logLevel is 3 (dfg0)', async () => {
|
||||||
@ -2045,7 +2103,7 @@ participant Alice
|
|||||||
expect(bounds.startx).toBe(0);
|
expect(bounds.startx).toBe(0);
|
||||||
expect(bounds.starty).toBe(0);
|
expect(bounds.starty).toBe(0);
|
||||||
expect(bounds.stopy).toBe(
|
expect(bounds.stopy).toBe(
|
||||||
models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin
|
models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('should hide sequence numbers when autonumber is removed when autonumber is enabled', async () => {
|
it('should hide sequence numbers when autonumber is removed when autonumber is enabled', async () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// @ts-nocheck TODO: fix file
|
// @ts-nocheck TODO: fix file
|
||||||
import { select, selectAll } from 'd3';
|
import { select, selectAll } from 'd3';
|
||||||
import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw.js';
|
import svgDraw, { ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js';
|
||||||
import { log } from '../../logger.js';
|
import { log } from '../../logger.js';
|
||||||
import common from '../common/common.js';
|
import common from '../common/common.js';
|
||||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||||
@ -478,29 +478,19 @@ const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Di
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const drawActors = function (
|
const addActorRenderingData = function (
|
||||||
diagram,
|
diagram,
|
||||||
actors,
|
actors,
|
||||||
|
createdActors,
|
||||||
actorKeys,
|
actorKeys,
|
||||||
verticalPos,
|
verticalPos,
|
||||||
configuration,
|
|
||||||
messages,
|
messages,
|
||||||
isFooter
|
isFooter
|
||||||
) {
|
) {
|
||||||
if (configuration.hideUnusedParticipants === true) {
|
|
||||||
const newActors = new Set();
|
|
||||||
messages.forEach((message) => {
|
|
||||||
newActors.add(message.from);
|
|
||||||
newActors.add(message.to);
|
|
||||||
});
|
|
||||||
actorKeys = actorKeys.filter((actorKey) => newActors.has(actorKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the actors
|
|
||||||
let prevWidth = 0;
|
let prevWidth = 0;
|
||||||
let prevMargin = 0;
|
let prevMargin = 0;
|
||||||
let maxHeight = 0;
|
|
||||||
let prevBox = undefined;
|
let prevBox = undefined;
|
||||||
|
let maxHeight = 0;
|
||||||
|
|
||||||
for (const actorKey of actorKeys) {
|
for (const actorKey of actorKeys) {
|
||||||
const actor = actors[actorKey];
|
const actor = actors[actorKey];
|
||||||
@ -528,12 +518,16 @@ export const drawActors = function (
|
|||||||
actor.height = common.getMax(actor.height || conf.height, conf.height);
|
actor.height = common.getMax(actor.height || conf.height, conf.height);
|
||||||
actor.margin = actor.margin || conf.actorMargin;
|
actor.margin = actor.margin || conf.actorMargin;
|
||||||
|
|
||||||
actor.x = prevWidth + prevMargin;
|
maxHeight = common.getMax(maxHeight, actor.height);
|
||||||
actor.y = bounds.getVerticalPos();
|
|
||||||
|
// if the actor is created by a message, widen margin
|
||||||
|
if (createdActors[actor.name]) {
|
||||||
|
prevMargin += actor.width / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.x = prevWidth + prevMargin;
|
||||||
|
actor.starty = bounds.getVerticalPos();
|
||||||
|
|
||||||
// Draw the box with the attached line
|
|
||||||
const height = svgDraw.drawActor(diagram, actor, conf, isFooter);
|
|
||||||
maxHeight = common.getMax(maxHeight, height);
|
|
||||||
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
|
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
|
||||||
|
|
||||||
prevWidth += actor.width + prevMargin;
|
prevWidth += actor.width + prevMargin;
|
||||||
@ -554,6 +548,28 @@ export const drawActors = function (
|
|||||||
bounds.bumpVerticalPos(maxHeight);
|
bounds.bumpVerticalPos(maxHeight);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const drawActors = function (diagram, actors, actorKeys, isFooter) {
|
||||||
|
if (!isFooter) {
|
||||||
|
for (const actorKey of actorKeys) {
|
||||||
|
const actor = actors[actorKey];
|
||||||
|
// Draw the box with the attached line
|
||||||
|
svgDraw.drawActor(diagram, actor, conf, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let maxHeight = 0;
|
||||||
|
bounds.bumpVerticalPos(conf.boxMargin * 2);
|
||||||
|
for (const actorKey of actorKeys) {
|
||||||
|
const actor = actors[actorKey];
|
||||||
|
if (!actor.stopy) {
|
||||||
|
actor.stopy = bounds.getVerticalPos();
|
||||||
|
}
|
||||||
|
const height = svgDraw.drawActor(diagram, actor, conf, true);
|
||||||
|
maxHeight = common.getMax(maxHeight, height);
|
||||||
|
}
|
||||||
|
bounds.bumpVerticalPos(maxHeight + conf.boxMargin);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const drawActorsPopup = function (diagram, actors, actorKeys, doc) {
|
export const drawActorsPopup = function (diagram, actors, actorKeys, doc) {
|
||||||
let maxHeight = 0;
|
let maxHeight = 0;
|
||||||
let maxWidth = 0;
|
let maxWidth = 0;
|
||||||
@ -633,6 +649,95 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop
|
|||||||
bounds.bumpVerticalPos(heightAdjust);
|
bounds.bumpVerticalPos(heightAdjust);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust the msgModel and the actor for the rendering in case the latter is created or destroyed by the msg
|
||||||
|
* @param msg - the potentially creating or destroying message
|
||||||
|
* @param msgModel - the model associated with the message
|
||||||
|
* @param lineStartY - the y position of the message line
|
||||||
|
* @param index - the index of the current actor under consideration
|
||||||
|
* @param actors - the array of all actors
|
||||||
|
* @param createdActors - the array of actors created in the diagram
|
||||||
|
* @param destroyedActors - the array of actors destroyed in the diagram
|
||||||
|
*/
|
||||||
|
function adjustCreatedDestroyedData(
|
||||||
|
msg,
|
||||||
|
msgModel,
|
||||||
|
lineStartY,
|
||||||
|
index,
|
||||||
|
actors,
|
||||||
|
createdActors,
|
||||||
|
destroyedActors
|
||||||
|
) {
|
||||||
|
function receiverAdjustment(actor, adjustment) {
|
||||||
|
if (actor.x < actors[msg.from].x) {
|
||||||
|
bounds.insert(
|
||||||
|
msgModel.stopx - adjustment,
|
||||||
|
msgModel.starty,
|
||||||
|
msgModel.startx,
|
||||||
|
msgModel.stopy + actor.height / 2 + conf.noteMargin
|
||||||
|
);
|
||||||
|
msgModel.stopx = msgModel.stopx + adjustment;
|
||||||
|
} else {
|
||||||
|
bounds.insert(
|
||||||
|
msgModel.startx,
|
||||||
|
msgModel.starty,
|
||||||
|
msgModel.stopx + adjustment,
|
||||||
|
msgModel.stopy + actor.height / 2 + conf.noteMargin
|
||||||
|
);
|
||||||
|
msgModel.stopx = msgModel.stopx - adjustment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function senderAdjustment(actor, adjustment) {
|
||||||
|
if (actor.x < actors[msg.to].x) {
|
||||||
|
bounds.insert(
|
||||||
|
msgModel.startx - adjustment,
|
||||||
|
msgModel.starty,
|
||||||
|
msgModel.stopx,
|
||||||
|
msgModel.stopy + actor.height / 2 + conf.noteMargin
|
||||||
|
);
|
||||||
|
msgModel.startx = msgModel.startx + adjustment;
|
||||||
|
} else {
|
||||||
|
bounds.insert(
|
||||||
|
msgModel.stopx,
|
||||||
|
msgModel.starty,
|
||||||
|
msgModel.startx + adjustment,
|
||||||
|
msgModel.stopy + actor.height / 2 + conf.noteMargin
|
||||||
|
);
|
||||||
|
msgModel.startx = msgModel.startx - adjustment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it is a create message
|
||||||
|
if (createdActors[msg.to] == index) {
|
||||||
|
const actor = actors[msg.to];
|
||||||
|
const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3;
|
||||||
|
receiverAdjustment(actor, adjustment);
|
||||||
|
actor.starty = lineStartY - actor.height / 2;
|
||||||
|
bounds.bumpVerticalPos(actor.height / 2);
|
||||||
|
}
|
||||||
|
// if it is a destroy sender message
|
||||||
|
else if (destroyedActors[msg.from] == index) {
|
||||||
|
const actor = actors[msg.from];
|
||||||
|
if (conf.mirrorActors) {
|
||||||
|
const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2;
|
||||||
|
senderAdjustment(actor, adjustment);
|
||||||
|
}
|
||||||
|
actor.stopy = lineStartY - actor.height / 2;
|
||||||
|
bounds.bumpVerticalPos(actor.height / 2);
|
||||||
|
}
|
||||||
|
// if it is a destroy receiver message
|
||||||
|
else if (destroyedActors[msg.to] == index) {
|
||||||
|
const actor = actors[msg.to];
|
||||||
|
if (conf.mirrorActors) {
|
||||||
|
const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3;
|
||||||
|
receiverAdjustment(actor, adjustment);
|
||||||
|
}
|
||||||
|
actor.stopy = lineStartY - actor.height / 2;
|
||||||
|
bounds.bumpVerticalPos(actor.height / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
|
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
|
||||||
*
|
*
|
||||||
@ -666,8 +771,10 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
|||||||
|
|
||||||
// Fetch data from the parsing
|
// Fetch data from the parsing
|
||||||
const actors = diagObj.db.getActors();
|
const actors = diagObj.db.getActors();
|
||||||
|
const createdActors = diagObj.db.getCreatedActors();
|
||||||
|
const destroyedActors = diagObj.db.getDestroyedActors();
|
||||||
const boxes = diagObj.db.getBoxes();
|
const boxes = diagObj.db.getBoxes();
|
||||||
const actorKeys = diagObj.db.getActorKeys();
|
let actorKeys = diagObj.db.getActorKeys();
|
||||||
const messages = diagObj.db.getMessages();
|
const messages = diagObj.db.getMessages();
|
||||||
const title = diagObj.db.getDiagramTitle();
|
const title = diagObj.db.getDiagramTitle();
|
||||||
const hasBoxes = diagObj.db.hasAtLeastOneBox();
|
const hasBoxes = diagObj.db.hasAtLeastOneBox();
|
||||||
@ -686,7 +793,16 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawActors(diagram, actors, actorKeys, 0, conf, messages, false);
|
if (conf.hideUnusedParticipants === true) {
|
||||||
|
const newActors = new Set();
|
||||||
|
messages.forEach((message) => {
|
||||||
|
newActors.add(message.from);
|
||||||
|
newActors.add(message.to);
|
||||||
|
});
|
||||||
|
actorKeys = actorKeys.filter((actorKey) => newActors.has(actorKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
addActorRenderingData(diagram, actors, createdActors, actorKeys, 0, messages, false);
|
||||||
const loopWidths = calculateLoopBounds(messages, actors, maxMessageWidthPerActor, diagObj);
|
const loopWidths = calculateLoopBounds(messages, actors, maxMessageWidthPerActor, diagObj);
|
||||||
|
|
||||||
// The arrow head definition is attached to the svg once
|
// The arrow head definition is attached to the svg once
|
||||||
@ -720,7 +836,8 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
|||||||
let sequenceIndex = 1;
|
let sequenceIndex = 1;
|
||||||
let sequenceIndexStep = 1;
|
let sequenceIndexStep = 1;
|
||||||
const messagesToDraw = [];
|
const messagesToDraw = [];
|
||||||
messages.forEach(function (msg) {
|
const backgrounds = [];
|
||||||
|
messages.forEach(function (msg, index) {
|
||||||
let loopModel, noteModel, msgModel;
|
let loopModel, noteModel, msgModel;
|
||||||
|
|
||||||
switch (msg.type) {
|
switch (msg.type) {
|
||||||
@ -757,7 +874,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
|||||||
break;
|
break;
|
||||||
case diagObj.db.LINETYPE.RECT_END:
|
case diagObj.db.LINETYPE.RECT_END:
|
||||||
loopModel = bounds.endLoop();
|
loopModel = bounds.endLoop();
|
||||||
svgDraw.drawBackgroundRect(diagram, loopModel);
|
backgrounds.push(loopModel);
|
||||||
bounds.models.addLoop(loopModel);
|
bounds.models.addLoop(loopModel);
|
||||||
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
|
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
|
||||||
break;
|
break;
|
||||||
@ -876,13 +993,20 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
try {
|
try {
|
||||||
// lastMsg = msg
|
|
||||||
bounds.resetVerticalPos();
|
|
||||||
msgModel = msg.msgModel;
|
msgModel = msg.msgModel;
|
||||||
msgModel.starty = bounds.getVerticalPos();
|
msgModel.starty = bounds.getVerticalPos();
|
||||||
msgModel.sequenceIndex = sequenceIndex;
|
msgModel.sequenceIndex = sequenceIndex;
|
||||||
msgModel.sequenceVisible = diagObj.db.showSequenceNumbers();
|
msgModel.sequenceVisible = diagObj.db.showSequenceNumbers();
|
||||||
const lineStartY = boundMessage(diagram, msgModel);
|
const lineStartY = boundMessage(diagram, msgModel);
|
||||||
|
adjustCreatedDestroyedData(
|
||||||
|
msg,
|
||||||
|
msgModel,
|
||||||
|
lineStartY,
|
||||||
|
index,
|
||||||
|
actors,
|
||||||
|
createdActors,
|
||||||
|
destroyedActors
|
||||||
|
);
|
||||||
messagesToDraw.push({ messageModel: msgModel, lineStartY: lineStartY });
|
messagesToDraw.push({ messageModel: msgModel, lineStartY: lineStartY });
|
||||||
bounds.models.addMessage(msgModel);
|
bounds.models.addMessage(msgModel);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -907,15 +1031,16 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
messagesToDraw.forEach((e) => drawMessage(diagram, e.messageModel, e.lineStartY, diagObj));
|
log.debug('createdActors', createdActors);
|
||||||
|
log.debug('destroyedActors', destroyedActors);
|
||||||
|
|
||||||
|
drawActors(diagram, actors, actorKeys, false);
|
||||||
|
messagesToDraw.forEach((e) => drawMessage(diagram, e.messageModel, e.lineStartY, diagObj));
|
||||||
if (conf.mirrorActors) {
|
if (conf.mirrorActors) {
|
||||||
// Draw actors below diagram
|
drawActors(diagram, actors, actorKeys, true);
|
||||||
bounds.bumpVerticalPos(conf.boxMargin * 2);
|
|
||||||
drawActors(diagram, actors, actorKeys, bounds.getVerticalPos(), conf, messages, true);
|
|
||||||
bounds.bumpVerticalPos(conf.boxMargin);
|
|
||||||
fixLifeLineHeights(diagram, bounds.getVerticalPos());
|
|
||||||
}
|
}
|
||||||
|
backgrounds.forEach((e) => svgDraw.drawBackgroundRect(diagram, e));
|
||||||
|
fixLifeLineHeights(diagram, actors, actorKeys, conf);
|
||||||
|
|
||||||
bounds.models.boxes.forEach(function (box) {
|
bounds.models.boxes.forEach(function (box) {
|
||||||
box.height = bounds.getVerticalPos() - box.y;
|
box.height = bounds.getVerticalPos() - box.y;
|
||||||
@ -937,11 +1062,6 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
|||||||
|
|
||||||
const { bounds: box } = bounds.getBounds();
|
const { bounds: box } = bounds.getBounds();
|
||||||
|
|
||||||
// Adjust line height of actor lines now that the height of the diagram is known
|
|
||||||
log.debug('For line height fix Querying: #' + id + ' .actor-line');
|
|
||||||
const actorLines = selectAll('#' + id + ' .actor-line');
|
|
||||||
actorLines.attr('y2', box.stopy);
|
|
||||||
|
|
||||||
// Make sure the height of the diagram supports long menus.
|
// Make sure the height of the diagram supports long menus.
|
||||||
let boxHeight = box.stopy - box.starty;
|
let boxHeight = box.stopy - box.starty;
|
||||||
if (boxHeight < requiredBoxSize.maxHeight) {
|
if (boxHeight < requiredBoxSize.maxHeight) {
|
||||||
|
@ -4,6 +4,8 @@ import { addFunction } from '../../interactionDb.js';
|
|||||||
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
|
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
|
||||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||||
|
|
||||||
|
export const ACTOR_TYPE_WIDTH = 18 * 2;
|
||||||
|
|
||||||
export const drawRect = function (elem, rectData) {
|
export const drawRect = function (elem, rectData) {
|
||||||
return svgDrawCommon.drawRect(elem, rectData);
|
return svgDrawCommon.drawRect(elem, rectData);
|
||||||
};
|
};
|
||||||
@ -294,14 +296,19 @@ export const drawLabel = function (elem, txtObject) {
|
|||||||
|
|
||||||
let actorCnt = -1;
|
let actorCnt = -1;
|
||||||
|
|
||||||
export const fixLifeLineHeights = (diagram, bounds) => {
|
export const fixLifeLineHeights = (diagram, actors, actorKeys, conf) => {
|
||||||
if (!diagram.selectAll) {
|
if (!diagram.select) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
diagram
|
actorKeys.forEach((actorKey) => {
|
||||||
.selectAll('.actor-line')
|
const actor = actors[actorKey];
|
||||||
.attr('class', '200')
|
const actorDOM = diagram.select('#actor' + actor.actorCnt);
|
||||||
.attr('y2', bounds - 55);
|
if (!conf.mirrorActors && actor.stopy) {
|
||||||
|
actorDOM.attr('y2', actor.stopy + actor.height / 2);
|
||||||
|
} else if (conf.mirrorActors) {
|
||||||
|
actorDOM.attr('y2', actor.stopy);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -313,10 +320,11 @@ export const fixLifeLineHeights = (diagram, bounds) => {
|
|||||||
* @param {boolean} isFooter - If the actor is the footer one
|
* @param {boolean} isFooter - If the actor is the footer one
|
||||||
*/
|
*/
|
||||||
const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
||||||
|
const actorY = isFooter ? actor.stopy : actor.starty;
|
||||||
const center = actor.x + actor.width / 2;
|
const center = actor.x + actor.width / 2;
|
||||||
const centerY = actor.y + 5;
|
const centerY = actorY + 5;
|
||||||
|
|
||||||
const boxpluslineGroup = elem.append('g');
|
const boxpluslineGroup = elem.append('g').lower();
|
||||||
var g = boxpluslineGroup;
|
var g = boxpluslineGroup;
|
||||||
|
|
||||||
if (!isFooter) {
|
if (!isFooter) {
|
||||||
@ -328,6 +336,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
|||||||
.attr('x2', center)
|
.attr('x2', center)
|
||||||
.attr('y2', 2000)
|
.attr('y2', 2000)
|
||||||
.attr('class', 'actor-line')
|
.attr('class', 'actor-line')
|
||||||
|
.attr('class', '200')
|
||||||
.attr('stroke-width', '0.5px')
|
.attr('stroke-width', '0.5px')
|
||||||
.attr('stroke', '#999');
|
.attr('stroke', '#999');
|
||||||
|
|
||||||
@ -348,7 +357,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
|||||||
rect.fill = '#eaeaea';
|
rect.fill = '#eaeaea';
|
||||||
}
|
}
|
||||||
rect.x = actor.x;
|
rect.x = actor.x;
|
||||||
rect.y = actor.y;
|
rect.y = actorY;
|
||||||
rect.width = actor.width;
|
rect.width = actor.width;
|
||||||
rect.height = actor.height;
|
rect.height = actor.height;
|
||||||
rect.class = cssclass;
|
rect.class = cssclass;
|
||||||
@ -388,8 +397,11 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const drawActorTypeActor = function (elem, actor, conf, isFooter) {
|
const drawActorTypeActor = function (elem, actor, conf, isFooter) {
|
||||||
|
const actorY = isFooter ? actor.stopy : actor.starty;
|
||||||
const center = actor.x + actor.width / 2;
|
const center = actor.x + actor.width / 2;
|
||||||
const centerY = actor.y + 80;
|
const centerY = actorY + 80;
|
||||||
|
|
||||||
|
elem.lower();
|
||||||
|
|
||||||
if (!isFooter) {
|
if (!isFooter) {
|
||||||
actorCnt++;
|
actorCnt++;
|
||||||
@ -401,15 +413,18 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
|
|||||||
.attr('x2', center)
|
.attr('x2', center)
|
||||||
.attr('y2', 2000)
|
.attr('y2', 2000)
|
||||||
.attr('class', 'actor-line')
|
.attr('class', 'actor-line')
|
||||||
|
.attr('class', '200')
|
||||||
.attr('stroke-width', '0.5px')
|
.attr('stroke-width', '0.5px')
|
||||||
.attr('stroke', '#999');
|
.attr('stroke', '#999');
|
||||||
|
|
||||||
|
actor.actorCnt = actorCnt;
|
||||||
}
|
}
|
||||||
const actElem = elem.append('g');
|
const actElem = elem.append('g');
|
||||||
actElem.attr('class', 'actor-man');
|
actElem.attr('class', 'actor-man');
|
||||||
|
|
||||||
const rect = svgDrawCommon.getNoteRect();
|
const rect = svgDrawCommon.getNoteRect();
|
||||||
rect.x = actor.x;
|
rect.x = actor.x;
|
||||||
rect.y = actor.y;
|
rect.y = actorY;
|
||||||
rect.fill = '#eaeaea';
|
rect.fill = '#eaeaea';
|
||||||
rect.width = actor.width;
|
rect.width = actor.width;
|
||||||
rect.height = actor.height;
|
rect.height = actor.height;
|
||||||
@ -421,33 +436,33 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
|
|||||||
.append('line')
|
.append('line')
|
||||||
.attr('id', 'actor-man-torso' + actorCnt)
|
.attr('id', 'actor-man-torso' + actorCnt)
|
||||||
.attr('x1', center)
|
.attr('x1', center)
|
||||||
.attr('y1', actor.y + 25)
|
.attr('y1', actorY + 25)
|
||||||
.attr('x2', center)
|
.attr('x2', center)
|
||||||
.attr('y2', actor.y + 45);
|
.attr('y2', actorY + 45);
|
||||||
|
|
||||||
actElem
|
actElem
|
||||||
.append('line')
|
.append('line')
|
||||||
.attr('id', 'actor-man-arms' + actorCnt)
|
.attr('id', 'actor-man-arms' + actorCnt)
|
||||||
.attr('x1', center - 18)
|
.attr('x1', center - ACTOR_TYPE_WIDTH / 2)
|
||||||
.attr('y1', actor.y + 33)
|
.attr('y1', actorY + 33)
|
||||||
.attr('x2', center + 18)
|
.attr('x2', center + ACTOR_TYPE_WIDTH / 2)
|
||||||
.attr('y2', actor.y + 33);
|
.attr('y2', actorY + 33);
|
||||||
actElem
|
actElem
|
||||||
.append('line')
|
.append('line')
|
||||||
.attr('x1', center - 18)
|
.attr('x1', center - ACTOR_TYPE_WIDTH / 2)
|
||||||
.attr('y1', actor.y + 60)
|
.attr('y1', actorY + 60)
|
||||||
.attr('x2', center)
|
.attr('x2', center)
|
||||||
.attr('y2', actor.y + 45);
|
.attr('y2', actorY + 45);
|
||||||
actElem
|
actElem
|
||||||
.append('line')
|
.append('line')
|
||||||
.attr('x1', center)
|
.attr('x1', center)
|
||||||
.attr('y1', actor.y + 45)
|
.attr('y1', actorY + 45)
|
||||||
.attr('x2', center + 16)
|
.attr('x2', center + ACTOR_TYPE_WIDTH / 2 - 2)
|
||||||
.attr('y2', actor.y + 60);
|
.attr('y2', actorY + 60);
|
||||||
|
|
||||||
const circle = actElem.append('circle');
|
const circle = actElem.append('circle');
|
||||||
circle.attr('cx', actor.x + actor.width / 2);
|
circle.attr('cx', actor.x + actor.width / 2);
|
||||||
circle.attr('cy', actor.y + 10);
|
circle.attr('cy', actorY + 10);
|
||||||
circle.attr('r', 15);
|
circle.attr('r', 15);
|
||||||
circle.attr('width', actor.width);
|
circle.attr('width', actor.width);
|
||||||
circle.attr('height', actor.height);
|
circle.attr('height', actor.height);
|
||||||
|
@ -358,7 +358,7 @@ const setupDoc = (g, parentParsedItem, doc, diagramStates, diagramDb, altFlag) =
|
|||||||
* Look through all of the documents (docs) in the parsedItems
|
* Look through all of the documents (docs) in the parsedItems
|
||||||
* Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction.
|
* Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction.
|
||||||
* @param {object[]} parsedItem - the parsed statement item to look through
|
* @param {object[]} parsedItem - the parsed statement item to look through
|
||||||
* @param [defaultDir=DEFAULT_NESTED_DOC_DIR] - the direction to use if none is found
|
* @param [defaultDir] - the direction to use if none is found
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
const getDir = (parsedItem, defaultDir = DEFAULT_NESTED_DOC_DIR) => {
|
const getDir = (parsedItem, defaultDir = DEFAULT_NESTED_DOC_DIR) => {
|
||||||
|
@ -57,8 +57,6 @@ export const draw = function (text, id, _version, diagObj) {
|
|||||||
: select('body');
|
: select('body');
|
||||||
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
||||||
|
|
||||||
// diagObj.db.clear();
|
|
||||||
// parser.parse(text);
|
|
||||||
log.debug('Rendering diagram ' + text);
|
log.debug('Rendering diagram ' + text);
|
||||||
|
|
||||||
// Fetch the default direction, use TD if none was found
|
// Fetch the default direction, use TD if none was found
|
||||||
|
@ -30,12 +30,6 @@ export const draw = function (text: string, id: string, version: string, diagObj
|
|||||||
// @ts-expect-error - wrong config?
|
// @ts-expect-error - wrong config?
|
||||||
const LEFT_MARGIN = conf.leftMargin ?? 50;
|
const LEFT_MARGIN = conf.leftMargin ?? 50;
|
||||||
|
|
||||||
//2. Clear the diagram db before parsing
|
|
||||||
diagObj.db.clear?.();
|
|
||||||
|
|
||||||
//3. Parse the diagram text
|
|
||||||
diagObj.parser.parse(text + '\n');
|
|
||||||
|
|
||||||
log.debug('timeline', diagObj.db);
|
log.debug('timeline', diagObj.db);
|
||||||
|
|
||||||
const securityLevel = conf.securityLevel;
|
const securityLevel = conf.securityLevel;
|
||||||
|
@ -49,8 +49,6 @@ const conf = getConfig().journey;
|
|||||||
const LEFT_MARGIN = conf.leftMargin;
|
const LEFT_MARGIN = conf.leftMargin;
|
||||||
export const draw = function (text, id, version, diagObj) {
|
export const draw = function (text, id, version, diagObj) {
|
||||||
const conf = getConfig().journey;
|
const conf = getConfig().journey;
|
||||||
diagObj.db.clear();
|
|
||||||
diagObj.parser.parse(text + '\n');
|
|
||||||
|
|
||||||
const securityLevel = getConfig().securityLevel;
|
const securityLevel = getConfig().securityLevel;
|
||||||
// Handle root and Document for when rendering in sandbox mode
|
// Handle root and Document for when rendering in sandbox mode
|
||||||
|
@ -34,7 +34,7 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync, rmdirSync }
|
|||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { globby } from 'globby';
|
import { globby } from 'globby';
|
||||||
import { JSDOM } from 'jsdom';
|
import { JSDOM } from 'jsdom';
|
||||||
import type { Code, Root } from 'mdast';
|
import type { Code, ListItem, Root, Text } from 'mdast';
|
||||||
import { posix, dirname, relative, join } from 'path';
|
import { posix, dirname, relative, join } from 'path';
|
||||||
import prettier from 'prettier';
|
import prettier from 'prettier';
|
||||||
import { remark } from 'remark';
|
import { remark } from 'remark';
|
||||||
@ -44,6 +44,7 @@ import chokidar from 'chokidar';
|
|||||||
import mm from 'micromatch';
|
import mm from 'micromatch';
|
||||||
// @ts-ignore No typescript declaration file
|
// @ts-ignore No typescript declaration file
|
||||||
import flatmap from 'unist-util-flatmap';
|
import flatmap from 'unist-util-flatmap';
|
||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
|
||||||
const MERMAID_MAJOR_VERSION = (
|
const MERMAID_MAJOR_VERSION = (
|
||||||
JSON.parse(readFileSync('../mermaid/package.json', 'utf8')).version as string
|
JSON.parse(readFileSync('../mermaid/package.json', 'utf8')).version as string
|
||||||
@ -122,7 +123,7 @@ const changeToFinalDocDir = (file: string): string => {
|
|||||||
const logWasOrShouldBeTransformed = (filename: string, wasCopied: boolean) => {
|
const logWasOrShouldBeTransformed = (filename: string, wasCopied: boolean) => {
|
||||||
const changeMsg = wasCopied ? LOGMSG_TRANSFORMED : LOGMSG_TO_BE_TRANSFORMED;
|
const changeMsg = wasCopied ? LOGMSG_TRANSFORMED : LOGMSG_TO_BE_TRANSFORMED;
|
||||||
let logMsg: string;
|
let logMsg: string;
|
||||||
logMsg = ` File ${changeMsg}: ${filename}`;
|
logMsg = ` File ${changeMsg}: ${filename.replace(FINAL_DOCS_DIR, SOURCE_DOCS_DIR)}`;
|
||||||
if (wasCopied) {
|
if (wasCopied) {
|
||||||
logMsg += LOGMSG_COPIED;
|
logMsg += LOGMSG_COPIED;
|
||||||
}
|
}
|
||||||
@ -150,6 +151,7 @@ const copyTransformedContents = (filename: string, doCopy = false, transformedCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
filesTransformed.add(fileInFinalDocDir);
|
filesTransformed.add(fileInFinalDocDir);
|
||||||
|
|
||||||
if (doCopy) {
|
if (doCopy) {
|
||||||
writeFileSync(fileInFinalDocDir, newBuffer);
|
writeFileSync(fileInFinalDocDir, newBuffer);
|
||||||
}
|
}
|
||||||
@ -321,6 +323,123 @@ const transformMarkdown = (file: string) => {
|
|||||||
copyTransformedContents(file, !verifyOnly, formatted);
|
copyTransformedContents(file, !verifyOnly, formatted);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
import { load, JSON_SCHEMA } from 'js-yaml';
|
||||||
|
// @ts-ignore: we're importing internal jsonschema2md functions
|
||||||
|
import { default as schemaLoader } from '@adobe/jsonschema2md/lib/schemaProxy.js';
|
||||||
|
// @ts-ignore: we're importing internal jsonschema2md functions
|
||||||
|
import { default as traverseSchemas } from '@adobe/jsonschema2md/lib/traverseSchema.js';
|
||||||
|
// @ts-ignore: we're importing internal jsonschema2md functions
|
||||||
|
import { default as buildMarkdownFromSchema } from '@adobe/jsonschema2md/lib/markdownBuilder.js';
|
||||||
|
// @ts-ignore: we're importing internal jsonschema2md functions
|
||||||
|
import { default as jsonSchemaReadmeBuilder } from '@adobe/jsonschema2md/lib/readmeBuilder.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the given JSON Schema into Markdown documentation
|
||||||
|
*/
|
||||||
|
async function transormJsonSchema(file: string) {
|
||||||
|
const yamlContents = readSyncedUTF8file(file);
|
||||||
|
const jsonSchema = load(yamlContents, {
|
||||||
|
filename: file,
|
||||||
|
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
|
||||||
|
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
|
||||||
|
schema: JSON_SCHEMA,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Location of the `schema.yaml` files */
|
||||||
|
const SCHEMA_INPUT_DIR = 'src/schemas/';
|
||||||
|
/**
|
||||||
|
* Location to store the generated `schema.json` file for the website
|
||||||
|
*
|
||||||
|
* Because Vitepress doesn't handle bundling `.json` files properly, we need
|
||||||
|
* to instead place it into a `public/` subdirectory.
|
||||||
|
*/
|
||||||
|
const SCHEMA_OUTPUT_DIR = 'src/docs/public/schemas/';
|
||||||
|
const VITEPRESS_PUBLIC_DIR = 'src/docs/public';
|
||||||
|
/**
|
||||||
|
* Location to store the generated Schema Markdown docs.
|
||||||
|
* Links to JSON Schemas should automatically be rewritten to point to
|
||||||
|
* `SCHEMA_OUTPUT_DIR`.
|
||||||
|
*/
|
||||||
|
const SCHEMA_MARKDOWN_OUTPUT_DIR = join('src', 'docs', 'config', 'schema-docs');
|
||||||
|
|
||||||
|
// write .schema.json files
|
||||||
|
const jsonFileName = file
|
||||||
|
.replace('.schema.yaml', '.schema.json')
|
||||||
|
.replace(SCHEMA_INPUT_DIR, SCHEMA_OUTPUT_DIR);
|
||||||
|
copyTransformedContents(jsonFileName, !verifyOnly, JSON.stringify(jsonSchema, undefined, 2));
|
||||||
|
|
||||||
|
const schemas = traverseSchemas([schemaLoader()(jsonFileName, jsonSchema)]);
|
||||||
|
|
||||||
|
// ignore output of this function
|
||||||
|
// for some reason, without calling this function, we get some broken links
|
||||||
|
// this is probably a bug in @adobe/jsonschema2md
|
||||||
|
jsonSchemaReadmeBuilder({ readme: true })(schemas);
|
||||||
|
|
||||||
|
// write Markdown files
|
||||||
|
const markdownFiles = buildMarkdownFromSchema({
|
||||||
|
header: true,
|
||||||
|
// links,
|
||||||
|
includeProperties: ['tsType'], // Custom TypeScript type
|
||||||
|
exampleFormat: 'json',
|
||||||
|
// skipProperties,
|
||||||
|
/**
|
||||||
|
* Automatically rewrite schema paths passed to `schemaLoader`
|
||||||
|
* (e.g. src/docs/schemas/config.schema.json)
|
||||||
|
* to relative links (e.g. /schemas/config.schema.json)
|
||||||
|
*
|
||||||
|
* See https://vitepress.vuejs.org/guide/asset-handling
|
||||||
|
*
|
||||||
|
* @param origin - Original schema path (relative to this script).
|
||||||
|
* @returns New absolute Vitepress path to schema
|
||||||
|
*/
|
||||||
|
rewritelinks: (origin: string) => {
|
||||||
|
return `/${relative(VITEPRESS_PUBLIC_DIR, origin)}`;
|
||||||
|
},
|
||||||
|
})(schemas);
|
||||||
|
|
||||||
|
for (const [name, markdownAst] of Object.entries(markdownFiles)) {
|
||||||
|
/*
|
||||||
|
* Converts list entries of shape '- tsType: () => Partial<FontConfig>'
|
||||||
|
* into '- tsType: `() => Partial<FontConfig>`' (e.g. escaping with back-ticks),
|
||||||
|
* as otherwise VitePress doesn't like the <FontConfig> bit.
|
||||||
|
*/
|
||||||
|
visit(markdownAst as Root, 'listItem', (listEntry: ListItem) => {
|
||||||
|
let listText: Text;
|
||||||
|
const blockItem = listEntry.children[0];
|
||||||
|
if (blockItem.type === 'paragraph' && blockItem.children[0].type === 'text') {
|
||||||
|
listText = blockItem.children[0];
|
||||||
|
} // @ts-expect-error: MD AST output from @adobe/jsonschema2md is technically wrong
|
||||||
|
else if (blockItem.type === 'text') {
|
||||||
|
listText = blockItem;
|
||||||
|
} else {
|
||||||
|
return; // skip
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listText.value.startsWith('tsType: ')) {
|
||||||
|
listText.value = listText.value.replace(/tsType: (.*)/g, 'tsType: `$1`');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const transformed = remark()
|
||||||
|
.use(remarkGfm)
|
||||||
|
.use(remarkFrontmatter, ['yaml']) // support YAML front-matter in Markdown
|
||||||
|
.use(transformMarkdownAst, {
|
||||||
|
// mermaid project specific plugin
|
||||||
|
originalFilename: file,
|
||||||
|
addAutogeneratedWarning: !noHeader,
|
||||||
|
removeYAML: !noHeader,
|
||||||
|
})
|
||||||
|
.stringify(markdownAst as Root);
|
||||||
|
|
||||||
|
const formatted = prettier.format(transformed, {
|
||||||
|
parser: 'markdown',
|
||||||
|
...prettierConfig,
|
||||||
|
});
|
||||||
|
const newFileName = join(SCHEMA_MARKDOWN_OUTPUT_DIR, `${name}.md`);
|
||||||
|
copyTransformedContents(newFileName, !verifyOnly, formatted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform an HTML file and write the transformed file to the directory for published
|
* Transform an HTML file and write the transformed file to the directory for published
|
||||||
* documentation
|
* documentation
|
||||||
@ -362,15 +481,15 @@ const transformHtml = (filename: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getGlobs = (globs: string[]): string[] => {
|
const getGlobs = (globs: string[]): string[] => {
|
||||||
globs.push(
|
globs.push('!**/dist/**', '!**/redirect.spec.ts', '!**/landing/**', '!**/node_modules/**');
|
||||||
'!**/dist',
|
|
||||||
'!**/redirect.spec.ts',
|
|
||||||
'!**/landing',
|
|
||||||
'!**/node_modules',
|
|
||||||
'!**/user-avatars'
|
|
||||||
);
|
|
||||||
if (!vitepress) {
|
if (!vitepress) {
|
||||||
globs.push('!**/.vitepress', '!**/vite.config.ts', '!src/docs/index.md', '!**/package.json');
|
globs.push(
|
||||||
|
'!**/.vitepress/**',
|
||||||
|
'!**/vite.config.ts',
|
||||||
|
'!src/docs/index.md',
|
||||||
|
'!**/package.json',
|
||||||
|
'!**/user-avatars/**'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return globs;
|
return globs;
|
||||||
};
|
};
|
||||||
@ -388,6 +507,14 @@ const main = async () => {
|
|||||||
const sourceDirGlob = posix.join('.', SOURCE_DOCS_DIR, '**');
|
const sourceDirGlob = posix.join('.', SOURCE_DOCS_DIR, '**');
|
||||||
const action = verifyOnly ? 'Verifying' : 'Transforming';
|
const action = verifyOnly ? 'Verifying' : 'Transforming';
|
||||||
|
|
||||||
|
if (vitepress) {
|
||||||
|
console.log(`${action} 1 .schema.yaml file`);
|
||||||
|
await transormJsonSchema('src/schemas/config.schema.yaml');
|
||||||
|
} else {
|
||||||
|
// skip because this creates so many Markdown files that it lags git
|
||||||
|
console.log('Skipping 1 .schema.yaml file');
|
||||||
|
}
|
||||||
|
|
||||||
const mdFileGlobs = getGlobs([posix.join(sourceDirGlob, '*.md')]);
|
const mdFileGlobs = getGlobs([posix.join(sourceDirGlob, '*.md')]);
|
||||||
const mdFiles = await getFilesFromGlobs(mdFileGlobs);
|
const mdFiles = await getFilesFromGlobs(mdFileGlobs);
|
||||||
console.log(`${action} ${mdFiles.length} markdown files...`);
|
console.log(`${action} ${mdFiles.length} markdown files...`);
|
||||||
|
@ -16,8 +16,12 @@ import { teamMembers } from '../contributors';
|
|||||||
<p text-lg max-w-200 text-center leading-7>
|
<p text-lg max-w-200 text-center leading-7>
|
||||||
<Contributors />
|
<Contributors />
|
||||||
<br />
|
<br />
|
||||||
<a href="https://chat.vitest.dev" rel="noopener noreferrer">Join the community</a> and
|
<a
|
||||||
get involved!
|
href="https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>Join the community</a
|
||||||
|
>
|
||||||
|
and get involved!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
@ -138,6 +138,7 @@ function sidebarSyntax() {
|
|||||||
{ text: 'Mindmaps 🔥', link: '/syntax/mindmap' },
|
{ text: 'Mindmaps 🔥', link: '/syntax/mindmap' },
|
||||||
{ text: 'Timeline 🔥', link: '/syntax/timeline' },
|
{ text: 'Timeline 🔥', link: '/syntax/timeline' },
|
||||||
{ text: 'Zenuml 🔥', link: '/syntax/zenuml' },
|
{ text: 'Zenuml 🔥', link: '/syntax/zenuml' },
|
||||||
|
{ text: 'Sankey 🔥', link: '/syntax/sankey' },
|
||||||
{ text: 'Other Examples', link: '/syntax/examples' },
|
{ text: 'Other Examples', link: '/syntax/examples' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -154,6 +155,7 @@ function sidebarConfig() {
|
|||||||
{ text: 'Tutorials', link: '/config/Tutorials' },
|
{ text: 'Tutorials', link: '/config/Tutorials' },
|
||||||
{ text: 'API-Usage', link: '/config/usage' },
|
{ text: 'API-Usage', link: '/config/usage' },
|
||||||
{ text: 'Mermaid API Configuration', link: '/config/setup/README' },
|
{ text: 'Mermaid API Configuration', link: '/config/setup/README' },
|
||||||
|
{ text: 'Mermaid Configuration Options', link: '/config/schema-docs/config' },
|
||||||
{ text: 'Directives', link: '/config/directives' },
|
{ text: 'Directives', link: '/config/directives' },
|
||||||
{ text: 'Theming', link: '/config/theming' },
|
{ text: 'Theming', link: '/config/theming' },
|
||||||
{ text: 'Accessibility', link: '/config/accessibility' },
|
{ text: 'Accessibility', link: '/config/accessibility' },
|
||||||
|
@ -1,30 +1,5 @@
|
|||||||
import contributorUsernamesJson from './contributor-names.json';
|
import contributorUsernamesJson from './contributor-names.json';
|
||||||
|
import { CoreTeam, knut, plainTeamMembers } from './teamMembers.js';
|
||||||
export interface Contributor {
|
|
||||||
name: string;
|
|
||||||
avatar: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SocialEntry {
|
|
||||||
icon: string | { svg: string };
|
|
||||||
link: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CoreTeam {
|
|
||||||
name: string;
|
|
||||||
// required to download avatars from GitHub
|
|
||||||
github: string;
|
|
||||||
avatar?: string;
|
|
||||||
twitter?: string;
|
|
||||||
mastodon?: string;
|
|
||||||
sponsor?: string;
|
|
||||||
website?: string;
|
|
||||||
linkedIn?: string;
|
|
||||||
title?: string;
|
|
||||||
org?: string;
|
|
||||||
desc?: string;
|
|
||||||
links?: SocialEntry[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const contributorUsernames: string[] = contributorUsernamesJson;
|
const contributorUsernames: string[] = contributorUsernamesJson;
|
||||||
|
|
||||||
@ -38,6 +13,7 @@ const websiteSVG = {
|
|||||||
|
|
||||||
const createLinks = (tm: CoreTeam): CoreTeam => {
|
const createLinks = (tm: CoreTeam): CoreTeam => {
|
||||||
tm.avatar = `/user-avatars/${tm.github}.png`;
|
tm.avatar = `/user-avatars/${tm.github}.png`;
|
||||||
|
tm.title = tm.title ?? 'Developer';
|
||||||
tm.links = [{ icon: 'github', link: `https://github.com/${tm.github}` }];
|
tm.links = [{ icon: 'github', link: `https://github.com/${tm.github}` }];
|
||||||
if (tm.mastodon) {
|
if (tm.mastodon) {
|
||||||
tm.links.push({ icon: 'mastodon', link: tm.mastodon });
|
tm.links.push({ icon: 'mastodon', link: tm.mastodon });
|
||||||
@ -54,91 +30,6 @@ const createLinks = (tm: CoreTeam): CoreTeam => {
|
|||||||
return tm;
|
return tm;
|
||||||
};
|
};
|
||||||
|
|
||||||
const knut: CoreTeam = {
|
|
||||||
github: 'knsv',
|
|
||||||
name: 'Knut Sveidqvist',
|
|
||||||
title: 'Creator',
|
|
||||||
twitter: 'knutsveidqvist',
|
|
||||||
sponsor: 'https://github.com/sponsors/knsv',
|
|
||||||
};
|
|
||||||
|
|
||||||
const plainTeamMembers: CoreTeam[] = [
|
|
||||||
{
|
|
||||||
github: 'NeilCuzon',
|
|
||||||
name: 'Neil Cuzon',
|
|
||||||
title: 'Developer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
github: 'tylerlong',
|
|
||||||
name: 'Tyler Liu',
|
|
||||||
title: 'Developer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
github: 'sidharthv96',
|
|
||||||
name: 'Sidharth Vinod',
|
|
||||||
title: 'Developer',
|
|
||||||
twitter: 'sidv42',
|
|
||||||
mastodon: 'https://techhub.social/@sidv',
|
|
||||||
sponsor: 'https://github.com/sponsors/sidharthv96',
|
|
||||||
linkedIn: 'sidharth-vinod',
|
|
||||||
website: 'https://sidharth.dev',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
github: 'ashishjain0512',
|
|
||||||
name: 'Ashish Jain',
|
|
||||||
title: 'Developer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
github: 'mmorel-35',
|
|
||||||
name: 'Matthieu Morel',
|
|
||||||
title: 'Developer',
|
|
||||||
linkedIn: 'matthieumorel35',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
github: 'aloisklink',
|
|
||||||
name: 'Alois Klink',
|
|
||||||
title: 'Developer',
|
|
||||||
linkedIn: 'aloisklink',
|
|
||||||
website: 'https://aloisklink.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
github: 'pbrolin47',
|
|
||||||
name: 'Per Brolin',
|
|
||||||
title: 'Developer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
github: 'Yash-Singh1',
|
|
||||||
name: 'Yash Singh',
|
|
||||||
title: 'Developer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
github: 'GDFaber',
|
|
||||||
name: 'Marc Faber',
|
|
||||||
title: 'Developer',
|
|
||||||
linkedIn: 'marc-faber',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
github: 'MindaugasLaganeckas',
|
|
||||||
name: 'Mindaugas Laganeckas',
|
|
||||||
title: 'Developer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
github: 'jgreywolf',
|
|
||||||
name: 'Justin Greywolf',
|
|
||||||
title: 'Developer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
github: 'IOrlandoni',
|
|
||||||
name: 'Nacho Orlandoni',
|
|
||||||
title: 'Developer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
github: 'huynhicode',
|
|
||||||
name: 'Steph Huynh',
|
|
||||||
title: 'Developer',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const teamMembers = plainTeamMembers.map((tm) => createLinks(tm));
|
const teamMembers = plainTeamMembers.map((tm) => createLinks(tm));
|
||||||
teamMembers.sort(
|
teamMembers.sort(
|
||||||
(a, b) => contributorUsernames.indexOf(a.github) - contributorUsernames.indexOf(b.github)
|
(a, b) => contributorUsernames.indexOf(a.github) - contributorUsernames.indexOf(b.github)
|
||||||
|
@ -54,6 +54,15 @@ const MermaidExample = async (md: MarkdownRenderer) => {
|
|||||||
return `<div class="tip custom-block"><p class="custom-block-title">NOTE</p><p>${token.content}}</p></div>`;
|
return `<div class="tip custom-block"><p class="custom-block-title">NOTE</p><p>${token.content}}</p></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token.info.trim() === 'regexp') {
|
||||||
|
// shiki doesn't yet support regexp code blocks, but the javascript
|
||||||
|
// one still makes RegExes look good
|
||||||
|
token.info = 'javascript';
|
||||||
|
// use trimEnd to move trailing `\n` outside if the JavaScript regex `/` block
|
||||||
|
token.content = `/${token.content.trimEnd()}/\n`;
|
||||||
|
return defaultRenderer(tokens, index, options, env, slf);
|
||||||
|
}
|
||||||
|
|
||||||
if (token.info.trim() === 'jison') {
|
if (token.info.trim() === 'jison') {
|
||||||
return `<div class="language-">
|
return `<div class="language-">
|
||||||
<button class="copy"></button>
|
<button class="copy"></button>
|
||||||
|
@ -18,7 +18,11 @@ async function download(url: string, fileName: URL) {
|
|||||||
const image = await fetch(url);
|
const image = await fetch(url);
|
||||||
await writeFile(fileName, Buffer.from(await image.arrayBuffer()));
|
await writeFile(fileName, Buffer.from(await image.arrayBuffer()));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error('failed to load', url, error);
|
||||||
|
// Exit the build process if we are in CI
|
||||||
|
if (process.env.CI) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,10 +30,13 @@ async function fetchAvatars() {
|
|||||||
await mkdir(fileURLToPath(new URL(getAvatarPath('none'))).replace('none.png', ''), {
|
await mkdir(fileURLToPath(new URL(getAvatarPath('none'))).replace('none.png', ''), {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
contributors = JSON.parse(await readFile(pathContributors, { encoding: 'utf-8' }));
|
contributors = JSON.parse(await readFile(pathContributors, { encoding: 'utf-8' }));
|
||||||
for (const name of contributors) {
|
let avatars = contributors.map((name) => {
|
||||||
await download(`https://github.com/${name}.png?size=100`, getAvatarPath(name));
|
download(`https://github.com/${name}.png?size=100`, getAvatarPath(name));
|
||||||
}
|
});
|
||||||
|
|
||||||
|
await Promise.allSettled(avatars);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchAvatars();
|
fetchAvatars();
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Adapted from https://github.dev/vitest-dev/vitest/blob/991ff33ab717caee85ef6cbe1c16dc514186b4cc/scripts/update-contributors.ts#L6
|
// Adapted from https://github.dev/vitest-dev/vitest/blob/991ff33ab717caee85ef6cbe1c16dc514186b4cc/scripts/update-contributors.ts#L6
|
||||||
|
|
||||||
import { writeFile } from 'node:fs/promises';
|
import { writeFile } from 'node:fs/promises';
|
||||||
|
import { knut, plainTeamMembers } from '../teamMembers.js';
|
||||||
|
import { existsSync } from 'node:fs';
|
||||||
|
|
||||||
const pathContributors = new URL('../contributor-names.json', import.meta.url);
|
const pathContributors = new URL('../contributor-names.json', import.meta.url);
|
||||||
|
|
||||||
@ -10,28 +12,40 @@ interface Contributor {
|
|||||||
|
|
||||||
async function fetchContributors() {
|
async function fetchContributors() {
|
||||||
const collaborators: string[] = [];
|
const collaborators: string[] = [];
|
||||||
let page = 1;
|
try {
|
||||||
let data: Contributor[] = [];
|
let page = 1;
|
||||||
do {
|
let data: Contributor[] = [];
|
||||||
const response = await fetch(
|
do {
|
||||||
`https://api.github.com/repos/mermaid-js/mermaid/contributors?per_page=100&page=${page}`,
|
const response = await fetch(
|
||||||
{
|
`https://api.github.com/repos/mermaid-js/mermaid/contributors?per_page=100&page=${page}`,
|
||||||
method: 'GET',
|
{
|
||||||
headers: {
|
method: 'GET',
|
||||||
'content-type': 'application/json',
|
headers: {
|
||||||
},
|
'content-type': 'application/json',
|
||||||
}
|
},
|
||||||
);
|
}
|
||||||
data = await response.json();
|
);
|
||||||
collaborators.push(...data.map((i) => i.login));
|
data = await response.json();
|
||||||
console.log(`Fetched page ${page}`);
|
collaborators.push(...data.map((i) => i.login));
|
||||||
page++;
|
console.log(`Fetched page ${page}`);
|
||||||
} while (data.length === 100);
|
page++;
|
||||||
|
} while (data.length === 100);
|
||||||
|
} catch (e) {
|
||||||
|
/* contributors fetching failure must not hinder docs development */
|
||||||
|
}
|
||||||
return collaborators.filter((name) => !name.includes('[bot]'));
|
return collaborators.filter((name) => !name.includes('[bot]'));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generate() {
|
async function generate() {
|
||||||
const collaborators = await fetchContributors();
|
if (existsSync(pathContributors)) {
|
||||||
|
// Only fetch contributors once, when running locally.
|
||||||
|
// In CI, the file won't exist, so it'll fetch every time as expected.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Will fetch all contributors only in CI to speed up local development.
|
||||||
|
const collaborators = process.env.CI
|
||||||
|
? await fetchContributors()
|
||||||
|
: [knut, ...plainTeamMembers].map((m) => m.github);
|
||||||
await writeFile(pathContributors, JSON.stringify(collaborators, null, 2) + '\n', 'utf8');
|
await writeFile(pathContributors, JSON.stringify(collaborators, null, 2) + '\n', 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
105
packages/mermaid/src/docs/.vitepress/teamMembers.ts
Normal file
105
packages/mermaid/src/docs/.vitepress/teamMembers.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
export interface Contributor {
|
||||||
|
name: string;
|
||||||
|
avatar: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SocialEntry {
|
||||||
|
icon: string | { svg: string };
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CoreTeam {
|
||||||
|
name: string;
|
||||||
|
// required to download avatars from GitHub
|
||||||
|
github: string;
|
||||||
|
avatar?: string;
|
||||||
|
twitter?: string;
|
||||||
|
mastodon?: string;
|
||||||
|
sponsor?: string;
|
||||||
|
website?: string;
|
||||||
|
linkedIn?: string;
|
||||||
|
title?: string;
|
||||||
|
org?: string;
|
||||||
|
desc?: string;
|
||||||
|
links?: SocialEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const knut: CoreTeam = {
|
||||||
|
github: 'knsv',
|
||||||
|
name: 'Knut Sveidqvist',
|
||||||
|
title: 'Creator',
|
||||||
|
twitter: 'knutsveidqvist',
|
||||||
|
sponsor: 'https://github.com/sponsors/knsv',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const plainTeamMembers: CoreTeam[] = [
|
||||||
|
{
|
||||||
|
github: 'NeilCuzon',
|
||||||
|
name: 'Neil Cuzon',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'tylerlong',
|
||||||
|
name: 'Tyler Liu',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'sidharthv96',
|
||||||
|
name: 'Sidharth Vinod',
|
||||||
|
twitter: 'sidv42',
|
||||||
|
mastodon: 'https://techhub.social/@sidv',
|
||||||
|
sponsor: 'https://github.com/sponsors/sidharthv96',
|
||||||
|
linkedIn: 'sidharth-vinod',
|
||||||
|
website: 'https://sidharth.dev',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'ashishjain0512',
|
||||||
|
name: 'Ashish Jain',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'mmorel-35',
|
||||||
|
name: 'Matthieu Morel',
|
||||||
|
linkedIn: 'matthieumorel35',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'aloisklink',
|
||||||
|
name: 'Alois Klink',
|
||||||
|
linkedIn: 'aloisklink',
|
||||||
|
website: 'https://aloisklink.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'pbrolin47',
|
||||||
|
name: 'Per Brolin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'Yash-Singh1',
|
||||||
|
name: 'Yash Singh',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'GDFaber',
|
||||||
|
name: 'Marc Faber',
|
||||||
|
linkedIn: 'marc-faber',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'MindaugasLaganeckas',
|
||||||
|
name: 'Mindaugas Laganeckas',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'jgreywolf',
|
||||||
|
name: 'Justin Greywolf',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'IOrlandoni',
|
||||||
|
name: 'Nacho Orlandoni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'huynhicode',
|
||||||
|
name: 'Steph Huynh',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'Yokozuna59',
|
||||||
|
name: 'Reda Al Sulais',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
github: 'nirname',
|
||||||
|
name: 'Nikolay Rozhkov',
|
||||||
|
},
|
||||||
|
];
|
@ -20,6 +20,10 @@ The definitions that can be generated the Live-Editor are also backwards-compati
|
|||||||
|
|
||||||
[Eddie Jaoude: Can you code your diagrams?](https://www.youtube.com/watch?v=9HZzKkAqrX8)
|
[Eddie Jaoude: Can you code your diagrams?](https://www.youtube.com/watch?v=9HZzKkAqrX8)
|
||||||
|
|
||||||
|
## Mermaid with OpenAI
|
||||||
|
|
||||||
|
[Elle Neal: Mind Mapping with AI: An Accessible Approach for Neurodiverse Learners Tutorial:](https://medium.com/@elle.neal_71064/mind-mapping-with-ai-an-accessible-approach-for-neurodiverse-learners-1a74767359ff), [Demo:](https://databutton.com/v/jk9vrghc)
|
||||||
|
|
||||||
## Mermaid with HTML
|
## Mermaid with HTML
|
||||||
|
|
||||||
Examples are provided in [Getting Started](../intro/n00b-gettingStarted.md)
|
Examples are provided in [Getting Started](../intro/n00b-gettingStarted.md)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user