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:
pinghe
2022-05-15 13:21:16 +08:00
parent 18114ab9ce
commit 015c112103
18 changed files with 2253 additions and 0 deletions

View File

@@ -20,6 +20,44 @@
</div> </div>
<hr /> <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"> <div class="mermaid">
pie pie
title Key elements in Product X title Key elements in Product X

View File

@@ -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 classDb from './diagrams/class/classDb';
import classRenderer from './diagrams/class/classRenderer'; import classRenderer from './diagrams/class/classRenderer';
import classRendererV2 from './diagrams/class/classRenderer-v2'; import classRendererV2 from './diagrams/class/classRenderer-v2';
@@ -49,6 +52,12 @@ class Diagram {
this.type = utils.detectType(txt, cnf); this.type = utils.detectType(txt, cnf);
log.debug('Type ' + this.type); log.debug('Type ' + this.type);
switch (this.type) { switch (this.type) {
case 'c4':
this.parser = c4Parser;
this.parser.parser.yy = c4Db;
this.db = c4Db;
this.renderer = c4Renderer;
break;
case 'gitGraph': case 'gitGraph':
this.parser = gitGraphParser; this.parser = gitGraphParser;
this.parser.parser.yy = gitGraphAst; this.parser.parser.yy = gitGraphAst;

View File

@@ -1059,6 +1059,165 @@ const config = {
showCommitLabel: true, showCommitLabel: true,
showBranches: 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; config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;

307
src/diagrams/c4/c4Db.js Normal file
View 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,
};

View 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,
};

View 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 { $$ = ""; }
;

View 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

File diff suppressed because one or more lines are too long

View File

@@ -19,6 +19,9 @@ import { select } from 'd3';
import { compile, serialize, stringify } from 'stylis'; import { compile, serialize, stringify } from 'stylis';
import pkg from '../package.json'; import pkg from '../package.json';
import * as configApi from './config'; 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 classDb from './diagrams/class/classDb';
import classRenderer from './diagrams/class/classRenderer'; import classRenderer from './diagrams/class/classRenderer';
import classRendererV2 from './diagrams/class/classRenderer-v2'; import classRendererV2 from './diagrams/class/classRenderer-v2';
@@ -84,6 +87,11 @@ function parse(text) {
log.debug('Type ' + graphType); log.debug('Type ' + graphType);
switch (graphType) { switch (graphType) {
case 'c4':
c4Db.clear();
parser = c4Parser;
parser.parser.yy = c4Parser;
break;
case 'gitGraph': case 'gitGraph':
gitGraphAst.clear(); gitGraphAst.clear();
parser = gitGraphParser; parser = gitGraphParser;
@@ -449,6 +457,10 @@ const render = function (id, _txt, cb, container) {
try { try {
switch (graphType) { switch (graphType) {
case 'c4':
c4Renderer.setConf(cnf.c4);
c4Renderer.draw(txt, id);
break;
case 'gitGraph': case 'gitGraph':
// cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute; // cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
//gitGraphRenderer.setConf(cnf.git); //gitGraphRenderer.setConf(cnf.git);

View File

@@ -9,6 +9,7 @@ import requirement from './diagrams/requirement/styles';
import sequence from './diagrams/sequence/styles'; import sequence from './diagrams/sequence/styles';
import stateDiagram from './diagrams/state/styles'; import stateDiagram from './diagrams/state/styles';
import journey from './diagrams/user-journey/styles'; import journey from './diagrams/user-journey/styles';
import c4 from './diagrams/c4/styles';
const themes = { const themes = {
flowchart, flowchart,
@@ -26,6 +27,7 @@ const themes = {
er, er,
journey, journey,
requirement, requirement,
c4,
}; };
export const calcThemeVariables = (theme, userOverRides) => theme.calcColors(userOverRides); export const calcThemeVariables = (theme, userOverRides) => theme.calcColors(userOverRides);

4
src/themes/c4.scss Normal file
View File

@@ -0,0 +1,4 @@
.person {
stroke: $personBorder;
fill: $personBkg;
}

View File

@@ -56,6 +56,11 @@ $critBorderColor: #ff8888;
$critBkgColor: red; $critBkgColor: red;
$todayLineColor: red; $todayLineColor: red;
/* C4 Context Diagram variables */
$personBorder: $border1;
$personBkg: $mainBkg;
/* state colors */ /* state colors */
$labelColor: black; $labelColor: black;

View File

@@ -113,6 +113,11 @@ class Theme {
this.taskTextDarkColor = this.taskTextDarkColor || this.textColor; this.taskTextDarkColor = this.taskTextDarkColor || this.textColor;
this.taskTextClickableColor = this.taskTextClickableColor || '#003163'; this.taskTextClickableColor = this.taskTextClickableColor || '#003163';
/* Sequence Diagram variables */
this.personBorder = this.personBorder || this.primaryBorderColor;
this.personBkg = this.personBkg || this.mainBkg;
/* state colors */ /* state colors */
this.transitionColor = this.transitionColor || this.lineColor; this.transitionColor = this.transitionColor || this.lineColor;
this.transitionLabelColor = this.transitionLabelColor || this.textColor; this.transitionLabelColor = this.transitionLabelColor || this.textColor;

View File

@@ -78,6 +78,11 @@ class Theme {
this.taskTextDarkColor = 'calculated'; this.taskTextDarkColor = 'calculated';
this.todayLineColor = '#DB5757'; this.todayLineColor = '#DB5757';
/* C4 Context Diagram variables */
this.personBorder = 'calculated';
this.personBkg = 'calculated';
/* state colors */ /* state colors */
this.labelColor = 'calculated'; this.labelColor = 'calculated';

View File

@@ -103,6 +103,11 @@ class Theme {
this.critBkgColor = 'red'; this.critBkgColor = 'red';
this.todayLineColor = 'red'; this.todayLineColor = 'red';
/* C4 Context Diagram variables */
this.personBorder = 'calculated';
this.personBkg = 'calculated';
/* state colors */ /* state colors */
this.labelColor = 'black'; this.labelColor = 'black';
this.errorBkgColor = '#552222'; this.errorBkgColor = '#552222';

View File

@@ -76,6 +76,11 @@ class Theme {
this.critBkgColor = 'red'; this.critBkgColor = 'red';
this.todayLineColor = 'red'; this.todayLineColor = 'red';
/* C4 Context Diagram variables */
this.personBorder = 'calculated';
this.personBkg = 'calculated';
/* state colors */ /* state colors */
this.labelColor = 'black'; this.labelColor = 'black';

View File

@@ -89,6 +89,11 @@ class Theme {
this.critBorderColor = 'calculated'; this.critBorderColor = 'calculated';
this.todayLineColor = 'calculated'; this.todayLineColor = 'calculated';
/* C4 Context Diagram variables */
this.personBorder = 'calculated';
this.personBkg = 'calculated';
/* state colors */ /* state colors */
this.labelColor = 'black'; this.labelColor = 'black';

View File

@@ -185,6 +185,10 @@ export const detectDirective = function (text, type = null) {
*/ */
export const detectType = function (text, cnf) { export const detectType = function (text, cnf) {
text = text.replace(directive, '').replace(anyComment, '\n'); text = text.replace(directive, '').replace(anyComment, '\n');
if (text.match(/^\s*C4Context|C4Container|C4Component|C4Dynamic|C4Deployment/)) {
return 'c4';
}
if (text.match(/^\s*sequenceDiagram/)) { if (text.match(/^\s*sequenceDiagram/)) {
return 'sequence'; return 'sequence';
} }