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