Merge branch 'develop' into update-er-diagram

This commit is contained in:
Yari DeWalt
2024-10-16 08:09:51 -07:00
committed by GitHub
153 changed files with 11462 additions and 3654 deletions

View File

@@ -1,3 +1,9 @@
export interface PackageOptions {
name: string;
packageName: string;
file: string;
}
/**
* Shared common options for both ESBuild and Vite
*/
@@ -27,4 +33,4 @@ export const packageOptions = {
packageName: 'mermaid-layout-elk',
file: 'layouts.ts',
},
} as const;
} as const satisfies Record<string, PackageOptions>;

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Jagged edge fix for icon shape

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
Fix for issue with calculation of label width when using in firefox

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
Add missing TypeScript dependencies

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Icon color fix for colored icons.

View File

@@ -26,6 +26,7 @@ concat
controlx
controly
CSSCLASS
curv
CYLINDEREND
CYLINDERSTART
DAGA

View File

@@ -28,6 +28,9 @@ dictionaryDefinitions:
- name: suggestions
words:
- none
- disp
- subproc
- tria
suggestWords:
- seperator:separator
- vertice:vertex

View File

@@ -5,6 +5,7 @@ bmatrix
braintree
catmull
compositTitleSize
curv
doublecircle
elems
gantt
@@ -24,6 +25,7 @@ multigraph
nodesep
NOTEGROUP
Pinterest
procs
rankdir
ranksep
rect

View File

@@ -8,7 +8,10 @@ import { defaultOptions, getBuildConfig } from './util.js';
const shouldVisualize = process.argv.includes('--visualize');
const buildPackage = async (entryName: keyof typeof packageOptions) => {
const commonOptions: MermaidBuildOptions = { ...defaultOptions, entryName } as const;
const commonOptions: MermaidBuildOptions = {
...defaultOptions,
options: packageOptions[entryName],
} as const;
const buildConfigs: MermaidBuildOptions[] = [
// package.mjs
{ ...commonOptions },
@@ -40,7 +43,7 @@ const buildPackage = async (entryName: keyof typeof packageOptions) => {
continue;
}
const fileName = Object.keys(metafile.outputs)
.find((file) => !file.includes('chunks') && file.endsWith('js'))
.find((file) => !file.includes('chunks') && file.endsWith('js'))!
.replace('dist/', '');
// Upload metafile into https://esbuild.github.io/analyze/
await writeFile(`stats/${fileName}.meta.json`, JSON.stringify(metafile));

View File

@@ -9,13 +9,18 @@ import { generateLangium } from '../.build/generateLangium.js';
import { defaultOptions, getBuildConfig } from './util.js';
const configs = Object.values(packageOptions).map(({ packageName }) =>
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: packageName })
getBuildConfig({
...defaultOptions,
minify: false,
core: false,
options: packageOptions[packageName],
})
);
const mermaidIIFEConfig = getBuildConfig({
...defaultOptions,
minify: false,
core: false,
entryName: 'mermaid',
options: packageOptions.mermaid,
format: 'iife',
});
configs.push(mermaidIIFEConfig);

View File

@@ -3,7 +3,7 @@ import { fileURLToPath } from 'url';
import type { BuildOptions } from 'esbuild';
import { readFileSync } from 'fs';
import jsonSchemaPlugin from './jsonSchemaPlugin.js';
import { packageOptions } from '../.build/common.js';
import type { PackageOptions } from '../.build/common.js';
import { jisonPlugin } from './jisonPlugin.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
@@ -13,10 +13,10 @@ export interface MermaidBuildOptions extends BuildOptions {
core: boolean;
metafile: boolean;
format: 'esm' | 'iife';
entryName: keyof typeof packageOptions;
options: PackageOptions;
}
export const defaultOptions: Omit<MermaidBuildOptions, 'entryName'> = {
export const defaultOptions: Omit<MermaidBuildOptions, 'entryName' | 'options'> = {
minify: false,
metafile: false,
core: false,
@@ -52,9 +52,14 @@ const getFileName = (fileName: string, { core, format, minify }: MermaidBuildOpt
};
export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
const { core, entryName, metafile, format, minify } = options;
const {
core,
metafile,
format,
minify,
options: { name, file, packageName },
} = options;
const external: string[] = ['require', 'fs', 'path'];
const { name, file, packageName } = packageOptions[entryName];
const outFileName = getFileName(name, options);
const output: BuildOptions = buildOptions({
absWorkingDir: resolve(__dirname, `../packages/${packageName}`),

5
.github/lychee.toml vendored
View File

@@ -44,7 +44,10 @@ exclude = [
"https://chromewebstore.google.com",
# Drupal 403
"https://(www.)?drupal.org"
"https://(www.)?drupal.org",
# Swimm returns 404, eventhough the link is valid
"https://docs.swimm.io"
]
# Exclude all private IPs from checking.

View File

@@ -19,7 +19,7 @@ jobs:
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -23,7 +23,7 @@ jobs:
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

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

View File

@@ -38,7 +38,7 @@ jobs:
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version-file: '.node-version'

53
.github/workflows/e2e-timings.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: E2E - Generate Timings
on:
# run this workflow every night at 3am
schedule:
- cron: '28 3 * * *'
# or when the user triggers it from GitHub Actions page
workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
jobs:
timings:
runs-on: ubuntu-latest
container:
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
options: --user 1001
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version-file: '.node-version'
- name: Install dependencies
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
with:
runTests: false
- name: Cypress run
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
id: cypress
with:
install: false
start: pnpm run dev:coverage
wait-on: 'http://localhost:9000'
browser: chrome
publish-summary: false
env:
VITEST_COVERAGE: true
CYPRESS_COMMIT: ${{ github.sha }}
SPLIT: 1
SPLIT_INDEX: 0
SPLIT_FILE: 'cypress/timings.json'
- name: Commit changes
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
with:
add: 'cypress/timings.json'
author_name: 'github-actions[bot]'
author_email: '41898282+github-actions[bot]@users.noreply.github.com'
message: 'chore: update E2E timings'

View File

@@ -28,7 +28,6 @@ env:
) ||
github.event.before
}}
shouldRunParallel: ${{ secrets.CYPRESS_RECORD_KEY != '' && !(github.event_name == 'push' && github.ref == 'refs/heads/develop') }}
jobs:
cache:
runs-on: ubuntu-latest
@@ -39,7 +38,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version-file: '.node-version'
- name: Cache snapshots
@@ -59,7 +58,7 @@ jobs:
- name: Install dependencies
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
with:
# just perform install
runTests: false
@@ -80,7 +79,7 @@ jobs:
strategy:
fail-fast: false
matrix:
containers: [1, 2, 3, 4]
containers: [1, 2, 3, 4, 5]
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
@@ -88,7 +87,7 @@ jobs:
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version-file: '.node-version'
@@ -101,7 +100,7 @@ jobs:
key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
- name: Install dependencies
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
with:
runTests: false
@@ -117,11 +116,8 @@ jobs:
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
id: cypress
# If CYPRESS_RECORD_KEY is set, run in parallel on all containers
# Otherwise (e.g. if running from fork), we run on a single container only
if: ${{ env.shouldRunParallel == 'true' || ( matrix.containers == 1 ) }}
with:
install: false
start: pnpm run dev:coverage
@@ -129,16 +125,18 @@ jobs:
browser: chrome
# Disable recording if we don't have an API key
# e.g. if this action was run from a fork
record: ${{ env.shouldRunParallel == 'true' }}
parallel: ${{ env.shouldRunParallel == 'true' }}
record: ${{ secrets.CYPRESS_RECORD_KEY != '' }}
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
VITEST_COVERAGE: true
CYPRESS_COMMIT: ${{ github.sha }}
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
ARGOS_PARALLEL: ${{ env.shouldRunParallel == 'true' }}
ARGOS_PARALLEL_TOTAL: 4
ARGOS_PARALLEL: true
ARGOS_PARALLEL_TOTAL: ${{ strategy.job-total }}
ARGOS_PARALLEL_INDEX: ${{ matrix.containers }}
SPLIT: ${{ strategy.job-total }}
SPLIT_INDEX: ${{ strategy.job-index }}
SPLIT_FILE: 'cypress/timings.json'
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0

View File

@@ -29,7 +29,7 @@ jobs:
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -28,7 +28,7 @@ jobs:
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -16,7 +16,7 @@ jobs:
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -31,7 +31,7 @@ jobs:
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -26,7 +26,7 @@ jobs:
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'
@@ -36,7 +36,7 @@ jobs:
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@aba318e9165b45b7948c60273e0b72fce0a64eb9 # v1.4.7
uses: changesets/action@3de3850952bec538fde60aac71731376e57b9b57 # v1.4.8
with:
version: pnpm changeset:version
publish: pnpm changeset:publish

View File

@@ -16,11 +16,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
persist-credentials: false
- name: Run analysis
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
with:
results_file: results.sarif
results_format: sarif
@@ -32,6 +32,6 @@ jobs:
path: results.sarif
retention-days: 5
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@f0f3afee809481da311ca3a6ff1ff51d81dbeb24 # v3.26.4
uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
with:
sarif_file: results.sarif

View File

@@ -15,7 +15,7 @@ jobs:
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -1,8 +1,9 @@
import { defineConfig } from 'cypress';
import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin';
import coverage from '@cypress/code-coverage/task';
import eyesPlugin from '@applitools/eyes-cypress';
import { registerArgosTask } from '@argos-ci/cypress/task';
import coverage from '@cypress/code-coverage/task';
import { defineConfig } from 'cypress';
import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin';
import cypressSplit from 'cypress-split';
export default eyesPlugin(
defineConfig({
@@ -13,6 +14,7 @@ export default eyesPlugin(
specPattern: 'cypress/integration/**/*.{js,ts}',
setupNodeEvents(on, config) {
coverage(on, config);
cypressSplit(on, config);
on('before:browser:launch', (browser, launchOptions) => {
if (browser.name === 'chrome' && browser.isHeadless) {
launchOptions.args.push('--window-size=1440,1024', '--force-device-scale-factor=1');

View File

@@ -29,6 +29,7 @@ export const mermaidUrl = (
options: CypressMermaidConfig,
api: boolean
): string => {
options.handDrawnSeed = 1;
const codeObject: CodeObject = {
code: graphStr,
mermaid: options,

View File

@@ -173,7 +173,8 @@ describe.skip('architecture diagram', () => {
});
});
describe('architecture - external', () => {
// Skipped as the layout is not deterministic, and causes issues in E2E tests.
describe.skip('architecture - external', () => {
it('should allow adding external icons', () => {
urlSnapshotTest('http://localhost:9000/architecture-external.html');
});

View File

@@ -581,4 +581,63 @@ class C13["With Città foreign language"]
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
});
it('renders a class diagram with a generic class in a namespace', () => {
const diagramDefinition = `
classDiagram-v2
namespace Company.Project.Module {
class GenericClass~T~ {
+addItem(item: T)
+getItem() T
}
}
`;
imgSnapshotTest(diagramDefinition);
});
it('renders a class diagram with nested namespaces and relationships', () => {
const diagramDefinition = `
classDiagram-v2
namespace Company.Project.Module.SubModule {
class Report {
+generatePDF(data: List)
+generateCSV(data: List)
}
}
namespace Company.Project.Module {
class Admin {
+generateReport()
}
}
Admin --> Report : generates
`;
imgSnapshotTest(diagramDefinition);
});
it('renders a class diagram with multiple classes and relationships in a namespace', () => {
const diagramDefinition = `
classDiagram-v2
namespace Company.Project.Module {
class User {
+login(username: String, password: String)
+logout()
}
class Admin {
+addUser(user: User)
+removeUser(user: User)
+generateReport()
}
class Report {
+generatePDF(reportData: List)
+generateCSV(reportData: List)
}
}
Admin --> User : manages
Admin --> Report : generates
`;
imgSnapshotTest(diagramDefinition);
});
});

View File

@@ -12,7 +12,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -30,7 +29,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: true },
fontFamily: 'courier',
}
@@ -47,7 +45,7 @@ describe('Flowchart HandDrawn', () => {
C -->|Two| E[iPhone]
C -->|Three| F[Car]
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -62,7 +60,7 @@ describe('Flowchart HandDrawn', () => {
C -->|Two| E[\\iPhone\\]
C -->|Three| F[Car]
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -78,7 +76,7 @@ describe('Flowchart HandDrawn', () => {
classDef processHead fill:#888888,color:white,font-weight:bold,stroke-width:3px,stroke:#001f3f
class 1A,1B,D,E processHead
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -107,7 +105,7 @@ describe('Flowchart HandDrawn', () => {
35(SAM.CommonFA.PopulationFME)-->39(SAM.CommonFA.ChargeDetails)
36(SAM.CommonFA.PremetricCost)-->39(SAM.CommonFA.ChargeDetails)
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -178,7 +176,7 @@ describe('Flowchart HandDrawn', () => {
9a072290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
9a072290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -187,7 +185,7 @@ describe('Flowchart HandDrawn', () => {
`
graph TB;subgraph "number as labels";1;end;
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -199,7 +197,7 @@ describe('Flowchart HandDrawn', () => {
a1-->a2
end
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -211,7 +209,7 @@ describe('Flowchart HandDrawn', () => {
a1-->a2
end
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -246,7 +244,7 @@ describe('Flowchart HandDrawn', () => {
style foo fill:#F99,stroke-width:2px,stroke:#F0F,color:darkred
style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -348,7 +346,7 @@ describe('Flowchart HandDrawn', () => {
sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4FC27B48-A6F9-460A-A675-021F5854FE22;
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -364,7 +362,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
listUrl: false,
listId: 'color styling',
fontFamily: 'courier',
@@ -390,7 +387,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
listUrl: false,
listId: 'color styling',
fontFamily: 'courier',
@@ -411,7 +407,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -435,7 +430,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -457,7 +451,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -471,7 +464,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -485,7 +477,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -500,7 +491,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -527,7 +517,6 @@ describe('Flowchart HandDrawn', () => {
class A someclass;`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -544,7 +533,7 @@ describe('Flowchart HandDrawn', () => {
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { nodeSpacing: 50 }, fontFamily: 'courier' }
{ look: 'handDrawn', flowchart: { nodeSpacing: 50 }, fontFamily: 'courier' }
);
});
@@ -560,7 +549,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { rankSpacing: '100' },
fontFamily: 'courier',
}
@@ -578,7 +566,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -603,7 +590,7 @@ describe('Flowchart HandDrawn', () => {
click E "notes://do-your-thing/id" "other protocol test"
click F "javascript:alert('test')" "script test"
`,
{ look: 'handDrawn', handDrawnSeed: 1, securityLevel: 'loose', fontFamily: 'courier' }
{ look: 'handDrawn', securityLevel: 'loose', fontFamily: 'courier' }
);
});
@@ -623,7 +610,7 @@ describe('Flowchart HandDrawn', () => {
click B "index.html#link-clicked" "link test"
click D testClick "click test"
`,
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { htmlLabels: true } }
{ look: 'handDrawn', flowchart: { htmlLabels: true } }
);
});
@@ -645,7 +632,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -664,7 +650,7 @@ describe('Flowchart HandDrawn', () => {
class A myClass1
class D myClass2
`,
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { htmlLabels: true } }
{ look: 'handDrawn', flowchart: { htmlLabels: true } }
);
});
@@ -682,7 +668,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -711,7 +696,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -728,7 +712,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -752,7 +735,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -769,7 +751,7 @@ describe('Flowchart HandDrawn', () => {
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { diagramPadding: 0 } }
{ look: 'handDrawn', flowchart: { diagramPadding: 0 } }
);
});
@@ -804,7 +786,7 @@ describe('Flowchart HandDrawn', () => {
`graph TD
a["<strong>Haiya</strong>"]-->b
`,
{ look: 'handDrawn', handDrawnSeed: 1, htmlLabels: false, flowchart: { htmlLabels: false } }
{ look: 'handDrawn', htmlLabels: false, flowchart: { htmlLabels: false } }
);
});
it('FDH37: should render non-escaped with html labels', () => {
@@ -814,7 +796,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -830,7 +811,7 @@ describe('Flowchart HandDrawn', () => {
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { useMaxWidth: true } }
{ look: 'handDrawn', flowchart: { useMaxWidth: true } }
);
cy.get('svg').should((svg) => {
expect(svg).to.have.attr('width', '100%');
@@ -853,7 +834,7 @@ describe('Flowchart HandDrawn', () => {
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { useMaxWidth: false } }
{ look: 'handDrawn', flowchart: { useMaxWidth: false } }
);
cy.get('svg').should((svg) => {
// const height = parseFloat(svg.attr('height'));
@@ -874,7 +855,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -904,7 +884,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -919,7 +898,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -937,7 +915,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -977,7 +954,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -999,7 +975,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -1016,7 +991,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -1032,7 +1006,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -1051,7 +1024,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
}

View File

@@ -0,0 +1,142 @@
import { imgSnapshotTest } from '../../helpers/util.ts';
const aliasSet1 = ['process', 'rect', 'proc', 'rectangle'] as const;
const aliasSet2 = ['event', 'rounded'] as const;
const aliasSet3 = ['stadium', 'pill', 'terminal'] as const;
const aliasSet4 = ['fr-rect', 'subproc', 'subprocess', 'framed-rectangle', 'subroutine'] as const;
const aliasSet5 = ['db', 'database', 'cylinder', 'cyl'] as const;
const aliasSet6 = ['diam', 'decision', 'diamond'] as const;
const aliasSet7 = ['hex', 'hexagon', 'prepare'] as const;
const aliasSet8 = ['lean-r', 'lean-right', 'in-out'] as const;
const aliasSet9 = ['lean-l', 'lean-left', 'out-in'] as const;
const aliasSet10 = ['trap-b', 'trapezoid-bottom', 'priority'] as const;
const aliasSet11 = ['trap-t', 'trapezoid-top', 'manual'] as const;
const aliasSet12 = ['dbl-circ', 'double-circle'] as const;
const aliasSet13 = ['notched-rectangle', 'card', 'notch-rect'] as const;
const aliasSet14 = [
'lin-rect',
'lined-rectangle',
'lin-proc',
'lined-process',
'shaded-process',
] as const;
const aliasSet15 = ['sm-circ', 'small-circle', 'start'] as const;
const aliasSet16 = ['fr-circ', 'framed-circle', 'stop'] as const;
const aliasSet17 = ['fork', 'join'] as const;
// brace-r', 'braces'
const aliasSet18 = ['comment', 'brace-l'] as const;
const aliasSet19 = ['bolt', 'com-link', 'lightning-bolt'] as const;
const aliasSet20 = ['doc', 'document'] as const;
const aliasSet21 = ['delay', 'half-rounded-rectangle'] as const;
const aliasSet22 = ['h-cyl', 'das', 'horizontal-cylinder'] as const;
const aliasSet23 = ['lin-cyl', 'disk', 'lined-cylinder'] as const;
const aliasSet24 = ['curv-trap', 'display', 'curved-trapezoid'] as const;
const aliasSet25 = ['div-rect', 'div-proc', 'divided-rectangle', 'divided-process'] as const;
const aliasSet26 = ['extract', 'tri', 'triangle'] as const;
const aliasSet27 = ['win-pane', 'internal-storage', 'window-pane'] as const;
const aliasSet28 = ['f-circ', 'junction', 'filled-circle'] as const;
const aliasSet29 = ['lin-doc', 'lined-document'] as const;
const aliasSet30 = ['notch-pent', 'loop-limit', 'notched-pentagon'] as const;
const aliasSet31 = ['flip-tri', 'manual-file', 'flipped-triangle'] as const;
const aliasSet32 = ['sl-rect', 'manual-input', 'sloped-rectangle'] as const;
const aliasSet33 = ['docs', 'documents', 'st-doc', 'stacked-document'] as const;
const aliasSet34 = ['procs', 'processes', 'st-rect', 'stacked-rectangle'] as const;
const aliasSet35 = ['flag', 'paper-tape'] as const;
const aliasSet36 = ['bow-rect', 'stored-data', 'bow-tie-rectangle'] as const;
const aliasSet37 = ['cross-circ', 'summary', 'crossed-circle'] as const;
const aliasSet38 = ['tag-doc', 'tagged-document'] as const;
const aliasSet39 = ['tag-rect', 'tag-proc', 'tagged-rectangle', 'tagged-process'] as const;
const aliasSet40 = ['collate', 'hourglass'] as const;
// Aggregate all alias sets into a single array
const aliasSets = [
aliasSet1,
aliasSet2,
aliasSet3,
aliasSet4,
aliasSet5,
aliasSet6,
aliasSet7,
aliasSet8,
aliasSet9,
aliasSet10,
aliasSet11,
aliasSet12,
aliasSet13,
aliasSet14,
aliasSet15,
aliasSet16,
aliasSet17,
aliasSet18,
aliasSet19,
aliasSet20,
aliasSet21,
aliasSet22,
aliasSet23,
aliasSet24,
aliasSet25,
aliasSet26,
aliasSet27,
aliasSet28,
aliasSet29,
aliasSet30,
aliasSet31,
aliasSet32,
aliasSet33,
aliasSet34,
aliasSet35,
aliasSet36,
aliasSet37,
aliasSet38,
aliasSet39,
] as const;
aliasSets.forEach((aliasSet) => {
describe(`Test ${aliasSet.join(',')} `, () => {
it(`All ${aliasSet.join(',')} should render same shape`, () => {
let flowchartCode = `flowchart \n`;
aliasSet.forEach((alias, index) => {
flowchartCode += ` n${index}@{ shape: ${alias}, label: "${alias}" }\n`;
});
imgSnapshotTest(flowchartCode);
});
});
});

View File

@@ -0,0 +1,143 @@
import { imgSnapshotTest } from '../../helpers/util';
const looks = ['classic', 'handDrawn'] as const;
const directions = [
'TB',
//'BT',
'LR',
// 'RL'
] as const;
const forms = [undefined, 'square', 'circle', 'rounded'] as const;
const labelPos = [undefined, 't', 'b'] as const;
looks.forEach((look) => {
directions.forEach((direction) => {
forms.forEach((form) => {
labelPos.forEach((pos) => {
describe(`Test iconShape in ${form ? `${form} form,` : ''} ${look} look and dir ${direction} with label position ${pos ? pos : 'not defined'}`, () => {
it(`without label`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell'`;
if (form) {
flowchartCode += `, form: '${form}'`;
}
flowchartCode += ` }\n`;
imgSnapshotTest(flowchartCode, { look });
});
it(`with label`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is a label for icon shape'`;
if (form) {
flowchartCode += `, form: '${form}'`;
}
if (pos) {
flowchartCode += `, pos: '${pos}'`;
}
flowchartCode += ` }\n`;
imgSnapshotTest(flowchartCode, { look });
});
it(`with very long label`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is a very very very very very long long long label for icon shape'`;
if (form) {
flowchartCode += `, form: '${form}'`;
}
if (pos) {
flowchartCode += `, pos: '${pos}'`;
}
flowchartCode += ` }\n`;
imgSnapshotTest(flowchartCode, { look });
});
it(`with markdown htmlLabels:true`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is **bold** </br>and <strong>strong</strong> for icon shape'`;
if (form) {
flowchartCode += `, form: '${form}'`;
}
if (pos) {
flowchartCode += `, pos: '${pos}'`;
}
flowchartCode += ` }\n`;
imgSnapshotTest(flowchartCode, { look });
});
it(`with markdown htmlLabels:false`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is **bold** </br>and <strong>strong</strong> for icon shape'`;
if (form) {
flowchartCode += `, form: '${form}'`;
}
if (pos) {
flowchartCode += `, pos: '${pos}'`;
}
flowchartCode += ` }\n`;
imgSnapshotTest(flowchartCode, {
look,
htmlLabels: false,
flowchart: { htmlLabels: false },
});
});
it(`with styles`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'new icon shape'`;
if (form) {
flowchartCode += `, form: '${form}'`;
}
if (pos) {
flowchartCode += `, pos: '${pos}'`;
}
flowchartCode += ` }\n`;
flowchartCode += ` style nAA fill:#f9f,stroke:#333,stroke-width:4px \n`;
imgSnapshotTest(flowchartCode, { look });
});
it(`with classDef`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5\n`;
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'new icon shape'`;
if (form) {
flowchartCode += `, form: '${form}'`;
}
if (pos) {
flowchartCode += `, pos: '${pos}'`;
}
flowchartCode += ` }\n`;
flowchartCode += ` nAA:::customClazz\n`;
imgSnapshotTest(flowchartCode, { look });
});
});
});
});
});
});
describe('Test iconShape with different h', () => {
it('with different h', () => {
let flowchartCode = `flowchart TB\n`;
const icon = 'fa:bell';
const iconHeight = 64;
flowchartCode += ` nA --> nAA@{ icon: '${icon}', label: 'icon with different h', h: ${iconHeight} }\n`;
imgSnapshotTest(flowchartCode);
});
});
describe('Test colored iconShape', () => {
it('with no styles', () => {
let flowchartCode = `flowchart TB\n`;
const icon = 'fluent-emoji:tropical-fish';
flowchartCode += ` nA --> nAA@{ icon: '${icon}', form: 'square', label: 'icon with color' }\n`;
imgSnapshotTest(flowchartCode);
});
it('with styles', () => {
let flowchartCode = `flowchart TB\n`;
const icon = 'fluent-emoji:tropical-fish';
flowchartCode += ` nA --> nAA@{ icon: '${icon}', form: 'square', label: 'icon with color' }\n`;
flowchartCode += ` style nAA fill:#f9f,stroke:#333,stroke-width:4px \n`;
imgSnapshotTest(flowchartCode);
});
});

