Compare commits

..

26 Commits

Author SHA1 Message Date
Sidharth Vinod
2f792e33d6 chore: Support local screenshot testing without applitools or argos 2024-06-20 15:11:38 +05:30
Sidharth Vinod
e8d18189ac Merge branch 'develop' of https://github.com/mermaid-js/mermaid into develop
* 'develop' of https://github.com/mermaid-js/mermaid:
  chore: Remove reference screenshot generation from E2E
  chore: Remove cytoscape patch.
  chore(argos): disable matchImageSnapshot
  chore(argos): put parallel mode only when necessary
  chore(argos): setup parallel mode
  chore: setup Argos Visual Testing on E2E
2024-06-20 14:47:57 +05:30
Sidharth Vinod
a6276a94c3 chore: Skip checking drupal links 2024-06-20 14:47:53 +05:30
Sidharth Vinod
85628f2148 Merge pull request #5577 from gregberge/setup-argos-visual-testing
chore: setup Argos Visual Testing
2024-06-20 14:46:54 +05:30
Sidharth Vinod
1f70717a53 Merge branch 'develop' into pr/gregberge/5577
* develop:
  Removed unused patch
  Fix cytoscape patch
  chore: Remove cytoscape patch.
  docs: fix node version in CONTRIBUTING.md
  Fixed entrypoint path
2024-06-20 14:44:16 +05:30
Sidharth Vinod
493bb8a80e chore: Remove reference screenshot generation from E2E 2024-06-20 14:39:21 +05:30
Sidharth Vinod
788e7c96ff chore: Remove cytoscape patch. 2024-06-20 14:39:21 +05:30
Greg Bergé
caa0ff340d chore(argos): disable matchImageSnapshot 2024-06-20 14:38:37 +05:30
Greg Bergé
44688a20b6 chore(argos): put parallel mode only when necessary 2024-06-20 14:38:37 +05:30
Greg Bergé
3f1bba407e chore(argos): setup parallel mode 2024-06-20 14:38:21 +05:30
Greg Bergé
91e8bcaba9 chore: setup Argos Visual Testing on E2E 2024-06-20 14:38:06 +05:30
Nikolay Rozhkov
0044aa3029 Merge pull request #5573 from exoego/docs/5572_node-version-contributing-md
docs: fix node version in CONTRIBUTING.md
2024-06-19 18:25:38 +00:00
Nikolay Rozhkov
9c3bcec7f0 Merge branch 'develop' into docs/5572_node-version-contributing-md 2024-06-19 21:04:46 +03:00
Sidharth Vinod
63f9e95795 Merge pull request #5566 from mermaid-js/bug/5565-fix-docker-local-development
Fixed entrypoint path
2024-06-19 17:31:01 +00:00
Nikolay Rozhkov
8f00555bf5 Removed unused patch 2024-06-19 19:51:09 +03:00
Nikolay Rozhkov
029b3c1101 Merge branch 'develop' into bug/5565-fix-docker-local-development 2024-06-19 19:21:48 +03:00
Nikolay Rozhkov
2340a3b836 Fix cytoscape patch 2024-06-19 19:17:56 +03:00
Sidharth Vinod
d84b4403ab chore: Remove cytoscape patch. 2024-06-19 21:47:07 +05:30
Nikolay Rozhkov
323f72ce33 Merge branch 'develop' into bug/5565-fix-docker-local-development 2024-06-19 16:49:23 +03:00
Sidharth Vinod
ecee23d8ba Merge pull request #5546 from mermaid-js/update-browserslist
Update Browserslist
2024-06-17 10:55:54 +05:30
exoego
7ee22de5e2 docs: fix node version in CONTRIBUTING.md 2024-06-16 07:29:35 +09:00
cmmoran
ce3d0a23de chore: update browsers list 2024-06-10 07:06:39 +00:00
Nikolay Rozhkov
1c4dd9b923 Fixed entrypoint path 2024-06-09 01:23:38 +03:00
Sidharth Vinod
d6ccd93cf2 Merge pull request #5532 from TWiStErRob/patch-1
Tiny improvements to Diagram Syntax sidebar
2024-05-24 09:24:17 +05:30
Sidharth Vinod
cf20ccb126 Create FUNDING.json 2024-05-21 18:28:21 +05:30
Róbert Papp
44f42b2a63 Tiny improvements to Diagram Syntax sidebar 2024-05-18 11:13:09 +01:00
108 changed files with 12790 additions and 19074 deletions

View File

@@ -22,9 +22,9 @@ export const packageOptions = {
packageName: 'mermaid-zenuml',
file: 'detector.ts',
},
'mermaid-layout-elk': {
name: 'mermaid-layout-elk',
packageName: 'mermaid-layout-elk',
file: 'layouts.ts',
'mermaid-flowchart-elk': {
name: 'mermaid-flowchart-elk',
packageName: 'mermaid-flowchart-elk',
file: 'detector.ts',
},
} as const;

View File

