-
-
Notifications
You must be signed in to change notification settings - Fork 6.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Component Diagram #1462
Comments
I would love to have this diagram type handled by mermaidjs ! |
+1 for component diagrams |
We would need:
Maybe something like this:
|
+1 for component diagrams |
+1 for component diagrams |
1 similar comment
+1 for component diagrams |
Team, |
+1 for component diagrams |
+1 for component diagram |
+1 for component diagram. |
1 similar comment
+1 for component diagram. |
+1 |
1 similar comment
+1 |
+1 We're now using Mermaid to document our API: https://doc.postsharp.net/caravela/api/caravela_framework_aspects |
+1 |
+1 for component diagrams. |
+1, would love to see this implemented as well! |
I've started an early POC for component diagrams... needs a lot of work and haven't put in the relationships yet, but I've got a starting point which includes system components, system nodes, and support for nested nodes which can go infinitely deep due to utilizing an AST structure.
Syntax is different that one proposed above... and definitely subject to change. I'm somewhat modeling it after the flowchart style where Curious if anyone has any thoughts/feedback, especially @knsv, incase you had some other ideas or if this is already a WIP in some way. :) Don't want to go too far down a dead-end rabbit hole! |
@andrewmcgivery, I think the hard thing will be relations. Especially things like provided interfaces, required interfaces, relationships through interfaces, relations through ports, and interfaces on ports. |
Update... did some initial work on interfaces and relationships. Representing interface with
Example Output: |
Good work. |
Looks promising! @andrewmcgivery where can I access your work? Would love to experiment with it and evaluate if it already does the job for me. |
@andrewmcgivery I'm also interested in your component diagram effort... is there any PR tangling around or have you committed anything of that anywhere? |
+1 |
👍 for component diagrams |
+1 |
1 similar comment
+1 |
Came here looking for component diagrams :) |
+1 for component diagram support. I'd really like to ditch |
I've been debating mermaid over d2. Mermaid has widepsread support already on Github, notion and tooling around sphinx etc. But this one bit was missing and making me look at D2. Although @da-kami comment above makes sense. I'll look at C4 diagrams to see if they have all the things i need. |
@andrewmcgivery Are you still working on this? |
+1 |
Hello, what's the status on this bug? |
Hallo mermaid team, any progress here please? |
+1 |
3 similar comments
+1 |
+1 |
+1 |
Almost 2 years later since I took a stab at this and it's still not in Mermaid X_X The code I started is super old now so likely no longer relevant or anything I could even begin to craft a PR out of... but incase anyone is feeling brave and wants to build on what I had started, I'll share the relevant pieces. I likely won't have time to pick this up again. Keep in mind this was all very much work in progress (from 2 years ago!) so it's a bit messy! componentDiagam.jison /** mermaid
* https://knsv.github.io/mermaid
* (c) 2015 Knut Sveidqvist
* MIT license.
*/
%lex
%options case-insensitive
%x string
%x token
%x unqString
%x open_directive
%x type_directive
%x arg_directive
%x close_directive
%%
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
(\r?\n)+ return 'NEWLINE';
\s+ /* skip all whitespace */
\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
<<EOF>> return 'EOF';
"componentDiagram" return 'COMPONENT_DIAGRAM';
"{" return 'STRUCT_START';
"}" return 'STRUCT_END';
":" return 'COLONSEP';
"[[" return 'NODE_START';
"]]" return 'NODE_END';
"[" return 'COMPONENT_START';
"]" return 'COMPONENT_END';
"<<" return 'NODE_TYPE_START';
">>" return 'NODE_TYPE_END';
"(" return "INTERFACE_START";
")" return "INTERFACE_END";
/*
"id" return 'ID';
"text" return 'TEXT';
"risk" return 'RISK';
"verifyMethod" return 'VERIFYMTHD';
"requirement" return 'REQUIREMENT';
"functionalRequirement" return 'FUNCTIONAL_REQUIREMENT';
"interfaceRequirement" return 'INTERFACE_REQUIREMENT';
"performanceRequirement" return 'PERFORMANCE_REQUIREMENT';
"physicalRequirement" return 'PHYSICAL_REQUIREMENT';
"designConstraint" return 'DESIGN_CONSTRAINT';
"low" return 'LOW_RISK';
"medium" return 'MED_RISK';
"high" return 'HIGH_RISK';
"analysis" return 'VERIFY_ANALYSIS';
"demonstration" return 'VERIFY_DEMONSTRATION';
"inspection" return 'VERIFY_INSPECTION';
"test" return 'VERIFY_TEST';
"element" return 'ELEMENT';
"contains" return 'CONTAINS';
"copies" return 'COPIES';
"derives" return 'DERIVES';
"satisfies" return 'SATISFIES';
"verifies" return 'VERIFIES';
"refines" return 'REFINES';
"traces" return 'TRACES';
"type" return 'TYPE';
"docref" return 'DOCREF';*/
"<-" return 'END_ARROW_L';
"->" {return 'END_ARROW_R';}
"-" {return 'LINE';}
["] { this.begin("string"); }
<string>["] { this.popState(); }
<string>[^"]* { return "qString"; }
[\w][^\r\n\{\<\>\-\=]* { yytext = yytext.trim(); return 'unqString';}
/lex
%start start
%% /* language grammar */
start
: directive NEWLINE start
| directive start
| COMPONENT_DIAGRAM NEWLINE document EOF;
directive
: openDirective typeDirective closeDirective
| openDirective typeDirective ':' argDirective closeDirective;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); };
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); };
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); };
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'pie'); };
separator: NEWLINE | SEMI | EOF ;
document
: /* empty */
{ $$ = [];}
| document line
{
//console.log($1, $2);
if($2 !== []){
$1.push($2);
}
$$=$1;}
;
line
: statement
{$$=$1;}
| SEMI
| NEWLINE
| SPACE
| EOF
;
statement
: COMPONENT_START qString COMPONENT_END
{ $$=yy.addComponent($2) }
| NODE_START qString NODE_END STRUCT_START separator NODE_TYPE_START qString NODE_TYPE_END separator document STRUCT_END
{$$=yy.addNode($2, $7, $10)}
| INTERFACE_START qString INTERFACE_END
{$$=yy.addInterface($2)}
| qString END_ARROW_R qString
{$$=yy.addRelationship($1, $3, 'SOLID')}
| qString LINE END_ARROW_R qString
{$$=yy.addRelationship($1, $4, 'DASHED')}
| qString LINE qString END_ARROW_R qString
{$$=yy.addRelationship($1, $5, 'SOLID', $3)}
| qString LINE LINE qString LINE END_ARROW_R qString
{$$=yy.addRelationship($1, $7, 'DASHED', $4)};
/*
diagram
: { $$ = [] }
| requirementDef diagram
| elementDef diagram
| relationshipDef diagram
| directive diagram
| NEWLINE diagram;
*/
/*
requirementDef
: requirementType requirementName STRUCT_START NEWLINE requirementBody
{ yy.addRequirement($2, $1) };
requirementBody
: ID COLONSEP id NEWLINE requirementBody
{ yy.setNewReqId($3); }
| TEXT COLONSEP text NEWLINE requirementBody
{ yy.setNewReqText($3); }
| RISK COLONSEP riskLevel NEWLINE requirementBody
{ yy.setNewReqRisk($3); }
| VERIFYMTHD COLONSEP verifyType NEWLINE requirementBody
{ yy.setNewReqVerifyMethod($3); }
| NEWLINE requirementBody
| STRUCT_STOP;
requirementType
: REQUIREMENT
{ $$=yy.RequirementType.REQUIREMENT;}
| FUNCTIONAL_REQUIREMENT
{ $$=yy.RequirementType.FUNCTIONAL_REQUIREMENT;}
| INTERFACE_REQUIREMENT
{ $$=yy.RequirementType.INTERFACE_REQUIREMENT;}
| PERFORMANCE_REQUIREMENT
{ $$=yy.RequirementType.PERFORMANCE_REQUIREMENT;}
| PHYSICAL_REQUIREMENT
{ $$=yy.RequirementType.PHYSICAL_REQUIREMENT;}
| DESIGN_CONSTRAINT
{ $$=yy.RequirementType.DESIGN_CONSTRAINT;};
riskLevel
: LOW_RISK { $$=yy.RiskLevel.LOW_RISK;}
| MED_RISK { $$=yy.RiskLevel.MED_RISK;}
| HIGH_RISK { $$=yy.RiskLevel.HIGH_RISK;};
verifyType
: VERIFY_ANALYSIS
{ $$=yy.VerifyType.VERIFY_ANALYSIS;}
| VERIFY_DEMONSTRATION
{ $$=yy.VerifyType.VERIFY_DEMONSTRATION;}
| VERIFY_INSPECTION
{ $$=yy.VerifyType.VERIFY_INSPECTION;}
| VERIFY_TEST
{ $$=yy.VerifyType.VERIFY_TEST;};
elementDef
: ELEMENT elementName STRUCT_START NEWLINE elementBody
{ yy.addElement($2) };
elementBody
: TYPE COLONSEP type NEWLINE elementBody
{ yy.setNewElementType($3); }
| DOCREF COLONSEP ref NEWLINE elementBody
{ yy.setNewElementDocRef($3); }
| NEWLINE elementBody
| STRUCT_STOP;
relationshipDef
: id END_ARROW_L relationship LINE id
{ yy.addRelationship($3, $5, $1) }
| id LINE relationship END_ARROW_R id
{ yy.addRelationship($3, $1, $5) };
relationship
: CONTAINS
{ $$=yy.Relationships.CONTAINS;}
| COPIES
{ $$=yy.Relationships.COPIES;}
| DERIVES
{ $$=yy.Relationships.DERIVES;}
| SATISFIES
{ $$=yy.Relationships.SATISFIES;}
| VERIFIES
{ $$=yy.Relationships.VERIFIES;}
| REFINES
{ $$=yy.Relationships.REFINES;}
| TRACES
{ $$=yy.Relationships.TRACES;};
requirementName: unqString | qString;
id : unqString | qString;
text : unqString | qString;
elementName : unqString | qString;
type : unqString | qString;
ref : unqString | qString;*/
%% componentDb.js import * as configApi from '../../config';
import { log } from '../../logger';
import mermaidAPI from '../../mermaidAPI';
let AST_NODE_TYPE = {
COMPONENT: 'COMPONENT',
NODE: 'NODE',
INTERFACE: 'INTERFACE',
};
let AST = {};
let relationships = [];
const addComponent = (name) => {
if (typeof AST[name] === 'undefined') {
AST[name] = {
name,
astType: AST_NODE_TYPE.COMPONENT,
};
}
return AST[name];
};
const addInterface = (name) => {
if (typeof AST[name] === 'undefined') {
AST[name] = {
name,
astType: AST_NODE_TYPE.INTERFACE,
};
}
return AST[name];
};
const addNode = (name, type, list) => {
//console.log(`Adding node ${name}, type: ${type}`);
if (typeof AST[name] === 'undefined') {
AST[name] = {
name,
type: type,
children: list.filter((item) => item.astType),
astType: AST_NODE_TYPE.NODE,
};
}
list.forEach((item) => {
delete AST[item.name];
});
return AST[name];
};
const getAST = () => AST;
const addRelationship = (src, dst, type, text) => {
relationships.push({
src,
dst,
type,
text,
});
};
const getRelationships = () => relationships;
const clear = () => {
AST = {};
relationships = [];
};
export const parseDirective = function (statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
export default {
parseDirective,
getConfig: () => configApi.getConfig().req,
AST_NODE_TYPE,
addComponent,
addInterface,
addNode,
addRelationship,
getAST,
getRelationships,
clear,
}; componentMarkers.js const ReqMarkers = {
CONTAINS: 'contains',
ARROW: 'arrow',
};
const insertLineEndings = (parentNode, conf) => {
let containsNode = parentNode
.append('defs')
.append('marker')
.attr('id', ReqMarkers.CONTAINS + '_line_ending')
.attr('refX', 0)
.attr('refY', conf.line_height / 2)
.attr('markerWidth', conf.line_height)
.attr('markerHeight', conf.line_height)
.attr('orient', 'auto')
.append('g');
containsNode
.append('circle')
.attr('cx', conf.line_height / 2)
.attr('cy', conf.line_height / 2)
.attr('r', conf.line_height / 2)
// .attr('stroke', conf.rect_border_color)
// .attr('stroke-width', 1)
.attr('fill', 'none');
containsNode
.append('line')
.attr('x1', 0)
.attr('x2', conf.line_height)
.attr('y1', conf.line_height / 2)
.attr('y2', conf.line_height / 2)
// .attr('stroke', conf.rect_border_color)
.attr('stroke-width', 1);
containsNode
.append('line')
.attr('y1', 0)
.attr('y2', conf.line_height)
.attr('x1', conf.line_height / 2)
.attr('x2', conf.line_height / 2)
// .attr('stroke', conf.rect_border_color)
.attr('stroke-width', 1);
parentNode
.append('defs')
.append('marker')
.attr('id', ReqMarkers.ARROW + '_line_ending')
.attr('refX', conf.line_height)
.attr('refY', 0.5 * conf.line_height)
.attr('markerWidth', conf.line_height)
.attr('markerHeight', conf.line_height)
.attr('orient', 'auto')
.append('path')
.attr(
'd',
`M0,0
L${conf.line_height},${conf.line_height / 2}
M${conf.line_height},${conf.line_height / 2}
L0,${conf.line_height}`
)
.attr('stroke-width', 1);
// .attr('stroke', conf.rect_border_color);
};
export default {
ReqMarkers,
insertLineEndings,
}; componentRenderer.js import { line, select } from 'd3';
import dagre from 'dagre';
import graphlib from 'graphlib';
// import * as configApi from '../../config';
import { log, setLogLevel } from '../../logger';
import { configureSvgSize } from '../../utils';
import common from '../common/common';
import { parser } from './parser/componentDiagram';
import componentDb from './componentDb';
import markers from './componentMarkers';
const conf = {};
let relCnt = 0;
export const setConf = function (cnf) {
if (typeof cnf === 'undefined') {
return;
}
const keys = Object.keys(cnf);
for (let i = 0; i < keys.length; i++) {
conf[keys[i]] = cnf[keys[i]];
}
};
const newSystemComponentNode = (parentNode, id, name) => {
const mainBox = parentNode
.insert('rect', '#' + id)
.attr('class', 'component componentBox')
.attr('x', 0)
.attr('y', 0)
.attr('width', conf.rect_min_width + 'px')
.attr('height', '60px');
const decorWidth = 16;
const decorHeight = 16;
const decorOffset = 5;
parentNode
.append('rect')
.attr('id', id + '-decor')
.attr('class', 'component componentBox cornerDecor')
.attr('x', `${conf.rect_min_width - decorWidth - decorOffset}px`)
.attr('y', `${decorOffset}px`)
.attr('width', `${decorWidth}px`)
.attr('height', `${decorHeight}px`);
parentNode
.append('rect')
.attr('id', id + '-decor-2')
.attr('class', 'component componentBox cornerDecor')
.attr('x', `${conf.rect_min_width - decorWidth - decorOffset - 5}px`)
.attr('y', `${decorOffset + 3}px`)
.attr('width', `10px`)
.attr('height', `3px`);
parentNode
.append('rect')
.attr('id', id + '-decor-3')
.attr('class', 'component componentBox cornerDecor')
.attr('x', `${conf.rect_min_width - decorWidth - decorOffset - 5}px`)
.attr('y', `${decorOffset + 9}px`)
.attr('width', `10px`)
.attr('height', `3px`);
let x = conf.rect_min_width / 2;
let title = parentNode
.append('text')
.attr('class', 'component componentLabel componentTitle')
.attr('id', id + '-name')
.attr('x', x)
.attr('y', '25px')
.attr('dominant-baseline', 'hanging')
.append('tspan')
.attr('text-anchor', 'middle')
.attr('x', conf.rect_min_width / 2)
.attr('dy', 0)
.text(name);
return mainBox;
};
const newSystemInterfaceNode = (parentNode, id, name) => {
const mainBox = parentNode
.insert('rect', '#' + id)
.attr('class', 'component interface')
.attr('x', 0)
.attr('y', 0)
.attr('width', '100px')
.attr('height', '32px');
const circleNode = parentNode
.append('circle')
.attr('id', id + '-decor')
.attr('class', 'component interface interface-decor')
.attr('cx', `${50}px`)
.attr('cy', `16px`)
.attr('r', `10px`);
parentNode
.append('text')
.attr('class', 'component interface interfaceTitle')
.attr('id', id + '-name')
.attr('x', `0px`)
.attr('y', '30px')
.attr('dominant-baseline', 'hanging')
.append('tspan')
.attr('text-anchor', 'middle')
.attr('x', 100 / 2)
.attr('dy', 0)
.text(name);
return mainBox;
};
const newSystemNodeNode = (parentNode, id, name, type) => {
parentNode
.append('rect')
.attr('id', id + '-box')
.attr('class', 'component componentBox')
.attr('x', 0)
.attr('y', 0)
.attr('width', conf.rect_min_width + 'px')
.attr('height', conf.rect_min_height + 'px');
parentNode
.append('rect')
.attr('id', id + '-decor1')
.attr('class', 'component componentBox componentBox-decor')
.attr('x', '0')
.attr('y', '0')
.attr('width', '10px')
.attr('height', conf.rect_min_height + 'px')
.attr('transform', `translate(${conf.rect_min_width}, 0), skewY(-45)`);
parentNode
.append('rect')
.attr('id', id + '-decor2')
.attr('class', 'component componentBox')
.attr('x', '0px')
.attr('y', '-10px')
.attr('width', conf.rect_min_width + 'px')
.attr('height', '10px')
.attr('transform', 'skewX(-45)');
// <rect x="-3" y="-3" width="2" height="6" fill="red" transform="skewY(-30)"></rect>
let x = conf.rect_min_width / 2;
let title = parentNode
.append('text')
.attr('class', 'component componentLabel componentTitle')
.attr('id', id + '-name')
.attr('x', x)
.attr('y', conf.rect_padding + 'px')
.attr('dominant-baseline', 'hanging');
title
.append('tspan')
.attr('text-anchor', 'left')
.attr('x', '20px')
.attr('dy', 0)
.text(`<<${type}>>`);
title
.append('tspan')
.attr('text-anchor', 'left')
.attr('x', '20px')
.attr('dy', conf.line_height * 0.75)
.text(name);
};
const addEdgeLabel = (parentNode, svgPath, conf, txt) => {
// Find the half-way point
const len = svgPath.node().getTotalLength();
const labelPoint = svgPath.node().getPointAtLength(len * 0.5);
// Append a text node containing the label
const labelId = 'rel' + relCnt;
relCnt++;
const labelNode = parentNode
.append('text')
.attr('class', 'component relationshipLabel')
.attr('id', labelId)
.attr('x', labelPoint.x)
.attr('y', labelPoint.y)
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
// .attr('style', 'font-family: ' + conf.fontFamily + '; font-size: ' + conf.fontSize + 'px')
.text(txt);
// Figure out how big the opaque 'container' rectangle needs to be
const labelBBox = labelNode.node().getBBox();
// Insert the opaque rectangle before the text label
parentNode
.insert('rect', '#' + labelId)
.attr('class', 'component componentLabelBox')
.attr('x', labelPoint.x - labelBBox.width / 2)
.attr('y', labelPoint.y - labelBBox.height / 2)
.attr('width', labelBBox.width)
.attr('height', labelBBox.height)
.attr('fill', 'white')
.attr('fill-opacity', '85%');
};
const drawRelationshipFromLayout = function (svg, rel, g, insert) {
// Find the edge relating to this relationship
const edge = g.edge(elementString(rel.src), elementString(rel.dst));
// TODO: Comment this putting the arrow up to the right side of the source circle
const srcNode = g.node(rel.src);
if (srcNode.shape === 'CIRCLE' && edge.points[0].x > srcNode.x && edge.points[0].y > srcNode.y) {
edge.points[0].x = srcNode.x;
edge.points[0].y = srcNode.y + 10;
} else if (
srcNode.shape === 'CIRCLE' &&
edge.points[0].x > srcNode.x &&
edge.points[0].y < srcNode.y
) {
edge.points[0].x = srcNode.x;
edge.points[0].y = srcNode.y - 10;
} else if (srcNode.shape === 'CIRCLE' && edge.points[0].x > srcNode.x) {
edge.points[0].x = srcNode.x + 10;
}
// TODO: Comment this putting the arrow up to the left side of the destination circle
const dstNode = g.node(rel.dst);
if (
dstNode.shape === 'CIRCLE' &&
edge.points[edge.points.length - 1].x < dstNode.x &&
edge.points[edge.points.length - 1].y > dstNode.y
) {
edge.points[edge.points.length - 1].x = dstNode.x;
edge.points[edge.points.length - 1].y = dstNode.y + 10;
} else if (
dstNode.shape === 'CIRCLE' &&
edge.points[edge.points.length - 1].x < dstNode.x &&
edge.points[edge.points.length - 1].y < dstNode.y
) {
edge.points[edge.points.length - 1].x = dstNode.x;
edge.points[edge.points.length - 1].y = dstNode.y - 10;
} else if (dstNode.shape === 'CIRCLE' && edge.points[edge.points.length - 1].x < dstNode.x) {
edge.points[edge.points.length - 1].x = dstNode.x - 10;
}
// Get a function that will generate the line path
const lineFunction = line()
.x(function (d) {
return d.x;
})
.y(function (d) {
return d.y;
});
// Insert the line at the right place
const svgPath = svg
.insert('path', '#' + insert)
.attr('class', 'er relationshipLine')
.attr('d', lineFunction(edge.points))
.attr('fill', 'none');
if (rel.type === 'DASHED') {
svgPath.attr('stroke-dasharray', '10,7');
}
svgPath.attr(
'marker-end',
'url(' +
common.getUrl(conf.arrowMarkerAbsolute) +
'#' +
markers.ReqMarkers.ARROW +
'_line_ending' +
')'
);
addEdgeLabel(svg, svgPath, conf, rel.text);
return;
};
export const drawASTNode = (ASTNode, graph, svgNode, parentNodeName = null) => {
log.info('Adding new ASTNode: ', ASTNode.name);
const groupNode = svgNode.append('g').attr('id', ASTNode.name);
const textId = `${ASTNode.astType.toLowerCase()}-${ASTNode.name}`;
let mainNode;
let shape = 'RECTANGLE';
if (ASTNode.astType === componentDb.AST_NODE_TYPE.COMPONENT) {
mainNode = newSystemComponentNode(groupNode, textId, ASTNode.name);
} else if (ASTNode.astType === componentDb.AST_NODE_TYPE.INTERFACE) {
mainNode = newSystemInterfaceNode(groupNode, textId, ASTNode.name);
shape = 'CIRCLE';
} else if (ASTNode.astType === componentDb.AST_NODE_TYPE.NODE) {
newSystemNodeNode(groupNode, textId, ASTNode.name, ASTNode.type);
mainNode = groupNode;
ASTNode.children.forEach((child) => {
drawASTNode(child, graph, svgNode, ASTNode.name);
});
}
if (parentNodeName) {
graph.setParent(ASTNode.name, parentNodeName);
}
const rectBBox = mainNode.node().getBBox();
// Add the entity to the graph
graph.setNode(ASTNode.name, {
width: rectBBox.width,
height: rectBBox.height,
id: ASTNode.name,
shape,
});
};
const addRelationships = (relationships, g) => {
relationships.forEach(function (r) {
let src = elementString(r.src);
let dst = elementString(r.dst);
g.setEdge(src, dst, { relationship: r });
});
return relationships;
};
const calculateContainerDimentions = (graph, nodeName) => {
let furthestRight = 0;
let furthestBottom = 0;
graph.children(nodeName).forEach((childName) => {
const child = graph.node(childName);
if (nodeName === 'gb-core-cac-dev') {
console.log(childName, child.x, child.width);
}
furthestRight = Math.max(furthestRight, child.x - child.width / 2 + child.width);
furthestBottom = Math.max(furthestBottom, child.y - child.height / 2 + child.height);
});
if (nodeName === 'gb-core-cac-dev') {
console.log(furthestRight);
}
return { furthestRight, furthestBottom };
};
const adjustEntities = function (svgNode, graph) {
console.log(graph);
graph.nodes().forEach(function (v) {
if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') {
svgNode.select('#' + v);
svgNode
.select('#' + v)
.attr(
'transform',
'translate(' +
(graph.node(v).x - graph.node(v).width / 2) +
',' +
(graph.node(v).y - graph.node(v).height / 2) +
' )'
);
if (graph.children(v).length > 0) {
const dimentions = calculateContainerDimentions(graph, v);
const newWidth =
dimentions.furthestRight - (graph.node(v).x - graph.node(v).width / 2) + 20;
const newHeight =
dimentions.furthestBottom - (graph.node(v).y - graph.node(v).height / 2) + 20;
svgNode
.select(`#node-${v}-box`)
.attr('width', `${newWidth}px`)
.attr('height', `${newHeight}px`);
svgNode
.select(`#node-${v}-decor1`)
.attr('height', `${newHeight}px`)
.attr('transform', `translate(${newWidth}, 0), skewY(-45)`);
svgNode.select(`#node-${v}-decor2`).attr('width', `${newWidth}px`);
}
}
});
return;
};
const elementString = (str) => {
return str.replace(/\s/g, '').replace(/\./g, '_');
};
export const draw = (text, id) => {
parser.yy = componentDb;
parser.yy.clear();
parser.parse(text);
const svg = select(`[id='${id}']`);
markers.insertLineEndings(svg, conf);
const g = new graphlib.Graph({
multigraph: true,
compound: true,
})
.setGraph({
rankdir: 'LR', //conf.layoutDirection,
marginx: 20,
marginy: 20,
nodesep: 50,
edgesep: 60,
ranksep: 100,
})
.setDefaultEdgeLabel(function () {
return {};
});
let AST = componentDb.getAST();
let relationships = componentDb.getRelationships();
console.log(AST);
console.table(relationships);
Object.keys(AST).forEach((nodeName) => {
let ASTNode = AST[nodeName];
drawASTNode(ASTNode, g, svg);
});
addRelationships(relationships, g);
dagre.layout(g);
adjustEntities(svg, g);
relationships.forEach(function (rel) {
drawRelationshipFromLayout(svg, rel, g, id);
});
const padding = conf.rect_padding;
const svgBounds = svg.node().getBBox();
const width = svgBounds.width + padding * 2;
const height = svgBounds.height + padding * 2;
configureSvgSize(svg, height, width, conf.useMaxWidth);
svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`);
};
export default {
setConf,
draw,
}; styles.js const getStyles = (options) => {
return `
marker {
fill: ${options.relationColor};
stroke: ${options.relationColor};
}
marker.cross {
stroke: ${options.lineColor};
}
svg {
font-family: ${options.fontFamily};
font-size: ${options.fontSize};
}
.componentBox {
fill: ${options.componentBackground};
fill-opacity: 100%;
stroke: ${options.componentBorderColor};
stroke-width: ${options.componentBorderSize};
}
.systemNode {
fill: ${options.componentBackground};
fill-opacity: 100%;
stroke: ${options.componentBorderColor};
stroke-width: ${options.componentBorderSize};
}
.componentTitle, .componentLabel{
fill: ${options.componentTextColor};
}
.componentLabelBox {
fill: ${options.relationLabelBackground};
fill-opacity: 100%;
}
.component-title-line {
stroke: ${options.componentBorderColor};
stroke-width: ${options.componentBorderSize};
}
.relationshipLine {
stroke: ${options.relationColor};
stroke-width: 1;
}
.relationshipLabel {
fill: ${options.relationLabelColor};
}
.interface {
fill: transparent;
}
.interface.interface-decor {
fill: ${options.componentBackground};
fill-opacity: 100%;
stroke: ${options.componentBorderColor};
stroke-width: ${options.componentBorderSize};
}
.interface.interfaceTitle {
fill: ${options.componentTextColor};
}
`;
};
// fill', conf.rect_fill)
export default getStyles; |
+1 |
1 similar comment
+1 |
+1 Why not? |
+1 |
4 similar comments
+1 |
+1 |
+1 |
+1 |
? 谁这么闲在点踩 |
+1 |
2 similar comments
+1 |
+1 |
As a software architect I use mermaid for drawing class diagrams. Classes compose components. Therefore on the top level I need to create component diagrams too. This type of diagrams is missing in mermaid though.
As an alternative I use plantUML: https://plantuml.com/component-diagram
For reference: https://en.wikipedia.org/wiki/Component_diagram
The text was updated successfully, but these errors were encountered: