feat(arch): implemented node labels

This commit is contained in:
NicolasNewman
2024-05-06 09:59:33 -05:00
parent cabb7b65e9
commit 734bde3877
8 changed files with 64 additions and 11 deletions

View File

@@ -48,7 +48,7 @@ const clear = (): void => {
commonClear(); commonClear();
}; };
const addService = function ({ id, icon, in: parent, title }: Omit<ArchitectureService, "edges">) { const addService = function ({ id, icon, in: parent, title, iconText }: Omit<ArchitectureService, "edges">) {
if (state.records.registeredIds[id] !== undefined) { if (state.records.registeredIds[id] !== undefined) {
throw new Error(`The service id [${id}] is already in use by another ${state.records.registeredIds[id]}`); throw new Error(`The service id [${id}] is already in use by another ${state.records.registeredIds[id]}`);
} }
@@ -71,6 +71,7 @@ const addService = function ({ id, icon, in: parent, title }: Omit<ArchitectureS
state.records.services[id] = { state.records.services[id] = {
id, id,
icon, icon,
iconText,
title, title,
edges: [], edges: [],
in: parent, in: parent,

View File

@@ -19,6 +19,20 @@ const getStyles: DiagramStylesProvider = (options: ArchitectureStyleOptions) =>
stroke-width: ${options.archGroupBorderStrokeWidth}; stroke-width: ${options.archGroupBorderStrokeWidth};
stroke-dasharray: 8; stroke-dasharray: 8;
} }
.node-icon-text {
display: flex;
align-items: center;
}
.node-icon-text > div {
color: #fff;
margin: 1px;
height: fit-content;
text-align: center;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
}
`; `;
export default getStyles; export default getStyles;

View File

@@ -183,6 +183,7 @@ export interface ArchitectureService {
id: string; id: string;
edges: ArchitectureEdge[]; edges: ArchitectureEdge[];
icon?: string; icon?: string;
iconText?: string;
title?: string; title?: string;
in?: string; in?: string;
width?: number; width?: number;

View File

@@ -118,15 +118,16 @@ export const drawEdges = function (edgesEl: D3Element, cy: cytoscape.Core) {
// Calculate the new width/height with the rotation applied, and transform to the proper position // Calculate the new width/height with the rotation applied, and transform to the proper position
const bboxNew = textElem.node().getBoundingClientRect(); const bboxNew = textElem.node().getBoundingClientRect();
textElem textElem.attr(
.attr('transform', ` 'transform',
translate(${midX}, ${midY - (bboxOrig.height / 2)}) `
translate(${x * bboxNew.width / 2}, ${y * bboxNew.height / 2}) translate(${midX}, ${midY - bboxOrig.height / 2})
translate(${(x * bboxNew.width) / 2}, ${(y * bboxNew.height) / 2})
rotate(${-1 * x * y * 45}, 0, ${bboxOrig.height / 2}) rotate(${-1 * x * y * 45}, 0, ${bboxOrig.height / 2})
`); `
);
} }
} }
} }
} }
}); });
@@ -163,12 +164,16 @@ export const drawGroups = function (groupsEl: D3Element, cy: cytoscape.Core) {
getIcon(data.icon)?.(bkgElem, groupIconSize); getIcon(data.icon)?.(bkgElem, groupIconSize);
bkgElem.attr( bkgElem.attr(
'transform', 'transform',
'translate(' + (shiftedX1 + halfIconSize + 1) + ', ' + (shiftedY1 + halfIconSize + 1) + ')' 'translate(' +
(shiftedX1 + halfIconSize + 1) +
', ' +
(shiftedY1 + halfIconSize + 1) +
')'
); );
shiftedX1 += groupIconSize; shiftedX1 += groupIconSize;
// TODO: test with more values // TODO: test with more values
// - 1 - 2 comes from the Y axis transform of the icon and label // - 1 - 2 comes from the Y axis transform of the icon and label
shiftedY1 += ((fontSize / 2) - 1 - 2); shiftedY1 += fontSize / 2 - 1 - 2;
} }
if (data.label) { if (data.label) {
const textElem = groupLabelContainer.append('g'); const textElem = groupLabelContainer.append('g');
@@ -190,7 +195,11 @@ export const drawGroups = function (groupsEl: D3Element, cy: cytoscape.Core) {
textElem.attr( textElem.attr(
'transform', 'transform',
'translate(' + (shiftedX1 + halfIconSize + 4) + ', ' + (shiftedY1 + halfIconSize + 2) + ')' 'translate(' +
(shiftedX1 + halfIconSize + 4) +
', ' +
(shiftedY1 + halfIconSize + 2) +
')'
); );
} }
} }
@@ -218,6 +227,7 @@ export const drawServices = function (
}, },
getConfig() getConfig()
); );
textElem textElem
.attr('dy', '1em') .attr('dy', '1em')
.attr('alignment-baseline', 'middle') .attr('alignment-baseline', 'middle')
@@ -234,6 +244,17 @@ export const drawServices = function (
// throw new Error(`Invalid SVG Icon name: "${service.icon}"`); // throw new Error(`Invalid SVG Icon name: "${service.icon}"`);
// } // }
bkgElem = getIcon(service.icon)?.(bkgElem, iconSize); bkgElem = getIcon(service.icon)?.(bkgElem, iconSize);
} else if (service.iconText) {
bkgElem = getIcon('blank')?.(bkgElem, iconSize);
const textElemContainer = bkgElem.append('g');
const fo = textElemContainer.append('foreignObject').attr('width', iconSize).attr('height', iconSize);
const divElem = fo
.append('div')
.attr('class', 'node-icon-text')
.attr('style', `height: ${iconSize}px;`)
.append('div').html(service.iconText);
const fontSize = parseInt(window.getComputedStyle(divElem.node(), null).getPropertyValue("font-size").replace(/[^\d]/g, '')) ?? 16;
divElem.attr('style', `-webkit-line-clamp: ${Math.floor((iconSize - 2) / fontSize)};`)
} else { } else {
bkgElem bkgElem
.append('path') .append('path')

View File

@@ -0,0 +1,11 @@
/**
* Designer: Nicolas Newman
*/
import { createIcon } from '../svgRegister.js';
export default createIcon(
`<g>
<rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/>
</g>`,
80
);

View File

@@ -5,6 +5,7 @@ import disk from './disk.js';
import internet from './internet.js'; import internet from './internet.js';
import cloud from './cloud.js'; import cloud from './cloud.js';
import unknown from './unknown.js'; import unknown from './unknown.js';
import blank from './blank.js';
const defaultIconLibrary: IconLibrary = { const defaultIconLibrary: IconLibrary = {
database: database, database: database,
@@ -13,6 +14,7 @@ const defaultIconLibrary: IconLibrary = {
internet: internet, internet: internet,
cloud: cloud, cloud: cloud,
unknown: unknown, unknown: unknown,
blank: blank,
}; };
export default defaultIconLibrary; export default defaultIconLibrary;

View File

@@ -26,7 +26,7 @@ Group:
; ;
Service: Service:
'service' id=ARCH_ID icon=ARCH_ICON? title=ARCH_TITLE? ('in' in=ARCH_ID)? EOL 'service' id=ARCH_ID (iconText=ARCH_TEXT_ICON | icon=ARCH_ICON)? title=ARCH_TITLE? ('in' in=ARCH_ID)? EOL
; ;
Edge: Edge:
@@ -35,6 +35,7 @@ Edge:
terminal ARROW_DIRECTION: 'L' | 'R' | 'T' | 'B'; terminal ARROW_DIRECTION: 'L' | 'R' | 'T' | 'B';
terminal ARCH_ID: /[\w]+/; terminal ARCH_ID: /[\w]+/;
terminal ARCH_TEXT_ICON: /\("[^"]+"\)/;
terminal ARCH_ICON: /\([\w]+\)/; terminal ARCH_ICON: /\([\w]+\)/;
terminal ARCH_TITLE: /\[[\w ]+\]/; terminal ARCH_TITLE: /\[[\w ]+\]/;
terminal ARROW_INTO: /\(|\)/; terminal ARROW_INTO: /\(|\)/;

View File

@@ -11,6 +11,8 @@ export class ArchitectureValueConverter extends AbstractMermaidValueConverter {
): ValueType | undefined { ): ValueType | undefined {
if (rule.name === 'ARCH_ICON') { if (rule.name === 'ARCH_ICON') {
return input.replace(/[()]/g, '').trim(); return input.replace(/[()]/g, '').trim();
} else if (rule.name === 'ARCH_TEXT_ICON') {
return input.replace(/[()"]/g, '');
} else if (rule.name === 'ARCH_TITLE') { } else if (rule.name === 'ARCH_TITLE') {
return input.replace(/[[\]]/g, '').trim(); return input.replace(/[[\]]/g, '').trim();
} }