@@ -1,7 +1,6 @@
import jison from 'jison';
export const transformJison = (src: string): string => {
// @ts-ignore - Jison is not typed properly
const parser = new jison.Generator(src, {
moduleType: 'js',
'token-stack': true,

View File

@@ -27,7 +27,6 @@ controly
CSSCLASS
CYLINDEREND
CYLINDERSTART
DAGA
datakey
DEND
descr
@@ -90,7 +89,6 @@ reqs
rewritelinks
rgba
RIGHTOF
roughjs
sankey
sequencenumber
shrc

View File

@@ -54,7 +54,6 @@ presetAttributify
pyplot
redmine
rehype
roughjs
rscratch
sparkline
sphinxcontrib

View File

@@ -9,7 +9,6 @@ elems
gantt
gitgraph
gzipped
handdrawn
knsv
Knut
marginx
@@ -18,7 +17,6 @@ Markdownish
mermaidjs
mindmap
mindmaps
mrtree
multigraph
nodesep
NOTEGROUP

View File

@@ -1,4 +1 @@
circo
handdrawnSeed
neato
newbranch

View File

@@ -7,8 +7,8 @@ import { MermaidBuildOptions, 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 buildConfigs: MermaidBuildOptions[] = [
const commonOptions = { ...defaultOptions, entryName } as const;
const buildConfigs = [
// package.mjs
{ ...commonOptions },
// package.min.mjs

View File

@@ -8,7 +8,7 @@ import { jisonPlugin } from './jisonPlugin.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
export interface MermaidBuildOptions extends BuildOptions {
export interface MermaidBuildOptions {
minify: boolean;
core: boolean;
metafile: boolean;

5
.github/lychee.toml vendored
View File

@@ -41,7 +41,10 @@ exclude = [
"https://bundlephobia.com",
# Chrome webstore migration issue. Temporary
"https://chromewebstore.google.com"
"https://chromewebstore.google.com",
# Drupal 403
"https://www.drupal.org"
]
# Exclude all private IPs from checking.

View File

@@ -1,9 +1,3 @@
# We use github cache to save snapshots between runs.
# For PRs and MergeQueues, the target commit is used, and for push events, github.event.previous is used.
# If a snapshot for a given Hash is not found, we checkout that commit, run the tests and cache the snapshots.
# These are then downloaded before running the E2E, providing the reference snapshots.
# If there are any errors, the diff image is uploaded to artifacts, and the user is notified.
name: E2E
on:
@@ -72,16 +66,6 @@ jobs:
mkdir -p cypress/snapshots/stats/base
mv stats cypress/snapshots/stats/base
- name: Cypress run
uses: cypress-io/github-action@v6
id: cypress-snapshot-gen
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
with:
install: false
start: pnpm run dev
wait-on: 'http://localhost:9000'
browser: chrome
e2e:
runs-on: ubuntu-latest
container:
@@ -146,6 +130,10 @@ jobs:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
VITEST_COVERAGE: true
CYPRESS_COMMIT: ${{ github.sha }}
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
ARGOS_PARALLEL: ${{ env.CYPRESS_RECORD_KEY != '' }}
ARGOS_PARALLEL_TOTAL: 4
ARGOS_PARALLEL_INDEX: ${{ matrix.containers }}
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v4
@@ -158,55 +146,3 @@ jobs:
fail_ci_if_error: false
verbose: true
token: 6845cc80-77ee-4e17-85a1-026cd95e0766
# We upload the artifacts into numbered archives to prevent overwriting
- name: Upload Artifacts
uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
name: snapshots-${{ matrix.containers }}
retention-days: 1
path: ./cypress/snapshots
combineArtifacts:
needs: e2e
runs-on: ubuntu-latest
if: ${{ always() }}
steps:
# Download all snapshot artifacts and merge them into a single folder
- name: Download All Artifacts
uses: actions/download-artifact@v4
with:
path: snapshots
pattern: snapshots-*
merge-multiple: true
# For successful push events, we save the snapshots cache
- name: Save snapshots cache
id: cache-upload
if: ${{ github.event_name == 'push' && needs.e2e.result != 'failure' }}
uses: actions/cache/save@v4
with:
path: ./snapshots
key: ${{ runner.os }}-snapshots-${{ github.event.after }}
- name: Flatten images to a folder
if: ${{ needs.e2e.result == 'failure' }}
run: |
mkdir errors
cd snapshots
find . -mindepth 2 -type d -name "*__diff_output__*" -exec sh -c 'mv "$0"/*.png ../errors/' {} \;
- name: Upload Error snapshots
if: ${{ needs.e2e.result == 'failure' }}
uses: actions/upload-artifact@v4
id: upload-artifacts
with:
name: error-snapshots
retention-days: 10
path: errors/
- name: Notify Users
if: ${{ needs.e2e.result == 'failure' }}
run: |
echo "::error title=Visual tests failed::You can view images that failed by downloading the error-snapshots artifact: ${{ steps.upload-artifacts.outputs.artifact-url }}"

2
.gitignore vendored
View File

@@ -35,7 +35,7 @@ cypress/snapshots/
.tsbuildinfo
tsconfig.tsbuildinfo
#knsv*.html
knsv*.html
local*.html
stats/

View File

@@ -16,5 +16,3 @@ generated/
# Ignore the files creates in /demos/dev except for example.html
demos/dev/**
!/demos/dev/example.html
# TODO: Lots of errors to fix
cypress/platform/state-refactor.html

7
FUNDING.json Normal file
View File

@@ -0,0 +1,7 @@
{
"drips": {
"ethereum": {
"ownedBy": "0x0831DDFe60d009d9448CC976157b539089aB821E"
}
}
}

View File

@@ -35,6 +35,7 @@ Try Live Editor previews of future releases: <a href="https://develop.git.mermai
[![NPM Downloads](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid)
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/AgrbSrBer3)
[![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=X)](https://twitter.com/mermaidjs_)
[![Covered by Argos Visual Testing](https://argos-ci.com/badge.svg)](https://argos-ci.com)
<img src="./img/header.png" alt="" />

View File

@@ -2,6 +2,8 @@ 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';
export default eyesPlugin(
defineConfig({
projectId: 'n2sma2',
@@ -17,10 +19,19 @@ export default eyesPlugin(
}
return launchOptions;
});
addMatchImageSnapshotPlugin(on, config);
// copy any needed variables from process.env to config.env
config.env.useAppli = process.env.USE_APPLI ? true : false;
config.env.useArgos = !!process.env.ARGOS_TOKEN;
if (config.env.useArgos) {
// Argos
registerArgosTask(on, config, {
uploadToArgos: !!process.env.CI,
token: process.env.ARGOS_TOKEN,
});
} else {
addMatchImageSnapshotPlugin(on, config);
}
// do not forget to return the changed config object!
return config;
},

View File

@@ -95,19 +95,8 @@ export const openURLAndVerifyRendering = (
options: CypressMermaidConfig,
validation?: any
): void => {
const useAppli: boolean = Cypress.env('useAppli');
const name: string = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
if (useAppli) {
cy.log(`Opening eyes ${Cypress.spec.name} --- ${name}`);
cy.eyesOpen({
appName: 'Mermaid',
testName: name,
batchName: Cypress.spec.name,
batchId: batchId + Cypress.spec.name,
});
}
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
cy.get('svg').should('be.visible');
@@ -116,11 +105,27 @@ export const openURLAndVerifyRendering = (
cy.get('svg').should(validation);
}
verifyScreenshot(name);
};
export const verifyScreenshot = (name: string): void => {
const useAppli: boolean = Cypress.env('useAppli');
const useArgos: boolean = Cypress.env('useArgos');
if (useAppli) {
cy.log(`Opening eyes ${Cypress.spec.name} --- ${name}`);
cy.eyesOpen({
appName: 'Mermaid',
testName: name,
batchName: Cypress.spec.name,
batchId: batchId + Cypress.spec.name,
});
cy.log(`Check eyes ${Cypress.spec.name}`);
cy.eyesCheckWindow('Click!');
cy.log(`Closing eyes ${Cypress.spec.name}`);
cy.eyesClose();
} else if (useArgos) {
cy.argosScreenshot(name);
} else {
cy.matchImageSnapshot(name);
}

View File

@@ -1,4 +1,4 @@
import { renderGraph } from '../../helpers/util.ts';
import { renderGraph, verifyScreenshot } from '../../helpers/util.ts';
describe('Configuration', () => {
describe('arrowMarkerAbsolute', () => {
it('should handle default value false of arrowMarkerAbsolute', () => {
@@ -120,7 +120,7 @@ describe('Configuration', () => {
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
cy.get('svg').should('be.visible');
cy.matchImageSnapshot(
verifyScreenshot(
'configuration.spec-should-not-taint-initial-configuration-when-using-multiple-directives'
);
});
@@ -145,7 +145,7 @@ describe('Configuration', () => {
// none of the diagrams should be error diagrams
expect($svg).to.not.contain('Syntax error');
});
cy.matchImageSnapshot(
verifyScreenshot(
'configuration.spec-should-not-render-error-diagram-if-suppressErrorRendering-is-set'
);
});
@@ -162,7 +162,7 @@ describe('Configuration', () => {
// some of the diagrams should be error diagrams
expect($svg).to.contain('Syntax error');
});
cy.matchImageSnapshot(
verifyScreenshot(
'configuration.spec-should-render-error-diagram-if-suppressErrorRendering-is-not-set'
);
});

View File

@@ -1,775 +0,0 @@
<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 {
font-family: 'Arial';
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
th,
td {
border: 1px solid black;
padding: 10px;
text-align: center;
vertical-align: middle;
}
.separator {
height: 20px;
background-color: #f0f0f0;
}
.vertical-header {
text-align: center;
}
.collapsible {
background-color: #f9f9f9;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
.active,
.collapsible:hover {
background-color: #ccc;
}
.collapsible:after {
content: '\002B';
color: #777;
font-weight: bold;
float: right;
margin-left: 2px;
}
.active:after {
content: "\2212";
}
.content {
padding: 0 5px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
.content .pre-scrollable {
max-height: 200px;
overflow-y: scroll;
}
</style>
</head>
<body>
<table>
<tr>
<th></th> <!-- Placeholder for the top left corner -->
<th>Dagre</th>
<th>Dagre with rough</th>
<th>ELK</th>
<th>ELK with rough</th>
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Stadium shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart LR
id1([This is the text in the box])
</pre>
</div>
</th>
<td>
<pre id="diagram1" class="mermaid">
flowchart LR
id1([This is the text in the box])
</pre>
</td>
<td>
<pre id="diagram2" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart LR
id1([This is the text in the box])
</pre>
</td>
<td>
<pre id="diagram3" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart LR
id1([This is the text in the box])
</pre>
</td>
<td>
<pre id="diagram4" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart LR
id1([This is the text in the box])
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Sub-Routine shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart LR
id1[[This is the text in the box]]
</pre>
</div>
</th>
<td>
<pre id="diagram5" class="mermaid">
flowchart LR
id1[[This is the text in the box]]
</pre>
</td>
<td>
<pre id="diagram6" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart LR
id1[[This is the text in the box]]
</pre>
</td>
<td>
<pre id="diagram7" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart LR
id1[[This is the text in the box]]
</pre>
</td>
<td>
<pre id="diagram8" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart LR
id1[[This is the text in the box]]
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Cylindrical shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart LR
id1[(Database)]
</pre>
</div>
</th>
<td>
<pre id="diagram9" class="mermaid">
flowchart LR
id1[(Database)]
</pre>
</td>
<td>
<pre id="diagram10" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart LR
id1[(Database)]
</pre>
</td>
<td>
<pre id="diagram11" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart LR
id1[(Database)]
</pre>
</td>
<td>
<pre id="diagram12" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart LR
id1[(Database)]
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Circle shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart LR
id1((This is the text in the circle))
</pre>
</div>
</th>
<td>
<pre id="diagram13" class="mermaid">
flowchart LR
id1((This is the text in the circle))
</pre>
</td>
<td>
<pre id="diagram14" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart LR
id1((This is the text in the circle))
</pre>
</td>
<td>
<pre id="diagram15" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart LR
id1((This is the text in the circle))
</pre>
</td>
<td>
<pre id="diagram16" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart LR
id1((This is the text in the circle))
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Double Circle shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart TD
id1(((This is the text in the circle)))
</pre>
</div>
</th>
<td>
<pre id="diagram17" class="mermaid">
flowchart TD
id1(((This is the text in the circle)))
</pre>
</td>
<td>
<pre id="diagram18" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart TD
id1(((This is the text in the circle)))
</pre>
</td>
<td>
<pre id="diagram19" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart TD
id1(((This is the text in the circle)))
</pre>
</td>
<td>
<pre id="diagram20" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart TD
id1(((This is the text in the circle)))
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Asymmetric shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart LR
id1>This is the text in the box]
</pre>
</div>
</th>
<td>
<pre id="diagram21" class="mermaid">
flowchart LR
id1>This is the text in the box]
</pre>
</td>
<td>
<pre id="diagram22" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart LR
id1>This is the text in the box]
</pre>
</td>
<td>
<pre id="diagram23" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart LR
id1>This is the text in the box]
</pre>
</td>
<td>
<pre id="diagram24" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart LR
id1>This is the text in the box]
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Rhombus/Diamond/Question shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart LR
id1{This is the text in the box}
</pre>
</div>
</th>
<td>
<pre id="diagram25" class="mermaid">
flowchart LR
id1{This is the text in the box}
</pre>
</td>
<td>
<pre id="diagram26" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart LR
id1{This is the text in the box}
</pre>
</td>
<td>
<pre id="diagram27" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart LR
id1{This is the text in the box}
</pre>
</td>
<td>
<pre id="diagram28" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart LR
id1{This is the text in the box}
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Hexagon shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart LR
id1{{This is the text in the box}}
</pre>
</div>
</th>
<td>
<pre id="diagram29" class="mermaid">
flowchart LR
id1{{This is the text in the box}}
</pre>
</td>
<td>
<pre id="diagram30" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart LR
id1{{This is the text in the box}}
</pre>
</td>
<td>
<pre id="diagram31" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart LR
id1{{This is the text in the box}}
</pre>
</td>
<td>
<pre id="diagram32" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart LR
id1{{This is the text in the box}}
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Parallelogram shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart TD
id1[/This is the text in the box/]
</pre>
</div>
</th>
<td>
<pre id="diagram33" class="mermaid">
flowchart TD
id1[/This is the text in the box/]
</pre>
</td>
<td>
<pre id="diagram34" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart TD
id1[/This is the text in the box/]
</pre>
</td>
<td>
<pre id="diagram35" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart TD
id1[/This is the text in the box/]
</pre>
</td>
<td>
<pre id="diagram36" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart TD
id1[/This is the text in the box/]
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Parallelogram Alt shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart TD
id1[\This is the text in the box\]
</pre>
</div>
</th>
<td>
<pre id="diagram37" class="mermaid">
flowchart TD
id1[\This is the text in the box\]
</pre>
</td>
<td>
<pre id="diagram38" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart TD
id1[\This is the text in the box\]
</pre>
</td>
<td>
<pre id="diagram39" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart TD
id1[\This is the text in the box\]
</pre>
</td>
<td>
<pre id="diagram40" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart TD
id1[\This is the text in the box\]
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Trapezoid shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart TD
A[/Christmas\]
</pre>
</div>
</th>
<td>
<pre id="diagram41" class="mermaid">
flowchart TD
A[/Christmas\]
</pre>
</td>
<td>
<pre id="diagram42" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart TD
A[/Christmas\]
</pre>
</td>
<td>
<pre id="diagram43" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart TD
A[/Christmas\]
</pre>
</td>
<td>
<pre id="diagram44" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart TD
A[/Christmas\]
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Trapezoid Alt shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart TD
A[\Christmas/]
</pre>
</div>
</th>
<td>
<pre id="diagram45" class="mermaid">
flowchart TD
A[\Christmas/]
</pre>
</td>
<td>
<pre id="diagram46" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart TD
A[\Christmas/]
</pre>
</td>
<td>
<pre id="diagram47" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart TD
A[\Christmas/]
</pre>
</td>
<td>
<pre id="diagram48" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart TD
A[\Christmas/]
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Rect with rounded corner</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart LR
id1(This is the text in the box)
</pre>
</div>
</th>
<td>
<pre id="diagram49" class="mermaid">
flowchart LR
id1(This is the text in the box)
</pre>
</td>
<td>
<pre id="diagram50" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart LR
id1(This is the text in the box)
</pre>
</td>
<td>
<pre id="diagram51" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart LR
id1(This is the text in the box)
</pre>
</td>
<td>
<pre id="diagram52" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart LR
id1(This is the text in the box)
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Rect with sharp corner</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart LR
id1[This is the text in the box]
</pre>
</div>
</th>
<td>
<pre id="diagram53" class="mermaid">
flowchart LR
id1[This is the text in the box]
</pre>
</td>
<td>
<pre id="diagram54" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart LR
id1[This is the text in the box]
</pre>
</td>
<td>
<pre id="diagram55" class="mermaid">
%%{init: {"handdrawn": false, "layout": "elk"} }%%
flowchart LR
id1[This is the text in the box]
</pre>
</td>
<td>
<pre id="diagram56" class="mermaid">
%%{init: {"look": "handdrawn", "layout": "elk"} }%%
flowchart LR
id1[This is the text in the box]
</pre>
</td>
</tr>
<!-- Separator row -->
<tr class="separator">
<td colspan="5"></td> <!-- This cell spans all columns including the vertical header -->
</tr>
</table>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import { layouts } from './mermaid-layout-elk.esm.mjs';
mermaid.registerLayoutLoaders(layouts);
mermaid.parseError = function (err, hash) {
};
mermaid.initialize({
handdrawn: false,
mergeEdges: true,
layout: 'dagre',
flowchart: { titleTopMargin: 10 },
// fontFamily: 'Caveat',
fontFamily: 'Kalam',
sequence: {
actorFontFamily: 'courier',
noteFontFamily: 'courier',
messageFontFamily: 'courier',
},
fontSize: 16,
logLevel: 0,
});
function callback() {
alert('It worked');
}
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);
};
let coll = document.getElementsByClassName("collapsible");
for (const element of coll) {
element.addEventListener("click", function () {
this.classList.toggle("active");
let content = this.nextElementSibling;
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
</script>
</body>
</html>

View File

@@ -1,181 +0,0 @@
<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 {
font-family: 'Arial';
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
th,
td {
border: 1px solid black;
padding: 10px;
text-align: center;
vertical-align: middle;
}
.separator {
height: 20px;
background-color: #f0f0f0;
}
.vertical-header {
text-align: center;
}
.collapsible {
background-color: #f9f9f9;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
.active,
.collapsible:hover {
background-color: #ccc;
}
.collapsible:after {
content: '\002B';
color: #777;
font-weight: bold;
float: right;
margin-left: 2px;
}
.active:after {
content: "\2212";
}
.content {
padding: 0 5px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
.content .pre-scrollable {
max-height: 200px;
overflow-y: scroll;
}
</style>
</head>
<body>
<table>
<tr>
<th></th> <!-- Placeholder for the top left corner -->
<th>State rough</th>
<th>Flowchart rough</th>
</tr>
<tr>
<th class="vertical-header">
<button class="collapsible">Stadium shape</button>
<div class="content">
<div class="pre-scrollable">
<pre>
flowchart LR
id1([This is the text in the box])
</pre>
</div>
</th>
<td>
<pre id="diagram1" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
stateDiagram-v2
stateA
</pre>
</td>
<td>
<pre id="diagram2" class="mermaid">
%%{init: {"look": "handdrawn"} }%%
flowchart LR
id1[[This is the text in the box]]
</pre>
</td>
</tr>
</table>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import { layouts } from './mermaid-layout-elk.esm.mjs';
mermaid.registerLayoutLoaders(layouts);
mermaid.parseError = function (err, hash) {
};
mermaid.initialize({
handdrawn: false,
mergeEdges: true,
layout: 'dagre',
flowchart: { titleTopMargin: 10 },
// fontFamily: 'Caveat',
fontFamily: 'Kalam',
sequence: {
actorFontFamily: 'courier',
noteFontFamily: 'courier',
messageFontFamily: 'courier',
},
fontSize: 16,
logLevel: 0,
});
function callback() {
alert('It worked');
}
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);
};
let coll = document.getElementsByClassName("collapsible");
for (const element of coll) {
element.addEventListener("click", function () {
this.classList.toggle("active");
let content = this.nextElementSibling;
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
</script>
</body>
</html>

View File

@@ -1,433 +0,0 @@
<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"
/>
<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;
}
.malware {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 150px;
background: red;
color: black;
display: flex;
display: flex;
justify-content: center;
align-items: center;
font-family: monospace;
font-size: 72px;
}
/* tspan {
font-size: 6px !important;
} */
</style>
</head>
<body>
<pre id="diagram" class="mermaid">
stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*] </pre
>
<pre id="diagram" class="mermaid2">
flowchart RL
subgraph "`one`"
a1 -- l1 --> a2
a1 -- l2 --> a2
end
</pre>
<pre id="diagram" class="mermaid">
flowchart RL
subgraph "`one`"
a1 -- l1 --> a2
a1 -- l2 --> a2
end
</pre>
<pre id="diagram" class="mermaid2">
flowchart
id["`A root with a long text that wraps to keep the node size in check. A root with a long text that wraps to keep the node size in check`"]</pre
>
<pre id="diagram" class="mermaid2">
flowchart LR
A[A text that needs to be wrapped wraps to another line]
B[A text that needs to be<br/>wrapped wraps to another line]
C["`A text that needs to be wrapped to another line`"]</pre>
<pre id="diagram" class="mermaid2">
flowchart LR
C["`A text
that needs
to be wrapped
in another
way`"]
</pre
>
<pre id="diagram" class="mermaid">
classDiagram-v2
note "I love this diagram!\nDo you love it?"
</pre>
<pre id="diagram" class="mermaid">
stateDiagram-v2
State1: The state with a note with minus - and plus + in it
note left of State1
Important information! You can write
notes with . and in them.
end note </pre
>
<pre id="diagram" class="mermaid2">
mindmap
root
Child3(A node with an icon and with a long text that wraps to keep the node size in check)
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"theme": "forest"} }%%
mindmap
id1[**Start2**<br/>end]
id2[**Start2**<br />end]
%% Another comment
id3[**Start2**<br>end] %% Comment
id4[**Start2**<br >end<br >the very end]
</pre>
<pre id="diagram" class="mermaid2">
mindmap
id1["`**Start2**
second line 😎 with long text that is wrapping to the next line`"]
id2["`Child **with bold** text`"]
id3["`Children of which some
is using *italic type of* text`"]
id4[Child]
id5["`Child
Row
and another
`"]
</pre>
<pre id="diagram" class="mermaid2">
mindmap
id1("`**Root**`"]
id2["`A formatted text... with **bold** and *italics*`"]
id3[Regular labels works as usual]
id4["`Emojis and unicode works too: 🤓
शान्तिः سلام 和平 `"]
</pre>
<pre id="diagram" class="mermaid">
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
subgraph ibm[IBM Espresso CPU]
core0[IBM PowerPC Broadway Core 0]
core1[IBM PowerPC Broadway Core 1]
core2[IBM PowerPC Broadway Core 2]
rom[16 KB ROM]
core0 --- core2
rom --> core2
end
subgraph amd["`**AMD** Latte GPU`"]
mem[Memory & I/O Bridge]
dram[DRAM Controller]
edram[32 MB EDRAM MEM1]
rom[512 B SEEPROM]
sata[SATA IF]
exi[EXI]
subgraph gx[GX]
sram[3 MB 1T-SRAM]
end
radeon[AMD Radeon R7xx GX2]
mem --- gx
mem --- radeon
rom --- mem
mem --- sata
mem --- exi
dram --- sata
dram --- exi
end
ddr3[2 GB DDR3 RAM MEM2]
mem --- ddr3
dram --- ddr3
edram --- ddr3
core1 --- mem
exi --- rtc
rtc{{rtc}}
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"flowchart": {"defaultRenderer": "elk", "htmlLabels": false}} }%%
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
subgraph ibm[IBM Espresso CPU]
core0[IBM PowerPC Broadway Core 0]
core1[IBM PowerPC Broadway Core 1]
core2[IBM PowerPC Broadway Core 2]
rom[16 KB ROM]
core0 --- core2
rom --> core2
end
subgraph amd["`**AMD** Latte GPU`"]
mem[Memory & I/O Bridge]
dram[DRAM Controller]
edram[32 MB EDRAM MEM1]
rom[512 B SEEPROM]
sata[SATA IF]
exi[EXI]
subgraph gx[GX]
sram[3 MB 1T-SRAM]
end
radeon[AMD Radeon R7xx GX2]
mem --- gx
mem --- radeon
rom --- mem
mem --- sata
mem --- exi
dram --- sata
dram --- exi
end
ddr3[2 GB DDR3 RAM MEM2]
mem --- ddr3
dram --- ddr3
edram --- ddr3
core1 --- mem
exi --- rtc
rtc{{rtc}}
</pre
>
<br />
<pre id="diagram" class="mermaid2">
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
subgraph ibm[IBM Espresso CPU]
core0[IBM PowerPC Broadway Core 0]
core1[IBM PowerPC Broadway Core 1]
core2[IBM PowerPC Broadway Core 2]
rom[16 KB ROM]
core0 --- core2
rom --> core2
end
subgraph amd[AMD Latte GPU]
mem[Memory & I/O Bridge]
dram[DRAM Controller]
edram[32 MB EDRAM MEM1]
rom[512 B SEEPROM]
sata[SATA IF]
exi[EXI]
subgraph gx[GX]
sram[3 MB 1T-SRAM]
end
radeon[AMD Radeon R7xx GX2]
mem --- gx
mem --- radeon
rom --- mem
mem --- sata
mem --- exi
dram --- sata
dram --- exi
end
ddr3[2 GB DDR3 RAM MEM2]
mem --- ddr3
dram --- ddr3
edram --- ddr3
core1 --- mem
exi --- rtc
rtc{{rtc}}
</pre
>
<br />
&nbsp;
<pre id="diagram" class="mermaid2">
flowchart LR
B1 --be be--x B2
B1 --bo bo--o B3
subgraph Ugge
B2
B3
subgraph inner
B4
B5
end
subgraph inner2
subgraph deeper
C4
C5
end
C6
end
B4 --> C4
B3 -- X --> B4
B2 --> inner
C4 --> C5
end
subgraph outer
B6
end
B6 --> B5
</pre
>
<pre id="diagram" class="mermaid2">
sequenceDiagram
Customer->>+Stripe: Makes a payment request
Stripe->>+Bank: Forwards the payment request to the bank
Bank->>+Customer: Asks for authorization
Customer->>+Bank: Provides authorization
Bank->>+Stripe: Sends a response with payment details
Stripe->>+Merchant: Sends a notification of payment receipt
Merchant->>+Stripe: Confirms the payment
Stripe->>+Customer: Sends a confirmation of payment
Customer->>+Merchant: Receives goods or services
</pre
>
<pre id="diagram" class="mermaid2">
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness<br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
Pen and paper
Mermaid
</pre>
<br />
<pre id="diagram" class="mermaid2">
example-diagram
</pre>
<!-- <div id="cy"></div> -->
<!-- <script src="http://localhost:9000/packages/mermaid-mindmap/dist/mermaid-mindmap-detector.js"></script> -->
<!-- <script src="./mermaid-example-diagram-detector.js"></script> -->
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@9.1.7/dist/mermaid.min.js"></script> -->
<!-- <script src="./mermaid.js"></script> -->
<scrpt>
// import mindmap from '../../packages/mermaid-mindmap/src/detector'; // import example from
'../../packages/mermaid-example-diagram/src/mermaid-example-diagram.core.mjs'; import mermaid
from './mermaid.esm.mjs'; // await mermaid.registerExternalDiagrams([example]);
mermaid.parseError = function (err, hash) { // console.error('Mermaid error: ', err); };
mermaid.initialize({ // theme: 'forest', startOnLoad: true, logLevel: 0, flowchart: { //
defaultRenderer: 'elk', useMaxWidth: false, // htmlLabels: false, htmlLabels: true, }, //
htmlLabels: false, gantt: { useMaxWidth: false, }, useMaxWidth: false, }); function callback()
{ alert('It worked'); } mermaid.parseError = function (err, hash) { console.error('In parse
error:'); console.error(err); }; // mermaid.test1('first_slow', 1200).then((r) =>
console.info(r)); // mermaid.test1('second_fast', 200).then((r) => console.info(r)); //
mermaid.test1('third_fast', 200).then((r) => console.info(r)); // mermaid.test1('forth_slow',
1200).then((r) => console.info(r));
</scrpt>
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/mermaid@10.2.0/dist/mermaid.min.js"
></script>
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10.2.0/dist/mermaid.min.js';
(function () {
mermaid.initialize({ startOnLoad: false });
const elements = document.getElementsByClassName('mermaid');
console.log(elements);
let id = 0;
[...elements].forEach((elem) => {
const insertSvg = function (svgCode) {
elem.innerHTML = svgCode;
};
console.log(atob(elem.innerText));
mermaid.render(`graphDiv-${id++}`, atob(elem.innerText), insertSvg);
});
})();
</script>
</body>
</html>

View File

@@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
@@ -14,45 +14,33 @@
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; */
background: #333;
font-family: 'Arial';
/* font-size: 18px !important; */
}
h1 {
color: grey;
}
.mermaid {
border: 1px solid #ddd;
margin: 10px;
}
.mermaid2 {
display: none;
}
.mermaid svg {
/* font-size: 18px !important; */
/* background-color: #efefef;
background-image: radial-gradient(#fff 51%, transparent 91%),
radial-gradient(#fff 51%, transparent 91%);
/* background-color: #efefef; */
background-color: #333;
background-image: radial-gradient(#333 51%, transparent 91%),
radial-gradient(#333 51%, transparent 91%);
background-size: 20px 20px;
background-position:
0 0,
10px 10px;
background-repeat: repeat; */
background-position: 0 0, 10px 10px;
background-repeat: repeat;
border: 2px solid rgb(131, 142, 205);
}
.malware {
position: fixed;
@@ -70,202 +58,544 @@
font-size: 72px;
}
/* tspan {
font-size: 6px !important;
} */
font-size: 6px !important;
} */
</style>
</head>
<body>
<pre id="diagram" class="mermaid2">
%%{
init: {
"theme":"base",
"fontFamily": "Kalam",
"themeVariables": {
"background": "#FFFFFF",
"primaryColor": "#7bdfa7",
"primaryTextColor": "#3c3c3b",
"secondaryColor": "#642470",
"secondaryTextColor": "#3c3c3b",
"tertiaryColor": "#1c736D",
"tertiaryTextColor": "#3c3c3b",
"noteBkgColor": "#9fd8ef",
"loopTextColor": "#636362",
"labelBoxBkgColor": "#642470",
"labelBoxBorderColor": "#642470",
"labelTextColor": "#d4d4d4",
"signalTextColor": "#636362",
"signalColor": "#642470"
}
}
}%%
sequenceDiagram
Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you!
John-->>-Alice: I feel great!
</pre
>
<pre id="diagram" class="mermaid2">
%%{
init: {
"theme":"base",
"fontFamily": "Forth Bold",
"themeVariables": {
"background": "#FFFFFF",
"primaryColor": "#7bdfa7",
"primaryTextColor": "#3c3c3b",
"secondaryColor": "#642470",
"secondaryTextColor": "#3c3c3b",
"tertiaryColor": "#1c736D",
"tertiaryTextColor": "#3c3c3b",
"noteBkgColor": "#9fd8ef",
"loopTextColor": "#636362",
"labelBoxBkgColor": "#642470",
"labelBoxBorderColor": "#642470",
"labelTextColor": "#d4d4d4",
"signalTextColor": "#636362",
"signalColor": "#642470"
}
}
}%%
sequenceDiagram
Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you!
John-->>-Alice: I feel great!
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"layout": "elk", "mergeEdges": true} }%%
stateDiagram
A --> B
</pre
>
<pre id="diagram" class="mermaid">
%%{init: {"layout": "elk", "mergeEdges": true} }%%
flowchart
A --> B(This is B)
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"layout": "elk", "mergeEdges": false, "elk.nodePlacement.strategy": "NETWORK_SIMPLEX"} }%%
stateDiagram
State T0 {
direction LR
A --> B
}
State T1 {
[*] --> NumLockOff
NumLockOff --> NumLockOn : EvNumLockPressed
NumLockOn --> NumLockOff : EvNumLockPressed
}
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"layout": "dagre", "mergeEdges": true} }%%
stateDiagram
direction TB
State T1 {
T11
}
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"layout": "dagre", "mergeEdges": true} }%%
stateDiagram
State T1 {
T21
--
T22
}
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"layout": "elk", "mergeEdges": true} }%%
stateDiagram
direction TB
State T1 {
T11
}
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"layout": "elk", "mergeEdges": true} }%%
stateDiagram
State T1 {
T21
--
T22
}
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"layout": "elk", "mergeEdges": true} }%%
stateDiagram
[*] --> T1
T1 --> T2
T1 --> T3
T1 --> T4
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"layout": "elk"} }%%
stateDiagram
[*] --> T1
T1 --> T2
T2 --> T3
T3 --> T1
T1 --> T3
</pre
>
<pre id="diagram" class="mermaid2">
stateDiagram
State1: The state with a note
note right of State1
Important information! You can write
notes.
end note
</pre
>
<pre id="diagram" class="mermaid2">
stateDiagram-v2
direction LR
[*] --> Active
block-beta
blockArrowId<["Label"]>(right)
blockArrowId2<["Label"]>(left)
blockArrowId3<["Label"]>(up)
blockArrowId4<["Label"]>(down)
blockArrowId5<["Label"]>(x)
blockArrowId6<["Label"]>(y)
blockArrowId6<["Label"]>(x, down)
</pre>
<pre id="diagram" class="mermaid">
block-beta
block:e:4
columns 2
f
g
end
state Active {
direction BT
[*] --> Inner
Inner --> NumLockOn : EvNumLockPressed
}
%% Outer --> Inner
</pre
</pre>
<pre id="diagram" class="mermaid">
block-beta
block:e:4
columns 2
f
g
h
end
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 4
a b c d
block:e:4
columns 2
f
g
h
end
i:4
</pre>
<pre id="diagram" class="mermaid2">
flowchart LR
X-- "y" -->z
</pre>
<pre id="diagram" class="mermaid2">
block-beta
columns 5
A space B
A --x B
</pre>
<pre id="diagram" class="mermaid2">
block-beta
columns 3
a["A wide one"] b:2 c:2 d
</pre>
<pre id="diagram" class="mermaid2">
block-beta
block:e
f
end
</pre>
<pre id="diagram" class="mermaid2">
block-beta
columns 3
a:3
block:e:3
f
end
g
</pre>
<pre id="diagram" class="mermaid2">
block-beta
columns 3
a:3
block:e:3
f
g
end
h
i
j
</pre>
<pre id="diagram" class="mermaid2">
block-beta
columns 3
a b:2
block:e:3
f
end
g h i
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 3
a b c
e:3
f g h
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#f9F,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram" class="mermaid">
block-beta
columns 5
A1:3
A2:1
A3
B1 B2 B3:3
</pre>
<pre id="diagram" class="mermaid2">
block-beta
block
D
E
end
db("This is the text in the box")
</pre>
<pre id="diagram" class="mermaid2">
block-beta
block
D
end
A["A: I am a wide one"]
</pre>
<pre id="diagram" class="mermaid2">
block-beta
A["square"]
B("rounded")
C(("circle"))
</pre>
<pre id="diagram" class="mermaid2">
block-beta
A>"rect_left_inv_arrow"]
B{"diamond"}
C{{"hexagon"}}
</pre>
<pre id="diagram" class="mermaid2">
block-beta
A(["stadium"])
</pre>
<pre id="diagram" class="mermaid2">
block-beta
%% A[["subroutine"]]
%% B[("cylinder")]
C>"surprise"]
</pre>
<pre id="diagram" class="mermaid2">
block-beta
A[/"lean right"/]
B[\"lean left"\]
C[/"trapezoid"\]
D[\"trapezoid"/]
</pre>
<pre id="diagram" class="mermaid2">
flowchart
B
style B fill:#f9F,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram" class="mermaid2">
flowchart LR
a1 -- apa --> b1
</pre>
<pre id="diagram" class="mermaid2">
flowchart RL
subgraph "`one`"
id
end
</pre>
<pre id="diagram" class="mermaid2">
flowchart RL
subgraph "`one`"
a1 -- l1 --> a2
a1 -- l2 --> a2
end
</pre>
<pre id="diagram" class="mermaid2">
flowchart
id["`A root with a long text that wraps to keep the node size in check. A root with a long text that wraps to keep the node size in check`"]</pre
>
<pre id="diagram" class="mermaid2">
flowchart LR
A[A text that needs to be wrapped wraps to another line]
B[A text that needs to be<br/>wrapped wraps to another line]
C["`A text that needs to be wrapped to another line`"]</pre>
<pre id="diagram" class="mermaid2">
flowchart LR
C["`A text
that needs
to be wrapped
in another
way`"]
</pre
>
<pre id="diagram" class="mermaid2">
classDiagram-v2
note "I love this diagram!\nDo you love it?"
</pre>
<pre id="diagram" class="mermaid2">
stateDiagram-v2
State1: The state with a note with minus - and plus + in it
note left of State1
Important information! You can write
notes with . and in them.
end note </pre
>
<pre id="diagram" class="mermaid2">
mindmap
root
Child3(A node with an icon and with a long text that wraps to keep the node size in check)
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"theme": "forest"} }%%
mindmap
id1[**Start2**<br/>end]
id2[**Start2**<br />end]
%% Another comment
id3[**Start2**<br>end] %% Comment
id4[**Start2**<br >end<br >the very end]
</pre>
<pre id="diagram" class="mermaid2">
mindmap
id1["`**Start2**
second line 😎 with long text that is wrapping to the next line`"]
id2["`Child **with bold** text`"]
id3["`Children of which some
is using *italic type of* text`"]
id4[Child]
id5["`Child
Row
and another
`"]
</pre>
<pre id="diagram" class="mermaid2">
mindmap
id1("`**Root**`"]
id2["`A formatted text... with **bold** and *italics*`"]
id3[Regular labels works as usual]
id4["`Emojis and unicode works too: 🤓
शान्तिः سلام 和平 `"]
</pre>
<pre id="diagram" class="mermaid2">
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
subgraph ibm[IBM Espresso CPU]
core0[IBM PowerPC Broadway Core 0]
core1[IBM PowerPC Broadway Core 1]
core2[IBM PowerPC Broadway Core 2]
rom[16 KB ROM]
core0 --- core2
rom --> core2
end
subgraph amd["`**AMD** Latte GPU`"]
mem[Memory & I/O Bridge]
dram[DRAM Controller]
edram[32 MB EDRAM MEM1]
rom[512 B SEEPROM]
sata[SATA IF]
exi[EXI]
subgraph gx[GX]
sram[3 MB 1T-SRAM]
end
radeon[AMD Radeon R7xx GX2]
mem --- gx
mem --- radeon
rom --- mem
mem --- sata
mem --- exi
dram --- sata
dram --- exi
end
ddr3[2 GB DDR3 RAM MEM2]
mem --- ddr3
dram --- ddr3
edram --- ddr3
core1 --- mem
exi --- rtc
rtc{{rtc}}
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"flowchart": {"defaultRenderer": "elk", "htmlLabels": false}} }%%
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
subgraph ibm[IBM Espresso CPU]
core0[IBM PowerPC Broadway Core 0]
core1[IBM PowerPC Broadway Core 1]
core2[IBM PowerPC Broadway Core 2]
rom[16 KB ROM]
core0 --- core2
rom --> core2
end
subgraph amd["`**AMD** Latte GPU`"]
mem[Memory & I/O Bridge]
dram[DRAM Controller]
edram[32 MB EDRAM MEM1]
rom[512 B SEEPROM]
sata[SATA IF]
exi[EXI]
subgraph gx[GX]
sram[3 MB 1T-SRAM]
end
radeon[AMD Radeon R7xx GX2]
mem --- gx
mem --- radeon
rom --- mem
mem --- sata
mem --- exi
dram --- sata
dram --- exi
end
ddr3[2 GB DDR3 RAM MEM2]
mem --- ddr3
dram --- ddr3
edram --- ddr3
core1 --- mem
exi --- rtc
rtc{{rtc}}
</pre
>
<br />
<pre id="diagram" class="mermaid2">
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
subgraph ibm[IBM Espresso CPU]
core0[IBM PowerPC Broadway Core 0]
core1[IBM PowerPC Broadway Core 1]
core2[IBM PowerPC Broadway Core 2]
rom[16 KB ROM]
core0 --- core2
rom --> core2
end
subgraph amd[AMD Latte GPU]
mem[Memory & I/O Bridge]
dram[DRAM Controller]
edram[32 MB EDRAM MEM1]
rom[512 B SEEPROM]
sata[SATA IF]
exi[EXI]
subgraph gx[GX]
sram[3 MB 1T-SRAM]
end
radeon[AMD Radeon R7xx GX2]
mem --- gx
mem --- radeon
rom --- mem
mem --- sata
mem --- exi
dram --- sata
dram --- exi
end
ddr3[2 GB DDR3 RAM MEM2]
mem --- ddr3
dram --- ddr3
edram --- ddr3
core1 --- mem
exi --- rtc
rtc{{rtc}}
</pre
>
<br />
&nbsp;
<pre id="diagram" class="mermaid2">
flowchart LR
B1 --be be--x B2
B1 --bo bo--o B3
subgraph Ugge
B2
B3
subgraph inner
B4
B5
end
subgraph inner2
subgraph deeper
C4
C5
end
C6
end
B4 --> C4
B3 -- X --> B4
B2 --> inner
C4 --> C5
end
subgraph outer
B6
end
B6 --> B5
</pre
>
<pre id="diagram" class="mermaid2">
sequenceDiagram
Customer->>+Stripe: Makes a payment request
Stripe->>+Bank: Forwards the payment request to the bank
Bank->>+Customer: Asks for authorization
Customer->>+Bank: Provides authorization
Bank->>+Stripe: Sends a response with payment details
Stripe->>+Merchant: Sends a notification of payment receipt
Merchant->>+Stripe: Confirms the payment
Stripe->>+Customer: Sends a confirmation of payment
Customer->>+Merchant: Receives goods or services
</pre
>
<pre id="diagram" class="mermaid2">
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness<br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
Pen and paper
Mermaid
</pre>
<br />
<pre id="diagram" class="mermaid2">
example-diagram
</pre>
<!-- <div id="cy"></div> -->
<!-- <script src="http://localhost:9000/packages/mermaid-mindmap/dist/mermaid-mindmap-detector.js"></script> -->
<!-- <script src="./mermaid-example-diagram-detector.js"></script> -->
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@9.1.7/dist/mermaid.min.js"></script> -->
<!-- <script src="./mermaid.js"></script> -->
<script type="module">
// import mindmap from '../../packages/mermaid-mindmap/src/detector';
// import example from '../../packages/mermaid-example-diagram/src/mermaid-example-diagram.core.mjs';
import mermaid from './mermaid.esm.mjs';
import { layouts } from './mermaid-layout-elk.esm.mjs';
mermaid.registerLayoutLoaders(layouts);
// await mermaid.registerExternalDiagrams([example]);
mermaid.parseError = function (err, hash) {
console.error('Mermaid error: ', err);
// console.error('Mermaid error: ', err);
};
// mermaid.initialize({
// // theme: 'forest',
// startOnLoad: true,
// logLevel: 0,
// flowchart: {
// // defaultRenderer: 'elk',
// useMaxWidth: false,
// // htmlLabels: false,
// htmlLabels: true,
// },
// // htmlLabels: false,
// gantt: {
// useMaxWidth: false,
// },
// useMaxWidth: false,
// });
mermaid.initialize({
theme: 'base',
handdrawnSeed: 12,
look: 'handdrawn',
'elk.nodePlacement.strategy': 'NETWORK_SIMPLEX',
// layout: 'dagre',
layout: 'elk',
flowchart: { titleTopMargin: 10 },
// fontFamily: 'Caveat',
// fontFamily: 'Kalam',
fontFamily: 'courier',
sequence: {
actorFontFamily: 'courier',
noteFontFamily: 'courier',
messageFontFamily: 'courier',
},
fontSize: 12,
theme: 'dark',
startOnLoad: true,
logLevel: 0,
});
function callback() {

View File

@@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@
import '@cypress/code-coverage/support';
import '@applitools/eyes-cypress/commands';
import '@argos-ci/cypress/support';
// Import commands.js using ES2015 syntax:
import './commands';

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
"target": "es2020",
"lib": ["es2020", "dom"],
"types": ["cypress", "node"],
"types": ["cypress", "node", "@argos-ci/cypress/dist/support.d.ts"],
"allowImportingTsExtensions": true,
"noEmit": true
},

View File

@@ -1,4 +1,3 @@
version: '3.9'
services:
mermaid:
build:
@@ -8,7 +7,7 @@ services:
tty: true
working_dir: /mermaid
mem_limit: '8G'
entrypoint: docker-entrypoint.sh
entrypoint: ./docker-entrypoint.sh
environment:
- NODE_OPTIONS=--max_old_space_size=8192
volumes:
@@ -16,6 +15,7 @@ services:
- root_cache:/root/.cache
- root_local:/root/.local
- root_npm:/root/.npm
- /tmp:/tmp
ports:
- 9000:9000
- 3333:3333

View File

@@ -56,7 +56,7 @@ The following commands must be sufficient enough to start with:
```bash
curl -fsSL https://get.pnpm.io/install.sh | sh -
pnpm env use --global 18
pnpm env use --global 20
```
You may also need to reload `.shrc` or `.bashrc` afterwards.

View File

@@ -118,7 +118,7 @@ The siteConfig
#### Defined in
[config.ts:221](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L221)
[config.ts:218](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L218)
---

View File

@@ -62,6 +62,7 @@
},
"devDependencies": {
"@applitools/eyes-cypress": "^3.42.3",
"@argos-ci/cypress": "^2.0.5",
"@cspell/eslint-plugin": "^8.6.0",
"@cypress/code-coverage": "^3.12.30",
"@rollup/plugin-typescript": "^11.1.6",
@@ -126,10 +127,5 @@
},
"nyc": {
"report-dir": "coverage/cypress"
},
"pnpm": {
"patchedDependencies": {
"cytoscape@3.28.1": "patches/cytoscape@3.28.1.patch"
}
}
}

View File

@@ -1,43 +0,0 @@
{
"name": "@mermaid-js/layout-elk",
"version": "0.0.1",
"description": "ELK layout engine for mermaid",
"module": "dist/mermaid-layout-elk.core.mjs",
"types": "dist/packages/mermaid-layout-elk/src/index.d.ts",
"type": "module",
"exports": {
".": {
"import": "./dist/mermaid-layout-elk.core.mjs",
"types": "./dist/packages/mermaid-layout-elk/src/index.d.ts"
},
"./*": "./*"
},
"keywords": [
"diagram",
"markdown",
"elk",
"mermaid"
],
"scripts": {
"prepublishOnly": "pnpm -w run build"
},
"repository": {
"type": "git",
"url": "https://github.com/mermaid-js/mermaid"
},
"contributors": [
"Knut Sveidqvist",
"Sidharth Vinod"
],
"license": "MIT",
"dependencies": {
"elkjs": "^0.9.3",
"d3": "^7.9.0"
},
"peerDependencies": {
"mermaid": "workspace:^"
},
"files": [
"dist"
]
}

View File

@@ -1,25 +0,0 @@
export interface TreeData {
parentById: Record<string, string>;
childrenById: Record<string, string[]>;
}
export const findCommonAncestor = (id1: string, id2: string, treeData: TreeData) => {
const { parentById } = treeData;
const visited = new Set();
let currentId = id1;
while (currentId) {
visited.add(currentId);
if (currentId === id2) {
return currentId;
}
currentId = parentById[currentId];
}
currentId = id2;
while (currentId) {
if (visited.has(currentId)) {
return currentId;
}
currentId = parentById[currentId];
}
return 'root';
};

View File

@@ -1,17 +0,0 @@
import type { LayoutLoaderDefinition } from 'mermaid';
const loader = async () => await import(`./render.js`);
const algos = ['elk.stress', 'elk.force', 'elk.mrtree', 'elk.sporeOverlap'];
export const layouts: LayoutLoaderDefinition[] = [
{
name: 'elk',
loader,
algorithm: 'elk.layered',
},
...algos.map((algo) => ({
name: algo,
loader,
algorithm: algo,
})),
];

View File

@@ -1,588 +0,0 @@
// @ts-nocheck File not ready to check types
import { curveLinear } from 'd3';
import ELK from 'elkjs/lib/elk.bundled.js';
import mermaid from 'mermaid';
import { findCommonAncestor } from './find-common-ancestor.js';
import config from '../../mermaid/src/defaultConfig';
const {
common,
getConfig,
insertCluster,
insertEdge,
insertEdgeLabel,
insertMarkers,
insertNode,
interpolateToCurve,
labelHelper,
log,
positionEdgeLabel,
} = mermaid.internalHelpers;
const nodeDb = {};
const portPos = {};
const clusterDb = {};
export const addVertex = async (nodeEl, graph, nodeArr, node) => {
const labelData = { width: 0, height: 0 };
const ports = [
{
id: node.id + '-west',
layoutOptions: {
'port.side': 'WEST',
},
},
{
id: node.id + '-east',
layoutOptions: {
'port.side': 'EAST',
},
},
{
id: node.id + '-south',
layoutOptions: {
'port.side': 'SOUTH',
},
},
{
id: node.id + '-north',
layoutOptions: {
'port.side': 'NORTH',
},
},
];
let boundingBox;
const child = {
...node,
ports: node.shape === 'diamond' ? ports : [],
};
graph.children.push(child);
nodeDb[node.id] = child;
// // Add the element to the DOM
if (!node.isGroup) {
const childNodeEl = await insertNode(nodeEl, node, node.dir);
boundingBox = childNodeEl.node().getBBox();
child.domId = childNodeEl;
child.width = boundingBox.width;
child.height = boundingBox.height;
} else {
child.children = [];
await addVertices(nodeEl, nodeArr, child, node.id);
if (node.label) {
const { shapeSvg, bbox } = await labelHelper(nodeEl, node, undefined, true);
labelData.width = bbox.width;
labelData.wrappingWidth = getConfig().flowchart.wrappingWidth;
labelData.height = bbox.height - 8;
labelData.labelNode = shapeSvg.node();
// We need the label hight to be able to size the subgraph;
shapeSvg.remove();
} else {
// Subgraph without label
labelData.width = 0;
labelData.height = 0;
}
child.labelData = labelData;
child.domId = nodeEl;
}
};
export const addVertices = async function (nodeEl, nodeArr, graph, parentId) {
const siblings = nodeArr.filter((node) => node.parentId === parentId);
log.info('addVertices DAGA', siblings, parentId);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
await Promise.all(
siblings.map(async (node) => {
await addVertex(nodeEl, graph, nodeArr, node);
})
);
return graph;
};
const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, depth) => {
nodeArray.forEach(function (node) {
if (node) {
nodeDb[node.id] = node;
nodeDb[node.id].offset = {
posX: node.x + relX,
posY: node.y + relY,
x: relX,
y: relY,
depth,
width: node.width,
height: node.height,
};
if (node.isGroup) {
log.debug('Id abc88 subgraph = ', node.id, node.x, node.y, node.labelData);
const subgraphEl = subgraphsEl.insert('g').attr('class', 'subgraph');
// TODO use faster way of cloning
const clusterNode = JSON.parse(JSON.stringify(node));
clusterNode.x = node.offset.posX + node.width / 2;
clusterNode.y = node.offset.posY + node.height / 2;
const cluster = insertCluster(subgraphEl, clusterNode);
log.info('Id (UGH)= ', node.shape, node.labels);
} else {
log.info(
'Id NODE = ',
node.id,
node.x,
node.y,
relX,
relY,
node.domId.node(),
`translate(${node.x + relX + node.width / 2}, ${node.y + relY + node.height / 2})`
);
node.domId.attr(
'transform',
`translate(${node.x + relX + node.width / 2}, ${node.y + relY + node.height / 2})`
);
}
}
});
nodeArray.forEach(function (node) {
if (node && node.isGroup) {
drawNodes(relX + node.x, relY + node.y, node.children, svg, subgraphsEl, depth + 1);
}
});
};
const getNextPort = (node, edgeDirection, graphDirection) => {
log.info('getNextPort abc88', { node, edgeDirection, graphDirection });
if (!portPos[node]) {
switch (graphDirection) {
case 'TB':
case 'TD':
portPos[node] = {
inPosition: 'north',
outPosition: 'south',
};
break;
case 'BT':
portPos[node] = {
inPosition: 'south',
outPosition: 'north',
};
break;
case 'RL':
portPos[node] = {
inPosition: 'east',
outPosition: 'west',
};
break;
case 'LR':
portPos[node] = {
inPosition: 'west',
outPosition: 'east',
};
break;
}
}
const result = edgeDirection === 'in' ? portPos[node].inPosition : portPos[node].outPosition;
if (edgeDirection === 'in') {
portPos[node].inPosition = getNextPosition(
portPos[node].inPosition,
edgeDirection,
graphDirection
);
} else {
portPos[node].outPosition = getNextPosition(
portPos[node].outPosition,
edgeDirection,
graphDirection
);
}
return result;
};
const addSubGraphs = function (nodeArr) {
const parentLookupDb = { parentById: {}, childrenById: {} };
const subgraphs = nodeArr.filter((node) => node.isGroup);
log.info('Subgraphs - ', subgraphs);
subgraphs.forEach(function (subgraph) {
const children = nodeArr.filter((node) => node.parentId === subgraph.id);
children.forEach(function (node) {
parentLookupDb.parentById[node.id] = subgraph.id;
if (parentLookupDb.childrenById[subgraph.id] === undefined) {
parentLookupDb.childrenById[subgraph.id] = [];
}
parentLookupDb.childrenById[subgraph.id].push(node);
});
});
subgraphs.forEach(function (subgraph) {
const data = { id: subgraph.id };
if (parentLookupDb.parentById[subgraph.id] !== undefined) {
data.parent = parentLookupDb.parentById[subgraph.id];
}
});
return parentLookupDb;
};
const getEdgeStartEndPoint = (edge, dir) => {
let source = edge.start;
let target = edge.end;
// Save the original source and target
const sourceId = source;
const targetId = target;
const startNode = nodeDb[edge.start.id];
const endNode = nodeDb[edge.end.id];
if (!startNode || !endNode) {
return { source, target };
}
if (startNode.shape === 'diamond') {
source = `${source}-${getNextPort(source, 'out', dir)}`;
}
if (endNode.shape === 'diamond') {
target = `${target}-${getNextPort(target, 'in', dir)}`;
}
// Add the edge to the graph
return { source, target, sourceId, targetId };
};
const calcOffset = function (src, dest, parentLookupDb) {
const ancestor = findCommonAncestor(src, dest, parentLookupDb);
if (ancestor === undefined || ancestor === 'root') {
return { x: 0, y: 0 };
}
const ancestorOffset = nodeDb[ancestor].offset;
return { x: ancestorOffset.posX, y: ancestorOffset.posY };
};
/**
* Add edges to graph based on parsed graph definition
*/
export const addEdges = function (dataForLayout, graph, svg) {
log.info('abc78 DAGA edges = ', dataForLayout);
const edges = dataForLayout.edges;
const labelsEl = svg.insert('g').attr('class', 'edgeLabels');
const linkIdCnt = {};
const dir = dataForLayout.direction || 'DOWN';
let defaultStyle;
let defaultLabelStyle;
edges.forEach(function (edge) {
// Identify Link
const linkIdBase = edge.id; // 'L-' + edge.start + '-' + edge.end;
// count the links from+to the same node to give unique id
if (linkIdCnt[linkIdBase] === undefined) {
linkIdCnt[linkIdBase] = 0;
log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);
} else {
linkIdCnt[linkIdBase]++;
log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);
}
const linkId = linkIdBase + '_' + linkIdCnt[linkIdBase];
edge.id = linkId;
log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
const linkNameStart = 'LS_' + edge.start;
const linkNameEnd = 'LE_' + edge.end;
const edgeData = { style: '', labelStyle: '' };
edgeData.minlen = edge.length || 1;
edge.text = edge.label;
// Set link type for rendering
if (edge.type === 'arrow_open') {
edgeData.arrowhead = 'none';
} else {
edgeData.arrowhead = 'normal';
}
// Check of arrow types, placed here in order not to break old rendering
edgeData.arrowTypeStart = 'arrow_open';
edgeData.arrowTypeEnd = 'arrow_open';
/* eslint-disable no-fallthrough */
switch (edge.type) {
case 'double_arrow_cross':
edgeData.arrowTypeStart = 'arrow_cross';
case 'arrow_cross':
edgeData.arrowTypeEnd = 'arrow_cross';
break;
case 'double_arrow_point':
edgeData.arrowTypeStart = 'arrow_point';
case 'arrow_point':
edgeData.arrowTypeEnd = 'arrow_point';
break;
case 'double_arrow_circle':
edgeData.arrowTypeStart = 'arrow_circle';
case 'arrow_circle':
edgeData.arrowTypeEnd = 'arrow_circle';
break;
}
let style = '';
let labelStyle = '';
switch (edge.stroke) {
case 'normal':
style = 'fill:none;';
if (defaultStyle !== undefined) {
style = defaultStyle;
}
if (defaultLabelStyle !== undefined) {
labelStyle = defaultLabelStyle;
}
edgeData.thickness = 'normal';
edgeData.pattern = 'solid';
break;
case 'dotted':
edgeData.thickness = 'normal';
edgeData.pattern = 'dotted';
edgeData.style = 'fill:none;stroke-width:2px;stroke-dasharray:3;';
break;
case 'thick':
edgeData.thickness = 'thick';
edgeData.pattern = 'solid';
edgeData.style = 'stroke-width: 3.5px;fill:none;';
break;
}
// if (edge.style !== undefined) {
// const styles = getStylesFromArray(edge.style);
// style = styles.style;
// labelStyle = styles.labelStyle;
// }
edgeData.style = edgeData.style += style;
edgeData.labelStyle = edgeData.labelStyle += labelStyle;
const conf = getConfig();
if (edge.interpolate !== undefined) {
edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear);
} else if (edges.defaultInterpolate !== undefined) {
edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear);
} else {
edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
}
if (edge.text === undefined) {
if (edge.style !== undefined) {
edgeData.arrowheadStyle = 'fill: #333';
}
} else {
edgeData.arrowheadStyle = 'fill: #333';
edgeData.labelpos = 'c';
}
edgeData.labelType = edge.labelType;
edgeData.label = (edge?.text || '').replace(common.lineBreakRegex, '\n');
if (edge.style === undefined) {
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;';
}
edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
edgeData.id = linkId;
edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd;
const labelEl = insertEdgeLabel(labelsEl, edgeData);
// calculate start and end points of the edge, note that the source and target
// can be modified for shapes that have ports
const { source, target, sourceId, targetId } = getEdgeStartEndPoint(edge, dir);
log.debug('abc78 source and target', source, target);
// Add the edge to the graph
graph.edges.push({
id: 'e' + edge.start + edge.end,
...edge,
sources: [source],
targets: [target],
sourceId,
targetId,
labelEl: labelEl,
labels: [
{
width: edgeData.width,
height: edgeData.height,
orgWidth: edgeData.width,
orgHeight: edgeData.height,
text: edgeData.label,
layoutOptions: {
'edgeLabels.inline': 'true',
'edgeLabels.placement': 'CENTER',
},
},
],
edgeData,
});
});
return graph;
};
function dir2ElkDirection(dir) {
switch (dir) {
case 'LR':
return 'RIGHT';
case 'RL':
return 'LEFT';
case 'TB':
return 'DOWN';
case 'BT':
return 'UP';
default:
return 'DOWN';
}
}
function setIncludeChildrenPolicy(nodeId: string, ancestorId: string) {
const node = nodeDb[nodeId];
if (!node) {
return;
}
if (node?.layoutOptions === undefined) {
node.layoutOptions = {};
}
node.layoutOptions['elk.hierarchyHandling'] = 'INCLUDE_CHILDREN';
if (node.id !== ancestorId) {
setIncludeChildrenPolicy(node.parentId, ancestorId);
}
}
export const render = async (data4Layout, svg, element, algorithm) => {
const elk = new ELK();
// Add the arrowheads to the svg
insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
// Setup the graph with the layout options and the data for the layout
let elkGraph = {
id: 'root',
layoutOptions: {
'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
'elk.algorithm': algorithm,
'nodePlacement.strategy': data4Layout.config['elk.nodePlacement.strategy'],
'elk.layered.mergeEdges': data4Layout.config.mergeEdges,
'elk.direction': 'DOWN',
'spacing.baseValue': 30,
},
children: [],
edges: [],
};
log.info('Drawing flowchart using v4 renderer', elk);
// Set the direction of the graph based on the parsed information
const dir = data4Layout.direction || 'DOWN';
elkGraph.layoutOptions['elk.direction'] = dir2ElkDirection(dir);
// Create the lookup db for the subgraphs and their children to used when creating
// the tree structured graph
const parentLookupDb = addSubGraphs(data4Layout.nodes);
// Add elements in the svg to be used to hold the subgraphs container
// elements and the nodes
const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');
const nodeEl = svg.insert('g').attr('class', 'nodes');
// Add the nodes to the graph, this will entail creating the actual nodes
// in order to get the size of the node. You can't get the size of a node
// that is not in the dom so we need to add it to the dom, get the size
// we will position the nodes when we get the layout from elkjs
elkGraph = await addVertices(nodeEl, data4Layout.nodes, elkGraph);
// Time for the edges, we start with adding an element in the node to hold the edges
const edgesEl = svg.insert('g').attr('class', 'edges edgePath');
// Add the edges to the elk graph, this will entail creating the actual edges
elkGraph = addEdges(data4Layout, elkGraph, svg);
// Iterate through all nodes and add the top level nodes to the graph
const nodes = data4Layout.nodes;
nodes.forEach((n) => {
const node = nodeDb[n.id];
// Subgraph
if (parentLookupDb.childrenById[node.id] !== undefined) {
log.trace('Subgraph XCX', node.id, node);
node.labels = [
{
text: node.labelText,
layoutOptions: {
'nodeLabels.placement': '[H_CENTER, V_TOP, INSIDE]',
},
width: node?.labelData?.width || 0,
height: node?.labelData?.height || 0,
},
];
node.layoutOptions = {
'spacing.baseValue': 30,
};
if (node.dir) {
node.layoutOptions = {
...node.layoutOptions,
'elk.direction': dir2ElkDirection(node.dir),
'elk.hierarchyHandling': 'SEPARATE_CHILDREN',
};
}
delete node.x;
delete node.y;
delete node.width;
delete node.height;
}
});
elkGraph.edges.forEach((edge) => {
const source = edge.sources[0];
const target = edge.targets[0];
if (nodeDb[source].parentId !== nodeDb[target].parentId) {
const ancestorId = findCommonAncestor(source, target, parentLookupDb);
// an edge that breaks a subgraph has been identified, set configuration accordingly
setIncludeChildrenPolicy(source, ancestorId);
setIncludeChildrenPolicy(target, ancestorId);
}
});
log.trace('before layout', JSON.stringify(elkGraph, null, 2));
const g = await elk.layout(elkGraph);
log.info('after layout', JSON.stringify(g));
// debugger;
drawNodes(0, 0, g.children, svg, subGraphsEl, 0);
g.edges?.map((edge) => {
// (elem, edge, clusterDb, diagramType, graph, id)
edge.start = nodeDb[edge.sources[0]];
edge.end = nodeDb[edge.targets[0]];
const sourceId = edge.start.id;
const targetId = edge.end.id;
const offset = calcOffset(sourceId, targetId, parentLookupDb);
if (edge.sections) {
const src = edge.sections[0].startPoint;
const dest = edge.sections[0].endPoint;
const segments = edge.sections[0].bendPoints ? edge.sections[0].bendPoints : [];
const segPoints = segments.map((segment) => {
return { x: segment.x + offset.x, y: segment.y + offset.y };
});
edge.points = [
{ x: src.x + offset.x, y: src.y + offset.y },
...segPoints,
{ x: dest.x + offset.x, y: dest.y + offset.y },
];
const paths = insertEdge(
edgesEl,
edge,
clusterDb,
data4Layout.type,
g,
data4Layout.diagramId
);
edge.x = edge.labels[0].x + offset.x + edge.labels[0].width / 2;
edge.y = edge.labels[0].y + offset.y + edge.labels[0].height / 2;
positionEdgeLabel(edge, paths);
}
});
};

View File

@@ -1,10 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"types": ["vitest/importMeta", "vitest/globals"]
},
"include": ["./src/**/*.ts"],
"typeRoots": ["./src/types"]
}

View File

@@ -70,25 +70,25 @@
"dependencies": {
"@braintree/sanitize-url": "^7.0.1",
"@mermaid-js/parser": "workspace:^",
"cytoscape": "^3.28.1",
"cytoscape": "^3.29.2",
"cytoscape-cose-bilkent": "^4.1.0",
"d3": "^7.9.0",
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.10",
"dayjs": "^1.11.10",
"dompurify": "^3.0.11",
"elkjs": "^0.9.2",
"katex": "^0.16.9",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
"mdast-util-from-markdown": "^2.0.0",
"roughjs": "^4.6.6",
"stylis": "^4.3.1",
"ts-dedent": "^2.2.0",
"uuid": "^9.0.1"
},
"devDependencies": {
"@adobe/jsonschema2md": "^8.0.0",
"@types/cytoscape": "^3.19.16",
"@types/cytoscape": "^3.21.4",
"@types/d3": "^7.4.3",
"@types/d3-sankey": "^0.12.4",
"@types/d3-scale": "^4.0.8",

View File

@@ -190,10 +190,7 @@ export const addDirective = (directive: MermaidConfig) => {
// If the directive has a fontFamily, but no themeVariables, add the fontFamily to the themeVariables
if (directive.fontFamily && (!directive.themeVariables || !directive.themeVariables.fontFamily)) {
directive.themeVariables = {
...directive.themeVariables,
fontFamily: directive.fontFamily,
};
directive.themeVariables = { fontFamily: directive.fontFamily };
}
directives.push(directive);

View File

@@ -64,21 +64,6 @@ export interface MermaidConfig {
theme?: 'default' | 'forest' | 'dark' | 'neutral' | 'null';
themeVariables?: any;
themeCSS?: string;
/**
* Defines which main look to use for the diagram.
*
*/
look?: 'classic' | 'handdrawn' | 'slick';
/**
* Defines the seed to be used when using handdrawn look. This is important for the automated tests as they will always find differences without the seed. The default value is 0 which gives a random seed.
*
*/
handdrawnSeed?: number;
/**
* Defines which layout algorithm to use for rendering the diagram.
*
*/
layout?: string;
/**
* The maximum allowed size of the users text diagram
*/
@@ -88,16 +73,6 @@ export interface MermaidConfig {
*
*/
maxEdges?: number;
/**
* Elk specific option that allows edge egdes to share path where it convenient. It can make for pretty diagrams but can also make it harder to read the diagram.
*
*/
'elk.mergeEdges'?: boolean;
/**
* Elk specific option affedcting how nodes are placed.
*
*/
'elk.nodePlacement.strategy'?: 'SIMPLE' | 'NETWORK_SIMPLEX' | 'LINEAR_SEGMENTS' | 'BRANDES_KOEPF';
darkMode?: boolean;
htmlLabels?: boolean;
/**

View File

@@ -1,12 +1,12 @@
import { select } from 'd3';
import { getConfig } from '../diagram-api/diagramAPI.js';
import { evaluate } from '../diagrams/common/common.js';
import { log } from '../logger.js';
import { getArrowPoints } from './blockArrowHelper.js';
import createLabel from './createLabel.js';
import { labelHelper, updateNodeBounds, insertPolygonShape } from './shapes/util.js';
import { getConfig } from '../diagram-api/diagramAPI.js';
import intersect from './intersect/index.js';
import createLabel from './createLabel.js';
import note from './shapes/note.js';
import { insertPolygonShape, labelHelper, updateNodeBounds } from './shapes/util.js';
import { evaluate } from '../diagrams/common/common.js';
import { getArrowPoints } from './blockArrowHelper.js';
const formatClass = (str) => {
if (str) {
@@ -395,7 +395,6 @@ const rect = async (parent, node) => {
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
// console.log('Rect node:', node, 'bbox:', bbox, 'halfPadding:', halfPadding, 'node.padding:', node.padding);
// const totalWidth = bbox.width + node.padding * 2;
// const totalHeight = bbox.height + node.padding * 2;
const totalWidth = node.positioned ? node.width : bbox.width + node.padding;

View File

@@ -15,8 +15,6 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
classes = _classes;
}
// console.log('parentY', parent.node());
// Add outer g element
const shapeSvg = parent
.insert('g')
@@ -35,7 +33,6 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
}
const textNode = label.node();
// console.log('parentX', parent, 'node',node,'labelText',labelText, textNode, node.labelType, 'label', label.node());
let text;
if (node.labelType === 'markdown') {
// text = textNode;

View File

@@ -2,7 +2,6 @@ import { select } from 'd3';
import utils from '../../utils.js';
import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js';
import common from '../common/common.js';
import type { LayoutData, LayoutMethod, Node, Edge } from '../../rendering-util/types.js';
import { log } from '../../logger.js';
import {
setAccTitle,
@@ -756,55 +755,11 @@ export const lex = {
firstGraph,
};
const getTypeFromVertex = (vertex: FlowVertex) => {
if (vertex.type === 'square') {
return 'squareRect';
}
if (vertex.type === 'round') {
return 'roundedRect';
}
return vertex.type || 'squareRect';
};
export const getData = () => {
const config = getConfig();
const nodes: Node[] = [];
const edges: Edge[] = [];
// extract(getRootDocV2());
// const diagramStates = getStates();
const useRough = config.look === 'handdrawn';
const n = getVertices();
n.forEach((vertex) => {
const node: Node = {
id: vertex.id,
label: vertex.text,
labelStyle: '',
padding: config.flowchart?.padding || 8,
cssStyles: vertex.styles.join(' '),
cssClasses: vertex.classes.join(' '),
shape: getTypeFromVertex(vertex),
dir: vertex.dir,
domId: vertex.domId,
type: undefined,
isGroup: false,
useRough,
};
nodes.push(node);
});
//const useRough = config.look === 'handdrawn';
return { nodes, edges, other: {}, config };
};
export default {
defaultConfig: () => defaultConfig.flowchart,
setAccTitle,
getAccTitle,
getAccDescription,
getData,
setAccDescription,
addVertex,
lookUpDomId,

View File

@@ -1,8 +1,7 @@
// @ts-ignore: JISON doesn't support types
import flowParser from './parser/flow.jison';
import flowDb from './flowDb.js';
// import flowRendererV2 from './flowRenderer-v2.js';
import flowRendererV3 from './flowRenderer-v3-unified.js';
import flowRendererV2 from './flowRenderer-v2.js';
import flowStyles from './styles.js';
import type { MermaidConfig } from '../../config.type.js';
import { setConfig } from '../../diagram-api/diagramAPI.js';
@@ -10,8 +9,7 @@ import { setConfig } from '../../diagram-api/diagramAPI.js';
export const diagram = {
parser: flowParser,
db: flowDb,
// renderer: flowRendererV2,
renderer: flowRendererV3,
renderer: flowRendererV2,
styles: flowStyles,
init: (cnf: MermaidConfig) => {
if (!cnf.flowchart) {
@@ -20,7 +18,7 @@ export const diagram = {
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
// flowchart-v2 uses dagre-wrapper, which doesn't have access to flowchart cnf
setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } });
flowRendererV3.setConf(cnf.flowchart);
flowRendererV2.setConf(cnf.flowchart);
flowDb.clear();
flowDb.setGen('gen-2');
},

View File

@@ -1,25 +0,0 @@
// @ts-ignore: JISON doesn't support types
import flowParser from './parser/flow.jison';
import flowDb from './flowDb.js';
import flowRendererV2 from './flowRenderer-v2.js';
import flowStyles from './styles.js';
import type { MermaidConfig } from '../../config.type.js';
import { setConfig } from '../../diagram-api/diagramAPI.js';
export const diagram = {
parser: flowParser,
db: flowDb,
renderer: flowRendererV2,
styles: flowStyles,
init: (cnf: MermaidConfig) => {
if (!cnf.flowchart) {
cnf.flowchart = {};
}
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
// flowchart-v2 uses dagre-wrapper, which doesn't have access to flowchart cnf
setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } });
flowRendererV2.setConf(cnf.flowchart);
flowDb.clear();
flowDb.setGen('gen-2');
},
};

View File

@@ -29,8 +29,6 @@ export const setConf = function (cnf) {
*/
export const addVertices = async function (vert, g, svgId, root, doc, diagObj) {
const svg = root.select(`[id="${svgId}"]`);
// console.log('SVG:', svg, svg.node(), 'root:', root, root.node());
const keys = vert.keys();
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition

View File

@@ -1,81 +0,0 @@
import { log } from '../../logger.js';
import type { DiagramStyleClassDef } from '../../diagram-api/types.js';
import type { LayoutData, LayoutMethod } from '../../rendering-util/types.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { render } from '../../rendering-util/render.js';
import { getDiagramElements } from '../../rendering-util/insertElementsForSize.js';
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
import { getDirection } from './flowDb.js';
import utils from '../../utils.js';
// Configuration
const conf: Record<string, any> = {};
export const setConf = function (cnf: Record<string, any>) {
const keys = Object.keys(cnf);
for (const key of keys) {
conf[key] = cnf[key];
}
};
export const getClasses = function (
text: string,
diagramObj: any
): Record<string, DiagramStyleClassDef> {
// diagramObj.db.extract(diagramObj.db.getRootDocV2());
return diagramObj.db.getClasses();
};
export const draw = async function (text: string, id: string, _version: string, diag: any) {
log.info('REF0:');
log.info('Drawing state diagram (v2)', id);
const { securityLevel, state: conf, layout } = getConfig();
const DIR = getDirection();
// The getData method provided in all supported diagrams is used to extract the data from the parsed structure
// into the Layout data format
console.log('Before getData: ');
const data4Layout = diag.db.getData() as LayoutData;
console.log('Data: ', data4Layout);
// Create the root SVG - the element is the div containing the SVG element
const { element, svg } = getDiagramElements(id, securityLevel);
// // For some diagrams this call is not needed, but in the state diagram it is
// await insertElementsForSize(element, data4Layout);
// console.log('data4Layout:', data4Layout);
// // Now we have layout data with real sizes, we can perform the layout
// const data4Rendering = doLayout(data4Layout, id, _version, 'dagre-wrapper');
// // The performRender method provided in all supported diagrams is used to render the data
// performRender(data4Rendering);
data4Layout.type = diag.type;
// data4Layout.layoutAlgorithm = 'dagre-wrapper';
// data4Layout.layoutAlgorithm = 'elk';
data4Layout.layoutAlgorithm = layout;
data4Layout.direction = DIR;
data4Layout.nodeSpacing = conf?.nodeSpacing || 50;
data4Layout.rankSpacing = conf?.rankSpacing || 50;
data4Layout.markers = ['barb'];
data4Layout.diagramId = id;
console.log('REF1:', data4Layout);
await render(data4Layout, svg, element);
const padding = 8;
utils.insertTitle(
element,
'statediagramTitleText',
conf?.titleTopMargin || 0,
diag.db.getDiagramTitle()
);
setupViewPortForSVG(svg, padding, 'flowchart', conf?.useMaxWidth || false);
};
export default {
setConf,
getClasses,
draw,
};

View File

@@ -1,394 +0,0 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js';
import common from '../common/common.js';
import {
CSS_DIAGRAM_CLUSTER,
CSS_DIAGRAM_CLUSTER_ALT,
CSS_DIAGRAM_NOTE,
CSS_DIAGRAM_STATE,
CSS_EDGE,
CSS_EDGE_NOTE_EDGE,
DEFAULT_NESTED_DOC_DIR,
DEFAULT_STATE_TYPE,
DIVIDER_TYPE,
DOMID_STATE,
DOMID_TYPE_SPACER,
G_EDGE_ARROWHEADSTYLE,
G_EDGE_LABELPOS,
G_EDGE_LABELTYPE,
G_EDGE_STYLE,
G_EDGE_THICKNESS,
NOTE,
NOTE_ID,
PARENT,
PARENT_ID,
SHAPE_DIVIDER,
SHAPE_END,
SHAPE_GROUP,
SHAPE_NOTE,
SHAPE_NOTEGROUP,
SHAPE_START,
SHAPE_STATE,
SHAPE_STATE_WITH_DESC,
STMT_RELATION,
STMT_STATE,
} from './stateCommon.js';
// List of nodes created from the parsed diagram statement items
let nodeDb = {};
let graphItemCount = 0; // used to construct ids, etc.
/**
* Create a standard string for the dom ID of an item.
* If a type is given, insert that before the counter, preceded by the type spacer
*
* @param itemId
* @param counter
* @param {string | null} type
* @param typeSpacer
* @returns {string}
*/
export function stateDomId(itemId = '', counter = 0, type = '', typeSpacer = DOMID_TYPE_SPACER) {
const typeStr = type !== null && type.length > 0 ? `${typeSpacer}${type}` : '';
return `${DOMID_STATE}-${itemId}${typeStr}-${counter}`;
}
const setupDoc = (parentParsedItem, doc, diagramStates, nodes, edges, altFlag, useRough) => {
// graphItemCount = 0;
log.trace('items', doc);
doc.forEach((item) => {
switch (item.stmt) {
case STMT_STATE:
dataFetcher(parentParsedItem, item, diagramStates, nodes, edges, altFlag, useRough);
break;
case DEFAULT_STATE_TYPE:
dataFetcher(parentParsedItem, item, diagramStates, nodes, edges, altFlag, useRough);
break;
case STMT_RELATION:
{
dataFetcher(
parentParsedItem,
item.state1,
diagramStates,
nodes,
edges,
altFlag,
useRough
);
dataFetcher(
parentParsedItem,
item.state2,
diagramStates,
nodes,
edges,
altFlag,
useRough
);
const edgeData = {
id: 'edge' + graphItemCount,
start: item.state1.id,
end: item.state2.id,
arrowhead: 'normal',
arrowTypeEnd: 'arrow_barb',
style: G_EDGE_STYLE,
labelStyle: '',
label: common.sanitizeText(item.description, getConfig()),
arrowheadStyle: G_EDGE_ARROWHEADSTYLE,
labelpos: G_EDGE_LABELPOS,
labelType: G_EDGE_LABELTYPE,
thickness: G_EDGE_THICKNESS,
classes: CSS_EDGE,
useRough,
};
edges.push(edgeData);
//g.setEdge(item.state1.id, item.state2.id, edgeData, graphItemCount);
graphItemCount++;
}
break;
}
});
};
/**
* Get the direction from the statement items.
* Look through all of the documents (docs) in the parsedItems
* Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction.
* @param {object[]} parsedItem - the parsed statement item to look through
* @param [defaultDir] - the direction to use if none is found
* @returns {string}
*/
const getDir = (parsedItem, defaultDir = DEFAULT_NESTED_DOC_DIR) => {
let dir = defaultDir;
if (parsedItem.doc) {
for (let i = 0; i < parsedItem.doc.length; i++) {
const parsedItemDoc = parsedItem.doc[i];
if (parsedItemDoc.stmt === 'dir') {
dir = parsedItemDoc.value;
}
}
}
return dir;
};
/**
* Returns a new list of classes.
* In the future, this can be replaced with a class common to all diagrams.
* ClassDef information = { id: id, styles: [], textStyles: [] }
*
* @returns {{}}
*/
function newClassesList() {
return {};
}
// let direction = DEFAULT_DIAGRAM_DIRECTION;
// let rootDoc = [];
let cssClasses = newClassesList(); // style classes defined by a classDef
/**
*
* @param nodes
* @param nodeData
*/
function insertOrUpdateNode(nodes, nodeData) {
if (!nodeData.id || nodeData.id === '</join></fork>' || nodeData.id === '</choice>') {
return;
}
//Populate node style attributes if nodeData has classes defined
if (nodeData.cssClasses) {
nodeData.cssClasses.split(' ').forEach((cssClass) => {
if (cssClasses[cssClass]) {
cssClasses[cssClass].styles.forEach((style) => {
// Populate nodeData with style attributes specifically to be used by rough.js
if (style && style.startsWith('fill:')) {
nodeData.backgroundColor = style.replace('fill:', '');
}
if (style && style.startsWith('stroke:')) {
nodeData.borderColor = style.replace('stroke:', '');
}
if (style && style.startsWith('stroke-width:')) {
nodeData.borderWidth = style.replace('stroke-width:', '');
}
nodeData.cssStyles += style + ';';
});
cssClasses[cssClass].textStyles.forEach((style) => {
nodeData.labelStyle += style + ';';
if (style && style.startsWith('fill:')) {
nodeData.labelTextColor = style.replace('fill:', '');
}
});
}
});
}
const existingNodeData = nodes.find((node) => node.id === nodeData.id);
if (existingNodeData) {
//update the existing nodeData
Object.assign(existingNodeData, nodeData);
} else {
nodes.push(nodeData);
}
}
/**
* Get classes from the db for the info item.
* If there aren't any or if dbInfoItem isn't defined, return an empty string.
* Else create 1 string from the list of classes found
*
* @param {undefined | null | object} dbInfoItem
* @returns {string}
*/
function getClassesFromDbInfo(dbInfoItem) {
if (dbInfoItem === undefined || dbInfoItem === null) {
return '';
} else {
if (dbInfoItem.cssClasses) {
return dbInfoItem.cssClasses.join(' ');
} else {
return '';
}
}
}
export const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, altFlag, useRough) => {
const itemId = parsedItem.id;
const classStr = getClassesFromDbInfo(diagramStates[itemId]);
if (itemId !== 'root') {
let shape = SHAPE_STATE;
if (parsedItem.start === true) {
shape = SHAPE_START;
}
if (parsedItem.start === false) {
shape = SHAPE_END;
}
if (parsedItem.type !== DEFAULT_STATE_TYPE) {
shape = parsedItem.type;
}
// Add the node to our list (nodeDb)
if (!nodeDb[itemId]) {
nodeDb[itemId] = {
id: itemId,
shape,
description: common.sanitizeText(itemId, getConfig()),
cssClasses: `${classStr} ${CSS_DIAGRAM_STATE}`,
};
}
const newNode = nodeDb[itemId];
// Save data for description and group so that for instance a statement without description overwrites
// one with description @todo TODO What does this mean? If important, add a test for it
// Build of the array of description strings
if (parsedItem.description) {
if (Array.isArray(newNode.description)) {
// There already is an array of strings,add to it
newNode.shape = SHAPE_STATE_WITH_DESC;
newNode.description.push(parsedItem.description);
} else {
if (newNode.description?.length > 0) {
// if there is a description already transform it to an array
newNode.shape = SHAPE_STATE_WITH_DESC;
if (newNode.description === itemId) {
// If the previous description was this, remove it
newNode.description = [parsedItem.description];
} else {
newNode.description = [newNode.description, parsedItem.description];
}
} else {
newNode.shape = SHAPE_STATE;
newNode.description = parsedItem.description;
}
}
newNode.description = common.sanitizeTextOrArray(newNode.description, getConfig());
}
// If there's only 1 description entry, just use a regular state shape
if (newNode.description?.length === 1 && newNode.shape === SHAPE_STATE_WITH_DESC) {
newNode.shape = SHAPE_STATE;
}
// group
if (!newNode.type && parsedItem.doc) {
log.info('Setting cluster for XCX', itemId, getDir(parsedItem));
newNode.type = 'group';
newNode.isGroup = true;
newNode.dir = getDir(parsedItem);
newNode.shape = parsedItem.type === DIVIDER_TYPE ? SHAPE_DIVIDER : SHAPE_GROUP;
newNode.cssClasses =
newNode.cssClasses +
' ' +
CSS_DIAGRAM_CLUSTER +
' ' +
(altFlag ? CSS_DIAGRAM_CLUSTER_ALT : '');
}
// This is what will be added to the graph
const nodeData = {
labelStyle: '',
shape: newNode.shape,
label: newNode.description,
cssClasses: newNode.cssClasses,
cssStyles: '',
id: itemId,
dir: newNode.dir,
domId: stateDomId(itemId, graphItemCount),
type: newNode.type,
isGroup: newNode.type === 'group',
padding: 8,
rx: 10,
ry: 10,
useRough,
};
// Clear the label for dividers who have no description
if (nodeData.shape === SHAPE_DIVIDER) {
nodeData.label = '';
}
if (parent && parent.id !== 'root') {
log.trace('Setting node ', itemId, ' to be child of its parent ', parent.id);
nodeData.parentId = parent.id;
}
nodeData.centerLabel = true;
if (parsedItem.note) {
// Todo: set random id
const noteData = {
labelStyle: '',
shape: SHAPE_NOTE,
label: parsedItem.note.text,
cssClasses: CSS_DIAGRAM_NOTE,
// useHtmlLabels: false,
cssStyles: '', // styles.style,
id: itemId + NOTE_ID + '-' + graphItemCount,
domId: stateDomId(itemId, graphItemCount, NOTE),
type: newNode.type,
isGroup: newNode.type === 'group',
padding: 0, //getConfig().flowchart.padding
useRough,
};
const groupData = {
labelStyle: '',
shape: SHAPE_NOTEGROUP,
label: parsedItem.note.text,
cssClasses: newNode.cssClasses,
cssStyles: '', // styles.style,
id: itemId + PARENT_ID,
domId: stateDomId(itemId, graphItemCount, PARENT),
type: 'group',
isGroup: true,
padding: 16, //getConfig().flowchart.padding
useRough,
};
graphItemCount++;
const parentNodeId = itemId + PARENT_ID;
//add parent id to groupData
groupData.id = parentNodeId;
//add parent id to noteData
noteData.parentId = parentNodeId;
//insert groupData
insertOrUpdateNode(nodes, groupData);
//insert noteData
insertOrUpdateNode(nodes, noteData);
//insert nodeData
insertOrUpdateNode(nodes, nodeData);
let from = itemId;
let to = noteData.id;
if (parsedItem.note.position === 'left of') {
from = noteData.id;
to = itemId;
}
edges.push({
id: from + '-' + to,
start: from,
end: to,
arrowhead: 'none',
arrowTypeEnd: '',
style: G_EDGE_STYLE,
labelStyle: '',
classes: CSS_EDGE_NOTE_EDGE,
arrowheadStyle: G_EDGE_ARROWHEADSTYLE,
labelpos: G_EDGE_LABELPOS,
labelType: G_EDGE_LABELTYPE,
thickness: G_EDGE_THICKNESS,
useRough,
});
} else {
insertOrUpdateNode(nodes, nodeData);
}
}
if (parsedItem.doc) {
log.trace('Adding nodes children ');
setupDoc(parsedItem, parsedItem.doc, diagramStates, nodes, edges, !altFlag, useRough);
}
};

View File

@@ -20,44 +20,6 @@ export const STMT_APPLYCLASS = 'applyClass';
export const DEFAULT_STATE_TYPE = 'default';
export const DIVIDER_TYPE = 'divider';
// Graph edge settings
export const G_EDGE_STYLE = 'fill:none';
export const G_EDGE_ARROWHEADSTYLE = 'fill: #333';
export const G_EDGE_LABELPOS = 'c';
export const G_EDGE_LABELTYPE = 'text';
export const G_EDGE_THICKNESS = 'normal';
export const SHAPE_STATE = 'rect';
export const SHAPE_STATE_WITH_DESC = 'rectWithTitle';
export const SHAPE_START = 'stateStart';
export const SHAPE_END = 'stateEnd';
export const SHAPE_DIVIDER = 'divider';
export const SHAPE_GROUP = 'roundedWithTitle';
export const SHAPE_NOTE = 'note';
export const SHAPE_NOTEGROUP = 'noteGroup';
// CSS classes
export const CSS_DIAGRAM = 'statediagram';
export const CSS_STATE = 'state';
export const CSS_DIAGRAM_STATE = `${CSS_DIAGRAM}-${CSS_STATE}`;
export const CSS_EDGE = 'transition';
export const CSS_NOTE = 'note';
export const CSS_NOTE_EDGE = 'note-edge';
export const CSS_EDGE_NOTE_EDGE = `${CSS_EDGE} ${CSS_NOTE_EDGE}`;
export const CSS_DIAGRAM_NOTE = `${CSS_DIAGRAM}-${CSS_NOTE}`;
export const CSS_CLUSTER = 'cluster';
export const CSS_DIAGRAM_CLUSTER = `${CSS_DIAGRAM}-${CSS_CLUSTER}`;
export const CSS_CLUSTER_ALT = 'cluster-alt';
export const CSS_DIAGRAM_CLUSTER_ALT = `${CSS_DIAGRAM}-${CSS_CLUSTER_ALT}`;
export const PARENT = 'parent';
export const NOTE = 'note';
export const DOMID_STATE = 'state';
export const DOMID_TYPE_SPACER = '----';
export const NOTE_ID = `${DOMID_TYPE_SPACER}${NOTE}`;
export const PARENT_ID = `${DOMID_TYPE_SPACER}${PARENT}`;
// --------------------------------------
export default {
DEFAULT_DIAGRAM_DIRECTION,
DEFAULT_NESTED_DOC_DIR,
@@ -67,35 +29,4 @@ export default {
STMT_APPLYCLASS,
DEFAULT_STATE_TYPE,
DIVIDER_TYPE,
G_EDGE_STYLE,
G_EDGE_ARROWHEADSTYLE,
G_EDGE_LABELPOS,
G_EDGE_LABELTYPE,
G_EDGE_THICKNESS,
CSS_EDGE,
CSS_DIAGRAM,
SHAPE_STATE,
SHAPE_STATE_WITH_DESC,
SHAPE_START,
SHAPE_END,
SHAPE_DIVIDER,
SHAPE_GROUP,
SHAPE_NOTE,
SHAPE_NOTEGROUP,
CSS_STATE,
CSS_DIAGRAM_STATE,
CSS_NOTE,
CSS_NOTE_EDGE,
CSS_EDGE_NOTE_EDGE,
CSS_DIAGRAM_NOTE,
CSS_CLUSTER,
CSS_DIAGRAM_CLUSTER,
CSS_CLUSTER_ALT,
CSS_DIAGRAM_CLUSTER_ALT,
PARENT,
NOTE,
DOMID_STATE,
DOMID_TYPE_SPACER,
NOTE_ID,
PARENT_ID,
};

View File

@@ -11,7 +11,6 @@ import {
setDiagramTitle,
getDiagramTitle,
} from '../common/commonDb.js';
import { dataFetcher } from './dataFetcher.js';
import {
DEFAULT_DIAGRAM_DIRECTION,
@@ -21,34 +20,7 @@ import {
STMT_APPLYCLASS,
DEFAULT_STATE_TYPE,
DIVIDER_TYPE,
G_EDGE_STYLE,
G_EDGE_ARROWHEADSTYLE,
G_EDGE_LABELPOS,
G_EDGE_LABELTYPE,
G_EDGE_THICKNESS,
CSS_EDGE,
DEFAULT_NESTED_DOC_DIR,
SHAPE_DIVIDER,
SHAPE_GROUP,
CSS_DIAGRAM_CLUSTER,
CSS_DIAGRAM_CLUSTER_ALT,
CSS_DIAGRAM_STATE,
SHAPE_STATE_WITH_DESC,
SHAPE_STATE,
SHAPE_START,
SHAPE_END,
SHAPE_NOTE,
SHAPE_NOTEGROUP,
CSS_DIAGRAM_NOTE,
DOMID_TYPE_SPACER,
DOMID_STATE,
NOTE_ID,
PARENT_ID,
NOTE,
PARENT,
CSS_EDGE_NOTE_EDGE,
} from './stateCommon.js';
import { node } from 'stylis';
const START_NODE = '[*]';
const START_TYPE = 'start';
@@ -75,8 +47,6 @@ let direction = DEFAULT_DIAGRAM_DIRECTION;
let rootDoc = [];
let classes = newClassesList(); // style classes defined by a classDef
// --------------------------------------
const newDoc = () => {
return {
/** @type {{ id1: string, id2: string, relationTitle: string }[]} */
@@ -570,28 +540,8 @@ const setDirection = (dir) => {
const trimColon = (str) => (str && str[0] === ':' ? str.substr(1).trim() : str.trim());
export const getData = () => {
const nodes = [];
const edges = [];
// for (const key in currentDocument.states) {
// if (currentDocument.states.hasOwnProperty(key)) {
// nodes.push({...currentDocument.states[key]});
// }
// }
extract(getRootDocV2());
const diagramStates = getStates();
const config = getConfig();
const useRough = config.look === 'handdrawn';
dataFetcher(undefined, getRootDocV2(), diagramStates, nodes, edges, true, useRough);
console.log('State Nodes XDX:', nodes);
return { nodes, edges, other: {}, config };
};
export default {
getConfig: () => getConfig().state,
getData,
addState,
clear,
getState,

View File

@@ -3,8 +3,7 @@ import type { DiagramDefinition } from '../../diagram-api/types.js';
import parser from './parser/stateDiagram.jison';
import db from './stateDb.js';
import styles from './styles.js';
//import renderer from './stateRenderer-v2.js';
import renderer from './stateRenderer-v3-unified.js';
import renderer from './stateRenderer-v2.js';
export const diagram: DiagramDefinition = {
parser,

View File

@@ -1,20 +0,0 @@
import type { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: JISON doesn't support types
import parser from './parser/stateDiagram.jison';
import db from './stateDb.js';
import styles from './styles.js';
import renderer from './stateRenderer-v3-unified.js';
export const diagram: DiagramDefinition = {
parser,
db,
renderer,
styles,
init: (cnf) => {
if (!cnf.state) {
cnf.state = {};
}
cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
db.clear();
},
};

View File

@@ -1,93 +0,0 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import type { DiagramStyleClassDef } from '../../diagram-api/types.js';
import { log } from '../../logger.js';
import { getDiagramElements } from '../../rendering-util/insertElementsForSize.js';
import { render } from '../../rendering-util/render.js';
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
import type { LayoutData } from '../../rendering-util/types.js';
import utils from '../../utils.js';
import { CSS_DIAGRAM, DEFAULT_NESTED_DOC_DIR } from './stateCommon.js';
/**
* Get the direction from the statement items.
* Look through all of the documents (docs) in the parsedItems
* Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction.
* @param parsedItem - the parsed statement item to look through
* @param defaultDir - the direction to use if none is found
* @returns The direction to use
*/
const getDir = (parsedItem: any, defaultDir = DEFAULT_NESTED_DOC_DIR) => {
let dir = defaultDir;
if (parsedItem.doc) {
for (let i = 0; i < parsedItem.doc.length; i++) {
const parsedItemDoc = parsedItem.doc[i];
if (parsedItemDoc.stmt === 'dir') {
dir = parsedItemDoc.value;
}
}
}
return dir;
};
export const getClasses = function (
text: string,
diagramObj: any
): Map<string, DiagramStyleClassDef> {
diagramObj.db.extract(diagramObj.db.getRootDocV2());
return diagramObj.db.getClasses();
};
export const draw = async function (text: string, id: string, _version: string, diag: any) {
log.info('REF0:');
log.info('Drawing state diagram (v2)', id);
const { securityLevel, state: conf, layout } = getConfig();
// Extracting the data from the parsed structure into a more usable form
// Not related to the refactoring, but this is the first step in the rendering process
diag.db.extract(diag.db.getRootDocV2());
const DIR = getDir(diag.db.getRootDocV2());
// The getData method provided in all supported diagrams is used to extract the data from the parsed structure
// into the Layout data format
const data4Layout = diag.db.getData() as LayoutData;
// Create the root SVG - the element is the div containing the SVG element
const { element, svg } = getDiagramElements(id, securityLevel);
// // For some diagrams this call is not needed, but in the state diagram it is
// await insertElementsForSize(element, data4Layout);
// console.log('data4Layout:', data4Layout);
// // Now we have layout data with real sizes, we can perform the layout
// const data4Rendering = doLayout(data4Layout, id, _version, 'dagre-wrapper');
// // The performRender method provided in all supported diagrams is used to render the data
// performRender(data4Rendering);
data4Layout.type = diag.type;
data4Layout.layoutAlgorithm = layout;
data4Layout.direction = DIR;
// TODO: Should we move these two to baseConfig? These types are not there in StateConfig.
// @ts-expect-error TODO: Will be fixed after config refactor
data4Layout.nodeSpacing = conf?.nodeSpacing || 50;
// @ts-expect-error TODO: Will be fixed after config refactor
data4Layout.rankSpacing = conf?.rankSpacing || 50;
data4Layout.markers = ['barb'];
data4Layout.diagramId = id;
// console.log('REF1:', data4Layout);
await render(data4Layout, svg, element);
const padding = 8;
utils.insertTitle(
element,
'statediagramTitleText',
conf?.titleTopMargin ?? 25,
diag.db.getDiagramTitle()
);
setupViewPortForSVG(svg, padding, CSS_DIAGRAM, conf?.useMaxWidth ?? true);
};
export default {
getClasses,
draw,
};

View File

@@ -150,9 +150,9 @@ function sidebarSyntax() {
{ text: 'C4 Diagram 🦺⚠️', link: '/syntax/c4' },
{ text: 'Mindmaps', link: '/syntax/mindmap' },
{ text: 'Timeline', link: '/syntax/timeline' },
{ text: 'Zenuml', link: '/syntax/zenuml' },
{ text: 'ZenUML', link: '/syntax/zenuml' },
{ text: 'Sankey 🔥', link: '/syntax/sankey' },
{ text: 'XYChart 🔥', link: '/syntax/xyChart' },
{ text: 'XY Chart 🔥', link: '/syntax/xyChart' },
{ text: 'Block Diagram 🔥', link: '/syntax/block' },
{ text: 'Packet 🔥', link: '/syntax/packet' },
{ text: 'Other Examples', link: '/syntax/examples' },

View File

@@ -52,7 +52,7 @@ The following commands must be sufficient enough to start with:
```bash
curl -fsSL https://get.pnpm.io/install.sh | sh -
pnpm env use --global 18
pnpm env use --global 20
```
You may also need to reload `.shrc` or `.bashrc` afterwards.

View File

@@ -1,31 +0,0 @@
import { getConfig } from './config.js';
import common from './diagrams/common/common.js';
import { log } from './logger.js';
import { insertCluster } from './rendering-util/rendering-elements/clusters.js';
import {
insertEdge,
insertEdgeLabel,
positionEdgeLabel,
} from './rendering-util/rendering-elements/edges.js';
import insertMarkers from './rendering-util/rendering-elements/markers.js';
import { insertNode } from './rendering-util/rendering-elements/nodes.js';
import { labelHelper } from './rendering-util/rendering-elements/shapes/util.js';
import { interpolateToCurve } from './utils.js';
/**
* Internal helpers for mermaid
* @deprecated - This should not be used by external packages, as the definitions will change without notice.
*/
export const internalHelpers = {
common,
getConfig,
insertCluster,
insertEdge,
insertEdgeLabel,
insertMarkers,
insertNode,
interpolateToCurve,
labelHelper,
log,
positionEdgeLabel,
};

View File

@@ -16,9 +16,6 @@ import type { DetailedError } from './utils.js';
import type { ExternalDiagramDefinition } from './diagram-api/types.js';
import type { UnknownDiagramError } from './errors.js';
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
import { registerLayoutLoaders } from './rendering-util/render.js';
import type { LayoutLoaderDefinition } from './rendering-util/render.js';
import { internalHelpers } from './internals.js';
export type {
MermaidConfig,
@@ -29,7 +26,6 @@ export type {
ParseOptions,
ParseResult,
UnknownDiagramError,
LayoutLoaderDefinition,
};
export interface RunOptions {
@@ -417,17 +413,11 @@ export interface Mermaid {
render: typeof render;
init: typeof init;
run: typeof run;
registerLayoutLoaders: typeof registerLayoutLoaders;
registerExternalDiagrams: typeof registerExternalDiagrams;
initialize: typeof initialize;
contentLoaded: typeof contentLoaded;
setParseErrorHandler: typeof setParseErrorHandler;
detectType: typeof detectType;
/**
* Internal helpers for mermaid
* @deprecated - This should not be used by external packages, as the definitions will change without notice.
*/
internalHelpers: typeof internalHelpers;
}
const mermaid: Mermaid = {
@@ -438,13 +428,11 @@ const mermaid: Mermaid = {
init,
run,
registerExternalDiagrams,
registerLayoutLoaders,
initialize,
parseError: undefined,
contentLoaded,
setParseErrorHandler,
detectType,
internalHelpers,
};
export default mermaid;

View File

@@ -1,15 +0,0 @@
import { log } from '$root/logger.js';
import type { LayoutData, LayoutMethod, RenderData } from './types.js';
const layoutAlgorithms = {} as Record<string, any>;
const performLayout = (
layoutData: LayoutData,
id: string,
_version: string,
layoutMethod: LayoutMethod
): RenderData => {
log.info('Performing layout', layoutData, id, _version, layoutMethod);
return { items: [] };
};
export default performLayout;

View File

@@ -1,64 +0,0 @@
// import type { LayoutData } from './types';
import { select } from 'd3';
import { insertNode } from '../dagre-wrapper/nodes.js';
// export const getDiagramElements = (id: string, securityLevel: any) => {
export const getDiagramElements = (id, securityLevel) => {
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
const svg = root.select(`[id="${id}"]`);
// Run the renderer. This is what draws the final graph.
// @ts-ignore todo: fix this
const element = root.select('#' + id + ' g');
return { svg, element };
};
// export function insertElementsForSize(el: SVGElement, data: LayoutData): void {
/**
*
* @param el
* @param data
*/
export function insertElementsForSize(el, data) {
const nodesElem = el.insert('g').attr('class', 'nodes');
const edgesElem = el.insert('g').attr('class', 'edges');
data.nodes.forEach(async (item) => {
item.shape = 'rect';
const e = 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);
});
}
export default insertElementsForSize;

View File

@@ -1,299 +0,0 @@
import { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js';
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import insertMarkers from '../../rendering-elements/markers.js';
import { updateNodeBounds } from '../../rendering-elements/shapes/util.js';
import {
clear as clearGraphlib,
clusterDb,
adjustClustersAndEdges,
findNonClusterChild,
sortNodesByHierarchy,
} from './mermaid-graphlib.js';
import {
insertNode,
positionNode,
clear as clearNodes,
setNodeElem,
} from '../../rendering-elements/nodes.js';
import { insertCluster, clear as clearClusters } from '../../rendering-elements/clusters.js';
import {
insertEdgeLabel,
positionEdgeLabel,
insertEdge,
clear as clearEdges,
} from '../../rendering-elements/edges.js';
import { log } from '$root/logger.js';
import { getSubGraphTitleMargins } from '../../../utils/subGraphTitleMargins.js';
import { getConfig } from '../../../diagram-api/diagramAPI.js';
const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, siteConfig) => {
log.info('Graph in recursive render: XXX', graphlibJson.write(graph), parentCluster);
const dir = graph.graph().rankdir;
log.trace('Dir in recursive render - dir:', dir);
const elem = _elem.insert('g').attr('class', 'root');
if (!graph.nodes()) {
log.info('No nodes found for', graph);
} else {
log.info('Recursive render XXX', graph.nodes());
}
if (graph.edges().length > 0) {
log.info('Recursive edges', graph.edge(graph.edges()[0]));
}
const clusters = elem.insert('g').attr('class', 'clusters');
const edgePaths = elem.insert('g').attr('class', 'edgePaths');
const edgeLabels = elem.insert('g').attr('class', 'edgeLabels');
const nodes = elem.insert('g').attr('class', 'nodes');
// Insert nodes, this will insert them into the dom and each node will get a size. The size is updated
// to the abstract node and is later used by dagre for the layout
await Promise.all(
graph.nodes().map(async function (v) {
const node = graph.node(v);
if (parentCluster !== undefined) {
const data = JSON.parse(JSON.stringify(parentCluster.clusterData));
// data.clusterPositioning = true;
log.trace(
'Setting data for parent cluster XXX\n Node.id = ',
v,
'\n data=',
data.height,
'\nParent cluster',
parentCluster.height
);
graph.setNode(parentCluster.id, data);
if (!graph.parent(v)) {
log.trace('Setting parent', v, parentCluster.id);
graph.setParent(v, parentCluster.id, data);
}
}
log.info('(Insert) Node XXX' + v + ': ' + JSON.stringify(graph.node(v)));
if (node && node.clusterNode) {
// const children = graph.children(v);
log.info('Cluster identified XXX', v, node.width, graph.node(v));
// "o" will contain the full cluster not just the children
const o = await recursiveRender(
nodes,
node.graph,
diagramType,
id,
graph.node(v),
siteConfig
);
const newEl = o.elem;
updateNodeBounds(node, newEl);
node.diff = o.diff || 0;
log.trace(
'New compound node after recursive render XAX',
v,
'width',
// node,
node.width,
'height',
node.height
// node.x,
// node.y
);
setNodeElem(newEl, node);
} else {
if (graph.children(v).length > 0) {
// This is a cluster but not to be rendered recursively
// Render as before
log.info('Cluster - the non recursive path XXX', v, node.id, node, graph);
log.info(findNonClusterChild(node.id, graph));
clusterDb[node.id] = { id: findNonClusterChild(node.id, graph), node };
// insertCluster(clusters, graph.node(v));
} else {
log.trace('Node - the non recursive path XAX', v, node.id, node);
await insertNode(nodes, graph.node(v), dir);
}
}
})
);
// Insert labels, this will insert them into the dom so that the width can be calculated
// Also figure out which edges point to/from clusters and adjust them accordingly
// Edges from/to clusters really points to the first child in the cluster.
// TODO: pick optimal child in the cluster to us as link anchor
graph.edges().forEach(function (e) {
const edge = graph.edge(e.v, e.w, e.name);
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
log.info('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e)));
// Check if link is either from or to a cluster
log.info('Fix', clusterDb, 'ids:', e.v, e.w, 'Translating: ', clusterDb[e.v], clusterDb[e.w]);
insertEdgeLabel(edgeLabels, edge);
});
graph.edges().forEach(function (e) {
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
});
log.info('############################################# XXX');
log.info('### Layout ### XXX');
log.info('############################################# XXX');
dagreLayout(graph);
log.info('Graph after layout:', graphlibJson.write(graph));
// Move the nodes to the correct place
let diff = 0;
log.info('Need the size here XAX', graph.node('T1')?.height);
let { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
subGraphTitleTotalMargin = 0;
sortNodesByHierarchy(graph).forEach(function (v) {
const node = graph.node(v);
const p = graph.node(node?.parentId);
subGraphTitleTotalMargin = p?.offsetY || subGraphTitleTotalMargin;
log.info(
'Position XAX' + v + ': (' + node.x,
',' + node.y,
') width: ',
node.width,
' height: ',
node.height
);
if (node && node.clusterNode) {
const parentId = graph.parent(v);
// Adjust for padding when on root level
node.y = parentId ? node.y + 2 : node.y - 8;
node.x -= 8;
log.info(
'A tainted cluster node XBX',
v,
node.id,
node.width,
node.height,
node.x,
node.y,
graph.parent(v)
);
clusterDb[node.id].node = node;
// node.y += subGraphTitleTotalMargin - 10;
node.y -= (node.offsetY || 0) / 2;
positionNode(node);
} else {
// Non cluster node
if (graph.children(v).length > 0) {
node.height += 0;
const parent = graph.node(node.parentId);
node.y += (node.offsetY || 0) / 2;
insertCluster(clusters, node);
// A cluster in the non-recursive way
log.info(
'A pure cluster node with children XBX',
v,
node.id,
node.width,
node.height,
node.x,
node.y,
'offset',
parent?.offsetY
);
clusterDb[node.id].node = node;
} else {
const parent = graph.node(node.parentId);
node.y += (parent?.offsetY || 0) / 2;
log.info(
'A regular node XBX - using the padding',
v,
node.id,
'parent',
node.parentId,
node.width,
node.height,
node.x,
node.y,
'offsetY',
node.offsetY,
'parent',
parent,
node
);
positionNode(node);
}
}
});
// Move the edge labels to the correct place after layout
graph.edges().forEach(function (e) {
const edge = graph.edge(e);
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);
edge.points.forEach((point) => (point.y += subGraphTitleTotalMargin / 2));
const paths = insertEdge(edgePaths, edge, clusterDb, diagramType, graph, id);
positionEdgeLabel(edge, paths);
});
graph.nodes().forEach(function (v) {
const n = graph.node(v);
log.info(v, n.type, n.diff);
if (n.isGroup) {
diff = n.diff;
}
});
log.trace('Returning from recursive render XAX', elem, diff);
return { elem, diff };
};
/**
* ###############################################################
* Render the graph
* ###############################################################
*/
export const render = async (data4Layout, svg, element) => {
// Create the input mermaid.graph
const graph = new graphlib.Graph({
multigraph: true,
compound: true,
})
.setGraph({
rankdir: data4Layout.direction,
nodesep: data4Layout.nodeSpacing,
ranksep: data4Layout.rankSpacing,
marginx: 8,
marginy: 8,
})
.setDefaultEdgeLabel(function () {
return {};
});
// Org
insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
clearNodes();
clearEdges();
clearClusters();
clearGraphlib();
// Add the nodes and edges to the graph
data4Layout.nodes.forEach((node) => {
graph.setNode(node.id, { ...node });
if (node.parentId) {
graph.setParent(node.id, node.parentId);
}
});
log.debug('Edges:', data4Layout.edges);
data4Layout.edges.forEach((edge) => {
graph.setEdge(edge.start, edge.end, { ...edge });
});
log.warn('Graph at first:', JSON.stringify(graphlibJson.write(graph)));
adjustClustersAndEdges(graph);
log.warn('Graph after:', JSON.stringify(graphlibJson.write(graph)));
const siteConfig = getConfig();
await recursiveRender(
element,
graph,
data4Layout.type,
data4Layout.diagramId,
undefined,
siteConfig
);
};

View File

@@ -1,467 +0,0 @@
/** Decorates with functions required by mermaids dagre-wrapper. */
import { log } from '$root/logger.js';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js';
export let clusterDb = {};
let descendants = {};
let parents = {};
export const clear = () => {
descendants = {};
parents = {};
clusterDb = {};
};
const isDescendant = (id, ancestorId) => {
log.trace('In isDescendant', ancestorId, ' ', id, ' = ', descendants[ancestorId].includes(id));
return descendants[ancestorId].includes(id);
};
const edgeInCluster = (edge, clusterId) => {
log.info('Descendants of ', clusterId, ' is ', descendants[clusterId]);
log.info('Edge is ', edge);
// Edges to/from the cluster is not in the cluster, they are in the parent
if (edge.v === clusterId || edge.w === clusterId) {
return false;
}
if (!descendants[clusterId]) {
log.debug('Tilt, ', clusterId, ',not in descendants');
return false;
}
return (
descendants[clusterId].includes(edge.v) ||
isDescendant(edge.v, clusterId) ||
isDescendant(edge.w, clusterId) ||
descendants[clusterId].includes(edge.w)
);
};
const copy = (clusterId, graph, newGraph, rootId) => {
log.warn(
'Copying children of ',
clusterId,
'root',
rootId,
'data',
graph.node(clusterId),
rootId
);
const nodes = graph.children(clusterId) || [];
// Include cluster node if it is not the root
if (clusterId !== rootId) {
nodes.push(clusterId);
}
log.warn('Copying (nodes) clusterId', clusterId, 'nodes', nodes);
nodes.forEach((node) => {
if (graph.children(node).length > 0) {
copy(node, graph, newGraph, rootId);
} else {
const data = graph.node(node);
log.info('cp ', node, ' to ', rootId, ' with parent ', clusterId); //,node, data, ' parent is ', clusterId);
newGraph.setNode(node, data);
if (rootId !== graph.parent(node)) {
log.warn('Setting parent', node, graph.parent(node));
newGraph.setParent(node, graph.parent(node));
}
if (clusterId !== rootId && node !== clusterId) {
log.debug('Setting parent', node, clusterId);
newGraph.setParent(node, clusterId);
} else {
log.info('In copy ', clusterId, 'root', rootId, 'data', graph.node(clusterId), rootId);
log.debug(
'Not Setting parent for node=',
node,
'cluster!==rootId',
clusterId !== rootId,
'node!==clusterId',
node !== clusterId
);
}
const edges = graph.edges(node);
log.debug('Copying Edges', edges);
edges.forEach((edge) => {
log.info('Edge', edge);
const data = graph.edge(edge.v, edge.w, edge.name);
log.info('Edge data', data, rootId);
try {
// Do not copy edges in and out of the root cluster, they belong to the parent graph
if (edgeInCluster(edge, rootId)) {
log.info('Copying as ', edge.v, edge.w, data, edge.name);
newGraph.setEdge(edge.v, edge.w, data, edge.name);
log.info('newGraph edges ', newGraph.edges(), newGraph.edge(newGraph.edges()[0]));
} else {
log.info(
'Skipping copy of edge ',
edge.v,
'-->',
edge.w,
' rootId: ',
rootId,
' clusterId:',
clusterId
);
}
} catch (e) {
log.error(e);
}
});
}
log.debug('Removing node', node);
graph.removeNode(node);
});
};
export const extractDescendants = (id, graph) => {
// log.debug('Extracting ', id);
const children = graph.children(id);
let res = [...children];
for (const child of children) {
parents[child] = id;
res = [...res, ...extractDescendants(child, graph)];
}
return res;
};
/**
* Validates the graph, checking that all parent child relation points to existing nodes and that
* edges between nodes also ia correct. When not correct the function logs the discrepancies.
*
* @param graph
*/
export const validate = (graph) => {
const edges = graph.edges();
log.trace('Edges: ', edges);
for (const edge of edges) {
if (graph.children(edge.v).length > 0) {
log.trace('The node ', edge.v, ' is part of and edge even though it has children');
return false;
}
if (graph.children(edge.w).length > 0) {
log.trace('The node ', edge.w, ' is part of and edge even though it has children');
return false;
}
}
return true;
};
/**
* Finds a child that is not a cluster. When faking an edge between a node and a cluster.
*
* @param id
* @param {any} graph
*/
export const findNonClusterChild = (id, graph) => {
// const node = graph.node(id);
log.trace('Searching', id);
// const children = graph.children(id).reverse();
const children = graph.children(id); //.reverse();
log.trace('Searching children of id ', id, children);
if (children.length < 1) {
log.trace('This is a valid node', id);
return id;
}
for (const child of children) {
const _id = findNonClusterChild(child, graph);
if (_id) {
log.trace('Found replacement for', id, ' => ', _id);
return _id;
}
}
};
const getAnchorId = (id) => {
if (!clusterDb[id]) {
return id;
}
// If the cluster has no external connections
if (!clusterDb[id].externalConnections) {
return id;
}
// Return the replacement node
if (clusterDb[id]) {
return clusterDb[id].id;
}
return id;
};
export const adjustClustersAndEdges = (graph, depth) => {
if (!graph || depth > 10) {
log.debug('Opting out, no graph ');
return;
} else {
log.debug('Opting in, graph ');
}
// Go through the nodes and for each cluster found, save a replacement node, this can be used when
// faking a link to a cluster
graph.nodes().forEach(function (id) {
const children = graph.children(id);
if (children.length > 0) {
log.warn(
'Cluster identified',
id,
' Replacement id in edges: ',
findNonClusterChild(id, graph)
);
descendants[id] = extractDescendants(id, graph);
clusterDb[id] = { id: findNonClusterChild(id, graph), clusterData: graph.node(id) };
}
});
// Check incoming and outgoing edges for each cluster
graph.nodes().forEach(function (id) {
const children = graph.children(id);
const edges = graph.edges();
if (children.length > 0) {
log.debug('Cluster identified', id, descendants);
edges.forEach((edge) => {
// log.debug('Edge, descendants: ', edge, descendants[id]);
// Check if any edge leaves the cluster (not the actual cluster, that's a link from the box)
if (edge.v !== id && edge.w !== id) {
// Any edge where either the one of the nodes is descending to the cluster but not the other
// if (descendants[id].indexOf(edge.v) < 0 && descendants[id].indexOf(edge.w) < 0) {
const d1 = isDescendant(edge.v, id);
const d2 = isDescendant(edge.w, id);
// d1 xor d2 - if either d1 is true and d2 is false or the other way around
if (d1 ^ d2) {
log.warn('Edge: ', edge, ' leaves cluster ', id);
log.warn('Descendants of XXX ', id, ': ', descendants[id]);
clusterDb[id].externalConnections = true;
}
}
});
} else {
log.debug('Not a cluster ', id, descendants);
}
});
for (let id of Object.keys(clusterDb)) {
const nonClusterChild = clusterDb[id].id;
const parent = graph.parent(nonClusterChild);
// Change replacement node of id to parent of current replacement node if valid
if (parent !== id && clusterDb[parent] && !clusterDb[parent].externalConnections) {
clusterDb[id].id = parent;
}
}
// For clusters with incoming and/or outgoing edges translate those edges to a real node
// in the cluster in order to fake the edge
graph.edges().forEach(function (e) {
const edge = graph.edge(e);
log.warn('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
log.warn('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
let v = e.v;
let w = e.w;
// Check if link is either from or to a cluster
log.warn(
'Fix XXX',
clusterDb,
'ids:',
e.v,
e.w,
'Translating: ',
clusterDb[e.v],
' --- ',
clusterDb[e.w]
);
if (clusterDb[e.v] && clusterDb[e.w] && clusterDb[e.v] === clusterDb[e.w]) {
// cspell:ignore trixing
log.warn('Fixing and trixing link to self - removing XXX', e.v, e.w, e.name);
log.warn('Fixing and trixing - removing XXX', e.v, e.w, e.name);
v = getAnchorId(e.v);
w = getAnchorId(e.w);
graph.removeEdge(e.v, e.w, e.name);
const specialId = e.w + '---' + e.v;
graph.setNode(specialId, {
domId: specialId,
id: specialId,
labelStyle: '',
label: edge.label,
padding: 0,
shape: 'labelRect',
style: '',
});
const edge1 = structuredClone(edge);
const edge2 = structuredClone(edge);
edge1.label = '';
edge1.arrowTypeEnd = 'none';
edge2.label = '';
edge1.fromCluster = e.v;
edge2.toCluster = e.v;
graph.setEdge(v, specialId, edge1, e.name + '-cyclic-special');
graph.setEdge(specialId, w, edge2, e.name + '-cyclic-special');
} else if (clusterDb[e.v] || clusterDb[e.w]) {
log.warn('Fixing and trixing - removing XXX', e.v, e.w, e.name);
v = getAnchorId(e.v);
w = getAnchorId(e.w);
graph.removeEdge(e.v, e.w, e.name);
if (v !== e.v) {
const parent = graph.parent(v);
clusterDb[parent].externalConnections = true;
edge.fromCluster = e.v;
}
if (w !== e.w) {
const parent = graph.parent(w);
clusterDb[parent].externalConnections = true;
edge.toCluster = e.w;
}
log.warn('Fix Replacing with XXX', v, w, e.name);
graph.setEdge(v, w, edge, e.name);
}
});
log.warn('Adjusted Graph', graphlibJson.write(graph));
extractor(graph, 0);
log.trace(clusterDb);
// Remove references to extracted cluster
// graph.edges().forEach(edge => {
// if (isDescendant(edge.v, clusterId) || isDescendant(edge.w, clusterId)) {
// graph.removeEdge(edge);
// }
// });
};
export const extractor = (graph, depth) => {
log.warn('extractor - ', depth, graphlibJson.write(graph), graph.children('D'));
if (depth > 10) {
log.error('Bailing out');
return;
}
// For clusters without incoming and/or outgoing edges, create a new cluster-node
// containing the nodes and edges in the custer in a new graph
// for (let i = 0;)
let nodes = graph.nodes();
let hasChildren = false;
for (const node of nodes) {
const children = graph.children(node);
hasChildren = hasChildren || children.length > 0;
}
if (!hasChildren) {
log.debug('Done, no node has children', graph.nodes());
return;
}
// const clusters = Object.keys(clusterDb);
// clusters.forEach(clusterId => {
log.debug('Nodes = ', nodes, depth);
for (const node of nodes) {
log.debug(
'Extracting node',
node,
clusterDb,
clusterDb[node] && !clusterDb[node].externalConnections,
!graph.parent(node),
graph.node(node),
graph.children('D'),
' Depth ',
depth
);
// Note that the node might have been removed after the Object.keys call so better check
// that it still is in the game
if (!clusterDb[node]) {
// Skip if the node is not a cluster
log.debug('Not a cluster', node, depth);
// break;
} else if (
!clusterDb[node].externalConnections &&
// !graph.parent(node) &&
graph.children(node) &&
graph.children(node).length > 0
) {
log.warn(
'Cluster without external connections, without a parent and with children',
node,
depth
);
const graphSettings = graph.graph();
let dir = graphSettings.rankdir === 'TB' ? 'LR' : 'TB';
if (clusterDb[node] && clusterDb[node].clusterData && clusterDb[node].clusterData.dir) {
dir = clusterDb[node].clusterData.dir;
log.warn('Fixing dir', clusterDb[node].clusterData.dir, dir);
}
const clusterGraph = new graphlib.Graph({
multigraph: true,
compound: true,
})
.setGraph({
rankdir: dir, // Todo: set proper spacing
nodesep: 50,
ranksep: 50,
marginx: 8,
marginy: 8,
})
.setDefaultEdgeLabel(function () {
return {};
});
log.warn('Old graph before copy', graphlibJson.write(graph));
copy(node, graph, clusterGraph, node);
graph.setNode(node, {
clusterNode: true,
id: node,
clusterData: clusterDb[node].clusterData,
label: clusterDb[node].label,
graph: clusterGraph,
});
log.warn('New graph after copy node: (', node, ')', graphlibJson.write(clusterGraph));
log.debug('Old graph after copy', graphlibJson.write(graph));
} else {
log.warn(
'Cluster ** ',
node,
' **not meeting the criteria !externalConnections:',
!clusterDb[node].externalConnections,
' no parent: ',
!graph.parent(node),
' children ',
graph.children(node) && graph.children(node).length > 0,
graph.children('D'),
depth
);
log.debug(clusterDb);
}
}
nodes = graph.nodes();
log.warn('New list of nodes', nodes);
for (const node of nodes) {
const data = graph.node(node);
log.warn(' Now next level', node, data);
if (data.clusterNode) {
extractor(data.graph, depth + 1);
}
}
};
const sorter = (graph, nodes) => {
if (nodes.length === 0) {
return [];
}
let result = Object.assign(nodes);
nodes.forEach((node) => {
const children = graph.children(node);
const sorted = sorter(graph, children);
result = [...result, ...sorted];
});
return result;
};
export const sortNodesByHierarchy = (graph) => sorter(graph, graph.children());

View File

@@ -1,508 +0,0 @@
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import {
validate,
adjustClustersAndEdges,
extractDescendants,
sortNodesByHierarchy,
} from './mermaid-graphlib.js';
import { setLogLevel, log } from '$root/logger.js';
describe('Graphlib decorations', () => {
let g;
beforeEach(function () {
setLogLevel(1);
g = new graphlib.Graph({
multigraph: true,
compound: true,
});
g.setGraph({
rankdir: 'TB',
nodesep: 10,
ranksep: 10,
marginx: 8,
marginy: 8,
});
g.setDefaultEdgeLabel(function () {
return {};
});
});
describe('validate', function () {
it('Validate should detect edges between clusters', function () {
/*
subgraph C1
a --> b
end
subgraph C2
c
end
C1 --> C2
*/
g.setNode('a', { data: 1 });
g.setNode('b', { data: 2 });
g.setNode('c', { data: 3 });
g.setParent('a', 'C1');
g.setParent('b', 'C1');
g.setParent('c', 'C2');
g.setEdge('a', 'b');
g.setEdge('C1', 'C2');
expect(validate(g)).toBe(false);
});
it('Validate should not detect edges between clusters after adjustment', function () {
/*
subgraph C1
a --> b
end
subgraph C2
c
end
C1 --> C2
*/
g.setNode('a', {});
g.setNode('b', {});
g.setNode('c', {});
g.setParent('a', 'C1');
g.setParent('b', 'C1');
g.setParent('c', 'C2');
g.setEdge('a', 'b');
g.setEdge('C1', 'C2');
adjustClustersAndEdges(g);
log.info(g.edges());
expect(validate(g)).toBe(true);
});
it('Validate should detect edges between clusters and transform clusters GLB4', function () {
/*
a --> b
subgraph C1
subgraph C2
a
end
b
end
C1 --> c
*/
g.setNode('a', { data: 1 });
g.setNode('b', { data: 2 });
g.setNode('c', { data: 3 });
g.setNode('C1', { data: 4 });
g.setNode('C2', { data: 5 });
g.setParent('a', 'C2');
g.setParent('b', 'C1');
g.setParent('C2', 'C1');
g.setEdge('a', 'b', { name: 'C1-internal-link' });
g.setEdge('C1', 'c', { name: 'C1-external-link' });
adjustClustersAndEdges(g);
log.info(g.nodes());
expect(g.nodes().length).toBe(2);
expect(validate(g)).toBe(true);
});
it('Validate should detect edges between clusters and transform clusters GLB5', function () {
/*
a --> b
subgraph C1
a
end
subgraph C2
b
end
C1 -->
*/
g.setNode('a', { data: 1 });
g.setNode('b', { data: 2 });
g.setParent('a', 'C1');
g.setParent('b', 'C2');
// g.setEdge('a', 'b', { name: 'C1-internal-link' });
g.setEdge('C1', 'C2', { name: 'C1-external-link' });
log.info(g.nodes());
adjustClustersAndEdges(g);
log.info(g.nodes());
expect(g.nodes().length).toBe(2);
expect(validate(g)).toBe(true);
});
it('adjustClustersAndEdges GLB6', function () {
/*
subgraph C1
a
end
C1 --> b
*/
g.setNode('a', { data: 1 });
g.setNode('b', { data: 2 });
g.setNode('C1', { data: 3 });
g.setParent('a', 'C1');
g.setEdge('C1', 'b', { data: 'link1' }, '1');
// log.info(g.edges())
adjustClustersAndEdges(g);
log.info(g.edges());
expect(g.nodes()).toEqual(['b', 'C1']);
expect(g.edges().length).toBe(1);
expect(validate(g)).toBe(true);
expect(g.node('C1').clusterNode).toBe(true);
const C1Graph = g.node('C1').graph;
expect(C1Graph.nodes()).toEqual(['a']);
});
it('adjustClustersAndEdges GLB7', function () {
/*
subgraph C1
a
end
C1 --> b
C1 --> c
*/
g.setNode('a', { data: 1 });
g.setNode('b', { data: 2 });
g.setNode('c', { data: 3 });
g.setParent('a', 'C1');
g.setNode('C1', { data: 4 });
g.setEdge('C1', 'b', { data: 'link1' }, '1');
g.setEdge('C1', 'c', { data: 'link2' }, '2');
log.info(g.node('C1'));
adjustClustersAndEdges(g);
log.info(g.edges());
expect(g.nodes()).toEqual(['b', 'c', 'C1']);
expect(g.nodes().length).toBe(3);
expect(g.edges().length).toBe(2);
expect(g.edges().length).toBe(2);
const edgeData = g.edge(g.edges()[1]);
expect(edgeData.data).toBe('link2');
expect(validate(g)).toBe(true);
const C1Graph = g.node('C1').graph;
expect(C1Graph.nodes()).toEqual(['a']);
});
it('adjustClustersAndEdges GLB8', function () {
/*
subgraph A
a
end
subgraph B
b
end
subgraph C
c
end
A --> B
A --> C
*/
g.setNode('a', { data: 1 });
g.setNode('b', { data: 2 });
g.setNode('c', { data: 3 });
g.setParent('a', 'A');
g.setParent('b', 'B');
g.setParent('c', 'C');
g.setEdge('A', 'B', { data: 'link1' }, '1');
g.setEdge('A', 'C', { data: 'link2' }, '2');
// log.info(g.edges())
adjustClustersAndEdges(g);
expect(g.nodes()).toEqual(['A', 'B', 'C']);
expect(g.edges().length).toBe(2);
expect(g.edges().length).toBe(2);
const edgeData = g.edge(g.edges()[1]);
expect(edgeData.data).toBe('link2');
expect(validate(g)).toBe(true);
const CGraph = g.node('C').graph;
expect(CGraph.nodes()).toEqual(['c']);
});
it('adjustClustersAndEdges the extracted graphs shall contain the correct data GLB10', function () {
/*
subgraph C
subgraph D
d
end
end
*/
g.setNode('C', { data: 1 });
g.setNode('D', { data: 2 });
g.setNode('d', { data: 3 });
g.setParent('d', 'D');
g.setParent('D', 'C');
// log.info('Graph before', g.node('D'))
// log.info('Graph before', graphlibJson.write(g))
adjustClustersAndEdges(g);
// log.info('Graph after', graphlibJson.write(g), g.node('C').graph)
const CGraph = g.node('C').graph;
const DGraph = CGraph.node('D').graph;
expect(CGraph.nodes()).toEqual(['D']);
expect(DGraph.nodes()).toEqual(['d']);
expect(g.nodes()).toEqual(['C']);
expect(g.nodes().length).toBe(1);
});
it('adjustClustersAndEdges the extracted graphs shall contain the correct data GLB11', function () {
/*
subgraph A
a
end
subgraph B
b
end
subgraph C
subgraph D
d
end
end
A --> B
A --> C
*/
g.setNode('C', { data: 1 });
g.setNode('D', { data: 2 });
g.setNode('d', { data: 3 });
g.setNode('B', { data: 4 });
g.setNode('b', { data: 5 });
g.setNode('A', { data: 6 });
g.setNode('a', { data: 7 });
g.setParent('a', 'A');
g.setParent('b', 'B');
g.setParent('d', 'D');
g.setParent('D', 'C');
g.setEdge('A', 'B', { data: 'link1' }, '1');
g.setEdge('A', 'C', { data: 'link2' }, '2');
log.info('Graph before', g.node('D'));
log.info('Graph before', graphlibJson.write(g));
adjustClustersAndEdges(g);
log.trace('Graph after', graphlibJson.write(g));
expect(g.nodes()).toEqual(['C', 'B', 'A']);
expect(g.nodes().length).toBe(3);
expect(g.edges().length).toBe(2);
const AGraph = g.node('A').graph;
const BGraph = g.node('B').graph;
const CGraph = g.node('C').graph;
// log.info(CGraph.nodes());
const DGraph = CGraph.node('D').graph;
// log.info('DG', CGraph.children('D'));
log.info('A', AGraph.nodes());
expect(AGraph.nodes().length).toBe(1);
expect(AGraph.nodes()).toEqual(['a']);
log.trace('Nodes', BGraph.nodes());
expect(BGraph.nodes().length).toBe(1);
expect(BGraph.nodes()).toEqual(['b']);
expect(CGraph.nodes()).toEqual(['D']);
expect(CGraph.nodes().length).toEqual(1);
expect(AGraph.edges().length).toBe(0);
expect(BGraph.edges().length).toBe(0);
expect(CGraph.edges().length).toBe(0);
expect(DGraph.nodes()).toEqual(['d']);
expect(DGraph.edges().length).toBe(0);
// expect(CGraph.node('D')).toEqual({ data: 2 });
expect(g.edges().length).toBe(2);
// expect(g.edges().length).toBe(2);
// const edgeData = g.edge(g.edges()[1]);
// expect(edgeData.data).toBe('link2');
// expect(validate(g)).toBe(true);
});
it('adjustClustersAndEdges the extracted graphs shall contain the correct links GLB20', function () {
/*
a --> b
subgraph b [Test]
c --> d -->e
end
*/
g.setNode('a', { data: 1 });
g.setNode('b', { data: 2 });
g.setNode('c', { data: 3 });
g.setNode('d', { data: 3 });
g.setNode('e', { data: 3 });
g.setParent('c', 'b');
g.setParent('d', 'b');
g.setParent('e', 'b');
g.setEdge('a', 'b', { data: 'link1' }, '1');
g.setEdge('c', 'd', { data: 'link2' }, '2');
g.setEdge('d', 'e', { data: 'link2' }, '2');
log.info('Graph before', graphlibJson.write(g));
adjustClustersAndEdges(g);
const bGraph = g.node('b').graph;
// log.trace('Graph after', graphlibJson.write(g))
log.info('Graph after', graphlibJson.write(bGraph));
expect(bGraph.nodes().length).toBe(3);
expect(bGraph.edges().length).toBe(2);
});
it('adjustClustersAndEdges the extracted graphs shall contain the correct links GLB21', function () {
/*
state a {
state b {
state c {
e
}
}
}
*/
g.setNode('a', { data: 1 });
g.setNode('b', { data: 2 });
g.setNode('c', { data: 3 });
g.setNode('e', { data: 3 });
g.setParent('b', 'a');
g.setParent('c', 'b');
g.setParent('e', 'c');
log.info('Graph before', graphlibJson.write(g));
adjustClustersAndEdges(g);
const aGraph = g.node('a').graph;
const bGraph = aGraph.node('b').graph;
log.info('Graph after', graphlibJson.write(aGraph));
const cGraph = bGraph.node('c').graph;
// log.trace('Graph after', graphlibJson.write(g))
expect(aGraph.nodes().length).toBe(1);
expect(bGraph.nodes().length).toBe(1);
expect(cGraph.nodes().length).toBe(1);
expect(bGraph.edges().length).toBe(0);
});
});
it('adjustClustersAndEdges should handle nesting GLB77', function () {
/*
flowchart TB
subgraph A
b-->B
a-->c
end
subgraph B
c
end
*/
const exportedGraph = JSON.parse(
'{"options":{"directed":true,"multigraph":true,"compound":true},"nodes":[{"v":"A","value":{"labelStyle":"","shape":"rect","labelText":"A","rx":0,"ry":0,"cssClass":"default","style":"","id":"A","width":500,"type":"group","padding":15}},{"v":"B","value":{"labelStyle":"","shape":"rect","labelText":"B","rx":0,"ry":0,"class":"default","style":"","id":"B","width":500,"type":"group","padding":15},"parent":"A"},{"v":"b","value":{"labelStyle":"","shape":"rect","labelText":"b","rx":0,"ry":0,"class":"default","style":"","id":"b","padding":15},"parent":"A"},{"v":"c","value":{"labelStyle":"","shape":"rect","labelText":"c","rx":0,"ry":0,"class":"default","style":"","id":"c","padding":15},"parent":"B"},{"v":"a","value":{"labelStyle":"","shape":"rect","labelText":"a","rx":0,"ry":0,"class":"default","style":"","id":"a","padding":15},"parent":"A"}],"edges":[{"v":"b","w":"B","name":"1","value":{"minlen":1,"arrowhead":"normal","arrowTypeStart":"arrow_open","arrowTypeEnd":"arrow_point","thickness":"normal","pattern":"solid","style":"fill:none","labelStyle":"","arrowheadStyle":"fill: #333","labelpos":"c","labelType":"text","label":"","id":"L-b-B","cssClasses":"flowchart-link LS-b LE-B"}},{"v":"a","w":"c","name":"2","value":{"minlen":1,"arrowhead":"normal","arrowTypeStart":"arrow_open","arrowTypeEnd":"arrow_point","thickness":"normal","pattern":"solid","style":"fill:none","labelStyle":"","arrowheadStyle":"fill: #333","labelpos":"c","labelType":"text","label":"","id":"L-a-c","cssClasses":"flowchart-link LS-a LE-c"}}],"value":{"rankdir":"TB","nodesep":50,"ranksep":50,"marginx":8,"marginy":8}}'
);
const gr = graphlibJson.read(exportedGraph);
log.info('Graph before', graphlibJson.write(gr));
adjustClustersAndEdges(gr);
const aGraph = gr.node('A').graph;
const bGraph = aGraph.node('B').graph;
log.info('Graph after', graphlibJson.write(aGraph));
// log.trace('Graph after', graphlibJson.write(g))
expect(aGraph.parent('c')).toBe('B');
expect(aGraph.parent('B')).toBe(undefined);
});
});
describe('extractDescendants', function () {
let g;
beforeEach(function () {
setLogLevel(1);
g = new graphlib.Graph({
multigraph: true,
compound: true,
});
g.setGraph({
rankdir: 'TB',
nodesep: 10,
ranksep: 10,
marginx: 8,
marginy: 8,
});
g.setDefaultEdgeLabel(function () {
return {};
});
});
it('Simple case of one level descendants GLB9', function () {
/*
subgraph A
a
end
subgraph B
b
end
subgraph C
c
end
A --> B
A --> C
*/
g.setNode('a', { data: 1 });
g.setNode('b', { data: 2 });
g.setNode('c', { data: 3 });
g.setParent('a', 'A');
g.setParent('b', 'B');
g.setParent('c', 'C');
g.setEdge('A', 'B', { data: 'link1' }, '1');
g.setEdge('A', 'C', { data: 'link2' }, '2');
// log.info(g.edges())
const d1 = extractDescendants('A', g);
const d2 = extractDescendants('B', g);
const d3 = extractDescendants('C', g);
expect(d1).toEqual(['a']);
expect(d2).toEqual(['b']);
expect(d3).toEqual(['c']);
});
});
describe('sortNodesByHierarchy', function () {
let g;
beforeEach(function () {
setLogLevel(1);
g = new graphlib.Graph({
multigraph: true,
compound: true,
});
g.setGraph({
rankdir: 'TB',
nodesep: 10,
ranksep: 10,
marginx: 8,
marginy: 8,
});
g.setDefaultEdgeLabel(function () {
return {};
});
});
it('should sort proper en nodes are in reverse order', function () {
/*
a -->b
subgraph B
b
end
subgraph A
B
end
*/
g.setNode('a', { data: 1 });
g.setNode('b', { data: 2 });
g.setParent('b', 'B');
g.setParent('B', 'A');
g.setEdge('a', 'b', '1');
expect(sortNodesByHierarchy(g)).toEqual(['a', 'A', 'B', 'b']);
});
it('should sort proper en nodes are in correct order', function () {
/*
a -->b
subgraph B
b
end
subgraph A
B
end
*/
g.setNode('a', { data: 1 });
g.setParent('B', 'A');
g.setParent('b', 'B');
g.setNode('b', { data: 2 });
g.setEdge('a', 'b', '1');
expect(sortNodesByHierarchy(g)).toEqual(['a', 'A', 'B', 'b']);
});
});

View File

@@ -1,40 +0,0 @@
export interface LayoutAlgorithm {
render(data4Layout: any, svg: any, element: any, algorithm?: string): any;
}
export type LayoutLoader = () => Promise<LayoutAlgorithm>;
export interface LayoutLoaderDefinition {
name: string;
loader: LayoutLoader;
algorithm?: string;
}
const layoutAlgorithms: Record<string, LayoutLoaderDefinition> = {};
export const registerLayoutLoaders = (loaders: LayoutLoaderDefinition[]) => {
for (const loader of loaders) {
layoutAlgorithms[loader.name] = loader;
}
};
// TODO: Should we load dagre without lazy loading?
const registerDefaultLayoutLoaders = () => {
registerLayoutLoaders([
{
name: 'dagre',
loader: async () => await import('./layout-algorithms/dagre/index.js'),
},
]);
};
registerDefaultLayoutLoaders();
export const render = async (data4Layout: any, svg: any, element: any) => {
if (!(data4Layout.layoutAlgorithm in layoutAlgorithms)) {
throw new Error(`Unknown layout algorithm: ${data4Layout.layoutAlgorithm}`);
}
const layoutDefinition = layoutAlgorithms[data4Layout.layoutAlgorithm];
const layoutRenderer = await layoutDefinition.loader();
return layoutRenderer.render(data4Layout, svg, element, layoutDefinition.algorithm);
};

View File

@@ -1,334 +0,0 @@
import { getConfig } from '$root/diagram-api/diagramAPI.js';
import { evaluate } from '$root/diagrams/common/common.js';
import { log } from '$root/logger.js';
import { getSubGraphTitleMargins } from '$root/utils/subGraphTitleMargins.js';
import { select } from 'd3';
import rough from 'roughjs';
import { createText } from '../createText.ts';
import intersectRect from '../rendering-elements/intersect/intersect-rect.js';
import createLabel from './createLabel.js';
import { createRoundedRectPathD } from './shapes/roundedRectPath.ts';
const rect = (parent, node) => {
log.info('Creating subgraph rect for ', node.id, node);
const siteConfig = getConfig();
// Add outer g element
const shapeSvg = parent.insert('g').attr('class', 'cluster').attr('id', node.id);
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
const useHtmlLabels = evaluate(siteConfig.flowchart.htmlLabels);
// Create the label and insert it after the rect
const labelEl = shapeSvg.insert('g').attr('class', 'cluster-label');
// const text = label
// .node()
// .appendChild(createLabel(node.label, node.labelStyle, undefined, true));
const text =
node.labelType === 'markdown'
? createText(labelEl, node.label, { style: node.labelStyle, useHtmlLabels })
: labelEl.node().appendChild(createLabel(node.label, node.labelStyle, undefined, true));
// Get the size of the label
let bbox = text.getBBox();
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
const padding = 0 * node.padding;
const halfPadding = padding / 2;
const width = node.width <= bbox.width + padding ? bbox.width + padding : node.width;
if (node.width <= bbox.width + padding) {
node.diff = (bbox.width - node.width) / 2 - node.padding / 2;
} else {
node.diff = -node.padding / 2;
}
log.trace('Data ', node, JSON.stringify(node));
// center the rect around its coordinate
rect
.attr('style', node.cssStyles)
.attr('rx', node.rx)
.attr('ry', node.ry)
.attr('x', node.x - width / 2)
.attr('y', node.y - node.height / 2 - halfPadding)
.attr('width', width)
.attr('height', node.height + padding);
const { subGraphTitleTopMargin } = getSubGraphTitleMargins(siteConfig);
if (useHtmlLabels) {
labelEl.attr(
'transform',
// This puts the label on top of the box instead of inside it
`translate(${node.x - bbox.width / 2}, ${node.y - node.height / 2 + subGraphTitleTopMargin})`
);
} else {
labelEl.attr(
'transform',
// This puts the label on top of the box instead of inside it
`translate(${node.x}, ${node.y - node.height / 2 + subGraphTitleTopMargin})`
);
}
// Center the label
const rectBox = rect.node().getBBox();
node.width = rectBox.width;
node.height = rectBox.height;
node.intersect = function (point) {
return intersectRect(node, point);
};
return shapeSvg;
};
/**
* Non visible cluster where the note is group with its
*
* @param {any} parent
* @param {any} node
* @returns {any} ShapeSvg
*/
const noteGroup = (parent, node) => {
const { themeVariables } = getConfig();
const {
textColor,
clusterTextColor,
altBackground,
compositeBackground,
compositeTitleBackground,
compositeBorder,
noteBorderColor,
noteBkgColor,
nodeBorder,
mainBkg,
stateBorder,
} = themeVariables;
// Add outer g element
const shapeSvg = parent.insert('g').attr('class', 'note-cluster').attr('id', node.id);
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
const padding = 0 * node.padding;
const halfPadding = padding / 2;
// center the rect around its coordinate
rect
.attr('rx', node.rx)
.attr('ry', node.ry)
.attr('x', node.x - node.width / 2 - halfPadding)
.attr('y', node.y - node.height / 2 - halfPadding)
.attr('width', node.width + padding)
.attr('height', node.height + padding)
.attr('fill', 'none');
const rectBox = rect.node().getBBox();
node.width = rectBox.width;
node.height = rectBox.height;
node.intersect = function (point) {
return intersectRect(node, point);
};
return shapeSvg;
};
const roundedWithTitle = (parent, node) => {
const siteConfig = getConfig();
const { themeVariables, handdrawnSeed } = siteConfig;
const { altBackground, compositeBackground, compositeTitleBackground, nodeBorder } =
themeVariables;
// Add outer g element
const shapeSvg = parent.insert('g').attr('class', node.cssClasses).attr('id', node.id);
// add the rect
const outerRectG = shapeSvg.insert('g', ':first-child');
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
let innerRect = shapeSvg.append('rect');
const text = label.node().appendChild(createLabel(node.label, node.labelStyle, undefined, true));
// Get the size of the label
let bbox = text.getBBox();
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
bbox = text.getBBox();
const padding = 0 * node.padding;
const halfPadding = padding / 2;
const width =
(node.width <= bbox.width + node.padding ? bbox.width + node.padding : node.width) + padding;
if (node.width <= bbox.width + node.padding) {
node.diff = (bbox.width + node.padding * 0 - node.width) / 2;
} else {
node.diff = -node.padding / 2;
}
const x = node.x - width / 2 - halfPadding;
const y = node.y - node.height / 2 - halfPadding;
const innerY = node.y - node.height / 2 - halfPadding + bbox.height - 1;
const height = node.height + padding;
const innerHeight = node.height + padding - bbox.height - 3;
// add the rect
let rect;
if (node.useRough) {
const isAlt = node.cssClasses.includes('statediagram-cluster-alt');
const rc = rough.svg(shapeSvg);
const roughOuterNode =
node.rx || node.ry
? rc.path(createRoundedRectPathD(x, y, width, height, 10), {
roughness: 0.7,
fill: compositeTitleBackground,
fillStyle: 'solid',
stroke: nodeBorder,
seed: handdrawnSeed,
})
: rc.rectangle(x, y, width, height, { seed: handdrawnSeed });
rect = shapeSvg.insert(() => roughOuterNode, ':first-child');
const roughInnerNode = rc.rectangle(x, innerY, width, innerHeight, {
fill: isAlt ? altBackground : compositeBackground,
fillStyle: isAlt ? 'hachure' : 'solid',
stroke: nodeBorder,
seed: handdrawnSeed,
});
rect = shapeSvg.insert(() => roughOuterNode, ':first-child');
innerRect = shapeSvg.insert(() => roughInnerNode);
} else {
rect = outerRectG.insert('rect', ':first-child');
// center the rect around its coordinate
rect
.attr('class', 'outer')
.attr('x', x)
.attr('y', y)
.attr('width', width)
.attr('height', node.height + padding);
innerRect
.attr('class', 'inner')
.attr('x', x)
.attr('y', innerY)
.attr('width', width)
.attr('height', innerHeight);
}
const { subGraphTitleTopMargin } = getSubGraphTitleMargins(siteConfig);
// Center the label
label.attr(
'transform',
`translate(${node.x - bbox.width / 2}, ${
node.y -
node.height / 2 -
node.padding / 3 +
(evaluate(siteConfig.flowchart.htmlLabels) ? 5 : 3) +
subGraphTitleTopMargin
})`
);
const rectBox = rect.node().getBBox();
node.height = rectBox.height;
node.offsetX = 0;
node.offsetY = 20;
node.intersect = function (point) {
return intersectRect(node, point);
};
return shapeSvg;
};
const divider = (parent, node) => {
const { handdrawnSeed } = getConfig();
// Add outer g element
const shapeSvg = parent.insert('g').attr('class', node.cssClasses).attr('id', node.id);
// add the rect
let rect;
const padding = 0 * node.padding;
const halfPadding = padding / 2;
const x = node.x - node.width / 2 - halfPadding;
const y = node.y - node.height / 2;
const width = node.width + padding;
const height = node.height + padding;
if (node.useRough) {
const rc = rough.svg(shapeSvg);
const roughNode = rc.rectangle(x, y, width, height, {
fill: 'lightgrey',
roughness: 0.5,
strokeLineDash: [5],
seed: handdrawnSeed,
});
rect = shapeSvg.insert(() => roughNode);
} else {
rect = shapeSvg.insert('rect', ':first-child');
// center the rect around its coordinate
rect
.attr('class', 'divider')
.attr('x', x)
.attr('y', y)
.attr('width', width)
.attr('height', height);
}
const rectBox = rect.node().getBBox();
node.width = rectBox.width;
node.height = rectBox.height - node.padding;
node.diff = 0; //-node.padding / 2;
node.offsetY = 0;
node.intersect = function (point) {
return intersectRect(node, point);
};
return shapeSvg;
};
const shapes = { rect, roundedWithTitle, noteGroup, divider };
let clusterElems = {};
export const insertCluster = (elem, node) => {
const shape = node.shape || 'rect';
const cluster = shapes[shape](elem, node);
clusterElems[node.id] = cluster;
return cluster;
};
export const getClusterTitleWidth = (elem, node) => {
const label = createLabel(node.label, node.labelStyle, undefined, true);
elem.node().appendChild(label);
const width = label.getBBox().width;
elem.node().removeChild(label);
return width;
};
export const clear = () => {
clusterElems = {};
};
export const positionCluster = (node) => {
log.info('Position cluster (' + node.id + ', ' + node.x + ', ' + node.y + ')');
const el = clusterElems[node.id];
el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
};

View File

@@ -1,101 +0,0 @@
import { select } from 'd3';
import { log } from '$root/logger.js';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
import { evaluate } from '$root/diagrams/common/common.js';
import { decodeEntities } from '$root/utils.js';
/**
* @param dom
* @param styleFn
*/
function applyStyle(dom, styleFn) {
if (styleFn) {
dom.attr('style', styleFn);
}
}
/**
* @param {any} node
* @returns {SVGForeignObjectElement} Node
*/
function addHtmlLabel(node) {
const fo = select(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'));
const div = fo.append('xhtml:div');
const label = node.label;
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
div.html(
'<span class="' +
labelClass +
'" ' +
(node.labelStyle ? 'style="' + node.labelStyle + '"' : '') +
'>' +
label +
'</span>'
);
applyStyle(div, node.labelStyle);
div.style('display', 'inline-block');
div.style('padding-right', '1px');
// Fix for firefox
div.style('white-space', 'nowrap');
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
return fo.node();
}
/**
* @param _vertexText
* @param style
* @param isTitle
* @param isNode
* @deprecated svg-util/createText instead
*/
const createLabel = (_vertexText, style, isTitle, isNode) => {
let vertexText = _vertexText || '';
if (typeof vertexText === 'object') {
vertexText = vertexText[0];
}
if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
vertexText = vertexText.replace(/\\n|\n/g, '<br />');
log.info('vertexText' + vertexText);
const node = {
isNode,
label: decodeEntities(vertexText).replace(
/fa[blrs]?:fa-[\w-]+/g,
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
labelStyle: style ? style.replace('fill:', 'color:') : style,
};
let vertexNode = addHtmlLabel(node);
// vertexNode.parentNode.removeChild(vertexNode);
return vertexNode;
} else {
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', style.replace('color:', 'fill:'));
let rows = [];
if (typeof vertexText === 'string') {
rows = vertexText.split(/\\n|\n|<br\s*\/?>/gi);
} else if (Array.isArray(vertexText)) {
rows = vertexText;
} else {
rows = [];
}
for (const row of rows) {
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '0');
if (isTitle) {
tspan.setAttribute('class', 'title-row');
} else {
tspan.setAttribute('class', 'row');
}
tspan.textContent = row.trim();
svgLabel.appendChild(tspan);
}
return svgLabel;
}
};
export default createLabel;

View File

@@ -1,79 +0,0 @@
import type { SVG } from '$root/diagram-api/types.js';
import type { Mocked } from 'vitest';
import { addEdgeMarkers } from './edgeMarker.js';
describe('addEdgeMarker', () => {
const svgPath = {
attr: vitest.fn(),
} as unknown as Mocked<SVG>;
const url = 'http://example.com';
const id = 'test';
const diagramType = 'test';
beforeEach(() => {
svgPath.attr.mockReset();
});
it('should add markers for arrow_cross:arrow_point', () => {
const arrowTypeStart = 'arrow_cross';
const arrowTypeEnd = 'arrow_point';
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-start',
`url(${url}#${id}_${diagramType}-crossStart)`
);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-end',
`url(${url}#${id}_${diagramType}-pointEnd)`
);
});
it('should add markers for aggregation:arrow_point', () => {
const arrowTypeStart = 'aggregation';
const arrowTypeEnd = 'arrow_point';
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-start',
`url(${url}#${id}_${diagramType}-aggregationStart)`
);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-end',
`url(${url}#${id}_${diagramType}-pointEnd)`
);
});
it('should add markers for arrow_point:aggregation', () => {
const arrowTypeStart = 'arrow_point';
const arrowTypeEnd = 'aggregation';
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-start',
`url(${url}#${id}_${diagramType}-pointStart)`
);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-end',
`url(${url}#${id}_${diagramType}-aggregationEnd)`
);
});
it('should add markers for aggregation:composition', () => {
const arrowTypeStart = 'aggregation';
const arrowTypeEnd = 'composition';
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-start',
`url(${url}#${id}_${diagramType}-aggregationStart)`
);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-end',
`url(${url}#${id}_${diagramType}-compositionEnd)`
);
});
it('should not add invalid markers', () => {
const arrowTypeStart = 'this is an invalid marker';
const arrowTypeEnd = ') url(https://my-malicious-site.example)';
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
expect(svgPath.attr).not.toHaveBeenCalled();
});
});

