Merge pull request #3038 from pinghe/develop

Add C4 Diagram. Compatible with C4-PlantUML syntax.
This commit is contained in:
Knut Sveidqvist 2022-06-07 17:12:10 +02:00 committed by GitHub
commit 3300ad09f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 2979 additions and 0 deletions

View File

@ -20,6 +20,165 @@
</div>
<hr />
<div class="mermaid">
C4Context
title System Context diagram for Internet Banking System
Enterprise_Boundary(b0, "BankBoundary0") {
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", "desc")
Person(customerD, "Banking Customer D", "A customer of the bank, <br/> with personal bank accounts.")
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
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. next line.")
}
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.")
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>
<div class="mermaid">
C4Container
title Container diagram for Internet Banking System
System_Ext(email_system, "E-Mail System", "The internal Microsoft Exchange system")
Person(customer, Customer, "A customer of the bank, with personal bank accounts")
Container_Boundary(c1, "Internet Banking") {
Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to cutomers via their web browser")
Container_Ext(mobile_app, "Mobile App", "C#, Xamarin", "Provides a limited subset of the Internet banking functionality to customers via their mobile device")
Container(web_app, "Web Application", "Java, Spring MVC", "Delivers the static content and the Internet banking SPA")
ContainerDb(database, "Database", "SQL Database", "Stores user registration information, hashed auth credentials, access logs, etc.")
ContainerDb_Ext(backend_api, "API Application", "Java, Docker Container", "Provides Internet banking functionality via API")
}
System_Ext(banking_system, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
Rel(customer, web_app, "Uses", "HTTPS")
Rel(customer, spa, "Uses", "HTTPS")
Rel(customer, mobile_app, "Uses")
Rel(web_app, spa, "Delivers")
Rel(spa, backend_api, "Uses", "async, JSON/HTTPS")
Rel(mobile_app, backend_api, "Uses", "async, JSON/HTTPS")
Rel_Back(database, backend_api, "Reads from and writes to", "sync, JDBC")
Rel(email_system, customer, "Sends e-mails to")
Rel(backend_api, email_system, "Sends e-mails using", "sync, SMTP")
Rel(backend_api, banking_system, "Uses", "sync/async, XML/HTTPS")
</div>
<div class="mermaid">
C4Component
title Component diagram for Internet Banking System - API Application
Container(spa, "Single Page Application", "javascript and angular", "Provides all the internet banking functionality to customers via their web browser.")
Container(ma, "Mobile App", "Xamarin", "Provides a limited subset ot the internet banking functionality to customers via their mobile mobile device.")
ContainerDb(db, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
System_Ext(mbs, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
Container_Boundary(api, "API Application") {
Component(sign, "Sign In Controller", "MVC Rest Controlle", "Allows users to sign in to the internet banking system")
Component(accounts, "Accounts Summary Controller", "MVC Rest Controller", "Provides customers with a summary of their bank accounts")
Component(security, "Security Component", "Spring Bean", "Provides functionality related to singing in, changing passwords, etc.")
Component(mbsfacade, "Mainframe Banking System Facade", "Spring Bean", "A facade onto the mainframe banking system.")
Rel(sign, security, "Uses")
Rel(accounts, mbsfacade, "Uses")
Rel(security, db, "Read & write to", "JDBC")
Rel(mbsfacade, mbs, "Uses", "XML/HTTPS")
}
Rel_Back(spa, sign, "Uses", "JSON/HTTPS")
Rel(spa, accounts, "Uses", "JSON/HTTPS")
Rel(ma, sign, "Uses", "JSON/HTTPS")
Rel(ma, accounts, "Uses", "JSON/HTTPS")
</div>
<div class="mermaid">
C4Dynamic
title Dynamic diagram for Internet Banking System - API Application
ContainerDb(c4, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
Container(c1, "Single-Page Application", "JavaScript and Angular", "Provides all of the Internet banking functionality to customers via their web browser.")
Container_Boundary(b, "API Application") {
Component(c3, "Security Component", "Spring Bean", "Provides functionality Related to signing in, changing passwords, etc.")
Component(c2, "Sign In Controller", "Spring MVC Rest Controller", "Allows users to sign in to the Internet Banking System.")
}
Rel(c1, c2, "Submits credentials to", "JSON/HTTPS")
Rel(c2, c3, "Calls isAuthenticated() on")
Rel(c3, c4, "select * from users where username = ?", "JDBC")
</div>
<div class="mermaid">
C4Deployment
title Deployment Diagram for Internet Banking System - Live
Deployment_Node(mob, "Customer's mobile device", "Apple IOS or Android"){
Container(mobile, "Mobile App", "Xamarin", "Provides a limited subset of the Internet Banking functionality to customers via their mobile device.")
}
Deployment_Node(comp, "Customer's computer", "Mircosoft Windows or Apple macOS"){
Deployment_Node(browser, "Web Browser", "Google Chrome, Mozilla Firefox,<br/> Apple Safari or Microsoft Edge"){
Container(spa, "Single Page Application", "JavaScript and Angular", "Provides all of the Internet Banking functionality to customers via their web browser.")
}
}
Deployment_Node(plc, "Big Bank plc", "Big Bank plc data center"){
Deployment_Node(dn, "bigbank-api*** x8", "Ubuntu 16.04 LTS"){
Deployment_Node(apache, "Apache Tomcat", "Apache Tomcat 8.x"){
Container(api, "API Application", "Java and Spring MVC", "Provides Internet Banking functionality via a JSON/HTTPS API.")
}
}
Deployment_Node(bb2, "bigbank-web*** x4", "Ubuntu 16.04 LTS"){
Deployment_Node(apache2, "Apache Tomcat", "Apache Tomcat 8.x"){
Container(web, "Web Application", "Java and Spring MVC", "Delivers the static content and the Internet Banking single page application.")
}
}
Deployment_Node(bigbankdb01, "bigbank-db01", "Ubuntu 16.04 LTS"){
Deployment_Node(oracle, "Oracle - Primary", "Oracle 12c"){
ContainerDb(db, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
}
}
Deployment_Node(bigbankdb02, "bigbank-db02", "Ubuntu 16.04 LTS") {
Deployment_Node(oracle2, "Oracle - Secondary", "Oracle 12c") {
ContainerDb(db2, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
}
}
}
Rel(mobile, api, "Makes API calls to", "json/HTTPS")
Rel(spa, api, "Makes API calls to", "json/HTTPS")
Rel_U(web, spa, "Delivers to the customer's web browser")
Rel(api, db, "Reads from and writes to", "JDBC")
Rel(api, db2, "Reads from and writes to", "JDBC")
Rel_R(db, db2, "Replicates data to")
</div>
<hr />
<div class="mermaid">
pie
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 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;

View File

@ -1059,6 +1059,405 @@ 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: 216
*/
width: 216,
/**
* | Parameter | Description | Type | Required | Values |
* | --------- | ---------------------- | ------- | -------- | ------------------ |
* | height | Height of person boxes | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 60
*/
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',
external_personFontSize: 14,
external_personFontFamily: '"Open Sans", sans-serif',
external_personFontWeight: 'normal',
systemFontSize: 14,
systemFontFamily: '"Open Sans", sans-serif',
systemFontWeight: 'normal',
external_systemFontSize: 14,
external_systemFontFamily: '"Open Sans", sans-serif',
external_systemFontWeight: 'normal',
system_dbFontSize: 14,
system_dbFontFamily: '"Open Sans", sans-serif',
system_dbFontWeight: 'normal',
external_system_dbFontSize: 14,
external_system_dbFontFamily: '"Open Sans", sans-serif',
external_system_dbFontWeight: 'normal',
system_queueFontSize: 14,
system_queueFontFamily: '"Open Sans", sans-serif',
system_queueFontWeight: 'normal',
external_system_queueFontSize: 14,
external_system_queueFontFamily: '"Open Sans", sans-serif',
external_system_queueFontWeight: 'normal',
boundaryFontSize: 14,
boundaryFontFamily: '"Open Sans", sans-serif',
boundaryFontWeight: 'normal',
messageFontSize: 12,
messageFontFamily: '"Open Sans", sans-serif',
messageFontWeight: 'normal',
containerFontSize: 14,
containerFontFamily: '"Open Sans", sans-serif',
containerFontWeight: 'normal',
external_containerFontSize: 14,
external_containerFontFamily: '"Open Sans", sans-serif',
external_containerFontWeight: 'normal',
container_dbFontSize: 14,
container_dbFontFamily: '"Open Sans", sans-serif',
container_dbFontWeight: 'normal',
external_container_dbFontSize: 14,
external_container_dbFontFamily: '"Open Sans", sans-serif',
external_container_dbFontWeight: 'normal',
container_queueFontSize: 14,
container_queueFontFamily: '"Open Sans", sans-serif',
container_queueFontWeight: 'normal',
external_container_queueFontSize: 14,
external_container_queueFontFamily: '"Open Sans", sans-serif',
external_container_queueFontWeight: 'normal',
componentFontSize: 14,
componentFontFamily: '"Open Sans", sans-serif',
componentFontWeight: 'normal',
external_componentFontSize: 14,
external_componentFontFamily: '"Open Sans", sans-serif',
external_componentFontWeight: 'normal',
component_dbFontSize: 14,
component_dbFontFamily: '"Open Sans", sans-serif',
component_dbFontWeight: 'normal',
external_component_dbFontSize: 14,
external_component_dbFontFamily: '"Open Sans", sans-serif',
external_component_dbFontWeight: 'normal',
component_queueFontSize: 14,
component_queueFontFamily: '"Open Sans", sans-serif',
component_queueFontWeight: 'normal',
external_component_queueFontSize: 14,
external_component_queueFontFamily: '"Open Sans", sans-serif',
external_component_queueFontWeight: '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,
};
},
external_personFont: function () {
return {
fontFamily: this.external_personFontFamily,
fontSize: this.external_personFontSize,
fontWeight: this.external_personFontWeight,
};
},
systemFont: function () {
return {
fontFamily: this.systemFontFamily,
fontSize: this.systemFontSize,
fontWeight: this.systemFontWeight,
};
},
external_systemFont: function () {
return {
fontFamily: this.external_systemFontFamily,
fontSize: this.external_systemFontSize,
fontWeight: this.external_systemFontWeight,
};
},
system_dbFont: function () {
return {
fontFamily: this.system_dbFontFamily,
fontSize: this.system_dbFontSize,
fontWeight: this.system_dbFontWeight,
};
},
external_system_dbFont: function () {
return {
fontFamily: this.external_system_dbFontFamily,
fontSize: this.external_system_dbFontSize,
fontWeight: this.external_system_dbFontWeight,
};
},
system_queueFont: function () {
return {
fontFamily: this.system_queueFontFamily,
fontSize: this.system_queueFontSize,
fontWeight: this.system_queueFontWeight,
};
},
external_system_queueFont: function () {
return {
fontFamily: this.external_system_queueFontFamily,
fontSize: this.external_system_queueFontSize,
fontWeight: this.external_system_queueFontWeight,
};
},
containerFont: function () {
return {
fontFamily: this.containerFontFamily,
fontSize: this.containerFontSize,
fontWeight: this.containerFontWeight,
};
},
external_containerFont: function () {
return {
fontFamily: this.external_containerFontFamily,
fontSize: this.external_containerFontSize,
fontWeight: this.external_containerFontWeight,
};
},
container_dbFont: function () {
return {
fontFamily: this.container_dbFontFamily,
fontSize: this.container_dbFontSize,
fontWeight: this.container_dbFontWeight,
};
},
external_container_dbFont: function () {
return {
fontFamily: this.external_container_dbFontFamily,
fontSize: this.external_container_dbFontSize,
fontWeight: this.external_container_dbFontWeight,
};
},
container_queueFont: function () {
return {
fontFamily: this.container_queueFontFamily,
fontSize: this.container_queueFontSize,
fontWeight: this.container_queueFontWeight,
};
},
external_container_queueFont: function () {
return {
fontFamily: this.external_container_queueFontFamily,
fontSize: this.external_container_queueFontSize,
fontWeight: this.external_container_queueFontWeight,
};
},
componentFont: function () {
return {
fontFamily: this.componentFontFamily,
fontSize: this.componentFontSize,
fontWeight: this.componentFontWeight,
};
},
external_componentFont: function () {
return {
fontFamily: this.external_componentFontFamily,
fontSize: this.external_componentFontSize,
fontWeight: this.external_componentFontWeight,
};
},
component_dbFont: function () {
return {
fontFamily: this.component_dbFontFamily,
fontSize: this.component_dbFontSize,
fontWeight: this.component_dbFontWeight,
};
},
external_component_dbFont: function () {
return {
fontFamily: this.external_component_dbFontFamily,
fontSize: this.external_component_dbFontSize,
fontWeight: this.external_component_dbFontWeight,
};
},
component_queueFont: function () {
return {
fontFamily: this.component_queueFontFamily,
fontSize: this.component_queueFontSize,
fontWeight: this.component_queueFontWeight,
};
},
external_component_queueFont: function () {
return {
fontFamily: this.external_component_queueFontFamily,
fontSize: this.external_component_queueFontSize,
fontWeight: this.external_component_queueFontWeight,
};
},
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',
container_bg_color: '#438DD5',
container_border_color: '#3C7FC0',
container_db_bg_color: '#438DD5',
container_db_border_color: '#3C7FC0',
container_queue_bg_color: '#438DD5',
container_queue_border_color: '#3C7FC0',
external_container_bg_color: '#B3B3B3',
external_container_border_color: '#A6A6A6',
external_container_db_bg_color: '#B3B3B3',
external_container_db_border_color: '#A6A6A6',
external_container_queue_bg_color: '#B3B3B3',
external_container_queue_border_color: '#A6A6A6',
component_bg_color: '#85BBF0',
component_border_color: '#78A8D8',
component_db_bg_color: '#85BBF0',
component_db_border_color: '#78A8D8',
component_queue_bg_color: '#85BBF0',
component_queue_border_color: '#78A8D8',
external_component_bg_color: '#CCCCCC',
external_component_border_color: '#BFBFBF',
external_component_db_bg_color: '#CCCCCC',
external_component_db_border_color: '#BFBFBF',
external_component_queue_bg_color: '#CCCCCC',
external_component_queue_border_color: '#BFBFBF',
},
};
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;

486
src/diagrams/c4/c4Db.js Normal file
View File

@ -0,0 +1,486 @@
import mermaidAPI from '../../mermaidAPI';
import * as configApi from '../../config';
import { log } from '../../logger';
import { sanitizeText } from '../common/common';
let c4ShapeArray = [];
let boundaryParseStack = [''];
let currentBoundaryParse = 'global';
let parentBoundaryParse = '';
let boundarys = [
{
alias: 'global',
label: { text: 'global' },
type: { text: 'global' },
tags: null,
link: null,
parentBoundary: '',
},
];
let rels = [];
let title = '';
let wrapEnabled = false;
let description = '';
var c4Type;
export const getC4Type = function () {
return c4Type;
};
export const setC4Type = function (c4TypeParam) {
let sanitizedText = sanitizeText(c4TypeParam, 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 (typeC4Shape, alias, label, descr, sprite, tags, link) {
// Don't allow label nulling
if (alias === null || label === null) return;
let personOrSystem = {};
const old = c4ShapeArray.find((personOrSystem) => personOrSystem.alias === alias);
if (old && alias === old.alias) {
personOrSystem = old;
} else {
personOrSystem.alias = alias;
c4ShapeArray.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.typeC4Shape = { text: typeC4Shape };
personOrSystem.parentBoundary = currentBoundaryParse;
};
//type, alias, label, ?techn, ?descr ?sprite, ?tags, $link
export const addContainer = function (typeC4Shape, alias, label, techn, descr, sprite, tags, link) {
// Don't allow label nulling
if (alias === null || label === null) return;
let container = {};
const old = c4ShapeArray.find((container) => container.alias === alias);
if (old && alias === old.alias) {
container = old;
} else {
container.alias = alias;
c4ShapeArray.push(container);
}
// Don't allow null labels, either
if (label === undefined || label === null) {
container.label = { text: '' };
} else {
container.label = { text: label };
}
if (techn === undefined || techn === null) {
container.techn = { text: '' };
} else {
container.techn = { text: techn };
}
if (descr === undefined || descr === null) {
container.descr = { text: '' };
} else {
container.descr = { text: descr };
}
container.sprite = sprite;
container.tags = tags;
container.link = link;
container.wrap = autoWrap();
container.typeC4Shape = { text: typeC4Shape };
container.parentBoundary = currentBoundaryParse;
};
//type, alias, label, ?techn, ?descr ?sprite, ?tags, $link
export const addComponent = function (typeC4Shape, alias, label, techn, descr, sprite, tags, link) {
// Don't allow label nulling
if (alias === null || label === null) return;
let component = {};
const old = c4ShapeArray.find((component) => component.alias === alias);
if (old && alias === old.alias) {
component = old;
} else {
component.alias = alias;
c4ShapeArray.push(component);
}
// Don't allow null labels, either
if (label === undefined || label === null) {
component.label = { text: '' };
} else {
component.label = { text: label };
}
if (techn === undefined || techn === null) {
component.techn = { text: '' };
} else {
component.techn = { text: techn };
}
if (descr === undefined || descr === null) {
component.descr = { text: '' };
} else {
component.descr = { text: descr };
}
component.sprite = sprite;
component.tags = tags;
component.link = link;
component.wrap = autoWrap();
component.typeC4Shape = { text: typeC4Shape };
component.parentBoundary = currentBoundaryParse;
};
//alias, label, ?type, ?tags, $link
export const addPersonOrSystemBoundary = 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.tags = tags;
boundary.link = link;
boundary.parentBoundary = currentBoundaryParse;
boundary.wrap = autoWrap();
parentBoundaryParse = currentBoundaryParse;
currentBoundaryParse = alias;
boundaryParseStack.push(parentBoundaryParse);
};
//alias, label, ?type, ?tags, $link
export const addContainerBoundary = 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: 'container' };
} else {
boundary.type = { text: type };
}
boundary.tags = tags;
boundary.link = link;
boundary.parentBoundary = currentBoundaryParse;
boundary.wrap = autoWrap();
parentBoundaryParse = currentBoundaryParse;
currentBoundaryParse = alias;
boundaryParseStack.push(parentBoundaryParse);
};
//alias, label, ?type, ?descr, ?sprite, ?tags, $link
export const addDeploymentNode = function (
nodeType,
alias,
label,
type,
descr,
sprite,
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: 'node' };
} else {
boundary.type = { text: type };
}
if (descr === undefined || descr === null) {
boundary.descr = { text: '' };
} else {
boundary.descr = { text: type };
}
boundary.tags = tags;
boundary.link = link;
boundary.nodeType = nodeType;
boundary.parentBoundary = currentBoundaryParse;
boundary.wrap = autoWrap();
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 getC4ShapeArray = function (parentBoundary) {
if (parentBoundary === undefined || parentBoundary === null) return c4ShapeArray;
else
return c4ShapeArray.filter((personOrSystem) => {
return personOrSystem.parentBoundary === parentBoundary;
});
};
export const getC4Shape = function (alias) {
return c4ShapeArray.find((personOrSystem) => personOrSystem.alias === alias);
};
export const getC4ShapeKeys = function (parentBoundary) {
return Object.keys(getC4ShapeArray(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 () {
c4ShapeArray = [];
boundarys = [
{
alias: 'global',
label: { text: 'global' },
type: { text: '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,
addPersonOrSystemBoundary,
addContainer,
addContainerBoundary,
addComponent,
addDeploymentNode,
popBoundaryParseStack,
addRel,
autoWrap,
setWrap,
getC4ShapeArray,
getC4Shape,
getC4ShapeKeys,
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,670 @@
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;
this.nextData.cnt = 0;
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) {
this.nextData.cnt = this.nextData.cnt + 1;
let _startx =
this.nextData.startx === this.nextData.stopx
? this.nextData.stopx + c4Shape.margin
: 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 ||
this.nextData.cnt > conf.c4ShapeInRow
) {
_startx = this.nextData.startx + c4Shape.margin + 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;
this.nextData.cnt = 1;
}
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.name = '';
this.data = {
startx: undefined,
stopx: undefined,
starty: undefined,
stopy: undefined,
widthLimit: undefined,
};
this.nextData = {
startx: undefined,
stopx: undefined,
starty: undefined,
stopy: undefined,
cnt: 0,
};
setConf(parser.yy.getConfig());
}
bumpLastMargin(margin) {
this.data.stopx += margin;
this.data.stopy += margin;
}
}
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;
}
};
const c4ShapeFont = (cnf, typeC4Shape) => {
return {
fontFamily: cnf[typeC4Shape + 'FontFamily'],
fontSize: cnf[typeC4Shape + 'FontSize'],
fontWeight: cnf[typeC4Shape + 'FontWeight'],
};
};
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 calcC4ShapeTextWH(textType, c4Shape, c4ShapeTextWrap, textConf, textLimitWidth) {
if (!c4Shape[textType].width) {
if (c4ShapeTextWrap) {
c4Shape[textType].text = wrapLabel(c4Shape[textType].text, textLimitWidth, textConf);
c4Shape[textType].textLines = c4Shape[textType].text.split(common.lineBreakRegex).length;
// c4Shape[textType].width = calculateTextWidth(c4Shape[textType].text, textConf);
c4Shape[textType].width = textLimitWidth;
// c4Shape[textType].height = c4Shape[textType].textLines * textConf.fontSize;
c4Shape[textType].height = calculateTextHeight(c4Shape[textType].text, textConf);
} else {
let lines = c4Shape[textType].text.split(common.lineBreakRegex);
c4Shape[textType].textLines = 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].textLines * 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);
calcC4ShapeTextWH('label', boundary, boundaryTextWrap, boundaryLabelConf, textLimitWidth);
svgDraw.drawBoundary(diagram, boundary, conf);
};
export const drawC4ShapeArray = function (currentBounds, diagram, c4ShapeArray, c4ShapeKeys) {
// Upper Y is relative point
let Y = 0;
// Draw the c4ShapeArray
for (let i = 0; i < c4ShapeKeys.length; i++) {
Y = 0;
const c4Shape = c4ShapeArray[c4ShapeKeys[i]];
// calc c4 shape type width and height
let c4ShapeTypeConf = c4ShapeFont(conf, c4Shape.typeC4Shape.text);
c4ShapeTypeConf.fontSize = c4ShapeTypeConf.fontSize - 2;
c4Shape.typeC4Shape.width = calculateTextWidth(
'<<' + c4Shape.typeC4Shape.text + '>>',
c4ShapeTypeConf
);
c4Shape.typeC4Shape.height = c4ShapeTypeConf.fontSize + 2;
c4Shape.typeC4Shape.Y = conf.c4ShapePadding;
Y = c4Shape.typeC4Shape.Y + c4Shape.typeC4Shape.height - 4;
// set image width and height c4Shape.x + c4Shape.width / 2 - 24, c4Shape.y + 28
// let imageWidth = 0,
// imageHeight = 0,
// imageY = 0;
//
c4Shape.image = { width: 0, height: 0, Y: 0 };
switch (c4Shape.typeC4Shape.text) {
case 'person':
case 'external_person':
c4Shape.image.width = 48;
c4Shape.image.height = 48;
c4Shape.image.Y = Y;
Y = c4Shape.image.Y + c4Shape.image.height;
break;
}
if (c4Shape.sprite) {
c4Shape.image.width = 48;
c4Shape.image.height = 48;
c4Shape.image.Y = Y;
Y = c4Shape.image.Y + c4Shape.image.height;
}
// Y = conf.c4ShapePadding + c4Shape.image.height;
let c4ShapeTextWrap = c4Shape.wrap && conf.wrap;
let textLimitWidth = conf.width - conf.c4ShapePadding * 2;
let c4ShapeLabelConf = c4ShapeFont(conf, c4Shape.typeC4Shape.text);
c4ShapeLabelConf.fontSize = c4ShapeLabelConf.fontSize + 2;
c4ShapeLabelConf.fontWeight = 'bold';
calcC4ShapeTextWH('label', c4Shape, c4ShapeTextWrap, c4ShapeLabelConf, textLimitWidth);
c4Shape['label'].Y = Y + 8;
Y = c4Shape['label'].Y + c4Shape['label'].height;
if (c4Shape.type && c4Shape.type.text !== '') {
c4Shape.type.text = '[' + c4Shape.type.text + ']';
let c4ShapeTypeConf = c4ShapeFont(conf, c4Shape.typeC4Shape.text);
calcC4ShapeTextWH('type', c4Shape, c4ShapeTextWrap, c4ShapeTypeConf, textLimitWidth);
c4Shape['type'].Y = Y + 5;
Y = c4Shape['type'].Y + c4Shape['type'].height;
} else if (c4Shape.techn && c4Shape.techn.text !== '') {
c4Shape.techn.text = '[' + c4Shape.techn.text + ']';
let c4ShapeTechnConf = c4ShapeFont(conf, c4Shape.techn.text);
calcC4ShapeTextWH('techn', c4Shape, c4ShapeTextWrap, c4ShapeTechnConf, textLimitWidth);
c4Shape['techn'].Y = Y + 5;
Y = c4Shape['techn'].Y + c4Shape['techn'].height;
}
let rectHeight = Y;
let rectWidth = c4Shape.label.width;
if (c4Shape.descr && c4Shape.descr.text !== '') {
let c4ShapeDescrConf = c4ShapeFont(conf, c4Shape.typeC4Shape.text);
calcC4ShapeTextWH('descr', c4Shape, c4ShapeTextWrap, c4ShapeDescrConf, textLimitWidth);
c4Shape['descr'].Y = Y + 20;
Y = c4Shape['descr'].Y + c4Shape['descr'].height;
rectWidth = Math.max(c4Shape.label.width, c4Shape.descr.width);
rectHeight = Y - c4Shape['descr'].textLines * 5;
}
rectWidth = rectWidth + conf.c4ShapePadding;
// let rectHeight =
c4Shape.width = Math.max(c4Shape.width || conf.width, rectWidth, conf.width);
c4Shape.height = Math.max(c4Shape.height || conf.height, rectHeight, conf.height);
c4Shape.margin = c4Shape.margin || conf.c4ShapeMargin;
currentBounds.insert(c4Shape);
const height = svgDraw.drawC4Shape(diagram, c4Shape, 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) {
let i = 0;
for (let rel of rels) {
i = i + 1;
let relTextWrap = rel.wrap && conf.wrap;
let relConf = messageFont(conf);
let diagramType = parser.yy.getC4Type();
if (diagramType === 'C4Dynamic') rel.label.text = i + ': ' + rel.label.text;
let textLimitWidth = calculateTextWidth(rel.label.text, relConf);
calcC4ShapeTextWH('label', rel, relTextWrap, relConf, textLimitWidth);
if (rel.techn && rel.techn.text !== '') {
textLimitWidth = calculateTextWidth(rel.techn.text, relConf);
calcC4ShapeTextWH('techn', rel, relTextWrap, relConf, textLimitWidth);
}
if (rel.descr && rel.descr.text !== '') {
textLimitWidth = calculateTextWidth(rel.descr.text, relConf);
calcC4ShapeTextWH('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);
};
/**
* @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 =
parentBounds.data.widthLimit / Math.min(conf.c4BoundaryInRow, currentBoundarys.length);
// Math.min(
// conf.width * conf.c4ShapeInRow + conf.c4ShapeMargin * conf.c4ShapeInRow * 2,
// parentBounds.data.widthLimit / Math.min(conf.c4BoundaryInRow, currentBoundarys.length)
// );
for (let i = 0; i < currentBoundarys.length; i++) {
let currentBoundary = currentBoundarys[i];
let Y = 0;
currentBoundary.image = { width: 0, height: 0, Y: 0 };
if (currentBoundary.sprite) {
currentBoundary.image.width = 48;
currentBoundary.image.height = 48;
currentBoundary.image.Y = Y;
Y = currentBoundary.image.Y + currentBoundary.image.height;
}
let currentBoundaryTextWrap = currentBoundary.wrap && conf.wrap;
let currentBoundaryLabelConf = boundaryFont(conf);
currentBoundaryLabelConf.fontSize = currentBoundaryLabelConf.fontSize + 2;
currentBoundaryLabelConf.fontWeight = 'bold';
calcC4ShapeTextWH(
'label',
currentBoundary,
currentBoundaryTextWrap,
currentBoundaryLabelConf,
currentBounds.data.widthLimit
);
currentBoundary['label'].Y = Y + 8;
Y = currentBoundary['label'].Y + currentBoundary['label'].height;
if (currentBoundary.type && currentBoundary.type.text !== '') {
currentBoundary.type.text = '[' + currentBoundary.type.text + ']';
let currentBoundaryTypeConf = boundaryFont(conf);
calcC4ShapeTextWH(
'type',
currentBoundary,
currentBoundaryTextWrap,
currentBoundaryTypeConf,
currentBounds.data.widthLimit
);
currentBoundary['type'].Y = Y + 5;
Y = currentBoundary['type'].Y + currentBoundary['type'].height;
}
if (currentBoundary.descr && currentBoundary.descr.text !== '') {
let currentBoundaryDescrConf = boundaryFont(conf);
currentBoundaryDescrConf.fontSize = currentBoundaryDescrConf.fontSize - 2;
calcC4ShapeTextWH(
'descr',
currentBoundary,
currentBoundaryTextWrap,
currentBoundaryDescrConf,
currentBounds.data.widthLimit
);
currentBoundary['descr'].Y = Y + 20;
Y = currentBoundary['descr'].Y + currentBoundary['descr'].height;
}
if (i == 0 || i % conf.c4BoundaryInRow === 0) {
// Calculate the drawing start point of the currentBoundarys.
let _x = parentBounds.data.startx + conf.diagramMarginX;
let _y = parentBounds.data.stopy + conf.diagramMarginY + Y;
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.getC4ShapeArray(currentBoundary.alias);
let currentPersonOrSystemKeys = parser.yy.getC4ShapeKeys(currentBoundary.alias);
if (currentPersonOrSystemKeys.length > 0) {
drawC4ShapeArray(
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.getC4Shape);
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', box.starty + conf.diagramMarginY);
}
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: drawC4ShapeArray,
drawBoundary,
setConf,
draw,
};

View File

@ -0,0 +1,312 @@
/** 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 node
%x node_l
%x node_r
/* Relationship Types */
%x rel
%x rel_bi
%x rel_u
%x rel_d
%x rel_l
%x rel_r
%x rel_b
%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';}
"ContainerQueue_Ext" { this.begin("container_ext_queue"); console.log('begin container_ext_queue'); return 'CONTAINER_EXT_QUEUE';}
"ContainerDb_Ext" { this.begin("container_ext_db"); console.log('begin container_ext_db'); return 'CONTAINER_EXT_DB';}
"Container_Ext" { this.begin("container_ext"); console.log('begin container_ext'); return 'CONTAINER_EXT';}
"ContainerQueue" { this.begin("container_queue"); console.log('begin container_queue'); return 'CONTAINER_QUEUE';}
"ContainerDb" { this.begin("container_db"); console.log('begin container_db'); return 'CONTAINER_DB';}
"Container" { this.begin("container"); console.log('begin container'); return 'CONTAINER';}
"Container_Boundary" { this.begin("container_boundary"); console.log('begin container_boundary'); return 'CONTAINER_BOUNDARY';}
"ComponentQueue_Ext" { this.begin("component_ext_queue"); console.log('begin component_ext_queue'); return 'COMPONENT_EXT_QUEUE';}
"ComponentDb_Ext" { this.begin("component_ext_db"); console.log('begin component_ext_db'); return 'COMPONENT_EXT_DB';}
"Component_Ext" { this.begin("component_ext"); console.log('begin component_ext'); return 'COMPONENT_EXT';}
"ComponentQueue" { this.begin("component_queue"); console.log('begin component_queue'); return 'COMPONENT_QUEUE';}
"ComponentDb" { this.begin("component_db"); console.log('begin component_db'); return 'COMPONENT_DB';}
"Component" { this.begin("component"); console.log('begin component'); return 'COMPONENT';}
"Deployment_Node" { this.begin("node"); console.log('begin node'); return 'NODE';}
"Node" { this.begin("node"); console.log('begin node'); return 'NODE';}
"Node_L" { this.begin("node_l"); console.log('begin node_l'); return 'NODE_L';}
"Node_R" { this.begin("node_r"); console.log('begin node_r'); return 'NODE_R';}
"Rel" { this.begin("rel"); console.log('begin rel'); return 'REL';}
"BiRel" { this.begin("birel"); console.log('begin birel'); return 'BIREL';}
"Rel_Up" { this.begin("rel_u"); console.log('begin rel_u'); return 'REL_U';}
"Rel_U" { this.begin("rel_u"); console.log('begin rel_u'); return 'REL_U';}
"Rel_Down" { this.begin("rel_d"); console.log('begin rel_d'); return 'REL_D';}
"Rel_D" { this.begin("rel_d"); console.log('begin rel_d'); return 'REL_D';}
"Rel_Left" { this.begin("rel_l"); console.log('begin rel_l'); return 'REL_L';}
"Rel_L" { this.begin("rel_l"); console.log('begin rel_l'); return 'REL_L';}
"Rel_Right" { this.begin("rel_r"); console.log('begin rel_r'); return 'REL_R';}
"Rel_R" { this.begin("rel_r"); console.log('begin rel_r'); return 'REL_R';}
"Rel_Back" { this.begin("rel_b"); console.log('begin rel_b'); return 'REL_B';}
"RelIndex" { this.begin("rel_index"); console.log('begin rel_index'); return 'REL_INDEX';}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index><<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,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index>[(][ ]*[,] { 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,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index>[(] { 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,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,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.addPersonOrSystemBoundary(...$2); $$=$2;}
| SYSTEM_BOUNDARY attributes {console.log($1,JSON.stringify($2)); $2.splice(2, 0, 'ENTERPRISE'); yy.addPersonOrSystemBoundary(...$2); $$=$2;}
| BOUNDARY attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystemBoundary(...$2); $$=$2;}
| CONTAINER_BOUNDARY attributes {console.log($1,JSON.stringify($2)); $2.splice(2, 0, 'CONTAINER'); yy.addContainerBoundary(...$2); $$=$2;}
| NODE attributes {console.log($1,JSON.stringify($2)); yy.addDeploymentNode('node', ...$2); $$=$2;}
| NODE_L attributes {console.log($1,JSON.stringify($2)); yy.addDeploymentNode('nodeL', ...$2); $$=$2;}
| NODE_R attributes {console.log($1,JSON.stringify($2)); yy.addDeploymentNode('nodeR', ...$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;}
| CONTAINER attributes {console.log($1,JSON.stringify($2)); yy.addContainer('container', ...$2); $$=$2;}
| CONTAINER_DB attributes {console.log($1,JSON.stringify($2)); yy.addContainer('container_db', ...$2); $$=$2;}
| CONTAINER_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addContainer('container_queue', ...$2); $$=$2;}
| CONTAINER_EXT attributes {console.log($1,JSON.stringify($2)); yy.addContainer('external_container', ...$2); $$=$2;}
| CONTAINER_EXT_DB attributes {console.log($1,JSON.stringify($2)); yy.addContainer('external_container_db', ...$2); $$=$2;}
| CONTAINER_EXT_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addContainer('external_container_queue', ...$2); $$=$2;}
| COMPONENT attributes {console.log($1,JSON.stringify($2)); yy.addComponent('component', ...$2); $$=$2;}
| COMPONENT_DB attributes {console.log($1,JSON.stringify($2)); yy.addComponent('component_db', ...$2); $$=$2;}
| COMPONENT_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addComponent('component_queue', ...$2); $$=$2;}
| COMPONENT_EXT attributes {console.log($1,JSON.stringify($2)); yy.addComponent('external_component', ...$2); $$=$2;}
| COMPONENT_EXT_DB attributes {console.log($1,JSON.stringify($2)); yy.addComponent('external_component_db', ...$2); $$=$2;}
| COMPONENT_EXT_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addComponent('external_component_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;}
| REL_B attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_b', ...$2); $$=$2;}
| REL_INDEX attributes {console.log($1,JSON.stringify($2)); $2.splice(0, 1); yy.addRel('rel', ...$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;

884
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 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);

View File

@ -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
View File

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

View File

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

View File

@ -95,6 +95,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;

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';
}