mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-16 14:59:24 +02:00
initial commit
This commit is contained in:
@@ -41,6 +41,21 @@ const packageOptions = {
|
|||||||
packageName: 'mermaid-mindmap',
|
packageName: 'mermaid-mindmap',
|
||||||
file: 'detector.ts',
|
file: 'detector.ts',
|
||||||
},
|
},
|
||||||
|
'mermaid-timeline': {
|
||||||
|
name: 'mermaid-timeline',
|
||||||
|
packageName: 'mermaid-timeline',
|
||||||
|
file: 'diagram-definition.ts',
|
||||||
|
},
|
||||||
|
// 'mermaid-timeline-detector': {
|
||||||
|
// name: 'mermaid-timeline-detector',
|
||||||
|
// packageName: 'mermaid-timeline',
|
||||||
|
// file: 'detector.ts',
|
||||||
|
// },
|
||||||
|
// 'mermaid-example-diagram': {
|
||||||
|
// name: 'mermaid-example-diagram',
|
||||||
|
// packageName: 'mermaid-example-diagram',
|
||||||
|
// file: 'diagram-definition.ts',
|
||||||
|
// },
|
||||||
// 'mermaid-example-diagram-detector': {
|
// 'mermaid-example-diagram-detector': {
|
||||||
// name: 'mermaid-example-diagram-detector',
|
// name: 'mermaid-example-diagram-detector',
|
||||||
// packageName: 'mermaid-example-diagram',
|
// packageName: 'mermaid-example-diagram',
|
||||||
@@ -123,6 +138,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
|||||||
'packages/mermaid-mindmap/src/**',
|
'packages/mermaid-mindmap/src/**',
|
||||||
'packages/mermaid/src/**',
|
'packages/mermaid/src/**',
|
||||||
// 'packages/mermaid-example-diagram/src/**',
|
// 'packages/mermaid-example-diagram/src/**',
|
||||||
|
'packages/mermaid-timeline/src/**',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -150,6 +166,8 @@ if (watch) {
|
|||||||
if (!mermaidOnly) {
|
if (!mermaidOnly) {
|
||||||
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-mindmap' }));
|
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-mindmap' }));
|
||||||
// build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
|
// build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
|
||||||
|
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-timeline' }));
|
||||||
|
//build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-timeline-detector' }));
|
||||||
}
|
}
|
||||||
} else if (visualize) {
|
} else if (visualize) {
|
||||||
await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' }));
|
await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' }));
|
||||||
|
@@ -24,6 +24,7 @@ async function createServer() {
|
|||||||
app.use(express.static('./packages/mermaid/dist'));
|
app.use(express.static('./packages/mermaid/dist'));
|
||||||
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
||||||
app.use(express.static('./packages/mermaid-mindmap/dist'));
|
app.use(express.static('./packages/mermaid-mindmap/dist'));
|
||||||
|
app.use(express.static('./packages/mermaid-timeline/dist'));
|
||||||
app.use(vite.middlewares);
|
app.use(vite.middlewares);
|
||||||
app.use(express.static('demos'));
|
app.use(express.static('demos'));
|
||||||
app.use(express.static('cypress/platform'));
|
app.use(express.static('cypress/platform'));
|
||||||
|
162
cypress/platform/ashish2.html
Normal file
162
cypress/platform/ashish2.html
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<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: #eee;
|
||||||
|
background-image: radial-gradient(#fff 1%, transparent 11%),
|
||||||
|
radial-gradient(#fff 1%, transparent 11%);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>Security check</div>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
flowchart TD
|
||||||
|
A --> B
|
||||||
|
B --> C
|
||||||
|
A --> C
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
direction LR
|
||||||
|
class Student {
|
||||||
|
-idCard : IdCard
|
||||||
|
}
|
||||||
|
class IdCard{
|
||||||
|
-id : int
|
||||||
|
-name : string
|
||||||
|
}
|
||||||
|
class Bike{
|
||||||
|
-id : int
|
||||||
|
-name : string
|
||||||
|
}
|
||||||
|
Student "1" --o "1" IdCard : carries
|
||||||
|
Student "1" --o "1" Bike : rides
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
timeline
|
||||||
|
title History of Social Media Platform
|
||||||
|
2002 : LinkedIn
|
||||||
|
2004 : Facebook : Google
|
||||||
|
2005 : Youtube
|
||||||
|
2006 : Twitter
|
||||||
|
2007 : Tumblr
|
||||||
|
2008s : Instagram
|
||||||
|
2010 : Pinterest
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
mindmap
|
||||||
|
root
|
||||||
|
child1((Circle))
|
||||||
|
grandchild 1
|
||||||
|
grandchild 2
|
||||||
|
child2(Round rectangle)
|
||||||
|
grandchild 3
|
||||||
|
grandchild 4
|
||||||
|
child3[Square]
|
||||||
|
grandchild 5
|
||||||
|
::icon(mdi mdi-fire)
|
||||||
|
gc6((grand<br/>child 6))
|
||||||
|
::icon(mdi mdi-fire)
|
||||||
|
gc7((grand<br/>grand<br/>child 8))
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
gantt
|
||||||
|
title Style today marker (vertical line should be 5px wide and half-transparent blue)
|
||||||
|
dateFormat YYYY-MM-DD
|
||||||
|
axisFormat %d
|
||||||
|
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
|
||||||
|
section Section1
|
||||||
|
Today: 1, -1h
|
||||||
|
</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 type="module">
|
||||||
|
import mindmap from '../../packages/mermaid-mindmap/src/detector';
|
||||||
|
// import example from '../../packages/mermaid-example-diagram/src/detector';
|
||||||
|
import timeline from '../../packages/mermaid-timeline/src/detector';
|
||||||
|
import mermaid from '../../packages/mermaid/src/mermaid';
|
||||||
|
await mermaid.registerExternalDiagrams([mindmap, timeline]);
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
// console.error('Mermaid error: ', err);
|
||||||
|
};
|
||||||
|
mermaid.initialize({
|
||||||
|
theme: 'base',
|
||||||
|
startOnLoad: true,
|
||||||
|
logLevel: 0,
|
||||||
|
flowchart: {
|
||||||
|
useMaxWidth: false,
|
||||||
|
htmlLabels: true,
|
||||||
|
},
|
||||||
|
gantt: {
|
||||||
|
useMaxWidth: false,
|
||||||
|
},
|
||||||
|
useMaxWidth: false,
|
||||||
|
lazyLoadedDiagrams: [
|
||||||
|
// './mermaid-mindmap-detector.esm.mjs',
|
||||||
|
// './mermaid-example-diagram-detector.esm.mjs',
|
||||||
|
//'./mermaid-timeline-detector.esm.mjs',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
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));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -46,13 +46,9 @@
|
|||||||
<pre class="mermaid" style="width: 100%; height: 20%">
|
<pre class="mermaid" style="width: 100%; height: 20%">
|
||||||
%%{init: {'theme': 'base', 'fontFamily': 'courier', 'themeVariables': { 'primaryColor': '#fff000'}}}%%
|
%%{init: {'theme': 'base', 'fontFamily': 'courier', 'themeVariables': { 'primaryColor': '#fff000'}}}%%
|
||||||
classDiagram-v2
|
classDiagram-v2
|
||||||
class BankAccount{
|
classA <|-- classB : implements
|
||||||
+String owner
|
classC *-- classD : composition
|
||||||
+BigDecimal balance
|
classE o-- classF : aggregation
|
||||||
+deposit(amount) bool
|
|
||||||
+withdrawl(amount) int
|
|
||||||
}
|
|
||||||
cssClass "BankAccount" customCss
|
|
||||||
</pre>
|
</pre>
|
||||||
<pre class="mermaid2" style="width: 100%; height: 20%">
|
<pre class="mermaid2" style="width: 100%; height: 20%">
|
||||||
%%{init: {'theme': 'base', 'fontFamily': 'courier', 'themeVariables': { 'primaryColor': '#fff000'}}}%%
|
%%{init: {'theme': 'base', 'fontFamily': 'courier', 'themeVariables': { 'primaryColor': '#fff000'}}}%%
|
||||||
|
38
demos/timeline.html
Normal file
38
demos/timeline.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<title>Mermaid Quick Test Page</title>
|
||||||
|
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||||
|
<style>
|
||||||
|
div.mermaid {
|
||||||
|
/* font-family: 'trebuchet ms', verdana, arial; */
|
||||||
|
font-family: 'Courier New', Courier, monospace !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<pre class="mermaid">
|
||||||
|
timeline
|
||||||
|
title My day
|
||||||
|
section Go to work
|
||||||
|
1930 : first step : second step
|
||||||
|
: third step
|
||||||
|
1940 : fourth step : fifth step
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<script src="./mermaid.js"></script>
|
||||||
|
<script>
|
||||||
|
mermaid.initialize({
|
||||||
|
theme: 'forest',
|
||||||
|
logLevel: 1,
|
||||||
|
securityLevel: 'loose',
|
||||||
|
flowchart: { curve: 'basis' },
|
||||||
|
gantt: { axisFormat: '%m/%d/%Y' },
|
||||||
|
sequence: { actorMargin: 50 },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -22,6 +22,7 @@ export const log: Record<keyof typeof LEVELS, typeof console.log> = {
|
|||||||
export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
|
export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
|
||||||
export let getConfig: () => object;
|
export let getConfig: () => object;
|
||||||
export let sanitizeText: (str: string) => string;
|
export let sanitizeText: (str: string) => string;
|
||||||
|
export let commonDb: any;
|
||||||
/**
|
/**
|
||||||
* Placeholder for the real function that will be injected by mermaid.
|
* Placeholder for the real function that will be injected by mermaid.
|
||||||
*/
|
*/
|
||||||
@@ -41,15 +42,17 @@ export let setupGraphViewbox: (
|
|||||||
* @param _getConfig - getConfig from mermaid/src/diagramAPI.ts
|
* @param _getConfig - getConfig from mermaid/src/diagramAPI.ts
|
||||||
* @param _sanitizeText - sanitizeText from mermaid/src/diagramAPI.ts
|
* @param _sanitizeText - sanitizeText from mermaid/src/diagramAPI.ts
|
||||||
* @param _setupGraphViewbox - setupGraphViewbox from mermaid/src/diagramAPI.ts
|
* @param _setupGraphViewbox - setupGraphViewbox from mermaid/src/diagramAPI.ts
|
||||||
|
* @param _commonDb
|
||||||
*/
|
*/
|
||||||
export const injectUtils = (
|
export const injectUtils = (
|
||||||
_log: Record<keyof typeof LEVELS, typeof console.log>,
|
_log: Record<keyof typeof LEVELS, typeof console.log>,
|
||||||
_setLogLevel: typeof setLogLevel,
|
_setLogLevel: typeof setLogLevel,
|
||||||
_getConfig: typeof getConfig,
|
_getConfig: typeof getConfig,
|
||||||
_sanitizeText: typeof sanitizeText,
|
_sanitizeText: typeof sanitizeText,
|
||||||
_setupGraphViewbox: typeof setupGraphViewbox
|
_setupGraphViewbox: typeof setupGraphViewbox,
|
||||||
|
_commonDb: any
|
||||||
) => {
|
) => {
|
||||||
_log.debug('Mermaid utils injected into example-diagram');
|
_log.info('Mermaid utils injected into timeline-diagram');
|
||||||
log.trace = _log.trace;
|
log.trace = _log.trace;
|
||||||
log.debug = _log.debug;
|
log.debug = _log.debug;
|
||||||
log.info = _log.info;
|
log.info = _log.info;
|
||||||
@@ -60,4 +63,6 @@ export const injectUtils = (
|
|||||||
getConfig = _getConfig;
|
getConfig = _getConfig;
|
||||||
sanitizeText = _sanitizeText;
|
sanitizeText = _sanitizeText;
|
||||||
setupGraphViewbox = _setupGraphViewbox;
|
setupGraphViewbox = _setupGraphViewbox;
|
||||||
|
commonDb = _commonDb;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@@ -26,6 +26,7 @@ export const log: Record<keyof typeof LEVELS, typeof console.log> = {
|
|||||||
export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
|
export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
|
||||||
export let getConfig: () => object;
|
export let getConfig: () => object;
|
||||||
export let sanitizeText: (str: string) => string;
|
export let sanitizeText: (str: string) => string;
|
||||||
|
export let commonDb: () => object;
|
||||||
// eslint-disable @typescript-eslint/no-explicit-any
|
// eslint-disable @typescript-eslint/no-explicit-any
|
||||||
export let setupGraphViewbox: (
|
export let setupGraphViewbox: (
|
||||||
graph: any,
|
graph: any,
|
||||||
@@ -39,7 +40,8 @@ export const injectUtils = (
|
|||||||
_setLogLevel: any,
|
_setLogLevel: any,
|
||||||
_getConfig: any,
|
_getConfig: any,
|
||||||
_sanitizeText: any,
|
_sanitizeText: any,
|
||||||
_setupGraphViewbox: any
|
_setupGraphViewbox: any,
|
||||||
|
_commonDb: any
|
||||||
) => {
|
) => {
|
||||||
_log.info('Mermaid utils injected');
|
_log.info('Mermaid utils injected');
|
||||||
log.trace = _log.trace;
|
log.trace = _log.trace;
|
||||||
@@ -52,4 +54,5 @@ export const injectUtils = (
|
|||||||
getConfig = _getConfig;
|
getConfig = _getConfig;
|
||||||
sanitizeText = _sanitizeText;
|
sanitizeText = _sanitizeText;
|
||||||
setupGraphViewbox = _setupGraphViewbox;
|
setupGraphViewbox = _setupGraphViewbox;
|
||||||
|
commonDb= _commonDb;
|
||||||
};
|
};
|
||||||
|
@@ -11,7 +11,7 @@ cytoscape.use(coseBilkent);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {any} svg The svg element to draw the diagram onto
|
* @param {any} svg The svg element to draw the diagram onto
|
||||||
* @param {object} mindmap The mindmap data and hierarchy
|
* @param {object} mindmap The maindmap data and hierarchy
|
||||||
* @param section
|
* @param section
|
||||||
* @param {object} conf The configuration object
|
* @param {object} conf The configuration object
|
||||||
*/
|
*/
|
||||||
@@ -89,6 +89,7 @@ function addNodes(mindmap, cy, conf, level) {
|
|||||||
/**
|
/**
|
||||||
* @param node
|
* @param node
|
||||||
* @param conf
|
* @param conf
|
||||||
|
* @param cy
|
||||||
*/
|
*/
|
||||||
function layoutMindmap(node, conf) {
|
function layoutMindmap(node, conf) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -109,7 +110,7 @@ function layoutMindmap(node, conf) {
|
|||||||
renderEl.remove();
|
renderEl.remove();
|
||||||
addNodes(node, cy, conf, 0);
|
addNodes(node, cy, conf, 0);
|
||||||
|
|
||||||
// Make cytoscape care about the dimensions of the nodes
|
// Make cytoscape care about the dimensisions of the nodes
|
||||||
cy.nodes().forEach(function (n) {
|
cy.nodes().forEach(function (n) {
|
||||||
n.layoutDimensions = () => {
|
n.layoutDimensions = () => {
|
||||||
const data = n.data();
|
const data = n.data();
|
||||||
@@ -131,7 +132,10 @@ function layoutMindmap(node, conf) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
* @param node
|
||||||
* @param cy
|
* @param cy
|
||||||
|
* @param positionedMindmap
|
||||||
|
* @param conf
|
||||||
*/
|
*/
|
||||||
function positionNodes(cy) {
|
function positionNodes(cy) {
|
||||||
cy.nodes().map((node, id) => {
|
cy.nodes().map((node, id) => {
|
||||||
@@ -169,7 +173,7 @@ export const draw = async (text, id, version, diagObj) => {
|
|||||||
log.debug('Renering info diagram\n' + text);
|
log.debug('Renering info diagram\n' + text);
|
||||||
|
|
||||||
const securityLevel = getConfig().securityLevel;
|
const securityLevel = getConfig().securityLevel;
|
||||||
// Handle root and Document for when rendering in sandbox mode
|
// Handle root and Document for when rendering in sanbox mode
|
||||||
let sandboxElement;
|
let sandboxElement;
|
||||||
if (securityLevel === 'sandbox') {
|
if (securityLevel === 'sandbox') {
|
||||||
sandboxElement = select('#i' + id);
|
sandboxElement = select('#i' + id);
|
||||||
|
69
packages/mermaid-timeline/package.json
Normal file
69
packages/mermaid-timeline/package.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"name": "@mermaid-js/mermaid-timeline",
|
||||||
|
"version": "9.2.0-rc2",
|
||||||
|
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||||
|
"main": "dist/mermaid-timeline.core.mjs",
|
||||||
|
"module": "dist/mermaid-timeline.core.mjs",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"require": "./dist/mermaid-timeline.min.js",
|
||||||
|
"import": "./dist/mermaid-timeline.core.mjs"
|
||||||
|
},
|
||||||
|
"./*": "./*"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"diagram",
|
||||||
|
"markdown",
|
||||||
|
"timeline",
|
||||||
|
"mermaid"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"build:types": "tsc -p ./tsconfig.json --emitDeclarationOnly",
|
||||||
|
"build:watch": "yarn build:code --watch",
|
||||||
|
"build:esbuild": "concurrently \"yarn build:code\" \"yarn build:types\"",
|
||||||
|
"build": "yarn clean; yarn build:esbuild",
|
||||||
|
"dev": "node .esbuild/serve.cjs",
|
||||||
|
"release": "yarn build",
|
||||||
|
"lint": "eslint --cache --ignore-path .gitignore . && yarn lint:jison && prettier --check .",
|
||||||
|
"lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write .",
|
||||||
|
"lint:jison": "ts-node-esm src/jison/lint.mts",
|
||||||
|
"todo-prepare": "concurrently \"husky install ../../.husky\" \"yarn build\"",
|
||||||
|
"todo-pre-commit": "lint-staged"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/mermaid-js/mermaid"
|
||||||
|
},
|
||||||
|
"author": "Knut Sveidqvist",
|
||||||
|
"license": "MIT",
|
||||||
|
"standard": {
|
||||||
|
"ignore": [
|
||||||
|
"**/parser/*.js",
|
||||||
|
"dist/**/*.js",
|
||||||
|
"cypress/**/*.js"
|
||||||
|
],
|
||||||
|
"globals": [
|
||||||
|
"page"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"d3": "^7.0.0",
|
||||||
|
"khroma": "^2.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"concurrently": "^7.4.0",
|
||||||
|
"rimraf": "^3.0.2"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"d3": "^7.0.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"sideEffects": [
|
||||||
|
"**/*.css",
|
||||||
|
"**/*.scss"
|
||||||
|
]
|
||||||
|
}
|
20
packages/mermaid-timeline/src/detector.ts
Normal file
20
packages/mermaid-timeline/src/detector.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { ExternalDiagramDefinition } from 'mermaid';
|
||||||
|
|
||||||
|
const id = 'timeline';
|
||||||
|
|
||||||
|
const detector = (txt: string) => {
|
||||||
|
return txt.match(/^\s*timeline/) !== null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loader = async () => {
|
||||||
|
const { diagram } = await import('./diagram-definition');
|
||||||
|
return { id, diagram };
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: ExternalDiagramDefinition = {
|
||||||
|
id,
|
||||||
|
detector,
|
||||||
|
loader,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
14
packages/mermaid-timeline/src/diagram-definition.ts
Normal file
14
packages/mermaid-timeline/src/diagram-definition.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// @ts-ignore: TODO Fix ts errors
|
||||||
|
import parser from './parser/timeline.jison';
|
||||||
|
import * as db from './timelineDb';
|
||||||
|
import renderer from './timelineRenderer';
|
||||||
|
import styles from './styles';
|
||||||
|
import { injectUtils } from './mermaidUtils';
|
||||||
|
|
||||||
|
export const diagram = {
|
||||||
|
db,
|
||||||
|
renderer,
|
||||||
|
parser,
|
||||||
|
styles,
|
||||||
|
injectUtils,
|
||||||
|
};
|
70
packages/mermaid-timeline/src/mermaidUtils.ts
Normal file
70
packages/mermaid-timeline/src/mermaidUtils.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
const warning = () => null;
|
||||||
|
let localCommonDb = {};
|
||||||
|
|
||||||
|
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
||||||
|
|
||||||
|
export const LEVELS: Record<LogLevel, number> = {
|
||||||
|
trace: 0,
|
||||||
|
debug: 1,
|
||||||
|
info: 2,
|
||||||
|
warn: 3,
|
||||||
|
error: 4,
|
||||||
|
fatal: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const log: Record<keyof typeof LEVELS, typeof console.log> = {
|
||||||
|
trace: warning,
|
||||||
|
debug: warning,
|
||||||
|
info: warning,
|
||||||
|
warn: warning,
|
||||||
|
error: warning,
|
||||||
|
fatal: warning,
|
||||||
|
};
|
||||||
|
export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
|
||||||
|
export let getConfig: () => object;
|
||||||
|
export let sanitizeText: (str: string) => string;
|
||||||
|
export const getCommonDb=() => localCommonDb;
|
||||||
|
/**
|
||||||
|
* Placeholder for the real function that will be injected by mermaid.
|
||||||
|
*/
|
||||||
|
// eslint-disable @typescript-eslint/no-explicit-any
|
||||||
|
export let setupGraphViewbox: (
|
||||||
|
graph: any,
|
||||||
|
svgElem: any,
|
||||||
|
padding: any,
|
||||||
|
useMaxWidth: boolean
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called by mermaid that injects utility functions that help the diagram to be a good citizen.
|
||||||
|
* @param _log
|
||||||
|
* @param _setLogLevel
|
||||||
|
* @param _getConfig
|
||||||
|
* @param _sanitizeText
|
||||||
|
* @param _setupGraphViewbox
|
||||||
|
* @param _commonDb
|
||||||
|
*/
|
||||||
|
export const injectUtils = (
|
||||||
|
_log: Record<keyof typeof LEVELS, typeof console.log>,
|
||||||
|
_setLogLevel: any,
|
||||||
|
_getConfig: any,
|
||||||
|
_sanitizeText: any,
|
||||||
|
_setupGraphViewbox: any,
|
||||||
|
_commonDb: any
|
||||||
|
) => {
|
||||||
|
_log.info('Mermaid utils injected into timeline-diagram');
|
||||||
|
log.trace = _log.trace;
|
||||||
|
log.debug = _log.debug;
|
||||||
|
log.info = _log.info;
|
||||||
|
log.warn = _log.warn;
|
||||||
|
log.error = _log.error;
|
||||||
|
log.fatal = _log.fatal;
|
||||||
|
setLogLevel = _setLogLevel;
|
||||||
|
getConfig = _getConfig;
|
||||||
|
sanitizeText = _sanitizeText;
|
||||||
|
setupGraphViewbox = _setupGraphViewbox;
|
||||||
|
localCommonDb = _commonDb;
|
||||||
|
|
||||||
|
};
|
106
packages/mermaid-timeline/src/parser/timeline.jison
Normal file
106
packages/mermaid-timeline/src/parser/timeline.jison
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/** mermaid
|
||||||
|
* https://mermaidjs.github.io/
|
||||||
|
* (c) 2015 Knut Sveidqvist
|
||||||
|
* MIT license.
|
||||||
|
*/
|
||||||
|
%lex
|
||||||
|
%options case-insensitive
|
||||||
|
%x acc_title
|
||||||
|
%x acc_descr
|
||||||
|
%x acc_descr_multiline
|
||||||
|
|
||||||
|
// Directive states
|
||||||
|
%x open_directive type_directive arg_directive
|
||||||
|
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
|
||||||
|
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||||
|
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||||
|
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||||
|
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
|
||||||
|
\%%(?!\{)[^\n]* /* skip comments */
|
||||||
|
[^\}]\%\%[^\n]* /* skip comments */
|
||||||
|
[\n]+ return 'NEWLINE';
|
||||||
|
\s+ /* skip whitespace */
|
||||||
|
\#[^\n]* /* skip comments */
|
||||||
|
|
||||||
|
"timeline" return 'timeline';
|
||||||
|
"title"\s[^#\n;]+ return 'title';
|
||||||
|
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
|
||||||
|
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
|
||||||
|
accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
|
||||||
|
<acc_descr>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; }
|
||||||
|
accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
|
||||||
|
<acc_descr_multiline>[\}] { this.popState(); }
|
||||||
|
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
|
||||||
|
"section"\s[^#:\n;]+ return 'section';
|
||||||
|
|
||||||
|
// event starting with "==>" keyword
|
||||||
|
":"\s[^#:\n;]+ return 'event';
|
||||||
|
[^#:\n;]+ return 'period';
|
||||||
|
|
||||||
|
|
||||||
|
<<EOF>> return 'EOF';
|
||||||
|
. return 'INVALID';
|
||||||
|
|
||||||
|
/lex
|
||||||
|
|
||||||
|
%left '^'
|
||||||
|
|
||||||
|
%start start
|
||||||
|
|
||||||
|
%% /* language grammar */
|
||||||
|
|
||||||
|
start
|
||||||
|
: timeline document 'EOF' { return $2; }
|
||||||
|
| directive start
|
||||||
|
;
|
||||||
|
|
||||||
|
document
|
||||||
|
: /* empty */ { $$ = [] }
|
||||||
|
| document line {$1.push($2);$$ = $1}
|
||||||
|
;
|
||||||
|
|
||||||
|
line
|
||||||
|
: SPACE statement { $$ = $2 }
|
||||||
|
| statement { $$ = $1 }
|
||||||
|
| NEWLINE { $$=[];}
|
||||||
|
| EOF { $$=[];}
|
||||||
|
;
|
||||||
|
|
||||||
|
directive
|
||||||
|
: openDirective typeDirective closeDirective 'NEWLINE'
|
||||||
|
| openDirective typeDirective ':' argDirective closeDirective 'NEWLINE'
|
||||||
|
;
|
||||||
|
|
||||||
|
statement
|
||||||
|
: title {yy.getCommonDb().setDiagramTitle($1.substr(6));$$=$1.substr(6);}
|
||||||
|
| acc_title acc_title_value { $$=$2.trim();yy.getCommonDb().setAccTitle($$); }
|
||||||
|
| acc_descr acc_descr_value { $$=$2.trim();yy.getCommonDb().setAccDescription($$); }
|
||||||
|
| acc_descr_multiline_value { $$=$1.trim();yy.getCommonDb().setAccDescription($$); }
|
||||||
|
| section {yy.addSection($1.substr(8));$$=$1.substr(8);}
|
||||||
|
| period {yy.addTask($1,0,'');$$=$1;}
|
||||||
|
| period event {yy.addTask($1,0,$2.substr(2));$$=$1;}
|
||||||
|
| event {yy.addEvent($1.substr(2));$$=$1;}
|
||||||
|
| directive
|
||||||
|
;
|
||||||
|
|
||||||
|
openDirective
|
||||||
|
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
|
||||||
|
;
|
||||||
|
|
||||||
|
typeDirective
|
||||||
|
: type_directive { yy.parseDirective($1, 'type_directive'); }
|
||||||
|
;
|
||||||
|
|
||||||
|
argDirective
|
||||||
|
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
|
||||||
|
;
|
||||||
|
|
||||||
|
closeDirective
|
||||||
|
: close_directive { yy.parseDirective('}%%', 'close_directive', 'timeline'); }
|
||||||
|
;
|
||||||
|
|
||||||
|
%%
|
150
packages/mermaid-timeline/src/parser/timeline.spec.js
Normal file
150
packages/mermaid-timeline/src/parser/timeline.spec.js
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import { parser } from './journey';
|
||||||
|
import journeyDb from '../journeyDb';
|
||||||
|
|
||||||
|
const parserFnConstructor = (str) => {
|
||||||
|
return () => {
|
||||||
|
parser.parse(str);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('when parsing a journey diagram it', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
parser.yy = journeyDb;
|
||||||
|
parser.yy.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle a title definition', function () {
|
||||||
|
const str = 'journey\ntitle Adding journey diagram functionality to mermaid';
|
||||||
|
|
||||||
|
expect(parserFnConstructor(str)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle an accessibility description (accDescr)', function () {
|
||||||
|
const str =
|
||||||
|
'journey\n' +
|
||||||
|
'accDescr: A user journey for family shopping\n' +
|
||||||
|
'title Adding journey diagram functionality to mermaid\n' +
|
||||||
|
'section Order from website';
|
||||||
|
|
||||||
|
expect(parserFnConstructor(str)).not.toThrow();
|
||||||
|
});
|
||||||
|
it('should handle an accessibility multiline description (accDescr)', function () {
|
||||||
|
const str =
|
||||||
|
'journey\n' +
|
||||||
|
`accDescr {
|
||||||
|
A user journey for
|
||||||
|
family shopping
|
||||||
|
}` +
|
||||||
|
'title Adding journey diagram functionality to mermaid\n' +
|
||||||
|
'accTitle: Adding acc journey diagram functionality to mermaid\n' +
|
||||||
|
'section Order from website';
|
||||||
|
|
||||||
|
expect(parserFnConstructor(str)).not.toThrow();
|
||||||
|
expect(journeyDb.getAccDescription()).toBe('A user journey for\nfamily shopping');
|
||||||
|
expect(journeyDb.getDiagramTitle()).toBe('Adding journey diagram functionality to mermaid');
|
||||||
|
expect(journeyDb.getAccTitle()).toBe('Adding acc journey diagram functionality to mermaid');
|
||||||
|
});
|
||||||
|
it('should handle an accessibility title (accDescr)', function () {
|
||||||
|
const str = `journey
|
||||||
|
accTitle: The title
|
||||||
|
section Order from website`;
|
||||||
|
|
||||||
|
expect(parserFnConstructor(str)).not.toThrow();
|
||||||
|
expect(journeyDb.getAccDescription()).toBe('');
|
||||||
|
expect(journeyDb.getAccTitle()).toBe('The title');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle a section definition', function () {
|
||||||
|
const str =
|
||||||
|
'journey\n' +
|
||||||
|
'title Adding journey diagram functionality to mermaid\n' +
|
||||||
|
'section Order from website';
|
||||||
|
|
||||||
|
expect(parserFnConstructor(str)).not.toThrow();
|
||||||
|
});
|
||||||
|
it('should handle multiline section titles with different line breaks', function () {
|
||||||
|
const str =
|
||||||
|
'journey\n' +
|
||||||
|
'title Adding gantt diagram functionality to mermaid\n' +
|
||||||
|
'section Line1<br>Line2<br/>Line3</br />Line4<br\t/>Line5';
|
||||||
|
|
||||||
|
expect(parserFnConstructor(str)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle a task definition', function () {
|
||||||
|
const str =
|
||||||
|
'journey\n' +
|
||||||
|
'title Adding journey diagram functionality to mermaid\n' +
|
||||||
|
'section Documentation\n' +
|
||||||
|
'A task: 5: Alice, Bob, Charlie\n' +
|
||||||
|
'B task: 3:Bob, Charlie\n' +
|
||||||
|
'C task: 5\n' +
|
||||||
|
'D task: 5: Charlie, Alice\n' +
|
||||||
|
'E task: 5:\n' +
|
||||||
|
'section Another section\n' +
|
||||||
|
'P task: 5:\n' +
|
||||||
|
'Q task: 5:\n' +
|
||||||
|
'R task: 5:';
|
||||||
|
expect(parserFnConstructor(str)).not.toThrow();
|
||||||
|
|
||||||
|
const tasks = parser.yy.getTasks();
|
||||||
|
expect(tasks.length).toEqual(8);
|
||||||
|
|
||||||
|
expect(tasks[0]).toEqual({
|
||||||
|
score: 5,
|
||||||
|
people: ['Alice', 'Bob', 'Charlie'],
|
||||||
|
section: 'Documentation',
|
||||||
|
task: 'A task',
|
||||||
|
type: 'Documentation',
|
||||||
|
});
|
||||||
|
expect(tasks[1]).toEqual({
|
||||||
|
score: 3,
|
||||||
|
people: ['Bob', 'Charlie'],
|
||||||
|
section: 'Documentation',
|
||||||
|
type: 'Documentation',
|
||||||
|
task: 'B task',
|
||||||
|
});
|
||||||
|
expect(tasks[2]).toEqual({
|
||||||
|
score: 5,
|
||||||
|
people: [],
|
||||||
|
section: 'Documentation',
|
||||||
|
type: 'Documentation',
|
||||||
|
task: 'C task',
|
||||||
|
});
|
||||||
|
expect(tasks[3]).toEqual({
|
||||||
|
score: 5,
|
||||||
|
people: ['Charlie', 'Alice'],
|
||||||
|
section: 'Documentation',
|
||||||
|
task: 'D task',
|
||||||
|
type: 'Documentation',
|
||||||
|
});
|
||||||
|
expect(tasks[4]).toEqual({
|
||||||
|
score: 5,
|
||||||
|
people: [''],
|
||||||
|
section: 'Documentation',
|
||||||
|
type: 'Documentation',
|
||||||
|
task: 'E task',
|
||||||
|
});
|
||||||
|
expect(tasks[5]).toEqual({
|
||||||
|
score: 5,
|
||||||
|
people: [''],
|
||||||
|
section: 'Another section',
|
||||||
|
type: 'Another section',
|
||||||
|
task: 'P task',
|
||||||
|
});
|
||||||
|
expect(tasks[6]).toEqual({
|
||||||
|
score: 5,
|
||||||
|
people: [''],
|
||||||
|
section: 'Another section',
|
||||||
|
type: 'Another section',
|
||||||
|
task: 'Q task',
|
||||||
|
});
|
||||||
|
expect(tasks[7]).toEqual({
|
||||||
|
score: 5,
|
||||||
|
people: [''],
|
||||||
|
section: 'Another section',
|
||||||
|
type: 'Another section',
|
||||||
|
task: 'R task',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
78
packages/mermaid-timeline/src/styles.js
Normal file
78
packages/mermaid-timeline/src/styles.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { darken, lighten, isDark } from 'khroma';
|
||||||
|
|
||||||
|
const genSections = (options) => {
|
||||||
|
let sections = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) {
|
||||||
|
options['lineColor' + i] = options['lineColor' + i] || options['cScaleInv' + i];
|
||||||
|
if (isDark(options['lineColor' + i])) {
|
||||||
|
options['lineColor' + i] = lighten(options['lineColor' + i], 20);
|
||||||
|
} else {
|
||||||
|
options['lineColor' + i] = darken(options['lineColor' + i], 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) {
|
||||||
|
const sw = '' + (17 - 3 * i);
|
||||||
|
sections += `
|
||||||
|
.section-${i - 1} rect, .section-${i - 1} path, .section-${i - 1} circle, .section-${
|
||||||
|
i - 1
|
||||||
|
} path {
|
||||||
|
fill: ${options['cScale' + i]};
|
||||||
|
}
|
||||||
|
.section-${i - 1} text {
|
||||||
|
fill: ${options['cScaleLabel' + i]};
|
||||||
|
}
|
||||||
|
.node-icon-${i - 1} {
|
||||||
|
font-size: 40px;
|
||||||
|
color: ${options['cScaleLabel' + i]};
|
||||||
|
}
|
||||||
|
.section-edge-${i - 1}{
|
||||||
|
stroke: ${options['cScale' + i]};
|
||||||
|
}
|
||||||
|
.edge-depth-${i - 1}{
|
||||||
|
stroke-width: ${sw};
|
||||||
|
}
|
||||||
|
.section-${i - 1} line {
|
||||||
|
stroke: ${options['cScaleInv' + i]} ;
|
||||||
|
stroke-width: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled, .disabled circle, .disabled text {
|
||||||
|
fill: lightgray;
|
||||||
|
}
|
||||||
|
.disabled text {
|
||||||
|
fill: #efefef;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return sections;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = (options) =>
|
||||||
|
`
|
||||||
|
.edge {
|
||||||
|
stroke-width: 3;
|
||||||
|
}
|
||||||
|
${genSections(options)}
|
||||||
|
.section-root rect, .section-root path, .section-root circle {
|
||||||
|
fill: ${options.git0};
|
||||||
|
}
|
||||||
|
.section-root text {
|
||||||
|
fill: ${options.gitBranchLabel0};
|
||||||
|
}
|
||||||
|
.icon-container {
|
||||||
|
height:100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.edge {
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
.eventWrapper {
|
||||||
|
filter: brightness(120%);
|
||||||
|
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export default getStyles;
|
604
packages/mermaid-timeline/src/svgDraw.js
Normal file
604
packages/mermaid-timeline/src/svgDraw.js
Normal file
@@ -0,0 +1,604 @@
|
|||||||
|
import { arc as d3arc , select} from 'd3';
|
||||||
|
const MAX_SECTIONS = 12;
|
||||||
|
|
||||||
|
export const drawRect = function (elem, rectData) {
|
||||||
|
const rectElem = elem.append('rect');
|
||||||
|
rectElem.attr('x', rectData.x);
|
||||||
|
rectElem.attr('y', rectData.y);
|
||||||
|
rectElem.attr('fill', rectData.fill);
|
||||||
|
rectElem.attr('stroke', rectData.stroke);
|
||||||
|
rectElem.attr('width', rectData.width);
|
||||||
|
rectElem.attr('height', rectData.height);
|
||||||
|
rectElem.attr('rx', rectData.rx);
|
||||||
|
rectElem.attr('ry', rectData.ry);
|
||||||
|
|
||||||
|
if (typeof rectData.class !== 'undefined') {
|
||||||
|
rectElem.attr('class', rectData.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rectElem;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawFace = function (element, faceData) {
|
||||||
|
const radius = 15;
|
||||||
|
const circleElement = element
|
||||||
|
.append('circle')
|
||||||
|
.attr('cx', faceData.cx)
|
||||||
|
.attr('cy', faceData.cy)
|
||||||
|
.attr('class', 'face')
|
||||||
|
.attr('r', radius)
|
||||||
|
.attr('stroke-width', 2)
|
||||||
|
.attr('overflow', 'visible');
|
||||||
|
|
||||||
|
const face = element.append('g');
|
||||||
|
|
||||||
|
//left eye
|
||||||
|
face
|
||||||
|
.append('circle')
|
||||||
|
.attr('cx', faceData.cx - radius / 3)
|
||||||
|
.attr('cy', faceData.cy - radius / 3)
|
||||||
|
.attr('r', 1.5)
|
||||||
|
.attr('stroke-width', 2)
|
||||||
|
.attr('fill', '#666')
|
||||||
|
.attr('stroke', '#666');
|
||||||
|
|
||||||
|
//right eye
|
||||||
|
face
|
||||||
|
.append('circle')
|
||||||
|
.attr('cx', faceData.cx + radius / 3)
|
||||||
|
.attr('cy', faceData.cy - radius / 3)
|
||||||
|
.attr('r', 1.5)
|
||||||
|
.attr('stroke-width', 2)
|
||||||
|
.attr('fill', '#666')
|
||||||
|
.attr('stroke', '#666');
|
||||||
|
|
||||||
|
/** @param {any} face */
|
||||||
|
function smile(face) {
|
||||||
|
const arc = d3arc()
|
||||||
|
.startAngle(Math.PI / 2)
|
||||||
|
.endAngle(3 * (Math.PI / 2))
|
||||||
|
.innerRadius(radius / 2)
|
||||||
|
.outerRadius(radius / 2.2);
|
||||||
|
//mouth
|
||||||
|
face
|
||||||
|
.append('path')
|
||||||
|
.attr('class', 'mouth')
|
||||||
|
.attr('d', arc)
|
||||||
|
.attr('transform', 'translate(' + faceData.cx + ',' + (faceData.cy + 2) + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {any} face */
|
||||||
|
function sad(face) {
|
||||||
|
const arc = d3arc()
|
||||||
|
.startAngle((3 * Math.PI) / 2)
|
||||||
|
.endAngle(5 * (Math.PI / 2))
|
||||||
|
.innerRadius(radius / 2)
|
||||||
|
.outerRadius(radius / 2.2);
|
||||||
|
//mouth
|
||||||
|
face
|
||||||
|
.append('path')
|
||||||
|
.attr('class', 'mouth')
|
||||||
|
.attr('d', arc)
|
||||||
|
.attr('transform', 'translate(' + faceData.cx + ',' + (faceData.cy + 7) + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {any} face */
|
||||||
|
function ambivalent(face) {
|
||||||
|
face
|
||||||
|
.append('line')
|
||||||
|
.attr('class', 'mouth')
|
||||||
|
.attr('stroke', 2)
|
||||||
|
.attr('x1', faceData.cx - 5)
|
||||||
|
.attr('y1', faceData.cy + 7)
|
||||||
|
.attr('x2', faceData.cx + 5)
|
||||||
|
.attr('y2', faceData.cy + 7)
|
||||||
|
.attr('class', 'mouth')
|
||||||
|
.attr('stroke-width', '1px')
|
||||||
|
.attr('stroke', '#666');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (faceData.score > 3) {
|
||||||
|
smile(face);
|
||||||
|
} else if (faceData.score < 3) {
|
||||||
|
sad(face);
|
||||||
|
} else {
|
||||||
|
ambivalent(face);
|
||||||
|
}
|
||||||
|
|
||||||
|
return circleElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawCircle = function (element, circleData) {
|
||||||
|
const circleElement = element.append('circle');
|
||||||
|
circleElement.attr('cx', circleData.cx);
|
||||||
|
circleElement.attr('cy', circleData.cy);
|
||||||
|
circleElement.attr('class', 'actor-' + circleData.pos);
|
||||||
|
circleElement.attr('fill', circleData.fill);
|
||||||
|
circleElement.attr('stroke', circleData.stroke);
|
||||||
|
circleElement.attr('r', circleData.r);
|
||||||
|
|
||||||
|
if (typeof circleElement.class !== 'undefined') {
|
||||||
|
circleElement.attr('class', circleElement.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof circleData.title !== 'undefined') {
|
||||||
|
circleElement.append('title').text(circleData.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
return circleElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawText = function (elem, textData) {
|
||||||
|
// Remove and ignore br:s
|
||||||
|
const nText = textData.text.replace(/<br\s*\/?>/gi, ' ');
|
||||||
|
|
||||||
|
const textElem = elem.append('text');
|
||||||
|
textElem.attr('x', textData.x);
|
||||||
|
textElem.attr('y', textData.y);
|
||||||
|
textElem.attr('class', 'legend');
|
||||||
|
|
||||||
|
textElem.style('text-anchor', textData.anchor);
|
||||||
|
|
||||||
|
if (typeof textData.class !== 'undefined') {
|
||||||
|
textElem.attr('class', textData.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
const span = textElem.append('tspan');
|
||||||
|
span.attr('x', textData.x + textData.textMargin * 2);
|
||||||
|
span.text(nText);
|
||||||
|
|
||||||
|
return textElem;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawLabel = function (elem, txtObject) {
|
||||||
|
/**
|
||||||
|
* @param {any} x
|
||||||
|
* @param {any} y
|
||||||
|
* @param {any} width
|
||||||
|
* @param {any} height
|
||||||
|
* @param {any} cut
|
||||||
|
*/
|
||||||
|
function genPoints(x, y, width, height, cut) {
|
||||||
|
return (
|
||||||
|
x +
|
||||||
|
',' +
|
||||||
|
y +
|
||||||
|
' ' +
|
||||||
|
(x + width) +
|
||||||
|
',' +
|
||||||
|
y +
|
||||||
|
' ' +
|
||||||
|
(x + width) +
|
||||||
|
',' +
|
||||||
|
(y + height - cut) +
|
||||||
|
' ' +
|
||||||
|
(x + width - cut * 1.2) +
|
||||||
|
',' +
|
||||||
|
(y + height) +
|
||||||
|
' ' +
|
||||||
|
x +
|
||||||
|
',' +
|
||||||
|
(y + height)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const polygon = elem.append('polygon');
|
||||||
|
polygon.attr('points', genPoints(txtObject.x, txtObject.y, 50, 20, 7));
|
||||||
|
polygon.attr('class', 'labelBox');
|
||||||
|
|
||||||
|
txtObject.y = txtObject.y + txtObject.labelMargin;
|
||||||
|
txtObject.x = txtObject.x + 0.5 * txtObject.labelMargin;
|
||||||
|
drawText(elem, txtObject);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawSection = function (elem, section, conf) {
|
||||||
|
const g = elem.append('g');
|
||||||
|
|
||||||
|
const rect = getNoteRect();
|
||||||
|
rect.x = section.x;
|
||||||
|
rect.y = section.y;
|
||||||
|
rect.fill = section.fill;
|
||||||
|
rect.width = conf.width;
|
||||||
|
rect.height = conf.height;
|
||||||
|
rect.class = 'journey-section section-type-' + section.num;
|
||||||
|
rect.rx = 3;
|
||||||
|
rect.ry = 3;
|
||||||
|
drawRect(g, rect);
|
||||||
|
|
||||||
|
_drawTextCandidateFunc(conf)(
|
||||||
|
section.text,
|
||||||
|
g,
|
||||||
|
rect.x,
|
||||||
|
rect.y,
|
||||||
|
rect.width,
|
||||||
|
rect.height,
|
||||||
|
{ class: 'journey-section section-type-' + section.num },
|
||||||
|
conf,
|
||||||
|
section.colour
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let taskCount = -1;
|
||||||
|
/**
|
||||||
|
* Draws an actor in the diagram with the attached line
|
||||||
|
*
|
||||||
|
* @param {any} elem The HTML element
|
||||||
|
* @param {any} task The task to render
|
||||||
|
* @param {any} conf The global configuration
|
||||||
|
*/
|
||||||
|
export const drawTask = function (elem, task, conf) {
|
||||||
|
const center = task.x + conf.width / 2;
|
||||||
|
const g = elem.append('g');
|
||||||
|
taskCount++;
|
||||||
|
const maxHeight = 300 + 5 * 30;
|
||||||
|
g.append('line')
|
||||||
|
.attr('id', 'task' + taskCount)
|
||||||
|
.attr('x1', center)
|
||||||
|
.attr('y1', task.y)
|
||||||
|
.attr('x2', center)
|
||||||
|
.attr('y2', maxHeight)
|
||||||
|
.attr('class', 'task-line')
|
||||||
|
.attr('stroke-width', '1px')
|
||||||
|
.attr('stroke-dasharray', '4 2')
|
||||||
|
.attr('stroke', '#666');
|
||||||
|
|
||||||
|
drawFace(g, {
|
||||||
|
cx: center,
|
||||||
|
cy: 300 + (5 - task.score) * 30,
|
||||||
|
score: task.score,
|
||||||
|
});
|
||||||
|
|
||||||
|
const rect = getNoteRect();
|
||||||
|
rect.x = task.x;
|
||||||
|
rect.y = task.y;
|
||||||
|
rect.fill = task.fill;
|
||||||
|
rect.width = conf.width;
|
||||||
|
rect.height = conf.height;
|
||||||
|
rect.class = 'task task-type-' + task.num;
|
||||||
|
rect.rx = 3;
|
||||||
|
rect.ry = 3;
|
||||||
|
drawRect(g, rect);
|
||||||
|
|
||||||
|
let xPos = task.x + 14;
|
||||||
|
// task.people.forEach((person) => {
|
||||||
|
// const colour = task.actors[person].color;
|
||||||
|
|
||||||
|
// const circle = {
|
||||||
|
// cx: xPos,
|
||||||
|
// cy: task.y,
|
||||||
|
// r: 7,
|
||||||
|
// fill: colour,
|
||||||
|
// stroke: '#000',
|
||||||
|
// title: person,
|
||||||
|
// pos: task.actors[person].position,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// drawCircle(g, circle);
|
||||||
|
// xPos += 10;
|
||||||
|
// });
|
||||||
|
|
||||||
|
_drawTextCandidateFunc(conf)(
|
||||||
|
task.task,
|
||||||
|
g,
|
||||||
|
rect.x,
|
||||||
|
rect.y,
|
||||||
|
rect.width,
|
||||||
|
rect.height,
|
||||||
|
{ class: 'task' },
|
||||||
|
conf,
|
||||||
|
task.colour
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws a background rectangle
|
||||||
|
*
|
||||||
|
* @param {any} elem The html element
|
||||||
|
* @param {any} bounds The bounds of the drawing
|
||||||
|
*/
|
||||||
|
export const drawBackgroundRect = function (elem, bounds) {
|
||||||
|
const rectElem = drawRect(elem, {
|
||||||
|
x: bounds.startx,
|
||||||
|
y: bounds.starty,
|
||||||
|
width: bounds.stopx - bounds.startx,
|
||||||
|
height: bounds.stopy - bounds.starty,
|
||||||
|
fill: bounds.fill,
|
||||||
|
class: 'rect',
|
||||||
|
});
|
||||||
|
rectElem.lower();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTextObj = function () {
|
||||||
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
fill: undefined,
|
||||||
|
'text-anchor': 'start',
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
textMargin: 0,
|
||||||
|
rx: 0,
|
||||||
|
ry: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getNoteRect = function () {
|
||||||
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 100,
|
||||||
|
anchor: 'start',
|
||||||
|
height: 100,
|
||||||
|
rx: 0,
|
||||||
|
ry: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const _drawTextCandidateFunc = (function () {
|
||||||
|
/**
|
||||||
|
* @param {any} content
|
||||||
|
* @param {any} g
|
||||||
|
* @param {any} x
|
||||||
|
* @param {any} y
|
||||||
|
* @param {any} width
|
||||||
|
* @param {any} height
|
||||||
|
* @param {any} textAttrs
|
||||||
|
* @param {any} colour
|
||||||
|
*/
|
||||||
|
function byText(content, g, x, y, width, height, textAttrs, colour) {
|
||||||
|
const text = g
|
||||||
|
.append('text')
|
||||||
|
.attr('x', x + width / 2)
|
||||||
|
.attr('y', y + height / 2 + 5)
|
||||||
|
.style('font-color', colour)
|
||||||
|
.style('text-anchor', 'middle')
|
||||||
|
.text(content);
|
||||||
|
_setTextAttrs(text, textAttrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} content
|
||||||
|
* @param {any} g
|
||||||
|
* @param {any} x
|
||||||
|
* @param {any} y
|
||||||
|
* @param {any} width
|
||||||
|
* @param {any} height
|
||||||
|
* @param {any} textAttrs
|
||||||
|
* @param {any} conf
|
||||||
|
* @param {any} colour
|
||||||
|
*/
|
||||||
|
function byTspan(content, g, x, y, width, height, textAttrs, conf, colour) {
|
||||||
|
const { taskFontSize, taskFontFamily } = conf;
|
||||||
|
|
||||||
|
const lines = content.split(/<br\s*\/?>/gi);
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const dy = i * taskFontSize - (taskFontSize * (lines.length - 1)) / 2;
|
||||||
|
const text = g
|
||||||
|
.append('text')
|
||||||
|
.attr('x', x + width / 2)
|
||||||
|
.attr('y', y)
|
||||||
|
.attr('fill', colour)
|
||||||
|
.style('text-anchor', 'middle')
|
||||||
|
.style('font-size', taskFontSize)
|
||||||
|
.style('font-family', taskFontFamily);
|
||||||
|
text
|
||||||
|
.append('tspan')
|
||||||
|
.attr('x', x + width / 2)
|
||||||
|
.attr('dy', dy)
|
||||||
|
.text(lines[i]);
|
||||||
|
|
||||||
|
text
|
||||||
|
.attr('y', y + height / 2.0)
|
||||||
|
.attr('dominant-baseline', 'central')
|
||||||
|
.attr('alignment-baseline', 'central');
|
||||||
|
|
||||||
|
_setTextAttrs(text, textAttrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} content
|
||||||
|
* @param {any} g
|
||||||
|
* @param {any} x
|
||||||
|
* @param {any} y
|
||||||
|
* @param {any} width
|
||||||
|
* @param {any} height
|
||||||
|
* @param {any} textAttrs
|
||||||
|
* @param {any} conf
|
||||||
|
*/
|
||||||
|
function byFo(content, g, x, y, width, height, textAttrs, conf) {
|
||||||
|
const body = g.append('switch');
|
||||||
|
const f = body
|
||||||
|
.append('foreignObject')
|
||||||
|
.attr('x', x)
|
||||||
|
.attr('y', y)
|
||||||
|
.attr('width', width)
|
||||||
|
.attr('height', height)
|
||||||
|
.attr('position', 'fixed');
|
||||||
|
|
||||||
|
const text = f
|
||||||
|
.append('xhtml:div')
|
||||||
|
.style('display', 'table')
|
||||||
|
.style('height', '100%')
|
||||||
|
.style('width', '100%');
|
||||||
|
|
||||||
|
text
|
||||||
|
.append('div')
|
||||||
|
.attr('class', 'label')
|
||||||
|
.style('display', 'table-cell')
|
||||||
|
.style('text-align', 'center')
|
||||||
|
.style('vertical-align', 'middle')
|
||||||
|
.text(content);
|
||||||
|
|
||||||
|
byTspan(content, body, x, y, width, height, textAttrs, conf);
|
||||||
|
_setTextAttrs(text, textAttrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} toText
|
||||||
|
* @param {any} fromTextAttrsDict
|
||||||
|
*/
|
||||||
|
function _setTextAttrs(toText, fromTextAttrsDict) {
|
||||||
|
for (const key in fromTextAttrsDict) {
|
||||||
|
if (key in fromTextAttrsDict) {
|
||||||
|
// noinspection JSUnfilteredForInLoop
|
||||||
|
toText.attr(key, fromTextAttrsDict[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return function (conf) {
|
||||||
|
return conf.textPlacement === 'fo' ? byFo : conf.textPlacement === 'old' ? byText : byTspan;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
const initGraphics = function (graphics) {
|
||||||
|
graphics
|
||||||
|
.append('defs')
|
||||||
|
.append('marker')
|
||||||
|
.attr('id', 'arrowhead')
|
||||||
|
.attr('refX', 5)
|
||||||
|
.attr('refY', 2)
|
||||||
|
.attr('markerWidth', 6)
|
||||||
|
.attr('markerHeight', 4)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M 0,0 V 4 L6,2 Z'); // this is actual shape for arrowhead
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text The text to be wrapped
|
||||||
|
* @param {number} width The max width of the text
|
||||||
|
*/
|
||||||
|
function wrap(text, width) {
|
||||||
|
text.each(function () {
|
||||||
|
var text = select(this),
|
||||||
|
words = text
|
||||||
|
.text()
|
||||||
|
.split(/(\s+|<br>)/)
|
||||||
|
.reverse(),
|
||||||
|
word,
|
||||||
|
line = [],
|
||||||
|
lineHeight = 1.1, // ems
|
||||||
|
y = text.attr('y'),
|
||||||
|
dy = parseFloat(text.attr('dy')),
|
||||||
|
tspan = text
|
||||||
|
.text(null)
|
||||||
|
.append('tspan')
|
||||||
|
.attr('x', 0)
|
||||||
|
.attr('y', y)
|
||||||
|
.attr('dy', dy + 'em');
|
||||||
|
for (let j = 0; j < words.length; j++) {
|
||||||
|
word = words[words.length - 1 - j];
|
||||||
|
line.push(word);
|
||||||
|
tspan.text(line.join(' ').trim());
|
||||||
|
if (tspan.node().getComputedTextLength() > width || word === '<br>') {
|
||||||
|
line.pop();
|
||||||
|
tspan.text(line.join(' ').trim());
|
||||||
|
if (word === '<br>') {
|
||||||
|
line = [''];
|
||||||
|
} else {
|
||||||
|
line = [word];
|
||||||
|
}
|
||||||
|
|
||||||
|
tspan = text
|
||||||
|
.append('tspan')
|
||||||
|
.attr('x', 0)
|
||||||
|
.attr('y', y)
|
||||||
|
.attr('dy', lineHeight + 'em')
|
||||||
|
.text(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const drawNode = function (elem, node, fullSection, conf) {
|
||||||
|
const section = (fullSection % MAX_SECTIONS) - 1;
|
||||||
|
const nodeElem = elem.append('g');
|
||||||
|
node.section = section;
|
||||||
|
nodeElem.attr(
|
||||||
|
'class',
|
||||||
|
(node.class ? node.class + ' ' : '') +
|
||||||
|
'timeline-node ' +
|
||||||
|
(section < 0 ? 'section-root' : 'section-' + section)
|
||||||
|
);
|
||||||
|
const bkgElem = nodeElem.append('g');
|
||||||
|
|
||||||
|
// Create the wrapped text element
|
||||||
|
const textElem = nodeElem.append('g');
|
||||||
|
|
||||||
|
const txt = textElem
|
||||||
|
.append('text')
|
||||||
|
.text(node.descr)
|
||||||
|
.attr('dy', '1em')
|
||||||
|
.attr('alignment-baseline', 'middle')
|
||||||
|
.attr('dominant-baseline', 'middle')
|
||||||
|
.attr('text-anchor', 'middle')
|
||||||
|
.call(wrap, node.width);
|
||||||
|
const bbox = txt.node().getBBox();
|
||||||
|
const fontSize = conf.fontSize && conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
|
||||||
|
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
|
||||||
|
node.height = Math.max(node.height, node.maxHeight);
|
||||||
|
node.width = node.width + 2 * node.padding;
|
||||||
|
|
||||||
|
textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')');
|
||||||
|
|
||||||
|
// Create the background element
|
||||||
|
defaultBkg(bkgElem, node, section, conf);
|
||||||
|
|
||||||
|
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getVirtualNodeHeight = function (elem,node,conf) {
|
||||||
|
const textElem = elem.append('g');
|
||||||
|
const txt = textElem
|
||||||
|
.append('text')
|
||||||
|
.text(node.descr)
|
||||||
|
.attr('dy', '1em')
|
||||||
|
.attr('alignment-baseline', 'middle')
|
||||||
|
.attr('dominant-baseline', 'middle')
|
||||||
|
.attr('text-anchor', 'middle')
|
||||||
|
.call(wrap, node.width);
|
||||||
|
const bbox = txt.node().getBBox();
|
||||||
|
const fontSize = conf.fontSize && conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
|
||||||
|
textElem.remove();
|
||||||
|
return bbox.height + fontSize * 1.1 * 0.5 + node.padding;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const defaultBkg = function (elem, node, section) {
|
||||||
|
const rd = 5;
|
||||||
|
elem
|
||||||
|
.append('path')
|
||||||
|
.attr('id', 'node-' + node.id)
|
||||||
|
.attr('class', 'node-bkg node-' + node.type)
|
||||||
|
.attr(
|
||||||
|
'd',
|
||||||
|
`M0 ${node.height - rd} v${-node.height + 2 * rd} q0,-5 5,-5 h${
|
||||||
|
node.width - 2 * rd
|
||||||
|
} q5,0 5,5 v${node.height - rd} H0 Z`
|
||||||
|
);
|
||||||
|
|
||||||
|
elem
|
||||||
|
.append('line')
|
||||||
|
.attr('class', 'node-line-' + section)
|
||||||
|
.attr('x1', 0)
|
||||||
|
.attr('y1', node.height)
|
||||||
|
.attr('x2', node.width)
|
||||||
|
.attr('y2', node.height);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
drawRect,
|
||||||
|
drawCircle,
|
||||||
|
drawSection,
|
||||||
|
drawText,
|
||||||
|
drawLabel,
|
||||||
|
drawTask,
|
||||||
|
drawBackgroundRect,
|
||||||
|
getTextObj,
|
||||||
|
getNoteRect,
|
||||||
|
initGraphics,
|
||||||
|
drawNode,
|
||||||
|
getVirtualNodeHeight,
|
||||||
|
};
|
100
packages/mermaid-timeline/src/timelineDb.js
Normal file
100
packages/mermaid-timeline/src/timelineDb.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { getCommonDb as _getCommonDb,log } from './mermaidUtils';
|
||||||
|
|
||||||
|
let currentSection = '';
|
||||||
|
let currentTaskId = 0;
|
||||||
|
|
||||||
|
const sections = [];
|
||||||
|
const tasks = [];
|
||||||
|
const rawTasks = [];
|
||||||
|
|
||||||
|
export const getCommonDb = _getCommonDb;
|
||||||
|
|
||||||
|
export const clear = function () {
|
||||||
|
sections.length = 0;
|
||||||
|
tasks.length = 0;
|
||||||
|
currentSection = '';
|
||||||
|
rawTasks.length = 0;
|
||||||
|
_getCommonDb().clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addSection = function (txt) {
|
||||||
|
currentSection = txt;
|
||||||
|
sections.push(txt);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSections = function () {
|
||||||
|
return sections;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTasks = function () {
|
||||||
|
let allItemsProcessed = compileTasks();
|
||||||
|
const maxDepth = 100;
|
||||||
|
let iterationCount = 0;
|
||||||
|
while (!allItemsProcessed && iterationCount < maxDepth) {
|
||||||
|
allItemsProcessed = compileTasks();
|
||||||
|
iterationCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.push(...rawTasks);
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addTask = function (period, length, event) {
|
||||||
|
|
||||||
|
const rawTask = {
|
||||||
|
id: currentTaskId++,
|
||||||
|
section: currentSection,
|
||||||
|
type: currentSection,
|
||||||
|
task: period,
|
||||||
|
score : length?length:0,
|
||||||
|
//if event is defined, then add it the events array
|
||||||
|
events: event?[event]:[],
|
||||||
|
};
|
||||||
|
rawTasks.push(rawTask);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addEvent = function (event) {
|
||||||
|
// fetch current task with currnetTaskId
|
||||||
|
const currentTask = rawTasks.find((task) => task.id === currentTaskId - 1);
|
||||||
|
//add event to the events array
|
||||||
|
currentTask.events.push(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const addTaskOrg = function (descr) {
|
||||||
|
const newTask = {
|
||||||
|
section: currentSection,
|
||||||
|
type: currentSection,
|
||||||
|
description: descr,
|
||||||
|
task: descr,
|
||||||
|
classes: [],
|
||||||
|
};
|
||||||
|
tasks.push(newTask);
|
||||||
|
};
|
||||||
|
|
||||||
|
const compileTasks = function () {
|
||||||
|
const compileTask = function (pos) {
|
||||||
|
return rawTasks[pos].processed;
|
||||||
|
};
|
||||||
|
|
||||||
|
let allProcessed = true;
|
||||||
|
for (let i = 0; i < rawTasks.length; i++) {
|
||||||
|
compileTask(i);
|
||||||
|
|
||||||
|
allProcessed = allProcessed && rawTasks[i].processed;
|
||||||
|
}
|
||||||
|
return allProcessed;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
clear,
|
||||||
|
getCommonDb,
|
||||||
|
addSection,
|
||||||
|
getSections,
|
||||||
|
getTasks,
|
||||||
|
addTask,
|
||||||
|
addTaskOrg,
|
||||||
|
addEvent,
|
||||||
|
};
|
||||||
|
|
91
packages/mermaid-timeline/src/timelineDb.spec.js
Normal file
91
packages/mermaid-timeline/src/timelineDb.spec.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import journeyDb from './journeyDb';
|
||||||
|
|
||||||
|
describe('when using the journeyDb', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
journeyDb.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when calling the clear function', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
journeyDb.addSection('weekends skip test');
|
||||||
|
journeyDb.addTask('test1', '4: id1, id3');
|
||||||
|
journeyDb.addTask('test2', '2: id2');
|
||||||
|
journeyDb.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each`
|
||||||
|
fn | expected
|
||||||
|
${'getTasks'} | ${[]}
|
||||||
|
${'getAccTitle'} | ${''}
|
||||||
|
${'getSections'} | ${[]}
|
||||||
|
${'getActors'} | ${[]}
|
||||||
|
`('should clear $fn', ({ fn, expected }) => {
|
||||||
|
expect(journeyDb[fn]()).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when calling the clear function', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
journeyDb.addSection('weekends skip test');
|
||||||
|
journeyDb.addTask('test1', '3: id1, id3');
|
||||||
|
journeyDb.addTask('test2', '1: id2');
|
||||||
|
journeyDb.clear();
|
||||||
|
});
|
||||||
|
it.each`
|
||||||
|
fn | expected
|
||||||
|
${'getTasks'} | ${[]}
|
||||||
|
${'getAccTitle'} | ${''}
|
||||||
|
${'getAccDescription'} | ${''}
|
||||||
|
${'getSections'} | ${[]}
|
||||||
|
`('should clear $fn', ({ fn, expected }) => {
|
||||||
|
expect(journeyDb[fn]()).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('tasks and actors should be added', function () {
|
||||||
|
journeyDb.setAccTitle('Shopping');
|
||||||
|
journeyDb.setAccDescription('A user journey for family shopping');
|
||||||
|
journeyDb.addSection('Journey to the shops');
|
||||||
|
journeyDb.addTask('Get car keys', ':5:Dad');
|
||||||
|
journeyDb.addTask('Go to car', ':3:Dad, Mum, Child#1, Child#2');
|
||||||
|
journeyDb.addTask('Drive to supermarket', ':4:Dad');
|
||||||
|
journeyDb.addSection('Do shopping');
|
||||||
|
journeyDb.addTask('Go shopping', ':5:Mum');
|
||||||
|
|
||||||
|
expect(journeyDb.getAccTitle()).toEqual('Shopping');
|
||||||
|
expect(journeyDb.getAccDescription()).toEqual('A user journey for family shopping');
|
||||||
|
expect(journeyDb.getTasks()).toEqual([
|
||||||
|
{
|
||||||
|
score: 5,
|
||||||
|
people: ['Dad'],
|
||||||
|
section: 'Journey to the shops',
|
||||||
|
task: 'Get car keys',
|
||||||
|
type: 'Journey to the shops',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
score: 3,
|
||||||
|
people: ['Dad', 'Mum', 'Child#1', 'Child#2'],
|
||||||
|
section: 'Journey to the shops',
|
||||||
|
task: 'Go to car',
|
||||||
|
type: 'Journey to the shops',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
score: 4,
|
||||||
|
people: ['Dad'],
|
||||||
|
section: 'Journey to the shops',
|
||||||
|
task: 'Drive to supermarket',
|
||||||
|
type: 'Journey to the shops',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
score: 5,
|
||||||
|
people: ['Mum'],
|
||||||
|
section: 'Do shopping',
|
||||||
|
task: 'Go shopping',
|
||||||
|
type: 'Do shopping',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(journeyDb.getActors()).toEqual(['Child#1', 'Child#2', 'Dad', 'Mum']);
|
||||||
|
|
||||||
|
expect(journeyDb.getSections()).toEqual(['Journey to the shops', 'Do shopping']);
|
||||||
|
});
|
||||||
|
});
|
415
packages/mermaid-timeline/src/timelineRenderer.ts
Normal file
415
packages/mermaid-timeline/src/timelineRenderer.ts
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
// @ts-nocheck TODO: fix file
|
||||||
|
import { select } from 'd3';
|
||||||
|
import svgDraw from './svgDraw';
|
||||||
|
import { configureSvgSize } from '../../setupGraphViewbox';
|
||||||
|
import addSVGAccessibilityFields from '../../accessibility';
|
||||||
|
|
||||||
|
import { log, getConfig, setupGraphViewbox } from './mermaidUtils';
|
||||||
|
|
||||||
|
export const setConf = function (cnf) {
|
||||||
|
const keys = Object.keys(cnf);
|
||||||
|
|
||||||
|
keys.forEach(function (key) {
|
||||||
|
conf[key] = cnf[key];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const draw = function (text, id, version, diagObj) {
|
||||||
|
//1. Fetch the configuration
|
||||||
|
const conf = getConfig();
|
||||||
|
const LEFT_MARGIN = conf.leftMargin?conf.leftMargin:50;
|
||||||
|
|
||||||
|
//2. Clear the diagram db before parsing
|
||||||
|
diagObj.db.clear();
|
||||||
|
|
||||||
|
//3. Parse the diagram text
|
||||||
|
diagObj.parser.parse(text + '\n');
|
||||||
|
|
||||||
|
log.info('timeline', diagObj.db);
|
||||||
|
|
||||||
|
const securityLevel = conf.securityLevel;
|
||||||
|
// Handle root and Document for when rendering in sandbox mode
|
||||||
|
let sandboxElement;
|
||||||
|
if (securityLevel === 'sandbox') {
|
||||||
|
sandboxElement = select('#i' + id);
|
||||||
|
}
|
||||||
|
const root =
|
||||||
|
securityLevel === 'sandbox'
|
||||||
|
? select(sandboxElement.nodes()[0].contentDocument.body)
|
||||||
|
: select('body');
|
||||||
|
|
||||||
|
// Init bounds
|
||||||
|
bounds.init();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const svg = root.select('#' + id);
|
||||||
|
|
||||||
|
svg.append('g');
|
||||||
|
|
||||||
|
//4. Fetch the diagram data
|
||||||
|
const tasks = diagObj.db.getTasks();
|
||||||
|
const title = diagObj.db.getCommonDb().getDiagramTitle();
|
||||||
|
|
||||||
|
//log tasks
|
||||||
|
log.info(tasks);
|
||||||
|
|
||||||
|
//5. Initialize the diagram
|
||||||
|
svgDraw.initGraphics(svg);
|
||||||
|
|
||||||
|
//bounds.insert(0, 0, LEFT_MARGIN, 0);
|
||||||
|
// fetch Sections
|
||||||
|
const sections = diagObj.db.getSections();
|
||||||
|
// log sections
|
||||||
|
log.info(sections);
|
||||||
|
|
||||||
|
let maxSectionHeight = 0;
|
||||||
|
let maxTaskHeight = 0;
|
||||||
|
let sectionBeginX = 0;
|
||||||
|
let sectionBeginY = 0;
|
||||||
|
let masterX = 50 + LEFT_MARGIN;
|
||||||
|
sectionBeginX = masterX;
|
||||||
|
let masterY = 50;
|
||||||
|
sectionBeginY=50;
|
||||||
|
//draw sections
|
||||||
|
let sectionNumber = 0;
|
||||||
|
|
||||||
|
//Calculate the max height of the sections
|
||||||
|
sections.forEach(function (section) {
|
||||||
|
const sectionNode = {
|
||||||
|
number: sectionNumber,
|
||||||
|
descr: section,
|
||||||
|
section: sectionNumber,
|
||||||
|
width: 150,
|
||||||
|
padding: 20,
|
||||||
|
maxHeight: maxSectionHeight,
|
||||||
|
};
|
||||||
|
const sectionHeight = svgDraw.getVirtualNodeHeight(svg, sectionNode, conf);
|
||||||
|
log.info('sectionHeight before draw', sectionHeight);
|
||||||
|
maxSectionHeight = Math.max(maxSectionHeight, sectionHeight +20);
|
||||||
|
});
|
||||||
|
|
||||||
|
//tasks length and maxEventCount
|
||||||
|
let maxEventCount = 0;
|
||||||
|
log.info('tasks.length', tasks.length);
|
||||||
|
//calculate max task height
|
||||||
|
// for loop till tasks.length
|
||||||
|
for (let i = 0; i < tasks.length; i++) {
|
||||||
|
const task = tasks[i];
|
||||||
|
|
||||||
|
const taskNode = {
|
||||||
|
number: i,
|
||||||
|
descr: task,
|
||||||
|
section: task.section,
|
||||||
|
width: 150,
|
||||||
|
padding: 20,
|
||||||
|
maxHeight: maxTaskHeight,
|
||||||
|
};
|
||||||
|
const taskHeight = svgDraw.getVirtualNodeHeight(svg, taskNode, conf);
|
||||||
|
log.info('taskHeight before draw', taskHeight);
|
||||||
|
maxTaskHeight = Math.max(maxTaskHeight, taskHeight + 20);
|
||||||
|
|
||||||
|
//calculate maxEventCount
|
||||||
|
maxEventCount = Math.max(maxEventCount, task.events.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
log.info('maxSectionHeight before draw', maxSectionHeight);
|
||||||
|
log.info('maxTaskHeight before draw', maxTaskHeight);
|
||||||
|
|
||||||
|
if (sections && sections.length > 0) {
|
||||||
|
sections.forEach((section) => {
|
||||||
|
|
||||||
|
const sectionNode = {
|
||||||
|
number: sectionNumber,
|
||||||
|
descr: section,
|
||||||
|
section: sectionNumber,
|
||||||
|
width: 150,
|
||||||
|
padding: 20,
|
||||||
|
maxHeight: maxSectionHeight,
|
||||||
|
};
|
||||||
|
//log section node
|
||||||
|
log.info('sectionNode', sectionNode);
|
||||||
|
const sectionNodeWrapper = svg.append('g');
|
||||||
|
const node = svgDraw.drawNode(sectionNodeWrapper, sectionNode, sectionNumber, conf);
|
||||||
|
// add node to section list
|
||||||
|
//sectionList.push(node);
|
||||||
|
//const nodeHeight = node.height + 20;
|
||||||
|
//Post process the node
|
||||||
|
//append g
|
||||||
|
|
||||||
|
sectionNodeWrapper.attr(
|
||||||
|
'transform',
|
||||||
|
`translate(${masterX}, ${sectionBeginY})`
|
||||||
|
);
|
||||||
|
//maxSectionHeight = Math.max(maxSectionHeight, nodeHeight);
|
||||||
|
masterY += maxSectionHeight + 50;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//draw tasks for this section
|
||||||
|
//filter task where tasks.section == section
|
||||||
|
const tasksForSection = tasks.filter((task) => task.section === section);
|
||||||
|
if (tasksForSection.length > 0) {
|
||||||
|
|
||||||
|
|
||||||
|
drawTasks(svg, tasksForSection, sectionNumber, masterX, masterY, maxTaskHeight, conf, maxEventCount,false);
|
||||||
|
}
|
||||||
|
// todo replace with total width of section and its tasks
|
||||||
|
masterX += 200 * Math.max(tasksForSection.length, 1);
|
||||||
|
|
||||||
|
masterY = sectionBeginY;
|
||||||
|
sectionNumber++;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
//draw tasks
|
||||||
|
drawTasks(svg, tasks, sectionNumber, masterX, masterY, maxTaskHeight, conf, maxEventCount,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// draw tasks
|
||||||
|
//drawTasks(svg, tasks, 0);
|
||||||
|
|
||||||
|
const box = bounds.getBounds();
|
||||||
|
if (title) {
|
||||||
|
svg
|
||||||
|
.append('text')
|
||||||
|
.text(title)
|
||||||
|
.attr('x', LEFT_MARGIN)
|
||||||
|
.attr('font-size', '4ex')
|
||||||
|
.attr('font-weight', 'bold')
|
||||||
|
.attr('y', 25);
|
||||||
|
}
|
||||||
|
|
||||||
|
const height = box.stopy - box.starty + 2 * conf.diagramMarginY;
|
||||||
|
const width = LEFT_MARGIN + box.stopx + 2 * conf.diagramMarginX;
|
||||||
|
|
||||||
|
// Setup the view box and size of the svg element
|
||||||
|
setupGraphViewbox(undefined, svg, conf.timeline.padding, conf.timeline.useMaxWidth);
|
||||||
|
|
||||||
|
//5. Draw the diagram
|
||||||
|
const maxTaskLength = 500;
|
||||||
|
|
||||||
|
// Draw activity line
|
||||||
|
svg
|
||||||
|
.append('line')
|
||||||
|
.attr('x1', LEFT_MARGIN)
|
||||||
|
.attr('y1', maxSectionHeight + maxTaskHeight +150) // One section head + one task + margins
|
||||||
|
.attr('x2', tasks && tasks.length? (tasks.length*200)+ 400 : 400) // Subtract stroke width so arrow point is retained
|
||||||
|
.attr('y2', maxSectionHeight + maxTaskHeight +150)
|
||||||
|
.attr('stroke-width', 4)
|
||||||
|
.attr('stroke', 'black')
|
||||||
|
.attr('marker-end', 'url(#arrowhead)');
|
||||||
|
|
||||||
|
const extraVertForTitle = title ? 70 : 0;
|
||||||
|
svg.attr('viewBox', `${box.startx} -25 ${width} ${height + extraVertForTitle}`);
|
||||||
|
svg.attr('preserveAspectRatio', 'xMinYMin meet');
|
||||||
|
svg.attr('height', height + extraVertForTitle + 25);
|
||||||
|
|
||||||
|
// addSVGAccessibilityFields(diagObj.db, diagram, id);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bounds = {
|
||||||
|
data: {
|
||||||
|
startx: undefined,
|
||||||
|
stopx: undefined,
|
||||||
|
starty: undefined,
|
||||||
|
stopy: undefined,
|
||||||
|
},
|
||||||
|
verticalPos: 0,
|
||||||
|
|
||||||
|
sequenceItems: [],
|
||||||
|
init: function () {
|
||||||
|
this.sequenceItems = [];
|
||||||
|
this.data = {
|
||||||
|
startx: undefined,
|
||||||
|
stopx: undefined,
|
||||||
|
starty: undefined,
|
||||||
|
stopy: undefined,
|
||||||
|
};
|
||||||
|
this.verticalPos = 0;
|
||||||
|
},
|
||||||
|
updateVal: function (obj, key, val, fun) {
|
||||||
|
if (typeof obj[key] === 'undefined') {
|
||||||
|
obj[key] = val;
|
||||||
|
} else {
|
||||||
|
obj[key] = fun(val, obj[key]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateBounds: function (startx, starty, stopx, stopy) {
|
||||||
|
const conf = getConfig().timeline;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
|
const _self = this;
|
||||||
|
let cnt = 0;
|
||||||
|
/** @param {any} type */
|
||||||
|
function updateFn(type) {
|
||||||
|
return function updateItemBounds(item) {
|
||||||
|
cnt++;
|
||||||
|
// The loop sequenceItems is a stack so the biggest margins in the beginning of the sequenceItems
|
||||||
|
const n = _self.sequenceItems.length - cnt + 1;
|
||||||
|
_self.updateVal(item, 'starty', starty - n * conf.boxMargin, Math.min);
|
||||||
|
_self.updateVal(item, 'stopy', stopy + n * conf.boxMargin, Math.max);
|
||||||
|
|
||||||
|
_self.updateVal(bounds.data, 'startx', startx - n * conf.boxMargin, Math.min);
|
||||||
|
_self.updateVal(bounds.data, 'stopx', stopx + n * conf.boxMargin, Math.max);
|
||||||
|
|
||||||
|
if (!(type === 'activation')) {
|
||||||
|
_self.updateVal(item, 'startx', startx - n * conf.boxMargin, Math.min);
|
||||||
|
_self.updateVal(item, 'stopx', stopx + n * conf.boxMargin, Math.max);
|
||||||
|
|
||||||
|
_self.updateVal(bounds.data, 'starty', starty - n * conf.boxMargin, Math.min);
|
||||||
|
_self.updateVal(bounds.data, 'stopy', stopy + n * conf.boxMargin, Math.max);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sequenceItems.forEach(updateFn());
|
||||||
|
},
|
||||||
|
insert: function (startx, starty, stopx, stopy) {
|
||||||
|
const _startx = Math.min(startx, stopx);
|
||||||
|
const _stopx = Math.max(startx, stopx);
|
||||||
|
const _starty = Math.min(starty, stopy);
|
||||||
|
const _stopy = Math.max(starty, stopy);
|
||||||
|
|
||||||
|
this.updateVal(bounds.data, 'startx', _startx, Math.min);
|
||||||
|
this.updateVal(bounds.data, 'starty', _starty, Math.min);
|
||||||
|
this.updateVal(bounds.data, 'stopx', _stopx, Math.max);
|
||||||
|
this.updateVal(bounds.data, 'stopy', _stopy, Math.max);
|
||||||
|
|
||||||
|
this.updateBounds(_startx, _starty, _stopx, _stopy);
|
||||||
|
},
|
||||||
|
bumpVerticalPos: function (bump) {
|
||||||
|
this.verticalPos = this.verticalPos + bump;
|
||||||
|
this.data.stopy = this.verticalPos;
|
||||||
|
},
|
||||||
|
getVerticalPos: function () {
|
||||||
|
return this.verticalPos;
|
||||||
|
},
|
||||||
|
getBounds: function () {
|
||||||
|
return this.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const drawTasks = function (diagram, tasks, sectionColor, masterX, masterY, maxTaskHeight,conf,maxEventCount, isWithoutSections) {
|
||||||
|
|
||||||
|
const taskBeginY = masterY;
|
||||||
|
|
||||||
|
const taskBeginX = masterX;
|
||||||
|
|
||||||
|
// Draw the tasks
|
||||||
|
for (let i = 0; i < tasks.length; i++) {
|
||||||
|
|
||||||
|
const task = tasks[i];
|
||||||
|
// create node from task
|
||||||
|
const taskNode = {
|
||||||
|
descr: task.task,
|
||||||
|
section: sectionColor,
|
||||||
|
number : sectionColor,
|
||||||
|
width: 150,
|
||||||
|
padding: 20,
|
||||||
|
maxHeight: maxTaskHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
//log task node
|
||||||
|
log.info('taskNode', taskNode);
|
||||||
|
// create task wrapper
|
||||||
|
const taskWrapper = diagram.append('g').attr('class', 'taskWrapper');
|
||||||
|
const node = svgDraw.drawNode(taskWrapper, taskNode, sectionColor, conf);
|
||||||
|
const taskHeight = node.height;
|
||||||
|
//log task height
|
||||||
|
log.info('taskHeight after draw', taskHeight);
|
||||||
|
taskWrapper.attr(
|
||||||
|
'transform',
|
||||||
|
`translate(${masterX}, ${masterY})`
|
||||||
|
);
|
||||||
|
|
||||||
|
// update max task height
|
||||||
|
maxTaskHeight = Math.max(maxTaskHeight, taskHeight);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// if task has events, draw them
|
||||||
|
if (task.events) {
|
||||||
|
// draw a line between the task and the events
|
||||||
|
const lineWrapper = diagram.append('g').attr('class', 'lineWrapper');
|
||||||
|
|
||||||
|
let linelength = maxTaskHeight;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//add margin to task
|
||||||
|
masterY += 100;
|
||||||
|
linelength = linelength+ drawEvents(diagram, task.events, sectionColor, masterX, masterY, conf);
|
||||||
|
masterY -= 100;
|
||||||
|
|
||||||
|
lineWrapper
|
||||||
|
.append('line')
|
||||||
|
.attr('x1', masterX + 190/2)
|
||||||
|
.attr('y1', masterY + maxTaskHeight) // One section head + one task + margins
|
||||||
|
.attr('x2', masterX + 190/2) // Subtract stroke width so arrow point is retained
|
||||||
|
.attr('y2', masterY + linelength + maxEventCount * 100)
|
||||||
|
.attr('stroke-width', 2)
|
||||||
|
.attr('stroke', 'black')
|
||||||
|
.attr('marker-end', 'url(#arrowhead)')
|
||||||
|
.attr('stroke-dasharray', "5,5");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
masterX = masterX + 200;
|
||||||
|
if (isWithoutSections) {
|
||||||
|
sectionColor++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// reset Y coordinate for next section
|
||||||
|
masterY= masterY -10; ;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawEvents = function (diagram, events, sectionColor, masterX, masterY, conf) {
|
||||||
|
|
||||||
|
let maxEventHeight = 0;
|
||||||
|
const eventBeginY = masterY;
|
||||||
|
masterY = masterY + 100
|
||||||
|
// Draw the events
|
||||||
|
for (let i = 0; i < events.length; i++) {
|
||||||
|
const event = events[i];
|
||||||
|
// create node from event
|
||||||
|
const eventNode = {
|
||||||
|
descr: event,
|
||||||
|
section: sectionColor,
|
||||||
|
number : sectionColor,
|
||||||
|
width: 150,
|
||||||
|
padding: 20,
|
||||||
|
maxHeight: 50,
|
||||||
|
};
|
||||||
|
|
||||||
|
//log task node
|
||||||
|
log.info('eventNode', eventNode);
|
||||||
|
// create event wrapper
|
||||||
|
const eventWrapper = diagram.append('g').attr('class', 'eventWrapper');
|
||||||
|
const node = svgDraw.drawNode(eventWrapper, eventNode, sectionColor, conf)
|
||||||
|
const eventHeight = node.height;
|
||||||
|
maxEventHeight= maxEventHeight + eventHeight;
|
||||||
|
eventWrapper.attr(
|
||||||
|
'transform',
|
||||||
|
`translate(${masterX}, ${masterY})`
|
||||||
|
);
|
||||||
|
masterY = masterY + 10 + eventHeight;
|
||||||
|
|
||||||
|
}
|
||||||
|
// set masterY back to eventBeginY
|
||||||
|
masterY = eventBeginY;
|
||||||
|
return maxEventHeight;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setConf,
|
||||||
|
draw,
|
||||||
|
};
|
7
packages/mermaid-timeline/src/types/index.d.ts
vendored
Normal file
7
packages/mermaid-timeline/src/types/index.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export {};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
mermaid: any; // 👈️ turn off type checking
|
||||||
|
}
|
||||||
|
}
|
10
packages/mermaid-timeline/tsconfig.json
Normal file
10
packages/mermaid-timeline/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"module": "esnext",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./dist"
|
||||||
|
},
|
||||||
|
"include": ["./src/**/*.ts"],
|
||||||
|
"typeRoots": ["./src/types"]
|
||||||
|
}
|
@@ -26,6 +26,7 @@ export interface MermaidConfig {
|
|||||||
sequence?: SequenceDiagramConfig;
|
sequence?: SequenceDiagramConfig;
|
||||||
gantt?: GanttDiagramConfig;
|
gantt?: GanttDiagramConfig;
|
||||||
journey?: JourneyDiagramConfig;
|
journey?: JourneyDiagramConfig;
|
||||||
|
timeline?: TimelineDiagramConfig;
|
||||||
class?: ClassDiagramConfig;
|
class?: ClassDiagramConfig;
|
||||||
state?: StateDiagramConfig;
|
state?: StateDiagramConfig;
|
||||||
er?: ErDiagramConfig;
|
er?: ErDiagramConfig;
|
||||||
@@ -292,6 +293,29 @@ export interface JourneyDiagramConfig extends BaseDiagramConfig {
|
|||||||
sectionColours?: string[];
|
sectionColours?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TimelineDiagramConfig extends BaseDiagramConfig {
|
||||||
|
diagramMarginX?: number;
|
||||||
|
diagramMarginY?: number;
|
||||||
|
leftMargin?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
boxMargin?: number;
|
||||||
|
boxTextMargin?: number;
|
||||||
|
noteMargin?: number;
|
||||||
|
messageMargin?: number;
|
||||||
|
messageAlign?: string;
|
||||||
|
bottomMarginAdj?: number;
|
||||||
|
rightAngles?: boolean;
|
||||||
|
taskFontSize?: string | number;
|
||||||
|
taskFontFamily?: string;
|
||||||
|
taskMargin?: number;
|
||||||
|
activationWidth?: number;
|
||||||
|
textPlacement?: string;
|
||||||
|
actorColours?: string[];
|
||||||
|
sectionFills?: string[];
|
||||||
|
sectionColours?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface GanttDiagramConfig extends BaseDiagramConfig {
|
export interface GanttDiagramConfig extends BaseDiagramConfig {
|
||||||
titleTopMargin?: number;
|
titleTopMargin?: number;
|
||||||
barHeight?: number;
|
barHeight?: number;
|
||||||
|
@@ -861,6 +861,155 @@ const config: Partial<MermaidConfig> = {
|
|||||||
sectionFills: ['#191970', '#8B008B', '#4B0082', '#2F4F4F', '#800000', '#8B4513', '#00008B'],
|
sectionFills: ['#191970', '#8B008B', '#4B0082', '#2F4F4F', '#800000', '#8B4513', '#00008B'],
|
||||||
sectionColours: ['#fff'],
|
sectionColours: ['#fff'],
|
||||||
},
|
},
|
||||||
|
/** The object containing configurations specific for timeline diagrams */
|
||||||
|
timeline: {
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | -------------- | ---------------------------------------------------- | ------- | -------- | ------------------ |
|
||||||
|
* | diagramMarginX | Margin to the right and left of the sequence diagram | Integer | Required | Any Positive Value |
|
||||||
|
*
|
||||||
|
* **Notes:** Default value: 50
|
||||||
|
*/
|
||||||
|
diagramMarginX: 50,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | -------------- | -------------------------------------------------- | ------- | -------- | ------------------ |
|
||||||
|
* | diagramMarginY | Margin to the over and under the sequence diagram. | Integer | Required | Any Positive Value |
|
||||||
|
*
|
||||||
|
* **Notes:** Default value: 10
|
||||||
|
*/
|
||||||
|
diagramMarginY: 10,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | ----------- | --------------------- | ------- | -------- | ------------------ |
|
||||||
|
* | actorMargin | Margin between actors | Integer | Required | Any Positive Value |
|
||||||
|
*
|
||||||
|
* **Notes:** Default value: 50
|
||||||
|
*/
|
||||||
|
leftMargin: 150,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | --------- | -------------------- | ------- | -------- | ------------------ |
|
||||||
|
* | width | Width of actor boxes | Integer | Required | Any Positive Value |
|
||||||
|
*
|
||||||
|
* **Notes:** Default value: 150
|
||||||
|
*/
|
||||||
|
width: 150,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | --------- | --------------------- | ------- | -------- | ------------------ |
|
||||||
|
* | height | Height of actor boxes | Integer | Required | Any Positive Value |
|
||||||
|
*
|
||||||
|
* **Notes:** Default value: 65
|
||||||
|
*/
|
||||||
|
height: 50,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | --------- | ------------------------ | ------- | -------- | ------------------ |
|
||||||
|
* | boxMargin | Margin around loop boxes | Integer | Required | Any Positive Value |
|
||||||
|
*
|
||||||
|
* **Notes:** Default value: 10
|
||||||
|
*/
|
||||||
|
boxMargin: 10,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | ------------- | -------------------------------------------- | ------- | -------- | ------------------ |
|
||||||
|
* | boxTextMargin | Margin around the text in loop/alt/opt boxes | Integer | Required | Any Positive Value |
|
||||||
|
*
|
||||||
|
* **Notes:** Default value: 5
|
||||||
|
*/
|
||||||
|
boxTextMargin: 5,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | ---------- | ------------------- | ------- | -------- | ------------------ |
|
||||||
|
* | noteMargin | Margin around notes | Integer | Required | Any Positive Value |
|
||||||
|
*
|
||||||
|
* **Notes:** Default value: 10
|
||||||
|
*/
|
||||||
|
noteMargin: 10,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | ------------- | ----------------------- | ------- | -------- | ------------------ |
|
||||||
|
* | messageMargin | Space between messages. | Integer | Required | Any Positive Value |
|
||||||
|
*
|
||||||
|
* **Notes:**
|
||||||
|
*
|
||||||
|
* Space between messages.
|
||||||
|
*
|
||||||
|
* Default value: 35
|
||||||
|
*/
|
||||||
|
messageMargin: 35,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | ------------ | --------------------------- | ---- | -------- | ------------------------- |
|
||||||
|
* | messageAlign | Multiline message alignment | 3 | 4 | 'left', 'center', 'right' |
|
||||||
|
*
|
||||||
|
* **Notes:** Default value: 'center'
|
||||||
|
*/
|
||||||
|
messageAlign: 'center',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | --------------- | ------------------------------------------ | ------- | -------- | ------------------ |
|
||||||
|
* | bottomMarginAdj | Prolongs the edge of the diagram downwards | Integer | 4 | Any Positive Value |
|
||||||
|
*
|
||||||
|
* **Notes:**
|
||||||
|
*
|
||||||
|
* Depending on css styling this might need adjustment.
|
||||||
|
*
|
||||||
|
* Default value: 1
|
||||||
|
*/
|
||||||
|
bottomMarginAdj: 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | ----------- | ----------- | ------- | -------- | ----------- |
|
||||||
|
* | useMaxWidth | See notes | boolean | 4 | true, false |
|
||||||
|
*
|
||||||
|
* **Notes:**
|
||||||
|
*
|
||||||
|
* When this flag is set the height and width is set to 100% and is then scaling with the
|
||||||
|
* available space if not the absolute space required is used.
|
||||||
|
*
|
||||||
|
* Default value: true
|
||||||
|
*/
|
||||||
|
useMaxWidth: true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | Parameter | Description | Type | Required | Values |
|
||||||
|
* | ----------- | --------------------------------- | ---- | -------- | ----------- |
|
||||||
|
* | rightAngles | Curved Arrows become Right Angles | 3 | 4 | true, false |
|
||||||
|
*
|
||||||
|
* **Notes:**
|
||||||
|
*
|
||||||
|
* This will display arrows that start and begin at the same node as right angles, rather than a
|
||||||
|
* curves
|
||||||
|
*
|
||||||
|
* Default value: false
|
||||||
|
*/
|
||||||
|
rightAngles: false,
|
||||||
|
taskFontSize: 14,
|
||||||
|
taskFontFamily: '"Open Sans", sans-serif',
|
||||||
|
taskMargin: 50,
|
||||||
|
// width of activation box
|
||||||
|
activationWidth: 10,
|
||||||
|
|
||||||
|
// text placement as: tspan | fo | old only text as before
|
||||||
|
textPlacement: 'fo',
|
||||||
|
actorColours: ['#8FBC8F', '#7CFC00', '#00FFFF', '#20B2AA', '#B0E0E6', '#FFFFE0'],
|
||||||
|
|
||||||
|
sectionFills: ['#191970', '#8B008B', '#4B0082', '#2F4F4F', '#800000', '#8B4513', '#00008B'],
|
||||||
|
sectionColours: ['#fff'],
|
||||||
|
},
|
||||||
class: {
|
class: {
|
||||||
/**
|
/**
|
||||||
* ### titleTopMargin
|
* ### titleTopMargin
|
||||||
|
@@ -5,6 +5,8 @@ import { sanitizeText as _sanitizeText } from '../diagrams/common/common';
|
|||||||
import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox';
|
import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox';
|
||||||
import { addStylesForDiagram } from '../styles';
|
import { addStylesForDiagram } from '../styles';
|
||||||
import { DiagramDefinition, DiagramDetector } from './types';
|
import { DiagramDefinition, DiagramDetector } from './types';
|
||||||
|
import * as _commonDb from '../commonDb';
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Packaging and exposing resources for external diagrams so that they can import
|
Packaging and exposing resources for external diagrams so that they can import
|
||||||
@@ -16,6 +18,7 @@ export const setLogLevel = _setLogLevel;
|
|||||||
export const getConfig = _getConfig;
|
export const getConfig = _getConfig;
|
||||||
export const sanitizeText = (text: string) => _sanitizeText(text, getConfig());
|
export const sanitizeText = (text: string) => _sanitizeText(text, getConfig());
|
||||||
export const setupGraphViewbox = _setupGraphViewbox;
|
export const setupGraphViewbox = _setupGraphViewbox;
|
||||||
|
export const getCommonDb = () => { return _commonDb };
|
||||||
|
|
||||||
const diagrams: Record<string, DiagramDefinition> = {};
|
const diagrams: Record<string, DiagramDefinition> = {};
|
||||||
export interface Detectors {
|
export interface Detectors {
|
||||||
@@ -46,7 +49,7 @@ export const registerDiagram = (
|
|||||||
addStylesForDiagram(id, diagram.styles);
|
addStylesForDiagram(id, diagram.styles);
|
||||||
|
|
||||||
if (diagram.injectUtils) {
|
if (diagram.injectUtils) {
|
||||||
diagram.injectUtils(log, setLogLevel, getConfig, sanitizeText, setupGraphViewbox);
|
diagram.injectUtils(log, setLogLevel, getConfig, sanitizeText, setupGraphViewbox,getCommonDb());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@ export interface InjectUtils {
|
|||||||
_getConfig: any;
|
_getConfig: any;
|
||||||
_sanitizeText: any;
|
_sanitizeText: any;
|
||||||
_setupGraphViewbox: any;
|
_setupGraphViewbox: any;
|
||||||
|
_commonDb: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,7 +28,8 @@ export interface DiagramDefinition {
|
|||||||
_setLogLevel: InjectUtils['_setLogLevel'],
|
_setLogLevel: InjectUtils['_setLogLevel'],
|
||||||
_getConfig: InjectUtils['_getConfig'],
|
_getConfig: InjectUtils['_getConfig'],
|
||||||
_sanitizeText: InjectUtils['_sanitizeText'],
|
_sanitizeText: InjectUtils['_sanitizeText'],
|
||||||
_setupGraphViewbox: InjectUtils['_setupGraphViewbox']
|
_setupGraphViewbox: InjectUtils['_setupGraphViewbox'],
|
||||||
|
_commonDb: InjectUtils['_commonDb']
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@@ -349,6 +349,22 @@ importers:
|
|||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
|
|
||||||
|
packages/mermaid-timeline:
|
||||||
|
dependencies:
|
||||||
|
d3:
|
||||||
|
specifier: ^7.0.0
|
||||||
|
version: 7.6.1
|
||||||
|
khroma:
|
||||||
|
specifier: ^2.0.0
|
||||||
|
version: 2.0.0
|
||||||
|
devDependencies:
|
||||||
|
concurrently:
|
||||||
|
specifier: ^7.4.0
|
||||||
|
version: 7.5.0
|
||||||
|
rimraf:
|
||||||
|
specifier: ^3.0.2
|
||||||
|
version: 3.0.2
|
||||||
|
|
||||||
tests/webpack:
|
tests/webpack:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mermaid-js/mermaid-mindmap':
|
'@mermaid-js/mermaid-mindmap':
|
||||||
@@ -3634,7 +3650,7 @@ packages:
|
|||||||
/axios/0.21.4_debug@4.3.2:
|
/axios/0.21.4_debug@4.3.2:
|
||||||
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
|
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.2_debug@4.3.2
|
follow-redirects: 1.15.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
dev: true
|
dev: true
|
||||||
@@ -6309,7 +6325,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==}
|
resolution: {integrity: sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/follow-redirects/1.15.2_debug@4.3.2:
|
/follow-redirects/1.15.2:
|
||||||
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
|
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -6317,8 +6333,6 @@ packages:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
debug:
|
debug:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
|
||||||
debug: 4.3.2
|
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/foreground-child/2.0.0:
|
/foreground-child/2.0.0:
|
||||||
@@ -6911,7 +6925,7 @@ packages:
|
|||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
eventemitter3: 4.0.7
|
eventemitter3: 4.0.7
|
||||||
follow-redirects: 1.15.2_debug@4.3.2
|
follow-redirects: 1.15.2
|
||||||
requires-port: 1.0.0
|
requires-port: 1.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
|
Reference in New Issue
Block a user