View File

@@ -0,0 +1,103 @@
import { imgSnapshotTest } from '../../helpers/util';
const looks = ['classic', 'handDrawn'] as const;
const directions = [
'TB',
//'BT',
'LR',
// 'RL'
] as const;
const labelPos = [undefined, 't', 'b'] as const;
looks.forEach((look) => {
directions.forEach((direction) => {
labelPos.forEach((pos) => {
describe(`Test imageShape in ${look} look and dir ${direction} with label position ${pos ? pos : 'not defined'}`, () => {
it(`without label`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', w: '100', h: '100' }\n`;
imgSnapshotTest(flowchartCode, { look });
});
it(`with label`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is a label for image shape'`;
flowchartCode += `, w: '100', h: '200'`;
if (pos) {
flowchartCode += `, pos: '${pos}'`;
}
flowchartCode += ` }\n`;
imgSnapshotTest(flowchartCode, { look });
});
it(`with very long label`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is a very very very very very long long long label for image shape'`;
flowchartCode += `, w: '100', h: '250'`;
if (pos) {
flowchartCode += `, pos: '${pos}'`;
}
flowchartCode += ` }\n`;
imgSnapshotTest(flowchartCode, { look });
});
it(`with markdown htmlLabels:true`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is **bold** </br>and <strong>strong</strong> for image shape'`;
flowchartCode += `, w: '550', h: '200'`;
if (pos) {
flowchartCode += `, pos: '${pos}'`;
}
flowchartCode += ` }\n`;
imgSnapshotTest(flowchartCode, { look, htmlLabels: true });
});
it(`with markdown htmlLabels:false`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is **bold** </br>and <strong>strong</strong> for image shape'`;
flowchartCode += `, w: '250', h: '200'`;
if (pos) {
flowchartCode += `, pos: '${pos}'`;
}
flowchartCode += ` }\n`;
imgSnapshotTest(flowchartCode, {
look,
htmlLabels: false,
flowchart: { htmlLabels: false },
});
});
it(`with styles`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'new image shape'`;
flowchartCode += `, w: '550', h: '200'`;
if (pos) {
flowchartCode += `, pos: '${pos}'`;
}
flowchartCode += ` }\n`;
flowchartCode += ` style A fill:#f9f,stroke:#333,stroke-width:4px \n`;
imgSnapshotTest(flowchartCode, { look });
});
it(`with classDef`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#000000,stroke-dasharray: 5 5\n`;
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'new image shape'`;
flowchartCode += `, w: '500', h: '550'`;
if (pos) {
flowchartCode += `, pos: '${pos}'`;
}
flowchartCode += ` }\n`;
flowchartCode += ` A:::customClazz\n`;
imgSnapshotTest(flowchartCode, { look });
});
});
});
});
});

View File

@@ -0,0 +1,146 @@
import { imgSnapshotTest } from '../../helpers/util.ts';
const looks = ['classic', 'handDrawn'] as const;
const directions = [
'TB',
//'BT',
'LR',
//'RL'
] as const;
const newShapesSet1 = [
'triangle',
'sloped-rectangle',
'horizontal-cylinder',
'flipped-triangle',
'hourglass',
] as const;
const newShapesSet2 = [
'tagged-rectangle',
'documents',
'lightning-bolt',
'filled-circle',
'window-pane',
] as const;
const newShapesSet3 = [
'curved-trapezoid',
'bow-rect',
'tagged-document',
'divided-rectangle',
'crossed-circle',
] as const;
const newShapesSet4 = [
'document',
'notched-pentagon',
'lined-cylinder',
'stacked-document',
'half-rounded-rectangle',
] as const;
const newShapesSet5 = [
'lined-document',
'tagged-document',
'brace-l',
'comment',
'braces',
'brace-r',
] as const;
const newShapesSet6 = ['brace-r', 'braces'] as const;
// Aggregate all shape sets into a single array
const newShapesSets = [
newShapesSet1,
newShapesSet2,
newShapesSet3,
newShapesSet4,
newShapesSet5,
newShapesSet6,
];
looks.forEach((look) => {
directions.forEach((direction) => {
newShapesSets.forEach((newShapesSet) => {
describe(`Test ${newShapesSet.join(', ')} in ${look} look and dir ${direction}`, () => {
it(`without label`, () => {
let flowchartCode = `flowchart ${direction}\n`;
newShapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape} }\n`;
});
imgSnapshotTest(flowchartCode, { look });
});
it(`with label`, () => {
let flowchartCode = `flowchart ${direction}\n`;
newShapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`;
});
imgSnapshotTest(flowchartCode, { look });
});
it(`connect all shapes with each other`, () => {
let flowchartCode = `flowchart ${direction}\n`;
newShapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`;
});
for (let i = 0; i < newShapesSet.length; i++) {
for (let j = i + 1; j < newShapesSet.length; j++) {
flowchartCode += ` n${i}${i} --> n${j}${j}\n`;
}
}
if (!(direction === 'TB' && look === 'handDrawn' && newShapesSet === newShapesSet1)) {
//skip this test, works in real. Need to look
imgSnapshotTest(flowchartCode, { look });
}
});
it(`with very long label`, () => {
let flowchartCode = `flowchart ${direction}\n`;
newShapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a very very very very very long long long label for ${newShape} shape' }\n`;
});
imgSnapshotTest(flowchartCode, { look });
});
it(`with markdown htmlLabels:true`, () => {
let flowchartCode = `flowchart ${direction}\n`;
newShapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold** </br>and <strong>strong</strong> for ${newShape} shape' }\n`;
});
imgSnapshotTest(flowchartCode, { look });
});
it(`with markdown htmlLabels:false`, () => {
let flowchartCode = `flowchart ${direction}\n`;
newShapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold** </br>and <strong>strong</strong> for ${newShape} shape' }\n`;
});
imgSnapshotTest(flowchartCode, {
look,
htmlLabels: false,
flowchart: { htmlLabels: false },
});
});
it(`with styles`, () => {
let flowchartCode = `flowchart ${direction}\n`;
newShapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`;
flowchartCode += ` style n${index}${index} fill:#f9f,stroke:#333,stroke-width:4px \n`;
});
imgSnapshotTest(flowchartCode, { look });
});
it(`with classDef`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5\n`;
newShapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`;
flowchartCode += ` n${index}${index}:::customClazz\n`;
});
imgSnapshotTest(flowchartCode, { look });
});
});
});
});
});

View File

@@ -0,0 +1,107 @@
import { imgSnapshotTest } from '../../helpers/util';
const looks = ['classic', 'handDrawn'] as const;
const directions = [
'TB',
//'BT',
'LR',
//'RL'
] as const;
const shapesSet1 = ['text', 'card', 'lin-rect', 'diamond', 'hexagon'] as const;
// removing labelRect, need have alias for it
const shapesSet2 = ['rounded', 'rect', 'start', 'stop'] as const;
const shapesSet3 = ['fork', 'choice', 'note', 'stadium', 'odd'] as const;
const shapesSet4 = ['subroutine', 'cylinder', 'circle', 'doublecircle', 'odd'] as const;
const shapesSet5 = ['anchor', 'lean-r', 'lean-l', 'trap-t', 'trap-b'] as const;
// Aggregate all shape sets into a single array
const shapesSets = [shapesSet1, shapesSet2, shapesSet3, shapesSet4, shapesSet5] as const;
looks.forEach((look) => {
directions.forEach((direction) => {
shapesSets.forEach((shapesSet) => {
describe(`Test ${shapesSet.join(', ')} in ${look} look and dir ${direction}`, () => {
it(`without label`, () => {
let flowchartCode = `flowchart ${direction}\n`;
shapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape} }\n`;
});
imgSnapshotTest(flowchartCode, { look });
});
it(`with label`, () => {
let flowchartCode = `flowchart ${direction}\n`;
shapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`;
});
imgSnapshotTest(flowchartCode, { look });
});
it(`connect all shapes with each other`, () => {
let flowchartCode = `flowchart ${direction}\n`;
shapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`;
});
for (let i = 0; i < shapesSet.length; i++) {
for (let j = i + 1; j < shapesSet.length; j++) {
flowchartCode += ` n${i}${i} --> n${j}${j}\n`;
}
}
imgSnapshotTest(flowchartCode, { look });
});
it(`with very long label`, () => {
let flowchartCode = `flowchart ${direction}\n`;
shapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a very very very very very long long long label for ${newShape} shape' }\n`;
});
imgSnapshotTest(flowchartCode, { look });
});
it(`with markdown htmlLabels:true`, () => {
let flowchartCode = `flowchart ${direction}\n`;
shapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold** </br>and <strong>strong</strong> for ${newShape} shape' }\n`;
});
imgSnapshotTest(flowchartCode, { look });
});
it(`with markdown htmlLabels:false`, () => {
let flowchartCode = `flowchart ${direction}\n`;
shapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold** </br>and <strong>strong</strong> for ${newShape} shape' }\n`;
});
imgSnapshotTest(flowchartCode, {
look,
htmlLabels: false,
flowchart: { htmlLabels: false },
});
});
it(`with styles`, () => {
let flowchartCode = `flowchart ${direction}\n`;
shapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`;
flowchartCode += ` style n${index}${index} fill:#f9f,stroke:#333,stroke-width:4px \n`;
});
imgSnapshotTest(flowchartCode, { look });
});
it(`with classDef`, () => {
let flowchartCode = `flowchart ${direction}\n`;
flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5\n`;
shapesSet.forEach((newShape, index) => {
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`;
flowchartCode += ` n${index}${index}:::customClazz\n`;
});
imgSnapshotTest(flowchartCode, { look });
});
});
});
});
});

View File

@@ -83,7 +83,8 @@
</head>
<body>
<pre id="diagram" class="mermaid">
<div class="flex">
<pre id="diagram" class="mermaid2">
---
title: hello2
config:
@@ -242,8 +243,190 @@ stateDiagram-v2
</pre>
</pre>
<pre id="diagram" class="mermaid2">
---
config:
look: neo
---
flowchart RL
subgraph " "
A5@{ shape: manual-file, label: "a label"}
B5@{ shape: manual-input, label: "a label" }
C5@{ shape: mul-doc, label: "a label" }
D5@{ shape: mul-proc, label: "a label" }
E5@{ shape: paper-tape, label: "a label" }
B3@{ shape: das, label: "a label" }
C3@{ shape: disk, label: "a label" }
D4@{ shape: lin-doc, label: "a label" }
E4@{ shape: loop-limit, label: "a label" }
end
subgraph " "
B6@{ shape: summary, label: "a label" }
C6@{ shape: tag-we-rect, label: "a label" }
D6@{ shape: tag-rect, label: "a label" }
A2@{ shape: fork}
B2@{ shape: hourglass }
C2@{ shape: comment, label: "I am a comment" }
D2@{ shape: bolt }
D3@{ shape: disp, label: "a label" }
C4@{ shape: junction, label: "a label" }
A4@{ shape: extract, label: "a label"}
B52[a fr]@{ shape: fr }
end
subgraph " "
A1@{ shape: text, label: This is a textblock}
B1@{ shape: card, label: "a label" }
C1@{ shape: lined-proc, label: "a label" }
D1@{ shape: start, label: "a label" }
E1@{ shape: stop, label: "a label" }
E2@{ shape: doc, label: "a label" }
A6@{ shape: stored-data, label: "a label"}
A3@{ shape: delay, label: "a label" }
E3@{ shape: div-proc, label: "a label" }
B4[a label]@{ shape: win-pane }
end
</pre>
<pre id="diagram" class="mermaid2">
---
title: hello2
config:
look: handDrawn
elk:
<!-- nodePlacementStrategy: SIMPLE -->
---
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
flowchart TD
A([Start]) -->|go to booking page| B("select
ISBS booking no")
A --> QQ{cancel booking}
A --> RR{no show}
A --> SS{change booking}
B -->C(wmpay_request_payment.request_type= 'partial',
wmpay_request_payment.status= 'paid',
pos_booking.booking_status= partial and 'full_deposit')
style C text-align:left
C -->D{manage booking}
D -->|cancel|E[ระบบแสดงช่องให้กรอกเหตุผล]
E -->F{กดปุ่ม 'cancel' หรือไม่}
F -->|Yes|G[ระบบบันทึกค่าใหม่
และไม่สามารถแก้ไขข้อมูลได้]
F -->|No|H[กดปุ่ม 'close']
H -->|ระบบไม่เปลี่ยนแปลงข้อมูล|Z
G -->|ระบบส่งข้อมูล|I[(POS_database)]
I -->|pos_booking.booking_status='cancel'|Z([End])
D -->|no show|J[ระบบแสดงช่องให้กรอกเหตุผล]
J -->K{กดปุ่ม 'noshow' หรือไม่}
K -->|Yes|L[ระบบสร้างใบเสร็จอัตโนมัติ
Product_id: 439,
ItemName: no show]
style L text-align:left
K -->|No|O[กดปุ่ม 'close']
O -->|ระบบไม่เปลี่ยนแปลงข้อมูล|Z
L -->M[ระบบบันทึกค่าใหม่]
M -->|ระบบส่งข้อมูล|N[(POS_database)]
N -->|pos_booking.booking_status=noshow|Z
</pre>
<pre id="diagram" class="mermaid2">
---
title: hello2
config:
look: handDrawn
layout: dagre
elk:
nodePlacementStrategy: BRANDES_KOEPF
---
flowchart
A --> A
subgraph A
B --> B
subgraph B
C
end
end
</pre
>
<pre id="diagram" class="mermaid2">
---
config:
look: handdrawn
flowchart:
htmlLabels: true
---
flowchart
A[I am a long text, where do I go??? handdrawn - true]
</pre
>
</div>
<div class="flex">
<pre id="diagram" class="mermaid2">
---
config:
flowchart:
htmlLabels: false
---
flowchart
A[I am a long text, where do I go??? classic - false]
</pre
>
<pre id="diagram" class="mermaid2">
---
config:
flowchart:
htmlLabels: true
---
flowchart
A[I am a long text, where do I go??? classic - true]
</pre
>
</div>
<pre id="diagram2" class="mermaid2">
flowchart LR
id1(Start)-->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
</pre>
<pre id="diagram3" class="mermaid2">
flowchart LR
A:::foo & B:::bar --> C:::foobar
classDef foo stroke:#f00
classDef bar stroke:#0f0
classDef ash color:red
class C ash
style C stroke:#00f, fill:black
</pre>
<pre id="diagram4" class="mermaid2">
stateDiagram
A:::foo
B:::bar --> C:::foobar
classDef foo stroke:#f00
classDef bar stroke:#0f0
style C stroke:#00f, fill:black, color:white
</pre>
<pre id="diagram4" class="mermaid">
flowchart TB
A@{
label: "aksljhf kasjdh"
}
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import layouts from './mermaid-layout-elk.esm.mjs';

View File

@@ -0,0 +1,174 @@
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
rel="stylesheet"
/>
<style>
body {
/* background: rgb(221, 208, 208); */
/* background: #333; */
font-family: 'Arial';
/* font-size: 18px !important; */
}
h1 {
color: grey;
}
.mermaid2 {
display: none;
}
.mermaid svg {
/* font-size: 18px !important; */
/* background-color: #efefef;
background-image: radial-gradient(#fff 51%, transparent 91%),
radial-gradient(#fff 51%, transparent 91%);
background-size: 20px 20px;
background-position:
0 0,
10px 10px;
background-repeat: repeat; */
}
</style>
</head>
<body style="display: flex; gap: 2rem; flex-direction: row">
<pre id="diagram4" class="mermaid">
flowchart LR
A@{ icon: "fa:window-minimize", form: circle }
E@{ icon: "fa:window-minimize", form: circle }
B@{ icon: "fa:bell", form: circle }
B2@{ icon: "fa:bell", form: circle }
C@{ icon: "fa:address-book", form: square }
D@{ icon: "fa:star-half", form: square }
A --> E
B --> B2
</pre>
<pre id="diagram4" class="mermaid2">
flowchart TB
A --test2--> B2@{ icon: "fa:bell", form: "rounded", label: "B2 aiduaid uyawduad uaduabd uyduadb", pos: "b" }
B2 --test--> C
D --> B2 --> E
style B2 fill:#f9f,stroke:#333,stroke-width:4px
</pre
>
<pre id="diagram43" class="mermaid2">
flowchart BT
A --test2--> B2@{ icon: "fa:bell", form: "square", label: "B2", pos: "t", h: 40, w: 30 }
B2 --test--> C
D --> B2 --> E
</pre
>
<pre id="diagram4" class="mermaid2">
flowchart BT
A --test2--> B2@{ icon: "fa:bell", label: "B2 awiugdawu uydgayuiwd wuydguy", pos: "b", h: 40, w: 30 }
B2 --test--> C
</pre
>
<pre id="diagram43" class="mermaid2">
flowchart BT
A --test2--> B2@{ icon: "fa:bell", label: "B2 dawuygd ayuwgd uy", pos: "t", h: 40, w: 30 }
B2 --test--> C
</pre
>
<pre id="diagram6" class="mermaid2">
flowchart TB
A --> B2@{ icon: "fa:bell", form: "circle", label: "test augfuyfavf ydvaubfuac", pos: "t", w: 200, h: 100 } --> C
</pre
>
<pre id="diagram6" class="mermaid2">
flowchart TB
A --> B2@{ icon: "fa:bell", form: "circle", label: "test augfuyfavf ydvaubfuac", pos: "b", w: 200, h: 100 } --> C
D --> B2 --> E
</pre
>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import layouts from './mermaid-layout-elk.esm.mjs';
mermaid.registerLayoutLoaders(layouts);
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
},
{
name: 'fa',
loader: () =>
fetch('https://unpkg.com/@iconify-json/fa6-solid/icons.json').then((res) => res.json()),
},
]);
mermaid.parseError = function (err, hash) {
console.error('Mermaid error: ', err);
};
window.callback = function () {
alert('A callback was triggered');
};
mermaid.initialize({
// theme: 'base',
// handdrawnSeed: 12,
look: 'classic',
// 'elk.nodePlacement.strategy': 'NETWORK_SIMPLEX',
// 'elk.nodePlacement.strategy': 'SIMPLE',
// 'elk.nodePlacement.strategy': 'LAYERED',
// 'elk.mergeEdges': true,
// layout: 'dagre',
// layout: 'elk',
// layout: 'fixed',
// htmlLabels: false,
flowchart: { titleTopMargin: 10, padding: 0, htmlLabels: true },
// fontFamily: 'Caveat',
// fontFamily: 'Kalam',
fontFamily: 'courier',
sequence: {
actorFontFamily: 'courier',
noteFontFamily: 'courier',
messageFontFamily: 'courier',
},
themeVariables: {
fontSize: 50,
fontFamily: 'courier',
},
logLevel: 0,
securityLevel: 'loose',
});
function callback() {
alert('It worked');
}
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);
};
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

152
cypress/timings.json Normal file
View File

@@ -0,0 +1,152 @@
{
"durations": [
{
"spec": "cypress/integration/other/configuration.spec.js",
"duration": 4989
},
{
"spec": "cypress/integration/other/external-diagrams.spec.js",
"duration": 1382
},
{
"spec": "cypress/integration/other/ghsa.spec.js",
"duration": 3178
},
{
"spec": "cypress/integration/other/iife.spec.js",
"duration": 1372
},
{
"spec": "cypress/integration/other/interaction.spec.js",
"duration": 8998
},
{
"spec": "cypress/integration/other/rerender.spec.js",
"duration": 1249
},
{
"spec": "cypress/integration/other/xss.spec.js",
"duration": 25664
},
{
"spec": "cypress/integration/rendering/appli.spec.js",
"duration": 1928
},
{
"spec": "cypress/integration/rendering/architecture.spec.ts",
"duration": 2330
},
{
"spec": "cypress/integration/rendering/block.spec.js",
"duration": 11156
},
{
"spec": "cypress/integration/rendering/c4.spec.js",
"duration": 3418
},
{
"spec": "cypress/integration/rendering/classDiagram-v2.spec.js",
"duration": 14866
},
{
"spec": "cypress/integration/rendering/classDiagram.spec.js",
"duration": 9894
},
{
"spec": "cypress/integration/rendering/conf-and-directives.spec.js",
"duration": 5778
},
{
"spec": "cypress/integration/rendering/current.spec.js",
"duration": 1690
},
{
"spec": "cypress/integration/rendering/erDiagram.spec.js",
"duration": 9144
},
{
"spec": "cypress/integration/rendering/errorDiagram.spec.js",
"duration": 1951
},
{
"spec": "cypress/integration/rendering/flowchart-elk.spec.js",
"duration": 2196
},
{
"spec": "cypress/integration/rendering/flowchart-handDrawn.spec.js",
"duration": 21029
},
{
"spec": "cypress/integration/rendering/flowchart-shape-alias.spec.ts",
"duration": 16087
},
{
"spec": "cypress/integration/rendering/flowchart-v2.spec.js",
"duration": 27465
},
{
"spec": "cypress/integration/rendering/flowchart.spec.js",
"duration": 20035
},
{
"spec": "cypress/integration/rendering/gantt.spec.js",
"duration": 11366
},
{
"spec": "cypress/integration/rendering/gitGraph.spec.js",
"duration": 34025
},
{
"spec": "cypress/integration/rendering/iconShape.spec.ts",
"duration": 185902
},
{
"spec": "cypress/integration/rendering/imageShape.spec.ts",
"duration": 41631
},
{
"spec": "cypress/integration/rendering/info.spec.ts",
"duration": 1736
},
{
"spec": "cypress/integration/rendering/journey.spec.js",
"duration": 2247
},
{
"spec": "cypress/integration/rendering/katex.spec.js",
"duration": 2144
},
{
"spec": "cypress/integration/rendering/marker_unique_id.spec.js",
"duration": 1646
},
{
"spec": "cypress/integration/rendering/mindmap.spec.ts",
"duration": 6406
},
{
"spec": "cypress/integration/rendering/newShapes.spec.ts",
"duration": 107219
},
{
"spec": "cypress/integration/rendering/stateDiagram.spec.js",
"duration": 15834
},
{
"spec": "cypress/integration/rendering/theme.spec.js",
"duration": 33240
},
{
"spec": "cypress/integration/rendering/timeline.spec.ts",
"duration": 7122
},
{
"spec": "cypress/integration/rendering/xyChart.spec.js",
"duration": 11127
},
{
"spec": "cypress/integration/rendering/zenuml.spec.js",
"duration": 2391
}
]
}