View File

@@ -1,57 +0,0 @@
import type { SVG } from '$root/diagram-api/types.js';
import { log } from '$root/logger.js';
import type { EdgeData } from '$root/types.js';
/**
* Adds SVG markers to a path element based on the arrow types specified in the edge.
*
* @param svgPath - The SVG path element to add markers to.
* @param edge - The edge data object containing the arrow types.
* @param url - The URL of the SVG marker definitions.
* @param id - The ID prefix for the SVG marker definitions.
* @param diagramType - The type of diagram being rendered.
*/
export const addEdgeMarkers = (
svgPath: SVG,
edge: Pick<EdgeData, 'arrowTypeStart' | 'arrowTypeEnd'>,
url: string,
id: string,
diagramType: string
) => {
if (edge.arrowTypeStart) {
addEdgeMarker(svgPath, 'start', edge.arrowTypeStart, url, id, diagramType);
}
if (edge.arrowTypeEnd) {
addEdgeMarker(svgPath, 'end', edge.arrowTypeEnd, url, id, diagramType);
}
};
const arrowTypesMap = {
arrow_cross: 'cross',
arrow_point: 'point',
arrow_barb: 'barb',
arrow_circle: 'circle',
aggregation: 'aggregation',
extension: 'extension',
composition: 'composition',
dependency: 'dependency',
lollipop: 'lollipop',
} as const;
const addEdgeMarker = (
svgPath: SVG,
position: 'start' | 'end',
arrowType: string,
url: string,
id: string,
diagramType: string
) => {
const endMarkerType = arrowTypesMap[arrowType as keyof typeof arrowTypesMap];
if (!endMarkerType) {
log.warn(`Unknown arrow type: ${arrowType}`);
return; // unknown arrow type, ignore
}
const suffix = position === 'start' ? 'Start' : 'End';
svgPath.attr(`marker-${position}`, `url(${url}#${id}_${diagramType}-${endMarkerType}${suffix})`);
};

