mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-13 12:29:42 +02:00
Add C4Context diagram. Compatible with C4-PlantUML syntax.
``` C4Context title System Context diagram for Internet Banking System Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.") Person(customerB, "Banking Customer B") Person_Ext(customerC, "Banking Customer C") System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.") Person(customerD, "Banking Customer D", "A customer of the bank, <br/> with personal bank accounts.") Enterprise_Boundary(b1, "BankBoundary") { SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.") System_Boundary(b2, "BankBoundary2") { System(SystemA, "Banking System A") System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts.") } System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.") SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.") Boundary(b3, "BankBoundary3", "boundary") { SystemQueue(SystemF, "Banking System F Queue", "A system of the bank, with personal bank accounts.") SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.") } } BiRel(customerA, SystemAA, "Uses") BiRel(SystemAA, SystemE, "Uses") Rel(SystemAA, SystemC, "Sends e-mails", "SMTP") Rel(SystemC, customerA, "Sends e-mails to") ```
This commit is contained in:
@@ -20,6 +20,44 @@
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="mermaid">
|
||||
C4Context
|
||||
title System Context diagram for Internet Banking System
|
||||
|
||||
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
|
||||
Person(customerB, "Banking Customer B")
|
||||
Person_Ext(customerC, "Banking Customer C")
|
||||
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
|
||||
|
||||
|
||||
Person(customerD, "Banking Customer D", "A customer of the bank, <br/> with personal bank accounts.")
|
||||
|
||||
Enterprise_Boundary(b1, "BankBoundary") {
|
||||
|
||||
SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||||
|
||||
System_Boundary(b2, "BankBoundary2") {
|
||||
System(SystemA, "Banking System A")
|
||||
System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts.")
|
||||
}
|
||||
|
||||
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
|
||||
SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.")
|
||||
|
||||
Boundary(b3, "BankBoundary3", "boundary") {
|
||||
SystemQueue(SystemF, "Banking System F Queue", "A system of the bank, with personal bank accounts.")
|
||||
SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.")
|
||||
}
|
||||
}
|
||||
|
||||
BiRel(customerA, SystemAA, "Uses")
|
||||
BiRel(SystemAA, SystemE, "Uses")
|
||||
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
|
||||
Rel(SystemC, customerA, "Sends e-mails to")
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="mermaid">
|
||||
pie
|
||||
title Key elements in Product X
|
||||
|
@@ -1,3 +1,6 @@
|
||||
import c4Db from './diagrams/c4/c4Db';
|
||||
import c4Renderer from './diagrams/c4/c4Renderer';
|
||||
import c4Parser from './diagrams/c4/parser/c4Diagram';
|
||||
import classDb from './diagrams/class/classDb';
|
||||
import classRenderer from './diagrams/class/classRenderer';
|
||||
import classRendererV2 from './diagrams/class/classRenderer-v2';
|
||||
@@ -49,6 +52,12 @@ class Diagram {
|
||||
this.type = utils.detectType(txt, cnf);
|
||||
log.debug('Type ' + this.type);
|
||||
switch (this.type) {
|
||||
case 'c4':
|
||||
this.parser = c4Parser;
|
||||
this.parser.parser.yy = c4Db;
|
||||
this.db = c4Db;
|
||||
this.renderer = c4Renderer;
|
||||
break;
|
||||
case 'gitGraph':
|
||||
this.parser = gitGraphParser;
|
||||
this.parser.parser.yy = gitGraphAst;
|
||||
|
@@ -1059,6 +1059,165 @@ const config = {
|
||||
showCommitLabel: true,
|
||||
showBranches: true,
|
||||
},
|
||||
|
||||
/** The object containing configurations specific for c4 diagrams */
|
||||
c4: {
|
||||
useWidth: undefined,
|
||||
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | -------------- | ---------------------------------------------- | ------- | -------- | ------------------ |
|
||||
* | diagramMarginX | Margin to the right and left of the c4 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 c4 diagram | Integer | Required | Any Positive Value |
|
||||
*
|
||||
* **Notes:** Default value: 10
|
||||
*/
|
||||
diagramMarginY: 10,
|
||||
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ----------- | --------------------- | ------- | -------- | ------------------ |
|
||||
* | shapeMargin | Margin between shapes | Integer | Required | Any Positive Value |
|
||||
*
|
||||
* **Notes:** Default value: 50
|
||||
*/
|
||||
c4ShapeMargin: 50,
|
||||
|
||||
c4ShapePadding: 20,
|
||||
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | --------- | --------------------- | ------- | -------- | ------------------ |
|
||||
* | width | Width of person boxes | Integer | Required | Any Positive Value |
|
||||
*
|
||||
* **Notes:** Default value: 215
|
||||
*/
|
||||
width: 216,
|
||||
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | --------- | ---------------------- | ------- | -------- | ------------------ |
|
||||
* | height | Height of person boxes | Integer | Required | Any Positive Value |
|
||||
*
|
||||
* **Notes:** Default value: 65
|
||||
*/
|
||||
height: 60,
|
||||
|
||||
/**
|
||||
* | 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 |
|
||||
* | ----------- | ----------- | ------- | -------- | ----------- |
|
||||
* | useMaxWidth | See Notes | boolean | Required | true, false |
|
||||
*
|
||||
* **Notes:** When this flag is set to true, the height and width is set to 100% and is then
|
||||
* scaling with the available space. If set to false, the absolute space required is used.
|
||||
*
|
||||
* Default value: true
|
||||
*/
|
||||
useMaxWidth: true,
|
||||
|
||||
c4ShapeInRow: 4,
|
||||
nextLinePaddingX: 0,
|
||||
|
||||
c4BoundaryInRow: 2,
|
||||
|
||||
personFontSize: 14,
|
||||
personFontFamily: '"Open Sans", sans-serif',
|
||||
personFontWeight: 'normal',
|
||||
|
||||
systemFontSize: 14,
|
||||
systemFontFamily: '"Open Sans", sans-serif',
|
||||
systemFontWeight: 'normal',
|
||||
|
||||
boundaryFontSize: 14,
|
||||
boundaryFontFamily: '"Open Sans", sans-serif',
|
||||
boundaryFontWeight: 'normal',
|
||||
|
||||
messageFontSize: 12,
|
||||
messageFontFamily: '"Open Sans", sans-serif',
|
||||
messageFontWeight: 'normal',
|
||||
|
||||
/**
|
||||
* This sets the auto-wrap state for the diagram
|
||||
*
|
||||
* **Notes:** Default value: true.
|
||||
*/
|
||||
wrap: true,
|
||||
|
||||
/**
|
||||
* This sets the auto-wrap padding for the diagram (sides only)
|
||||
*
|
||||
* **Notes:** Default value: 0.
|
||||
*/
|
||||
wrapPadding: 10,
|
||||
|
||||
personFont: function () {
|
||||
return {
|
||||
fontFamily: this.personFontFamily,
|
||||
fontSize: this.personFontSize,
|
||||
fontWeight: this.personFontWeight,
|
||||
};
|
||||
},
|
||||
|
||||
systemFont: function () {
|
||||
return {
|
||||
fontFamily: this.systemFontFamily,
|
||||
fontSize: this.systemFontSize,
|
||||
fontWeight: this.systemFontWeight,
|
||||
};
|
||||
},
|
||||
|
||||
boundaryFont: function () {
|
||||
return {
|
||||
fontFamily: this.boundaryFontFamily,
|
||||
fontSize: this.boundaryFontSize,
|
||||
fontWeight: this.boundaryFontWeight,
|
||||
};
|
||||
},
|
||||
|
||||
messageFont: function () {
|
||||
return {
|
||||
fontFamily: this.messageFontFamily,
|
||||
fontSize: this.messageFontSize,
|
||||
fontWeight: this.messageFontWeight,
|
||||
};
|
||||
},
|
||||
|
||||
// ' Colors
|
||||
// ' ##################################
|
||||
person_bg_color: '#08427B',
|
||||
person_border_color: '#073B6F',
|
||||
external_person_bg_color: '#686868',
|
||||
external_person_border_color: '#8A8A8A',
|
||||
system_bg_color: '#1168BD',
|
||||
system_border_color: '#3C7FC0',
|
||||
system_db_bg_color: '#1168BD',
|
||||
system_db_border_color: '#3C7FC0',
|
||||
system_queue_bg_color: '#1168BD',
|
||||
system_queue_border_color: '#3C7FC0',
|
||||
external_system_bg_color: '#999999',
|
||||
external_system_border_color: '#8A8A8A',
|
||||
external_system_db_bg_color: '#999999',
|
||||
external_system_db_border_color: '#8A8A8A',
|
||||
external_system_queue_bg_color: '#999999',
|
||||
external_system_queue_border_color: '#8A8A8A',
|
||||
},
|
||||
};
|
||||
|
||||
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
|
307
src/diagrams/c4/c4Db.js
Normal file
307
src/diagrams/c4/c4Db.js
Normal file
@@ -0,0 +1,307 @@
|
||||
import mermaidAPI from '../../mermaidAPI';
|
||||
import * as configApi from '../../config';
|
||||
import { log } from '../../logger';
|
||||
import { sanitizeText } from '../common/common';
|
||||
|
||||
let personOrSystemArray = [];
|
||||
let boundaryParseStack = [''];
|
||||
let currentBoundaryParse = 'global';
|
||||
let parentBoundaryParse = '';
|
||||
let boundarys = [
|
||||
{
|
||||
alias: 'global',
|
||||
label: { text: 'global' },
|
||||
type: 'global',
|
||||
tags: null,
|
||||
link: null,
|
||||
parentBoundary: '',
|
||||
},
|
||||
];
|
||||
let rels = [];
|
||||
let title = '';
|
||||
let wrapEnabled = false;
|
||||
let description = '';
|
||||
let c4Type = 'C4Context';
|
||||
|
||||
export const getC4Type = function () {
|
||||
return c4Type;
|
||||
};
|
||||
|
||||
export const setC4Type = function (c4Type) {
|
||||
let sanitizedText = sanitizeText(c4Type, configApi.getConfig());
|
||||
c4Type = sanitizedText;
|
||||
};
|
||||
|
||||
export const parseDirective = function (statement, context, type) {
|
||||
mermaidAPI.parseDirective(this, statement, context, type);
|
||||
};
|
||||
|
||||
//type, from, to, label, ?techn, ?descr, ?sprite, ?tags, $link
|
||||
export const addRel = function (type, from, to, label, techn, descr, sprite, tags, link) {
|
||||
// Don't allow label nulling
|
||||
if (
|
||||
type === undefined ||
|
||||
type === null ||
|
||||
from === undefined ||
|
||||
from === null ||
|
||||
to === undefined ||
|
||||
to === null ||
|
||||
label === undefined ||
|
||||
label === null
|
||||
)
|
||||
return;
|
||||
|
||||
let rel = {};
|
||||
const old = rels.find((rel) => rel.from === from && rel.to === to);
|
||||
if (old) {
|
||||
rel = old;
|
||||
} else {
|
||||
rels.push(rel);
|
||||
}
|
||||
|
||||
rel.type = type;
|
||||
rel.from = from;
|
||||
rel.to = to;
|
||||
rel.label = { text: label };
|
||||
|
||||
if (descr === undefined || descr === null) {
|
||||
rel.descr = { text: '' };
|
||||
} else {
|
||||
rel.descr = { text: descr };
|
||||
}
|
||||
|
||||
if (techn === undefined || techn === null) {
|
||||
rel.techn = { text: '' };
|
||||
} else {
|
||||
rel.techn = { text: techn };
|
||||
}
|
||||
|
||||
// rel.techn = techn;
|
||||
rel.sprite = sprite;
|
||||
rel.tags = tags;
|
||||
rel.link = link;
|
||||
rel.wrap = autoWrap();
|
||||
};
|
||||
|
||||
//type, alias, label, ?descr, ?sprite, ?tags, $link
|
||||
export const addPersonOrSystem = function (type, alias, label, descr, sprite, tags, link) {
|
||||
// Don't allow label nulling
|
||||
if (alias === null || label === null) return;
|
||||
|
||||
let personOrSystem = {};
|
||||
const old = personOrSystemArray.find((personOrSystem) => personOrSystem.alias === alias);
|
||||
if (old && alias === old.alias) {
|
||||
personOrSystem = old;
|
||||
} else {
|
||||
personOrSystem.alias = alias;
|
||||
personOrSystemArray.push(personOrSystem);
|
||||
}
|
||||
|
||||
// Don't allow null labels, either
|
||||
if (label === undefined || label === null) {
|
||||
personOrSystem.label = { text: '' };
|
||||
} else {
|
||||
personOrSystem.label = { text: label };
|
||||
}
|
||||
|
||||
if (descr === undefined || descr === null) {
|
||||
personOrSystem.descr = { text: '' };
|
||||
} else {
|
||||
personOrSystem.descr = { text: descr };
|
||||
}
|
||||
|
||||
personOrSystem.wrap = autoWrap();
|
||||
personOrSystem.sprite = sprite;
|
||||
personOrSystem.tags = tags;
|
||||
personOrSystem.link = link;
|
||||
personOrSystem.type = type;
|
||||
personOrSystem.parentBoundary = currentBoundaryParse;
|
||||
};
|
||||
|
||||
//alias, label, ?type, ?tags, $link
|
||||
export const addBoundary = function (alias, label, type, tags, link) {
|
||||
// if (parentBoundary === null) return;
|
||||
|
||||
// Don't allow label nulling
|
||||
if (alias === null || label === null) return;
|
||||
|
||||
let boundary = {};
|
||||
const old = boundarys.find((boundary) => boundary.alias === alias);
|
||||
if (old && alias === old.alias) {
|
||||
boundary = old;
|
||||
} else {
|
||||
boundary.alias = alias;
|
||||
boundarys.push(boundary);
|
||||
}
|
||||
|
||||
// Don't allow null labels, either
|
||||
if (label === undefined || label === null) {
|
||||
boundary.label = { text: '' };
|
||||
} else {
|
||||
boundary.label = { text: label };
|
||||
}
|
||||
|
||||
if (type === undefined || type === null) {
|
||||
boundary.type = { text: 'system' };
|
||||
} else {
|
||||
boundary.type = { text: type };
|
||||
}
|
||||
|
||||
boundary.wrap = autoWrap();
|
||||
boundary.tags = tags;
|
||||
boundary.link = link;
|
||||
boundary.type = type;
|
||||
boundary.parentBoundary = currentBoundaryParse;
|
||||
|
||||
parentBoundaryParse = currentBoundaryParse;
|
||||
currentBoundaryParse = alias;
|
||||
boundaryParseStack.push(parentBoundaryParse);
|
||||
};
|
||||
|
||||
export const popBoundaryParseStack = function () {
|
||||
currentBoundaryParse = parentBoundaryParse;
|
||||
boundaryParseStack.pop();
|
||||
parentBoundaryParse = boundaryParseStack.pop();
|
||||
boundaryParseStack.push(parentBoundaryParse);
|
||||
};
|
||||
|
||||
export const getCurrentBoundaryParse = function () {
|
||||
return currentBoundaryParse;
|
||||
};
|
||||
|
||||
export const getParentBoundaryParse = function () {
|
||||
return parentBoundaryParse;
|
||||
};
|
||||
|
||||
export const getPersonOrSystemArray = function (parentBoundary) {
|
||||
if (parentBoundary === undefined || parentBoundary === null) return personOrSystemArray;
|
||||
else
|
||||
return personOrSystemArray.filter((personOrSystem) => {
|
||||
return personOrSystem.parentBoundary === parentBoundary;
|
||||
});
|
||||
};
|
||||
export const getPersonOrSystem = function (alias) {
|
||||
return personOrSystemArray.find((personOrSystem) => personOrSystem.alias === alias);
|
||||
};
|
||||
export const getPersonOrSystemKeys = function (parentBoundary) {
|
||||
return Object.keys(getPersonOrSystemArray(parentBoundary));
|
||||
};
|
||||
|
||||
export const getBoundarys = function (parentBoundary) {
|
||||
if (parentBoundary === undefined || parentBoundary === null) return boundarys;
|
||||
else return boundarys.filter((boundary) => boundary.parentBoundary === parentBoundary);
|
||||
};
|
||||
|
||||
export const getRels = function () {
|
||||
return rels;
|
||||
};
|
||||
|
||||
export const getTitle = function () {
|
||||
return title;
|
||||
};
|
||||
|
||||
export const setWrap = function (wrapSetting) {
|
||||
wrapEnabled = wrapSetting;
|
||||
};
|
||||
|
||||
export const autoWrap = function () {
|
||||
return wrapEnabled;
|
||||
};
|
||||
|
||||
export const clear = function () {
|
||||
personOrSystemArray = [];
|
||||
boundarys = [
|
||||
{
|
||||
alias: 'global',
|
||||
label: { text: 'global' },
|
||||
type: 'global',
|
||||
tags: null,
|
||||
link: null,
|
||||
parentBoundary: '',
|
||||
},
|
||||
];
|
||||
parentBoundaryParse = '';
|
||||
currentBoundaryParse = 'global';
|
||||
boundaryParseStack = [''];
|
||||
rels = [];
|
||||
};
|
||||
|
||||
export const LINETYPE = {
|
||||
SOLID: 0,
|
||||
DOTTED: 1,
|
||||
NOTE: 2,
|
||||
SOLID_CROSS: 3,
|
||||
DOTTED_CROSS: 4,
|
||||
SOLID_OPEN: 5,
|
||||
DOTTED_OPEN: 6,
|
||||
LOOP_START: 10,
|
||||
LOOP_END: 11,
|
||||
ALT_START: 12,
|
||||
ALT_ELSE: 13,
|
||||
ALT_END: 14,
|
||||
OPT_START: 15,
|
||||
OPT_END: 16,
|
||||
ACTIVE_START: 17,
|
||||
ACTIVE_END: 18,
|
||||
PAR_START: 19,
|
||||
PAR_AND: 20,
|
||||
PAR_END: 21,
|
||||
RECT_START: 22,
|
||||
RECT_END: 23,
|
||||
SOLID_POINT: 24,
|
||||
DOTTED_POINT: 25,
|
||||
};
|
||||
|
||||
export const ARROWTYPE = {
|
||||
FILLED: 0,
|
||||
OPEN: 1,
|
||||
};
|
||||
|
||||
export const PLACEMENT = {
|
||||
LEFTOF: 0,
|
||||
RIGHTOF: 1,
|
||||
OVER: 2,
|
||||
};
|
||||
|
||||
export const setTitle = function (txt) {
|
||||
let sanitizedText = sanitizeText(txt, configApi.getConfig());
|
||||
title = sanitizedText;
|
||||
};
|
||||
|
||||
const setAccDescription = function (description_lex) {
|
||||
let sanitizedText = sanitizeText(description_lex, configApi.getConfig());
|
||||
description = sanitizedText;
|
||||
};
|
||||
|
||||
const getAccDescription = function () {
|
||||
return description;
|
||||
};
|
||||
|
||||
export default {
|
||||
addPersonOrSystem,
|
||||
addBoundary,
|
||||
popBoundaryParseStack,
|
||||
addRel,
|
||||
autoWrap,
|
||||
setWrap,
|
||||
getPersonOrSystemArray,
|
||||
getPersonOrSystem,
|
||||
getPersonOrSystemKeys,
|
||||
getBoundarys,
|
||||
getCurrentBoundaryParse,
|
||||
getParentBoundaryParse,
|
||||
getRels,
|
||||
getTitle,
|
||||
getC4Type,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
parseDirective,
|
||||
getConfig: () => configApi.getConfig().c4,
|
||||
clear,
|
||||
LINETYPE,
|
||||
ARROWTYPE,
|
||||
PLACEMENT,
|
||||
setTitle,
|
||||
setC4Type,
|
||||
// apply,
|
||||
};
|
608
src/diagrams/c4/c4Renderer.js
Normal file
608
src/diagrams/c4/c4Renderer.js
Normal file
@@ -0,0 +1,608 @@
|
||||
import { select } from 'd3';
|
||||
import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw';
|
||||
import { log } from '../../logger';
|
||||
import { parser } from './parser/c4Diagram';
|
||||
import common from '../common/common';
|
||||
import c4Db from './c4Db';
|
||||
import * as configApi from '../../config';
|
||||
import utils, {
|
||||
wrapLabel,
|
||||
calculateTextWidth,
|
||||
calculateTextHeight,
|
||||
assignWithDepth,
|
||||
configureSvgSize,
|
||||
} from '../../utils';
|
||||
import addSVGAccessibilityFields from '../../accessibility';
|
||||
|
||||
let globalBoundaryMaxX = 0,
|
||||
globalBoundaryMaxY = 0;
|
||||
|
||||
parser.yy = c4Db;
|
||||
|
||||
let conf = {};
|
||||
|
||||
class Bounds {
|
||||
constructor() {
|
||||
this.name = '';
|
||||
this.data = {};
|
||||
this.data.startx = undefined;
|
||||
this.data.stopx = undefined;
|
||||
this.data.starty = undefined;
|
||||
this.data.stopy = undefined;
|
||||
this.data.widthLimit = undefined;
|
||||
|
||||
this.nextData = {};
|
||||
this.nextData.startx = undefined;
|
||||
this.nextData.stopx = undefined;
|
||||
this.nextData.starty = undefined;
|
||||
this.nextData.stopy = undefined;
|
||||
|
||||
setConf(parser.yy.getConfig());
|
||||
}
|
||||
|
||||
setData(startx, stopx, starty, stopy) {
|
||||
this.nextData.startx = this.data.startx = startx;
|
||||
this.nextData.stopx = this.data.stopx = stopx;
|
||||
this.nextData.starty = this.data.starty = starty;
|
||||
this.nextData.stopy = this.data.stopy = stopy;
|
||||
}
|
||||
|
||||
updateVal(obj, key, val, fun) {
|
||||
if (typeof obj[key] === 'undefined') {
|
||||
obj[key] = val;
|
||||
} else {
|
||||
obj[key] = fun(val, obj[key]);
|
||||
}
|
||||
}
|
||||
|
||||
insert(c4Shape) {
|
||||
let _startx = this.nextData.stopx + c4Shape.margin * 2;
|
||||
let _stopx = _startx + c4Shape.width;
|
||||
let _starty = this.nextData.starty + c4Shape.margin * 2;
|
||||
let _stopy = _starty + c4Shape.height;
|
||||
if (_startx >= this.data.widthLimit || _stopx >= this.data.widthLimit) {
|
||||
_startx = this.nextData.startx + c4Shape.margin * 2 + conf.nextLinePaddingX;
|
||||
_starty = this.nextData.stopy + c4Shape.margin * 2;
|
||||
|
||||
this.nextData.stopx = _stopx = _startx + c4Shape.width;
|
||||
this.nextData.starty = this.nextData.stopy;
|
||||
this.nextData.stopy = _stopy = _starty + c4Shape.height;
|
||||
}
|
||||
|
||||
c4Shape.x = _startx;
|
||||
c4Shape.y = _starty;
|
||||
|
||||
this.updateVal(this.data, 'startx', _startx, Math.min);
|
||||
this.updateVal(this.data, 'starty', _starty, Math.min);
|
||||
this.updateVal(this.data, 'stopx', _stopx, Math.max);
|
||||
this.updateVal(this.data, 'stopy', _stopy, Math.max);
|
||||
|
||||
this.updateVal(this.nextData, 'startx', _startx, Math.min);
|
||||
this.updateVal(this.nextData, 'starty', _starty, Math.min);
|
||||
this.updateVal(this.nextData, 'stopx', _stopx, Math.max);
|
||||
this.updateVal(this.nextData, 'stopy', _stopy, Math.max);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.data = {
|
||||
startx: undefined,
|
||||
stopx: undefined,
|
||||
starty: undefined,
|
||||
stopy: undefined,
|
||||
widthLimit: undefined,
|
||||
};
|
||||
setConf(parser.yy.getConfig());
|
||||
}
|
||||
|
||||
bumpLastMargin(margin) {
|
||||
this.data.stopx += margin;
|
||||
this.data.stopy += margin;
|
||||
}
|
||||
}
|
||||
|
||||
const personFont = (cnf) => {
|
||||
return {
|
||||
fontFamily: cnf.personFontFamily,
|
||||
fontSize: cnf.personFontSize,
|
||||
fontWeight: cnf.personFontWeight,
|
||||
};
|
||||
};
|
||||
|
||||
const systemFont = (cnf) => {
|
||||
return {
|
||||
fontFamily: cnf.systemFontFamily,
|
||||
fontSize: cnf.systemFontSize,
|
||||
fontWeight: cnf.systemFontWeight,
|
||||
};
|
||||
};
|
||||
|
||||
const boundaryFont = (cnf) => {
|
||||
return {
|
||||
fontFamily: cnf.boundaryFontFamily,
|
||||
fontSize: cnf.boundaryFontSize,
|
||||
fontWeight: cnf.boundaryFontWeight,
|
||||
};
|
||||
};
|
||||
|
||||
const messageFont = (cnf) => {
|
||||
return {
|
||||
fontFamily: cnf.messageFontFamily,
|
||||
fontSize: cnf.messageFontSize,
|
||||
fontWeight: cnf.messageFontWeight,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param textType
|
||||
* @param c4Shape
|
||||
* @param c4ShapeTextWrap
|
||||
* @param textConf
|
||||
* @param textLimitWidth
|
||||
*/
|
||||
function setC4ShapeText(textType, c4Shape, c4ShapeTextWrap, textConf, textLimitWidth) {
|
||||
if (!c4Shape[textType].width) {
|
||||
if (c4ShapeTextWrap) {
|
||||
c4Shape[textType].text = wrapLabel(c4Shape[textType].text, textLimitWidth, textConf);
|
||||
c4Shape[textType].labelLines = c4Shape[textType].text.split(common.lineBreakRegex).length;
|
||||
c4Shape[textType].width = textLimitWidth;
|
||||
c4Shape[textType].height = c4Shape[textType].labelLines * (textConf.fontSize + 2);
|
||||
} else {
|
||||
let lines = c4Shape[textType].text.split(common.lineBreakRegex);
|
||||
c4Shape[textType].labelLines = lines.length;
|
||||
let lineHeight = 0;
|
||||
c4Shape[textType].height = 0;
|
||||
c4Shape[textType].width = 0;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
c4Shape[textType].width = Math.max(
|
||||
calculateTextWidth(lines[i], textConf),
|
||||
c4Shape[textType].width
|
||||
);
|
||||
lineHeight = calculateTextHeight(lines[i], textConf);
|
||||
c4Shape[textType].height = c4Shape[textType].height + lineHeight;
|
||||
}
|
||||
// c4Shapes[textType].height = c4Shapes[textType].labelLines * textConf.fontSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const drawBoundary = function (diagram, boundary, bounds) {
|
||||
boundary.x = bounds.data.startx;
|
||||
boundary.y = bounds.data.starty;
|
||||
boundary.width = bounds.data.stopx - bounds.data.startx;
|
||||
boundary.height = bounds.data.stopy - bounds.data.starty;
|
||||
|
||||
boundary.label.y = conf.c4ShapeMargin - 35;
|
||||
|
||||
let boundaryTextWrap = boundary.wrap && conf.wrap;
|
||||
let boundaryLabelConf = boundaryFont(conf);
|
||||
boundaryLabelConf.fontSize = boundaryLabelConf.fontSize + 2;
|
||||
boundaryLabelConf.fontWeight = 'bold';
|
||||
let textLimitWidth = calculateTextWidth(boundary.label.text, boundaryLabelConf);
|
||||
setC4ShapeText('label', boundary, boundaryTextWrap, boundaryLabelConf, textLimitWidth);
|
||||
|
||||
svgDraw.drawBoundary(diagram, boundary, conf);
|
||||
};
|
||||
|
||||
export const drawPersonOrSystemArray = function (
|
||||
currentBounds,
|
||||
diagram,
|
||||
personOrSystemArray,
|
||||
personOrSystemKeys
|
||||
) {
|
||||
// Draw the personOrSystemArray
|
||||
|
||||
// let prevWidth = currentBounds.data.stopx;
|
||||
// let prevMarginX = conf.c4ShapeMargin;
|
||||
// let prevMarginY = conf.c4ShapeMargin;
|
||||
// let maxHeight = currentBounds.data.starty;
|
||||
|
||||
for (let i = 0; i < personOrSystemKeys.length; i++) {
|
||||
const personOrSystem = personOrSystemArray[personOrSystemKeys[i]];
|
||||
|
||||
let imageWidth = 0,
|
||||
imageHeight = 0;
|
||||
switch (personOrSystem.type) {
|
||||
case 'person':
|
||||
case 'external_person':
|
||||
imageWidth = 48;
|
||||
imageHeight = 48;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!personOrSystem.typeLabelWidth) {
|
||||
let personOrSystemTypeConf = personFont(conf);
|
||||
personOrSystemTypeConf.fontSize = personOrSystemTypeConf.fontSize - 2;
|
||||
personOrSystem.typeLabelWidth = calculateTextWidth(
|
||||
'<<' + personOrSystem.type + '>>',
|
||||
personOrSystemTypeConf
|
||||
);
|
||||
personOrSystem.typeLabelHeight = personOrSystemTypeConf.fontSize + 2;
|
||||
|
||||
switch (personOrSystem.type) {
|
||||
case 'system_db':
|
||||
case 'external_system_db':
|
||||
personOrSystem.typeLabelY = conf.c4ShapePadding;
|
||||
break;
|
||||
default:
|
||||
personOrSystem.typeLabelY = conf.c4ShapePadding - 5;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let personOrSystemTextWrap = personOrSystem.wrap && conf.wrap;
|
||||
let textLimitWidth = conf.width - conf.c4ShapePadding * 2;
|
||||
|
||||
let personOrSystemLabelConf = personFont(conf);
|
||||
personOrSystemLabelConf.fontSize = personOrSystemLabelConf.fontSize + 2;
|
||||
personOrSystemLabelConf.fontWeight = 'bold';
|
||||
|
||||
setC4ShapeText(
|
||||
'label',
|
||||
personOrSystem,
|
||||
personOrSystemTextWrap,
|
||||
personOrSystemLabelConf,
|
||||
textLimitWidth
|
||||
);
|
||||
personOrSystem['label'].Y =
|
||||
conf.c4ShapePadding + personOrSystem.typeLabelHeight + imageHeight + 10;
|
||||
|
||||
let personOrSystemDescrConf = personFont(conf);
|
||||
setC4ShapeText(
|
||||
'descr',
|
||||
personOrSystem,
|
||||
personOrSystemTextWrap,
|
||||
personOrSystemDescrConf,
|
||||
textLimitWidth
|
||||
);
|
||||
personOrSystem['descr'].Y =
|
||||
conf.c4ShapePadding +
|
||||
personOrSystem.typeLabelHeight +
|
||||
imageHeight +
|
||||
5 +
|
||||
personOrSystem.label.height +
|
||||
conf.personFontSize +
|
||||
2;
|
||||
|
||||
// Add some rendering data to the object
|
||||
let rectWidth =
|
||||
Math.max(personOrSystem.label.width, personOrSystem.descr.width) + conf.c4ShapePadding * 2;
|
||||
let rectHeight =
|
||||
conf.c4ShapePadding +
|
||||
personOrSystem.typeLabelHeight +
|
||||
imageHeight +
|
||||
personOrSystem.label.height +
|
||||
conf.personFontSize +
|
||||
2 +
|
||||
personOrSystem.descr.height;
|
||||
|
||||
personOrSystem.width = Math.max(personOrSystem.width || conf.width, rectWidth, conf.width);
|
||||
personOrSystem.height = Math.max(personOrSystem.height || conf.height, rectHeight, conf.height);
|
||||
personOrSystem.margin = personOrSystem.margin || conf.c4ShapeMargin;
|
||||
|
||||
currentBounds.insert(personOrSystem);
|
||||
|
||||
const height = svgDraw.drawPersonOrSystem(diagram, personOrSystem, conf);
|
||||
}
|
||||
|
||||
currentBounds.bumpLastMargin(conf.c4ShapeMargin);
|
||||
};
|
||||
|
||||
class Point {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
/* * *
|
||||
* Get the intersection of the line between the center point of a rectangle and a point outside the rectangle.
|
||||
* Algorithm idea.
|
||||
* Using a point outside the rectangle as the coordinate origin, the graph is divided into four quadrants, and each quadrant is divided into two cases, with separate treatment on the coordinate axes
|
||||
* 1. The case of coordinate axes.
|
||||
* 1. The case of the negative x-axis
|
||||
* 2. The case of the positive x-axis
|
||||
* 3. The case of the positive y-axis
|
||||
* 4. The negative y-axis case
|
||||
* 2. Quadrant cases.
|
||||
* 2.1. first quadrant: the case where the line intersects the left side of the rectangle; the case where it intersects the lower side of the rectangle
|
||||
* 2.2. second quadrant: the case where the line intersects the right side of the rectangle; the case where it intersects the lower edge of the rectangle
|
||||
* 2.3. third quadrant: the case where the line intersects the right side of the rectangle; the case where it intersects the upper edge of the rectangle
|
||||
* 2.4. fourth quadrant: the case where the line intersects the left side of the rectangle; the case where it intersects the upper side of the rectangle
|
||||
*
|
||||
*/
|
||||
let getIntersectPoint = function (fromNode, endPoint) {
|
||||
let x1 = fromNode.x;
|
||||
|
||||
let y1 = fromNode.y;
|
||||
|
||||
let x2 = endPoint.x;
|
||||
|
||||
let y2 = endPoint.y;
|
||||
|
||||
let fromCenterX = x1 + fromNode.width / 2;
|
||||
|
||||
let fromCenterY = y1 + fromNode.height / 2;
|
||||
|
||||
let dx = Math.abs(x1 - x2);
|
||||
|
||||
let dy = Math.abs(y1 - y2);
|
||||
|
||||
let tanDYX = dy / dx;
|
||||
|
||||
let fromDYX = fromNode.height / fromNode.width;
|
||||
|
||||
let returnPoint = null;
|
||||
|
||||
if (y1 == y2 && x1 < x2) {
|
||||
returnPoint = new Point(x1 + fromNode.width, fromCenterY);
|
||||
} else if (y1 == y2 && x1 > x2) {
|
||||
returnPoint = new Point(x1, fromCenterY);
|
||||
} else if (x1 == x2 && y1 < y2) {
|
||||
returnPoint = new Point(fromCenterX, y1 + fromNode.height);
|
||||
} else if (x1 == x2 && y1 > y2) {
|
||||
returnPoint = new Point(fromCenterX, y1);
|
||||
}
|
||||
|
||||
if (x1 > x2 && y1 < y2) {
|
||||
if (fromDYX >= tanDYX) {
|
||||
returnPoint = new Point(x1, fromCenterY + (tanDYX * fromNode.width) / 2);
|
||||
} else {
|
||||
returnPoint = new Point(
|
||||
fromCenterX - ((dx / dy) * fromNode.height) / 2,
|
||||
y1 + fromNode.height
|
||||
);
|
||||
}
|
||||
} else if (x1 < x2 && y1 < y2) {
|
||||
//
|
||||
if (fromDYX >= tanDYX) {
|
||||
returnPoint = new Point(x1 + fromNode.width, fromCenterY + (tanDYX * fromNode.width) / 2);
|
||||
} else {
|
||||
returnPoint = new Point(
|
||||
fromCenterX + ((dx / dy) * fromNode.height) / 2,
|
||||
y1 + fromNode.height
|
||||
);
|
||||
}
|
||||
} else if (x1 < x2 && y1 > y2) {
|
||||
if (fromDYX >= tanDYX) {
|
||||
returnPoint = new Point(x1 + fromNode.width, fromCenterY - (tanDYX * fromNode.width) / 2);
|
||||
} else {
|
||||
returnPoint = new Point(fromCenterX + ((fromNode.height / 2) * dx) / dy, y1);
|
||||
}
|
||||
} else if (x1 > x2 && y1 > y2) {
|
||||
if (fromDYX >= tanDYX) {
|
||||
returnPoint = new Point(x1, fromCenterY - (fromNode.width / 2) * tanDYX);
|
||||
} else {
|
||||
returnPoint = new Point(fromCenterX - ((fromNode.height / 2) * dx) / dy, y1);
|
||||
}
|
||||
}
|
||||
return returnPoint;
|
||||
};
|
||||
|
||||
let getIntersectPoints = function (fromNode, endNode) {
|
||||
let endIntersectPoint = { x: 0, y: 0 };
|
||||
endIntersectPoint.x = endNode.x + endNode.width / 2;
|
||||
endIntersectPoint.y = endNode.y + endNode.height / 2;
|
||||
let startPoint = getIntersectPoint(fromNode, endIntersectPoint);
|
||||
|
||||
endIntersectPoint.x = fromNode.x + fromNode.width / 2;
|
||||
endIntersectPoint.y = fromNode.y + fromNode.height / 2;
|
||||
let endPoint = getIntersectPoint(endNode, endIntersectPoint);
|
||||
return { startPoint: startPoint, endPoint: endPoint };
|
||||
};
|
||||
|
||||
export const drawRels = function (diagram, rels, getC4ShapeObj) {
|
||||
for (let rel of rels) {
|
||||
let relTextWrap = rel.wrap && conf.wrap;
|
||||
let relConf = messageFont(conf);
|
||||
let textLimitWidth = calculateTextWidth(rel.label.text, relConf);
|
||||
setC4ShapeText('label', rel, relTextWrap, relConf, textLimitWidth);
|
||||
|
||||
if (rel.techn && rel.techn.text !== '') {
|
||||
textLimitWidth = calculateTextWidth(rel.techn.text, relConf);
|
||||
setC4ShapeText('techn', rel, relTextWrap, relConf, textLimitWidth);
|
||||
}
|
||||
|
||||
if (rel.descr && rel.descr.text !== '') {
|
||||
textLimitWidth = calculateTextWidth(rel.descr.text, relConf);
|
||||
setC4ShapeText('descr', rel, relTextWrap, relConf, textLimitWidth);
|
||||
}
|
||||
|
||||
let fromNode = getC4ShapeObj(rel.from);
|
||||
let endNode = getC4ShapeObj(rel.to);
|
||||
let points = getIntersectPoints(fromNode, endNode);
|
||||
rel.startPoint = points.startPoint;
|
||||
rel.endPoint = points.endPoint;
|
||||
}
|
||||
svgDraw.drawRels(diagram, rels, conf);
|
||||
};
|
||||
|
||||
export const setConf = function (cnf) {
|
||||
assignWithDepth(conf, cnf);
|
||||
|
||||
if (cnf.fontFamily) {
|
||||
conf.personFontFamily = conf.systemFontFamily = conf.messageFontFamily = cnf.fontFamily;
|
||||
}
|
||||
if (cnf.fontSize) {
|
||||
conf.personFontSize = conf.systemFontSize = conf.messageFontSize = cnf.fontSize;
|
||||
}
|
||||
if (cnf.fontWeight) {
|
||||
conf.personFontWeight = conf.systemFontWeight = conf.messageFontWeight = cnf.fontWeight;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param diagram
|
||||
* @param parentBoundaryAlias
|
||||
* @param parentBounds
|
||||
* @param currentBoundarys
|
||||
*/
|
||||
function drawInsideBoundary(diagram, parentBoundaryAlias, parentBounds, currentBoundarys) {
|
||||
let currentBounds = new Bounds();
|
||||
// Calculate the width limit of the boundar. label/type 的长度,
|
||||
currentBounds.data.widthLimit = Math.min(
|
||||
conf.width * conf.c4ShapeInRow + conf.c4ShapeMargin * (conf.c4ShapeInRow + 1),
|
||||
parentBounds.data.widthLimit / Math.min(conf.c4BoundaryInRow, currentBoundarys.length)
|
||||
);
|
||||
for (let i = 0; i < currentBoundarys.length; i++) {
|
||||
let currentBoundary = currentBoundarys[i];
|
||||
if (i == 0) {
|
||||
// Calculate the drawing start point of the currentBoundarys.
|
||||
let _x = parentBounds.data.startx + conf.diagramMarginX;
|
||||
let _y = parentBounds.data.stopy + conf.diagramMarginY;
|
||||
|
||||
currentBounds.setData(_x, _x, _y, _y);
|
||||
} else {
|
||||
// Calculate the drawing start point of the currentBoundarys.
|
||||
let _x =
|
||||
currentBounds.data.stopx !== currentBounds.data.startx
|
||||
? currentBounds.data.stopx + conf.diagramMarginX
|
||||
: currentBounds.data.startx;
|
||||
let _y = currentBounds.data.starty;
|
||||
|
||||
currentBounds.setData(_x, _x, _y, _y);
|
||||
}
|
||||
currentBounds.name = currentBoundary.alias;
|
||||
let currentPersonOrSystemArray = parser.yy.getPersonOrSystemArray(currentBoundary.alias);
|
||||
let currentPersonOrSystemKeys = parser.yy.getPersonOrSystemKeys(currentBoundary.alias);
|
||||
|
||||
if (currentPersonOrSystemKeys.length > 0) {
|
||||
drawPersonOrSystemArray(
|
||||
currentBounds,
|
||||
diagram,
|
||||
currentPersonOrSystemArray,
|
||||
currentPersonOrSystemKeys
|
||||
);
|
||||
}
|
||||
parentBoundaryAlias = currentBoundary.alias;
|
||||
let nextCurrentBoundarys = parser.yy.getBoundarys(parentBoundaryAlias);
|
||||
|
||||
if (nextCurrentBoundarys.length > 0) {
|
||||
// draw boundary inside currentBoundary
|
||||
// bounds.init();
|
||||
// parentBoundaryWidthLimit = bounds.data.stopx - bounds.startx;
|
||||
drawInsideBoundary(diagram, parentBoundaryAlias, currentBounds, nextCurrentBoundarys);
|
||||
}
|
||||
// draw boundary
|
||||
if (currentBoundary.alias !== 'global') drawBoundary(diagram, currentBoundary, currentBounds);
|
||||
parentBounds.data.stopy = Math.max(
|
||||
currentBounds.data.stopy + conf.c4ShapeMargin,
|
||||
parentBounds.data.stopy
|
||||
);
|
||||
parentBounds.data.stopx = Math.max(
|
||||
currentBounds.data.stopx + conf.c4ShapeMargin,
|
||||
parentBounds.data.stopx
|
||||
);
|
||||
globalBoundaryMaxX = Math.max(globalBoundaryMaxX, parentBounds.data.stopx);
|
||||
globalBoundaryMaxY = Math.max(globalBoundaryMaxY, parentBounds.data.stopy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
|
||||
*
|
||||
* @param {any} text
|
||||
* @param {any} id
|
||||
*/
|
||||
export const draw = function (text, id) {
|
||||
conf = configApi.getConfig().c4;
|
||||
const securityLevel = configApi.getConfig().securityLevel;
|
||||
// Handle root and ocument for when rendering in sanbox mode
|
||||
let sandboxElement;
|
||||
if (securityLevel === 'sandbox') {
|
||||
sandboxElement = select('#i' + id);
|
||||
}
|
||||
const root =
|
||||
securityLevel === 'sandbox'
|
||||
? select(sandboxElement.nodes()[0].contentDocument.body)
|
||||
: select('body');
|
||||
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
||||
|
||||
parser.yy.clear();
|
||||
parser.yy.setWrap(conf.wrap);
|
||||
parser.parse(text + '\n');
|
||||
|
||||
log.debug(`C:${JSON.stringify(conf, null, 2)}`);
|
||||
|
||||
const diagram =
|
||||
securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : select(`[id="${id}"]`);
|
||||
|
||||
svgDraw.insertComputerIcon(diagram);
|
||||
svgDraw.insertDatabaseIcon(diagram);
|
||||
svgDraw.insertClockIcon(diagram);
|
||||
|
||||
let screenBounds = new Bounds();
|
||||
screenBounds.setData(
|
||||
conf.diagramMarginX,
|
||||
conf.diagramMarginX,
|
||||
conf.diagramMarginY,
|
||||
conf.diagramMarginY
|
||||
);
|
||||
|
||||
screenBounds.data.widthLimit = screen.availWidth;
|
||||
globalBoundaryMaxX = conf.diagramMarginX;
|
||||
globalBoundaryMaxY = conf.diagramMarginY;
|
||||
|
||||
const title = parser.yy.getTitle();
|
||||
const c4type = parser.yy.getC4Type();
|
||||
let currentBoundarys = parser.yy.getBoundarys('');
|
||||
switch (c4type) {
|
||||
case 'C4Context':
|
||||
drawInsideBoundary(diagram, '', screenBounds, currentBoundarys);
|
||||
break;
|
||||
}
|
||||
|
||||
// The arrow head definition is attached to the svg once
|
||||
svgDraw.insertArrowHead(diagram);
|
||||
svgDraw.insertArrowEnd(diagram);
|
||||
svgDraw.insertArrowCrossHead(diagram);
|
||||
svgDraw.insertArrowFilledHead(diagram);
|
||||
|
||||
drawRels(diagram, parser.yy.getRels(), parser.yy.getPersonOrSystem);
|
||||
|
||||
screenBounds.data.stopx = globalBoundaryMaxX;
|
||||
screenBounds.data.stopy = globalBoundaryMaxY;
|
||||
|
||||
const box = screenBounds.data;
|
||||
|
||||
// Make sure the height of the diagram supports long menus.
|
||||
let boxHeight = box.stopy - box.starty;
|
||||
|
||||
let height = boxHeight + 2 * conf.diagramMarginY;
|
||||
|
||||
// Make sure the width of the diagram supports wide menus.
|
||||
let boxWidth = box.stopx - box.startx;
|
||||
const width = boxWidth + 2 * conf.diagramMarginX;
|
||||
|
||||
if (title) {
|
||||
diagram
|
||||
.append('text')
|
||||
.text(title)
|
||||
.attr('x', (box.stopx - box.startx) / 2 - 4 * conf.diagramMarginX)
|
||||
.attr('y', -25);
|
||||
}
|
||||
|
||||
configureSvgSize(diagram, height, width, conf.useMaxWidth);
|
||||
|
||||
const extraVertForTitle = title ? 60 : 0;
|
||||
diagram.attr(
|
||||
'viewBox',
|
||||
box.startx -
|
||||
conf.diagramMarginX +
|
||||
' -' +
|
||||
(conf.diagramMarginY + extraVertForTitle) +
|
||||
' ' +
|
||||
width +
|
||||
' ' +
|
||||
(height + extraVertForTitle)
|
||||
);
|
||||
|
||||
addSVGAccessibilityFields(parser.yy, diagram, id);
|
||||
log.debug(`models:`, box);
|
||||
};
|
||||
|
||||
export default {
|
||||
drawPersonOrSystemArray,
|
||||
drawBoundary,
|
||||
setConf,
|
||||
draw,
|
||||
};
|
267
src/diagrams/c4/parser/c4Diagram.jison
Normal file
267
src/diagrams/c4/parser/c4Diagram.jison
Normal file
@@ -0,0 +1,267 @@
|
||||
/** mermaid
|
||||
* https://mermaidjs.github.io/
|
||||
* (c) 2022 mzhx.meng@gmail.com
|
||||
* MIT license.
|
||||
*/
|
||||
|
||||
/* lexical grammar */
|
||||
%lex
|
||||
|
||||
/* context */
|
||||
%x person
|
||||
%x person_ext
|
||||
%x system
|
||||
%x system_db
|
||||
%x system_queue
|
||||
%x system_ext
|
||||
%x system_ext_db
|
||||
%x system_ext_queue
|
||||
%x boundary
|
||||
%x enterprise_boundary
|
||||
%x system_boundary
|
||||
%x rel
|
||||
%x birel
|
||||
%x rel_u
|
||||
%x rel_d
|
||||
%x rel_l
|
||||
%x rel_r
|
||||
|
||||
/* container */
|
||||
%x container
|
||||
%x container_db
|
||||
%x container_queue
|
||||
%x container_ext
|
||||
%x container_ext_db
|
||||
%x container_ext_queue
|
||||
%x container_boundary
|
||||
|
||||
/* component */
|
||||
%x component
|
||||
%x component_db
|
||||
%x component_queue
|
||||
%x component_ext
|
||||
%x component_ext_db
|
||||
%x component_ext_queue
|
||||
|
||||
/* Dynamic diagram */
|
||||
%x rel_index
|
||||
%x index
|
||||
|
||||
/* Deployment diagram */
|
||||
%x deployment_node
|
||||
%x node
|
||||
%x node_l
|
||||
%x node_r
|
||||
|
||||
/* Relationship Types */
|
||||
%x rel
|
||||
%x rel_bi
|
||||
%x rel_up
|
||||
%x rel_down
|
||||
%x rel_left
|
||||
%x rel_right
|
||||
|
||||
%x attribute
|
||||
%x string
|
||||
|
||||
%x open_directive
|
||||
%x type_directive
|
||||
%x arg_directive
|
||||
|
||||
%%
|
||||
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
|
||||
.*direction\s+TB[^\n]* return 'direction_tb';
|
||||
.*direction\s+BT[^\n]* return 'direction_bt';
|
||||
.*direction\s+RL[^\n]* return 'direction_rl';
|
||||
.*direction\s+LR[^\n]* return 'direction_lr';
|
||||
<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]*(\r?\n?)+ /* skip comments */
|
||||
\%\%[^\n]*(\r?\n)* c /* skip comments */
|
||||
|
||||
"title"\s[^#\n;]+ return 'title';
|
||||
"accDescription"\s[^#\n;]+ return 'accDescription';
|
||||
|
||||
\s*(\r?\n)+ return 'NEWLINE';
|
||||
\s+ /* skip whitespace */
|
||||
"C4Context" return 'C4_CONTEXT';
|
||||
"C4Container" return 'C4_CONTAINER';
|
||||
"C4Component" return 'C4_COMPONENT';
|
||||
"C4Dynamic" return 'C4_DYNAMIC';
|
||||
"C4Deployment" return 'C4_DEPLOYMENT';
|
||||
|
||||
"Person_Ext" { this.begin("person_ext"); console.log('begin person_ext'); return 'PERSON_EXT';}
|
||||
"Person" { this.begin("person"); console.log('begin person'); return 'PERSON';}
|
||||
"SystemQueue_Ext" { this.begin("system_ext_queue"); console.log('begin system_ext_queue'); return 'SYSTEM_EXT_QUEUE';}
|
||||
"SystemDb_Ext" { this.begin("system_ext_db"); console.log('begin system_ext_db'); return 'SYSTEM_EXT_DB';}
|
||||
"System_Ext" { this.begin("system_ext"); console.log('begin system_ext'); return 'SYSTEM_EXT';}
|
||||
"SystemQueue" { this.begin("system_queue"); console.log('begin system_queue'); return 'SYSTEM_QUEUE';}
|
||||
"SystemDb" { this.begin("system_db"); console.log('begin system_db'); return 'SYSTEM_DB';}
|
||||
"System" { this.begin("system"); console.log('begin system'); return 'SYSTEM';}
|
||||
|
||||
"Boundary" { this.begin("boundary"); console.log('begin boundary'); return 'BOUNDARY';}
|
||||
"Enterprise_Boundary" { this.begin("enterprise_boundary"); console.log('begin enterprise_boundary'); return 'ENTERPRISE_BOUNDARY';}
|
||||
"System_Boundary" { this.begin("system_boundary"); console.log('begin system_boundary'); return 'SYSTEM_BOUNDARY';}
|
||||
|
||||
"Rel" { this.begin("rel"); console.log('begin rel'); return 'REL';}
|
||||
"BiRel" { this.begin("birel"); console.log('begin birel'); return 'BIREL';}
|
||||
"Rel_U|Rel_Up" { this.begin("rel_u"); console.log('begin rel_u'); return 'REL_U';}
|
||||
"Rel_D|Rel_Down" { this.begin("rel_d"); console.log('begin rel_d'); return 'REL_D';}
|
||||
"Rel_L|Rel_Left" { this.begin("rel_l"); console.log('begin rel_l'); return 'REL_L';}
|
||||
"Rel_R|Rel_Right" { this.begin("rel_r"); console.log('begin rel_r'); return 'REL_R';}
|
||||
|
||||
|
||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,rel,birel,rel_u,rel_d,rel_l,rel_r><<EOF>> return "EOF_IN_STRUCT";
|
||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,rel,birel,rel_u,rel_d,rel_l,rel_r>[(][ ]*[,] { console.log('begin attribute with ATTRIBUTE_EMPTY'); this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
|
||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,rel,birel,rel_u,rel_d,rel_l,rel_r>[(] { console.log('begin attribute'); this.begin("attribute"); }
|
||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,rel,birel,rel_u,rel_d,rel_l,rel_r,attribute>[)] { console.log('STOP attribute'); this.popState();console.log('STOP diagram'); this.popState();}
|
||||
|
||||
<attribute>",," { console.log(',,'); return 'ATTRIBUTE_EMPTY';}
|
||||
<attribute>"," { console.log(','); }
|
||||
<attribute>[ ]*["]["] { console.log('ATTRIBUTE_EMPTY'); return 'ATTRIBUTE_EMPTY';}
|
||||
<attribute>[ ]*["] { console.log('begin string'); this.begin("string");}
|
||||
<string>["] { console.log('STOP string'); this.popState(); }
|
||||
<string>[^"]* { console.log('STR'); return "STR";}
|
||||
<attribute>[^,]+ { console.log('not STR'); return "STR";}
|
||||
|
||||
'{' { /* this.begin("lbrace"); */ console.log('begin boundary block'); return "LBRACE";}
|
||||
'}' { /* this.popState(); */ console.log('STOP boundary block'); return "RBRACE";}
|
||||
|
||||
[\s]+ return 'SPACE';
|
||||
[\n\r]+ return 'EOL';
|
||||
<<EOF>> return 'EOF';
|
||||
|
||||
/lex
|
||||
|
||||
/* operator associations and precedence */
|
||||
|
||||
%left '^'
|
||||
|
||||
%start start
|
||||
|
||||
%% /* language grammar */
|
||||
|
||||
start
|
||||
: mermaidDoc
|
||||
| direction
|
||||
| directive start
|
||||
;
|
||||
|
||||
direction
|
||||
: direction_tb
|
||||
{ yy.setDirection('TB');}
|
||||
| direction_bt
|
||||
{ yy.setDirection('BT');}
|
||||
| direction_rl
|
||||
{ yy.setDirection('RL');}
|
||||
| direction_lr
|
||||
{ yy.setDirection('LR');}
|
||||
;
|
||||
|
||||
mermaidDoc
|
||||
: graphConfig
|
||||
;
|
||||
|
||||
directive
|
||||
: openDirective typeDirective closeDirective NEWLINE
|
||||
| openDirective typeDirective ':' argDirective closeDirective NEWLINE
|
||||
;
|
||||
|
||||
openDirective
|
||||
: open_directive { console.log("open_directive: ", $1); yy.parseDirective('%%{', 'open_directive'); }
|
||||
;
|
||||
|
||||
typeDirective
|
||||
: type_directive { }
|
||||
;
|
||||
|
||||
argDirective
|
||||
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); console.log("arg_directive: ", $1); yy.parseDirective($1, 'arg_directive'); }
|
||||
;
|
||||
|
||||
closeDirective
|
||||
: close_directive { console.log("close_directive: ", $1); yy.parseDirective('}%%', 'close_directive', 'c4Context'); }
|
||||
;
|
||||
|
||||
graphConfig
|
||||
: C4_CONTEXT NEWLINE statements EOF {yy.setC4Type($1)}
|
||||
| C4_CONTAINER NEWLINE statements EOF {yy.setC4Type($1)}
|
||||
| C4_COMPONENT NEWLINE statements EOF {yy.setC4Type($1)}
|
||||
| C4_DYNAMIC NEWLINE statements EOF {yy.setC4Type($1)}
|
||||
| C4_DEPLOYMENT NEWLINE statements EOF {yy.setC4Type($1)}
|
||||
;
|
||||
|
||||
statements
|
||||
: otherStatements
|
||||
| diagramStatements
|
||||
| otherStatements diagramStatements
|
||||
;
|
||||
|
||||
otherStatements
|
||||
: otherStatement
|
||||
| otherStatement NEWLINE
|
||||
| otherStatement NEWLINE otherStatements
|
||||
;
|
||||
|
||||
otherStatement
|
||||
: title {yy.setTitle($1.substring(6));$$=$1.substring(6);}
|
||||
| accDescription {yy.setAccDescription($1.substring(15));$$=$1.substring(15);}
|
||||
;
|
||||
|
||||
boundaryStatement
|
||||
: boundaryStartStatement diagramStatements boundaryStopStatement
|
||||
;
|
||||
|
||||
boundaryStartStatement
|
||||
: boundaryStart LBRACE NEWLINE
|
||||
| boundaryStart NEWLINE LBRACE
|
||||
| boundaryStart NEWLINE LBRACE NEWLINE
|
||||
;
|
||||
|
||||
boundaryStart
|
||||
: ENTERPRISE_BOUNDARY attributes {console.log($1,JSON.stringify($2)); $2.splice(2, 0, 'ENTERPRISE'); yy.addBoundary(...$2); $$=$2;}
|
||||
| SYSTEM_BOUNDARY attributes {console.log($1,JSON.stringify($2)); $2.splice(2, 0, 'ENTERPRISE'); yy.addBoundary(...$2); $$=$2;}
|
||||
| BOUNDARY attributes {console.log($1,JSON.stringify($2)); yy.addBoundary(...$2); $$=$2;}
|
||||
;
|
||||
|
||||
boundaryStopStatement
|
||||
: RBRACE { yy.popBoundaryParseStack() }
|
||||
;
|
||||
|
||||
diagramStatements
|
||||
: diagramStatement
|
||||
| diagramStatement NEWLINE
|
||||
| diagramStatement NEWLINE statements
|
||||
;
|
||||
|
||||
diagramStatement
|
||||
: PERSON attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('person', ...$2); $$=$2;}
|
||||
| PERSON_EXT attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('external_person', ...$2); $$=$2;}
|
||||
| SYSTEM attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('system', ...$2); $$=$2;}
|
||||
| SYSTEM_DB attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('system_db', ...$2); $$=$2;}
|
||||
| SYSTEM_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('system_queue', ...$2); $$=$2;}
|
||||
| SYSTEM_EXT attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('external_system', ...$2); $$=$2;}
|
||||
| SYSTEM_EXT_DB attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('external_system_db', ...$2); $$=$2;}
|
||||
| SYSTEM_EXT_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('external_system_queue', ...$2); $$=$2;}
|
||||
| boundaryStatement
|
||||
| REL attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel', ...$2); $$=$2;}
|
||||
| BIREL attributes {console.log($1,JSON.stringify($2)); yy.addRel('birel', ...$2); $$=$2;}
|
||||
| REL_U attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_u', ...$2); $$=$2;}
|
||||
| REL_D attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_d', ...$2); $$=$2;}
|
||||
| REL_L attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_l', ...$2); $$=$2;}
|
||||
| REL_R attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_r', ...$2); $$=$2;}
|
||||
;
|
||||
|
||||
attributes
|
||||
: attribute { console.log('PUSH ATTRIBUTE: ', $1); $$ = [$1]; }
|
||||
| attribute attributes { console.log('PUSH ATTRIBUTE: ', $1); $2.unshift($1); $$=$2;}
|
||||
;
|
||||
|
||||
attribute
|
||||
: STR { $$ = $1.trim(); }
|
||||
| ATTRIBUTE { $$ = $1.trim(); }
|
||||
| ATTRIBUTE_EMPTY { $$ = ""; }
|
||||
;
|
||||
|
8
src/diagrams/c4/styles.js
Normal file
8
src/diagrams/c4/styles.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const getStyles = (options) =>
|
||||
`.person {
|
||||
stroke: ${options.personBorder};
|
||||
fill: ${options.personBkg};
|
||||
}
|
||||
`;
|
||||
|
||||
export default getStyles;
|
805
src/diagrams/c4/svgDraw.js
Normal file
805
src/diagrams/c4/svgDraw.js
Normal file
File diff suppressed because one or more lines are too long
@@ -19,6 +19,9 @@ import { select } from 'd3';
|
||||
import { compile, serialize, stringify } from 'stylis';
|
||||
import pkg from '../package.json';
|
||||
import * as configApi from './config';
|
||||
import c4Db from './diagrams/c4/c4Db';
|
||||
import c4Renderer from './diagrams/c4/c4Renderer';
|
||||
import c4Parser from './diagrams/c4/parser/c4Diagram';
|
||||
import classDb from './diagrams/class/classDb';
|
||||
import classRenderer from './diagrams/class/classRenderer';
|
||||
import classRendererV2 from './diagrams/class/classRenderer-v2';
|
||||
@@ -84,6 +87,11 @@ function parse(text) {
|
||||
|
||||
log.debug('Type ' + graphType);
|
||||
switch (graphType) {
|
||||
case 'c4':
|
||||
c4Db.clear();
|
||||
parser = c4Parser;
|
||||
parser.parser.yy = c4Parser;
|
||||
break;
|
||||
case 'gitGraph':
|
||||
gitGraphAst.clear();
|
||||
parser = gitGraphParser;
|
||||
@@ -449,6 +457,10 @@ const render = function (id, _txt, cb, container) {
|
||||
|
||||
try {
|
||||
switch (graphType) {
|
||||
case 'c4':
|
||||
c4Renderer.setConf(cnf.c4);
|
||||
c4Renderer.draw(txt, id);
|
||||
break;
|
||||
case 'gitGraph':
|
||||
// cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
//gitGraphRenderer.setConf(cnf.git);
|
||||
|
@@ -9,6 +9,7 @@ import requirement from './diagrams/requirement/styles';
|
||||
import sequence from './diagrams/sequence/styles';
|
||||
import stateDiagram from './diagrams/state/styles';
|
||||
import journey from './diagrams/user-journey/styles';
|
||||
import c4 from './diagrams/c4/styles';
|
||||
|
||||
const themes = {
|
||||
flowchart,
|
||||
@@ -26,6 +27,7 @@ const themes = {
|
||||
er,
|
||||
journey,
|
||||
requirement,
|
||||
c4,
|
||||
};
|
||||
|
||||
export const calcThemeVariables = (theme, userOverRides) => theme.calcColors(userOverRides);
|
||||
|
4
src/themes/c4.scss
Normal file
4
src/themes/c4.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
.person {
|
||||
stroke: $personBorder;
|
||||
fill: $personBkg;
|
||||
}
|
@@ -56,6 +56,11 @@ $critBorderColor: #ff8888;
|
||||
$critBkgColor: red;
|
||||
$todayLineColor: red;
|
||||
|
||||
/* C4 Context Diagram variables */
|
||||
|
||||
$personBorder: $border1;
|
||||
$personBkg: $mainBkg;
|
||||
|
||||
/* state colors */
|
||||
$labelColor: black;
|
||||
|
||||
|
@@ -113,6 +113,11 @@ class Theme {
|
||||
this.taskTextDarkColor = this.taskTextDarkColor || this.textColor;
|
||||
this.taskTextClickableColor = this.taskTextClickableColor || '#003163';
|
||||
|
||||
/* Sequence Diagram variables */
|
||||
|
||||
this.personBorder = this.personBorder || this.primaryBorderColor;
|
||||
this.personBkg = this.personBkg || this.mainBkg;
|
||||
|
||||
/* state colors */
|
||||
this.transitionColor = this.transitionColor || this.lineColor;
|
||||
this.transitionLabelColor = this.transitionLabelColor || this.textColor;
|
||||
|
@@ -78,6 +78,11 @@ class Theme {
|
||||
this.taskTextDarkColor = 'calculated';
|
||||
this.todayLineColor = '#DB5757';
|
||||
|
||||
/* C4 Context Diagram variables */
|
||||
|
||||
this.personBorder = 'calculated';
|
||||
this.personBkg = 'calculated';
|
||||
|
||||
/* state colors */
|
||||
this.labelColor = 'calculated';
|
||||
|
||||
|
@@ -103,6 +103,11 @@ class Theme {
|
||||
this.critBkgColor = 'red';
|
||||
this.todayLineColor = 'red';
|
||||
|
||||
/* C4 Context Diagram variables */
|
||||
|
||||
this.personBorder = 'calculated';
|
||||
this.personBkg = 'calculated';
|
||||
|
||||
/* state colors */
|
||||
this.labelColor = 'black';
|
||||
this.errorBkgColor = '#552222';
|
||||
|
@@ -76,6 +76,11 @@ class Theme {
|
||||
this.critBkgColor = 'red';
|
||||
this.todayLineColor = 'red';
|
||||
|
||||
/* C4 Context Diagram variables */
|
||||
|
||||
this.personBorder = 'calculated';
|
||||
this.personBkg = 'calculated';
|
||||
|
||||
/* state colors */
|
||||
this.labelColor = 'black';
|
||||
|
||||
|
@@ -89,6 +89,11 @@ class Theme {
|
||||
this.critBorderColor = 'calculated';
|
||||
this.todayLineColor = 'calculated';
|
||||
|
||||
/* C4 Context Diagram variables */
|
||||
|
||||
this.personBorder = 'calculated';
|
||||
this.personBkg = 'calculated';
|
||||
|
||||
/* state colors */
|
||||
this.labelColor = 'black';
|
||||
|
||||
|
@@ -185,6 +185,10 @@ export const detectDirective = function (text, type = null) {
|
||||
*/
|
||||
export const detectType = function (text, cnf) {
|
||||
text = text.replace(directive, '').replace(anyComment, '\n');
|
||||
if (text.match(/^\s*C4Context|C4Container|C4Component|C4Dynamic|C4Deployment/)) {
|
||||
return 'c4';
|
||||
}
|
||||
|
||||
if (text.match(/^\s*sequenceDiagram/)) {
|
||||
return 'sequence';
|
||||
}
|
||||
|
Reference in New Issue
Block a user