View File

@@ -159,30 +159,87 @@
class People List~List~Person~~
</pre>
<hr />
<pre class="mermaid">
classDiagram
A1 --> B1
namespace A {
class A1 {
+foo : string
}
class A2 {
+bar : int
namespace Company.Project.Module {
class GenericClass~T~ {
+addItem(item: T)
+getItem() T
}
}
namespace B {
class B1 {
+foo : bool
}
class B2 {
+bar : float
}
}
A2 --> B2
</pre>
<hr />
<pre class="mermaid">
classDiagram
namespace Company.Project.Module.SubModule {
class Report {
+generatePDF(data: List)
+generateCSV(data: List)
}
}
namespace Company.Project.Module {
class Admin {
+generateReport()
}
}
Admin --> Report : generates
</pre>
<pre class="mermaid">
classDiagram
namespace Company.Project.Module {
class User {
+login(username: String, password: String)
+logout()
}
class Admin {
+addUser(user: User)
+removeUser(user: User)
+generateReport()
}
class Report {
+generatePDF(reportData: List)
+generateCSV(reportData: List)
}
}
Admin --> User : manages
Admin --> Report : generates
</pre>
<hr />
<pre class="mermaid">
classDiagram
namespace Shapes {
class Shape {
+calculateArea() double
}
class Circle {
+double radius
}
class Square {
+double side
}
}
Shape <|-- Circle
Shape <|-- Square
namespace Vehicles {
class Vehicle {
+String brand
}
class Car {
+int horsepower
}
class Bike {
+boolean hasGears
}
}
Vehicle <|-- Car
Vehicle <|-- Bike
Car --> Circle : "Logo Shape"
Bike --> Square : "Logo Shape"
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({

233
docs/adding-new-shape.md Normal file
View File

@@ -0,0 +1,233 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/adding-new-shape.md](../packages/mermaid/src/docs/adding-new-shape.md).
# Custom SVG Shapes Library
This library provides a collection of custom SVG shapes, utilities, and helpers for generating diagram components. The shapes are designed to be used within an SVG container and include a variety of common and complex shapes.
## Overview
## Shape Helpers and Utilities
Before starting with shape creation, it's essential to familiarize yourself with the utilities provided in the `utils.ts` file from `packages/mermaid/src/rendering-util/rendering-elements/shapes/util.js`. These utilities are designed to assist with various aspects of SVG shape manipulation and ensure consistent and accurate rendering.
## Available Utilities
### 1. `labelHelper`
- **Purpose**: This function creates and inserts labels inside SVG shapes.
- **Features**:
- Handles both HTML labels and plain text.
- Calculates the bounding box dimensions of the label.
- Ensures proper positioning of labels within shapes.
### 2. `updateNodeBounds`
- **Purpose**: Updates the bounding box dimensions (width and height) of a node.
- **Usage**:
- Adjusts the size of the node to fit the content or shape.
- Useful for ensuring that shapes resize appropriately based on their content.
### 3. `insertPolygonShape`
- **Purpose**: Inserts a polygon shape into an SVG container.
- **Features**:
- Handles the creation and insertion of complex polygonal shapes.
- Configures the shape's appearance and positioning within the SVG container.
### 4. `getNodeClasses`
- **Purpose**: Returns the appropriate CSS classes for a node based on its configuration.
- **Usage**:
- Dynamically applies CSS classes to nodes for styling purposes.
- Ensures that nodes adhere to the desired design and theme.
### 5. `createPathFromPoints`
- **Purpose**: Generates an SVG path string from an array of points.
- **Usage**:
- Converts a list of points into a smooth path.
- Useful for creating custom shapes or paths within the SVG.
### 6. `generateFullSineWavePoints`
- **Purpose**: Generates points for a sine wave, useful for creating wavy-edged shapes.
- **Usage**:
- Facilitates the creation of shapes with wavy or sine-wave edges.
- Can be used to add decorative or dynamic edges to shapes.
## Getting Started
To utilize these utilities, simply import them from the `utils.ts` file into your shape creation script. These helpers will streamline the process of building and customizing SVG shapes, ensuring consistent results across your projects.
```typescript
import {
labelHelper,
updateNodeBounds,
insertPolygonShape,
getNodeClasses,
createPathFromPoints,
generateFullSineWavePoints,
} from './utils.ts';
```
## Example Usage
Heres a basic example of how you might use some of these utilities:
```typescript
import { labelHelper, insertPolygonShape } from './utils.ts';
const svgContainer = document.getElementById('svgContainer');
// Insert a polygon shape
insertPolygonShape(svgContainer /* shape-specific parameters */);
// Create and insert a label inside the shape
labelHelper(svgContainer /* label-specific parameters */);
```
## Adding New Shapes
### 1. Create the Shape Function
To add a new shape:
- **Create the shape function**: Create a new file of name of the shape and export a function in the `shapes` directory that generates your shape. The file and function should follow the pattern used in existing shapes and return an SVG element.
- **Example**:
```typescript
import { Node, RenderOptions } from '../../types.ts';
export const myNewShape = async (
parent: SVGAElement,
node: Node,
renderOptions: RenderOptions
) => {
// Create your shape here
const shape = parent.insert('g').attr('class', 'my-new-shape');
// Add other elements or styles as needed
return shape;
};
```
### 2. Register the Shape
- **Register the shape**: Add your shape to the `shapes` object in the [main shapes module](../rendering-util/rendering-elements/shapes.ts). This allows your shape to be recognized and used within the system.
- **Example**:
```typescript
import { myNewShape } from './shapes/myNewShape';
const shapes = {
...,
{
semanticName: 'My Shape',
name: 'Shape Name',
shortName: '<short-name>',
description: '<Description for the shape>',
aliases: ['<alias-one>', '<al-on>', '<alias-two>', '<al-two>'],
handler: myNewShape,
},
};
```
# Shape Intersection Algorithms
This contains algorithms and utilities for calculating intersection points for various shapes in SVG. Arrow intersection points are crucial for accurately determining where arrows connect with shapes. Ensuring precise intersection points enhances the clarity and accuracy of flowcharts and diagrams.
## Shape Intersection Functions
### 1. `Ellipse`
Calculates the intersection points for an ellipse.
**Usage**:
```javascript
import intersectEllipse from './intersect-ellipse.js';
const intersection = intersectEllipse(node, rx, ry, point);
```
- **Parameters**:
- `node`: The SVG node element.
- `rx`: The x-radius of the ellipse.
- `ry`: The y-radius of the ellipse.
- `point`: The point from which the intersection is calculated.
### 2. `intersectRect`
Calculates the intersection points for a rectangle.
**Usage**:
```javascript
import intersectRect from './intersect-rect.js';
const intersection = intersectRect(node, point);
```
- **Parameters**:
- `node`: The SVG node element.
- `point`: The point from which the intersection is calculated.
### 3. `intersectPolygon`
Calculates the intersection points for a polygon.
**Usage**:
```javascript
import intersectPolygon from './intersect-polygon.js';
const intersection = intersectPolygon(node, polyPoints, point);
```
- **Parameters**:
- `node`: The SVG node element.
- `polyPoints`: Array of points defining the polygon.
- `point`: The point from which the intersection is calculated.
## Cypress Tests
To ensure the robustness of the flowchart shapes, there are implementation of comprehensive Cypress test cases in `newShapes.spec.ts` file. These tests cover various aspects such as:
- **Shapes**: Testing new shapes like `bowTieRect`, `waveRectangle`, `trapezoidalPentagon`, etc.
- **Looks**: Verifying shapes under different visual styles (`classic` and `handDrawn`).
- **Directions**: Ensuring correct rendering in all flow directions of arrows :
- `TB` `(Top -> Bottom)`
- `BT` `(Bottom -> Top)`
- `LR` `(Left -> Right)`
- `RL` `(Right -> Left)`
- **Labels**: Testing shapes with different labels, including:
- No labels
- Short labels
- Very long labels
- Markdown with `htmlLabels:true` and `htmlLabels:false`
- **Styles**: Applying custom styles to shapes and verifying correct rendering.
- **Class Definitions**: Using `classDef` to apply custom classes and testing their impact.
### Running the Tests
To run the Cypress tests, follow these steps:
1. Ensure you have all dependencies installed by running:
```bash
pnpm install
```
2. Start the Cypress test runner:
```bash
cypress open --env updateSnapshots=true
```
3. Select the test suite from the Cypress interface to run all the flowchart shape tests.

55
docs/config/icons.md Normal file
View File

@@ -0,0 +1,55 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/icons.md](../../packages/mermaid/src/docs/config/icons.md).
# Registering icon pack in mermaid
The icon packs available can be found at [icones.js.org](https://icones.js.org/).
We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram.
Using JSON file directly from CDN:
```js
import mermaid from 'CDN/mermaid.esm.mjs';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()),
},
]);
```
Using packages and a bundler:
```bash
npm install @iconify-json/logos@1
```
With lazy loading
```js
import mermaid from 'mermaid';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () => import('@iconify-json/logos').then((module) => module.icons),
},
]);
```
Without lazy loading
```js
import mermaid from 'mermaid';
import { icons } from '@iconify-json/logos';
mermaid.registerIconPacks([
{
name: icons.prefix, // To use the prefix defined in the icon pack
icons,
},
]);
```

View File

@@ -127,7 +127,7 @@ Error.prepareStackTrace
#### Defined in
node_modules/.pnpm/@types+node\@20.16.2/node_modules/@types/node/globals.d.ts:28
node_modules/.pnpm/@types+node\@20.16.11/node_modules/@types/node/globals.d.ts:98
---
@@ -141,7 +141,7 @@ Error.stackTraceLimit
#### Defined in
node_modules/.pnpm/@types+node\@20.16.2/node_modules/@types/node/globals.d.ts:30
node_modules/.pnpm/@types+node\@20.16.11/node_modules/@types/node/globals.d.ts:100
## Methods
@@ -168,4 +168,4 @@ Error.captureStackTrace
#### Defined in
node_modules/.pnpm/@types+node\@20.16.2/node_modules/@types/node/globals.d.ts:21
node_modules/.pnpm/@types+node\@20.16.11/node_modules/@types/node/globals.d.ts:91

View File

@@ -20,7 +20,7 @@
#### Defined in
[packages/mermaid/src/rendering-util/types.ts:117](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L117)
[packages/mermaid/src/rendering-util/types.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L125)
---
@@ -30,7 +30,7 @@
#### Defined in
[packages/mermaid/src/rendering-util/types.ts:116](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L116)
[packages/mermaid/src/rendering-util/types.ts:124](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L124)
---
@@ -40,4 +40,4 @@
#### Defined in
[packages/mermaid/src/rendering-util/types.ts:115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L115)
[packages/mermaid/src/rendering-util/types.ts:123](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L123)

View File

@@ -19,4 +19,4 @@ The `parseError` function will not be called.
#### Defined in
[packages/mermaid/src/types.ts:45](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L45)
[packages/mermaid/src/types.ts:56](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L56)

View File

@@ -18,7 +18,7 @@ The config passed as YAML frontmatter or directives
#### Defined in
[packages/mermaid/src/types.ts:56](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L56)
[packages/mermaid/src/types.ts:67](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L67)
---
@@ -30,4 +30,4 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
#### Defined in
[packages/mermaid/src/types.ts:52](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L52)
[packages/mermaid/src/types.ts:63](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L63)

View File

@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
#### Defined in
[packages/mermaid/src/types.ts:79](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L79)
[packages/mermaid/src/types.ts:90](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L90)
---
@@ -51,7 +51,7 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
#### Defined in
[packages/mermaid/src/types.ts:69](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L69)
[packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L80)
---
@@ -63,4 +63,4 @@ The svg code for the rendered graph.
#### Defined in
[packages/mermaid/src/types.ts:65](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L65)
[packages/mermaid/src/types.ts:76](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L76)

View File

@@ -14,7 +14,7 @@
#### Defined in
[packages/mermaid/src/defaultConfig.ts:266](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L266)
[packages/mermaid/src/defaultConfig.ts:267](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L267)
---

View File

@@ -64,7 +64,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
- [Mermaid Flow Visual Editor](https://www.mermaidflow.app) ✅
- [Mermerd](https://github.com/KarnerTh/mermerd)
- [Slab](https://slab.com) ✅
- [Swimm](https://docs.swimm.io/features/diagrams-and-charts/#mermaid--swimm--up-to-date-diagrams-) ✅
- [Swimm](https://docs.swimm.io/features/diagrams-and-charts) ✅
- [NotesHub](https://noteshub.app) ✅
- [Notion](https://notion.so) ✅
- [Observable](https://observablehq.com/@observablehq/mermaid) ✅

View File

@@ -12,7 +12,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
<br />
<a href="https://www.producthunt.com/products/mermaid-chart?utm_source=badge-follow&utm_medium=badge&utm_souce=badge-mermaid&#0045;chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=552855&theme=light" alt="Mermaid&#0032;Chart - A&#0032;smarter&#0032;way&#0032;to&#0032;create&#0032;diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://www.producthunt.com/posts/mermaid-whiteboard?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;whiteboard" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=486720&theme=light" alt="Mermaid&#0032;Whiteboard - Drag&#0032;&#0038;&#0032;Drop&#0032;your&#0032;Nodes&#0032;with&#0032;Mermaid&#0039;s&#0032;new&#0032;Whiteboard&#0033; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## About
@@ -22,17 +22,20 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
- **Editor** - A web based editor for creating and editing Mermaid diagrams.
- **Visual Editor** - The Visual Editor enables users of all skill levels to create diagrams easily and efficiently, with both GUI and code-based editing options.
- **Mermaid AI** - Use our embedded AI Chat to generate diagrams from natural language descriptions.
- **AI Chat** - Use our embedded AI Chat to generate diagrams from natural language descriptions.
- **Whiteboard** - A virtual whiteboard for creating and editing Mermaid diagrams.
- **Plugins** - A plugin system for extending the functionality of Mermaid.
Official Mermaid Chart plugins:
- [Mermaid Chart GPT](https://chat.openai.com/g/g-1IRFKwq4G-mermaid-chart)
- [Confluence](https://marketplace.atlassian.com/apps/1234056/mermaid-chart-for-confluence?hosting=cloud&tab=overview)
- [Jira](https://marketplace.atlassian.com/apps/1234810/mermaid-chart-for-jira?tab=overview&hosting=cloud)
- [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=MermaidChart.vscode-mermaid-chart)
- [JetBrains IDE](https://plugins.jetbrains.com/plugin/23043-mermaid-chart)
- [Google Docs](https://gsuite.google.com/marketplace/app/mermaidchart/947683068472)
- [Microsoft PowerPoint and Word](https://appsource.microsoft.com/en-us/product/office/WA200006214?tab=Overview)
Visit our [Plugins](https://www.mermaidchart.com/plugins) page for more information.

View File

@@ -6,6 +6,12 @@
# Blog
## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/)
24 September 2024 · 5 mins
Discover 30 new shapes in Mermaid flowcharts, offering enhanced clarity, customization, and versatility for more dynamic and expressive visualizations.
## [Introducing Architecture Diagrams in Mermaid](https://www.mermaidchart.com/blog/posts/mermaid-supports-architecture-diagrams/)
2 September 2024 · 2 mins

View File

@@ -79,15 +79,15 @@ service {service id}({icon name})[{title}] (in {parent id})?
Put together:
```
service database(db)[Database]
service database1(database)[My Database]
```
creates the service identified as `database`, using the icon `db`, with the label `Database`.
creates the service identified as `database1`, using the icon `database`, with the label `My Database`.
If the service belongs to a group, it can be placed inside it through the optional `in` keyword
```
service database(db)[Database] in private_api
service database1(database)[My Database] in private_api
```
### Edges
@@ -194,55 +194,7 @@ architecture-beta
## Icons
By default, architecture diagram supports the following icons: `cloud`, `database`, `disk`, `internet`, `server`.
Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps below.
The icon packs available can be found at [icones.js.org](https://icones.js.org/).
We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram.
Using JSON file directly from CDN:
```js
import mermaid from 'CDN/mermaid.esm.mjs';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
},
]);
```
Using packages and a bundler:
```bash
npm install @iconify-json/logos
```
With lazy loading
```js
import mermaid from 'mermaid';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () => import('@iconify-json/logos').then((module) => module.icons),
},
]);
```
Without lazy loading
```js
import mermaid from 'mermaid';
import { icons } from '@iconify-json/logos';
mermaid.registerIconPacks([
{
name: icons.prefix, // To use the prefix defined in the icon pack
icons,
},
]);
```
Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps [here](../config/icons.md).
After the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack.

View File

@@ -211,6 +211,27 @@ block-beta
This example demonstrates how Mermaid dynamically adjusts the width of the columns to accommodate the widest block, in this case, 'a' and the composite block 'e'. This dynamic adjustment is essential for creating visually balanced and easy-to-understand diagrams.
**Merging Blocks Horizontally:**
In scenarios where you need to stack blocks horizontally, you can use column width to accomplish the task. Blocks can be arranged vertically by putting them in a single column. Here is how you can create a block diagram in which 4 blocks are stacked on top of each other:
```mermaid-example
block-beta
block
columns 1
a["A label"] b c d
end
```
```mermaid
block-beta
block
columns 1
a["A label"] b c d
end
```
In this example, the width of the merged block dynamically adjusts to the width of the largest child block.
With these advanced configuration options, Mermaid's block diagrams can be tailored to represent a wide array of complex systems and structures. The flexibility offered by these features enables users to create diagrams that are both informative and visually appealing. In the following sections, we will explore further capabilities, including different block shapes and linking options.
## 4. Block Varieties and Shapes

View File

@@ -298,6 +298,694 @@ flowchart TD
id1(((This is the text in the circle)))
```
## Expanded Node Shapes in Mermaid Flowcharts (v11.3.0+)
Mermaid introduces 30 new shapes to enhance the flexibility and precision of flowchart creation. These new shapes provide more options to represent processes, decisions, events, data storage visually, and other elements within your flowcharts, improving clarity and semantic meaning.
New Syntax for Shape Definition
Mermaid now supports a general syntax for defining shape types to accommodate the growing number of shapes. This syntax allows you to assign specific shapes to nodes using a clear and flexible format:
```
A@{ shape: rect }
```
This syntax creates a node A as a rectangle. It renders in the same way as `A["A"]`, or `A`.
### Complete List of New Shapes
Below is a comprehensive list of the newly introduced shapes and their corresponding semantic meanings, short names, and aliases:
| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
| Card | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` |
| Collate | Hourglass | `hourglass` | Represents a collate operation | `collate`, `hourglass` |
| Com Link | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` |
| Comment | Curly Brace | `brace` | Adds a comment | `brace-l`, `comment` |
| Comment Right | Curly Brace | `brace-r` | Adds a comment | |
| Comment with braces on both sides | Curly Braces | `braces` | Adds a comment | |
| Data Input/Output | Lean Right | `lean-r` | Represents input or output | `in-out`, `lean-right` |
| Data Input/Output | Lean Left | `lean-l` | Represents output or input | `lean-left`, `out-in` |
| Database | Cylinder | `cyl` | Database storage | `cylinder`, `database`, `db` |
| Decision | Diamond | `diam` | Decision-making step | `decision`, `diamond`, `question` |
| Delay | Half-Rounded Rectangle | `delay` | Represents a delay | `half-rounded-rectangle` |
| Direct Access Storage | Horizontal Cylinder | `h-cyl` | Direct access storage | `das`, `horizontal-cylinder` |
| Disk Storage | Lined Cylinder | `lin-cyl` | Disk storage | `disk`, `lined-cylinder` |
| Display | Curved Trapezoid | `curv-trap` | Represents a display | `curved-trapezoid`, `display` |
| Divided Process | Divided Rectangle | `div-rect` | Divided process shape | `div-proc`, `divided-process`, `divided-rectangle` |
| Document | Document | `doc` | Represents a document | `doc`, `document` |
| Event | Rounded Rectangle | `rounded` | Represents an event | `event` |
| Extract | Triangle | `tri` | Extraction process | `extract`, `triangle` |
| Fork/Join | Filled Rectangle | `fork` | Fork or join in process flow | `join` |
| Internal Storage | Window Pane | `win-pane` | Internal storage | `internal-storage`, `window-pane` |
| Junction | Filled Circle | `f-circ` | Junction point | `filled-circle`, `junction` |
| Lined Document | Lined Document | `lin-doc` | Lined document | `lined-document` |
| Lined/Shaded Process | Lined Rectangle | `lin-rect` | Lined process shape | `lin-proc`, `lined-process`, `lined-rectangle`, `shaded-process` |
| Loop Limit | Trapezoidal Pentagon | `notch-pent` | Loop limit step | `loop-limit`, `notched-pentagon` |
| Manual File | Flipped Triangle | `flip-tri` | Manual file operation | `flipped-triangle`, `manual-file` |
| Manual Input | Sloped Rectangle | `sl-rect` | Manual input step | `manual-input`, `sloped-rectangle` |
| Manual Operation | Trapezoid Base Top | `trap-t` | Represents a manual task | `inv-trapezoid`, `manual`, `trapezoid-top` |
| Multi-Document | Stacked Document | `docs` | Multiple documents | `documents`, `st-doc`, `stacked-document` |
| Multi-Process | Stacked Rectangle | `st-rect` | Multiple processes | `processes`, `procs`, `stacked-rectangle` |
| Odd | Odd | `odd` | Odd shape | |
| Paper Tape | Flag | `flag` | Paper tape | `paper-tape` |
| Prepare Conditional | Hexagon | `hex` | Preparation or condition step | `hexagon`, `prepare` |
| Priority Action | Trapezoid Base Bottom | `trap-b` | Priority action | `priority`, `trapezoid`, `trapezoid-bottom` |
| Process | Rectangle | `rect` | Standard process shape | `proc`, `process`, `rectangle` |
| Start | Circle | `circle` | Starting point | `circ` |
| Start | Small Circle | `sm-circ` | Small starting point | `small-circle`, `start` |
| Stop | Double Circle | `dbl-circ` | Represents a stop point | `double-circle` |
| Stop | Framed Circle | `fr-circ` | Stop point | `framed-circle`, `stop` |
| Stored Data | Bow Tie Rectangle | `bow-rect` | Stored data | `bow-tie-rectangle`, `stored-data` |
| Subprocess | Framed Rectangle | `fr-rect` | Subprocess | `framed-rectangle`, `subproc`, `subprocess`, `subroutine` |
| Summary | Crossed Circle | `cross-circ` | Summary | `crossed-circle`, `summary` |
| Tagged Document | Tagged Document | `tag-doc` | Tagged document | `tag-doc`, `tagged-document` |
| Tagged Process | Tagged Rectangle | `tag-rect` | Tagged process | `tag-proc`, `tagged-process`, `tagged-rectangle` |
| Terminal Point | Stadium | `stadium` | Terminal point | `pill`, `terminal` |
| Text Block | Text Block | `text` | Text block | |
### Example Flowchart with New Shapes
Heres an example flowchart that utilizes some of the newly introduced shapes:
```mermaid-example
flowchart RL
A@{ shape: manual-file, label: "File Handling"}
B@{ shape: manual-input, label: "User Input"}
C@{ shape: docs, label: "Multiple Documents"}
D@{ shape: procs, label: "Process Automation"}
E@{ shape: paper-tape, label: "Paper Records"}
```
```mermaid
flowchart RL
A@{ shape: manual-file, label: "File Handling"}
B@{ shape: manual-input, label: "User Input"}
C@{ shape: docs, label: "Multiple Documents"}
D@{ shape: procs, label: "Process Automation"}
E@{ shape: paper-tape, label: "Paper Records"}
```
### Process
```mermaid-example
flowchart TD
A@{ shape: rect, label: "This is a process" }
```
```mermaid
flowchart TD
A@{ shape: rect, label: "This is a process" }
```
### Event
```mermaid-example
flowchart TD
A@{ shape: rounded, label: "This is an event" }
```
```mermaid
flowchart TD
A@{ shape: rounded, label: "This is an event" }
```
### Terminal Point (Stadium)
```mermaid-example
flowchart TD
A@{ shape: stadium, label: "Terminal point" }
```
```mermaid
flowchart TD
A@{ shape: stadium, label: "Terminal point" }
```
### Subprocess
```mermaid-example
flowchart TD
A@{ shape: subproc, label: "This is a subprocess" }
```
```mermaid
flowchart TD
A@{ shape: subproc, label: "This is a subprocess" }
```
### Database (Cylinder)
```mermaid-example
flowchart TD
A@{ shape: cyl, label: "Database" }
```
```mermaid
flowchart TD
A@{ shape: cyl, label: "Database" }
```
### Start (Circle)
```mermaid-example
flowchart TD
A@{ shape: circle, label: "Start" }
```
```mermaid
flowchart TD
A@{ shape: circle, label: "Start" }
```
### Odd
```mermaid-example
flowchart TD
A@{ shape: odd, label: "Odd shape" }
```
```mermaid
flowchart TD
A@{ shape: odd, label: "Odd shape" }
```
### Decision (Diamond)
```mermaid-example
flowchart TD
A@{ shape: diamond, label: "Decision" }
```
```mermaid
flowchart TD
A@{ shape: diamond, label: "Decision" }
```
### Prepare Conditional (Hexagon)
```mermaid-example
flowchart TD
A@{ shape: hex, label: "Prepare conditional" }
```
```mermaid
flowchart TD
A@{ shape: hex, label: "Prepare conditional" }
```
### Data Input/Output (Lean Right)
```mermaid-example
flowchart TD
A@{ shape: lean-r, label: "Input/Output" }
```
```mermaid
flowchart TD
A@{ shape: lean-r, label: "Input/Output" }
```
### Data Input/Output (Lean Left)
```mermaid-example
flowchart TD
A@{ shape: lean-l, label: "Output/Input" }
```
```mermaid
flowchart TD
A@{ shape: lean-l, label: "Output/Input" }
```
### Priority Action (Trapezoid Base Bottom)
```mermaid-example
flowchart TD
A@{ shape: trap-b, label: "Priority action" }
```
```mermaid
flowchart TD
A@{ shape: trap-b, label: "Priority action" }
```
### Manual Operation (Trapezoid Base Top)
```mermaid-example
flowchart TD
A@{ shape: trap-t, label: "Manual operation" }
```
```mermaid
flowchart TD
A@{ shape: trap-t, label: "Manual operation" }
```
### Stop (Double Circle)
```mermaid-example
flowchart TD
A@{ shape: dbl-circ, label: "Stop" }
```
```mermaid
flowchart TD
A@{ shape: dbl-circ, label: "Stop" }
```
### Text Block
```mermaid-example
flowchart TD
A@{ shape: text, label: "This is a text block" }
```
```mermaid
flowchart TD
A@{ shape: text, label: "This is a text block" }
```
### Card (Notched Rectangle)
```mermaid-example
flowchart TD
A@{ shape: notch-rect, label: "Card" }
```
```mermaid
flowchart TD
A@{ shape: notch-rect, label: "Card" }
```
### Lined/Shaded Process
```mermaid-example
flowchart TD
A@{ shape: lin-rect, label: "Lined process" }
```
```mermaid
flowchart TD
A@{ shape: lin-rect, label: "Lined process" }
```
### Start (Small Circle)
```mermaid-example
flowchart TD
A@{ shape: sm-circ, label: "Small start" }
```
```mermaid
flowchart TD
A@{ shape: sm-circ, label: "Small start" }
```
### Stop (Framed Circle)
```mermaid-example
flowchart TD
A@{ shape: framed-circle, label: "Stop" }
```
```mermaid
flowchart TD
A@{ shape: framed-circle, label: "Stop" }
```
### Fork/Join (Long Rectangle)
```mermaid-example
flowchart TD
A@{ shape: fork, label: "Fork or Join" }
```
```mermaid
flowchart TD
A@{ shape: fork, label: "Fork or Join" }
```
### Collate (Hourglass)
```mermaid-example
flowchart TD
A@{ shape: hourglass, label: "Collate" }
```
```mermaid
flowchart TD
A@{ shape: hourglass, label: "Collate" }
```
### Comment (Curly Brace)
```mermaid-example
flowchart TD
A@{ shape: comment, label: "Comment" }
```
```mermaid
flowchart TD
A@{ shape: comment, label: "Comment" }
```
### Comment Right (Curly Brace Right)
```mermaid-example
flowchart TD
A@{ shape: brace-r, label: "Comment" }
```
```mermaid
flowchart TD
A@{ shape: brace-r, label: "Comment" }
```
### Comment with braces on both sides
```mermaid-example
flowchart TD
A@{ shape: braces, label: "Comment" }
```
```mermaid
flowchart TD
A@{ shape: braces, label: "Comment" }
```
### Com Link (Lightning Bolt)
```mermaid-example
flowchart TD
A@{ shape: bolt, label: "Communication link" }
```
```mermaid
flowchart TD
A@{ shape: bolt, label: "Communication link" }
```
### Document
```mermaid-example
flowchart TD
A@{ shape: doc, label: "Document" }
```
```mermaid
flowchart TD
A@{ shape: doc, label: "Document" }
```
### Delay (Half-Rounded Rectangle)
```mermaid-example
flowchart TD
A@{ shape: delay, label: "Delay" }
```
```mermaid
flowchart TD
A@{ shape: delay, label: "Delay" }
```
### Direct Access Storage (Horizontal Cylinder)
```mermaid-example
flowchart TD
A@{ shape: das, label: "Direct access storage" }
```
```mermaid
flowchart TD
A@{ shape: das, label: "Direct access storage" }
```
### Disk Storage (Lined Cylinder)
```mermaid-example
flowchart TD
A@{ shape: lin-cyl, label: "Disk storage" }
```
```mermaid
flowchart TD
A@{ shape: lin-cyl, label: "Disk storage" }
```
### Display (Curved Trapezoid)
```mermaid-example
flowchart TD
A@{ shape: curv-trap, label: "Display" }
```
```mermaid
flowchart TD
A@{ shape: curv-trap, label: "Display" }
```
### Divided Process (Divided Rectangle)
```mermaid-example
flowchart TD
A@{ shape: div-rect, label: "Divided process" }
```
```mermaid
flowchart TD
A@{ shape: div-rect, label: "Divided process" }
```
### Extract (Small Triangle)
```mermaid-example
flowchart TD
A@{ shape: tri, label: "Extract" }
```
```mermaid
flowchart TD
A@{ shape: tri, label: "Extract" }
```
### Internal Storage (Window Pane)
```mermaid-example
flowchart TD
A@{ shape: win-pane, label: "Internal storage" }
```
```mermaid
flowchart TD
A@{ shape: win-pane, label: "Internal storage" }
```
### Junction (Filled Circle)
```mermaid-example
flowchart TD
A@{ shape: f-circ, label: "Junction" }
```
```mermaid
flowchart TD
A@{ shape: f-circ, label: "Junction" }
```
### Lined Document
```mermaid-example
flowchart TD
A@{ shape: lin-doc, label: "Lined document" }
```
```mermaid
flowchart TD
A@{ shape: lin-doc, label: "Lined document" }
```
### Loop Limit (Notched Pentagon)
```mermaid-example
flowchart TD
A@{ shape: notch-pent, label: "Loop limit" }
```
```mermaid
flowchart TD
A@{ shape: notch-pent, label: "Loop limit" }
```
### Manual File (Flipped Triangle)
```mermaid-example
flowchart TD
A@{ shape: flip-tri, label: "Manual file" }
```
```mermaid
flowchart TD
A@{ shape: flip-tri, label: "Manual file" }
```
### Manual Input (Sloped Rectangle)
```mermaid-example
flowchart TD
A@{ shape: sl-rect, label: "Manual input" }
```
```mermaid
flowchart TD
A@{ shape: sl-rect, label: "Manual input" }
```
### Multi-Document (Stacked Document)
```mermaid-example
flowchart TD
A@{ shape: docs, label: "Multiple documents" }
```
```mermaid
flowchart TD
A@{ shape: docs, label: "Multiple documents" }
```
### Multi-Process (Stacked Rectangle)
```mermaid-example
flowchart TD
A@{ shape: processes, label: "Multiple processes" }
```
```mermaid
flowchart TD
A@{ shape: processes, label: "Multiple processes" }
```
### Paper Tape (Flag)
```mermaid-example
flowchart TD
A@{ shape: flag, label: "Paper tape" }
```
```mermaid
flowchart TD
A@{ shape: flag, label: "Paper tape" }
```
### Stored Data (Bow Tie Rectangle)
```mermaid-example
flowchart TD
A@{ shape: bow-rect, label: "Stored data" }
```
```mermaid
flowchart TD
A@{ shape: bow-rect, label: "Stored data" }
```
### Summary (Crossed Circle)
```mermaid-example
flowchart TD
A@{ shape: cross-circ, label: "Summary" }
```
```mermaid
flowchart TD
A@{ shape: cross-circ, label: "Summary" }
```
### Tagged Document
```mermaid-example
flowchart TD
A@{ shape: tag-doc, label: "Tagged document" }
```
```mermaid
flowchart TD
A@{ shape: tag-doc, label: "Tagged document" }
```
### Tagged Process (Tagged Rectangle)
```mermaid-example
flowchart TD
A@{ shape: tag-rect, label: "Tagged process" }
```
```mermaid
flowchart TD
A@{ shape: tag-rect, label: "Tagged process" }
```
## Special shapes in Mermaid Flowcharts (v11.3.0+)
Mermaid also introduces 2 special shapes to enhance your flowcharts: **icon** and **image**. These shapes allow you to include icons and images directly within your flowcharts, providing more visual context and clarity.
### Icon Shape
You can use the `icon` shape to include an icon in your flowchart. To use icons, you need to register the icon pack first. Follow the instructions provided [here](../config/icons.md). The syntax for defining an icon shape is as follows:
```mermaid-example
flowchart TD
A@{ icon: "fa:user", form: "square", label: "User Icon", pos: "t", h: 60 }
```
```mermaid
flowchart TD
A@{ icon: "fa:user", form: "square", label: "User Icon", pos: "t", h: 60 }
```
### Parameters
- **icon**: The name of the icon from the registered icon pack.
- **form**: Specifies the background shape of the icon. If not defined there will be no background to icon. Options include:
- `square`
- `circle`
- `rounded`
- **label**: The text label associated with the icon. This can be any string. If not defined, no label will be displayed.
- **pos**: The position of the label. If not defined label will default to bottom of icon. Possible values are:
- `t`
- `b`
- **h**: The height of the icon. If not defined this will default to 48 which is minimum.
### Image Shape
You can use the `image` shape to include an image in your flowchart. The syntax for defining an image shape is as follows:
```mermaid-example
flowchart TD
A@{ img: "https://example.com/image.png", label: "Image Label", pos: "t", w: 60, h: 60, constraint: "off" }
```
```mermaid
flowchart TD
A@{ img: "https://example.com/image.png", label: "Image Label", pos: "t", w: 60, h: 60, constraint: "off" }
```
### Parameters
- **img**: The URL of the image to be displayed.
- **label**: The text label associated with the image. This can be any string. If not defined, no label will be displayed.
- **pos**: The position of the label. If not defined, the label will default to the bottom of the image. Possible values are:
- `t`
- `b`
- **w**: The width of the image. If not defined, this will default to the natural width of the image.
- **h**: The height of the image. If not defined, this will default to the natural height of the image.
- **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the height (`h`) accordingly to the width (`w`). If not defined, this will default to `off` Possible values are:
- `on`
- `off`
These new shapes provide additional flexibility and visual appeal to your flowcharts, making them more informative and engaging.
## Links between nodes
Nodes can be connected with links/edges. It is possible to have different types of links or attach a text string to a link.

View File

@@ -155,6 +155,9 @@ end
box rgb(33,66,99)
... actors ...
end
box rgba(33,66,99,0.5)
... actors ...
end
```
> **Note**
@@ -580,6 +583,12 @@ sequenceDiagram
It is possible to highlight flows by providing colored background rects. This is done by the notation
```
rect COLOR
... content ...
end
```
The colors are defined using rgb and rgba syntax.
```

View File

@@ -64,7 +64,7 @@
},
"devDependencies": {
"@applitools/eyes-cypress": "^3.44.4",
"@argos-ci/cypress": "^2.1.0",
"@argos-ci/cypress": "^2.2.2",
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.7",
"@cspell/eslint-plugin": "^8.8.4",
@@ -91,6 +91,7 @@
"cspell": "^8.6.0",
"cypress": "^13.14.1",
"cypress-image-snapshot": "^4.0.1",
"cypress-split": "^1.24.0",
"esbuild": "^0.21.5",
"eslint": "^9.4.0",
"eslint-config-prettier": "^9.1.0",
@@ -103,7 +104,7 @@
"eslint-plugin-markdown": "^5.0.0",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-tsdoc": "^0.3.0",
"eslint-plugin-unicorn": "^55.0.0",
"eslint-plugin-unicorn": "^56.0.0",
"express": "^4.19.1",
"globals": "^15.4.0",
"globby": "^14.0.1",
@@ -131,5 +132,10 @@
},
"nyc": {
"report-dir": "coverage/cypress"
},
"pnpm": {
"patchedDependencies": {
"roughjs": "patches/roughjs.patch"
}
}
}

View File

@@ -1,5 +1,14 @@
# @mermaid-js/layout-elk
## 0.1.5
### Patch Changes
- [#5825](https://github.com/mermaid-js/mermaid/pull/5825) [`233e36c`](https://github.com/mermaid-js/mermaid/commit/233e36c9884fcce141a72ce7c845179781e18632) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - chore: Update render options
- Updated dependencies [[`6c5b7ce`](https://github.com/mermaid-js/mermaid/commit/6c5b7ce9f41c0fbd59fe03dbefc8418d97697f0a), [`9e3aa70`](https://github.com/mermaid-js/mermaid/commit/9e3aa705ae21fd4898504ab22d775a9e437b898e), [`de2c05c`](https://github.com/mermaid-js/mermaid/commit/de2c05cd5463af68d19dd7b6b3f1303d69ddb2dd)]:
- mermaid@11.3.0
## 0.1.4
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@mermaid-js/layout-elk",
"version": "0.1.4",
"version": "0.1.5",
"description": "ELK layout engine for mermaid",
"module": "dist/mermaid-layout-elk.core.mjs",
"types": "dist/layouts.d.ts",

View File

@@ -33,10 +33,11 @@ export const render = async (
};
graph.children.push(child);
nodeDb[node.id] = child;
const config = getConfig();
// Add the element to the DOM
if (!node.isGroup) {
const childNodeEl = await insertNode(nodeEl, node, node.dir);
const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir });
boundingBox = childNodeEl.node().getBBox();
child.domId = childNodeEl;
child.width = boundingBox.width;
@@ -50,7 +51,7 @@ export const render = async (
// @ts-ignore TODO: fix this
const { shapeSvg, bbox } = await labelHelper(nodeEl, node, undefined, true);
labelData.width = bbox.width;
labelData.wrappingWidth = getConfig().flowchart!.wrappingWidth;
labelData.wrappingWidth = config.flowchart!.wrappingWidth;
// Give some padding for elk
labelData.height = bbox.height - 2;
labelData.labelNode = shapeSvg.node();

View File

@@ -1,5 +1,35 @@
# mermaid
## 11.3.0
### Minor Changes
- [#5825](https://github.com/mermaid-js/mermaid/pull/5825) [`9e3aa70`](https://github.com/mermaid-js/mermaid/commit/9e3aa705ae21fd4898504ab22d775a9e437b898e) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - New Flowchart Shapes (with new syntax)
### Patch Changes
- [#5849](https://github.com/mermaid-js/mermaid/pull/5849) [`6c5b7ce`](https://github.com/mermaid-js/mermaid/commit/6c5b7ce9f41c0fbd59fe03dbefc8418d97697f0a) Thanks [@ReneLombard](https://github.com/ReneLombard)! - Fixed an issue when the mermaid classdiagram crashes when adding a . to the namespace.
Forexample
```mermaid
classDiagram
namespace Company.Project.Module {
class GenericClass~T~ {
+addItem(item: T)
+getItem() T
}
}
```
- [#5914](https://github.com/mermaid-js/mermaid/pull/5914) [`de2c05c`](https://github.com/mermaid-js/mermaid/commit/de2c05cd5463af68d19dd7b6b3f1303d69ddb2dd) Thanks [@aloisklink](https://github.com/aloisklink)! - Ban DOMPurify v3.1.7 as a dependency
## 11.2.1
### Patch Changes
- [#5856](https://github.com/mermaid-js/mermaid/pull/5856) [`bfd8c63`](https://github.com/mermaid-js/mermaid/commit/bfd8c63daaa8420e57da9953922b9f0c94123064) Thanks [@knsv](https://github.com/knsv)! - Fix for issue with calculation of label width when using in firefox
## 11.2.0
### Minor Changes

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "11.2.0",
"version": "11.3.0",
"description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
"type": "module",
"module": "./dist/mermaid.core.mjs",
@@ -35,8 +35,8 @@
"clean": "rimraf dist",
"dev": "pnpm -w dev",
"docs:code": "typedoc src/defaultConfig.ts src/config.ts src/mermaid.ts && prettier --write ./src/docs/config/setup",
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && tsx scripts/docs.cli.mts",
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && tsx scripts/docs.cli.mts --verify",
"docs:build": "rimraf ../../docs && pnpm docs:code && pnpm docs:spellcheck && tsx scripts/docs.cli.mts",
"docs:verify": "pnpm docs:code && pnpm docs:spellcheck && tsx scripts/docs.cli.mts --verify",
"docs:pre:vitepress": "pnpm --filter ./src/docs prefetch && rimraf src/vitepress && pnpm docs:code && tsx scripts/docs.cli.mts --vitepress && pnpm --filter ./src/vitepress install --no-frozen-lockfile --ignore-scripts",
"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\" \"tsx scripts/docs.cli.mts --watch --vitepress\"",
@@ -70,14 +70,16 @@
"@braintree/sanitize-url": "^7.0.1",
"@iconify/utils": "^2.1.32",
"@mermaid-js/parser": "workspace:^",
"@types/d3": "^7.4.3",
"@types/dompurify": "^3.0.5",
"cytoscape": "^3.29.2",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.2.0",
"d3": "^7.9.0",
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.10",
"dagre-d3-es": "7.0.11",
"dayjs": "^1.11.10",
"dompurify": "^3.0.11",
"dompurify": "^3.0.11 <3.1.7",
"katex": "^0.16.9",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
@@ -92,13 +94,11 @@
"@iconify/types": "^2.0.0",
"@types/cytoscape": "^3.21.4",
"@types/cytoscape-fcose": "^2.2.4",
"@types/d3": "^7.4.3",
"@types/d3-sankey": "^0.12.4",
"@types/d3-scale": "^4.0.8",
"@types/d3-scale-chromatic": "^3.0.3",
"@types/d3-selection": "^3.0.10",
"@types/d3-shape": "^3.1.6",
"@types/dompurify": "^3.0.5",
"@types/jsdom": "^21.1.6",
"@types/katex": "^0.16.7",
"@types/lodash-es": "^4.17.12",

View File

@@ -41,7 +41,8 @@ import { exec } from 'child_process';
import { globby } from 'globby';
import { JSDOM } from 'jsdom';
import { dump, load, JSON_SCHEMA } from 'js-yaml';
import type { Code, ListItem, Root, Text, YAML } from 'mdast';
import type { Code, ListItem, PhrasingContent, Root, Text, YAML } from 'mdast';
import { register } from 'node:module';
import { posix, dirname, relative, join } from 'path';
import prettier from 'prettier';
import { remark } from 'remark';
@@ -53,6 +54,10 @@ import mm from 'micromatch';
import flatmap from 'unist-util-flatmap';
import { visit } from 'unist-util-visit';
// short-circuit `.schema.yaml` imports, so that we can safely import `shapes.js`
register('./loadHook.mjs', import.meta.url);
const { shapesDefs } = await import('../src/rendering-util/rendering-elements/shapes.js');
export const MERMAID_RELEASE_VERSION = JSON.parse(readFileSync('../mermaid/package.json', 'utf8'))
.version as string;
const MERMAID_MAJOR_VERSION = MERMAID_RELEASE_VERSION.split('.')[0];
@@ -103,6 +108,60 @@ const generateHeader = (file: string): string => {
> ## Please edit the corresponding file in [${filePathFromRoot}](${sourcePathRelativeToGenerated}).`;
};
/**
* Builds a markdown list of shapes supported in flowcharts.
*/
export function buildShapeDoc() {
const data = shapesDefs
.sort((a, b) => a.semanticName.localeCompare(b.semanticName))
.map((shape): PhrasingContent[][] => {
const { name, semanticName, description, shortName, aliases = [] } = shape;
return [
[{ type: 'text', value: semanticName }],
[{ type: 'text', value: name }],
[{ type: 'inlineCode', value: shortName }],
[{ type: 'text', value: description }],
aliases.sort().flatMap((alias, index) => [
...(index !== 0 ? ([{ type: 'text', value: ', ' }] as const) : []),
{
type: 'inlineCode',
value: alias,
},
]),
];
});
// don't prettify this table, since we'd do it later
return remark()
.use(remarkGfm)
.stringify({
type: 'root',
children: [
{
type: 'table',
children: [
['Semantic Name', 'Shape Name', 'Short Name', 'Description', 'Alias Supported'].map(
(s): PhrasingContent[] => [
{
type: 'strong',
children: [{ type: 'text', value: s }],
},
]
),
...data,
].map((row) => ({
type: 'tableRow',
children: row.map((cell) => ({
type: 'tableCell',
children: cell,
})),
})),
},
],
})
.toString();
}
/**
* Given a source file name and path, return the documentation destination full path and file name
* Create the destination path if it does not already exist.
@@ -192,10 +251,22 @@ export const transformToBlockQuote = (
const injectPlaceholders = (text: string): string =>
text.replace(/<MERMAID_VERSION>/g, MERMAID_MAJOR_VERSION).replace(/<CDN_URL>/g, CDN_URL);
const virtualGenerators: Record<string, () => string> = {
shapesTable: buildShapeDoc,
};
const transformIncludeStatements = (file: string, text: string): string => {
// resolve includes - src https://github.com/vuejs/vitepress/blob/428eec3750d6b5648a77ac52d88128df0554d4d1/src/node/markdownToVue.ts#L65-L76
return text.replace(includesRE, (m, m1) => {
return text.replace(includesRE, (m, m1: string) => {
try {
if (m1.startsWith('virtual:')) {
const key = m1.replace('virtual:', '');
const generator = virtualGenerators[key];
if (!generator) {
throw new Error(`Unknown virtual generator: ${key} in "${file}"`);
}
return generator();
}
const includePath = join(dirname(file), m1).replaceAll('\\', '/');
const content = readSyncedUTF8file(includePath);
includedFiles.add(changeToFinalDocDir(includePath));

View File

@@ -1,4 +1,4 @@
import { transformMarkdownAst, transformToBlockQuote } from './docs.mjs';
import { buildShapeDoc, transformMarkdownAst, transformToBlockQuote } from './docs.mjs';
import { remark } from 'remark'; // import it this way so we can mock it
import remarkFrontmatter from 'remark-frontmatter';
@@ -165,4 +165,59 @@ This Markdown should be kept.
});
});
});
describe('buildShapeDoc', () => {
it('should build shapesTable based on the shapeDefs', () => {
expect(buildShapeDoc()).toMatchInlineSnapshot(`
"| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
| Card | Notched Rectangle | \`notch-rect\` | Represents a card | \`card\`, \`notched-rectangle\` |
| Collate | Hourglass | \`hourglass\` | Represents a collate operation | \`collate\`, \`hourglass\` |
| Com Link | Lightning Bolt | \`bolt\` | Communication link | \`com-link\`, \`lightning-bolt\` |
| Comment | Curly Brace | \`brace\` | Adds a comment | \`brace-l\`, \`comment\` |
| Comment Right | Curly Brace | \`brace-r\` | Adds a comment | |
| Comment with braces on both sides | Curly Braces | \`braces\` | Adds a comment | |
| Data Input/Output | Lean Right | \`lean-r\` | Represents input or output | \`in-out\`, \`lean-right\` |
| Data Input/Output | Lean Left | \`lean-l\` | Represents output or input | \`lean-left\`, \`out-in\` |
| Database | Cylinder | \`cyl\` | Database storage | \`cylinder\`, \`database\`, \`db\` |
| Decision | Diamond | \`diam\` | Decision-making step | \`decision\`, \`diamond\`, \`question\` |
| Delay | Half-Rounded Rectangle | \`delay\` | Represents a delay | \`half-rounded-rectangle\` |
| Direct Access Storage | Horizontal Cylinder | \`h-cyl\` | Direct access storage | \`das\`, \`horizontal-cylinder\` |
| Disk Storage | Lined Cylinder | \`lin-cyl\` | Disk storage | \`disk\`, \`lined-cylinder\` |
| Display | Curved Trapezoid | \`curv-trap\` | Represents a display | \`curved-trapezoid\`, \`display\` |
| Divided Process | Divided Rectangle | \`div-rect\` | Divided process shape | \`div-proc\`, \`divided-process\`, \`divided-rectangle\` |
| Document | Document | \`doc\` | Represents a document | \`doc\`, \`document\` |
| Event | Rounded Rectangle | \`rounded\` | Represents an event | \`event\` |
| Extract | Triangle | \`tri\` | Extraction process | \`extract\`, \`triangle\` |
| Fork/Join | Filled Rectangle | \`fork\` | Fork or join in process flow | \`join\` |
| Internal Storage | Window Pane | \`win-pane\` | Internal storage | \`internal-storage\`, \`window-pane\` |
| Junction | Filled Circle | \`f-circ\` | Junction point | \`filled-circle\`, \`junction\` |
| Lined Document | Lined Document | \`lin-doc\` | Lined document | \`lined-document\` |
| Lined/Shaded Process | Lined Rectangle | \`lin-rect\` | Lined process shape | \`lin-proc\`, \`lined-process\`, \`lined-rectangle\`, \`shaded-process\` |
| Loop Limit | Trapezoidal Pentagon | \`notch-pent\` | Loop limit step | \`loop-limit\`, \`notched-pentagon\` |
| Manual File | Flipped Triangle | \`flip-tri\` | Manual file operation | \`flipped-triangle\`, \`manual-file\` |
| Manual Input | Sloped Rectangle | \`sl-rect\` | Manual input step | \`manual-input\`, \`sloped-rectangle\` |
| Manual Operation | Trapezoid Base Top | \`trap-t\` | Represents a manual task | \`inv-trapezoid\`, \`manual\`, \`trapezoid-top\` |
| Multi-Document | Stacked Document | \`docs\` | Multiple documents | \`documents\`, \`st-doc\`, \`stacked-document\` |
| Multi-Process | Stacked Rectangle | \`st-rect\` | Multiple processes | \`processes\`, \`procs\`, \`stacked-rectangle\` |
| Odd | Odd | \`odd\` | Odd shape | |
| Paper Tape | Flag | \`flag\` | Paper tape | \`paper-tape\` |
| Prepare Conditional | Hexagon | \`hex\` | Preparation or condition step | \`hexagon\`, \`prepare\` |
| Priority Action | Trapezoid Base Bottom | \`trap-b\` | Priority action | \`priority\`, \`trapezoid\`, \`trapezoid-bottom\` |
| Process | Rectangle | \`rect\` | Standard process shape | \`proc\`, \`process\`, \`rectangle\` |
| Start | Circle | \`circle\` | Starting point | \`circ\` |
| Start | Small Circle | \`sm-circ\` | Small starting point | \`small-circle\`, \`start\` |
| Stop | Double Circle | \`dbl-circ\` | Represents a stop point | \`double-circle\` |
| Stop | Framed Circle | \`fr-circ\` | Stop point | \`framed-circle\`, \`stop\` |
| Stored Data | Bow Tie Rectangle | \`bow-rect\` | Stored data | \`bow-tie-rectangle\`, \`stored-data\` |
| Subprocess | Framed Rectangle | \`fr-rect\` | Subprocess | \`framed-rectangle\`, \`subproc\`, \`subprocess\`, \`subroutine\` |
| Summary | Crossed Circle | \`cross-circ\` | Summary | \`crossed-circle\`, \`summary\` |
| Tagged Document | Tagged Document | \`tag-doc\` | Tagged document | \`tag-doc\`, \`tagged-document\` |
| Tagged Process | Tagged Rectangle | \`tag-rect\` | Tagged process | \`tag-proc\`, \`tagged-process\`, \`tagged-rectangle\` |
| Terminal Point | Stadium | \`stadium\` | Terminal point | \`pill\`, \`terminal\` |
| Text Block | Text Block | \`text\` | Text block | |
"
`);
});
});
});

View File

@@ -0,0 +1,22 @@
import { fileURLToPath } from 'node:url';
/** @import import { LoadHook } from "node:module"; */
/**
* @type {LoadHook}
*
* Load hook that short circuits the loading of `.schema.yaml` files with `export default {}`.
* These would normally be loaded using ESBuild, but that doesn't work for these local scripts.
*
* @see https://nodejs.org/api/module.html#loadurl-context-nextload
*/
export const load = async (url, context, nextLoad) => {
const filePath = url.startsWith('file://') ? fileURLToPath(url) : url;
if (filePath.endsWith('.schema.yaml')) {
return {
format: 'module',
shortCircuit: true,
source: `export default {}`,
};
} else {
return await nextLoad(url, context);
}
};

View File

@@ -87,7 +87,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
// insertCluster(clusters, graph.node(v));
} else {
log.info('Node - the non recursive path', v, node.id, node);
await insertNode(nodes, graph.node(v), dir);
await insertNode(nodes, graph.node(v), { config: siteConfig, dir });
}
}
})

View File

@@ -1131,7 +1131,7 @@ const shapes = {
let nodeElems = {};
export const insertNode = async (elem, node, dir) => {
export const insertNode = async (elem, node, renderOptions) => {
let newEl;
let el;
@@ -1144,9 +1144,9 @@ export const insertNode = async (elem, node, dir) => {
target = node.linkTarget || '_blank';
}
newEl = elem.insert('svg:a').attr('xlink:href', node.link).attr('target', target);
el = await shapes[node.shape](newEl, node, dir);
el = await shapes[node.shape](newEl, node, renderOptions);
} else {
el = await shapes[node.shape](elem, node, dir);
el = await shapes[node.shape](elem, node, renderOptions);
newEl = el;
}
if (node.tooltip) {

View File

@@ -21,8 +21,9 @@ const config: RequiredDeep<MermaidConfig> = {
// TODO: Should we replace these with `null` so that they can go in the JSON Schema?
deterministicIDSeed: undefined,
elk: {
// mergeEdges is needed here to be considered
mergeEdges: false,
nodePlacementStrategy: 'SIMPLE',
nodePlacementStrategy: 'BRANDES_KOEPF',
},
themeCSS: undefined,

View File

@@ -124,7 +124,8 @@ async function calculateBlockSize(
}
// Add the element to the DOM to size it
const nodeEl = await insertNode(elem, node);
const config = getConfig();
const nodeEl = await insertNode(elem, node, { config });
const boundingBox = nodeEl.node().getBBox();
const obj = db.getBlock(node.id);
obj.size = { width: boundingBox.width, height: boundingBox.height, x: 0, y: 0, node: nodeEl };
@@ -138,7 +139,8 @@ export async function insertBlockPositioned(elem: any, block: Block, db: any) {
// Add the element to the DOM to size it
const obj = db.getBlock(node.id);
if (obj.type !== 'space') {
await insertNode(elem, node);
const config = getConfig();
await insertNode(elem, node, { config });
block.intersect = node?.intersect;
positionNode(node);
}

View File

@@ -14,6 +14,78 @@ describe('given a basic class diagram, ', function () {
classDb.clear();
parser.yy = classDb;
});
it('should handle classes within namespaces', () => {
const str = `classDiagram
namespace Company.Project {
class User {
+login(username: String, password: String)
+logout()
}
}
namespace Company.Project.Module {
class Admin {
+addUser(user: User)
+removeUser(user: User)
}
}`;
parser.parse(str);
const user = classDb.getClass('User');
const admin = classDb.getClass('Admin');
// Check if the classes are correctly registered under their respective namespaces
expect(user.parent).toBe('Company.Project');
expect(admin.parent).toBe('Company.Project.Module');
expect(user.methods.length).toBe(2);
expect(admin.methods.length).toBe(2);
});
it('should handle generic classes within namespaces', () => {
const str = `classDiagram
namespace Company.Project.Module {
class GenericClass~T~ {
+addItem(item: T)
+getItem() T
}
}`;
parser.parse(str);
const genericClass = classDb.getClass('GenericClass');
expect(genericClass.type).toBe('T');
expect(genericClass.methods[0].getDisplayDetails().displayText).toBe('+addItem(item: T)');
expect(genericClass.methods[1].getDisplayDetails().displayText).toBe('+getItem() : T');
});
it('should handle nested namespaces and relationships', () => {
const str = ` classDiagram
namespace Company.Project.Module.SubModule {
class Report {
+generatePDF(data: List)
+generateCSV(data: List)
}
}
namespace Company.Project.Module {
class Admin {
+generateReport()
}
}
Admin --> Report : generates`;
parser.parse(str);
const report = classDb.getClass('Report');
const admin = classDb.getClass('Admin');
const relations = classDb.getRelations();
expect(report.parent).toBe('Company.Project.Module.SubModule');
expect(admin.parent).toBe('Company.Project.Module');
expect(relations[0].id1).toBe('Admin');
expect(relations[0].id2).toBe('Report');
expect(relations[0].title).toBe('generates');
});
it('should handle accTitle and accDescr', function () {
const str = `classDiagram
accTitle: My Title
@@ -48,6 +120,22 @@ describe('given a basic class diagram, ', function () {
}
});
it('should handle fully qualified class names in relationships', () => {
const str = `classDiagram
namespace Company.Project.Module {
class User
class Admin
}
Admin --> User : manages`;
parser.parse(str);
const relations = classDb.getRelations();
expect(relations[0].id1).toBe('Admin');
expect(relations[0].id2).toBe('User');
expect(relations[0].title).toBe('manages');
});
it('should handle backquoted class names', function () {
const str = 'classDiagram\n' + 'class `Car`';
@@ -393,27 +481,23 @@ class C13["With Città foreign language"]
Student "1" --o "1" IdCard : carries
Student "1" --o "1" Bike : rides`);
const studentClass = classDb.getClasses().get('Student');
// Check that the important properties match, but ignore the domId
expect(studentClass).toMatchObject({
id: 'Student',
label: 'Student',
members: [
expect.objectContaining({
id: 'idCard : IdCard',
visibility: '-',
}),
],
methods: [],
annotations: [],
cssClasses: [],
});
expect(classDb.getClasses().size).toBe(3);
expect(classDb.getClasses().get('Student')).toMatchInlineSnapshot(`
{
"annotations": [],
"cssClasses": [],
"domId": "classId-Student-134",
"id": "Student",
"label": "Student",
"members": [
ClassMember {
"classifier": "",
"id": "idCard : IdCard",
"memberType": "attribute",
"visibility": "-",
},
],
"methods": [],
"styles": [],
"type": "",
}
`);
expect(classDb.getRelations().length).toBe(2);
expect(classDb.getRelations()).toMatchInlineSnapshot(`
[

View File

@@ -241,11 +241,13 @@ classLabel
namespaceName
: alphaNumToken { $$=$1; }
| alphaNumToken DOT namespaceName { $$=$1+'.'+$3; }
| alphaNumToken namespaceName { $$=$1+$2; }
;
className
: alphaNumToken { $$=$1; }
| alphaNumToken DOT className { $$=$1+'.'+$3; }
| classLiteralName { $$=$1; }
| alphaNumToken className { $$=$1+$2; }
| alphaNumToken GENERICTYPE { $$=$1+'~'+$2+'~'; }
@@ -270,12 +272,12 @@ statement
;
namespaceStatement
: namespaceIdentifier STRUCT_START classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $3);}
| namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $4);}
: namespaceIdentifier STRUCT_START classStatements STRUCT_STOP { yy.addClassesToNamespace($1, $3); }
| namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP { yy.addClassesToNamespace($1, $4); }
;
namespaceIdentifier
: NAMESPACE namespaceName {$$=$2; yy.addNamespace($2);}
: NAMESPACE namespaceName { $$=$2; yy.addNamespace($2); }
;
classStatements

View File

@@ -4,6 +4,7 @@ import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js';
import common from '../common/common.js';
import type { Node, Edge } from '../../rendering-util/types.js';
import { log } from '../../logger.js';
import * as yaml from 'js-yaml';
import {
setAccTitle,
getAccTitle,
@@ -14,6 +15,7 @@ import {
getDiagramTitle,
} from '../common/commonDb.js';
import type { FlowVertex, FlowClass, FlowSubGraph, FlowText, FlowEdge, FlowLink } from './types.js';
import type { NodeMetaData } from '../../types.js';
const MERMAID_DOM_ID_PREFIX = 'flowchart-';
let vertexCounter = 0;
@@ -60,8 +62,10 @@ export const addVertex = function (
style: string[],
classes: string[],
dir: string,
props = {}
props = {},
shapeData: any
) {
// console.log('addVertex', id, shapeData);
if (!id || id.trim().length === 0) {
return;
}
@@ -115,6 +119,59 @@ export const addVertex = function (
} else if (props !== undefined) {
Object.assign(vertex.props, props);
}
if (shapeData !== undefined) {
let yamlData;
// detect if shapeData contains a newline character
// console.log('shapeData', shapeData);
if (!shapeData.includes('\n')) {
// console.log('yamlData shapeData has no new lines', shapeData);
yamlData = '{\n' + shapeData + '\n}';
} else {
// console.log('yamlData shapeData has new lines', shapeData);
yamlData = shapeData + '\n';
}
// console.log('yamlData', yamlData);
const doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
if (doc.shape && (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_'))) {
throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
}
// console.log('yamlData doc', doc);
if (doc?.shape) {
vertex.type = doc?.shape;
}
if (doc?.label) {
vertex.text = doc?.label;
}
if (doc?.icon) {
vertex.icon = doc?.icon;
if (!doc.label?.trim() && vertex.text === id) {
vertex.text = '';
}
}
if (doc?.form) {
vertex.form = doc?.form;
}
if (doc?.pos) {
vertex.pos = doc?.pos;
}
if (doc?.img) {
vertex.img = doc?.img;
if (!doc.label?.trim() && vertex.text === id) {
vertex.text = '';
}
}
if (doc?.constraint) {
vertex.constraint = doc.constraint;
}
if (doc.w) {
vertex.assetWidth = Number(doc.w);
}
if (doc.h) {
vertex.assetHeight = Number(doc.h);
}
}
};
/**
@@ -760,6 +817,21 @@ export const lex = {
};
const getTypeFromVertex = (vertex: FlowVertex) => {
if (vertex.img) {
return 'imageSquare';
}
if (vertex.icon) {
if (vertex.form === 'circle') {
return 'iconCircle';
}
if (vertex.form === 'square') {
return 'iconSquare';
}
if (vertex.form === 'rounded') {
return 'iconRounded';
}
return 'icon';
}
if (vertex.type === 'square') {
return 'squareRect';
}
@@ -825,6 +897,12 @@ const addNodeFromVertex = (
link: vertex.link,
linkTarget: vertex.linkTarget,
tooltip: getTooltip(vertex.id),
icon: vertex.icon,
pos: vertex.pos,
img: vertex.img,
assetWidth: vertex.assetWidth,
assetHeight: vertex.assetHeight,
constraint: vertex.constraint,
});
}
};

View File

@@ -0,0 +1,278 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { setConfig } from '../../../config.js';
setConfig({
securityLevel: 'strict',
});
describe('when parsing directions', function () {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy.clear();
flow.parser.yy.setGen('gen-2');
});
it('should handle basic shape data statements', function () {
const res = flow.parser.parse(`flowchart TB
D@{ shape: rounded}`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
});
it('should handle basic shape data statements', function () {
const res = flow.parser.parse(`flowchart TB
D@{ shape: rounded }`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
});
it('should handle basic shape data statements with &', function () {
const res = flow.parser.parse(`flowchart TB
D@{ shape: rounded } & E`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(2);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should handle shape data statements with edges', function () {
const res = flow.parser.parse(`flowchart TB
D@{ shape: rounded } --> E`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(2);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should handle basic shape data statements with amp and edges 1', function () {
const res = flow.parser.parse(`flowchart TB
D@{ shape: rounded } & E --> F`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(3);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should handle basic shape data statements with amp and edges 2', function () {
const res = flow.parser.parse(`flowchart TB
D@{ shape: rounded } & E@{ shape: rounded } --> F`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(3);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should handle basic shape data statements with amp and edges 3', function () {
const res = flow.parser.parse(`flowchart TB
D@{ shape: rounded } & E@{ shape: rounded } --> F & G@{ shape: rounded }`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(4);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should handle basic shape data statements with amp and edges 4', function () {
const res = flow.parser.parse(`flowchart TB
D@{ shape: rounded } & E@{ shape: rounded } --> F@{ shape: rounded } & G@{ shape: rounded }`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(4);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should handle basic shape data statements with amp and edges 5, trailing space', function () {
const res = flow.parser.parse(`flowchart TB
D@{ shape: rounded } & E@{ shape: rounded } --> F{ shape: rounded } & G{ shape: rounded } `);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(4);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should no matter of there are no leading spaces', function () {
const res = flow.parser.parse(`flowchart TB
D@{shape: rounded}`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
});
it('should no matter of there are many leading spaces', function () {
const res = flow.parser.parse(`flowchart TB
D@{ shape: rounded}`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
});
it('should be forgiving with many spaces before teh end', function () {
const res = flow.parser.parse(`flowchart TB
D@{ shape: rounded }`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
});
it('should be possible to add multiple properties on the same line', function () {
const res = flow.parser.parse(`flowchart TB
D@{ shape: rounded , label: "DD"}`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('DD');
});
it('should be possible to link to a node with more data', function () {
const res = flow.parser.parse(`flowchart TB
A --> D@{
shape: circle
other: "clock"
}
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(2);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('A');
expect(data4Layout.nodes[1].label).toEqual('D');
expect(data4Layout.nodes[1].shape).toEqual('circle');
expect(data4Layout.edges.length).toBe(1);
});
it('should not disturb adding multiple nodes after each other', function () {
const res = flow.parser.parse(`flowchart TB
A[hello]
B@{
shape: circle
other: "clock"
}
C[Hello]@{
shape: circle
other: "clock"
}
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(3);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('hello');
expect(data4Layout.nodes[1].shape).toEqual('circle');
expect(data4Layout.nodes[1].label).toEqual('B');
expect(data4Layout.nodes[2].shape).toEqual('circle');
expect(data4Layout.nodes[2].label).toEqual('Hello');
});
it('should use handle bracket end (}) character inside the shape data', function () {
const res = flow.parser.parse(`flowchart TB
A@{
label: "This is }"
other: "clock"
}
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is }');
});
it('Diamond shapes should work as usual', function () {
const res = flow.parser.parse(`flowchart TB
A{This is a label}
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('diamond');
expect(data4Layout.nodes[0].label).toEqual('This is a label');
});
it('Multi line strings should be supported', function () {
const res = flow.parser.parse(`flowchart TB
A@{
label: |
This is a
multiline string
other: "clock"
}
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a\nmultiline string\n');
});
it('Multi line strings should be supported', function () {
const res = flow.parser.parse(`flowchart TB
A@{
label: "This is a
multiline string"
other: "clock"
}
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a<br/>multiline string');
});
it(' should be possible to use } in strings', function () {
const res = flow.parser.parse(`flowchart TB
A@{
label: "This is a string with }"
other: "clock"
}
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a string with }');
});
it(' should be possible to use @ in strings', function () {
const res = flow.parser.parse(`flowchart TB
A@{
label: "This is a string with @"
other: "clock"
}
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a string with @');
});
it(' should be possible to use @ in strings', function () {
const res = flow.parser.parse(`flowchart TB
A@{
label: "This is a string with}"
other: "clock"
}
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a string with}');
});
});

View File

@@ -23,6 +23,9 @@
%x href
%x callbackname
%x callbackargs
%x shapeData
%x shapeDataStr
%x shapeDataEndBracket
%%
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
@@ -34,6 +37,32 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multilin
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
// <acc_descr_multiline>.*[^\n]* { return "acc_descr_line"}
\@\{ {
// console.log('=> shapeData', yytext);
this.pushState("shapeData"); yytext=""; return 'SHAPE_DATA' }
<shapeData>["] {
// console.log('=> shapeDataStr', yytext);
this.pushState("shapeDataStr");
return 'SHAPE_DATA';
}
<shapeDataStr>["] {
// console.log('shapeData <==', yytext);
this.popState(); return 'SHAPE_DATA'}
<shapeDataStr>[^\"]+ {
// console.log('shapeData', yytext);
const re = /\n\s*/g;
yytext = yytext.replace(re,"<br/>");
return 'SHAPE_DATA'}
<shapeData>[^}^"]+ {
// console.log('shapeData', yytext);
return 'SHAPE_DATA';
}
<shapeData>"}" {
// console.log('<== root', yytext)
this.popState();
}
/*
---interactivity command---
'call' adds a callback to the specified node. 'call' can only be specified when
@@ -49,10 +78,11 @@ Function arguments are optional: 'call <callbackname>()' simply executes 'callba
<callbackargs>\) this.popState();
<callbackargs>[^)]* return 'CALLBACKARGS';
<md_string>[^`"]+ { return "MD_STR";}
<md_string>[`]["] { this.popState();}
<*>["][`] { this.begin("md_string");}
<string>[^"]+ return "STR";
<string>[^"]+ { return "STR"; }
<string>["] this.popState();
<*>["] this.pushState("string");
"style" return 'STYLE';
@@ -62,6 +92,8 @@ Function arguments are optional: 'call <callbackname>()' simply executes 'callba
"classDef" return 'CLASSDEF';
"class" return 'CLASS';
/*
---interactivity command---
'href' adds a link to the specified node. 'href' can only be specified when the
@@ -356,23 +388,38 @@ statement
separator: NEWLINE | SEMI | EOF ;
shapeData:
shapeData SHAPE_DATA
{ $$ = $1 + $2; }
| SHAPE_DATA
{ $$ = $1; }
;
vertexStatement: vertexStatement link node
{ /* console.warn('vs',$vertexStatement.stmt,$node); */ yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } }
vertexStatement: vertexStatement link node shapeData
{ /* console.warn('vs shapeData',$vertexStatement.stmt,$node, $shapeData);*/ yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } }
| vertexStatement link node
{ /*console.warn('vs',$vertexStatement.stmt,$node);*/ yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } }
| vertexStatement link node spaceList
{ /* console.warn('vs',$vertexStatement.stmt,$node); */ yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } }
|node spaceList {/*console.warn('noda', $node);*/ $$ = {stmt: $node, nodes:$node }}
|node { /*console.warn('noda', $node);*/ $$ = {stmt: $node, nodes:$node }}
|node spaceList { /*console.warn('vertexStatement: node spaceList', $node);*/ $$ = {stmt: $node, nodes:$node }}
|node shapeData {
/*console.warn('vertexStatement: node shapeData', $node[0], $shapeData);*/
yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData);
$$ = {stmt: $node, nodes:$node, shapeData: $shapeData}
}
|node { /* console.warn('vertexStatement: single node', $node); */ $$ = {stmt: $node, nodes:$node }}
;
node: styledVertex
{ /* console.warn('nod', $styledVertex); */ $$ = [$styledVertex];}
{ /*console.warn('nod', $styledVertex);*/ $$ = [$styledVertex];}
| node shapeData spaceList AMP spaceList styledVertex
{ yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); $$ = $node.concat($styledVertex); /*console.warn('pip2', $node[0], $styledVertex, $$);*/ }
| node spaceList AMP spaceList styledVertex
{ $$ = $node.concat($styledVertex); /* console.warn('pip', $node[0], $styledVertex, $$); */ }
{ $$ = $node.concat($styledVertex); /*console.warn('pip', $node[0], $styledVertex, $$);*/ }
;
styledVertex: vertex
{ /* console.warn('nod', $vertex); */ $$ = $vertex;}
{ /* console.warn('nodc', $vertex);*/ $$ = $vertex;}
| vertex STYLE_SEPARATOR idString
{$$ = $vertex;yy.setClass($vertex,$idString)}
;

View File

@@ -59,7 +59,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
stroke: ${options.nodeBorder};
stroke-width: 1px;
}
.rough-node .label text , .node .label text {
.rough-node .label text , .node .label text, .image-shape .label, .icon-shape .label {
text-anchor: middle;
}
// .flowchart-label .text-outer-tspan {
@@ -75,13 +75,20 @@ const getStyles = (options: FlowChartStyleOptions) =>
stroke-width: 1px;
}
.node .label {
.rough-node .label,.node .label, .image-shape .label, .icon-shape .label {
text-align: center;
}
.node.clickable {
cursor: pointer;
}
.root .anchor path {
fill: ${options.lineColor} !important;
stroke-width: 0;
stroke: ${options.lineColor};
}
.arrowheadPath {
fill: ${options.arrowheadColor};
}
@@ -151,6 +158,25 @@ const getStyles = (options: FlowChartStyleOptions) =>
font-size: 18px;
fill: ${options.textColor};
}
rect.text {
fill: none;
stroke-width: 0;
}
.icon-shape, .image-shape {
background-color: ${options.edgeLabelBackground};
p {
background-color: ${options.edgeLabelBackground};
padding: 2px;
}
rect {
opacity: 0.5;
background-color: ${options.edgeLabelBackground};
fill: ${options.edgeLabelBackground};
}
text-align: center;
}
`;
export default getStyles;

View File

@@ -11,6 +11,15 @@ export interface FlowVertex {
styles: string[];
text?: string;
type?: string;
icon?: string;
form?: string;
pos?: 't' | 'b';
img?: string;
assetWidth?: number;
assetHeight?: number;
defaultWidth?: number;
imageAspectRatio?: number;
constraint?: 'on' | 'off';
}
export interface FlowText {

View File

@@ -1,11 +1,11 @@
<template>
<div class="-mt-6 mb-8">
<a
href="https://www.producthunt.com/products/mermaid-chart?utm_source=badge-follow&utm_medium=badge&utm_souce=badge-mermaid&#0045;chart"
href="https://www.producthunt.com/posts/mermaid-whiteboard?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;whiteboard"
target="_blank"
><img
src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=552855&theme=light"
alt="Mermaid&#0032;Chart - A&#0032;smarter&#0032;way&#0032;to&#0032;create&#0032;diagrams | Product Hunt"
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=486720&theme=light"
alt="Mermaid&#0032;Whiteboard - Drag&#0032;&#0038;&#0032;Drop&#0032;your&#0032;Nodes&#0032;with&#0032;Mermaid&#0039;s&#0032;new&#0032;Whiteboard&#0033; | Product Hunt"
style="width: 250px; height: 54px"
width="250"
height="54"

View File

@@ -174,6 +174,7 @@ function sidebarConfig() {
{ text: 'API-Usage', link: '/config/usage' },
{ text: 'Mermaid API Configuration', link: '/config/setup/README' },
{ text: 'Mermaid Configuration Options', link: '/config/schema-docs/config' },
{ text: 'Registering icons', link: '/config/icons' },
{ text: 'Directives', link: '/config/directives' },
{ text: 'Theming', link: '/config/theming' },
{ text: 'Math', link: '/config/math' },

View File

@@ -0,0 +1,225 @@
# Custom SVG Shapes Library
This library provides a collection of custom SVG shapes, utilities, and helpers for generating diagram components. The shapes are designed to be used within an SVG container and include a variety of common and complex shapes.
## Overview
## Shape Helpers and Utilities
Before starting with shape creation, it's essential to familiarize yourself with the utilities provided in the `utils.ts` file from `packages/mermaid/src/rendering-util/rendering-elements/shapes/util.js`. These utilities are designed to assist with various aspects of SVG shape manipulation and ensure consistent and accurate rendering.
## Available Utilities
### 1. `labelHelper`
- **Purpose**: This function creates and inserts labels inside SVG shapes.
- **Features**:
- Handles both HTML labels and plain text.
- Calculates the bounding box dimensions of the label.
- Ensures proper positioning of labels within shapes.
### 2. `updateNodeBounds`
- **Purpose**: Updates the bounding box dimensions (width and height) of a node.
- **Usage**:
- Adjusts the size of the node to fit the content or shape.
- Useful for ensuring that shapes resize appropriately based on their content.
### 3. `insertPolygonShape`
- **Purpose**: Inserts a polygon shape into an SVG container.
- **Features**:
- Handles the creation and insertion of complex polygonal shapes.
- Configures the shape's appearance and positioning within the SVG container.
### 4. `getNodeClasses`
- **Purpose**: Returns the appropriate CSS classes for a node based on its configuration.
- **Usage**:
- Dynamically applies CSS classes to nodes for styling purposes.
- Ensures that nodes adhere to the desired design and theme.
### 5. `createPathFromPoints`
- **Purpose**: Generates an SVG path string from an array of points.
- **Usage**:
- Converts a list of points into a smooth path.
- Useful for creating custom shapes or paths within the SVG.
### 6. `generateFullSineWavePoints`
- **Purpose**: Generates points for a sine wave, useful for creating wavy-edged shapes.
- **Usage**:
- Facilitates the creation of shapes with wavy or sine-wave edges.
- Can be used to add decorative or dynamic edges to shapes.
## Getting Started
To utilize these utilities, simply import them from the `utils.ts` file into your shape creation script. These helpers will streamline the process of building and customizing SVG shapes, ensuring consistent results across your projects.
```typescript
import {
labelHelper,
updateNodeBounds,
insertPolygonShape,
getNodeClasses,
createPathFromPoints,
generateFullSineWavePoints,
} from './utils.ts';
```
## Example Usage
Heres a basic example of how you might use some of these utilities:
```typescript
import { labelHelper, insertPolygonShape } from './utils.ts';
const svgContainer = document.getElementById('svgContainer');
// Insert a polygon shape
insertPolygonShape(svgContainer /* shape-specific parameters */);
// Create and insert a label inside the shape
labelHelper(svgContainer /* label-specific parameters */);
```
## Adding New Shapes
### 1. Create the Shape Function
To add a new shape:
- **Create the shape function**: Create a new file of name of the shape and export a function in the `shapes` directory that generates your shape. The file and function should follow the pattern used in existing shapes and return an SVG element.
- **Example**:
```typescript
import { Node, RenderOptions } from '../../types.ts';
export const myNewShape = async (
parent: SVGAElement,
node: Node,
renderOptions: RenderOptions
) => {
// Create your shape here
const shape = parent.insert('g').attr('class', 'my-new-shape');
// Add other elements or styles as needed
return shape;
};
```
### 2. Register the Shape
- **Register the shape**: Add your shape to the `shapes` object in the [main shapes module](../rendering-util/rendering-elements/shapes.ts). This allows your shape to be recognized and used within the system.
- **Example**:
```typescript
import { myNewShape } from './shapes/myNewShape';
const shapes = {
...,
{
semanticName: 'My Shape',
name: 'Shape Name',
shortName: '<short-name>',
description: '<Description for the shape>',
aliases: ['<alias-one>', '<al-on>', '<alias-two>', '<al-two>'],
handler: myNewShape,
},
};
```
# Shape Intersection Algorithms
This contains algorithms and utilities for calculating intersection points for various shapes in SVG. Arrow intersection points are crucial for accurately determining where arrows connect with shapes. Ensuring precise intersection points enhances the clarity and accuracy of flowcharts and diagrams.
## Shape Intersection Functions
### 1. `Ellipse`
Calculates the intersection points for an ellipse.
**Usage**:
```javascript
import intersectEllipse from './intersect-ellipse.js';
const intersection = intersectEllipse(node, rx, ry, point);
```
- **Parameters**:
- `node`: The SVG node element.
- `rx`: The x-radius of the ellipse.
- `ry`: The y-radius of the ellipse.
- `point`: The point from which the intersection is calculated.
### 2. `intersectRect`
Calculates the intersection points for a rectangle.
**Usage**:
```javascript
import intersectRect from './intersect-rect.js';
const intersection = intersectRect(node, point);
```
- **Parameters**:
- `node`: The SVG node element.
- `point`: The point from which the intersection is calculated.
### 3. `intersectPolygon`
Calculates the intersection points for a polygon.
**Usage**:
```javascript
import intersectPolygon from './intersect-polygon.js';
const intersection = intersectPolygon(node, polyPoints, point);
```
- **Parameters**:
- `node`: The SVG node element.
- `polyPoints`: Array of points defining the polygon.
- `point`: The point from which the intersection is calculated.
## Cypress Tests
To ensure the robustness of the flowchart shapes, there are implementation of comprehensive Cypress test cases in `newShapes.spec.ts` file. These tests cover various aspects such as:
- **Shapes**: Testing new shapes like `bowTieRect`, `waveRectangle`, `trapezoidalPentagon`, etc.
- **Looks**: Verifying shapes under different visual styles (`classic` and `handDrawn`).
- **Directions**: Ensuring correct rendering in all flow directions of arrows :
- `TB` `(Top -> Bottom)`
- `BT` `(Bottom -> Top)`
- `LR` `(Left -> Right)`
- `RL` `(Right -> Left)`
- **Labels**: Testing shapes with different labels, including:
- No labels
- Short labels
- Very long labels
- Markdown with `htmlLabels:true` and `htmlLabels:false`
- **Styles**: Applying custom styles to shapes and verifying correct rendering.
- **Class Definitions**: Using `classDef` to apply custom classes and testing their impact.
### Running the Tests
To run the Cypress tests, follow these steps:
1. Ensure you have all dependencies installed by running:
```bash
pnpm install
```
2. Start the Cypress test runner:
```bash
cypress open --env updateSnapshots=true
```
3. Select the test suite from the Cypress interface to run all the flowchart shape tests.

View File

@@ -0,0 +1,49 @@
# Registering icon pack in mermaid
The icon packs available can be found at [icones.js.org](https://icones.js.org/).
We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram.
Using JSON file directly from CDN:
```js
import mermaid from 'CDN/mermaid.esm.mjs';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()),
},
]);
```
Using packages and a bundler:
```bash
npm install @iconify-json/logos@1
```
With lazy loading
```js
import mermaid from 'mermaid';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () => import('@iconify-json/logos').then((module) => module.icons),
},
]);
```
Without lazy loading
```js
import mermaid from 'mermaid';
import { icons } from '@iconify-json/logos';
mermaid.registerIconPacks([
{
name: icons.prefix, // To use the prefix defined in the icon pack
icons,
},
]);
```

View File

@@ -59,7 +59,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
- [Mermaid Flow Visual Editor](https://www.mermaidflow.app) ✅
- [Mermerd](https://github.com/KarnerTh/mermerd)
- [Slab](https://slab.com) ✅
- [Swimm](https://docs.swimm.io/features/diagrams-and-charts/#mermaid--swimm--up-to-date-diagrams-) ✅
- [Swimm](https://docs.swimm.io/features/diagrams-and-charts) ✅
- [NotesHub](https://noteshub.app) ✅
- [Notion](https://notion.so) ✅
- [Observable](https://observablehq.com/@observablehq/mermaid) ✅

View File

@@ -6,7 +6,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
<br />
<a href="https://www.producthunt.com/products/mermaid-chart?utm_source=badge-follow&utm_medium=badge&utm_souce=badge-mermaid&#0045;chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=552855&theme=light" alt="Mermaid&#0032;Chart - A&#0032;smarter&#0032;way&#0032;to&#0032;create&#0032;diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://www.producthunt.com/posts/mermaid-whiteboard?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;whiteboard" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=486720&theme=light" alt="Mermaid&#0032;Whiteboard - Drag&#0032;&#0038;&#0032;Drop&#0032;your&#0032;Nodes&#0032;with&#0032;Mermaid&#0039;s&#0032;new&#0032;Whiteboard&#0033; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## About
@@ -16,17 +16,20 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
- **Editor** - A web based editor for creating and editing Mermaid diagrams.
- **Visual Editor** - The Visual Editor enables users of all skill levels to create diagrams easily and efficiently, with both GUI and code-based editing options.
- **Mermaid AI** - Use our embedded AI Chat to generate diagrams from natural language descriptions.
- **AI Chat** - Use our embedded AI Chat to generate diagrams from natural language descriptions.
- **Whiteboard** - A virtual whiteboard for creating and editing Mermaid diagrams.
- **Plugins** - A plugin system for extending the functionality of Mermaid.
Official Mermaid Chart plugins:
- [Mermaid Chart GPT](https://chat.openai.com/g/g-1IRFKwq4G-mermaid-chart)
- [Confluence](https://marketplace.atlassian.com/apps/1234056/mermaid-chart-for-confluence?hosting=cloud&tab=overview)
- [Jira](https://marketplace.atlassian.com/apps/1234810/mermaid-chart-for-jira?tab=overview&hosting=cloud)
- [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=MermaidChart.vscode-mermaid-chart)
- [JetBrains IDE](https://plugins.jetbrains.com/plugin/23043-mermaid-chart)
- [Google Docs](https://gsuite.google.com/marketplace/app/mermaidchart/947683068472)
- [Microsoft PowerPoint and Word](https://appsource.microsoft.com/en-us/product/office/WA200006214?tab=Overview)
Visit our [Plugins](https://www.mermaidchart.com/plugins) page for more information.

View File

@@ -1,5 +1,11 @@
# Blog
## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/)
24 September 2024 · 5 mins
Discover 30 new shapes in Mermaid flowcharts, offering enhanced clarity, customization, and versatility for more dynamic and expressive visualizations.
## [Introducing Architecture Diagrams in Mermaid](https://www.mermaidchart.com/blog/posts/mermaid-supports-architecture-diagrams/)
2 September 2024 · 2 mins

View File

@@ -59,15 +59,15 @@ service {service id}({icon name})[{title}] (in {parent id})?
Put together:
```
service database(db)[Database]
service database1(database)[My Database]
```
creates the service identified as `database`, using the icon `db`, with the label `Database`.
creates the service identified as `database1`, using the icon `database`, with the label `My Database`.
If the service belongs to a group, it can be placed inside it through the optional `in` keyword
```
service database(db)[Database] in private_api
service database1(database)[My Database] in private_api
```
### Edges
@@ -156,55 +156,7 @@ architecture-beta
## Icons
By default, architecture diagram supports the following icons: `cloud`, `database`, `disk`, `internet`, `server`.
Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps below.
The icon packs available can be found at [icones.js.org](https://icones.js.org/).
We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram.
Using JSON file directly from CDN:
```js
import mermaid from 'CDN/mermaid.esm.mjs';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
},
]);
```
Using packages and a bundler:
```bash
npm install @iconify-json/logos
```
With lazy loading
```js
import mermaid from 'mermaid';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () => import('@iconify-json/logos').then((module) => module.icons),
},
]);
```
Without lazy loading
```js
import mermaid from 'mermaid';
import { icons } from '@iconify-json/logos';
mermaid.registerIconPacks([
{
name: icons.prefix, // To use the prefix defined in the icon pack
icons,
},
]);
```
Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps [here](../config/icons.md).
After the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack.

View File

@@ -153,6 +153,19 @@ block-beta
This example demonstrates how Mermaid dynamically adjusts the width of the columns to accommodate the widest block, in this case, 'a' and the composite block 'e'. This dynamic adjustment is essential for creating visually balanced and easy-to-understand diagrams.
**Merging Blocks Horizontally:**
In scenarios where you need to stack blocks horizontally, you can use column width to accomplish the task. Blocks can be arranged vertically by putting them in a single column. Here is how you can create a block diagram in which 4 blocks are stacked on top of each other:
```mermaid-example
block-beta
block
columns 1
a["A label"] b c d
end
```
In this example, the width of the merged block dynamically adjusts to the width of the largest child block.
With these advanced configuration options, Mermaid's block diagrams can be tailored to represent a wide array of complex systems and structures. The flexibility offered by these features enables users to create diagrams that are both informative and visually appealing. In the following sections, we will explore further capabilities, including different block shapes and linking options.
## 4. Block Varieties and Shapes

View File

@@ -194,6 +194,404 @@ flowchart TD
id1(((This is the text in the circle)))
```
## Expanded Node Shapes in Mermaid Flowcharts (v11.3.0+)
Mermaid introduces 30 new shapes to enhance the flexibility and precision of flowchart creation. These new shapes provide more options to represent processes, decisions, events, data storage visually, and other elements within your flowcharts, improving clarity and semantic meaning.
New Syntax for Shape Definition
Mermaid now supports a general syntax for defining shape types to accommodate the growing number of shapes. This syntax allows you to assign specific shapes to nodes using a clear and flexible format:
```
A@{ shape: rect }
```
This syntax creates a node A as a rectangle. It renders in the same way as `A["A"]`, or `A`.
### Complete List of New Shapes
Below is a comprehensive list of the newly introduced shapes and their corresponding semantic meanings, short names, and aliases:
<!--@include: virtual:shapesTable -->
### Example Flowchart with New Shapes
Heres an example flowchart that utilizes some of the newly introduced shapes:
```mermaid-example
flowchart RL
A@{ shape: manual-file, label: "File Handling"}
B@{ shape: manual-input, label: "User Input"}
C@{ shape: docs, label: "Multiple Documents"}
D@{ shape: procs, label: "Process Automation"}
E@{ shape: paper-tape, label: "Paper Records"}
```
### Process
```mermaid-example
flowchart TD
A@{ shape: rect, label: "This is a process" }
```
### Event
```mermaid-example
flowchart TD
A@{ shape: rounded, label: "This is an event" }
```
### Terminal Point (Stadium)
```mermaid-example
flowchart TD
A@{ shape: stadium, label: "Terminal point" }
```
### Subprocess
```mermaid-example
flowchart TD
A@{ shape: subproc, label: "This is a subprocess" }
```
### Database (Cylinder)
```mermaid-example
flowchart TD
A@{ shape: cyl, label: "Database" }
```
### Start (Circle)
```mermaid-example
flowchart TD
A@{ shape: circle, label: "Start" }
```
### Odd
```mermaid-example
flowchart TD
A@{ shape: odd, label: "Odd shape" }
```
### Decision (Diamond)
```mermaid-example
flowchart TD
A@{ shape: diamond, label: "Decision" }
```
### Prepare Conditional (Hexagon)
```mermaid-example
flowchart TD
A@{ shape: hex, label: "Prepare conditional" }
```
### Data Input/Output (Lean Right)
```mermaid-example
flowchart TD
A@{ shape: lean-r, label: "Input/Output" }
```
### Data Input/Output (Lean Left)
```mermaid-example
flowchart TD
A@{ shape: lean-l, label: "Output/Input" }
```
### Priority Action (Trapezoid Base Bottom)
```mermaid-example
flowchart TD
A@{ shape: trap-b, label: "Priority action" }
```
### Manual Operation (Trapezoid Base Top)
```mermaid-example
flowchart TD
A@{ shape: trap-t, label: "Manual operation" }
```
### Stop (Double Circle)
```mermaid-example
flowchart TD
A@{ shape: dbl-circ, label: "Stop" }
```
### Text Block
```mermaid-example
flowchart TD
A@{ shape: text, label: "This is a text block" }
```
### Card (Notched Rectangle)
```mermaid-example
flowchart TD
A@{ shape: notch-rect, label: "Card" }
```
### Lined/Shaded Process
```mermaid-example
flowchart TD
A@{ shape: lin-rect, label: "Lined process" }
```
### Start (Small Circle)
```mermaid-example
flowchart TD
A@{ shape: sm-circ, label: "Small start" }
```
### Stop (Framed Circle)
```mermaid-example
flowchart TD
A@{ shape: framed-circle, label: "Stop" }
```
### Fork/Join (Long Rectangle)
```mermaid-example
flowchart TD
A@{ shape: fork, label: "Fork or Join" }
```
### Collate (Hourglass)
```mermaid-example
flowchart TD
A@{ shape: hourglass, label: "Collate" }
```
### Comment (Curly Brace)
```mermaid-example
flowchart TD
A@{ shape: comment, label: "Comment" }
```
### Comment Right (Curly Brace Right)
```mermaid-example
flowchart TD
A@{ shape: brace-r, label: "Comment" }
```
### Comment with braces on both sides
```mermaid-example
flowchart TD
A@{ shape: braces, label: "Comment" }
```
### Com Link (Lightning Bolt)
```mermaid-example
flowchart TD
A@{ shape: bolt, label: "Communication link" }
```
### Document
```mermaid-example
flowchart TD
A@{ shape: doc, label: "Document" }
```
### Delay (Half-Rounded Rectangle)
```mermaid-example
flowchart TD
A@{ shape: delay, label: "Delay" }
```
### Direct Access Storage (Horizontal Cylinder)
```mermaid-example
flowchart TD
A@{ shape: das, label: "Direct access storage" }
```
### Disk Storage (Lined Cylinder)
```mermaid-example
flowchart TD
A@{ shape: lin-cyl, label: "Disk storage" }
```
### Display (Curved Trapezoid)
```mermaid-example
flowchart TD
A@{ shape: curv-trap, label: "Display" }
```
### Divided Process (Divided Rectangle)
```mermaid-example
flowchart TD
A@{ shape: div-rect, label: "Divided process" }
```
### Extract (Small Triangle)
```mermaid-example
flowchart TD
A@{ shape: tri, label: "Extract" }
```
### Internal Storage (Window Pane)
```mermaid-example
flowchart TD
A@{ shape: win-pane, label: "Internal storage" }
```
### Junction (Filled Circle)
```mermaid-example
flowchart TD
A@{ shape: f-circ, label: "Junction" }
```
### Lined Document
```mermaid-example
flowchart TD
A@{ shape: lin-doc, label: "Lined document" }
```
### Loop Limit (Notched Pentagon)
```mermaid-example
flowchart TD
A@{ shape: notch-pent, label: "Loop limit" }
```
### Manual File (Flipped Triangle)
```mermaid-example
flowchart TD
A@{ shape: flip-tri, label: "Manual file" }
```
### Manual Input (Sloped Rectangle)
```mermaid-example
flowchart TD
A@{ shape: sl-rect, label: "Manual input" }
```
### Multi-Document (Stacked Document)
```mermaid-example
flowchart TD
A@{ shape: docs, label: "Multiple documents" }
```
### Multi-Process (Stacked Rectangle)
```mermaid-example
flowchart TD
A@{ shape: processes, label: "Multiple processes" }
```
### Paper Tape (Flag)
```mermaid-example
flowchart TD
A@{ shape: flag, label: "Paper tape" }
```
### Stored Data (Bow Tie Rectangle)
```mermaid-example
flowchart TD
A@{ shape: bow-rect, label: "Stored data" }
```
### Summary (Crossed Circle)
```mermaid-example
flowchart TD
A@{ shape: cross-circ, label: "Summary" }
```
### Tagged Document
```mermaid-example
flowchart TD
A@{ shape: tag-doc, label: "Tagged document" }
```
### Tagged Process (Tagged Rectangle)
```mermaid-example
flowchart TD
A@{ shape: tag-rect, label: "Tagged process" }
```
## Special shapes in Mermaid Flowcharts (v11.3.0+)
Mermaid also introduces 2 special shapes to enhance your flowcharts: **icon** and **image**. These shapes allow you to include icons and images directly within your flowcharts, providing more visual context and clarity.
### Icon Shape
You can use the `icon` shape to include an icon in your flowchart. To use icons, you need to register the icon pack first. Follow the instructions provided [here](../config/icons.md). The syntax for defining an icon shape is as follows:
```mermaid-example
flowchart TD
A@{ icon: "fa:user", form: "square", label: "User Icon", pos: "t", h: 60 }
```
### Parameters
- **icon**: The name of the icon from the registered icon pack.
- **form**: Specifies the background shape of the icon. If not defined there will be no background to icon. Options include:
- `square`
- `circle`
- `rounded`
- **label**: The text label associated with the icon. This can be any string. If not defined, no label will be displayed.
- **pos**: The position of the label. If not defined label will default to bottom of icon. Possible values are:
- `t`
- `b`
- **h**: The height of the icon. If not defined this will default to 48 which is minimum.
### Image Shape
You can use the `image` shape to include an image in your flowchart. The syntax for defining an image shape is as follows:
```mermaid-example
flowchart TD
A@{ img: "https://example.com/image.png", label: "Image Label", pos: "t", w: 60, h: 60, constraint: "off" }
```
### Parameters
- **img**: The URL of the image to be displayed.
- **label**: The text label associated with the image. This can be any string. If not defined, no label will be displayed.
- **pos**: The position of the label. If not defined, the label will default to the bottom of the image. Possible values are:
- `t`
- `b`
- **w**: The width of the image. If not defined, this will default to the natural width of the image.
- **h**: The height of the image. If not defined, this will default to the natural height of the image.
- **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the height (`h`) accordingly to the width (`w`). If not defined, this will default to `off` Possible values are:
- `on`
- `off`
These new shapes provide additional flexibility and visual appeal to your flowcharts, making them more informative and engaging.
## Links between nodes
Nodes can be connected with links/edges. It is possible to have different types of links or attach a text string to a link.