View File

@@ -1,668 +0,0 @@
import { getConfig } from '$root/diagram-api/diagramAPI.js';
import { evaluate } from '$root/diagrams/common/common.js';
import { log } from '$root/logger.js';
import { createText } from '$root/rendering-util/createText.ts';
import utils from '$root/utils.js';
import { getLineFunctionsWithOffset } from '$root/utils/lineWithOffset.js';
import { getSubGraphTitleMargins } from '$root/utils/subGraphTitleMargins.js';
import { curveBasis, line, select } from 'd3';
import rough from 'roughjs';
import createLabel from './createLabel.js';
import { addEdgeMarkers } from './edgeMarker.ts';
//import type { Edge } from '$root/rendering-util/types.d.ts';
let edgeLabels = {};
let terminalLabels = {};
export const clear = () => {
edgeLabels = {};
terminalLabels = {};
};
export const insertEdgeLabel = (elem, edge) => {
const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
// Create the actual text element
const labelElement =
edge.labelType === 'markdown'
? createText(elem, edge.label, {
style: edge.labelStyle,
useHtmlLabels,
addSvgBackground: true,
})
: createLabel(edge.label, edge.labelStyle);
log.info('abc82', edge, edge.labelType);
// Create outer g, edgeLabel, this will be positioned after graph layout
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
// Create inner g, label, this will be positioned now for centering the text
const label = edgeLabel.insert('g').attr('class', 'label');
label.node().appendChild(labelElement);
// Center the label
let bbox = labelElement.getBBox();
if (useHtmlLabels) {
const div = labelElement.children[0];
const dv = select(labelElement);
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
// Make element accessible by id for positioning
edgeLabels[edge.id] = edgeLabel;
// Update the abstract data of the edge with the new information about its width and height
edge.width = bbox.width;
edge.height = bbox.height;
let fo;
if (edge.startLabelLeft) {
// Create the actual text element
const startLabelElement = createLabel(edge.startLabelLeft, edge.labelStyle);
const startEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
const inner = startEdgeLabelLeft.insert('g').attr('class', 'inner');
fo = inner.node().appendChild(startLabelElement);
const slBox = startLabelElement.getBBox();
inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');
if (!terminalLabels[edge.id]) {
terminalLabels[edge.id] = {};
}
terminalLabels[edge.id].startLeft = startEdgeLabelLeft;
setTerminalWidth(fo, edge.startLabelLeft);
}
if (edge.startLabelRight) {
// Create the actual text element
const startLabelElement = createLabel(edge.startLabelRight, edge.labelStyle);
const startEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
const inner = startEdgeLabelRight.insert('g').attr('class', 'inner');
fo = startEdgeLabelRight.node().appendChild(startLabelElement);
inner.node().appendChild(startLabelElement);
const slBox = startLabelElement.getBBox();
inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');
if (!terminalLabels[edge.id]) {
terminalLabels[edge.id] = {};
}
terminalLabels[edge.id].startRight = startEdgeLabelRight;
setTerminalWidth(fo, edge.startLabelRight);
}
if (edge.endLabelLeft) {
// Create the actual text element
const endLabelElement = createLabel(edge.endLabelLeft, edge.labelStyle);
const endEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
const inner = endEdgeLabelLeft.insert('g').attr('class', 'inner');
fo = inner.node().appendChild(endLabelElement);
const slBox = endLabelElement.getBBox();
inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');
endEdgeLabelLeft.node().appendChild(endLabelElement);
if (!terminalLabels[edge.id]) {
terminalLabels[edge.id] = {};
}
terminalLabels[edge.id].endLeft = endEdgeLabelLeft;
setTerminalWidth(fo, edge.endLabelLeft);
}
if (edge.endLabelRight) {
// Create the actual text element
const endLabelElement = createLabel(edge.endLabelRight, edge.labelStyle);
const endEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
const inner = endEdgeLabelRight.insert('g').attr('class', 'inner');
fo = inner.node().appendChild(endLabelElement);
const slBox = endLabelElement.getBBox();
inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');
endEdgeLabelRight.node().appendChild(endLabelElement);
if (!terminalLabels[edge.id]) {
terminalLabels[edge.id] = {};
}
terminalLabels[edge.id].endRight = endEdgeLabelRight;
setTerminalWidth(fo, edge.endLabelRight);
}
return labelElement;
};
/**
* @param {any} fo
* @param {any} value
*/
function setTerminalWidth(fo, value) {
if (getConfig().flowchart.htmlLabels && fo) {
fo.style.width = value.length * 9 + 'px';
fo.style.height = '12px';
}
}
export const positionEdgeLabel = (edge, paths) => {
log.info('Moving label abc78 ', edge.id, edge.label, edgeLabels[edge.id]);
let path = paths.updatedPath ? paths.updatedPath : paths.originalPath;
const siteConfig = getConfig();
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
if (edge.label) {
const el = edgeLabels[edge.id];
let x = edge.x;
let y = edge.y;
if (path) {
// // debugger;
const pos = utils.calcLabelPosition(path);
log.info(
'Moving label ' + edge.label + ' from (',
x,
',',
y,
') to (',
pos.x,
',',
pos.y,
') abc78'
);
if (paths.updatedPath) {
x = pos.x;
y = pos.y;
}
}
el.attr('transform', `translate(${x}, ${y + subGraphTitleTotalMargin / 2})`);
}
//let path = paths.updatedPath ? paths.updatedPath : paths.originalPath;
if (edge.startLabelLeft) {
const el = terminalLabels[edge.id].startLeft;
let x = edge.x;
let y = edge.y;
if (path) {
// debugger;
const pos = utils.calcTerminalLabelPosition(edge.arrowTypeStart ? 10 : 0, 'start_left', path);
x = pos.x;
y = pos.y;
}
el.attr('transform', `translate(${x}, ${y})`);
}
if (edge.startLabelRight) {
const el = terminalLabels[edge.id].startRight;
let x = edge.x;
let y = edge.y;
if (path) {
// debugger;
const pos = utils.calcTerminalLabelPosition(
edge.arrowTypeStart ? 10 : 0,
'start_right',
path
);
x = pos.x;
y = pos.y;
}
el.attr('transform', `translate(${x}, ${y})`);
}
if (edge.endLabelLeft) {
const el = terminalLabels[edge.id].endLeft;
let x = edge.x;
let y = edge.y;
if (path) {
// debugger;
const pos = utils.calcTerminalLabelPosition(edge.arrowTypeEnd ? 10 : 0, 'end_left', path);
x = pos.x;
y = pos.y;
}
el.attr('transform', `translate(${x}, ${y})`);
}
if (edge.endLabelRight) {
const el = terminalLabels[edge.id].endRight;
let x = edge.x;
let y = edge.y;
if (path) {
// debugger;
const pos = utils.calcTerminalLabelPosition(edge.arrowTypeEnd ? 10 : 0, 'end_right', path);
x = pos.x;
y = pos.y;
}
el.attr('transform', `translate(${x}, ${y})`);
}
};
const outsideNode = (node, point) => {
// log.warn('Checking bounds ', node, point);
const x = node.x;
const y = node.y;
const dx = Math.abs(point.x - x);
const dy = Math.abs(point.y - y);
const w = node.width / 2;
const h = node.height / 2;
if (dx >= w || dy >= h) {
return true;
}
return false;
};
export const intersection = (node, outsidePoint, insidePoint) => {
log.warn(`intersection calc abc89:
outsidePoint: ${JSON.stringify(outsidePoint)}
insidePoint : ${JSON.stringify(insidePoint)}
node : x:${node.x} y:${node.y} w:${node.width} h:${node.height}`);
const x = node.x;
const y = node.y;
const dx = Math.abs(x - insidePoint.x);
// const dy = Math.abs(y - insidePoint.y);
const w = node.width / 2;
let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx;
const h = node.height / 2;
// const edges = {
// x1: x - w,
// x2: x + w,
// y1: y - h,
// y2: y + h
// };
// if (
// outsidePoint.x === edges.x1 ||
// outsidePoint.x === edges.x2 ||
// outsidePoint.y === edges.y1 ||
// outsidePoint.y === edges.y2
// ) {
// log.warn('abc89 calc equals on edge', outsidePoint, edges);
// return outsidePoint;
// }
const Q = Math.abs(outsidePoint.y - insidePoint.y);
const R = Math.abs(outsidePoint.x - insidePoint.x);
// log.warn();
if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h) {
// Intersection is top or bottom of rect.
// let q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y;
let q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y;
r = (R * q) / Q;
const res = {
x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x - R + r,
y: insidePoint.y < outsidePoint.y ? insidePoint.y + Q - q : insidePoint.y - Q + q,
};
if (r === 0) {
res.x = outsidePoint.x;
res.y = outsidePoint.y;
}
if (R === 0) {
res.x = outsidePoint.x;
}
if (Q === 0) {
res.y = outsidePoint.y;
}
log.warn(`abc89 top/bot calc, Q ${Q}, q ${q}, R ${R}, r ${r}`, res);
return res;
} else {
// Intersection onn sides of rect
if (insidePoint.x < outsidePoint.x) {
r = outsidePoint.x - w - x;
} else {
// r = outsidePoint.x - w - x;
r = x - w - outsidePoint.x;
}
let q = (Q * r) / R;
// OK let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x + dx - w;
// OK let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : outsidePoint.x + r;
let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - R + r;
// let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : outsidePoint.x + r;
let _y = insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q;
log.warn(`sides calc abc89, Q ${Q}, q ${q}, R ${R}, r ${r}`, { _x, _y });
if (r === 0) {
_x = outsidePoint.x;
_y = outsidePoint.y;
}
if (R === 0) {
_x = outsidePoint.x;
}
if (Q === 0) {
_y = outsidePoint.y;
}
return { x: _x, y: _y };
}
};
/**
* This function will page a path and node where the last point(s) in the path is inside the node
* and return an update path ending by the border of the node.
*
* @param {Array} _points
* @param {any} boundaryNode
* @returns {Array} Points
*/
const cutPathAtIntersect = (_points, boundaryNode) => {
log.warn('abc88 cutPathAtIntersect', _points, boundaryNode);
let points = [];
let lastPointOutside = _points[0];
let isInside = false;
_points.forEach((point) => {
// const node = clusterDb[edge.toCluster].node;
log.info('abc88 checking point', point, boundaryNode);
// check if point is inside the boundary rect
if (!outsideNode(boundaryNode, point) && !isInside) {
// First point inside the rect found
// Calc the intersection coord between the point anf the last point outside the rect
const inter = intersection(boundaryNode, lastPointOutside, point);
log.warn('abc88 inside', point, lastPointOutside, inter);
log.warn('abc88 intersection', inter);
// // Check case where the intersection is the same as the last point
let pointPresent = false;
points.forEach((p) => {
pointPresent = pointPresent || (p.x === inter.x && p.y === inter.y);
});
// // if (!pointPresent) {
if (!points.some((e) => e.x === inter.x && e.y === inter.y)) {
points.push(inter);
} else {
log.warn('abc88 no intersect', inter, points);
}
// points.push(inter);
isInside = true;
} else {
// Outside
log.warn('abc88 outside', point, lastPointOutside);
lastPointOutside = point;
// points.push(point);
if (!isInside) {
points.push(point);
}
}
});
log.warn('abc88 returning points', points);
return points;
};
/**
* Given an edge, this function will return the corner points of the edge. This is defined as:
* one point that has a previous point and a next point such as the angle between the previous
* point and the next point is 90 degrees. Meaning that the previous point has the same x coordinate
* as the center point and at the same time the next point has the same y coordinate or vice versa.
* @param points
*/
function extractCornerPoints(points) {
const cornerPoints = [];
const cornerPointPositions = [];
for (let i = 1; i < points.length - 1; i++) {
const prev = points[i - 1];
const curr = points[i];
const next = points[i + 1];
if (
prev.x === curr.x &&
curr.y === next.y &&
Math.abs(curr.x - next.x) > 5 &&
Math.abs(curr.y - prev.y) > 5
) {
cornerPoints.push(curr);
cornerPointPositions.push(i);
} else if (
prev.y === curr.y &&
curr.x === next.x &&
Math.abs(curr.x - prev.x) > 5 &&
Math.abs(curr.y - next.y) > 5
) {
cornerPoints.push(curr);
cornerPointPositions.push(i);
}
}
return { cornerPoints, cornerPointPositions };
}
const findAdjacentPoint = function (pointA, pointB, distance) {
const xDiff = pointB.x - pointA.x;
const yDiff = pointB.y - pointA.y;
const length = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
const ratio = distance / length;
return { x: pointB.x - ratio * xDiff, y: pointB.y - ratio * yDiff };
};
/**
* Given an array of points, this function will return a new array of points where the corners have been removed and replaced with
* adjacent points in each direction. SO a corder will be replaced with a point before and the point after the corner.
*/
const fixCorners = function (lineData) {
const { cornerPoints, cornerPointPositions } = extractCornerPoints(lineData);
const newLineData = [];
let lastCorner = 0;
for (let i = 0; i < lineData.length; i++) {
if (cornerPointPositions.includes(i)) {
const prevPoint = lineData[i - 1];
const nextPoint = lineData[i + 1];
const cornerPoint = lineData[i];
// Find a new point on the line point 5 points back and push it to the new array
const newPrevPoint = findAdjacentPoint(prevPoint, cornerPoint, 5);
const newNextPoint = findAdjacentPoint(nextPoint, cornerPoint, 5);
newLineData.push(newPrevPoint);
const xDiff = newNextPoint.x - newPrevPoint.x;
const yDiff = newNextPoint.y - newPrevPoint.y;
const a = Math.sqrt(2) * 2;
let newCornerPoint = { x: cornerPoint.x, y: cornerPoint.y };
if (cornerPoint.x === newPrevPoint.x) {
// if (yDiff > 0) {
newCornerPoint = {
x: xDiff < 0 ? newPrevPoint.x - 5 + a : newPrevPoint.x + 5 - a,
y: yDiff < 0 ? newPrevPoint.y - a : newPrevPoint.y + a,
};
// } else {
// newCornerPoint = { x: newPrevPoint.x - a, y: newPrevPoint.y + a };
// }
} else {
// if (yDiff > 0) {
// newCornerPoint = { x: newPrevPoint.x - 5 + a, y: newPrevPoint.y + a };
// } else {
newCornerPoint = {
x: xDiff < 0 ? newPrevPoint.x - a : newPrevPoint.x + a,
y: yDiff < 0 ? newPrevPoint.y - 5 + a : newPrevPoint.y + 5 - a,
};
// }
}
// newLineData.push(cornerPoint);
newLineData.push(newCornerPoint, newNextPoint);
} else {
newLineData.push(lineData[i]);
}
}
return newLineData;
};
/**
* Given a line, this function will return a new line where the corners are rounded.
* @param lineData
*/
function roundedCornersLine(lineData) {
const newLineData = fixCorners(lineData);
let path = '';
for (let i = 0; i < newLineData.length; i++) {
if (i === 0) {
path += 'M' + newLineData[i].x + ',' + newLineData[i].y;
} else if (i === newLineData.length - 1) {
path += 'L' + newLineData[i].x + ',' + newLineData[i].y;
} else {
path += 'L' + newLineData[i].x + ',' + newLineData[i].y;
}
}
return path;
}
export const insertEdge = function (elem, edge, clusterDb, diagramType, graph, id) {
const { handdrawnSeed } = getConfig();
let points = edge.points;
let pointsHasChanged = false;
const tail = edge.start;
var head = edge.end;
log.info('abc88 InsertEdge: ', points);
if (head.intersect && tail.intersect) {
log.info('abc88 InsertEdge: 0.5', points);
// points = points.slice(1, edge.points.length - 1);
log.info('abc88 InsertEdge: 0.7', points);
// points.unshift(tail.intersect(points[0]));
// log.info(
// 'Last point abc88',
// points[points.length - 1],
// head,
// head.intersect(points[points.length - 1])
// );
// points.push(head.intersect(points[points.length - 1]));
}
log.info('abc88 InsertEdge 2: ', points);
if (edge.toCluster) {
log.info('to cluster abc88', clusterDb[edge.toCluster]);
points = cutPathAtIntersect(edge.points, clusterDb[edge.toCluster].node);
pointsHasChanged = true;
}
if (edge.fromCluster) {
log.info('from cluster abc88', clusterDb[edge.fromCluster]);
points = cutPathAtIntersect(points.reverse(), clusterDb[edge.fromCluster].node).reverse();
pointsHasChanged = true;
}
// The data for our line
let lineData = points.filter((p) => !Number.isNaN(p.y));
const { cornerPoints, cornerPointPositions } = extractCornerPoints(lineData);
lineData = fixCorners(lineData);
let lastPoint = lineData[0];
if (lineData.length > 1) {
lastPoint = lineData[lineData.length - 1];
const secondLastPoint = lineData[lineData.length - 2];
// Calculate the mid point of the last two points
const diffX = (lastPoint.x - secondLastPoint.x) / 4;
const diffY = (lastPoint.y - secondLastPoint.y) / 4;
const midPoint = { x: secondLastPoint.x + 3 * diffX, y: secondLastPoint.y + 3 * diffY };
lineData.splice(-1, 0, midPoint);
}
// This is the accessor function we talked about above
let curve;
curve = curveBasis;
// curve = curveCardinal;
// curve = curveLinear;
// curve = curveNatural;
// curve = curveCatmullRom.alpha(0.5);
// curve = curveCatmullRom;
// curve = curveCardinal.tension(0.7);
// curve = curveMonotoneY;
// let curve = interpolateToCurve([5], curveNatural, 0.01, 10);
// Currently only flowcharts get the curve from the settings, perhaps this should
// be expanded to a common setting? Restricting it for now in order not to cause side-effects that
// have not been thought through
if (edge.curve) {
curve = edge.curve;
}
const { x, y } = getLineFunctionsWithOffset(edge);
// const lineFunction = edge.curve ? line().x(x).y(y).curve(curve) : roundedCornersLine;
const lineFunction = line().x(x).y(y).curve(curve);
// Construct stroke classes based on properties
let strokeClasses;
switch (edge.thickness) {
case 'normal':
strokeClasses = 'edge-thickness-normal';
break;
case 'thick':
strokeClasses = 'edge-thickness-thick';
break;
case 'invisible':
strokeClasses = 'edge-thickness-thick';
break;
default:
strokeClasses = 'edge-thickness-normal';
}
switch (edge.pattern) {
case 'solid':
strokeClasses += ' edge-pattern-solid';
break;
case 'dotted':
strokeClasses += ' edge-pattern-dotted';
break;
case 'dashed':
strokeClasses += ' edge-pattern-dashed';
break;
default:
strokeClasses += ' edge-pattern-solid';
}
let useRough = edge.useRough;
let svgPath;
let path = '';
if (useRough) {
const rc = rough.svg(elem);
const ld = Object.assign([], lineData);
const svgPathNode = rc.path(lineFunction(ld.splice(0, ld.length - 1)), {
roughness: 0.3,
seed: handdrawnSeed,
});
strokeClasses += ' transition';
svgPath = select(svgPathNode)
.select('path')
// .attr('d', lineFunction(lineData))
.attr('id', edge.id)
.attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
.attr('style', edge.style);
let d = svgPath.attr('d');
d = d + ' L ' + lastPoint.x + ' ' + lastPoint.y;
svgPath.attr('d', d);
elem.node().appendChild(svgPath.node());
} else {
svgPath = elem
.append('path')
.attr('d', lineFunction(lineData))
.attr('id', edge.id)
.attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
.attr('style', edge.style);
}
// DEBUG code, adds a red circle at each edge coordinate
// cornerPoints.forEach((point) => {
// elem
// .append('circle')
// .style('stroke', 'blue')
// .style('fill', 'blue')
// .attr('r', 3)
// .attr('cx', point.x)
// .attr('cy', point.y);
// });
// lineData.forEach((point) => {
// elem
// .append('circle')
// .style('stroke', 'red')
// .style('fill', 'red')
// .attr('r', 1)
// .attr('cx', point.x)
// .attr('cy', point.y);
// });
let url = '';
// // TODO: Can we load this config only from the rendered graph type?
if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) {
url =
window.location.protocol +
'//' +
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
}
log.info('arrowTypeStart', edge.arrowTypeStart);
log.info('arrowTypeEnd', edge.arrowTypeEnd);
addEdgeMarkers(svgPath, edge, url, id, diagramType);
let paths = {};
if (pointsHasChanged) {
paths.updatedPath = points;
}
paths.originalPath = edge.points;
return paths;
};

