Merge branch 'chrismoran-bkt-develop' into develop

This commit is contained in:
Knut Sveidqvist 2020-06-19 08:41:52 +02:00
commit 8e1e7fb314
26 changed files with 1195 additions and 593 deletions

View File

@ -214,7 +214,7 @@ pie
## Related projects
- [Command Line Interface](https://github.com/mermaid-js/mermaid.cli)
- [Command Line Interface](https://github.com/mermaid-js/mermaid-cli)
- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor)
- [HTTP Server](https://github.com/TomWright/mermaid-server)

View File

@ -88,7 +88,7 @@ describe('Configuration', () => {
C -->|Three| F[fa:fa-car Car]
`,
{
arrowMarkerAbsolute: true
logLevel:0, arrowMarkerAbsolute: true,fontFamily: '"Noto Sans SC", sans-serif'
}
);

View File

@ -126,6 +126,29 @@ context('Sequence diagram', () => {
participant A as Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
A->>Bob: Hola
Bob-->A: Pasten !
`,
{logLevel: 0}
);
});
it('should wrap (inline) long actor descriptions', () => {
imgSnapshotTest(
`
sequenceDiagram
participant A as wrap:Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
A->>Bob: Hola
Bob-->A: Pasten !
`,
{logLevel: 0}
);
});
it('should wrap (directive) long actor descriptions', () => {
imgSnapshotTest(
`
%%{init: {'config': {'wrapEnabled': true }}}%%
sequenceDiagram
participant A as Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
A->>Bob: Hola
Bob-->A: Pasten !
`,
{}
);
@ -141,6 +164,17 @@ context('Sequence diagram', () => {
{}
);
});
it('should render long notes wrapped (inline) left of actor', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice->>Bob: Hola
Note left of Alice:wrap: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render long notes right of actor', () => {
imgSnapshotTest(
`
@ -152,6 +186,17 @@ context('Sequence diagram', () => {
{}
);
});
it('should render long notes wrapped (inline) right of actor', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice->>Bob: Hola
Note right of Alice:wrap: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render long notes over actor', () => {
imgSnapshotTest(
`
@ -163,6 +208,17 @@ context('Sequence diagram', () => {
{}
);
});
it('should render long notes wrapped (inline) over actor', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice->>Bob: Hola
Note over Alice:wrap: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render long messages from an actor to the left to one to the right', () => {
imgSnapshotTest(
`
@ -173,6 +229,16 @@ context('Sequence diagram', () => {
{}
);
});
it('should render long messages wrapped (inline) from an actor to the left to one to the right', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice->>Bob:wrap:Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render long messages from an actor to the right to one to the left', () => {
imgSnapshotTest(
`
@ -183,6 +249,16 @@ context('Sequence diagram', () => {
{}
);
});
it('should render long messages wrapped (inline) from an actor to the right to one to the left', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice->>Bob: I'm short
Bob->>Alice:wrap: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
`,
{}
);
});
});
context('background rects', () => {
it('should render a single and nested rects', () => {
@ -216,6 +292,69 @@ context('Sequence diagram', () => {
{}
);
});
it('should render a single and nested opt with long test overflowing', () => {
imgSnapshotTest(
`
sequenceDiagram
participant A
participant B
participant C
participant D
participant E
participant G
A ->>+ B: Task 1
opt this is an opt with a long title that will overflow
B ->>+ C: Task 2
C -->>- B: Return
end
A ->> D: Task 3
opt this is another opt with a long title that will overflow
D ->>+ E: Task 4
opt this is a nested opt with a long title that will overflow
E ->>+ G: Task 5
G -->>- E: Return
end
E ->> E: Task 6
end
D -->> A: Complete
`,
{}
);
});
it('should render a single and nested opt with long test wrapping', () => {
imgSnapshotTest(
`
%%{init: { 'config': { 'wrapEnabled': true } } }%%
sequenceDiagram
participant A
participant B
participant C
participant D
participant E
participant G
A ->>+ B: Task 1
opt this is an opt with a long title that will overflow
B ->>+ C: Task 2
C -->>- B: Return
end
A ->> D: Task 3
opt this is another opt with a long title that will overflow
D ->>+ E: Task 4
opt this is a nested opt with a long title that will overflow
E ->>+ G: Task 5
G -->>- E: Return
end
E ->> E: Task 6
end
D -->> A: Complete
`,
{}
);
});
it('should render rect around and inside loops', () => {
imgSnapshotTest(
`
@ -327,5 +466,69 @@ context('Sequence diagram', () => {
{}
);
});
it('should render dark theme from init directive and configure font size 24 font', () => {
imgSnapshotTest(
`
%%{init: {'theme': 'dark', 'config': {'fontSize': 24}}}%%
sequenceDiagram
Alice->>John: Hello John, how are you?
Alice->>John: John, can you hear me?
John-->>Alice: Hi Alice, I can hear you!
John-->>Alice: I feel great!
`,
{}
);
});
it('should render with wrapping enabled', () => {
imgSnapshotTest(
`
%%{init: { 'config': { 'wrapEnabled': true }}}%%
sequenceDiagram
participant A as Alice, the talkative one
A->>John: Hello John, how are you today? I'm feeling quite verbose today.
A->>John: John, can you hear me? If you are not available, we can talk later.
John-->>A: Hi Alice, I can hear you! I was finishing up an important meeting.
John-->>A: I feel great! I was not ignoring you. I am sorry you had to wait for a response.
`,
{}
);
});
it('should render with an init directive', () => {
imgSnapshotTest(
`%%{init: { "theme": "dark", 'config': { "fontFamily": "Menlo", "fontSize": 18, "fontWeight": 400, "wrapEnabled": true }}}%%
sequenceDiagram
Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can!
Note left of Alice: Bob thinks
Bob->>Alice: Fine!`,
{}
)
});
});
context('directives', () => {
it('should overide config with directive settings', () => {
imgSnapshotTest(
`
%%{init: { "sequence": { "mirrorActors": true }}}%%
sequenceDiagram
Alice->>Bob: I'm short
note left of Alice: config set to mirrorActors: false<br/>directive set to mirrorActors: true
Bob->>Alice: Short as well
`,
{ logLevel:0, sequence: { mirrorActors: false, noteFontSize: 18, noteFontFamily: 'Arial' } }
);
});
it('should overide config with directive settings', () => {
imgSnapshotTest(
`
%%{init: { "sequence": { "mirrorActors": false }}}%%
sequenceDiagram
%%{config: { "mirrorActors": false} }%%
Alice->>Bob: I'm short
note left of Alice: config set to mirrorActors: true<br/>directive set to mirrorActors: false
Bob->>Alice: Short as well
`,
{ logLevel:0, sequence: { mirrorActors: true, noteFontSize: 18, noteFontFamily: 'Arial' } }
);
});
});
});

View File

@ -21,6 +21,14 @@
<body>
<h1>info below</h1>
<div class="mermaid" style="width: 50%; height: 20%;">
graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
</div>
<div class="mermaid2" style="width: 50%; height: 20%;">
flowchart TB
subgraph 1
A --> B;
@ -64,13 +72,14 @@ flowchart TB
// arrowMarkerAbsolute: true,
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
logLevel: 0,
flowchart: { curve: 'linear', "htmlLabels": false },
flowchart: { curve: 'linear',htmlLabels: false },
// gantt: { axisFormat: '%m/%d/%Y' },
sequence: { actorMargin: 50, showSequenceNumbers: true },
// sequenceDiagram: { actorMargin: 300 } // deprecated
fontFamily: '"arial", sans-serif',
curve: 'linear',
securityLevel: 'loose'
securityLevel: 'loose',
htmlLabels: false
});
function callback(){alert('It worked');}
</script>

View File