View File

@@ -105,6 +105,9 @@ end
box rgb(33,66,99)
... actors ...
end
box rgba(33,66,99,0.5)
... actors ...
end
```
```note
@@ -394,6 +397,12 @@ sequenceDiagram
It is possible to highlight flows by providing colored background rects. This is done by the notation
```
rect COLOR
... content ...
end
```
The colors are defined using rgb and rgba syntax.
```

View File

@@ -141,8 +141,8 @@ function createFormattedText(
const bbox = textElement.node().getBBox();
const padding = 2;
bkg
.attr('x', -padding)
.attr('y', -padding)
.attr('x', bbox.x - padding)
.attr('y', bbox.y - padding)
.attr('width', bbox.width + 2 * padding)
.attr('height', bbox.height + 2 * padding);

View File

@@ -1,5 +1,4 @@
import { select } from 'd3';
import { insertNode } from '../dagre-wrapper/nodes.js';
export const getDiagramElement = (id, securityLevel) => {
let sandboxElement;
@@ -17,36 +16,3 @@ export const getDiagramElement = (id, securityLevel) => {
return svg;
};
export function insertElementsForSize(el, data) {
const nodesElem = el.insert('g').attr('class', 'nodes');
el.insert('g').attr('class', 'edges');
data.nodes.forEach(async (item) => {
item.shape = 'rect';
await insertNode(nodesElem, {
...item,
class: 'default flowchart-label',
labelStyle: '',
x: 0,
y: 0,
width: 100,
rx: 0,
ry: 0,
height: 100,
shape: 'rect',
padding: 8,
});
// Create a new DOM element
// const element = document.createElement('div');
// // Set the content of the element to the name of the item
// element.textContent = item.name;
// // Set the size of the element to the size of the item
// element.style.width = `${item.size}px`;
// element.style.height = `${item.size}px`;
// Append the element to the body of the document
// document.body.appendChild(element);
});
}

View File

@@ -125,7 +125,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
// insertCluster(clusters, graph.node(v));
} else {
log.trace('Node - the non recursive path XAX', v, nodes, graph.node(v), dir);
await insertNode(nodes, graph.node(v), dir);
await insertNode(nodes, graph.node(v), { config: siteConfig, dir });
}
}
})

View File

@@ -1,59 +1,9 @@
import { log } from '../../logger.js';
import { state } from './shapes/state.ts';
import { roundedRect } from './shapes/roundedRect.ts';
import { squareRect } from './shapes/squareRect.ts';
import { stateStart } from './shapes/stateStart.ts';
import { stateEnd } from './shapes/stateEnd.ts';
import { forkJoin } from './shapes/forkJoin.ts';
import { choice } from './shapes/choice.ts';
import { note } from './shapes/note.ts';
import { stadium } from './shapes/stadium.js';
import { rectWithTitle } from './shapes/rectWithTitle.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { subroutine } from './shapes/subroutine.js';
import { cylinder } from './shapes/cylinder.js';
import { circle } from './shapes/circle.js';
import { doublecircle } from './shapes/doubleCircle.js';
import { rect_left_inv_arrow } from './shapes/rectLeftInvArrow.js';
import { question } from './shapes/question.js';
import { hexagon } from './shapes/hexagon.js';
import { lean_right } from './shapes/leanRight.js';
import { lean_left } from './shapes/leanLeft.js';
import { trapezoid } from './shapes/trapezoid.js';
import { inv_trapezoid } from './shapes/invertedTrapezoid.js';
import { labelRect } from './shapes/labelRect.js';
import { erBox } from './shapes/erBox.js';
const shapes = {
state,
stateStart,
stateEnd,
fork: forkJoin,
join: forkJoin,
choice,
note,
roundedRect,
rectWithTitle,
squareRect,
stadium,
subroutine,
cylinder,
circle,
doublecircle,
odd: rect_left_inv_arrow,
diamond: question,
hexagon,
lean_right,
lean_left,
trapezoid,
inv_trapezoid,
labelRect,
erBox,
};
import { shapes } from './shapes.js';
const nodeElems = new Map();
export const insertNode = async (elem, node, dir) => {
export const insertNode = async (elem, node, renderOptions) => {
let newEl;
let el;
@@ -66,18 +16,24 @@ export const insertNode = async (elem, node, dir) => {
}
}
// Add link when appropriate
const shapeHandler = shapes[node.shape];
if (!shapeHandler) {
throw new Error(`No such shape: ${node.shape}. Please check your syntax.`);
}
if (node.link) {
// Add link when appropriate
let target;
if (getConfig().securityLevel === 'sandbox') {
if (renderOptions.config.securityLevel === 'sandbox') {
target = '_top';
} else if (node.linkTarget) {
target = node.linkTarget || '_blank';
}
newEl = elem.insert('svg:a').attr('xlink:href', node.link).attr('target', target);
el = await shapes[node.shape](newEl, node, dir);
el = await shapeHandler(newEl, node, renderOptions);
} else {
el = await shapes[node.shape](elem, node, dir);
el = await shapeHandler(elem, node, renderOptions);
newEl = el;
}
if (node.tooltip) {
@@ -91,9 +47,11 @@ export const insertNode = async (elem, node, dir) => {
}
return newEl;
};
export const setNodeElem = (elem, node) => {
nodeElems.set(node.id, elem);
};
export const clear = () => {
nodeElems.clear();
};

View File

@@ -0,0 +1,236 @@
import { shapes } from './shapes.js';
import { describe, it, expect } from 'vitest';
describe('Test Alias for shapes', function () {
// for each shape in docs/syntax/flowchart.md, along with its semantic name, short name, and alias name, add a test case
// Process | rect | proc | rectangle
it('should support alias for rectangle shape ', function () {
expect(shapes.process).toBe(shapes.rect);
expect(shapes.proc).toBe(shapes.rect);
expect(shapes.rectangle).toBe(shapes.rect);
});
// event | rounded
it('should support alias for rounded shape ', function () {
expect(shapes.event).toBe(shapes.rounded);
});
// stadium | pill | term
it('should support alias for stadium shape ', function () {
expect(shapes.pill).toBe(shapes.stadium);
expect(shapes.terminal).toBe(shapes.stadium);
});
// fr-rect | subproc | framed-rectangle | subroutine
it('should support alias for subroutine shape ', function () {
expect(shapes['framed-rectangle']).toBe(shapes['fr-rect']);
expect(shapes.subproc).toBe(shapes['fr-rect']);
expect(shapes.subroutine).toBe(shapes['fr-rect']);
});
// cyl | db | cylinder
it('should support alias for cylinder shape ', function () {
expect(shapes.db).toBe(shapes.cylinder);
expect(shapes.cyl).toBe(shapes.cylinder);
});
// diam | decision | diamond
it('should support alias for diamond shape ', function () {
expect(shapes.diam).toBe(shapes.decision);
expect(shapes.diamond).toBe(shapes.decision);
});
// hex | hexagon | prepare
it('should support alias for hexagon shape ', function () {
expect(shapes.hex).toBe(shapes.hexagon);
expect(shapes.prepare).toBe(shapes.hexagon);
});
// l-r | lean-right | in-out
it('should support alias for lean-right shape ', function () {
expect(shapes['lean-r']).toBe(shapes['lean-right']);
expect(shapes['in-out']).toBe(shapes['lean-right']);
});
// l-l | lean-left | out-in
it('should support alias for lean-left shape ', function () {
expect(shapes['lean-l']).toBe(shapes['lean-left']);
expect(shapes['out-in']).toBe(shapes['lean-left']);
});
// trap-b | trapezoid-bottom | priority | trapezoid
it('should support alias for trapezoid shape ', function () {
expect(shapes['trapezoid-bottom']).toBe(shapes['trap-b']);
expect(shapes.priority).toBe(shapes['trap-b']);
expect(shapes.trapezoid).toBe(shapes['trap-b']);
});
// trap-t | trapezoid-top | manual | inv-trapezoid
it('should support alias for inv_trapezoid shape ', function () {
expect(shapes['trapezoid-top']).toBe(shapes['trap-t']);
expect(shapes.manual).toBe(shapes['trap-t']);
expect(shapes['inv-trapezoid']).toBe(shapes['trap-t']);
});
// dbl-circ| double-circle
it('should support alias for doublecircle shape ', function () {
expect(shapes['double-circle']).toBe(shapes['dbl-circ']);
});
// notched-rectangle | card | notch-rect
it('should support alias for notched-rectangle shape ', function () {
expect(shapes.card).toBe(shapes['notched-rectangle']);
expect(shapes['notch-rect']).toBe(shapes['notched-rectangle']);
});
it('should support alias for shadedProcess shape ', function () {
const aliases = ['lined-process', 'lined-rectangle', 'lin-proc', 'lin-rect'];
for (const alias of aliases) {
expect(shapes[alias]).toBe(shapes['shaded-process']);
}
});
// sm-circ | small-circle | start
it('should support alias for smallCircle shape ', function () {
expect(shapes['small-circle']).toBe(shapes['sm-circ']);
expect(shapes.start).toBe(shapes['sm-circ']);
});
// framed-circle | stop
it('should support alias for framed circle shape ', function () {
expect(shapes.stop).toBe(shapes['framed-circle']);
});
// fork | join
it('should support alias for fork shape ', function () {
expect(shapes.join).toBe(shapes.fork);
});
// brace | comment | brace-l
it('should support alias for brace shape ', function () {
expect(shapes.comment).toBe(shapes.brace);
expect(shapes['brace-l']).toBe(shapes.brace);
});
// bolt | com-link | lightning-bolt
it('should support alias for bolt shape ', function () {
expect(shapes['com-link']).toBe(shapes.bolt);
expect(shapes['lightning-bolt']).toBe(shapes.bolt);
});
// document | doc
it('should support alias for waveEdgedRectangle shape ', function () {
expect(shapes.doc).toBe(shapes.document);
});
// delay | half-rounded-rectangle
it('should support alias for halfRoundedRectangle shape ', function () {
expect(shapes.delay).toBe(shapes['half-rounded-rectangle']);
});
// h-cyl | das | horizontal-cylinder
it('should support alias for horizontal-cylinder shape ', function () {
expect(shapes.das).toBe(shapes['h-cyl']);
expect(shapes['horizontal-cylinder']).toBe(shapes['h-cyl']);
});
// lin-cyl | disk | lined-cylinder
it('should support alias for linedCylinder shape ', function () {
expect(shapes.disk).toBe(shapes['lin-cyl']);
expect(shapes['lined-cylinder']).toBe(shapes['lin-cyl']);
});
// curv-trap | display | curved-trapezoid
it('should support alias for curvedTrapezoid shape ', function () {
expect(shapes.display).toBe(shapes['curv-trap']);
expect(shapes['curved-trapezoid']).toBe(shapes['curv-trap']);
});
// div-rect | div-proc | divided-rectangle
it('should support alias for dividedRectangle shape ', function () {
expect(shapes['div-proc']).toBe(shapes['div-rect']);
expect(shapes['divided-rectangle']).toBe(shapes['div-rect']);
});
// sm-tri | extract | small-triangle | triangle
it('should support alias for smallTriangle shape ', function () {
expect(shapes.extract).toBe(shapes.tri);
expect(shapes.triangle).toBe(shapes.tri);
});
// win-pane | internal-storage | window-pane
it('should support alias for windowPane shape ', function () {
expect(shapes['internal-storage']).toBe(shapes['win-pane']);
expect(shapes['window-pane']).toBe(shapes['win-pane']);
});
// fc | junction | filled-circle
it('should support alias for filledCircle shape ', function () {
expect(shapes.junction).toBe(shapes['f-circ']);
expect(shapes['filled-circle']).toBe(shapes['f-circ']);
});
// | lin-doc | lined-document
it('should support alias for linedWaveEdgedRectangle shape ', function () {
expect(shapes['lin-doc']).toBe(shapes['lined-document']);
});
// notch-pent | loop-limit | notched-pentagon
it('should support alias for notchedPentagon shape ', function () {
expect(shapes['loop-limit']).toBe(shapes['notch-pent']);
expect(shapes['notched-pentagon']).toBe(shapes['notch-pent']);
});
// flip-tri | manual-file | flipped-triangle
it('should support alias for flippedTriangle shape ', function () {
expect(shapes['manual-file']).toBe(shapes['flip-tri']);
expect(shapes['flipped-triangle']).toBe(shapes['flip-tri']);
});
//sl-rect | manual-input | sloped-rectangle
it('should support alias for slopedRectangle shape ', function () {
expect(shapes['manual-input']).toBe(shapes['sl-rect']);
expect(shapes['sloped-rectangle']).toBe(shapes['sl-rect']);
});
// docs | documents | st-doc | stacked-document
it('should support alias for multiWaveEdgedRectangle shape ', function () {
expect(shapes.docs).toBe(shapes.documents);
expect(shapes['st-doc']).toBe(shapes['stacked-document']);
});
// procs | processes | st-rect | stacked-rectangle
it('should support alias for multiRect shape ', function () {
expect(shapes.procs).toBe(shapes.processes);
expect(shapes['st-rect']).toBe(shapes['stacked-rectangle']);
});
// flag | paper-tape
it('should support alias for paperTape shape ', function () {
expect(shapes['paper-tape']).toBe(shapes.flag);
});
// bow-rect| stored-data | bow-tie-rectangle
it('should support alias for bowTieRect shape ', function () {
expect(shapes['stored-data']).toBe(shapes['bow-rect']);
expect(shapes['bow-tie-rectangle']).toBe(shapes['bow-rect']);
});
// cross-circ | summary | crossed-circle
it('should support alias for crossedCircle shape ', function () {
expect(shapes.summary).toBe(shapes['cross-circ']);
expect(shapes['crossed-circle']).toBe(shapes['cross-circ']);
});
// tag-doc| tag-document
it('should support alias for taggedDocument shape ', function () {
expect(shapes['tag-doc']).toBe(shapes['tagged-document']);
});
// tag-rect | tag-proc | tagged-rectangle | tagged-process
it('should support alias for taggedRect shape ', function () {
expect(shapes['tag-proc']).toBe(shapes['tag-rect']);
expect(shapes['tagged-rectangle']).toBe(shapes['tag-rect']);
expect(shapes['tagged-process']).toBe(shapes['tag-rect']);
});
});

View File

@@ -0,0 +1,481 @@
import type { Node, ShapeRenderOptions } from '../types.js';
import { anchor } from './shapes/anchor.js';
import { bowTieRect } from './shapes/bowTieRect.js';
import { card } from './shapes/card.js';
import { choice } from './shapes/choice.js';
import { circle } from './shapes/circle.js';
import { crossedCircle } from './shapes/crossedCircle.js';
import { curlyBraceLeft } from './shapes/curlyBraceLeft.js';
import { curlyBraceRight } from './shapes/curlyBraceRight.js';
import { curlyBraces } from './shapes/curlyBraces.js';
import { curvedTrapezoid } from './shapes/curvedTrapezoid.js';
import { cylinder } from './shapes/cylinder.js';
import { dividedRectangle } from './shapes/dividedRect.js';
import { doublecircle } from './shapes/doubleCircle.js';
import { filledCircle } from './shapes/filledCircle.js';
import { flippedTriangle } from './shapes/flippedTriangle.js';
import { forkJoin } from './shapes/forkJoin.js';
import { halfRoundedRectangle } from './shapes/halfRoundedRectangle.js';
import { hexagon } from './shapes/hexagon.js';
import { hourglass } from './shapes/hourglass.js';
import { icon } from './shapes/icon.js';
import { iconCircle } from './shapes/iconCircle.js';
import { iconRounded } from './shapes/iconRounded.js';
import { iconSquare } from './shapes/iconSquare.js';
import { imageSquare } from './shapes/imageSquare.js';
import { inv_trapezoid } from './shapes/invertedTrapezoid.js';
import { labelRect } from './shapes/labelRect.js';
import { lean_left } from './shapes/leanLeft.js';
import { lean_right } from './shapes/leanRight.js';
import { lightningBolt } from './shapes/lightningBolt.js';
import { linedCylinder } from './shapes/linedCylinder.js';
import { linedWaveEdgedRect } from './shapes/linedWaveEdgedRect.js';
import { multiRect } from './shapes/multiRect.js';
import { multiWaveEdgedRectangle } from './shapes/multiWaveEdgedRectangle.js';
import { note } from './shapes/note.js';
import { question } from './shapes/question.js';
import { rect_left_inv_arrow } from './shapes/rectLeftInvArrow.js';
import { rectWithTitle } from './shapes/rectWithTitle.js';
import { roundedRect } from './shapes/roundedRect.js';
import { shadedProcess } from './shapes/shadedProcess.js';
import { slopedRect } from './shapes/slopedRect.js';
import { squareRect } from './shapes/squareRect.js';
import { stadium } from './shapes/stadium.js';
import { state } from './shapes/state.js';
import { stateEnd } from './shapes/stateEnd.js';
import { stateStart } from './shapes/stateStart.js';
import { subroutine } from './shapes/subroutine.js';
import { taggedRect } from './shapes/taggedRect.js';
import { taggedWaveEdgedRectangle } from './shapes/taggedWaveEdgedRectangle.js';
import { text } from './shapes/text.js';
import { tiltedCylinder } from './shapes/tiltedCylinder.js';
import { trapezoid } from './shapes/trapezoid.js';
import { trapezoidalPentagon } from './shapes/trapezoidalPentagon.js';
import { triangle } from './shapes/triangle.js';
import { waveEdgedRectangle } from './shapes/waveEdgedRectangle.js';
import { waveRectangle } from './shapes/waveRectangle.js';
import { windowPane } from './shapes/windowPane.js';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ShapeHandler = (parent: any, node: Node, options: ShapeRenderOptions) => unknown;
export interface ShapeDefinition {
semanticName: string;
name: string;
shortName: string;
description: string;
/**
* Aliases can include descriptive names, other short names, etc.
*/
aliases?: string[];
/**
* These are names used by mermaid before the introduction of new shapes. These will not be in standard formats, and shouldn't be used by the users
*/
internalAliases?: string[];
handler: ShapeHandler;
}
export const shapesDefs: ShapeDefinition[] = [
{
semanticName: 'Process',
name: 'Rectangle',
shortName: 'rect',
description: 'Standard process shape',
aliases: ['proc', 'process', 'rectangle'],
internalAliases: ['squareRect'],
handler: squareRect,
},
{
semanticName: 'Event',
name: 'Rounded Rectangle',
shortName: 'rounded',
description: 'Represents an event',
aliases: ['event'],
internalAliases: ['roundedRect'],
handler: roundedRect,
},
{
semanticName: 'Terminal Point',
name: 'Stadium',
shortName: 'stadium',
description: 'Terminal point',
aliases: ['terminal', 'pill'],
handler: stadium,
},
{
semanticName: 'Subprocess',
name: 'Framed Rectangle',
shortName: 'fr-rect',
description: 'Subprocess',
aliases: ['subprocess', 'subproc', 'framed-rectangle', 'subroutine'],
handler: subroutine,
},
{
semanticName: 'Database',
name: 'Cylinder',
shortName: 'cyl',
description: 'Database storage',
aliases: ['db', 'database', 'cylinder'],
handler: cylinder,
},
{
semanticName: 'Start',
name: 'Circle',
shortName: 'circle',
description: 'Starting point',
aliases: ['circ'],
handler: circle,
},
{
semanticName: 'Decision',
name: 'Diamond',
shortName: 'diam',
description: 'Decision-making step',
aliases: ['decision', 'diamond', 'question'],
handler: question,
},
{
semanticName: 'Prepare Conditional',
name: 'Hexagon',
shortName: 'hex',
description: 'Preparation or condition step',
aliases: ['hexagon', 'prepare'],
handler: hexagon,
},
{
semanticName: 'Data Input/Output',
name: 'Lean Right',
shortName: 'lean-r',
description: 'Represents input or output',
aliases: ['lean-right', 'in-out'],
internalAliases: ['lean_right'],
handler: lean_right,
},
{
semanticName: 'Data Input/Output',
name: 'Lean Left',
shortName: 'lean-l',
description: 'Represents output or input',
aliases: ['lean-left', 'out-in'],
internalAliases: ['lean_left'],
handler: lean_left,
},
{
semanticName: 'Priority Action',
name: 'Trapezoid Base Bottom',
shortName: 'trap-b',
description: 'Priority action',
aliases: ['priority', 'trapezoid-bottom', 'trapezoid'],
handler: trapezoid,
},
{
semanticName: 'Manual Operation',
name: 'Trapezoid Base Top',
shortName: 'trap-t',
description: 'Represents a manual task',
aliases: ['manual', 'trapezoid-top', 'inv-trapezoid'],
internalAliases: ['inv_trapezoid'],
handler: inv_trapezoid,
},
{
semanticName: 'Stop',
name: 'Double Circle',
shortName: 'dbl-circ',
description: 'Represents a stop point',
aliases: ['double-circle'],
internalAliases: ['doublecircle'],
handler: doublecircle,
},
{
semanticName: 'Text Block',
name: 'Text Block',
shortName: 'text',
description: 'Text block',
handler: text,
},
{
semanticName: 'Card',
name: 'Notched Rectangle',
shortName: 'notch-rect',
description: 'Represents a card',
aliases: ['card', 'notched-rectangle'],
handler: card,
},
{
semanticName: 'Lined/Shaded Process',
name: 'Lined Rectangle',
shortName: 'lin-rect',
description: 'Lined process shape',
aliases: ['lined-rectangle', 'lined-process', 'lin-proc', 'shaded-process'],
handler: shadedProcess,
},
{
semanticName: 'Start',
name: 'Small Circle',
shortName: 'sm-circ',
description: 'Small starting point',
aliases: ['start', 'small-circle'],
internalAliases: ['stateStart'],
handler: stateStart,
},
{
semanticName: 'Stop',
name: 'Framed Circle',
shortName: 'fr-circ',
description: 'Stop point',
aliases: ['stop', 'framed-circle'],
internalAliases: ['stateEnd'],
handler: stateEnd,
},
{
semanticName: 'Fork/Join',
name: 'Filled Rectangle',
shortName: 'fork',
description: 'Fork or join in process flow',
aliases: ['join'],
internalAliases: ['forkJoin'],
handler: forkJoin,
},
{
semanticName: 'Collate',
name: 'Hourglass',
shortName: 'hourglass',
description: 'Represents a collate operation',
aliases: ['hourglass', 'collate'],
handler: hourglass,
},
{
semanticName: 'Comment',
name: 'Curly Brace',
shortName: 'brace',
description: 'Adds a comment',
aliases: ['comment', 'brace-l'],
handler: curlyBraceLeft,
},
{
semanticName: 'Comment Right',
name: 'Curly Brace',
shortName: 'brace-r',
description: 'Adds a comment',
handler: curlyBraceRight,
},
{
semanticName: 'Comment with braces on both sides',
name: 'Curly Braces',
shortName: 'braces',
description: 'Adds a comment',
handler: curlyBraces,
},
{
semanticName: 'Com Link',
name: 'Lightning Bolt',
shortName: 'bolt',
description: 'Communication link',
aliases: ['com-link', 'lightning-bolt'],
handler: lightningBolt,
},
{
semanticName: 'Document',
name: 'Document',
shortName: 'doc',
description: 'Represents a document',
aliases: ['doc', 'document'],
handler: waveEdgedRectangle,
},
{
semanticName: 'Delay',
name: 'Half-Rounded Rectangle',
shortName: 'delay',
description: 'Represents a delay',
aliases: ['half-rounded-rectangle'],
handler: halfRoundedRectangle,
},
{
semanticName: 'Direct Access Storage',
name: 'Horizontal Cylinder',
shortName: 'h-cyl',
description: 'Direct access storage',
aliases: ['das', 'horizontal-cylinder'],
handler: tiltedCylinder,
},
{
semanticName: 'Disk Storage',
name: 'Lined Cylinder',
shortName: 'lin-cyl',
description: 'Disk storage',
aliases: ['disk', 'lined-cylinder'],
handler: linedCylinder,
},
{
semanticName: 'Display',
name: 'Curved Trapezoid',
shortName: 'curv-trap',
description: 'Represents a display',
aliases: ['curved-trapezoid', 'display'],
handler: curvedTrapezoid,
},
{
semanticName: 'Divided Process',
name: 'Divided Rectangle',
shortName: 'div-rect',
description: 'Divided process shape',
aliases: ['div-proc', 'divided-rectangle', 'divided-process'],
handler: dividedRectangle,
},
{
semanticName: 'Extract',
name: 'Triangle',
shortName: 'tri',
description: 'Extraction process',
aliases: ['extract', 'triangle'],
handler: triangle,
},
{
semanticName: 'Internal Storage',
name: 'Window Pane',
shortName: 'win-pane',
description: 'Internal storage',
aliases: ['internal-storage', 'window-pane'],
handler: windowPane,
},
{
semanticName: 'Junction',
name: 'Filled Circle',
shortName: 'f-circ',
description: 'Junction point',
aliases: ['junction', 'filled-circle'],
handler: filledCircle,
},
{
semanticName: 'Loop Limit',
name: 'Trapezoidal Pentagon',
shortName: 'notch-pent',
description: 'Loop limit step',
aliases: ['loop-limit', 'notched-pentagon'],
handler: trapezoidalPentagon,
},
{
semanticName: 'Manual File',
name: 'Flipped Triangle',
shortName: 'flip-tri',
description: 'Manual file operation',
aliases: ['manual-file', 'flipped-triangle'],
handler: flippedTriangle,
},
{
semanticName: 'Manual Input',
name: 'Sloped Rectangle',
shortName: 'sl-rect',
description: 'Manual input step',
aliases: ['manual-input', 'sloped-rectangle'],
handler: slopedRect,
},
{
semanticName: 'Multi-Document',
name: 'Stacked Document',
shortName: 'docs',
description: 'Multiple documents',
aliases: ['documents', 'st-doc', 'stacked-document'],
handler: multiWaveEdgedRectangle,
},
{
semanticName: 'Multi-Process',
name: 'Stacked Rectangle',
shortName: 'st-rect',
description: 'Multiple processes',
aliases: ['procs', 'processes', 'stacked-rectangle'],
handler: multiRect,
},
{
semanticName: 'Stored Data',
name: 'Bow Tie Rectangle',
shortName: 'bow-rect',
description: 'Stored data',
aliases: ['stored-data', 'bow-tie-rectangle'],
handler: bowTieRect,
},
{
semanticName: 'Summary',
name: 'Crossed Circle',
shortName: 'cross-circ',
description: 'Summary',
aliases: ['summary', 'crossed-circle'],
handler: crossedCircle,
},
{
semanticName: 'Tagged Document',
name: 'Tagged Document',
shortName: 'tag-doc',
description: 'Tagged document',
aliases: ['tag-doc', 'tagged-document'],
handler: taggedWaveEdgedRectangle,
},
{
semanticName: 'Tagged Process',
name: 'Tagged Rectangle',
shortName: 'tag-rect',
description: 'Tagged process',
aliases: ['tagged-rectangle', 'tag-proc', 'tagged-process'],
handler: taggedRect,
},
{
semanticName: 'Paper Tape',
name: 'Flag',
shortName: 'flag',
description: 'Paper tape',
aliases: ['paper-tape'],
handler: waveRectangle,
},
{
semanticName: 'Odd',
name: 'Odd',
shortName: 'odd',
description: 'Odd shape',
internalAliases: ['rect_left_inv_arrow'],
handler: rect_left_inv_arrow,
},
{
semanticName: 'Lined Document',
name: 'Lined Document',
shortName: 'lin-doc',
description: 'Lined document',
aliases: ['lined-document'],
handler: linedWaveEdgedRect,
},
];
const generateShapeMap = () => {
// These are the shapes that didn't have documentation present
const shapeMap: Record<string, ShapeHandler> = {
// States
state,
choice,
note,
// Rectangles
rectWithTitle,
labelRect,
// Icons
iconSquare,
iconCircle,
icon,
iconRounded,
imageSquare,
anchor,
};
for (const shape of shapesDefs) {
for (const alias of [
shape.shortName,
...(shape.aliases ?? []),
...(shape.internalAliases ?? []),
]) {
shapeMap[alias] = shape.handler;
}
}
return shapeMap;
};
export const shapes = generateShapeMap();

View File

@@ -0,0 +1,44 @@
import { log } from '../../../logger.js';
import { updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
export const anchor = (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { labelStyles } = styles2String(node);
node.labelStyle = labelStyles;
const classes = getNodeClasses(node);
let cssClasses = classes;
if (!classes) {
cssClasses = 'anchor';
}
const shapeSvg = parent
// @ts-ignore - SVGElement is not typed
.insert('g')
.attr('class', cssClasses)
.attr('id', node.domId || node.id);
const radius = 1;
const { cssStyles } = node;
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { fill: 'black', stroke: 'none', fillStyle: 'solid' });
if (node.look !== 'handDrawn') {
options.roughness = 0;
}
const roughNode = rc.circle(0, 0, radius * 2, options);
const circleElem = shapeSvg.insert(() => roughNode, ':first-child');
circleElem.attr('class', 'anchor').attr('style', cssStyles);
updateNodeBounds(node, circleElem);
node.intersect = function (point) {
log.info('Circle intersect', node, radius, point);
return intersect.circle(node, radius, point);
};
return shapeSvg;
};

View File

@@ -0,0 +1,125 @@
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
function generateArcPoints(
x1: number,
y1: number,
x2: number,
y2: number,
rx: number,
ry: number,
clockwise: boolean
) {
const numPoints = 20;
// Calculate midpoint
const midX = (x1 + x2) / 2;
const midY = (y1 + y2) / 2;
// Calculate the angle of the line connecting the points
const angle = Math.atan2(y2 - y1, x2 - x1);
// Calculate transformed coordinates for the ellipse
const dx = (x2 - x1) / 2;
const dy = (y2 - y1) / 2;
// Scale to unit circle
const transformedX = dx / rx;
const transformedY = dy / ry;
// Calculate the distance between points on the unit circle
const distance = Math.sqrt(transformedX ** 2 + transformedY ** 2);
// Check if the ellipse can be drawn with the given radii
if (distance > 1) {
throw new Error('The given radii are too small to create an arc between the points.');
}
// Calculate the distance from the midpoint to the center of the ellipse
const scaledCenterDistance = Math.sqrt(1 - distance ** 2);
// Calculate the center of the ellipse
const centerX = midX + scaledCenterDistance * ry * Math.sin(angle) * (clockwise ? -1 : 1);
const centerY = midY - scaledCenterDistance * rx * Math.cos(angle) * (clockwise ? -1 : 1);
// Calculate the start and end angles on the ellipse
const startAngle = Math.atan2((y1 - centerY) / ry, (x1 - centerX) / rx);
const endAngle = Math.atan2((y2 - centerY) / ry, (x2 - centerX) / rx);
// Adjust angles for clockwise/counterclockwise
let angleRange = endAngle - startAngle;
if (clockwise && angleRange < 0) {
angleRange += 2 * Math.PI;
}
if (!clockwise && angleRange > 0) {
angleRange -= 2 * Math.PI;
}
// Generate points
const points = [];
for (let i = 0; i < numPoints; i++) {
const t = i / (numPoints - 1);
const angle = startAngle + t * angleRange;
const x = centerX + rx * Math.cos(angle);
const y = centerY + ry * Math.sin(angle);
points.push({ x, y });
}
return points;
}
export const bowTieRect = async (parent: SVGAElement, node: Node) => {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
const w = bbox.width + node.padding + 20;
const h = bbox.height + node.padding;
const ry = h / 2;
const rx = ry / (2.5 + h / 50);
// let shape: d3.Selection<SVGPathElement | SVGGElement, unknown, null, undefined>;
const { cssStyles } = node;
const points = [
{ x: w / 2, y: -h / 2 },
{ x: -w / 2, y: -h / 2 },
...generateArcPoints(-w / 2, -h / 2, -w / 2, h / 2, rx, ry, false),
{ x: w / 2, y: h / 2 },
...generateArcPoints(w / 2, h / 2, w / 2, -h / 2, rx, ry, true),
];
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
if (node.look !== 'handDrawn') {
options.roughness = 0;
options.fillStyle = 'solid';
}
const bowTieRectPath = createPathFromPoints(points);
const bowTieRectShapePath = rc.path(bowTieRectPath, options);
const bowTieRectShape = shapeSvg.insert(() => bowTieRectShapePath, ':first-child');
bowTieRectShape.attr('class', 'basic label-container');
if (cssStyles && node.look !== 'handDrawn') {
bowTieRectShape.selectAll('path').attr('style', cssStyles);
}
if (nodeStyles && node.look !== 'handDrawn') {
bowTieRectShape.selectAll('path').attr('style', nodeStyles);
}
bowTieRectShape.attr('transform', `translate(${rx / 2}, 0)`);
updateNodeBounds(node, bowTieRectShape);
node.intersect = function (point) {
const pos = intersect.polygon(node, points, point);
return pos;
};
return shapeSvg;
};

View File

@@ -0,0 +1,68 @@
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
import { createPathFromPoints } from './util.js';
// const createPathFromPoints = (points: { x: number; y: number }[]): string => {
// const pointStrings = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`);
// pointStrings.push('Z');
// return pointStrings.join(' ');
// };
export async function card(parent: SVGAElement, node: Node): Promise<SVGAElement> {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
const h = bbox.height + node.padding;
const padding = 12;
const w = bbox.width + node.padding + padding;
const left = 0;
const right = w;
const top = -h;
const bottom = 0;
const points = [
{ x: left + padding, y: top },
{ x: right, y: top },
{ x: right, y: bottom },
{ x: left, y: bottom },
{ x: left, y: top + padding },
{ x: left + padding, y: top },
];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
const { cssStyles } = node;
if (node.look === 'handDrawn') {
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createPathFromPoints(points);
const roughNode = rc.path(pathData, options);
polygon = shapeSvg
.insert(() => roughNode, ':first-child')
.attr('transform', `translate(${-w / 2}, ${h / 2})`);
if (cssStyles) {
polygon.attr('style', cssStyles);
}
} else {
polygon = insertPolygonShape(shapeSvg, w, h, points);
}
if (nodeStyles) {
polygon.attr('style', nodeStyles);
}
updateNodeBounds(node, polygon);
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};
return shapeSvg;
}