View File

@@ -1,17 +0,0 @@
/*
* Borrowed with love from from dagre-d3. Many thanks to cpettitt!
*/
import node from './intersect-node.js';
import circle from './intersect-circle.js';
import ellipse from './intersect-ellipse.js';
import polygon from './intersect-polygon.js';
import rect from './intersect-rect.js';
export default {
node,
circle,
ellipse,
polygon,
rect,
};

View File

@@ -1,12 +0,0 @@
import intersectEllipse from './intersect-ellipse.js';
/**
* @param node
* @param rx
* @param point
*/
function intersectCircle(node, rx, point) {
return intersectEllipse(node, rx, rx, point);
}
export default intersectCircle;

View File

@@ -1,30 +0,0 @@
/**
* @param node
* @param rx
* @param ry
* @param point
*/
function intersectEllipse(node, rx, ry, point) {
// Formulae from: https://mathworld.wolfram.com/Ellipse-LineIntersection.html
var cx = node.x;
var cy = node.y;
var px = cx - point.x;
var py = cy - point.y;
var det = Math.sqrt(rx * rx * py * py + ry * ry * px * px);
var dx = Math.abs((rx * ry * px) / det);
if (point.x < cx) {
dx = -dx;
}
var dy = Math.abs((rx * ry * py) / det);
if (point.y < cy) {
dy = -dy;
}
return { x: cx + dx, y: cy + dy };
}
export default intersectEllipse;

