diff --git a/demos/index.html b/demos/index.html
index bdbd2f180b..0abe627a71 100644
--- a/demos/index.html
+++ b/demos/index.html
@@ -20,6 +20,165 @@
+ 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,
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")
+
+
+
+ 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")
+
+
+
+ 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")
+
+
+
+ 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")
+
+
+
+ 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,
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")
+
+
+
pie
title Key elements in Product X
diff --git a/src/Diagram.js b/src/Diagram.js
index 8e1e7a6bb1..c8cc5a3ea1 100644
--- a/src/Diagram.js
+++ b/src/Diagram.js
@@ -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;
diff --git a/src/defaultConfig.js b/src/defaultConfig.js
index 8b5a16d562..67a0b7d277 100644
--- a/src/defaultConfig.js
+++ b/src/defaultConfig.js
@@ -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;
diff --git a/src/diagrams/c4/c4Db.js b/src/diagrams/c4/c4Db.js
new file mode 100644
index 0000000000..c483df2874
--- /dev/null
+++ b/src/diagrams/c4/c4Db.js
@@ -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,
+};
diff --git a/src/diagrams/c4/c4Renderer.js b/src/diagrams/c4/c4Renderer.js
new file mode 100644
index 0000000000..d71e70e2ef
--- /dev/null
+++ b/src/diagrams/c4/c4Renderer.js
@@ -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,
+};
diff --git a/src/diagrams/c4/parser/c4Diagram.jison b/src/diagrams/c4/parser/c4Diagram.jison
new file mode 100644
index 0000000000..9b57e3cf11
--- /dev/null
+++ b/src/diagrams/c4/parser/c4Diagram.jison
@@ -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';
+
((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
+":" { this.popState(); this.begin('arg_directive'); return ':'; }
+\}\%\% { this.popState(); this.popState(); return 'close_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';}
+
+<> return "EOF_IN_STRUCT";
+[(][ ]*[,] { console.log('begin attribute with ATTRIBUTE_EMPTY'); this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
+[(] { console.log('begin attribute'); this.begin("attribute"); }
+[)] { console.log('STOP attribute'); this.popState();console.log('STOP diagram'); this.popState();}
+
+",," { console.log(',,'); return 'ATTRIBUTE_EMPTY';}
+"," { console.log(','); }
+[ ]*["]["] { console.log('ATTRIBUTE_EMPTY'); return 'ATTRIBUTE_EMPTY';}
+[ ]*["] { console.log('begin string'); this.begin("string");}
+["] { console.log('STOP string'); this.popState(); }
+[^"]* { console.log('STR'); return "STR";}
+[^,]+ { 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';
+<> 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 { $$ = ""; }
+ ;
+
diff --git a/src/diagrams/c4/styles.js b/src/diagrams/c4/styles.js
new file mode 100644
index 0000000000..c24412b3c7
--- /dev/null
+++ b/src/diagrams/c4/styles.js
@@ -0,0 +1,8 @@
+const getStyles = (options) =>
+ `.person {
+ stroke: ${options.personBorder};
+ fill: ${options.personBkg};
+ }
+`;
+
+export default getStyles;
diff --git a/src/diagrams/c4/svgDraw.js b/src/diagrams/c4/svgDraw.js
new file mode 100644
index 0000000000..fb3a276dba
--- /dev/null
+++ b/src/diagrams/c4/svgDraw.js
@@ -0,0 +1,884 @@
+import common from '../common/common';
+import { addFunction } from '../../interactionDb';
+import { sanitizeUrl } from '@braintree/sanitize-url';
+
+export const drawRect = function (elem, rectData) {
+ const rectElem = elem.append('rect');
+ rectElem.attr('x', rectData.x);
+ rectElem.attr('y', rectData.y);
+ rectElem.attr('fill', rectData.fill);
+ rectElem.attr('stroke', rectData.stroke);
+ rectElem.attr('width', rectData.width);
+ rectElem.attr('height', rectData.height);
+ rectElem.attr('rx', rectData.rx);
+ rectElem.attr('ry', rectData.ry);
+
+ if (rectData.attrs !== 'undefined' && rectData.attrs !== null) {
+ for (let attrKey in rectData.attrs) rectElem.attr(attrKey, rectData.attrs[attrKey]);
+ }
+
+ if (rectData.class !== 'undefined') {
+ rectElem.attr('class', rectData.class);
+ }
+
+ return rectElem;
+};
+
+export const drawImage = function (elem, width, height, x, y, link) {
+ const imageElem = elem.append('image');
+ imageElem.attr('width', width);
+ imageElem.attr('height', height);
+ imageElem.attr('x', x);
+ imageElem.attr('y', y);
+ let sanitizedLink = link.startsWith('data:image/png;base64') ? link : sanitizeUrl(link);
+ imageElem.attr('xlink:href', sanitizedLink);
+};
+
+export const drawEmbeddedImage = function (elem, x, y, link) {
+ const imageElem = elem.append('use');
+ imageElem.attr('x', x);
+ imageElem.attr('y', y);
+ var sanitizedLink = sanitizeUrl(link);
+ imageElem.attr('xlink:href', '#' + sanitizedLink);
+};
+
+export const drawText = function (elem, textData) {
+ let prevTextHeight = 0,
+ textHeight = 0;
+ const lines = textData.text.split(common.lineBreakRegex);
+
+ let textElems = [];
+ let dy = 0;
+ let yfunc = () => textData.y;
+ if (
+ typeof textData.valign !== 'undefined' &&
+ typeof textData.textMargin !== 'undefined' &&
+ textData.textMargin > 0
+ ) {
+ switch (textData.valign) {
+ case 'top':
+ case 'start':
+ yfunc = () => Math.round(textData.y + textData.textMargin);
+ break;
+ case 'middle':
+ case 'center':
+ yfunc = () =>
+ Math.round(textData.y + (prevTextHeight + textHeight + textData.textMargin) / 2);
+ break;
+ case 'bottom':
+ case 'end':
+ yfunc = () =>
+ Math.round(
+ textData.y +
+ (prevTextHeight + textHeight + 2 * textData.textMargin) -
+ textData.textMargin
+ );
+ break;
+ }
+ }
+ if (
+ typeof textData.anchor !== 'undefined' &&
+ typeof textData.textMargin !== 'undefined' &&
+ typeof textData.width !== 'undefined'
+ ) {
+ switch (textData.anchor) {
+ case 'left':
+ case 'start':
+ textData.x = Math.round(textData.x + textData.textMargin);
+ textData.anchor = 'start';
+ textData.dominantBaseline = 'text-after-edge';
+ textData.alignmentBaseline = 'middle';
+ break;
+ case 'middle':
+ case 'center':
+ textData.x = Math.round(textData.x + textData.width / 2);
+ textData.anchor = 'middle';
+ textData.dominantBaseline = 'middle';
+ textData.alignmentBaseline = 'middle';
+ break;
+ case 'right':
+ case 'end':
+ textData.x = Math.round(textData.x + textData.width - textData.textMargin);
+ textData.anchor = 'end';
+ textData.dominantBaseline = 'text-before-edge';
+ textData.alignmentBaseline = 'middle';
+ break;
+ }
+ }
+ for (let i = 0; i < lines.length; i++) {
+ let line = lines[i];
+ if (
+ typeof textData.textMargin !== 'undefined' &&
+ textData.textMargin === 0 &&
+ typeof textData.fontSize !== 'undefined'
+ ) {
+ dy = i * textData.fontSize;
+ }
+
+ const textElem = elem.append('text');
+ textElem.attr('x', textData.x);
+ textElem.attr('y', yfunc());
+ if (typeof textData.anchor !== 'undefined') {
+ textElem
+ .attr('text-anchor', textData.anchor)
+ .attr('dominant-baseline', textData.dominantBaseline)
+ .attr('alignment-baseline', textData.alignmentBaseline);
+ }
+ if (typeof textData.fontFamily !== 'undefined') {
+ textElem.style('font-family', textData.fontFamily);
+ }
+ if (typeof textData.fontSize !== 'undefined') {
+ textElem.style('font-size', textData.fontSize);
+ }
+ if (typeof textData.fontWeight !== 'undefined') {
+ textElem.style('font-weight', textData.fontWeight);
+ }
+ if (typeof textData.fill !== 'undefined') {
+ textElem.attr('fill', textData.fill);
+ }
+ if (typeof textData.class !== 'undefined') {
+ textElem.attr('class', textData.class);
+ }
+ if (typeof textData.dy !== 'undefined') {
+ textElem.attr('dy', textData.dy);
+ } else if (dy !== 0) {
+ textElem.attr('dy', dy);
+ }
+
+ if (textData.tspan) {
+ const span = textElem.append('tspan');
+ span.attr('x', textData.x);
+ if (typeof textData.fill !== 'undefined') {
+ span.attr('fill', textData.fill);
+ }
+ span.text(line);
+ } else {
+ textElem.text(line);
+ }
+ if (
+ typeof textData.valign !== 'undefined' &&
+ typeof textData.textMargin !== 'undefined' &&
+ textData.textMargin > 0
+ ) {
+ textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
+ prevTextHeight = textHeight;
+ }
+
+ textElems.push(textElem);
+ }
+
+ return textElems;
+};
+
+export const drawLabel = function (elem, txtObject) {
+ /**
+ * @param {any} x
+ * @param {any} y
+ * @param {any} width
+ * @param {any} height
+ * @param {any} cut
+ * @returns {any}
+ */
+ function genPoints(x, y, width, height, cut) {
+ return (
+ x +
+ ',' +
+ y +
+ ' ' +
+ (x + width) +
+ ',' +
+ y +
+ ' ' +
+ (x + width) +
+ ',' +
+ (y + height - cut) +
+ ' ' +
+ (x + width - cut * 1.2) +
+ ',' +
+ (y + height) +
+ ' ' +
+ x +
+ ',' +
+ (y + height)
+ );
+ }
+ const polygon = elem.append('polygon');
+ polygon.attr('points', genPoints(txtObject.x, txtObject.y, txtObject.width, txtObject.height, 7));
+ polygon.attr('class', 'labelBox');
+
+ txtObject.y = txtObject.y + txtObject.height / 2;
+
+ drawText(elem, txtObject);
+ return polygon;
+};
+
+export const drawRels = (elem, rels, conf) => {
+ const relsElem = elem.append('g');
+ let i = 0;
+ for (let rel of rels) {
+ let url = '';
+ if (i === 0) {
+ let line = relsElem.append('line');
+ line.attr('x1', rel.startPoint.x);
+ line.attr('y1', rel.startPoint.y);
+ line.attr('x2', rel.endPoint.x);
+ line.attr('y2', rel.endPoint.y);
+
+ line.attr('stroke-width', '1');
+ line.attr('stroke', '#444444');
+ line.style('fill', 'none');
+ if (rel.type !== 'rel_b') line.attr('marker-end', 'url(' + url + '#arrowhead)');
+ if (rel.type === 'birel' || rel.type === 'rel_b')
+ line.attr('marker-start', 'url(' + url + '#arrowend)');
+ i = -1;
+ } else {
+ let line = relsElem.append('path');
+ line
+ .attr('fill', 'none')
+ .attr('stroke-width', '1')
+ .attr('stroke', '#444444')
+ .attr(
+ 'd',
+ 'Mstartx,starty Qcontrolx,controly stopx,stopy '
+ .replaceAll('startx', rel.startPoint.x)
+ .replaceAll('starty', rel.startPoint.y)
+ .replaceAll(
+ 'controlx',
+ rel.startPoint.x +
+ (rel.endPoint.x - rel.startPoint.x) / 2 -
+ (rel.endPoint.x - rel.startPoint.x) / 4
+ )
+ .replaceAll('controly', rel.startPoint.y + (rel.endPoint.y - rel.startPoint.y) / 2)
+ .replaceAll('stopx', rel.endPoint.x)
+ .replaceAll('stopy', rel.endPoint.y)
+ );
+ if (rel.type !== 'rel_b') line.attr('marker-end', 'url(' + url + '#arrowhead)');
+ if (rel.type === 'birel' || rel.type === 'rel_b')
+ line.attr('marker-start', 'url(' + url + '#arrowend)');
+ }
+
+ let messageConf = conf.messageFont();
+ _drawTextCandidateFunc(conf)(
+ rel.label.text,
+ relsElem,
+ Math.min(rel.startPoint.x, rel.endPoint.x) + Math.abs(rel.endPoint.x - rel.startPoint.x) / 2,
+ Math.min(rel.startPoint.y, rel.endPoint.y) + Math.abs(rel.endPoint.y - rel.startPoint.y) / 2,
+ rel.label.width,
+ rel.label.height,
+ { fill: '#444444' },
+ messageConf
+ );
+
+ if (rel.techn && rel.techn.text !== '') {
+ messageConf = conf.messageFont();
+ _drawTextCandidateFunc(conf)(
+ '[' + rel.techn.text + ']',
+ relsElem,
+ Math.min(rel.startPoint.x, rel.endPoint.x) +
+ Math.abs(rel.endPoint.x - rel.startPoint.x) / 2,
+ Math.min(rel.startPoint.y, rel.endPoint.y) +
+ Math.abs(rel.endPoint.y - rel.startPoint.y) / 2 +
+ conf.messageFontSize +
+ 5,
+ Math.max(rel.label.width, rel.techn.width),
+ rel.techn.height,
+ { fill: '#444444', 'font-style': 'italic' },
+ messageConf
+ );
+ }
+ }
+};
+
+/**
+ * Draws an boundary in the diagram
+ *
+ * @param {any} elem - The diagram we'll draw to.
+ * @param {any} boundary - The boundary to draw.
+ * @param {any} conf - DrawText implementation discriminator object
+ */
+const drawBoundary = function (elem, boundary, conf) {
+ const boundaryElem = elem.append('g');
+
+ let attrsValue = { 'stroke-width': 1.0, 'stroke-dasharray': '7.0,7.0' };
+ if (boundary.nodeType) attrsValue = { 'stroke-width': 1.0 };
+ let rectData = {
+ x: boundary.x,
+ y: boundary.y,
+ fill: 'none',
+ stroke: '#444444',
+ width: boundary.width,
+ height: boundary.height,
+ rx: 2.5,
+ ry: 2.5,
+ attrs: attrsValue,
+ };
+
+ drawRect(boundaryElem, rectData);
+
+ // draw lable
+ let boundaryConf = conf.boundaryFont();
+ boundaryConf.fontWeight = 'bold';
+ boundaryConf.fontSize = boundaryConf.fontSize + 2;
+ _drawTextCandidateFunc(conf)(
+ boundary.label.text,
+ boundaryElem,
+ boundary.x,
+ boundary.y + boundary.label.Y,
+ boundary.width,
+ boundary.height,
+ { fill: '#444444' },
+ boundaryConf
+ );
+
+ // draw type
+ if (boundary.type && boundary.type.text !== '') {
+ boundaryConf = conf.boundaryFont();
+ _drawTextCandidateFunc(conf)(
+ boundary.type.text,
+ boundaryElem,
+ boundary.x,
+ boundary.y + boundary.type.Y,
+ boundary.width,
+ boundary.height,
+ { fill: '#444444' },
+ boundaryConf
+ );
+ }
+
+ // draw descr
+ if (boundary.descr && boundary.descr.text !== '') {
+ boundaryConf = conf.boundaryFont();
+ boundaryConf.fontSize = boundaryConf.fontSize - 2;
+ _drawTextCandidateFunc(conf)(
+ boundary.descr.text,
+ boundaryElem,
+ boundary.x,
+ boundary.y + boundary.descr.Y,
+ boundary.width,
+ boundary.height,
+ { fill: '#444444' },
+ boundaryConf
+ );
+ }
+};
+
+export const drawC4Shape = function (elem, c4Shape, conf) {
+ let fillColor = conf[c4Shape.typeC4Shape.text + '_bg_color'];
+ let strokeColor = conf[c4Shape.typeC4Shape.text + '_border_color'];
+ let personImg =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAACD0lEQVR4Xu2YoU4EMRCGT+4j8Ai8AhaH4QHgAUjQuFMECUgMIUgwJAgMhgQsAYUiJCiQIBBY+EITsjfTdme6V24v4c8vyGbb+ZjOtN0bNcvjQXmkH83WvYBWto6PLm6v7p7uH1/w2fXD+PBycX1Pv2l3IdDm/vn7x+dXQiAubRzoURa7gRZWd0iGRIiJbOnhnfYBQZNJjNbuyY2eJG8fkDE3bbG4ep6MHUAsgYxmE3nVs6VsBWJSGccsOlFPmLIViMzLOB7pCVO2AtHJMohH7Fh6zqitQK7m0rJvAVYgGcEpe//PLdDz65sM4pF9N7ICcXDKIB5Nv6j7tD0NoSdM2QrU9Gg0ewE1LqBhHR3BBdvj2vapnidjHxD/q6vd7Pvhr31AwcY8eXMTXAKECZZJFXuEq27aLgQK5uLMohCenGGuGewOxSjBvYBqeG6B+Nqiblggdjnc+ZXDy+FNFpFzw76O3UBAROuXh6FoiAcf5g9eTvUgzy0nWg6I8cXHRUpg5bOVBCo+KDpFajOf23GgPme7RSQ+lacIENUgJ6gg1k6HjgOlqnLqip4tEuhv0hNEMXUD0clyXE3p6pZA0S2nnvTlXwLJEZWlb7cTQH1+USgTN4VhAenm/wea1OCAOmqo6fE1WCb9WSKBah+rbUWPWAmE2Rvk0ApiB45eOyNAzU8xcTvj8KvkKEoOaIYeHNA3ZuygAvFMUO0AAAAASUVORK5CYII=';
+ switch (c4Shape.typeC4Shape.text) {
+ case 'person':
+ personImg =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAACD0lEQVR4Xu2YoU4EMRCGT+4j8Ai8AhaH4QHgAUjQuFMECUgMIUgwJAgMhgQsAYUiJCiQIBBY+EITsjfTdme6V24v4c8vyGbb+ZjOtN0bNcvjQXmkH83WvYBWto6PLm6v7p7uH1/w2fXD+PBycX1Pv2l3IdDm/vn7x+dXQiAubRzoURa7gRZWd0iGRIiJbOnhnfYBQZNJjNbuyY2eJG8fkDE3bbG4ep6MHUAsgYxmE3nVs6VsBWJSGccsOlFPmLIViMzLOB7pCVO2AtHJMohH7Fh6zqitQK7m0rJvAVYgGcEpe//PLdDz65sM4pF9N7ICcXDKIB5Nv6j7tD0NoSdM2QrU9Gg0ewE1LqBhHR3BBdvj2vapnidjHxD/q6vd7Pvhr31AwcY8eXMTXAKECZZJFXuEq27aLgQK5uLMohCenGGuGewOxSjBvYBqeG6B+Nqiblggdjnc+ZXDy+FNFpFzw76O3UBAROuXh6FoiAcf5g9eTvUgzy0nWg6I8cXHRUpg5bOVBCo+KDpFajOf23GgPme7RSQ+lacIENUgJ6gg1k6HjgOlqnLqip4tEuhv0hNEMXUD0clyXE3p6pZA0S2nnvTlXwLJEZWlb7cTQH1+USgTN4VhAenm/wea1OCAOmqo6fE1WCb9WSKBah+rbUWPWAmE2Rvk0ApiB45eOyNAzU8xcTvj8KvkKEoOaIYeHNA3ZuygAvFMUO0AAAAASUVORK5CYII=';
+ break;
+ case 'external_person':
+ personImg =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAAB6ElEQVR4Xu2YLY+EMBCG9+dWr0aj0Wg0Go1Go0+j8Xdv2uTCvv1gpt0ebHKPuhDaeW4605Z9mJvx4AdXUyTUdd08z+u6flmWZRnHsWkafk9DptAwDPu+f0eAYtu2PEaGWuj5fCIZrBAC2eLBAnRCsEkkxmeaJp7iDJ2QMDdHsLg8SxKFEJaAo8lAXnmuOFIhTMpxxKATebo4UiFknuNo4OniSIXQyRxEA3YsnjGCVEjVXD7yLUAqxBGUyPv/Y4W2beMgGuS7kVQIBycH0fD+oi5pezQETxdHKmQKGk1eQEYldK+jw5GxPfZ9z7Mk0Qnhf1W1m3w//EUn5BDmSZsbR44QQLBEqrBHqOrmSKaQAxdnLArCrxZcM7A7ZKs4ioRq8LFC+NpC3WCBJsvpVw5edm9iEXFuyNfxXAgSwfrFQ1c0iNda8AdejvUgnktOtJQQxmcfFzGglc5WVCj7oDgFqU18boeFSs52CUh8LE8BIVQDT1ABrB0HtgSEYlX5doJnCwv9TXocKCaKbnwhdDKPq4lf3SwU3HLq4V/+WYhHVMa/3b4IlfyikAduCkcBc7mQ3/z/Qq/cTuikhkzB12Ae/mcJC9U+Vo8Ej1gWAtgbeGgFsAMHr50BIWOLCbezvhpBFUdY6EJuJ/QDW0XoMX60zZ0AAAAASUVORK5CYII=';
+ break;
+ }
+
+ const c4ShapeElem = elem.append('g');
+ c4ShapeElem.attr('class', 'person-man');
+
+ //
+ // draw rect of c4Shape
+ const rect = getNoteRect();
+ switch (c4Shape.typeC4Shape.text) {
+ case 'person':
+ case 'external_person':
+ case 'system':
+ case 'external_system':
+ case 'container':
+ case 'external_container':
+ case 'component':
+ case 'external_component':
+ rect.x = c4Shape.x;
+ rect.y = c4Shape.y;
+ rect.fill = fillColor;
+ rect.width = c4Shape.width;
+ rect.height = c4Shape.height;
+ rect.style = 'stroke:' + strokeColor + ';stroke-width:0.5;';
+ rect.rx = 2.5;
+ rect.ry = 2.5;
+ drawRect(c4ShapeElem, rect);
+ break;
+ case 'system_db':
+ case 'external_system_db':
+ case 'container_db':
+ case 'external_container_db':
+ case 'component_db':
+ case 'external_component_db':
+ c4ShapeElem
+ .append('path')
+ .attr('fill', fillColor)
+ .attr('stroke-width', '0.5')
+ .attr('stroke', strokeColor)
+ .attr(
+ 'd',
+ 'Mstartx,startyc0,-10 half,-10 half,-10c0,0 half,0 half,10l0,heightc0,10 -half,10 -half,10c0,0 -half,0 -half,-10l0,-height'
+ .replaceAll('startx', c4Shape.x)
+ .replaceAll('starty', c4Shape.y)
+ .replaceAll('half', c4Shape.width / 2)
+ .replaceAll('height', c4Shape.height)
+ );
+ c4ShapeElem
+ .append('path')
+ .attr('fill', 'none')
+ .attr('stroke-width', '0.5')
+ .attr('stroke', strokeColor)
+ .attr(
+ 'd',
+ 'Mstartx,startyc0,10 half,10 half,10c0,0 half,0 half,-10'
+ .replaceAll('startx', c4Shape.x)
+ .replaceAll('starty', c4Shape.y)
+ .replaceAll('half', c4Shape.width / 2)
+ );
+ break;
+ case 'system_queue':
+ case 'external_system_queue':
+ case 'container_queue':
+ case 'external_container_queue':
+ case 'component_queue':
+ case 'external_component_queue':
+ c4ShapeElem
+ .append('path')
+ .attr('fill', fillColor)
+ .attr('stroke-width', '0.5')
+ .attr('stroke', strokeColor)
+ .attr(
+ 'd',
+ 'Mstartx,startylwidth,0c5,0 5,half 5,halfc0,0 0,half -5,halfl-width,0c-5,0 -5,-half -5,-halfc0,0 0,-half 5,-half'
+ .replaceAll('startx', c4Shape.x)
+ .replaceAll('starty', c4Shape.y)
+ .replaceAll('width', c4Shape.width)
+ .replaceAll('half', c4Shape.height / 2)
+ );
+ c4ShapeElem
+ .append('path')
+ .attr('fill', 'none')
+ .attr('stroke-width', '0.5')
+ .attr('stroke', strokeColor)
+ .attr(
+ 'd',
+ 'Mstartx,startyc-5,0 -5,half -5,halfc0,half 5,half 5,half'
+ .replaceAll('startx', c4Shape.x + c4Shape.width)
+ .replaceAll('starty', c4Shape.y)
+ .replaceAll('half', c4Shape.height / 2)
+ );
+ break;
+ }
+
+ // draw type of c4Shape
+ let c4ShapeFontConf = getC4ShapeFont(conf, c4Shape.typeC4Shape.text);
+ c4ShapeElem
+ .append('text')
+ .attr('fill', '#FFFFFF')
+ .attr('font-family', c4ShapeFontConf.fontFamily)
+ .attr('font-size', c4ShapeFontConf.fontSize - 2)
+ .attr('font-style', 'italic')
+ .attr('lengthAdjust', 'spacing')
+ .attr('textLength', c4Shape.typeC4Shape.width)
+ .attr('x', c4Shape.x + c4Shape.width / 2 - c4Shape.typeC4Shape.width / 2)
+ .attr('y', c4Shape.y + c4Shape.typeC4Shape.Y)
+ .text('<<' + c4Shape.typeC4Shape.text + '>>');
+
+ // draw image/sprite
+ switch (c4Shape.typeC4Shape.text) {
+ case 'person':
+ case 'external_person':
+ drawImage(
+ c4ShapeElem,
+ 48,
+ 48,
+ c4Shape.x + c4Shape.width / 2 - 24,
+ c4Shape.y + c4Shape.image.Y,
+ personImg
+ );
+ break;
+ }
+
+ // draw label
+ let textFontConf = conf[c4Shape.typeC4Shape.text + 'Font']();
+ textFontConf.fontWeight = 'bold';
+ textFontConf.fontSize = textFontConf.fontSize + 2;
+ _drawTextCandidateFunc(conf)(
+ c4Shape.label.text,
+ c4ShapeElem,
+ c4Shape.x,
+ c4Shape.y + c4Shape.label.Y,
+ c4Shape.width,
+ c4Shape.height,
+ { fill: '#FFFFFF' },
+ textFontConf
+ );
+
+ // draw techn/type
+ textFontConf = conf[c4Shape.typeC4Shape.text + 'Font']();
+
+ if (c4Shape.thchn && c4Shape.thchn.text !== '') {
+ _drawTextCandidateFunc(conf)(
+ c4Shape.thchn.text,
+ c4ShapeElem,
+ c4Shape.x,
+ c4Shape.y + c4Shape.thchn.Y,
+ c4Shape.width,
+ c4Shape.height,
+ { fill: '#FFFFFF', 'font-style': 'italic' },
+ textFontConf
+ );
+ } else if (c4Shape.type && c4Shape.type.text !== '') {
+ _drawTextCandidateFunc(conf)(
+ c4Shape.type.text,
+ c4ShapeElem,
+ c4Shape.x,
+ c4Shape.y + c4Shape.type.Y,
+ c4Shape.width,
+ c4Shape.height,
+ { fill: '#FFFFFF', 'font-style': 'italic' },
+ textFontConf
+ );
+ }
+
+ // draw descr
+ if (c4Shape.descr && c4Shape.descr.text !== '') {
+ textFontConf = conf.personFont();
+ _drawTextCandidateFunc(conf)(
+ c4Shape.descr.text,
+ c4ShapeElem,
+ c4Shape.x,
+ c4Shape.y + c4Shape.descr.Y,
+ c4Shape.width,
+ c4Shape.height,
+ { fill: '#FFFFFF' },
+ textFontConf
+ );
+ }
+
+ return c4Shape.height;
+};
+
+export const insertDatabaseIcon = function (elem) {
+ elem
+ .append('defs')
+ .append('symbol')
+ .attr('id', 'database')
+ .attr('fill-rule', 'evenodd')
+ .attr('clip-rule', 'evenodd')
+ .append('path')
+ .attr('transform', 'scale(.5)')
+ .attr(
+ 'd',
+ 'M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z'
+ );
+};
+
+export const insertComputerIcon = function (elem) {
+ elem
+ .append('defs')
+ .append('symbol')
+ .attr('id', 'computer')
+ .attr('width', '24')
+ .attr('height', '24')
+ .append('path')
+ .attr('transform', 'scale(.5)')
+ .attr(
+ 'd',
+ 'M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z'
+ );
+};
+
+export const insertClockIcon = function (elem) {
+ elem
+ .append('defs')
+ .append('symbol')
+ .attr('id', 'clock')
+ .attr('width', '24')
+ .attr('height', '24')
+ .append('path')
+ .attr('transform', 'scale(.5)')
+ .attr(
+ 'd',
+ 'M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z'
+ );
+};
+
+/**
+ * Setup arrow head and define the marker. The result is appended to the svg.
+ *
+ * @param elem
+ */
+export const insertArrowHead = function (elem) {
+ elem
+ .append('defs')
+ .append('marker')
+ .attr('id', 'arrowhead')
+ .attr('refX', 9)
+ .attr('refY', 5)
+ .attr('markerUnits', 'userSpaceOnUse')
+ .attr('markerWidth', 12)
+ .attr('markerHeight', 12)
+ .attr('orient', 'auto')
+ .append('path')
+ .attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead
+};
+export const insertArrowEnd = function (elem) {
+ elem
+ .append('defs')
+ .append('marker')
+ .attr('id', 'arrowend')
+ .attr('refX', 1)
+ .attr('refY', 5)
+ .attr('markerUnits', 'userSpaceOnUse')
+ .attr('markerWidth', 12)
+ .attr('markerHeight', 12)
+ .attr('orient', 'auto')
+ .append('path')
+ .attr('d', 'M 10 0 L 0 5 L 10 10 z'); // this is actual shape for arrowhead
+};
+/**
+ * Setup arrow head and define the marker. The result is appended to the svg.
+ *
+ * @param {any} elem
+ */
+export const insertArrowFilledHead = function (elem) {
+ elem
+ .append('defs')
+ .append('marker')
+ .attr('id', 'filled-head')
+ .attr('refX', 18)
+ .attr('refY', 7)
+ .attr('markerWidth', 20)
+ .attr('markerHeight', 28)
+ .attr('orient', 'auto')
+ .append('path')
+ .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
+};
+/**
+ * Setup node number. The result is appended to the svg.
+ *
+ * @param {any} elem
+ */
+export const insertDynamicNumber = function (elem) {
+ elem
+ .append('defs')
+ .append('marker')
+ .attr('id', 'sequencenumber')
+ .attr('refX', 15)
+ .attr('refY', 15)
+ .attr('markerWidth', 60)
+ .attr('markerHeight', 40)
+ .attr('orient', 'auto')
+ .append('circle')
+ .attr('cx', 15)
+ .attr('cy', 15)
+ .attr('r', 6);
+ // .style("fill", '#f00');
+};
+/**
+ * Setup arrow head and define the marker. The result is appended to the svg.
+ *
+ * @param {any} elem
+ */
+export const insertArrowCrossHead = function (elem) {
+ const defs = elem.append('defs');
+ const marker = defs
+ .append('marker')
+ .attr('id', 'crosshead')
+ .attr('markerWidth', 15)
+ .attr('markerHeight', 8)
+ .attr('orient', 'auto')
+ .attr('refX', 16)
+ .attr('refY', 4);
+
+ // The arrow
+ marker
+ .append('path')
+ .attr('fill', 'black')
+ .attr('stroke', '#000000')
+ .style('stroke-dasharray', '0, 0')
+ .attr('stroke-width', '1px')
+ .attr('d', 'M 9,2 V 6 L16,4 Z');
+
+ // The cross
+ marker
+ .append('path')
+ .attr('fill', 'none')
+ .attr('stroke', '#000000')
+ .style('stroke-dasharray', '0, 0')
+ .attr('stroke-width', '1px')
+ .attr('d', 'M 0,1 L 6,7 M 6,1 L 0,7');
+ // this is actual shape for arrowhead
+};
+
+export const getTextObj = function () {
+ return {
+ x: 0,
+ y: 0,
+ fill: undefined,
+ anchor: undefined,
+ style: '#666',
+ width: undefined,
+ height: undefined,
+ textMargin: 0,
+ rx: 0,
+ ry: 0,
+ tspan: true,
+ valign: undefined,
+ };
+};
+
+export const getNoteRect = function () {
+ return {
+ x: 0,
+ y: 0,
+ fill: '#EDF2AE',
+ stroke: '#666',
+ width: 100,
+ anchor: 'start',
+ height: 100,
+ rx: 0,
+ ry: 0,
+ };
+};
+
+const getC4ShapeFont = (cnf, typeC4Shape) => {
+ return {
+ fontFamily: cnf[typeC4Shape + 'FontFamily'],
+ fontSize: cnf[typeC4Shape + 'FontSize'],
+ fontWeight: cnf[typeC4Shape + 'FontWeight'],
+ };
+};
+
+const _drawTextCandidateFunc = (function () {
+ /**
+ * @param {any} content
+ * @param {any} g
+ * @param {any} x
+ * @param {any} y
+ * @param {any} width
+ * @param {any} height
+ * @param {any} textAttrs
+ */
+ function byText(content, g, x, y, width, height, textAttrs) {
+ const text = g
+ .append('text')
+ .attr('x', x + width / 2)
+ .attr('y', y + height / 2 + 5)
+ .style('text-anchor', 'middle')
+ .text(content);
+ _setTextAttrs(text, textAttrs);
+ }
+
+ /**
+ * @param {any} content
+ * @param {any} g
+ * @param {any} x
+ * @param {any} y
+ * @param {any} width
+ * @param {any} height
+ * @param {any} textAttrs
+ * @param {any} conf
+ */
+ function byTspan(content, g, x, y, width, height, textAttrs, conf) {
+ const { fontSize, fontFamily, fontWeight } = conf;
+
+ const lines = content.split(common.lineBreakRegex);
+ for (let i = 0; i < lines.length; i++) {
+ const dy = i * fontSize - (fontSize * (lines.length - 1)) / 2;
+ const text = g
+ .append('text')
+ .attr('x', x + width / 2)
+ .attr('y', y)
+ .style('text-anchor', 'middle')
+ .attr('dominant-baseline', 'middle')
+ .style('font-size', fontSize)
+ .style('font-weight', fontWeight)
+ .style('font-family', fontFamily);
+ text
+ .append('tspan')
+ // .attr('x', x + width / 2)
+ .attr('dy', dy)
+ .text(lines[i])
+ // .attr('y', y + height / 2)
+ .attr('alignment-baseline', 'mathematical');
+
+ _setTextAttrs(text, textAttrs);
+ }
+ }
+
+ /**
+ * @param {any} content
+ * @param {any} g
+ * @param {any} x
+ * @param {any} y
+ * @param {any} width
+ * @param {any} height
+ * @param {any} textAttrs
+ * @param {any} conf
+ */
+ function byFo(content, g, x, y, width, height, textAttrs, conf) {
+ const s = g.append('switch');
+ const f = s
+ .append('foreignObject')
+ .attr('x', x)
+ .attr('y', y)
+ .attr('width', width)
+ .attr('height', height);
+
+ const text = f
+ .append('xhtml:div')
+ .style('display', 'table')
+ .style('height', '100%')
+ .style('width', '100%');
+
+ text
+ .append('div')
+ .style('display', 'table-cell')
+ .style('text-align', 'center')
+ .style('vertical-align', 'middle')
+ .text(content);
+
+ byTspan(content, s, x, y, width, height, textAttrs, conf);
+ _setTextAttrs(text, textAttrs);
+ }
+
+ /**
+ * @param {any} toText
+ * @param {any} fromTextAttrsDict
+ */
+ function _setTextAttrs(toText, fromTextAttrsDict) {
+ for (const key in fromTextAttrsDict) {
+ if (fromTextAttrsDict.hasOwnProperty(key)) {
+ // eslint-disable-line
+ toText.attr(key, fromTextAttrsDict[key]);
+ }
+ }
+ }
+
+ return function (conf) {
+ return conf.textPlacement === 'fo' ? byFo : conf.textPlacement === 'old' ? byText : byTspan;
+ };
+})();
+
+export default {
+ drawRect,
+ drawText,
+ drawLabel,
+ drawBoundary,
+ drawC4Shape,
+ drawRels,
+ drawImage,
+ drawEmbeddedImage,
+ insertArrowHead,
+ insertArrowEnd,
+ insertArrowFilledHead,
+ insertDynamicNumber,
+ insertArrowCrossHead,
+ insertDatabaseIcon,
+ insertComputerIcon,
+ insertClockIcon,
+ getTextObj,
+ getNoteRect,
+ sanitizeUrl,
+};
diff --git a/src/mermaidAPI.js b/src/mermaidAPI.js
index d985c39443..7d0eb3d523 100644
--- a/src/mermaidAPI.js
+++ b/src/mermaidAPI.js
@@ -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);
diff --git a/src/styles.js b/src/styles.js
index 9d39fe4c80..f5608f0dc1 100644
--- a/src/styles.js
+++ b/src/styles.js
@@ -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);
diff --git a/src/themes/c4.scss b/src/themes/c4.scss
new file mode 100644
index 0000000000..0c3fca3a99
--- /dev/null
+++ b/src/themes/c4.scss
@@ -0,0 +1,4 @@
+.person {
+ stroke: $personBorder;
+ fill: $personBkg;
+}
diff --git a/src/themes/default/index.scss b/src/themes/default/index.scss
index 7daea0e630..a20f81a7d6 100644
--- a/src/themes/default/index.scss
+++ b/src/themes/default/index.scss
@@ -56,6 +56,11 @@ $critBorderColor: #ff8888;
$critBkgColor: red;
$todayLineColor: red;
+/* C4 Context Diagram variables */
+
+$personBorder: $border1;
+$personBkg: $mainBkg;
+
/* state colors */
$labelColor: black;
diff --git a/src/themes/theme-base.js b/src/themes/theme-base.js
index 7474da8987..361e4495ba 100644
--- a/src/themes/theme-base.js
+++ b/src/themes/theme-base.js
@@ -113,6 +113,11 @@ class Theme {
this.taskTextDarkColor = this.taskTextDarkColor || this.textColor;
this.taskTextClickableColor = this.taskTextClickableColor || '#003163';
+ /* Sequence Diagram variables */
+
+ this.personBorder = this.personBorder || this.primaryBorderColor;
+ this.personBkg = this.personBkg || this.mainBkg;
+
/* state colors */
this.transitionColor = this.transitionColor || this.lineColor;
this.transitionLabelColor = this.transitionLabelColor || this.textColor;
diff --git a/src/themes/theme-dark.js b/src/themes/theme-dark.js
index 71b9d446ce..ecccaa8769 100644
--- a/src/themes/theme-dark.js
+++ b/src/themes/theme-dark.js
@@ -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';
diff --git a/src/themes/theme-default.js b/src/themes/theme-default.js
index 81a07b9b8d..a91a9a249e 100644
--- a/src/themes/theme-default.js
+++ b/src/themes/theme-default.js
@@ -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';
diff --git a/src/themes/theme-forest.js b/src/themes/theme-forest.js
index b0ec574580..b92291d2a1 100644
--- a/src/themes/theme-forest.js
+++ b/src/themes/theme-forest.js
@@ -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';
diff --git a/src/themes/theme-neutral.js b/src/themes/theme-neutral.js
index af228513a5..78873392ef 100644
--- a/src/themes/theme-neutral.js
+++ b/src/themes/theme-neutral.js
@@ -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';
diff --git a/src/utils.js b/src/utils.js
index e8a24bb6aa..d19078777f 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -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';
}