@ -6,12 +6,12 @@
<style>
body {
/* font-family: 'Mansalva', cursive;*/
font-family: 'Mansalva', cursive;
font-family: '"Noto Sans SC", sans-serif';
}
.mermaid-main-font {
font-family: '"Noto Sans SC", sans-serif';
/* font-family: var(--mermaid-font-family); */
}
/* .mermaid-main-font {
font-family: "trebuchet ms", verdana, arial;
font-family: var(--mermaid-font-family);
} */
/* :root {
--mermaid-font-family: '"trebuchet ms", verdana, arial';
--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive;
@ -27,7 +27,7 @@
<script>
// Notice startOnLoad=false
// This prevents default handling in mermaid from render before the e2e logic is applied
mermaid.initialize({
window.mermaidDefault = {
startOnLoad: false,
useMaxWidth: true,
// "themeCSS": ":root { --mermaid-font-family: \"trebuchet ms\", verdana, arial;}",
@ -35,7 +35,8 @@
// fontFamily: '"Comic Sans MS", "Comic Sans", cursive'
// fontFamily: '"Mansalva", cursive',
fontFamily: '"Noto Sans SC", sans-serif'
});
};
mermaid.initialize(window.mermaidDefault);
</script>
</body>
</html>

View File

@ -30,6 +30,11 @@ const contentLoaded = function() {
div.innerHTML = graphObj.code;
document.getElementsByTagName('body')[0].appendChild(div);
}
if (window.mermaidDefault.fontFamily) {
graphObj.mermaid.fontFamily = window.mermaidDefault.fontFamily;
}
global.mermaid.initialize(graphObj.mermaid);
global.mermaid.init();
}

21
dist/mermaid.core.js vendored
View File

@ -832,7 +832,8 @@ function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "functi
var config = {};
var setConf = function setConf(cnf) {
// Top level initially mermaid, gflow, sequenceDiagram and gantt
console.log('config::setConf', cnf); // Top level initially mermaid, gflow, sequenceDiagram and gantt
var lvl1Keys = Object.keys(cnf);
for (var i = 0; i < lvl1Keys.length; i++) {
@ -852,6 +853,8 @@ var setConf = function setConf(cnf) {
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
}
}
console.log('config::setConf done', config);
};
var setConfig = function setConfig(conf) {
@ -21478,7 +21481,8 @@ var render = function render(id, _txt, cb, container) {
};
var setConf = function setConf(cnf) {
// Top level initially mermaid, gflow, sequenceDiagram and gantt
console.log('set conf ', cnf); // Top level initially mermaid, gflow, sequenceDiagram and gantt
var lvl1Keys = Object.keys(cnf);
for (var i = 0; i < lvl1Keys.length; i++) {
@ -21499,6 +21503,8 @@ var setConf = function setConf(cnf) {
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
}
}
console.log('set conf done', config);
};
function reinitialize(options) {
@ -21516,7 +21522,16 @@ function reinitialize(options) {
}
function initialize(options) {
var _config = config;
console.log('initialize ', options, config, Object(_config__WEBPACK_IMPORTED_MODULE_3__["getConfig"])());
var _config = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getConfig"])(); // Set default options
if (_typeof(options) === 'object') {
setConf(_config);
Object(_config__WEBPACK_IMPORTED_MODULE_3__["setConfig"])(_config);
}
_logger__WEBPACK_IMPORTED_MODULE_4__["logger"].debug('Initializing mermaidAPI ', {
version: _package_json__WEBPACK_IMPORTED_MODULE_2__.version,
options: options,

File diff suppressed because one or more lines are too long

21
dist/mermaid.js vendored
View File

@ -46615,7 +46615,8 @@ function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "functi
var config = {};
var setConf = function setConf(cnf) {
// Top level initially mermaid, gflow, sequenceDiagram and gantt
console.log('config::setConf', cnf); // Top level initially mermaid, gflow, sequenceDiagram and gantt
var lvl1Keys = Object.keys(cnf);
for (var i = 0; i < lvl1Keys.length; i++) {
@ -46635,6 +46636,8 @@ var setConf = function setConf(cnf) {
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
}
}
console.log('config::setConf done', config);
};
var setConfig = function setConfig(conf) {
@ -67236,7 +67239,8 @@ var render = function render(id, _txt, cb, container) {
};
var setConf = function setConf(cnf) {
// Top level initially mermaid, gflow, sequenceDiagram and gantt
console.log('set conf ', cnf); // Top level initially mermaid, gflow, sequenceDiagram and gantt
var lvl1Keys = Object.keys(cnf);
for (var i = 0; i < lvl1Keys.length; i++) {
@ -67257,6 +67261,8 @@ var setConf = function setConf(cnf) {
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
}
}
console.log('set conf done', config);
};
function reinitialize(options) {
@ -67274,7 +67280,16 @@ function reinitialize(options) {
}
function initialize(options) {
var _config = config;
console.log('initialize ', options, config, Object(_config__WEBPACK_IMPORTED_MODULE_3__["getConfig"])());
var _config = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getConfig"])(); // Set default options
if (_typeof(options) === 'object') {
setConf(_config);
Object(_config__WEBPACK_IMPORTED_MODULE_3__["setConfig"])(_config);
}
_logger__WEBPACK_IMPORTED_MODULE_4__["logger"].debug('Initializing mermaidAPI ', {
version: _package_json__WEBPACK_IMPORTED_MODULE_2__.version,
options: options,

2
dist/mermaid.js.map vendored

File diff suppressed because one or more lines are too long

2
dist/mermaid.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -34,7 +34,7 @@
window.$docsify = {
search: 'auto',
name: 'mermaid',
repo: 'https://github.com/knsv/mermaid',
repo: 'https://github.com/mermaid-js/mermaid',
loadSidebar: true,
mergeNavbar: true,
maxLevel: 4,

View File

@ -454,12 +454,11 @@ mermaidAPI.initialize({
### Parameters
- `id` the id of the element to be rendered
- `_txt`
- `_txt` the graph definition
- `cb` callback which is called after rendering is finished with the svg code as inparam.
- `container` selector to element in which a div with the graph temporarily will be inserted. In one is
provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is
completed.
- `txt` the graph definition
##

View File

@ -1,36 +1,19 @@
let config = {};
const setConf = function(cnf) {
// Top level initially mermaid, gflow, sequenceDiagram and gantt
const lvl1Keys = Object.keys(cnf);
for (let i = 0; i < lvl1Keys.length; i++) {
if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) {
const lvl2Keys = Object.keys(cnf[lvl1Keys[i]]);
for (let j = 0; j < lvl2Keys.length; j++) {
// logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j])
if (typeof config[lvl1Keys[i]] === 'undefined') {
config[lvl1Keys[i]] = {};
}
// logger.debug('Setting config: ' + lvl1Keys[i] + ' ' + lvl2Keys[j] + ' to ' + cnf[lvl1Keys[i]][lvl2Keys[j]])
config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]];
}
} else {
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
}
}
};
import { assignWithDepth } from './utils';
const config = {};
export const setConfig = conf => {
setConf(conf);
assignWithDepth(config, conf);
};
export const getConfig = () => config;
export const reset = conf => {
Object.keys(config).forEach(key => delete config[key]);
assignWithDepth(config, conf, { clobber: true });
};
const configApi = {
setConfig,
getConfig
// get conf() {
// return config;
// }
getConfig,
reset
};
export default configApi;

View File

@ -1,5 +1,4 @@
import { logger } from '../../logger';
import { getConfig, setConfig } from '../../config';
import mermaidAPI from '../../mermaidAPI';
let prevActor = undefined;
@ -10,7 +9,6 @@ let title = '';
let titleWrapped = false;
let sequenceNumbersEnabled = false;
let wrapEnabled = false;
let configUpdated = false;
let currentDirective = {};
export const parseDirective = function(statement, context) {
@ -46,18 +44,24 @@ const handleDirective = function(directive) {
switch (directive.type) {
case 'init':
case 'initialize':
['config'].forEach(prop => {
if (typeof directive.args[prop] !== 'undefined') {
directive.args.sequence = directive.args[prop];
delete directive.args[prop];
}
});
mermaidAPI.initialize(directive.args);
break;
case 'config':
updateConfig(directive.args);
break;
case 'wrap':
case 'nowrap':
wrapEnabled = directive.type === 'wrap';
break;
default:
logger.warn(
`Unrecognized directive: source: '%%{${directive.type}: ${directive.args}}%%`,
`Unhandled directive: source: '%%{${directive.type}: ${JSON.stringify(
directive.args ? directive.args : {}
)}}%%`,
directive
);
break;
@ -77,7 +81,7 @@ export const addActor = function(id, name, description) {
actors[id] = {
name: name,
description: description.text,
wrap: (description.wrap === null && autoWrap()) || !!description.wrap,
wrap: (description.wrap === undefined && autoWrap()) || !!description.wrap,
prevActor: prevActor
};
if (prevActor && actors[prevActor]) {
@ -111,12 +115,17 @@ export const addMessage = function(idFrom, idTo, message, answer) {
from: idFrom,
to: idTo,
message: message.text,
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
wrap: (message.wrap === undefined && autoWrap()) || !!message.wrap,
answer: answer
});
};
export const addSignal = function(idFrom, idTo, message = { text: null, wrap: null }, messageType) {
export const addSignal = function(
idFrom,
idTo,
message = { text: undefined, wrap: undefined },
messageType
) {
logger.debug(
'Adding message from=' +
idFrom +
@ -150,7 +159,7 @@ export const addSignal = function(idFrom, idTo, message = { text: null, wrap: nu
from: idFrom,
to: idTo,
message: message.text,
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
wrap: (message.wrap === undefined && autoWrap()) || !!message.wrap,
type: messageType
});
return true;
@ -180,12 +189,8 @@ export const enableSequenceNumbers = function() {
};
export const showSequenceNumbers = () => sequenceNumbersEnabled;
export const enableWrap = function() {
wrapEnabled = true;
};
export const disableWrap = function() {
wrapEnabled = false;
export const setWrap = function(wrapSetting) {
wrapEnabled = wrapSetting;
};
export const autoWrap = () => wrapEnabled;
@ -193,12 +198,11 @@ export const autoWrap = () => wrapEnabled;
export const clear = function() {
actors = {};
messages = [];
configUpdated = false;
};
export const parseMessage = function(str) {
const _str = str.trim();
return {
const retVal = {
text: _str.replace(/^[:]?(?:no)?wrap:/, '').trim(),
wrap:
_str.match(/^[:]?(?:no)?wrap:/) === null
@ -209,6 +213,8 @@ export const parseMessage = function(str) {
? false
: autoWrap()
};
logger.debug(`ParseMessage[${str}] [${JSON.stringify(retVal, null, 2)}`);
return retVal;
};
export const LINETYPE = {
@ -251,7 +257,7 @@ export const addNote = function(actor, placement, message) {
actor: actor,
placement: placement,
message: message.text,
wrap: (message.wrap === null && autoWrap()) || !!message.wrap
wrap: (message.wrap === undefined && autoWrap()) || !!message.wrap
};
// Coerce actor into a [to, from, ...] array
@ -262,7 +268,7 @@ export const addNote = function(actor, placement, message) {
from: actors[0],
to: actors[1],
message: message.text,
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
wrap: (message.wrap === undefined && autoWrap()) || !!message.wrap,
type: LINETYPE.NOTE,
placement: placement
});
@ -270,20 +276,7 @@ export const addNote = function(actor, placement, message) {
export const setTitle = function(titleWrap) {
title = titleWrap.text;
titleWrapped = (titleWrap.wrap === null && autoWrap()) || !!titleWrap.wrap;
};
export const updateConfig = function(config = getConfig()) {
try {
setConfig(config);
configUpdated = true;
} catch (error) {
logger.error('Error: unable to parse config');
}
};
export const hasConfigChange = function() {
return configUpdated;
titleWrapped = (titleWrap.wrap === undefined && autoWrap()) || !!titleWrap.wrap;
};
export const apply = function(param) {
@ -355,20 +348,16 @@ export default {
addActor,
addMessage,
addSignal,
enableWrap,
disableWrap,
autoWrap,
setWrap,
enableSequenceNumbers,
showSequenceNumbers,
autoWrap,
getMessages,
getActors,
getActor,
getActorKeys,
getTitle,
parseDirective,
hasConfigChange,
getConfig,
updateConfig,
getTitleWrapped,
clear,
parseMessage,

View File

@ -1,7 +1,7 @@
/* eslint-env jasmine */
import { parser } from './parser/sequenceDiagram';
import sequenceDb from './sequenceDb';
import renderer, { calculateTextHeight, calculateTextWidth } from './sequenceRenderer';
import renderer from './sequenceRenderer';
import mermaidAPI from '../../mermaidAPI';
function addConf(conf, key, value) {
@ -10,7 +10,6 @@ function addConf(conf, key, value) {
}
return conf;
}
describe('when parsing a sequenceDiagram', function() {
beforeEach(function() {
parser.yy = sequenceDb;
@ -774,6 +773,7 @@ end`;
describe('when checking the bounds in a sequenceDiagram', function() {
let conf;
beforeEach(function() {
mermaidAPI.reset();
parser.yy = sequenceDb;
parser.yy.clear();
conf = {
@ -788,10 +788,11 @@ describe('when checking the bounds in a sequenceDiagram', function() {
boxTextMargin: 15,
noteMargin: 25
};
renderer.setConf(conf);
mermaidAPI.initialize({ sequence: conf });
renderer.bounds.init();
});
it('it should handle a simple bound call', function() {
renderer.bounds.init();
renderer.bounds.insert(100, 100, 200, 200);
@ -802,7 +803,6 @@ describe('when checking the bounds in a sequenceDiagram', function() {
expect(bounds.stopy).toBe(200);
});
it('it should handle an expanding bound', function() {
renderer.bounds.init();
renderer.bounds.insert(100, 100, 200, 200);
renderer.bounds.insert(25, 50, 300, 400);
@ -814,7 +814,6 @@ describe('when checking the bounds in a sequenceDiagram', function() {
expect(bounds.stopy).toBe(400);
});
it('it should handle inserts within the bound without changing the outer bounds', function() {
renderer.bounds.init();
renderer.bounds.insert(100, 100, 200, 200);
renderer.bounds.insert(25, 50, 300, 400);
@ -827,7 +826,6 @@ describe('when checking the bounds in a sequenceDiagram', function() {
expect(bounds.stopy).toBe(400);
});
it('it should handle a loop without expanding the area', function() {
renderer.bounds.init();
renderer.bounds.insert(25, 50, 300, 400);
renderer.bounds.verticalPos = 150;
@ -850,7 +848,6 @@ describe('when checking the bounds in a sequenceDiagram', function() {
expect(bounds.stopy).toBe(400);
});
it('it should handle multiple loops withtout expanding the bounds', function() {
renderer.bounds.init();
renderer.bounds.insert(100, 100, 1000, 1000);
renderer.bounds.verticalPos = 200;
@ -883,7 +880,6 @@ describe('when checking the bounds in a sequenceDiagram', function() {
expect(bounds.stopy).toBe(1000);
});
it('it should handle a loop that expands the area', function() {
renderer.bounds.init();
renderer.bounds.insert(100, 100, 200, 200);
renderer.bounds.verticalPos = 200;
@ -910,6 +906,7 @@ describe('when checking the bounds in a sequenceDiagram', function() {
describe('when rendering a sequenceDiagram', function() {
let conf;
beforeEach(function() {
mermaidAPI.reset();
parser.yy = sequenceDb;
parser.yy.clear();
@ -923,19 +920,22 @@ describe('when rendering a sequenceDiagram', function() {
boxMargin: 10,
messageMargin: 40,
boxTextMargin: 15,
noteMargin: 25
noteMargin: 25,
wrapEnabled: false,
mirrorActors: false
};
renderer.setConf(conf);
mermaidAPI.initialize({ sequence: conf });
renderer.bounds.init();
});
['tspan', 'fo', 'old', undefined].forEach(function(textPlacement) {
it(`
it should handle one actor, when textPlacement is ${textPlacement}`, function() {
renderer.setConf(addConf(conf, 'textPlacement', textPlacement));
renderer.bounds.init();
const str = `
sequenceDiagram
participant Alice`;
mermaidAPI.initialize(addConf(conf, 'textPlacement', textPlacement));
renderer.bounds.init();
parser.parse(str);
renderer.draw(str, 'tst');
@ -947,8 +947,6 @@ participant Alice`;
});
});
it('it should handle same actor with different whitespace properly', function() {
renderer.bounds.init();
const str = `
sequenceDiagram
participant Alice
@ -962,7 +960,6 @@ participant Alice
expect(Object.keys(actors)).toEqual(['Alice']);
});
it('it should handle one actor and a centered note', function() {
renderer.bounds.init();
const str = `
sequenceDiagram
participant Alice
@ -980,7 +977,6 @@ Note over Alice: Alice thinks
expect(bounds.stopy).toBe(conf.height + conf.boxMargin + 2 * conf.noteMargin + 10);
});
it('it should handle one actor and a note to the left', function() {
renderer.bounds.init();
const str = `
sequenceDiagram
participant Alice
@ -997,7 +993,6 @@ Note left of Alice: Alice thinks`;
expect(bounds.stopy).toBe(conf.height + conf.boxMargin + 2 * conf.noteMargin + 10);
});
it('it should handle one actor and a note to the right', function() {
renderer.bounds.init();
const str = `
sequenceDiagram
participant Alice
@ -1014,7 +1009,6 @@ Note right of Alice: Alice thinks`;
expect(bounds.stopy).toBe(conf.height + conf.boxMargin + 2 * conf.noteMargin + 10);
});
it('it should handle two actors', function() {
renderer.bounds.init();
const str = `
sequenceDiagram
Alice->Bob: Hello Bob, how are you?`;
@ -1026,10 +1020,9 @@ Alice->Bob: Hello Bob, how are you?`;
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe(0 + conf.messageMargin + conf.height);
expect(bounds.stopy).toBe(conf.messageMargin + conf.height);
});
it('it should handle two actors with init directive', function() {
renderer.bounds.init();
const str = `
%%{init: {'logLevel': 0}}%%
sequenceDiagram
@ -1044,10 +1037,9 @@ Alice->Bob: Hello Bob, how are you?`;
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe(0 + conf.messageMargin + conf.height);
expect(bounds.stopy).toBe(conf.height + conf.messageMargin + (conf.mirrorActors ? 2 * conf.boxMargin + conf.height : 0));
});
it('it should handle two actors with init directive with multiline directive', function() {
renderer.bounds.init();
const str = `
%%{init: { 'logLevel': 0}}%%
sequenceDiagram
@ -1059,16 +1051,18 @@ Alice->Bob: Hello Bob, how are you?`;
parser.parse(str);
renderer.draw(str, 'tst');
const msgs = parser.yy.getMessages();
const bounds = renderer.bounds.getBounds();
const mermaid = mermaidAPI.getConfig();
expect(mermaid.logLevel).toBe(0);
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe(0 + conf.messageMargin + conf.height);
expect(bounds.stopy).toBe(conf.messageMargin + conf.height);
expect(msgs.every(v => v.wrap)).toBe(true);
});
it('it should handle two actors and two centered shared notes', function() {
renderer.bounds.init();
const str = `
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
@ -1088,7 +1082,6 @@ Note over Bob,Alice: Looks back
);
});
it('it should draw two actors and two messages', function() {
renderer.bounds.init();
const str = `
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
@ -1104,7 +1097,6 @@ Bob->Alice: Fine!`;
expect(bounds.stopy).toBe(0 + 2 * conf.messageMargin + conf.height);
});
it('it should draw two actors notes to the right', function() {
renderer.bounds.init();
const str = `
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
@ -1126,7 +1118,6 @@ Bob->Alice: Fine!`;
);
});
it('it should draw two actors notes to the left', function() {
renderer.bounds.init();
const str = `
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
@ -1146,7 +1137,6 @@ Bob->Alice: Fine!`;
);
});
it('it should draw two actors notes to the left with text wrapped (inline)', function() {
renderer.bounds.init();
const str = `
sequenceDiagram
Alice->>Bob:wrap: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can!
@ -1168,7 +1158,6 @@ Bob->>Alice: Fine!`;
);
});
it('it should draw two actors notes to the left with text wrapped (directive)', function() {
renderer.bounds.init();
const str = `
%%{init: { 'theme': 'dark' } }%%
sequenceDiagram
@ -1194,7 +1183,6 @@ Bob->>Alice: Fine!`;
);
});
it('it should draw two actors notes to the left with text wrapped and the init directive sets the theme to dark', function() {
renderer.bounds.init();
const str = `
%%{init:{'theme':'dark'}}%%
sequenceDiagram
@ -1202,6 +1190,7 @@ sequenceDiagram
Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can!
Note left of Alice: Bob thinks
Bob->>Alice: Fine!`;
parser.parse(str);
renderer.draw(str, 'tst');
@ -1218,17 +1207,16 @@ Bob->>Alice: Fine!`;
2 * conf.messageMargin + conf.height + conf.boxMargin + 10 + 2 * conf.noteMargin
);
});
it('it should draw two actors notes to the left with text wrapped and the init directive sets the theme to dark and fontFamily to Menlo, fontSize to 18, and fontWeight to 800', function() {
renderer.bounds.init();
it('it should draw two actors, notes to the left with text wrapped and the init directive sets the theme to dark and fontFamily to Menlo, fontSize to 18, and fontWeight to 800', function() {
const str = `
%%{init: { "theme": "dark" }}%%
%%{init: { "theme": "dark", 'config': { "fontFamily": "Menlo", "fontSize": 18, "fontWeight": 400, "wrapEnabled": true }}}%%
sequenceDiagram
%%{config: { "fontFamily": "Menlo", "fontSize": 18, "fontWeight": 400 } }%%
%%{wrap}%%
Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can!
Note left of Alice: Bob thinks
Bob->>Alice: Fine!`;
parser.parse(str);
// renderer.setConf(mermaidAPI.getConfig().sequence);
renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds();
@ -1237,9 +1225,9 @@ Bob->>Alice: Fine!`;
expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2);
expect(bounds.starty).toBe(0);
expect(mermaid.theme).toBe('dark');
expect(mermaid.fontFamily).toBe('Menlo');
expect(mermaid.fontSize).toBe(18);
expect(mermaid.fontWeight).toBe(400);
expect(mermaid.sequence.fontFamily).toBe('Menlo');
expect(mermaid.sequence.fontSize).toBe(18);
expect(mermaid.sequence.fontWeight).toBe(400);
expect(msgs.every(v => v.wrap)).toBe(true);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
@ -1248,13 +1236,13 @@ Bob->>Alice: Fine!`;
);
});
it('it should draw two loops', function() {
renderer.bounds.init();
const str = `
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
loop Cheers
Bob->Alice: Fine!
end`;
parser.parse(str);
renderer.draw(str, 'tst');
@ -1268,7 +1256,6 @@ end`;
);
});
it('it should draw background rect', function() {
renderer.bounds.init();
const str = `
sequenceDiagram
Alice->Bob: Hello Bob, are you alright?
@ -1290,6 +1277,7 @@ end`;
describe('when rendering a sequenceDiagram with actor mirror activated', function() {
let conf;
beforeEach(function() {
mermaidAPI.reset();
parser.yy = sequenceDb;
parser.yy.clear();
@ -1309,11 +1297,12 @@ describe('when rendering a sequenceDiagram with actor mirror activated', functio
// Prolongs the edge of the diagram downwards
bottomMarginAdj: 1
};
renderer.setConf(conf);
mermaidAPI.initialize({ sequence: conf });
renderer.bounds.init();
});
['tspan', 'fo', 'old', undefined].forEach(function(textPlacement) {
it('it should handle one actor, when textPlacement is' + textPlacement, function() {
renderer.setConf(addConf(conf, 'textPlacement', textPlacement));
mermaidAPI.initialize(addConf(conf, 'textPlacement', textPlacement));
renderer.bounds.init();
const str = `
sequenceDiagram
@ -1334,6 +1323,7 @@ participant Alice`;
describe('when rendering a sequenceDiagram with directives', function() {
let conf;
beforeEach(function() {
mermaidAPI.reset();
parser.yy = sequenceDb;
parser.yy.clear();
conf = {
@ -1348,11 +1338,11 @@ describe('when rendering a sequenceDiagram with directives', function() {
boxTextMargin: 15,
noteMargin: 25
};
renderer.setConf(conf);
mermaidAPI.initialize({ sequence: conf });
renderer.bounds.init();
});
it('it should handle one actor, when theme is dark and logLevel is 1', function() {
renderer.bounds.init();
it('it should handle one actor, when theme is dark and logLevel is 1 DX1', function() {
const str = `
%%{init: { "theme": "dark", "logLevel": 1 } }%%
sequenceDiagram
@ -1373,7 +1363,6 @@ participant Alice
expect(bounds.stopy).toBe(2 * conf.height + 2 * conf.boxMargin);
});
it('it should handle one actor, when logLevel is 3', function() {
renderer.bounds.init();
const str = `
%%{initialize: { "logLevel": 3 }}%%
sequenceDiagram

View File

@ -1,10 +1,10 @@
import { select, selectAll } from 'd3';
import svgDraw from './svgDraw';
import svgDraw, { drawText } from './svgDraw';
import { logger } from '../../logger';
import { parser } from './parser/sequenceDiagram';
import common from '../common/common';
import sequenceDb from './sequenceDb';
import { getConfig } from '../../config';
import utils, { assignWithDepth } from '../../utils';
parser.yy = sequenceDb;
@ -79,9 +79,6 @@ export const bounds = {
stopy: undefined
};
this.verticalPos = 0;
if (parser.yy.hasConfigChange()) {
setConf(getConfig());
}
},
updateVal: function(obj, key, val, fun) {
if (typeof obj[key] === 'undefined') {
@ -153,15 +150,20 @@ export const bounds = {
.lastIndexOf(message.from.actor);
return this.activations.splice(lastActorActivationIdx, 1)[0];
},
newLoop: function(title, fill) {
this.sequenceItems.push({
createLoop: function(title = { message: undefined, wrap: false, width: undefined }, fill) {
return {
startx: undefined,
starty: this.verticalPos,
stopx: undefined,
stopy: undefined,
title: title,
title: title.message,
wrap: title.wrap,
width: title.width,
fill: fill
});
};
},
newLoop: function(title = { message: undefined, wrap: false, width: undefined }, fill) {
this.sequenceItems.push(this.createLoop(title, fill));
},
endLoop: function() {
return this.sequenceItems.pop();
@ -186,59 +188,7 @@ export const bounds = {
}
};
const wrapLabel = (label, maxWidth, joinWith = '<br/>') => {
if (common.lineBreakRegex.test(label)) {
return label;
}
const words = label.split(' ');
const completedLines = [];
let nextLine = '';
words.forEach((word, index) => {
const wordLength = calculateTextWidth(`${word} `);
const nextLineLength = calculateTextWidth(nextLine);
if (wordLength > maxWidth) {
const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth);
completedLines.push(nextLine, ...hyphenatedStrings);
nextLine = remainingWord;
} else if (nextLineLength + wordLength >= maxWidth) {
completedLines.push(nextLine);
nextLine = word;
} else {
nextLine = [nextLine, word].filter(Boolean).join(' ');
}
const currentWord = index + 1;
const isLastWord = currentWord === words.length;
if (isLastWord) {
completedLines.push(nextLine);
}
});
return completedLines.filter(line => line !== '').join(joinWith);
};
const breakString = (word, maxWidth, hyphenCharacter = '-') => {
const characters = word.split('');
const lines = [];
let currentLine = '';
characters.forEach((character, index) => {
const nextLine = `${currentLine}${character}`;
const lineWidth = calculateTextWidth(nextLine);
if (lineWidth >= maxWidth) {
const currentCharacter = index + 1;
const isLastLine = characters.length === currentCharacter;
const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`;
lines.push(isLastLine ? nextLine : hyphenatedNextLine);
currentLine = '';
} else {
currentLine = nextLine;
}
});
return { hyphenatedStrings: lines, remainingWord: currentLine };
};
const _drawLongText = (text, x, y, g, width) => {
let textHeight = 0;
let prevTextHeight = 0;
const drawLongText = (text, x, y, g, width) => {
const alignmentToAnchor = {
left: 'start',
start: 'start',
@ -247,43 +197,45 @@ const _drawLongText = (text, x, y, g, width) => {
right: 'end',
end: 'end'
};
const lines = text.split(common.lineBreakRegex);
for (const line of lines) {
const textObj = svgDraw.getTextObj();
const alignment = alignmentToAnchor[conf.noteAlign] || 'middle';
switch (alignment) {
case 'start':
textObj.x = x + conf.noteMargin;
break;
case 'middle':
textObj.x = x + width / 2;
break;
case 'end':
textObj.x = x + width - conf.noteMargin;
break;
}
textObj.y = y + textHeight;
textObj.dy = '1em';
textObj.text = line;
textObj.class = 'noteText';
const textElem = svgDraw
.drawText(g, textObj)
.style('text-anchor', alignment)
.style('font-size', conf.noteFontSize)
.style('font-family', conf.noteFontFamily)
.style('font-weight', conf.noteFontWeight)
.attr('dominant-baseline', 'central')
.attr('alignment-baseline', 'central');
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
textElem.attr('y', y + (prevTextHeight + textHeight + 2 * conf.noteMargin) / 2);
prevTextHeight = textHeight;
const alignment = alignmentToAnchor[conf.noteAlign] || 'middle';
const textObj = svgDraw.getTextObj();
switch (alignment) {
case 'start':
textObj.x = x + conf.noteMargin;
break;
case 'middle':
textObj.x = x + width / 2;
break;
case 'end':
textObj.x = x + width - conf.noteMargin;
break;
}
return textHeight;
textObj.y = y;
textObj.dy = '1em';
textObj.text = text;
textObj.class = 'noteText';
textObj.fontFamily = conf.noteFontFamily;
textObj.fontSize = conf.noteFontSize;
textObj.fontWeight = conf.noteFontWeight;
textObj.anchor = alignment;
textObj.textMargin = conf.noteMargin;
textObj.valign = alignment;
textObj.wrap = true;
let textElem = drawText(g, textObj);
if (!Array.isArray(textElem)) {
textElem = [textElem];
}
textElem.forEach(te => {
te.attr('dominant-baseline', 'central').attr('alignment-baseline', 'central');
});
return textElem
.map(te => (te._groups || te)[0][0].getBBox().height)
.reduce((acc, curr) => acc + curr);
};
/**
@ -304,7 +256,7 @@ const drawNote = function(elem, startx, verticalPos, msg, forceWidth) {
let g = elem.append('g');
const rectElem = svgDraw.drawRect(g, rect);
const textHeight = _drawLongText(msg.message, startx, verticalPos, g, rect.width);
const textHeight = drawLongText(msg.message, startx, verticalPos, g, rect.width);
bounds.insert(
startx,
@ -331,36 +283,15 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
const txtCenter = startx + (stopx - startx) / 2;
let textElems = [];
/*
let textHeight = 0;
const breaklines = msg.message.split(common.lineBreakRegex);
for (const breakline of breaklines) {
let textElem = g
.append('text') // text label for the x axis
.attr('x', txtCenter)
.attr('y', verticalPos + textHeight)
.style('font-size', conf.messageFontSize)
.style('font-family', conf.messageFontFamily)
.style('font-weight', conf.messageFontWeight)
.style('text-anchor', 'middle')
.attr('class', 'messageText')
.text(breakline.trim());
textElems.push(textElem);
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
}
let totalOffset = textHeight;
*/
let counterBreaklines = 0;
let breaklineOffset = conf.messageFontSize + 4;
let breaklineOffset = conf.messageFontSize;
const breaklines = msg.message.split(common.lineBreakRegex);
for (const breakline of breaklines) {
textElems.push(
g
.append('text') // text label for the x axis
.attr('x', txtCenter)
// .attr('y', verticalPos - breaklineVerticalOffset + counterBreaklines * breaklineOffset)
.attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset)
.style('font-size', conf.messageFontSize)
.style('font-family', conf.messageFontFamily)
@ -500,32 +431,18 @@ export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
// Draw the actors
let prevWidth = 0;
let prevMargin = 0;
let maxActorHeight = conf.height;
for (let i = 0; i < actorKeys.length; i++) {
const actor = actors[actorKeys[i]];
// Add some rendering data to the object
actor.width = actor.width || calculateActorWidth(actor);
actor.height = actor.wrap
? calculateTextHeight(
actor.message,
conf.height,
actor.width,
conf.wrapPadding,
actor.wrap,
conf.actorFontSize
)
: conf.height;
maxActorHeight = Math.max(maxActorHeight, actor.height);
actor.margin = actor.margin || conf.actorMargin;
actor.width = typeof actor.width === 'undefined' ? calculateActorWidth(actor) : actor.width;
actor.height = conf.height;
actor.margin = conf.actorMargin;
actor.x = prevWidth + prevMargin;
actor.y = verticalPos;
if (actor.wrap) {
actor.description = wrapLabel(actor.description, actor.width);
}
// Draw the box with the attached line
svgDraw.drawActor(diagram, actor, conf);
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
@ -535,22 +452,17 @@ export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
}
// Add a margin between the actor boxes and the first arrow
bounds.bumpVerticalPos(maxActorHeight);
bounds.bumpVerticalPos(conf.height);
};
export const setConf = function(cnf) {
const keys = Object.keys(cnf);
keys.forEach(function(key) {
conf[key] = cnf[key];
});
assignWithDepth(conf, cnf);
if (cnf.fontFamily) {
conf.actorFontFamily = conf.noteFontFamily = conf.messageFontFamily = cnf.fontFamily;
}
if (cnf.fontSize) {
conf.actorFontSize = conf.noteFontSize = conf.messageFontSize = cnf.fontSize;
// conf.height = cnf.fontSize * (65 / 14);
}
if (cnf.fontWeight) {
conf.actorFontWeight = conf.noteFontWeight = conf.messageFontWeight = cnf.fontWeight;
@ -596,100 +508,41 @@ const calculateActorWidth = function(actor) {
? conf.width
: Math.max(
conf.width,
calculateTextWidth(
actor.description,
conf.actorFontSize,
conf.actorFontFamily,
conf.actorFontWeight
)
utils.calculateTextWidth(actor.description, {
fontSize: conf.actorFontSize,
fontFamily: conf.actorFontFamily,
fontWeight: conf.actorFontWeight,
margin: conf.wrapPadding
})
);
};
/**
* This calculates the text's height, taking into account the wrap value and
* both the statically configured height, width, and the length of the text (in pixels).
*
* If the wrapped text text has greater height, we extend the height, so it's
* value won't overflow.
*
* @return - The height for the given actor
* @param message the text to measure
* @param elementHeight the height of the default bounding box containing the text
* @param elementWidth the width of the default bounding box containing the text
* @param margin space above and below
* @param wrap wrap the text based on: elementWidth - 2 * margin
* @param fontSize
*/
export const calculateTextHeight = function(
message,
elementHeight,
elementWidth,
margin,
wrap,
fontSize
) {
if (!message) {
return elementHeight;
function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoopFn) {
let heightAdjust = 0;
bounds.bumpVerticalPos(preMargin);
if (msg.message && msg.wrap && loopWidths[msg.message]) {
let loopWidth = loopWidths[msg.message].width;
let minSize =
Math.round((3 * conf.fontSize) / 4) < 10
? conf.fontSize
: Math.round((3 * conf.fontSize) / 4);
let textConf = {
fontSize: minSize,
fontFamily: conf.messageFontFamily,
fontWeight: conf.messageFontWeight,
margin: conf.wrapPadding
};
msg.message = msg.message
? utils.wrapLabel(`[${msg.message}]`, loopWidth, textConf)
: msg.message;
heightAdjust = Math.max(
0,
utils.calculateTextHeight(msg.message, textConf) - (preMargin + postMargin)
);
}
let lineHeightFactor = wrap
? wrapLabel(message, elementWidth - 2 * margin).split(common.lineBreakRegex).length
: 1;
return wrap ? Math.max(elementHeight, lineHeightFactor * fontSize) : elementHeight;
};
/**
* This calculates the width of the given text, font size and family.
*
* @param text - The text to calculate the width of
* @param fontSize - The font size of the given text
* @param fontFamily - The font family (one, or more fonts) to render
* @param fontWeight - The font weight (normal, bold, italics)
*/
export const calculateTextWidth = function(text, fontSize, fontFamily, fontWeight) {
if (!text) {
return 0;
}
fontSize = fontSize ? fontSize : conf.actorFontSize;
fontFamily = fontFamily ? fontFamily : conf.actorFontFamily;
fontWeight = fontWeight ? fontWeight : conf.actorFontWeight;
// We can't really know if the user supplied font family will render on the user agent;
// thus, we'll take the max width between the user supplied font family, and a default
// of sans-serif.
const fontFamilies = ['sans-serif', fontFamily];
const lines = text.split(common.lineBreakRegex);
let maxWidth = 0;
const body = select('body');
// We don't want to leak DOM elements - if a removal operation isn't available
// for any reason, do not continue.
if (!body.remove) {
return 0;
}
const g = body.append('svg');
for (let line of lines) {
for (let fontFamily of fontFamilies) {
const textObj = svgDraw.getTextObj();
textObj.text = line;
const textElem = svgDraw
.drawText(g, textObj)
.style('font-size', fontSize)
.style('font-weight', fontWeight)
.style('font-family', fontFamily);
maxWidth = Math.max(maxWidth, (textElem._groups || textElem)[0][0].getBBox().width);
}
}
g.remove();
// Adds some padding, so the text won't sit exactly within the actor's borders
return maxWidth + conf.wrapPadding * 2;
};
addLoopFn(msg);
bounds.bumpVerticalPos(heightAdjust + postMargin);
}
/**
* Draws a flowchart in the tag with id: id based on the graph definition in text.
@ -698,6 +551,7 @@ export const calculateTextWidth = function(text, fontSize, fontFamily, fontWeigh
*/
export const draw = function(text, id) {
parser.yy.clear();
parser.yy.setWrap(conf.wrapEnabled);
parser.parse(text + '\n');
bounds.init();
@ -714,15 +568,10 @@ export const draw = function(text, id) {
const title = parser.yy.getTitle();
const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages);
const maxActorHeight = calculateActorMargins(actors, maxMessageWidthPerActor);
conf.height = calculateActorMargins(actors, maxMessageWidthPerActor);
drawActors(diagram, actors, actorKeys, 0);
bounds.bumpVerticalPos(
maxActorHeight > conf.height
? Math.min(conf.boxMargin, Math.abs(maxActorHeight - conf.height))
: 0
);
const loopWidths = calculateLoopMargins(messages, actors);
// The arrow head definition is attached to the svg once
svgDraw.insertArrowHead(diagram);
@ -752,6 +601,7 @@ export const draw = function(text, id) {
let loopData,
noteWidth,
textWidth,
textConf,
shouldWrap = msg.wrap && msg.message && !common.lineBreakRegex.test(msg.message);
switch (msg.type) {
@ -760,17 +610,18 @@ export const draw = function(text, id) {
startx = actors[msg.from].x;
stopx = actors[msg.to].x;
textWidth = calculateTextWidth(
msg.message,
conf.noteFontSize,
conf.noteFontFamily,
conf.noteFontWeight
);
textConf = {
fontSize: conf.noteFontSize,
fontFamily: conf.noteFontFamily,
fontWeight: conf.noteFontWeight,
margin: conf.wrapPadding
};
textWidth = utils.calculateTextWidth(msg.message, textConf);
noteWidth = shouldWrap ? conf.width : Math.max(conf.width, textWidth);
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) {
if (shouldWrap) {
msg.message = wrapLabel(msg.message, noteWidth);
msg.message = utils.wrapLabel(msg.message, noteWidth, textConf);
}
drawNote(
diagram,
@ -781,7 +632,7 @@ export const draw = function(text, id) {
);
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
if (shouldWrap) {
msg.message = wrapLabel(msg.message, noteWidth);
msg.message = utils.wrapLabel(msg.message, noteWidth, textConf);
}
drawNote(
diagram,
@ -793,7 +644,7 @@ export const draw = function(text, id) {
} else if (msg.to === msg.from) {
// Single-actor over
if (shouldWrap) {
msg.message = wrapLabel(msg.message, noteWidth);
msg.message = utils.wrapLabel(msg.message, noteWidth, textConf);
}
drawNote(
diagram,
@ -807,7 +658,7 @@ export const draw = function(text, id) {
forceWidth = Math.abs(startx - stopx) + conf.actorMargin / 2;
if (shouldWrap) {
noteWidth = forceWidth;
msg.message = wrapLabel(msg.message, noteWidth);
msg.message = utils.wrapLabel(msg.message, noteWidth, textConf);
} else {
noteWidth = Math.max(forceWidth, textWidth - 2 * conf.noteMargin);
}
@ -826,13 +677,16 @@ export const draw = function(text, id) {
activeEnd(msg, bounds.getVerticalPos());
break;
case parser.yy.LINETYPE.LOOP_START:
bounds.bumpVerticalPos(conf.boxMargin);
bounds.newLoop(msg.message);
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
adjustLoopHeightForWrap(
loopWidths,
msg,
conf.boxMargin,
conf.boxMargin + conf.boxTextMargin,
message => bounds.newLoop(message)
);
break;
case parser.yy.LINETYPE.LOOP_END:
loopData = bounds.endLoop();
svgDraw.drawLoop(diagram, loopData, 'loop', conf);
bounds.bumpVerticalPos(conf.boxMargin);
break;
@ -841,51 +695,56 @@ export const draw = function(text, id) {
bounds.newLoop(undefined, msg.message);
bounds.bumpVerticalPos(conf.boxMargin);
break;
case parser.yy.LINETYPE.RECT_END: {
const rectData = bounds.endLoop();
svgDraw.drawBackgroundRect(diagram, rectData);
case parser.yy.LINETYPE.RECT_END:
svgDraw.drawBackgroundRect(diagram, bounds.endLoop());
bounds.bumpVerticalPos(conf.boxMargin);
break;
}
case parser.yy.LINETYPE.OPT_START:
bounds.bumpVerticalPos(conf.boxMargin);
bounds.newLoop(msg.message);
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
adjustLoopHeightForWrap(
loopWidths,
msg,
conf.boxMargin,
conf.boxMargin + conf.boxTextMargin,
message => bounds.newLoop(message)
);
break;
case parser.yy.LINETYPE.OPT_END:
loopData = bounds.endLoop();
svgDraw.drawLoop(diagram, loopData, 'opt', conf);
bounds.bumpVerticalPos(conf.boxMargin);
break;
case parser.yy.LINETYPE.ALT_START:
bounds.bumpVerticalPos(conf.boxMargin);
bounds.newLoop(msg.message);
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
adjustLoopHeightForWrap(
loopWidths,
msg,
conf.boxMargin,
conf.boxMargin + conf.boxTextMargin,
message => bounds.newLoop(message)
);
break;
case parser.yy.LINETYPE.ALT_ELSE:
bounds.bumpVerticalPos(conf.boxMargin);
loopData = bounds.addSectionToLoop(msg.message);
bounds.bumpVerticalPos(conf.boxMargin);
adjustLoopHeightForWrap(loopWidths, msg, conf.boxMargin, conf.boxMargin, message =>
bounds.addSectionToLoop(message)
);
break;
case parser.yy.LINETYPE.ALT_END:
loopData = bounds.endLoop();
svgDraw.drawLoop(diagram, loopData, 'alt', conf);
bounds.bumpVerticalPos(conf.boxMargin);
break;
case parser.yy.LINETYPE.PAR_START:
bounds.bumpVerticalPos(conf.boxMargin);
if (shouldWrap) {
msg.message = wrapLabel(msg.message, conf.boxMargin);
}
bounds.newLoop(msg.message);
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
adjustLoopHeightForWrap(
loopWidths,
msg,
conf.boxMargin,
conf.boxMargin + conf.boxTextMargin,
message => bounds.newLoop(message)
);
break;
case parser.yy.LINETYPE.PAR_AND:
bounds.bumpVerticalPos(conf.boxMargin);
loopData = bounds.addSectionToLoop(msg.message);
bounds.bumpVerticalPos(conf.boxMargin);
adjustLoopHeightForWrap(loopWidths, msg, conf.boxMargin, conf.boxMargin, message =>
bounds.addSectionToLoop(message)
);
break;
case parser.yy.LINETYPE.PAR_END:
loopData = bounds.endLoop();
@ -902,13 +761,20 @@ export const draw = function(text, id) {
const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1;
startx = fromBounds[fromIdx];
stopx = toBounds[toIdx];
textConf = {
fontSize: conf.messageFontSize,
fontFamily: conf.messageFontFamily,
fontWeight: conf.messageFontWeight,
margin: conf.wrapPadding
};
if (shouldWrap) {
msg.message = wrapLabel(
msg.message = utils.wrapLabel(
msg.message,
Math.max(
Math.abs(stopx - startx) + conf.messageMargin * 2,
conf.width + conf.messageMargin * 2
)
),
textConf
);
}
@ -972,6 +838,7 @@ export const draw = function(text, id) {
diagram.attr('height', '100%');
diagram.attr('width', '100%');
diagram.attr('style', 'max-width:' + width + 'px;');
// diagram.attr('style', 'max-width:100%;');
} else {
diagram.attr('height', height);
diagram.attr('width', width);
@ -1023,12 +890,17 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
const fontSize = isNote ? conf.noteFontSize : conf.messageFontSize;
const fontFamily = isNote ? conf.noteFontFamily : conf.messageFontFamily;
const fontWeight = isNote ? conf.noteFontWeight : conf.messageFontWeight;
const messageWidth = calculateTextWidth(
msg.wrap ? wrapLabel(msg.message, conf.width - conf.noteMargin) : msg.message,
const textConf = { fontFamily, fontSize, fontWeight, margin: conf.wrapPadding };
let wrappedMessage = msg.wrap
? utils.wrapLabel(msg.message, conf.width - conf.noteMargin, textConf)
: msg.message;
const messageDimensions = utils.calculateTextDimensions(wrappedMessage, {
fontSize,
fontFamily,
fontWeight
);
fontWeight,
margin: conf.wrapPadding
});
const messageWidth = messageDimensions.width;
/*
* The following scenarios should be supported:
@ -1104,6 +976,13 @@ const calculateActorMargins = function(actors, actorToMessageWidth) {
continue;
}
const textConf = {
fontSize: conf.actorFontSize,
fontFamily: conf.actorFontFamily,
fontWeight: conf.actorFontWeight,
margin: conf.wrapPadding
};
const nextActor = actors[actor.nextActor];
// No need to space out an actor that doesn't have a next link
@ -1112,28 +991,14 @@ const calculateActorMargins = function(actors, actorToMessageWidth) {
}
[actor, nextActor].forEach(function(act) {
act.width = act.wrap
? conf.width
: Math.max(
conf.width,
calculateTextWidth(
act.description,
conf.actorFontSize,
conf.actorFontFamily,
conf.actorFontWeight
)
);
if (act.wrap) {
act.description = utils.wrapLabel(act.description, conf.width, textConf);
}
const actDims = utils.calculateTextDimensions(act.description, textConf);
act.width = act.wrap ? conf.width : Math.max(conf.width, actDims.width);
act.height = act.wrap
? calculateTextHeight(
act.description,
conf.height,
actor.width,
conf.actorMargin,
act.wrap,
conf.actorFontSize
)
: conf.height;
act.height = act.wrap ? Math.max(actDims.height, conf.height) : conf.height;
logger.debug(`Actor h:${act.height} ${actDims.height} d:${act.description}`);
maxHeight = Math.max(maxHeight, act.height);
});
@ -1142,11 +1007,62 @@ const calculateActorMargins = function(actors, actorToMessageWidth) {
actor.margin = Math.max(actorWidth, conf.actorMargin);
}
Object.keys(actors).forEach(function(key) {
actors[key].height = maxHeight;
});
return maxHeight;
return Math.max(maxHeight, conf.height);
};
const calculateLoopMargins = function(messages, actors) {
const loops = {};
const stack = [];
let current;
messages.forEach(function(msg) {
switch (msg.type) {
case parser.yy.LINETYPE.LOOP_START:
case parser.yy.LINETYPE.ALT_START:
case parser.yy.LINETYPE.OPT_START:
case parser.yy.LINETYPE.PAR_START:
stack.push({
msg: msg.message,
from: Number.MAX_SAFE_INTEGER,
to: Number.MIN_SAFE_INTEGER,
width: 0
});
break;
case parser.yy.LINETYPE.ALT_ELSE:
case parser.yy.LINETYPE.PAR_AND:
if (msg.message) {
current = stack.pop();
loops[msg.message] = current;
stack.push(current);
}
break;
case parser.yy.LINETYPE.LOOP_END:
case parser.yy.LINETYPE.ALT_END:
case parser.yy.LINETYPE.OPT_END:
case parser.yy.LINETYPE.PAR_END:
current = stack.pop();
loops[current.msg] = current;
break;
}
if (msg.from && msg.to && stack.length > 0) {
stack.forEach(stk => {
current = stk;
let from = actors[msg.from];
let to = actors[msg.to];
if (from.x < to.x) {
current.from = Math.min(current.from, from.x);
current.to = Math.max(current.to, to.x);
} else {
current.from = Math.min(current.from, to.x);
current.to = Math.max(current.to, from.x);
}
current.width = Math.abs(current.from - current.to) - 20 + 2 * conf.wrapPadding;
});
}
});
logger.debug('LoopWidths:', { loops, actors });
return loops;
};
export default {

View File

@ -19,27 +19,95 @@ export const drawRect = function(elem, rectData) {
};
export const drawText = function(elem, textData) {
// Remove and ignore br:s
const nText = textData.text.replace(common.lineBreakRegex, ' ');
let prevTextHeight = 0,
textHeight = 0;
const lines = textData.wrap
? textData.text.split(common.lineBreakRegex)
: [textData.text.replace(common.lineBreakRegex, ' ')];
const textElem = elem.append('text');
textElem.attr('x', textData.x);
textElem.attr('y', textData.y);
textElem.style('text-anchor', textData.anchor);
textElem.style('font-family', textData.fontFamily);
textElem.style('font-size', textData.fontSize);
textElem.style('font-weight', textData.fontWeight);
textElem.attr('fill', textData.fill);
if (typeof textData.class !== 'undefined') {
textElem.attr('class', textData.class);
let textElems = [];
let dy = 0;
let yfunc = () => textData.y;
if (
typeof textData.valign !== 'undefined' &&
typeof textData.textMargin !== 'undefined' &&
textData.textMargin > 0
) {
switch (textData.valign) {
case 'top':
case 'start':
yfunc = () => textData.y + textData.textMargin;
break;
case 'middle':
case 'center':
yfunc = () => textData.y + (prevTextHeight + textHeight + textData.textMargin) / 2;
break;
case 'bottom':
case 'end':
yfunc = () =>
textData.y +
(prevTextHeight + textHeight + 2 * textData.textMargin) -
textData.textMargin;
break;
}
}
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
if (
typeof textData.textMargin !== 'undefined' &&
textData.textMargin === 0 &&
typeof textData.fontSize !== 'undefined'
) {
dy = i * textData.fontSize;
}
const textElem = elem.append('text');
textElem.attr('x', textData.x);
textElem.attr('y', yfunc());
if (typeof textData.anchor !== 'undefined') {
textElem.style('text-anchor', textData.anchor);
}
if (typeof textData.fontFamily !== 'undefined') {
textElem.style('font-family', textData.fontFamily);
}
if (typeof textData.fontSize !== 'undefined') {
textElem.style('font-size', textData.fontSize);
}
if (typeof textData.fontWeight !== 'undefined') {
textElem.style('font-weight', textData.fontWeight);
}
if (typeof textData.fill !== 'undefined') {
textElem.attr('fill', textData.fill);
}
if (typeof textData.class !== 'undefined') {
textElem.attr('class', textData.class);
}
if (typeof textData.dy !== 'undefined') {
textElem.attr('dy', textData.dy);
} else if (dy !== 0) {
textElem.attr('dy', dy);
}
const span = textElem.append('tspan');
span.attr('x', textData.x);
if (typeof textData.fill !== 'undefined') {
span.attr('fill', textData.fill);
}
span.text(line);
if (
typeof textData.valign !== 'undefined' &&
typeof textData.textMargin !== 'undefined' &&
textData.textMargin > 0
) {
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
prevTextHeight = textHeight;
}
textElems.push(textElem);
}
const span = textElem.append('tspan');
span.attr('x', textData.x + textData.textMargin * 2);
span.attr('fill', textData.fill);
span.text(nText);
return textElem;
return textElems.length === 1 ? textElems[0] : textElems;
};
export const drawLabel = function(elem, txtObject) {
@ -72,7 +140,7 @@ export const drawLabel = function(elem, txtObject) {
txtObject.y = txtObject.y + txtObject.labelMargin;
txtObject.x = txtObject.x + 0.5 * txtObject.labelMargin;
drawText(elem, txtObject);
return drawText(elem, txtObject);
};
let actorCnt = -1;
@ -80,7 +148,7 @@ let actorCnt = -1;
* Draws an actor in the diagram with the attaced line
* @param elem - The diagram we'll draw to.
* @param actor - The actor to draw.
* @param conf - drawText implementation discriminator object
* @param conf - utils.drawText implementation discriminator object
*/
export const drawActor = function(elem, actor, conf) {
const center = actor.x + actor.width / 2;
@ -185,33 +253,39 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
txt.fontWeight = conf.fontWeight;
txt.class = 'labelText'; // Its size & position are fixed.
drawLabel(g, txt);
let labelElem = drawLabel(g, txt);
let labelBoxWidth = (labelElem._groups || labelElem)[0][0].getBBox().width;
txt = getTextObj();
txt.text = '[ ' + bounds.title + ' ]';
txt.x = bounds.startx + (bounds.stopx - bounds.startx) / 2;
txt.y = bounds.starty + 1.5 * conf.boxMargin;
txt.text = bounds.title;
txt.x = bounds.startx + (bounds.stopx - bounds.startx) / 2 + labelBoxWidth;
txt.y = bounds.starty + conf.boxMargin + conf.boxTextMargin;
txt.anchor = 'middle';
txt.class = 'loopText';
txt.fontFamily = conf.fontFamily;
txt.fontSize = minSize;
txt.fontWeight = conf.fontWeight;
txt.wrap = bounds.wrap;
let textElem = drawText(g, txt);
let textHeight = (textElem._groups || textElem)[0][0].getBBox().height;
drawText(g, txt);
if (typeof bounds.sectionTitles !== 'undefined') {
bounds.sectionTitles.forEach(function(item, idx) {
if (item !== '') {
txt.text = '[ ' + item + ' ]';
if (item.message) {
txt.text = item.message;
txt.x = bounds.startx + (bounds.stopx - bounds.startx) / 2;
txt.y = bounds.sections[idx] + 1.5 * conf.boxMargin;
textElem = drawText(g, txt);
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
txt.class = 'loopText';
txt.anchor = 'middle';
txt.fontFamily = conf.fontFamily;
txt.fontSize = minSize;
txt.fontWeight = conf.fontWeight;
txt.wrap = bounds.wrap;
drawText(g, txt);
}
});
}
return textHeight + 4;
return g;
};
/**
@ -300,23 +374,23 @@ export const insertArrowCrossHead = function(elem) {
};
export const getTextObj = function() {
const txt = {
return {
x: 0,
y: 0,
fill: undefined,
'text-anchor': 'start',
anchor: 'start',
style: '#666',
width: 100,
height: 100,
textMargin: 0,
rx: 0,
ry: 0
ry: 0,
valign: undefined
};
return txt;
};
export const getNoteRect = function() {
const rect = {
return {
x: 0,
y: 0,
fill: '#EDF2AE',
@ -327,7 +401,6 @@ export const getNoteRect = function() {
rx: 0,
ry: 0
};
return rect;
};
const _drawTextCandidateFunc = (function() {

View File

@ -6,6 +6,8 @@
import decode from 'entity-decode/browser';
import mermaidAPI from './mermaidAPI';
import { logger } from './logger';
import utils from './utils';
/**
* ## init
* Function that goes through the document to find the chart definitions in there and render them.
@ -29,7 +31,7 @@ import { logger } from './logger';
*/
const init = function() {
const conf = mermaidAPI.getConfig();
logger.debug('Starting rendering diagrams');
// console.log('Starting rendering diagrams (init) - mermaid.init');
let nodes;
if (arguments.length >= 2) {
/*! sequence config was passed as #1 */
@ -69,11 +71,11 @@ const init = function() {
logger.debug('Start On Load before: ' + mermaid.startOnLoad);
if (typeof mermaid.startOnLoad !== 'undefined') {
logger.debug('Start On Load inner: ' + mermaid.startOnLoad);
mermaidAPI.initialize({ startOnLoad: mermaid.startOnLoad });
mermaidAPI.setConfig({ startOnLoad: mermaid.startOnLoad });
}
if (typeof mermaid.ganttConfig !== 'undefined') {
mermaidAPI.initialize({ gantt: mermaid.ganttConfig });
mermaidAPI.setConfig({ gantt: mermaid.ganttConfig });
}
let txt;
@ -98,6 +100,11 @@ const init = function() {
.trim()
.replace(/<br\s*\/?>/gi, '<br/>');
const init = utils.detectInit(txt);
if (init) {
logger.debug('Detected early reinit: ', init);
}
try {
mermaidAPI.render(
id,
@ -122,6 +129,9 @@ const init = function() {
};
const initialize = function(config) {
mermaidAPI.reset();
// console.log('mermaid.initialize1', config);
if (typeof config.mermaid !== 'undefined') {
if (typeof config.mermaid.startOnLoad !== 'undefined') {
mermaid.startOnLoad = config.mermaid.startOnLoad;
@ -130,8 +140,9 @@ const initialize = function(config) {
mermaid.htmlLabels = config.mermaid.htmlLabels;
}
}
// console.log('Initializing mermaid 2', config);
mermaidAPI.initialize(config);
logger.debug('Initializing mermaid ');
// logger.debug('Initializing mermaid 3', config);
};
/**

View File

@ -15,7 +15,7 @@ import scope from 'scope-css';
import pkg from '../package.json';
import { setConfig, getConfig } from './config';
import { logger, setLogLevel } from './logger';
import utils from './utils';
import utils, { assignWithDepth } from './utils';
import flowRenderer from './diagrams/flowchart/flowRenderer';
import flowRendererV2 from './diagrams/flowchart/flowRenderer-v2';
import flowParser from './diagrams/flowchart/parser/flow';
@ -49,6 +49,7 @@ import erRenderer from './diagrams/er/erRenderer';
import journeyParser from './diagrams/user-journey/parser/journey';
import journeyDb from './diagrams/user-journey/journeyDb';
import journeyRenderer from './diagrams/user-journey/journeyRenderer';
import configApi from './config';
const themes = {};
for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
@ -556,14 +557,17 @@ const config = {
fontSize: 12
}
};
export const defaultConfig = Object.freeze(assignWithDepth({}, config));
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
config.git.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
setLogLevel(config.logLevel);
setConfig(config);
configApi.reset(config);
function parse(text) {
const graphInit = utils.detectInit(text);
if (graphInit) {
reinitialize(graphInit);
initialize(graphInit);
logger.debug('Init ', graphInit);
}
const graphType = utils.detectType(text);
@ -633,6 +637,7 @@ function parse(text) {
};
parser.parse(text);
return parser;
}
export const encodeEntities = function(text) {
@ -692,18 +697,24 @@ export const decodeEntities = function(text) {
* });
*```
* @param id the id of the element to be rendered
* @param txt the graph definition
* @param _txt the graph definition
* @param cb callback which is called after rendering is finished with the svg code as inparam.
* @param container selector to element in which a div with the graph temporarily will be inserted. In one is
* provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is
* completed.
*/
const render = function(id, _txt, cb, container) {
const cnf = getConfig();
// Check the maximum allowed text size
let txt = _txt;
if (_txt.length > config.maxTextSize) {
if (_txt.length > cnf.maxTextSize) {
txt = 'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa';
}
const graphInit = utils.detectInit(txt);
if (graphInit) {
initialize(graphInit);
assignWithDepth(cnf, getConfig());
}
if (typeof container !== 'undefined') {
container.innerHTML = '';
@ -711,7 +722,7 @@ const render = function(id, _txt, cb, container) {
select(container)
.append('div')
.attr('id', 'd' + id)
.attr('style', 'font-family: ' + config.fontFamily)
.attr('style', 'font-family: ' + cnf.fontFamily)
.append('svg')
.attr('id', id)
.attr('width', '100%')
@ -741,10 +752,6 @@ const render = function(id, _txt, cb, container) {
txt = encodeEntities(txt);
const element = select('#d' + id).node();
const graphInit = utils.detectInit(txt);
if (graphInit) {
reinitialize(graphInit);
}
const graphType = utils.detectType(txt);
// insert inline style into svg
@ -752,22 +759,22 @@ const render = function(id, _txt, cb, container) {
const firstChild = svg.firstChild;
// pre-defined theme
let style = themes[config.theme];
let style = themes[cnf.theme];
if (style === undefined) {
style = '';
}
// user provided theme CSS
if (config.themeCSS !== undefined) {
style += `\n${config.themeCSS}`;
if (cnf.themeCSS !== undefined) {
style += `\n${cnf.themeCSS}`;
}
// user provided theme CSS
if (config.fontFamily !== undefined) {
style += `\n:root { --mermaid-font-family: ${config.fontFamily}}`;
if (cnf.fontFamily !== undefined) {
style += `\n:root { --mermaid-font-family: ${cnf.fontFamily}}`;
}
// user provided theme CSS
if (config.altFontFamily !== undefined) {
style += `\n:root { --mermaid-alt-font-family: ${config.altFontFamily}}`;
if (cnf.altFontFamily !== undefined) {
style += `\n:root { --mermaid-alt-font-family: ${cnf.altFontFamily}}`;
}
// classDef
@ -800,74 +807,74 @@ const render = function(id, _txt, cb, container) {
try {
switch (graphType) {
case 'git':
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
gitGraphRenderer.setConf(config.git);
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
gitGraphRenderer.setConf(cnf.git);
gitGraphRenderer.draw(txt, id, false);
break;
case 'flowchart':
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
flowRenderer.setConf(config.flowchart);
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
flowRenderer.setConf(cnf.flowchart);
flowRenderer.draw(txt, id, false);
break;
case 'flowchart-v2':
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
flowRendererV2.setConf(config.flowchart);
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
flowRendererV2.setConf(cnf.flowchart);
flowRendererV2.draw(txt, id, false);
break;
case 'sequence':
config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
if (config.sequenceDiagram) {
cnf.sequence.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
if (cnf.sequenceDiagram) {
// backwards compatibility
sequenceRenderer.setConf(Object.assign(config.sequence, config.sequenceDiagram));
sequenceRenderer.setConf(Object.assign(cnf.sequence, cnf.sequenceDiagram));
console.error(
'`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.'
);
} else {
sequenceRenderer.setConf(config.sequence);
sequenceRenderer.setConf(cnf.sequence);
}
sequenceRenderer.draw(txt, id);
break;
case 'gantt':
config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
ganttRenderer.setConf(config.gantt);
cnf.gantt.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
ganttRenderer.setConf(cnf.gantt);
ganttRenderer.draw(txt, id);
break;
case 'class':
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
classRenderer.setConf(config.class);
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
classRenderer.setConf(cnf.class);
classRenderer.draw(txt, id);
break;
case 'state':
// config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
stateRenderer.setConf(config.state);
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
stateRenderer.setConf(cnf.state);
stateRenderer.draw(txt, id);
break;
case 'stateDiagram':
// config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
stateRendererV2.setConf(config.state);
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
stateRendererV2.setConf(cnf.state);
stateRendererV2.draw(txt, id);
break;
case 'info':
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
infoRenderer.setConf(config.class);
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
infoRenderer.setConf(cnf.class);
infoRenderer.draw(txt, id, pkg.version);
break;
case 'pie':
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
pieRenderer.setConf(config.class);
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
pieRenderer.setConf(cnf.class);
pieRenderer.draw(txt, id, pkg.version);
break;
case 'er':
erRenderer.setConf(config.er);
erRenderer.setConf(cnf.er);
erRenderer.draw(txt, id, pkg.version);
break;
case 'journey':
journeyRenderer.setConf(config.journey);
journeyRenderer.setConf(cnf.journey);
journeyRenderer.draw(txt, id, pkg.version);
break;
}
} catch (e) {
errorRenderer.setConf(config.class);
// errorRenderer.setConf(cnf.class);
errorRenderer.draw(id, pkg.version);
throw e;
}
@ -876,7 +883,7 @@ const render = function(id, _txt, cb, container) {
.selectAll('foreignobject > *')
.attr('xmlns', 'http://www.w3.org/1999/xhtml');
// if (config.arrowMarkerAbsolute) {
// if (cnf.arrowMarkerAbsolute) {
// url =
// window.location.protocol +
// '//' +
@ -889,8 +896,8 @@ const render = function(id, _txt, cb, container) {
// Fix for when the base tag is used
let svgCode = select('#d' + id).node().innerHTML;
if (!config.arrowMarkerAbsolute || config.arrowMarkerAbsolute === 'false') {
logger.debug('cnf.arrowMarkerAbsolute', cnf.arrowMarkerAbsolute);
if (!cnf.arrowMarkerAbsolute || cnf.arrowMarkerAbsolute === 'false') {
svgCode = svgCode.replace(/marker-end="url\(.*?#/g, 'marker-end="url(#', 'g');
}
@ -925,53 +932,36 @@ const render = function(id, _txt, cb, container) {
return svgCode;
};
const setConf = function(cnf) {
// Top level initially mermaid, gflow, sequenceDiagram and gantt
const lvl1Keys = Object.keys(cnf);
for (let i = 0; i < lvl1Keys.length; i++) {
if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) {
const lvl2Keys = Object.keys(cnf[lvl1Keys[i]]);
for (let j = 0; j < lvl2Keys.length; j++) {
logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j]);
if (typeof config[lvl1Keys[i]] === 'undefined') {
config[lvl1Keys[i]] = {};
}
logger.debug(
'Setting config: ' +
lvl1Keys[i] +
' ' +
lvl2Keys[j] +
' to ' +
cnf[lvl1Keys[i]][lvl2Keys[j]]
);
config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]];
}
} else {
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
}
function updateRendererConfigs(conf) {
gitGraphRenderer.setConf(conf.git);
flowRenderer.setConf(conf.flowchart);
flowRendererV2.setConf(conf.flowchart);
if (typeof conf['sequenceDiagram'] !== 'undefined') {
sequenceRenderer.setConf(assignWithDepth(conf.sequence, conf['sequenceDiagram']));
}
};
function reinitialize(options) {
if (typeof options === 'object') {
setConf(options);
}
setConfig(config);
setLogLevel(config.logLevel);
logger.debug('RE-Initializing mermaidAPI ', { version: pkg.version, options, config });
sequenceRenderer.setConf(conf.sequence);
ganttRenderer.setConf(conf.gantt);
classRenderer.setConf(conf.class);
stateRenderer.setConf(conf.state);
stateRendererV2.setConf(conf.state);
infoRenderer.setConf(conf.class);
pieRenderer.setConf(conf.class);
erRenderer.setConf(conf.er);
journeyRenderer.setConf(conf.journey);
errorRenderer.setConf(conf.class);
}
function initialize(options) {
let _config = config;
logger.debug('Initializing mermaidAPI ', { version: pkg.version, options, _config });
// Update default config with options supplied at initialization
console.log(`mermaidAPI.initialize: v${pkg.version}`);
// Set default options
if (typeof options === 'object') {
_config = Object.assign(_config, options);
setConf(_config);
assignWithDepth(config, options);
updateRendererConfigs(config);
}
setConfig(_config);
setLogLevel(_config.logLevel);
setConfig(config);
setLogLevel(config.logLevel);
logger.debug('mermaidAPI.initialize: ', config);
}
// function getConfig () {
@ -979,13 +969,20 @@ function initialize(options) {
// return config
// }
const mermaidAPI = {
const mermaidAPI = Object.freeze({
render,
parse,
initialize,
reinitialize,
getConfig
};
getConfig,
setConfig,
reset: () => {
// console.warn('reset');
configApi.reset(defaultConfig);
assignWithDepth(config, defaultConfig, { clobber: true });
updateRendererConfigs(config);
},
defaultConfig
});
export default mermaidAPI;
/**

View File

@ -1,10 +1,12 @@
/* eslint-env jasmine */
import mermaidAPI from './mermaidAPI';
import { assignWithDepth } from './utils';
describe('when using mermaidAPI and ', function() {
describe('doing initialize ', function() {
beforeEach(function() {
document.body.innerHTML = '';
mermaidAPI.reset();
});
it('should copy a literal into the configuration', function() {
@ -26,13 +28,64 @@ describe('when using mermaidAPI and ', function() {
};
mermaidAPI.initialize({ testObject: object });
let config = mermaidAPI.getConfig();
console.log('1:', config);
expect(config.testObject.test1).toBe(1);
mermaidAPI.initialize({ testObject: { test3: true } });
const config = mermaidAPI.getConfig();
config = mermaidAPI.getConfig();
console.log(config);
expect(config.testObject.test1).toBe(1);
expect(config.testObject.test2).toBe(false);
expect(config.testObject.test3).toBe(true);
});
it('should reset mermaid config to global defaults', function() {
let config = {
logLevel: 0
};
mermaidAPI.initialize(config);
expect(mermaidAPI.getConfig().logLevel).toBe(0);
mermaidAPI.reset();
expect(mermaidAPI.getConfig()).toEqual(mermaidAPI.defaultConfig);
});
it('should prevent clobbering global defaults (direct)', function() {
let config = assignWithDepth({}, mermaidAPI.defaultConfig);
assignWithDepth(config, { logLevel: 0 });
let error = { message: '' };
try {
mermaidAPI['defaultConfig'] = config;
} catch(e) {
error = e;
}
expect(error.message).toBe('Cannot assign to read only property \'defaultConfig\' of object \'#<Object>\'');
expect(mermaidAPI.defaultConfig['logLevel']).toBe(5);
});
it('should prevent changes to global defaults (direct)', function() {
let error = { message: '' };
try {
mermaidAPI.defaultConfig.logLevel = 0;
} catch(e) {
error = e;
}
expect(error.message).toBe('Cannot assign to read only property \'logLevel\' of object \'#<Object>\'');
expect(mermaidAPI.defaultConfig['logLevel']).toBe(5);
});
it('should prevent sneaky changes to global defaults (assignWithDepth)', function() {
let config = {
logLevel: 0
};
let error = { message: '' };
try {
assignWithDepth(mermaidAPI.defaultConfig, config);
} catch(e) {
error = e;
}
expect(error.message).toBe('Cannot assign to read only property \'logLevel\' of object \'#<Object>\'');
expect(mermaidAPI.defaultConfig['logLevel']).toBe(5);
});
});
describe('checking validity of input ', function() {
it('it should throw for an invalid definiton', function() {

View File

@ -1,10 +1,10 @@
$mainBkg: #1f2020;
$secondBkg: lighten(#1f2020, 16);
$mainContrastColor: lightgrey;
$secondBkg: lighten(#1f2020, 24);
$mainContrastColor: rgba(226, 226, 226, .75);
$darkTextColor: #323D47;
$lineColor: $mainContrastColor;
$border1: #81B1DB;
$border2: rgba(255, 255, 255, 0.25);
$lineColor: lighten($mainContrastColor, 24);
$border1: #81b1db;
$border2: darken(#81b1db, 32);
$arrowheadColor: $mainContrastColor;
/* Flowchart variables */
@ -31,10 +31,10 @@ $labelTextColor: $mainContrastColor;
$loopTextColor: $mainContrastColor;
$noteBorderColor: $border2;
$noteBkgColor: #fff5ad;
$noteTextColor: $mainBkg;
$noteTextColor: #1f2020;
$activationBorderColor: $border1;
$activationBkgColor: $secondBkg;
$sequenceNumberColor: white;
$sequenceNumberColor: $mainContrastColor;
/* Gantt chart variables */

View File

@ -18,7 +18,9 @@
}
/* Classes common for multiple diagrams */
body {
background-color: $mainBkg;
}
.error-icon {
fill: $errorBkgColor;
}

View File

@ -9,10 +9,12 @@ import {
curveNatural,
curveStep,
curveStepAfter,
curveStepBefore
curveStepBefore,
select
} from 'd3';
import { logger } from './logger';
import { sanitizeUrl } from '@braintree/sanitize-url';
import common from './diagrams/common/common';
// Effectively an enum of the supported curve types, accessible by name
const d3CurveTypes = {
@ -34,7 +36,7 @@ const anyComment = /\s*%%.*\n/gm;
/**
* @function detectInit
* Detects the init config object from the text
* Detects the init config object from the text and (re)initializes mermaid
* ```mermaid
* %%{init: {"theme": "debug", "logLevel": 1 }}%%
* graph LR
@ -60,17 +62,29 @@ const anyComment = /\s*%%.*\n/gm;
* ```
*
* @param {string} text The text defining the graph
* @returns {object} the json object representing the init to pass to mermaid.initialize()
* @returns {object} the json object representing the init passed to mermaid.initialize()
*/
export const detectInit = function(text) {
let inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/);
let results = {};
if (Array.isArray(inits)) {
let args = inits.map(init => init.args);
results = Object.assign(results, ...args);
results = assignWithDepth(results, [...args]);
} else {
results = inits.args;
}
if (results) {
let type = detectType(text);
['config'].forEach(prop => {
if (typeof results[prop] !== 'undefined') {
if (type === 'flowchart-v2') {
type = 'flowchart';
}
results[type] = results[prop];
delete results[prop];
}
});
}
return results;
};
@ -382,7 +396,257 @@ export const generateId = () => {
);
};
/**
* @function assignWithDepth
* Extends the functionality of {@link ObjectConstructor.assign} with the ability to merge arbitrary-depth objects
* For each key in src with path `k` (recursively) performs an Object.assign(dst[`k`], src[`k`]) with
* a slight change from the typical handling of undefined for dst[`k`]: instead of raising an error,
* dst[`k`] is auto-initialized to {} and effectively merged with src[`k`]
* <p>
* Additionally, dissimilar types will not clobber unless the config.clobber parameter === true. Example:
* ```
* let config_0 = { foo: { bar: 'bar' }, bar: 'foo' };
* let config_1 = { foo: 'foo', bar: 'bar' };
* let result = assignWithDepth(config_0, config_1);
* console.log(result);
* //-> result: { foo: { bar: 'bar' }, bar: 'bar' }
* ```
* <p>
* Traditional Object.assign would have clobbered foo in config_0 with foo in config_1.
* <p>
* If src is a destructured array of objects and dst is not an array, assignWithDepth will apply each element of src to dst
* in order.
* @param dst:any - the destination of the merge
* @param src:any - the source object(s) to merge into destination
* @param config:{ depth: number, clobber: boolean } - depth: depth to traverse within src and dst for merging -
* clobber: should dissimilar types clobber (default: { depth: 2, clobber: false })
* @returns {*}
*/
export const assignWithDepth = function(dst, src, config) {
const { depth, clobber } = Object.assign({ depth: 2, clobber: false }, config);
if (Array.isArray(src) && !Array.isArray(dst)) {
src.forEach(s => assignWithDepth(dst, s, config));
return dst;
}
if (typeof dst === 'undefined' || depth <= 0) {
if (dst !== undefined && dst !== null && typeof dst === 'object' && typeof src === 'object') {
return Object.assign(dst, src);
} else {
return src;
}
}
if (typeof src !== 'undefined' && typeof dst === 'object' && typeof src === 'object') {
Object.keys(src).forEach(key => {
if (
typeof src[key] === 'object' &&
(dst[key] === undefined || typeof dst[key] === 'object')
) {
if (dst[key] === undefined) {
dst[key] = {};
}
dst[key] = assignWithDepth(dst[key], src[key], { depth: depth - 1, clobber });
} else if (clobber || (typeof dst[key] !== 'object' && typeof src[key] !== 'object')) {
dst[key] = src[key];
}
});
}
return dst;
};
export const getTextObj = function() {
return {
x: 0,
y: 0,
fill: undefined,
anchor: 'start',
style: '#666',
width: 100,
height: 100,
textMargin: 0,
rx: 0,
ry: 0,
valign: undefined
};
};
export const drawSimpleText = function(elem, textData) {
// Remove and ignore br:s
const nText = textData.text.replace(common.lineBreakRegex, ' ');
const textElem = elem.append('text');
textElem.attr('x', textData.x);
textElem.attr('y', textData.y);
textElem.style('text-anchor', textData.anchor);
textElem.style('font-family', textData.fontFamily);
textElem.style('font-size', textData.fontSize);
textElem.style('font-weight', textData.fontWeight);
textElem.attr('fill', textData.fill);
if (typeof textData.class !== 'undefined') {
textElem.attr('class', textData.class);
}
const span = textElem.append('tspan');
span.attr('x', textData.x + textData.textMargin * 2);
span.attr('fill', textData.fill);
span.text(nText);
return textElem;
};
export const wrapLabel = (label, maxWidth, config) => {
config = Object.assign(
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15, joinWith: '<br/>' },
config
);
if (common.lineBreakRegex.test(label)) {
return label;
}
const words = label.split(' ');
const completedLines = [];
let nextLine = '';
words.forEach((word, index) => {
const wordLength = calculateTextWidth(`${word} `, config);
const nextLineLength = calculateTextWidth(nextLine, config);
if (wordLength > maxWidth) {
const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth, '-', config);
completedLines.push(nextLine, ...hyphenatedStrings);
nextLine = remainingWord;
} else if (nextLineLength + wordLength >= maxWidth) {
completedLines.push(nextLine);
nextLine = word;
} else {
nextLine = [nextLine, word].filter(Boolean).join(' ');
}
const currentWord = index + 1;
const isLastWord = currentWord === words.length;
if (isLastWord) {
completedLines.push(nextLine);
}
});
return completedLines.filter(line => line !== '').join(config.joinWith);
};
const breakString = (word, maxWidth, hyphenCharacter = '-', config) => {
config = Object.assign(
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15 },
config
);
const characters = word.split('');
const lines = [];
let currentLine = '';
characters.forEach((character, index) => {
const nextLine = `${currentLine}${character}`;
const lineWidth = calculateTextWidth(nextLine, config);
if (lineWidth >= maxWidth) {
const currentCharacter = index + 1;
const isLastLine = characters.length === currentCharacter;
const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`;
lines.push(isLastLine ? nextLine : hyphenatedNextLine);
currentLine = '';
} else {
currentLine = nextLine;
}
});
return { hyphenatedStrings: lines, remainingWord: currentLine };
};
/**
* This calculates the text's height, taking into account the wrap breaks and
* both the statically configured height, width, and the length of the text (in pixels).
*
* If the wrapped text text has greater height, we extend the height, so it's
* value won't overflow.
*
* @return - The height for the given text
* @param text the text to measure
* @param config - the config for fontSize, fontFamily, fontWeight, and margin all impacting the resulting size
*/
export const calculateTextHeight = function(text, config) {
config = Object.assign(
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15 },
config
);
return calculateTextDimensions(text, config).height;
};
/**
* This calculates the width of the given text, font size and family.
*
* @return - The width for the given text
* @param text - The text to calculate the width of
* @param config - the config for fontSize, fontFamily, fontWeight, and margin all impacting the resulting size
*/
export const calculateTextWidth = function(text, config) {
config = Object.assign(
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15 },
config
);
return calculateTextDimensions(text, config).width;
};
/**
* This calculates the dimensions of the given text, font size, font family, font weight, and margins.
*
* @return - The width for the given text
* @param text - The text to calculate the width of
* @param config - the config for fontSize, fontFamily, fontWeight, and margin all impacting the resulting size
*/
export const calculateTextDimensions = function(text, config) {
config = Object.assign(
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15 },
config
);
const { fontSize, fontFamily, fontWeight, margin } = config;
if (!text) {
return 0;
}
// We can't really know if the user supplied font family will render on the user agent;
// thus, we'll take the max width between the user supplied font family, and a default
// of sans-serif.
const fontFamilies = ['sans-serif', fontFamily];
const lines = text.split(common.lineBreakRegex);
let maxWidth = 0,
height = 0;
const body = select('body');
// We don't want to leak DOM elements - if a removal operation isn't available
// for any reason, do not continue.
if (!body.remove) {
return { width: 0, height: 0 };
}
const g = body.append('svg');
for (let line of lines) {
let cheight = 0;
for (let fontFamily of fontFamilies) {
const textObj = getTextObj();
textObj.text = line;
const textElem = drawSimpleText(g, textObj)
.style('font-size', fontSize)
.style('font-weight', fontWeight)
.style('font-family', fontFamily);
let bBox = (textElem._groups || textElem)[0][0].getBBox();
maxWidth = Math.max(maxWidth, bBox.width);
cheight = Math.max(bBox.height, cheight);
}
height += cheight;
}
g.remove();
// Adds some padding, so the text won't sit exactly within the actor's borders
return { width: maxWidth + 2 * margin, height: height + 2 * margin };
};
export default {
assignWithDepth,
wrapLabel,
calculateTextHeight,
calculateTextWidth,
calculateTextDimensions,
detectInit,
detectDirective,
detectType,

View File

@ -1,6 +1,74 @@
/* eslint-env jasmine */
import utils from './utils';
describe('when assignWithDepth: should merge objects within objects', function() {
it('should handle simple, depth:1 types (identity)', function() {
let config_0 = { foo: 'bar', bar: 0 };
let config_1 = { foo: 'bar', bar: 0 };
let result = utils.assignWithDepth(config_0, config_1);
expect(result).toEqual(config_1);
});
it('should handle simple, depth:1 types (dst: undefined)', function() {
let config_0 = undefined;
let config_1 = { foo: 'bar', bar: 0 };
let result = utils.assignWithDepth(config_0, config_1);
expect(result).toEqual(config_1);
});
it('should handle simple, depth:1 types (src: undefined)', function() {
let config_0 = { foo: 'bar', bar: 0 };
let config_1 = undefined;
let result = utils.assignWithDepth(config_0, config_1);
expect(result).toEqual(config_0);
});
it('should handle simple, depth:1 types (merge)', function() {
let config_0 = { foo: 'bar', bar: 0 };
let config_1 = { foo: 'foo' };
let result = utils.assignWithDepth(config_0, config_1);
expect(result).toEqual({ foo: 'foo', bar: 0});
});
it('should handle depth:2 types (dst: orphan)', function() {
let config_0 = { foo: 'bar', bar: { foo: 'bar' } };
let config_1 = { foo: 'bar' };
let result = utils.assignWithDepth(config_0, config_1);
expect(result).toEqual(config_0);
});
it('should handle depth:2 types (dst: object, src: simple type)', function() {
let config_0 = { foo: 'bar', bar: { foo: 'bar' } };
let config_1 = { foo: 'foo', bar: 'should NOT clobber'};
let result = utils.assignWithDepth(config_0, config_1);
expect(result).toEqual({ foo: 'foo', bar: { foo: 'bar' } } );
});
it('should handle depth:2 types (src: orphan)', function() {
let config_0 = { foo: 'bar' };
let config_1 = { foo: 'bar', bar: { foo: 'bar' } };
let result = utils.assignWithDepth(config_0, config_1);
expect(result).toEqual(config_1);
});
it('should handle depth:2 types (merge)', function() {
let config_0 = { foo: 'bar', bar: { foo: 'bar' }, boofar: 1 };
let config_1 = { foo: 'foo', bar: { bar: 0 }, foobar: 'foobar' };
let result = utils.assignWithDepth(config_0, config_1);
expect(result).toEqual({ foo: "foo", bar: { foo: "bar", bar: 0 }, foobar: "foobar", boofar: 1 });
});
it('should handle depth:3 types (merge with clobber because assignWithDepth::depth == 2)', function() {
let config_0 = { foo: 'bar', bar: { foo: 'bar', bar: { foo: { message: 'this', willbe: 'clobbered' } } }, boofar: 1 };
let config_1 = { foo: 'foo', bar: { foo: 'foo', bar: { foo: { message: 'clobbered other foo' } } }, foobar: 'foobar' };
let result = utils.assignWithDepth(config_0, config_1);
expect(result).toEqual({ foo: "foo", bar: { foo: 'foo', bar: { foo: { message: 'clobbered other foo' } } }, foobar: "foobar", boofar: 1 });
});
it('should handle depth:3 types (merge with clobber because assignWithDepth::depth == 1)', function() {
let config_0 = { foo: 'bar', bar: { foo: 'bar', bar: { foo: { message: '', willNotbe: 'present' }, bar: 'shouldNotBePresent' } }, boofar: 1 };
let config_1 = { foo: 'foo', bar: { foo: 'foo', bar: { foo: { message: 'this' } } }, foobar: 'foobar' };
let result = utils.assignWithDepth(config_0, config_1, { depth: 1 });
expect(result).toEqual({ foo: "foo", bar: { foo: 'foo', bar: { foo: { message: 'this' } } }, foobar: "foobar", boofar: 1 });
});
it('should handle depth:3 types (merge with no clobber because assignWithDepth::depth == 3)', function() {
let config_0 = { foo: 'bar', bar: { foo: 'bar', bar: { foo: { message: '', willbe: 'present' } } }, boofar: 1 };
let config_1 = { foo: 'foo', bar: { foo: 'foo', bar: { foo: { message: 'this' } } }, foobar: 'foobar' };
let result = utils.assignWithDepth(config_0, config_1, { depth: 3 });
expect(result).toEqual({ foo: "foo", bar: { foo: 'foo', bar: { foo: { message: 'this', willbe: 'present' } } }, foobar: "foobar", boofar: 1 });
});
});
describe('when detecting chart type ', function() {
it('should handle a graph definition', function() {
const str = 'graph TB\nbfs1:queue';
@ -27,6 +95,16 @@ Alice->Bob: hi`;
expect(type).toBe('sequence');
expect(init).toEqual({logLevel:0,theme:"dark"});
});
it('should handle an init definition with config converted to the proper diagram configuration', function() {
const str = `
%%{init: { 'logLevel': 0, 'theme': 'dark', 'config': {'wrapEnabled': true} } }%%
sequenceDiagram
Alice->Bob: hi`;
const type = utils.detectType(str);
const init = utils.detectInit(str);
expect(type).toBe('sequence');
expect(init).toEqual({logLevel:0,theme:"dark", sequence: { wrapEnabled: true }});
});
it('should handle a multiline init definition', function() {
const str = `
%%{