View File

@@ -1,78 +0,0 @@
/**
* Returns the point at which two lines, p and q, intersect or returns undefined if they do not intersect.
*
* @param p1
* @param p2
* @param q1
* @param q2
*/
function intersectLine(p1, p2, q1, q2) {
// Algorithm from J. Avro, (ed.) Graphics Gems, No 2, Morgan Kaufmann, 1994,
// p7 and p473.
var a1, a2, b1, b2, c1, c2;
var r1, r2, r3, r4;
var denom, offset, num;
var x, y;
// Compute a1, b1, c1, where line joining points 1 and 2 is F(x,y) = a1 x +
// b1 y + c1 = 0.
a1 = p2.y - p1.y;
b1 = p1.x - p2.x;
c1 = p2.x * p1.y - p1.x * p2.y;
// Compute r3 and r4.
r3 = a1 * q1.x + b1 * q1.y + c1;
r4 = a1 * q2.x + b1 * q2.y + c1;
// Check signs of r3 and r4. If both point 3 and point 4 lie on
// same side of line 1, the line segments do not intersect.
if (r3 !== 0 && r4 !== 0 && sameSign(r3, r4)) {
return /*DON'T_INTERSECT*/;
}
// Compute a2, b2, c2 where line joining points 3 and 4 is G(x,y) = a2 x + b2 y + c2 = 0
a2 = q2.y - q1.y;
b2 = q1.x - q2.x;
c2 = q2.x * q1.y - q1.x * q2.y;
// Compute r1 and r2
r1 = a2 * p1.x + b2 * p1.y + c2;
r2 = a2 * p2.x + b2 * p2.y + c2;
// Check signs of r1 and r2. If both point 1 and point 2 lie
// on same side of second line segment, the line segments do
// not intersect.
if (r1 !== 0 && r2 !== 0 && sameSign(r1, r2)) {
return /*DON'T_INTERSECT*/;
}
// Line segments intersect: compute intersection point.
denom = a1 * b2 - a2 * b1;
if (denom === 0) {
return /*COLLINEAR*/;
}
offset = Math.abs(denom / 2);
// The denom/2 is to get rounding instead of truncating. It
// is added or subtracted to the numerator, depending upon the
// sign of the numerator.
num = b1 * c2 - b2 * c1;
x = num < 0 ? (num - offset) / denom : (num + offset) / denom;
num = a2 * c1 - a1 * c2;
y = num < 0 ? (num - offset) / denom : (num + offset) / denom;
return { x: x, y: y };
}
/**
* @param r1
* @param r2
*/
function sameSign(r1, r2) {
return r1 * r2 > 0;
}
export default intersectLine;