View File

@@ -3,20 +3,20 @@ import type { Node } from '../../types.js';
import type { SVG } from '../../../diagram-api/types.js';
// @ts-ignore TODO: Fix rough typings
import rough from 'roughjs';
import { solidStateFill, styles2String } from './handDrawnShapeStyles.js';
import { getConfig } from '../../../diagram-api/diagramAPI.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { createPathFromPoints, getNodeClasses } from './util.js';
export const choice = (parent: SVG, node: Node) => {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { themeVariables } = getConfig();
const { lineColor } = themeVariables;
const { nodeStyles } = styles2String(node);
node.label = '';
const shapeSvg = parent
.insert('g')
.attr('class', 'node default')
.attr('id', node.domId || node.id);
.attr('class', getNodeClasses(node))
.attr('id', node.domId ?? node.id);
const { cssStyles } = node;
const s = Math.max(28, node.width ?? 0);
const s = 28;
const points = [
{ x: 0, y: s / 2 },
{ x: s / 2, y: 0 },
@@ -24,40 +24,32 @@ export const choice = (parent: SVG, node: Node) => {
{ x: -s / 2, y: 0 },
];
let choice;
if (node.look === 'handDrawn') {
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const pointArr = points.map(function (d) {
return [d.x, d.y];
});
const roughNode = rc.polygon(pointArr, solidStateFill(lineColor));
choice = shapeSvg.insert(() => roughNode);
} else {
choice = shapeSvg.insert('polygon', ':first-child').attr(
'points',
points
.map(function (d) {
return d.x + ',' + d.y;
})
.join(' ')
);
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
if (node.look !== 'handDrawn') {
options.roughness = 0;
options.fillStyle = 'solid';
}
// center the circle around its coordinate
choice
.attr('class', 'state-start')
// @ts-ignore TODO: Fix rough typings
.attr('r', 7)
.attr('width', 28)
.attr('height', 28)
.attr('style', nodeStyles);
const choicePath = createPathFromPoints(points);
const roughNode = rc.path(choicePath, options);
const choiceShape = shapeSvg.insert(() => roughNode, ':first-child');
if (cssStyles && node.look !== 'handDrawn') {
choiceShape.selectAll('path').attr('style', cssStyles);
}
if (nodeStyles && node.look !== 'handDrawn') {
choiceShape.selectAll('path').attr('style', nodeStyles);
}
node.width = 28;
node.height = 28;
node.intersect = function (point) {
return intersect.circle(node, 14, point);
return intersect.polygon(node, points, point);
};
return shapeSvg;

View File

@@ -15,7 +15,6 @@ export const circle = async (parent: SVGAElement, node: Node): Promise<SVGAEleme
const { cssStyles } = node;
if (node.look === 'handDrawn') {
// @ts-ignore - rough is not typed
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const roughNode = rc.circle(0, 0, radius * 2, options);

View File

@@ -0,0 +1,67 @@
import { log } from '../../../logger.js';
import { getNodeClasses, updateNodeBounds } from './util.js';
import type { SVG } from '../../../diagram-api/types.js';
import type { Node } from '../../types.ts';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import intersect from '../intersect/index.js';
function createLine(r: number) {
const xAxis45 = Math.cos(Math.PI / 4); // cosine of 45 degrees
const yAxis45 = Math.sin(Math.PI / 4); // sine of 45 degrees
const lineLength = r * 2;
const pointQ1 = { x: (lineLength / 2) * xAxis45, y: (lineLength / 2) * yAxis45 }; // Quadrant I
const pointQ2 = { x: -(lineLength / 2) * xAxis45, y: (lineLength / 2) * yAxis45 }; // Quadrant II
const pointQ3 = { x: -(lineLength / 2) * xAxis45, y: -(lineLength / 2) * yAxis45 }; // Quadrant III
const pointQ4 = { x: (lineLength / 2) * xAxis45, y: -(lineLength / 2) * yAxis45 }; // Quadrant IV
return `M ${pointQ2.x},${pointQ2.y} L ${pointQ4.x},${pointQ4.y}
M ${pointQ1.x},${pointQ1.y} L ${pointQ3.x},${pointQ3.y}`;
}
export const crossedCircle = (parent: SVG, node: Node) => {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
node.label = '';
const shapeSvg = parent
.insert('g')
.attr('class', getNodeClasses(node))
.attr('id', node.domId ?? node.id);
const radius = Math.max(30, node?.width ?? 0);
const { cssStyles } = node;
// @ts-expect-error shapeSvg d3 class is incorrect?
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
if (node.look !== 'handDrawn') {
options.roughness = 0;
options.fillStyle = 'solid';
}
const circleNode = rc.circle(0, 0, radius * 2, options);
const linePath = createLine(radius);
const lineNode = rc.path(linePath, options);
const crossedCircle = shapeSvg.insert(() => circleNode, ':first-child');
crossedCircle.insert(() => lineNode);
if (cssStyles && node.look !== 'handDrawn') {
crossedCircle.selectAll('path').attr('style', cssStyles);
}
if (nodeStyles && node.look !== 'handDrawn') {
crossedCircle.selectAll('path').attr('style', nodeStyles);
}
updateNodeBounds(node, crossedCircle);
node.intersect = function (point) {
log.info('crossedCircle intersect', node, { radius, point });
const pos = intersect.circle(node, radius, point);
return pos;
};
return shapeSvg;
};

View File

@@ -0,0 +1,110 @@
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
function generateCirclePoints(
centerX: number,
centerY: number,
radius: number,
numPoints = 100,
startAngle = 0,
endAngle = 180
) {
const points = [];
// Convert angles to radians
const startAngleRad = (startAngle * Math.PI) / 180;
const endAngleRad = (endAngle * Math.PI) / 180;
// Calculate the angle range in radians
const angleRange = endAngleRad - startAngleRad;
// Calculate the angle step
const angleStep = angleRange / (numPoints - 1);
for (let i = 0; i < numPoints; i++) {
const angle = startAngleRad + i * angleStep;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
points.push({ x: -x, y: -y });
}
return points;
}
export const curlyBraceLeft = async (parent: SVGAElement, node: Node) => {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
const w = bbox.width + (node.padding ?? 0);
const h = bbox.height + (node.padding ?? 0);
const radius = Math.max(5, h * 0.1);
const { cssStyles } = node;
const points = [
...generateCirclePoints(w / 2, -h / 2, radius, 30, -90, 0),
{ x: -w / 2 - radius, y: radius },
...generateCirclePoints(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
...generateCirclePoints(w / 2 + radius * 2, radius, radius, 20, -90, -180),
{ x: -w / 2 - radius, y: -h / 2 },
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
];
const rectPoints = [
{ x: w / 2, y: -h / 2 - radius },
{ x: -w / 2, y: -h / 2 - radius },
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
{ x: -w / 2 - radius, y: -radius },
...generateCirclePoints(w / 2 + w * 0.1, -radius, radius, 20, -180, -270),
...generateCirclePoints(w / 2 + w * 0.1, radius, radius, 20, -90, -180),
{ x: -w / 2 - radius, y: h / 2 },
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
{ x: -w / 2, y: h / 2 + radius },
{ x: w / 2, y: h / 2 + radius },
];
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { fill: 'none' });
if (node.look !== 'handDrawn') {
options.roughness = 0;
options.fillStyle = 'solid';
}
const curlyBraceLeftPath = createPathFromPoints(points);
const newCurlyBracePath = curlyBraceLeftPath.replace('Z', '');
const curlyBraceLeftNode = rc.path(newCurlyBracePath, options);
const rectPath = createPathFromPoints(rectPoints);
const rectShape = rc.path(rectPath, { ...options });
const curlyBraceLeftShape = shapeSvg.insert('g', ':first-child');
curlyBraceLeftShape.insert(() => rectShape, ':first-child').attr('stroke-opacity', 0);
curlyBraceLeftShape.insert(() => curlyBraceLeftNode, ':first-child');
curlyBraceLeftShape.attr('class', 'text');
if (cssStyles && node.look !== 'handDrawn') {
curlyBraceLeftShape.selectAll('path').attr('style', cssStyles);
}
if (nodeStyles && node.look !== 'handDrawn') {
curlyBraceLeftShape.selectAll('path').attr('style', nodeStyles);
}
curlyBraceLeftShape.attr('transform', `translate(${radius}, 0)`);
label.attr(
'transform',
`translate(${-w / 2 + radius - (bbox.x - (bbox.left ?? 0))},${-h / 2 + (node.padding ?? 0) / 2 - (bbox.y - (bbox.top ?? 0))})`
);
updateNodeBounds(node, curlyBraceLeftShape);
node.intersect = function (point) {
const pos = intersect.polygon(node, rectPoints, point);
return pos;
};
return shapeSvg;
};

View File

@@ -0,0 +1,110 @@
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
function generateCirclePoints(
centerX: number,
centerY: number,
radius: number,
numPoints = 100,
startAngle = 0,
endAngle = 180
) {
const points = [];
// Convert angles to radians
const startAngleRad = (startAngle * Math.PI) / 180;
const endAngleRad = (endAngle * Math.PI) / 180;
// Calculate the angle range in radians
const angleRange = endAngleRad - startAngleRad;
// Calculate the angle step
const angleStep = angleRange / (numPoints - 1);
for (let i = 0; i < numPoints; i++) {
const angle = startAngleRad + i * angleStep;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
points.push({ x, y });
}
return points;
}
export const curlyBraceRight = async (parent: SVGAElement, node: Node) => {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
const w = bbox.width + (node.padding ?? 0);
const h = bbox.height + (node.padding ?? 0);
const radius = Math.max(5, h * 0.1);
const { cssStyles } = node;
const points = [
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
{ x: w / 2 + radius, y: -radius },
...generateCirclePoints(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
...generateCirclePoints(w / 2 + radius * 2, radius, radius, 20, -90, -180),
{ x: w / 2 + radius, y: h / 2 },
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
];
const rectPoints = [
{ x: -w / 2, y: -h / 2 - radius },
{ x: w / 2, y: -h / 2 - radius },
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
{ x: w / 2 + radius, y: -radius },
...generateCirclePoints(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
...generateCirclePoints(w / 2 + radius * 2, radius, radius, 20, -90, -180),
{ x: w / 2 + radius, y: h / 2 },
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
{ x: w / 2, y: h / 2 + radius },
{ x: -w / 2, y: h / 2 + radius },
];
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { fill: 'none' });
if (node.look !== 'handDrawn') {
options.roughness = 0;
options.fillStyle = 'solid';
}
const curlyBraceRightPath = createPathFromPoints(points);
const newCurlyBracePath = curlyBraceRightPath.replace('Z', '');
const curlyBraceRightNode = rc.path(newCurlyBracePath, options);
const rectPath = createPathFromPoints(rectPoints);
const rectShape = rc.path(rectPath, { ...options });
const curlyBraceRightShape = shapeSvg.insert('g', ':first-child');
curlyBraceRightShape.insert(() => rectShape, ':first-child').attr('stroke-opacity', 0);
curlyBraceRightShape.insert(() => curlyBraceRightNode, ':first-child');
curlyBraceRightShape.attr('class', 'text');
if (cssStyles && node.look !== 'handDrawn') {
curlyBraceRightShape.selectAll('path').attr('style', cssStyles);
}
if (nodeStyles && node.look !== 'handDrawn') {
curlyBraceRightShape.selectAll('path').attr('style', nodeStyles);
}
curlyBraceRightShape.attr('transform', `translate(${-radius}, 0)`);
label.attr(
'transform',
`translate(${-w / 2 + (node.padding ?? 0) / 2 - (bbox.x - (bbox.left ?? 0))},${-h / 2 + (node.padding ?? 0) / 2 - (bbox.y - (bbox.top ?? 0))})`
);
updateNodeBounds(node, curlyBraceRightShape);
node.intersect = function (point) {
const pos = intersect.polygon(node, rectPoints, point);
return pos;
};
return shapeSvg;
};

Some files were not shown because too many files have changed in this diff Show More