View File

@@ -1,9 +0,0 @@
/**
* @param node
* @param point
*/
function intersectNode(node, point) {
return node.intersect(point);
}
export default intersectNode;

View File

@@ -1,69 +0,0 @@
/* eslint "no-console": off */
import intersectLine from './intersect-line.js';
export default intersectPolygon;
/**
* Returns the point ({x, y}) at which the point argument intersects with the node argument assuming
* that it has the shape specified by polygon.
*
* @param node
* @param polyPoints
* @param point
*/
function intersectPolygon(node, polyPoints, point) {
var x1 = node.x;
var y1 = node.y;
var intersections = [];
var minX = Number.POSITIVE_INFINITY;
var minY = Number.POSITIVE_INFINITY;
if (typeof polyPoints.forEach === 'function') {
polyPoints.forEach(function (entry) {
minX = Math.min(minX, entry.x);
minY = Math.min(minY, entry.y);
});
} else {
minX = Math.min(minX, polyPoints.x);
minY = Math.min(minY, polyPoints.y);
}
var left = x1 - node.width / 2 - minX;
var top = y1 - node.height / 2 - minY;
for (var i = 0; i < polyPoints.length; i++) {
var p1 = polyPoints[i];
var p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0];
var intersect = intersectLine(
node,
point,
{ x: left + p1.x, y: top + p1.y },
{ x: left + p2.x, y: top + p2.y }
);
if (intersect) {
intersections.push(intersect);
}
}
if (!intersections.length) {
return node;
}
if (intersections.length > 1) {
// More intersections, find the one nearest to edge end point
intersections.sort(function (p, q) {
var pdx = p.x - point.x;
var pdy = p.y - point.y;
var distp = Math.sqrt(pdx * pdx + pdy * pdy);
var qdx = q.x - point.x;
var qdy = q.y - point.y;
var distq = Math.sqrt(qdx * qdx + qdy * qdy);
return distp < distq ? -1 : distp === distq ? 0 : 1;
});
}
return intersections[0];
}

View File

@@ -1,32 +0,0 @@
const intersectRect = (node, point) => {
var x = node.x;
var y = node.y;
// Rectangle intersection algorithm from:
// https://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
var dx = point.x - x;
var dy = point.y - y;
var w = node.width / 2;
var h = node.height / 2;
var sx, sy;
if (Math.abs(dy) * w > Math.abs(dx) * h) {
// Intersection is top or bottom of rect.
if (dy < 0) {
h = -h;
}
sx = dy === 0 ? 0 : (h * dx) / dy;
sy = h;
} else {
// Intersection is left or right of rect.
if (dx < 0) {
w = -w;
}
sx = w;
sy = dx === 0 ? 0 : (w * dy) / dx;
}
return { x: x + sx, y: y + sy };
};
export default intersectRect;

View File

@@ -1,293 +0,0 @@
/** Setup arrow head and define the marker. The result is appended to the svg. */
import { log } from '$root/logger.js';
// Only add the number of markers that the diagram needs
const insertMarkers = (elem, markerArray, type, id) => {
markerArray.forEach((markerName) => {
markers[markerName](elem, type, id);
});
};
const extension = (elem, type, id) => {
log.trace('Making markers for ', id);
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-extensionStart')
.attr('class', 'marker extension ' + type)
.attr('refX', 18)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 1,7 L18,13 V 1 Z');
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-extensionEnd')
.attr('class', 'marker extension ' + type)
.attr('refX', 1)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 1,1 V 13 L18,7 Z'); // this is actual shape for arrowhead
};
const composition = (elem, type, id) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-compositionStart')
.attr('class', 'marker composition ' + type)
.attr('refX', 18)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-compositionEnd')
.attr('class', 'marker composition ' + type)
.attr('refX', 1)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
};
const aggregation = (elem, type, id) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-aggregationStart')
.attr('class', 'marker aggregation ' + type)
.attr('refX', 18)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-aggregationEnd')
.attr('class', 'marker aggregation ' + type)
.attr('refX', 1)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
};
const dependency = (elem, type, id) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-dependencyStart')
.attr('class', 'marker dependency ' + type)
.attr('refX', 6)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 5,7 L9,13 L1,7 L9,1 Z');
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-dependencyEnd')
.attr('class', 'marker dependency ' + type)
.attr('refX', 13)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
};
const lollipop = (elem, type, id) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-lollipopStart')
.attr('class', 'marker lollipop ' + type)
.attr('refX', 13)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.append('circle')
.attr('stroke', 'black')
.attr('fill', 'transparent')
.attr('cx', 7)
.attr('cy', 7)
.attr('r', 6);
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-lollipopEnd')
.attr('class', 'marker lollipop ' + type)
.attr('refX', 1)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.append('circle')
.attr('stroke', 'black')
.attr('fill', 'transparent')
.attr('cx', 7)
.attr('cy', 7)
.attr('r', 6);
};
const point = (elem, type, id) => {
elem
.append('marker')
.attr('id', id + '_' + type + '-pointEnd')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', 6)
.attr('refY', 5)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 0 0 L 10 5 L 0 10 z')
.attr('class', 'arrowMarkerPath')
.style('stroke-width', 1)
.style('stroke-dasharray', '1,0');
elem
.append('marker')
.attr('id', id + '_' + type + '-pointStart')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', 4.5)
.attr('refY', 5)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 0 5 L 10 10 L 10 0 z')
.attr('class', 'arrowMarkerPath')
.style('stroke-width', 1)
.style('stroke-dasharray', '1,0');
};
const circle = (elem, type, id) => {
elem
.append('marker')
.attr('id', id + '_' + type + '-circleEnd')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', 11)
.attr('refY', 5)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 11)
.attr('markerHeight', 11)
.attr('orient', 'auto')
.append('circle')
.attr('cx', '5')
.attr('cy', '5')
.attr('r', '5')
.attr('class', 'arrowMarkerPath')
.style('stroke-width', 1)
.style('stroke-dasharray', '1,0');
elem
.append('marker')
.attr('id', id + '_' + type + '-circleStart')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', -1)
.attr('refY', 5)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 11)
.attr('markerHeight', 11)
.attr('orient', 'auto')
.append('circle')
.attr('cx', '5')
.attr('cy', '5')
.attr('r', '5')
.attr('class', 'arrowMarkerPath')
.style('stroke-width', 1)
.style('stroke-dasharray', '1,0');
};
const cross = (elem, type, id) => {
elem
.append('marker')
.attr('id', id + '_' + type + '-crossEnd')
.attr('class', 'marker cross ' + type)
.attr('viewBox', '0 0 11 11')
.attr('refX', 12)
.attr('refY', 5.2)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 11)
.attr('markerHeight', 11)
.attr('orient', 'auto')
.append('path')
// .attr('stroke', 'black')
.attr('d', 'M 1,1 l 9,9 M 10,1 l -9,9')
.attr('class', 'arrowMarkerPath')
.style('stroke-width', 2)
.style('stroke-dasharray', '1,0');
elem
.append('marker')
.attr('id', id + '_' + type + '-crossStart')
.attr('class', 'marker cross ' + type)
.attr('viewBox', '0 0 11 11')
.attr('refX', -1)
.attr('refY', 5.2)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 11)
.attr('markerHeight', 11)
.attr('orient', 'auto')
.append('path')
// .attr('stroke', 'black')
.attr('d', 'M 1,1 l 9,9 M 10,1 l -9,9')
.attr('class', 'arrowMarkerPath')
.style('stroke-width', 2)
.style('stroke-dasharray', '1,0');
};
const barb = (elem, type, id) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-barbEnd')
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 14)
.attr('markerUnits', 'strokeWidth')
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 19,7 L9,13 L14,7 L9,1 Z');
};
// TODO rename the class diagram markers to something shape descriptive and semantic free
const markers = {
extension,
composition,
aggregation,
dependency,
lollipop,
point,
circle,
cross,
barb,
};
export default insertMarkers;

View File

@@ -1,127 +0,0 @@
import { log } from '$root/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 { getConfig } from '$root/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';
const formatClass = (str) => {
if (str) {
return ' ' + str;
}
return '';
};
const shapes = {
state,
stateStart,
stateEnd,
fork: forkJoin,
join: forkJoin,
choice,
note,
roundedRect,
squareRect,
stadium,
subroutine,
cylinder,
circle,
doublecircle,
odd: rect_left_inv_arrow,
diamond: question,
hexagon,
lean_right,
lean_left,
trapezoid,
inv_trapezoid,
};
let nodeElems = {};
export const insertNode = async (elem, node, dir) => {
let newEl;
let el;
if (node) {
console.log('BLA: rect node', JSON.stringify(node));
}
//special check for rect shape (with or without rounded corners)
if (node.shape === 'rect') {
if (node.rx && node.ry) {
node.shape = 'roundedRect';
} else {
node.shape = 'squareRect';
}
}
// Add link when appropriate
if (node.link) {
let target;
if (getConfig().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);
} else {
el = await shapes[node.shape](elem, node, dir);
newEl = el;
}
if (node.tooltip) {
el.attr('title', node.tooltip);
}
// if (node.class) {
// el.attr('class', 'node default ' + node.class);
// }
nodeElems[node.id] = newEl;
if (node.haveCallback) {
nodeElems[node.id].attr('class', nodeElems[node.id].attr('class') + ' clickable');
}
return newEl;
};
export const setNodeElem = (elem, node) => {
nodeElems[node.id] = elem;
};
export const clear = () => {
nodeElems = {};
};
export const positionNode = (node) => {
const el = nodeElems[node.id];
log.trace(
'Transforming node',
node.diff,
node,
'translate(' + (node.x - node.width / 2 - 5) + ', ' + node.width / 2 + ')'
);
const diff = 0;
if (node.clusterNode) {
el.attr(
'transform',
'translate(' + (node.x + diff - node.width / 2) + ', ' + (node.y - node.height / 2) + ')'
);
} else {
el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
}
return diff;
};

View File

@@ -1,55 +0,0 @@
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import type { SVG } from '$root/diagram-api/types.js';
import rough from 'roughjs';
import { solidStateFill } from './handdrawnStyles.js';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
export const choice = (parent: SVG, node: Node) => {
const { themeVariables } = getConfig();
const { lineColor } = themeVariables;
const shapeSvg = parent
.insert('g')
.attr('class', 'node default')
.attr('id', node.domId || node.id);
const s = 28;
const points = [
{ x: 0, y: s / 2 },
{ x: s / 2, y: 0 },
{ x: 0, y: -s / 2 },
{ x: -s / 2, y: 0 },
];
let choice;
if (node.useRough) {
// @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(' ')
);
}
// center the circle around its coordinate
// @ts-ignore TODO: Fix rough typings
choice.attr('class', 'state-start').attr('r', 7).attr('width', 28).attr('height', 28);
node.width = 28;
node.height = 28;
node.intersect = function (point) {
return intersect.circle(node, 14, point);
};
return shapeSvg;
};

View File

@@ -1,46 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
export const circle = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { shapeSvg, bbox, halfPadding } = await labelHelper(
parent,
node,
getNodeClasses(node),
true
);
const radius = bbox.width / 2 + halfPadding;
let circleElem;
const { cssStyles, useRough } = node;
if (useRough) {
// @ts-ignore
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const roughNode = rc.circle(0, 0, radius * 2, options);
circleElem = shapeSvg.insert(() => roughNode, ':first-child');
circleElem.attr('class', 'basic label-container').attr('style', cssStyles);
} else {
circleElem = shapeSvg
.insert('circle', ':first-child')
.attr('class', 'basic label-container')
.attr('style', cssStyles)
.attr('r', radius)
.attr('cx', 0)
.attr('cy', 0);
}
updateNodeBounds(node, circleElem);
node.intersect = function (point) {
log.info('Circle intersect', node, radius, point);
return intersect.circle(node, radius, point);
};
return shapeSvg;
};

View File

@@ -1,127 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
/**
* Creates an SVG path for a cylindrical shape.
* @param {number} x - The x coordinate of the top-left corner.
* @param {number} y - The y coordinate of the top-left corner.
* @param {number} width - The width of the cylinder.
* @param {number} height - The height of the cylinder.
* @param {number} rx - The x-radius of the cylinder's ends.
* @param {number} ry - The y-radius of the cylinder's ends.
* @returns {string} The path data for the cylindrical shape.
*/
export const createCylinderPathD = (
x: number,
y: number,
width: number,
height: number,
rx: number,
ry: number
): string => {
return [
`M${x},${y + ry}`,
`a${rx},${ry} 0,0,0 ${width},0`,
`a${rx},${ry} 0,0,0 ${-width},0`,
`l0,${height}`,
`a${rx},${ry} 0,0,0 ${width},0`,
`l0,${-height}`,
].join(' ');
};
export const createOuterCylinderPathD = (
x: number,
y: number,
width: number,
height: number,
rx: number,
ry: number
): string => {
return [
`M${x},${y + ry}`,
`M${x + width},${y + ry}`,
`a${rx},${ry} 0,0,0 ${-width},0`,
`l0,${height}`,
`a${rx},${ry} 0,0,0 ${width},0`,
`l0,${-height}`,
].join(' ');
};
export const createInnerCylinderPathD = (
x: number,
y: number,
width: number,
height: number,
rx: number,
ry: number
): string => {
return [`M${x - width / 2},${-height / 2}`, `a${rx},${ry} 0,0,0 ${width},0`].join(' ');
};
export const cylinder = async (parent: SVGAElement, node: Node) => {
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node), true);
const w = bbox.width + node.padding;
const rx = w / 2;
const ry = rx / (2.5 + w / 50);
const h = bbox.height + ry + node.padding;
let cylinder: d3.Selection<SVGPathElement | SVGGElement, unknown, null, undefined>;
const { cssStyles, useRough } = node;
if (useRough) {
// @ts-ignore
const rc = rough.svg(shapeSvg);
const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry);
const innerPathData = createInnerCylinderPathD(0, ry, w, h, rx, ry);
const outerNode = rc.path(outerPathData, userNodeOverrides(node, {}));
const innerLine = rc.path(innerPathData, userNodeOverrides(node, { fill: 'none' }));
cylinder = shapeSvg.insert(() => innerLine, ':first-child');
cylinder = shapeSvg.insert(() => outerNode, ':first-child');
cylinder.attr('class', 'basic label-container');
if (cssStyles) {
cylinder.attr('style', cssStyles);
}
} else {
const pathData = createCylinderPathD(0, 0, w, h, rx, ry);
cylinder = shapeSvg
.insert('path', ':first-child')
.attr('d', pathData)
.attr('class', 'basic label-container')
.attr('style', cssStyles);
}
cylinder.attr('label-offset-y', ry);
cylinder.attr('transform', `translate(${-w / 2}, ${-(h / 2 + ry)})`);
updateNodeBounds(node, cylinder);
node.intersect = function (point) {
const pos = intersect.rect(node, point);
const x = pos.x - (node.x ?? 0);
if (
rx != 0 &&
(Math.abs(x) < (node.width ?? 0) / 2 ||
(Math.abs(x) == (node.width ?? 0) / 2 &&
Math.abs(pos.y - (node.y ?? 0)) > (node.height ?? 0) / 2 - ry))
) {
let y = ry * ry * (1 - (x * x) / (rx * rx));
if (y != 0) {
y = Math.sqrt(y);
}
y = ry - y;
if (point.y - (node.y ?? 0) > 0) {
y = -y;
}
pos.y += y;
}
return pos;
};
return shapeSvg;
};

View File

@@ -1,68 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
//import d3 from 'd3';
export const doublecircle = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { shapeSvg, bbox, halfPadding } = await labelHelper(
parent,
node,
getNodeClasses(node),
true
);
const gap = 5;
const outerRadius = bbox.width / 2 + halfPadding + gap;
const innerRadius = bbox.width / 2 + halfPadding;
let circleGroup;
const { cssStyles, useRough } = node;
if (useRough) {
// @ts-ignore
const rc = rough.svg(shapeSvg);
const outerOptions = userNodeOverrides(node, { roughness: 0.2, strokeWidth: 2.5 });
const innerOptions = userNodeOverrides(node, { roughness: 0.2, strokeWidth: 1.5 });
const outerRoughNode = rc.circle(0, 0, outerRadius * 2, outerOptions);
const innerRoughNode = rc.circle(0, 0, innerRadius * 2, innerOptions);
circleGroup = shapeSvg.insert('g', ':first-child');
// circleGroup = circleGroup.insert(() => outerRoughNode, ':first-child');
circleGroup.attr('class', node.cssClasses).attr('style', cssStyles);
circleGroup.node()?.appendChild(outerRoughNode);
circleGroup.node()?.appendChild(innerRoughNode);
} else {
circleGroup = shapeSvg.insert('g', ':first-child');
const outerCircle = circleGroup.insert('circle', ':first-child');
const innerCircle = circleGroup.insert('circle', ':first-child');
circleGroup.attr('class', 'basic label-container').attr('style', cssStyles);
outerCircle
.attr('class', 'outer-circle')
.attr('style', cssStyles)
.attr('r', outerRadius)
.attr('cx', 0)
.attr('cy', 0);
innerCircle
.attr('class', 'inner-circle')
.attr('style', cssStyles)
.attr('r', innerRadius)
.attr('cx', 0)
.attr('cy', 0);
}
updateNodeBounds(node, circleGroup);
node.intersect = function (point) {
log.info('DoubleCircle intersect', node, outerRadius, point);
return intersect.circle(node, outerRadius, point);
};
return shapeSvg;
};

View File

@@ -1,111 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node, RectOptions } from '$root/rendering-util/types.d.ts';
import { createRoundedRectPathD } from './roundedRectPath.js';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
export const drawRect = async (parent: SVGAElement, node: Node, options: RectOptions) => {
const { shapeSvg, bbox, halfPadding } = await labelHelper(
parent,
node,
getNodeClasses(node),
true
);
const totalWidth = bbox.width + node.padding;
const totalHeight = bbox.height + node.padding;
const x = -bbox.width / 2 - halfPadding;
const y = -bbox.height / 2 - halfPadding;
let rect;
let { rx, ry, cssStyles, useRough } = node;
//use options rx, ry overrides if present
if (options && options.rx && options.ry) {
rx = options.rx;
ry = options.ry;
}
if (useRough) {
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
console.log('rect options: ', options);
const roughNode =
rx || ry
? rc.path(createRoundedRectPathD(x, y, totalWidth, totalHeight, rx || 0), options)
: rc.rectangle(x, y, totalWidth, totalHeight, options);
rect = shapeSvg.insert(() => roughNode, ':first-child');
rect.attr('class', 'basic label-container').attr('style', cssStyles);
} else {
rect = shapeSvg.insert('rect', ':first-child');
rect
.attr('class', 'basic label-container')
.attr('style', cssStyles)
.attr('rx', rx)
.attr('data-id', 'abc')
.attr('data-et', 'node')
.attr('ry', ry)
.attr('x', x)
.attr('y', y)
.attr('width', totalWidth)
.attr('height', totalHeight);
}
// if (node.props) {
// const propKeys = new Set(Object.keys(node.props));
// if (node.props.borders) {
// applyNodePropertyBorders(rect, node.props.borders + '', totalWidth, totalHeight);
// propKeys.delete('borders');
// }
// propKeys.forEach((propKey) => {
// log.warn(`Unknown node property ${propKey}`);
// });
// }
updateNodeBounds(node, rect);
node.intersect = function (point) {
return intersect.rect(node, point);
};
return shapeSvg;
};
export const labelRect = async (parent: SVGElement, node: Node) => {
const { shapeSvg } = await labelHelper(parent, node, 'label', true);
// log.trace('Classes = ', node.class);
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
// Hide the rect we are only after the label
const totalWidth = 0;
const totalHeight = 0;
rect.attr('width', totalWidth).attr('height', totalHeight);
shapeSvg.attr('class', 'label edgeLabel');
// if (node.props) {
// const propKeys = new Set(Object.keys(node.props));
// if (node.props.borders) {
// applyNodePropertyBorders(rect, node.borders, totalWidth, totalHeight);
// propKeys.delete('borders');
// }
// propKeys.forEach((propKey) => {
// log.warn(`Unknown node property ${propKey}`);
// });
// }
updateNodeBounds(node, rect);
node.intersect = function (point) {
return intersect.rect(node, point);
};
return shapeSvg;
};

View File

@@ -1,65 +0,0 @@
import { updateNodeBounds } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import type { SVG } from '$root/diagram-api/types.js';
import rough from 'roughjs';
import { solidStateFill } from './handdrawnStyles.js';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
export const forkJoin = (parent: SVG, node: Node, dir: string) => {
const { themeVariables } = getConfig();
const { lineColor } = themeVariables;
const shapeSvg = parent
.insert('g')
.attr('class', 'node default')
.attr('id', node.domId || node.id);
let width = 70;
let height = 10;
if (dir === 'LR') {
width = 10;
height = 70;
}
const x = (-1 * width) / 2;
const y = (-1 * height) / 2;
let shape;
if (node.useRough) {
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const roughNode = rc.rectangle(x, y, width, height, solidStateFill(lineColor));
shape = shapeSvg.insert(() => roughNode);
} else {
shape = shapeSvg
.append('rect')
.attr('x', x)
.attr('y', y)
.attr('width', width)
.attr('height', height)
.attr('class', 'fork-join');
}
updateNodeBounds(node, shape);
let nodeHeight = 0;
let nodeWidth = 0;
let nodePadding = 10;
if (node.height) {
nodeHeight = node.height;
}
if (node.width) {
nodeWidth = node.width;
}
if (node.padding) {
nodePadding = node.padding;
}
node.height = nodeHeight + nodePadding / 2;
node.width = nodeWidth + nodePadding / 2;
node.intersect = function (point) {
return intersect.rect(node, point);
};
return shapeSvg;
};

View File

@@ -1,42 +0,0 @@
import { getConfig } from '$root/diagram-api/diagramAPI.js';
import type { Node } from '$root/rendering-util/types.d.ts';
// Striped fill like start or fork nodes in state diagrams
export const solidStateFill = (color: string) => {
const { handdrawnSeed } = getConfig();
return {
fill: color,
hachureAngle: 120, // angle of hachure,
hachureGap: 4,
fillWeight: 2,
roughness: 0.7,
stroke: color,
seed: handdrawnSeed,
};
};
// Striped fill like start or fork nodes in state diagrams
// TODO remove any
export const userNodeOverrides = (node: Node, options: any) => {
const { themeVariables, handdrawnSeed } = getConfig();
const { nodeBorder, mainBkg } = themeVariables;
const result = Object.assign(
{
roughness: 0.7,
fill: mainBkg,
fillStyle: 'hachure', // solid fill
fillWeight: 3.5,
stroke: nodeBorder,
seed: handdrawnSeed,
strokeWidth: 1.3,
},
options
);
if (node.backgroundColor) {
result.fill = node.backgroundColor;
}
if (node.borderColor) {
result.stroke = node.borderColor;
}
return result;
};

View File

@@ -1,93 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
/**
* Creates an SVG path for a hexagon shape.
* @param {number} x - The x coordinate of the top-left corner.
* @param {number} y - The y coordinate of the top-left corner.
* @param {number} width - The width of the hexagon.
* @param {number} height - The height of the hexagon.
* @param {number} m - The margin size for the hexagon.
* @returns {string} The path data for the hexagon shape.
*/
export const createHexagonPathD = (
x: number,
y: number,
width: number,
height: number,
m: number
): string => {
return [
`M${x + m},${y}`,
`L${x + width - m},${y}`,
`L${x + width},${y - height / 2}`,
`L${x + width - m},${y - height}`,
`L${x + m},${y - height}`,
`L${x},${y - height / 2}`,
'Z',
].join(' ');
};
export const hexagon = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { shapeSvg, bbox, halfPadding } = await labelHelper(
parent,
node,
getNodeClasses(node),
true
);
const f = 4;
const h = bbox.height + node.padding;
const m = h / f;
const w = bbox.width + 2 * m + node.padding;
const points = [
{ x: m, y: 0 },
{ x: w - m, y: 0 },
{ x: w, y: -h / 2 },
{ x: w - m, y: -h },
{ x: m, y: -h },
{ x: 0, y: -h / 2 },
];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
const { cssStyles, useRough } = node;
if (useRough) {
// @ts-ignore
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createHexagonPathD(0, 0, w, h, m);
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 (cssStyles) {
polygon.attr('style', cssStyles);
}
node.width = w;
node.height = h;
updateNodeBounds(node, polygon);
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};
return shapeSvg;
};

View File

@@ -1,25 +0,0 @@
/**
* @param parent
* @param w
* @param h
* @param points
*/
export function insertPolygonShape(
parent: any,
w: number,
h: number,
points: { x: number; y: number }[]
) {
return parent
.insert('polygon', ':first-child')
.attr(
'points',
points
.map(function (d) {
return d.x + ',' + d.y;
})
.join(' ')
)
.attr('class', 'label-container')
.attr('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')');
}

View File

@@ -1,80 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
/**
* Creates an SVG path for an inverted trapezoid shape.
* @param {number} x - The x coordinate of the top-left corner.
* @param {number} y - The y coordinate of the top-left corner.
* @param {number} width - The width of the shape.
* @param {number} height - The height of the shape.
* @returns {string} The path data for the inverted trapezoid shape.
*/
export const createInvertedTrapezoidPathD = (
x: number,
y: number,
width: number,
height: number
): string => {
return [
`M${x + height / 6},${y}`,
`L${x + width - height / 6},${y}`,
`L${x + width + (2 * height) / 6},${y - height}`,
`L${x - (2 * height) / 6},${y - height}`,
'Z',
].join(' ');
};
export const inv_trapezoid = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node), true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
const points = [
{ x: h / 6, y: 0 },
{ x: w - h / 6, y: 0 },
{ x: w + (2 * h) / 6, y: -h },
{ x: (-2 * h) / 6, y: -h },
];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
const { cssStyles, useRough } = node;
if (useRough) {
// @ts-ignore
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createInvertedTrapezoidPathD(0, 0, w, h);
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 (cssStyles) {
polygon.attr('style', cssStyles);
}
node.width = w;
node.height = h;
updateNodeBounds(node, polygon);
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};
return shapeSvg;
};

View File

@@ -1,79 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
/**
* Creates an SVG path for a lean left shape.
* @param {number} x - The x coordinate of the top-left corner.
* @param {number} y - The y coordinate of the top-left corner.
* @param {number} width - The width of the shape.
* @param {number} height - The height of the shape.
* @returns {string} The path data for the lean left shape.
*/
export const createLeanLeftPathD = (
x: number,
y: number,
width: number,
height: number
): string => {
return [
`M${x + (2 * height) / 6},${y}`,
`L${x + width + height / 6},${y}`,
`L${x + width - (2 * height) / 6},${y - height}`,
`L${x - height / 6},${y - height}`,
'Z',
].join(' ');
};
export const lean_left = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node), true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
const points = [
{ x: (2 * h) / 6, y: 0 },
{ x: w + h / 6, y: 0 },
{ x: w - (2 * h) / 6, y: -h },
{ x: -h / 6, y: -h },
];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
const { cssStyles, useRough } = node;
if (useRough) {
// @ts-ignore
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createLeanLeftPathD(0, 0, w, h);
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 (cssStyles) {
polygon.attr('style', cssStyles);
}
node.width = w;
node.height = h;
updateNodeBounds(node, polygon);
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};
return shapeSvg;
};

View File

@@ -1,79 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
/**
* Creates an SVG path for a lean right shape.
* @param {number} x - The x coordinate of the top-left corner.
* @param {number} y - The y coordinate of the top-left corner.
* @param {number} width - The width of the shape.
* @param {number} height - The height of the shape.
* @returns {string} The path data for the lean right shape.
*/
export const createLeanRightPathD = (
x: number,
y: number,
width: number,
height: number
): string => {
return [
`M${x - (2 * height) / 6},${y}`,
`L${x + width - height / 6},${y}`,
`L${x + width + (2 * height) / 6},${y - height}`,
`L${x + height / 6},${y - height}`,
'Z',
].join(' ');
};
export const lean_right = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node), true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
const points = [
{ x: (-2 * h) / 6, y: 0 },
{ x: w - h / 6, y: 0 },
{ x: w + (2 * h) / 6, y: -h },
{ x: h / 6, y: -h },
];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
const { cssStyles, useRough } = node;
if (useRough) {
// @ts-ignore
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createLeanRightPathD(0, 0, w, h);
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 (cssStyles) {
polygon.attr('style', cssStyles);
}
node.width = w;
node.height = h;
updateNodeBounds(node, polygon);
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};
return shapeSvg;
};

View File

@@ -1,64 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds } from './util.js';
import intersect from '../intersect/index.js';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import rough from 'roughjs';
export const note = async (parent: SVGAElement, node: Node) => {
const { themeVariables, handdrawnSeed } = getConfig();
const { noteBorderColor, noteBkgColor } = themeVariables;
const useHtmlLabels = node.useHtmlLabels;
if (!useHtmlLabels) {
node.centerLabel = true;
}
const { shapeSvg, bbox, halfPadding } = await labelHelper(
parent,
node,
'node ' + node.cssClasses,
true
);
log.info('Classes = ', node.cssClasses);
const { cssStyles, useRough } = node;
let rect;
const totalWidth = bbox.width + node.padding;
const totalHeight = bbox.height + node.padding;
const x = -bbox.width / 2 - halfPadding;
const y = -bbox.height / 2 - halfPadding;
if (useRough) {
// add the rect
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const roughNode = rc.rectangle(x, y, totalWidth, totalHeight, {
roughness: 0.7,
fill: noteBkgColor,
fillWeight: 3,
seed: handdrawnSeed,
// fillStyle: 'solid', // solid fill'
stroke: noteBorderColor,
});
rect = shapeSvg.insert(() => roughNode, ':first-child');
rect.attr('class', 'basic label-container').attr('style', cssStyles);
} else {
rect = shapeSvg.insert('rect', ':first-child');
rect
.attr('rx', node.rx)
.attr('ry', node.ry)
.attr('x', x)
.attr('y', y)
.attr('width', totalWidth)
.attr('height', totalHeight);
}
updateNodeBounds(node, rect);
node.intersect = function (point) {
return intersect.rect(node, point);
};
return shapeSvg;
};

View File

@@ -1,73 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
/**
* Creates an SVG path for a decision box shape (question shape).
* @param {number} x - The x coordinate of the top-left corner.
* @param {number} y - The y coordinate of the top-left corner.
* @param {number} size - The size of the shape.
* @returns {string} The path data for the decision box shape.
*/
export const createDecisionBoxPathD = (x: number, y: number, size: number): string => {
return [
`M${x + size / 2},${y}`,
`L${x + size},${y - size / 2}`,
`L${x + size / 2},${y - size}`,
`L${x},${y - size / 2}`,
'Z',
].join(' ');
};
export const question = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node), true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
const s = w + h;
const points = [
{ x: s / 2, y: 0 },
{ x: s, y: -s / 2 },
{ x: s / 2, y: -s },
{ x: 0, y: -s / 2 },
];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
const { cssStyles, useRough } = node;
if (useRough) {
// @ts-ignore
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createDecisionBoxPathD(0, 0, s);
const roughNode = rc.path(pathData, options);
polygon = shapeSvg
.insert(() => roughNode, ':first-child')
.attr('transform', `translate(${-s / 2}, ${s / 2})`);
if (cssStyles) {
polygon.attr('style', cssStyles);
}
} else {
polygon = insertPolygonShape(shapeSvg, s, s, points);
}
if (cssStyles) {
polygon.attr('style', cssStyles);
}
updateNodeBounds(node, polygon);
node.intersect = function (point) {
log.warn('Intersect called');
return intersect.polygon(node, points, point);
};
return shapeSvg;
};

View File

@@ -1,77 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
/**
* Creates an SVG path for a special polygon shape with a left-inverted arrow.
* @param {number} x - The x coordinate of the top-left corner.
* @param {number} y - The y coordinate of the top-left corner.
* @param {number} width - The width of the shape.
* @param {number} height - The height of the shape.
* @returns {string} The path data for the special polygon shape.
*/
export const createPolygonPathD = (x: number, y: number, width: number, height: number): string => {
return [
`M${x - height / 2},${y}`,
`L${x + width},${y}`,
`L${x + width},${y - height}`,
`L${x - height / 2},${y - height}`,
`L${x},${y - height / 2}`,
'Z',
].join(' ');
};
export const rect_left_inv_arrow = async (
parent: SVGAElement,
node: Node
): Promise<SVGAElement> => {
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node), true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
const points = [
{ x: -h / 2, y: 0 },
{ x: w, y: 0 },
{ x: w, y: -h },
{ x: -h / 2, y: -h },
{ x: 0, y: -h / 2 },
];
let polygon;
const { cssStyles, useRough } = node;
if (useRough) {
// @ts-ignore
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createPolygonPathD(0, 0, w, h);
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 (cssStyles) {
polygon.attr('style', cssStyles);
}
node.width = w + h;
node.height = h;
updateNodeBounds(node, polygon);
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};
return shapeSvg;
};

View File

@@ -1,13 +0,0 @@
import type { Node, RectOptions } from '$root/rendering-util/types.d.ts';
import { drawRect } from './drawRect.js';
export const roundedRect = async (parent: SVGAElement, node: Node) => {
const options = {
rx: 5,
ry: 5,
classes: '',
} as RectOptions;
console.log('roundedRect XDX');
return drawRect(parent, node, options);
};

View File

@@ -1,53 +0,0 @@
export const createRoundedRectPathD = (
x: number,
y: number,
totalWidth: number,
totalHeight: number,
radius: number
) =>
[
'M',
x + radius,
y, // Move to the first point
'H',
x + totalWidth - radius, // Draw horizontal line to the beginning of the right corner
'A',
radius,
radius,
0,
0,
1,
x + totalWidth,
y + radius, // Draw arc to the right top corner
'V',
y + totalHeight - radius, // Draw vertical line down to the beginning of the right bottom corner
'A',
radius,
radius,
0,
0,
1,
x + totalWidth - radius,
y + totalHeight, // Draw arc to the right bottom corner
'H',
x + radius, // Draw horizontal line to the beginning of the left bottom corner
'A',
radius,
radius,
0,
0,
1,
x,
y + totalHeight - radius, // Draw arc to the left bottom corner
'V',
y + radius, // Draw vertical line up to the beginning of the left top corner
'A',
radius,
radius,
0,
0,
1,
x + radius,
y, // Draw arc to the left top corner
'Z', // Close the path
].join(' ');

View File

@@ -1,11 +0,0 @@
import type { Node, RectOptions } from '$root/rendering-util/types.d.ts';
import { drawRect } from './drawRect.js';
export const squareRect = async (parent: SVGAElement, node: Node) => {
const options = {
rx: 0,
ry: 0,
classes: '',
} as RectOptions;
return drawRect(parent, node, options);
};

View File

@@ -1,93 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
import { createRoundedRectPathD } from './roundedRectPath.js';
export const createStadiumPathD = (
x: number,
y: number,
totalWidth: number,
totalHeight: number
) => {
const radius = totalHeight / 2;
return [
'M',
x + radius,
y, // Move to the start of the top-left arc
'H',
x + totalWidth - radius, // Draw horizontal line to the start of the top-right arc
'A',
radius,
radius,
0,
0,
1,
x + totalWidth,
y + radius, // Draw top-right arc
'H',
x, // Draw horizontal line to the start of the bottom-right arc
'A',
radius,
radius,
0,
0,
1,
x + totalWidth - radius,
y + totalHeight, // Draw bottom-right arc
'H',
x + radius, // Draw horizontal line to the start of the bottom-left arc
'A',
radius,
radius,
0,
0,
1,
x,
y + radius, // Draw bottom-left arc
'Z', // Close the path
].join(' ');
};
export const stadium = async (parent: SVGAElement, node: Node) => {
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node), true);
const h = bbox.height + node.padding;
const w = bbox.width + h / 4 + node.padding;
let rect;
const { cssStyles, useRough } = node;
if (useRough) {
// @ts-ignore
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createRoundedRectPathD(-w / 2, -h / 2, w, h, h / 2);
const roughNode = rc.path(pathData, options);
rect = shapeSvg.insert(() => roughNode, ':first-child');
rect.attr('class', 'basic label-container').attr('style', cssStyles);
} else {
rect = shapeSvg.insert('rect', ':first-child');
rect
.attr('class', 'basic label-container')
.attr('style', cssStyles)
.attr('rx', h / 2)
.attr('ry', h / 2)
.attr('x', -w / 2)
.attr('y', -h / 2)
.attr('width', w)
.attr('height', h);
}
updateNodeBounds(node, rect);
node.intersect = function (point) {
return intersect.rect(node, point);
};
return shapeSvg;
};

View File

@@ -1,11 +0,0 @@
import type { Node } from '$root/rendering-util/types.d.ts';
import { drawRect } from './drawRect.js';
export const state = async (parent: SVGAElement, node: Node) => {
const options = {
rx: 5,
ry: 5,
classes: 'flowchart-node',
};
return drawRect(parent, node, options);
};

View File

@@ -1,43 +0,0 @@
import { log } from '$root/logger.js';
import { updateNodeBounds } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import type { SVG } from '$root/diagram-api/types.js';
import rough from 'roughjs';
import { solidStateFill } from './handdrawnStyles.js';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
export const stateEnd = (parent: SVG, node: Node) => {
const { themeVariables } = getConfig();
const { lineColor } = themeVariables;
const shapeSvg = parent
.insert('g')
.attr('class', 'node default')
.attr('id', node.domId || node.id);
let circle;
let innerCircle;
if (node.useRough) {
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const roughNode = rc.circle(0, 0, 14, { ...solidStateFill(lineColor), roughness: 0.5 });
const roughInnerNode = rc.circle(0, 0, 5, { ...solidStateFill(lineColor), fillStyle: 'solid' });
circle = shapeSvg.insert(() => roughNode);
innerCircle = shapeSvg.insert(() => roughInnerNode);
} else {
innerCircle = shapeSvg.insert('circle', ':first-child');
circle = shapeSvg.insert('circle', ':first-child');
circle.attr('class', 'state-start').attr('r', 7).attr('width', 14).attr('height', 14);
innerCircle.attr('class', 'state-end').attr('r', 5).attr('width', 10).attr('height', 10);
}
updateNodeBounds(node, circle);
node.intersect = function (point) {
return intersect.circle(node, 7, point);
};
return shapeSvg;
};

View File

@@ -1,40 +0,0 @@
import { log } from '$root/logger.js';
import { updateNodeBounds } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import type { SVG } from '$root/diagram-api/types.js';
import rough from 'roughjs';
import { solidStateFill } from './handdrawnStyles.js';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
export const stateStart = (parent: SVG, node: Node) => {
const { themeVariables } = getConfig();
const { lineColor } = themeVariables;
const shapeSvg = parent
.insert('g')
.attr('class', 'node default')
.attr('id', node.domId || node.id);
let circle;
if (node.useRough) {
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const roughNode = rc.circle(0, 0, 14, solidStateFill(lineColor));
circle = shapeSvg.insert(() => roughNode);
} else {
circle = shapeSvg.insert('circle', ':first-child');
}
// center the circle around its coordinate
// @ts-ignore TODO: Fix typings
circle.attr('class', 'state-start').attr('r', 7).attr('width', 14).attr('height', 14);
updateNodeBounds(node, circle);
node.intersect = function (point) {
return intersect.circle(node, 7, point);
};
return shapeSvg;
};

View File

@@ -1,87 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
export const createSubroutinePathD = (
x: number,
y: number,
width: number,
height: number
): string => {
const offset = 8;
return [
`M${x - offset},${y}`,
`H${x + width + offset}`,
`V${y + height}`,
`H${x - offset}`,
`V${y}`,
'M',
x,
y,
'H',
x + width,
'V',
y + height,
'H',
x,
'Z',
].join(' ');
};
export const subroutine = async (parent: SVGAElement, node: Node) => {
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node), true);
const halfPadding = (node?.padding || 0) / 2;
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
const x = -bbox.width / 2 - halfPadding;
const y = -bbox.height / 2 - halfPadding;
let rect;
const { cssStyles, useRough } = node;
const points = [
{ x: 0, y: 0 },
{ x: w, y: 0 },
{ x: w, y: -h },
{ x: 0, y: -h },
{ x: 0, y: 0 },
{ x: -8, y: 0 },
{ x: w + 8, y: 0 },
{ x: w + 8, y: -h },
{ x: -8, y: -h },
{ x: -8, y: 0 },
];
if (useRough) {
// @ts-ignore
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createSubroutinePathD(-w / 2, -h / 2, w, h);
const roughNode = rc.rectangle(x - 8, y, w + 16, h, options);
const l1 = rc.line(x, y, x, y + h, options);
const l2 = rc.line(x + w, y, x + w, y + h, options);
shapeSvg.insert(() => l1, ':first-child');
shapeSvg.insert(() => l2, ':first-child');
rect = shapeSvg.insert(() => roughNode, ':first-child');
rect.attr('class', 'basic label-container').attr('style', cssStyles);
} else {
const el = insertPolygonShape(shapeSvg, w, h, points);
if (cssStyles) {
el.attr('style', cssStyles);
}
updateNodeBounds(node, el);
}
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};
return shapeSvg;
};
export default subroutine;

View File

@@ -1,80 +0,0 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
/**
* Creates an SVG path for a trapezoid shape.
* @param {number} x - The x coordinate of the top-left corner.
* @param {number} y - The y coordinate of the top-left corner.
* @param {number} width - The width of the shape.
* @param {number} height - The height of the shape.
* @returns {string} The path data for the trapezoid shape.
*/
export const createTrapezoidPathD = (
x: number,
y: number,
width: number,
height: number
): string => {
return [
`M${x - (2 * height) / 6},${y}`,
`L${x + width + (2 * height) / 6},${y}`,
`L${x + width - height / 6},${y - height}`,
`L${x + height / 6},${y - height}`,
'Z',
].join(' ');
};
export const trapezoid = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node), true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
const points = [
{ x: (-2 * h) / 6, y: 0 },
{ x: w + (2 * h) / 6, y: 0 },
{ x: w - h / 6, y: -h },
{ x: h / 6, y: -h },
];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
const { cssStyles, useRough } = node;
if (useRough) {
console.log('Trapezoid: Inside use useRough');
// @ts-ignore
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createTrapezoidPathD(0, 0, w, h);
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 (cssStyles) {
polygon.attr('style', cssStyles);
}
node.width = w;
node.height = h;
updateNodeBounds(node, polygon);
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};
return shapeSvg;
};

View File

@@ -1,144 +0,0 @@
import createLabel from '../createLabel.js';
import { createText } from '$root/rendering-util/createText.ts';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
import { select } from 'd3';
import { evaluate, sanitizeText } from '$root/diagrams/common/common.js';
import { decodeEntities } from '$root/utils.js';
export const labelHelper = async (parent, node, _classes, isNode) => {
let cssClasses;
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig().flowchart.htmlLabels);
if (!_classes) {
cssClasses = 'node default';
} else {
cssClasses = _classes;
}
// Add outer g element
const shapeSvg = parent
.insert('g')
.attr('class', cssClasses)
.attr('id', node.domId || node.id);
// Create the label and insert it after the rect
const labelEl = shapeSvg.insert('g').attr('class', 'label').attr('style', node.labelStyle);
// Replace label with default value if undefined
let label;
if (node.label === undefined) {
label = '';
} else {
label = typeof node.label === 'string' ? node.label : node.label[0];
}
const textNode = labelEl.node();
let text;
if (node.labelType === 'markdown') {
// text = textNode;
text = createText(labelEl, sanitizeText(decodeEntities(label), getConfig()), {
useHtmlLabels,
width: node.width || getConfig().flowchart.wrappingWidth,
cssClasses: 'markdown-node-label',
});
} else {
text = textNode.appendChild(
createLabel(sanitizeText(decodeEntities(label), getConfig()), node.labelStyle, false, isNode)
);
}
// Get the size of the label
let bbox = text.getBBox();
const halfPadding = node.padding / 2;
if (evaluate(getConfig().flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
// if there are images, need to wait for them to load before getting the bounding box
const images = div.getElementsByTagName('img');
if (images) {
const noImgText = label.replace(/<img[^>]*>/g, '').trim() === '';
await Promise.all(
[...images].map(
(img) =>
new Promise((res) => {
/**
*
*/
function setupImage() {
img.style.display = 'flex';
img.style.flexDirection = 'column';
if (noImgText) {
// default size if no text
const bodyFontSize = getConfig().fontSize
? getConfig().fontSize
: window.getComputedStyle(document.body).fontSize;
const enlargingFactor = 5;
const width = parseInt(bodyFontSize, 10) * enlargingFactor + 'px';
img.style.minWidth = width;
img.style.maxWidth = width;
} else {
img.style.width = '100%';
}
res(img);
}
setTimeout(() => {
if (img.complete) {
setupImage();
}
});
img.addEventListener('error', setupImage);
img.addEventListener('load', setupImage);
})
)
);
}
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
// Center the label
if (useHtmlLabels) {
labelEl.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
} else {
labelEl.attr('transform', 'translate(' + 0 + ', ' + -bbox.height / 2 + ')');
}
if (node.centerLabel) {
labelEl.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
}
labelEl.insert('rect', ':first-child');
return { shapeSvg, bbox, halfPadding, label: labelEl };
};
export const updateNodeBounds = (node, element) => {
const bbox = element.node().getBBox();
node.width = bbox.width;
node.height = bbox.height;
};
/**
* @param parent
* @param w
* @param h
* @param points
*/
export function insertPolygonShape(parent, w, h, points) {
return parent
.insert('polygon', ':first-child')
.attr(
'points',
points
.map(function (d) {
return d.x + ',' + d.y;
})
.join(' ')
)
.attr('class', 'label-container')
.attr('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')');
}
export const getNodeClasses = (node, extra) =>
(node.useRough ? 'rough-node' : 'node') + ' ' + node.cssClasses + ' ' + (extra || '');

View File

@@ -1,40 +0,0 @@
import { configureSvgSize } from '$root/setupGraphViewbox.js';
import type { SVG } from '$root/diagram-api/types.js';
import { log } from '$root/logger.js';
export const setupViewPortForSVG = (
svg: SVG,
padding: number,
cssDiagram: string,
useMaxWidth: boolean
) => {
// Initialize the SVG element and set the diagram class
svg.attr('class', cssDiagram);
// Calculate the dimensions and position with padding
const { width, height, x, y } = calculateDimensionsWithPadding(svg, padding);
// Configure the size and aspect ratio of the SVG
configureSvgSize(svg, height, width, useMaxWidth);
// Update the viewBox to ensure all content is visible with padding
const viewBox = createViewBox(x, y, width, height, padding);
svg.attr('viewBox', viewBox);
// Log the viewBox configuration for debugging
log.debug(`viewBox configured: ${viewBox}`);
};
const calculateDimensionsWithPadding = (svg: SVG, padding: number) => {
const bounds = svg.node()?.getBBox() || { width: 0, height: 0, x: 0, y: 0 };
return {
width: bounds.width + padding * 2,
height: bounds.height + padding * 2,
x: bounds.x,
y: bounds.y,
};
};
const createViewBox = (x: number, y: number, width: number, height: number, padding: number) => {
return `${x - padding} ${y - padding} ${width} ${height}`;
};

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