From 734bde38777c9190a5a72e96421c83424442d4e4 Mon Sep 17 00:00:00 2001 From: NicolasNewman Date: Mon, 6 May 2024 09:59:33 -0500 Subject: [PATCH] feat(arch): implemented node labels --- .../diagrams/architecture/architectureDb.ts | 3 +- .../architecture/architectureStyles.ts | 14 +++++++ .../architecture/architectureTypes.ts | 1 + .../src/diagrams/architecture/svgDraw.ts | 39 ++++++++++++++----- .../mermaid/src/rendering-util/svg/blank.ts | 11 ++++++ .../mermaid/src/rendering-util/svg/index.ts | 2 + .../architecture/architecture.langium | 3 +- .../language/architecture/valueConverter.ts | 2 + 8 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 packages/mermaid/src/rendering-util/svg/blank.ts diff --git a/packages/mermaid/src/diagrams/architecture/architectureDb.ts b/packages/mermaid/src/diagrams/architecture/architectureDb.ts index e04e9f8da..506178513 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureDb.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureDb.ts @@ -48,7 +48,7 @@ const clear = (): void => { commonClear(); }; -const addService = function ({ id, icon, in: parent, title }: Omit) { +const addService = function ({ id, icon, in: parent, title, iconText }: Omit) { if (state.records.registeredIds[id] !== undefined) { 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 stroke-width: ${options.archGroupBorderStrokeWidth}; 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; diff --git a/packages/mermaid/src/diagrams/architecture/architectureTypes.ts b/packages/mermaid/src/diagrams/architecture/architectureTypes.ts index 2bcea7480..a043b8864 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureTypes.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureTypes.ts @@ -183,6 +183,7 @@ export interface ArchitectureService { id: string; edges: ArchitectureEdge[]; icon?: string; + iconText?: string; title?: string; in?: string; width?: number; diff --git a/packages/mermaid/src/diagrams/architecture/svgDraw.ts b/packages/mermaid/src/diagrams/architecture/svgDraw.ts index 3eba3d3eb..a7c0bed01 100644 --- a/packages/mermaid/src/diagrams/architecture/svgDraw.ts +++ b/packages/mermaid/src/diagrams/architecture/svgDraw.ts @@ -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 const bboxNew = textElem.node().getBoundingClientRect(); - textElem - .attr('transform', ` - translate(${midX}, ${midY - (bboxOrig.height / 2)}) - translate(${x * bboxNew.width / 2}, ${y * bboxNew.height / 2}) + textElem.attr( + 'transform', + ` + translate(${midX}, ${midY - bboxOrig.height / 2}) + translate(${(x * bboxNew.width) / 2}, ${(y * bboxNew.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); bkgElem.attr( 'transform', - 'translate(' + (shiftedX1 + halfIconSize + 1) + ', ' + (shiftedY1 + halfIconSize + 1) + ')' + 'translate(' + + (shiftedX1 + halfIconSize + 1) + + ', ' + + (shiftedY1 + halfIconSize + 1) + + ')' ); shiftedX1 += groupIconSize; // TODO: test with more values // - 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) { const textElem = groupLabelContainer.append('g'); @@ -190,7 +195,11 @@ export const drawGroups = function (groupsEl: D3Element, cy: cytoscape.Core) { textElem.attr( 'transform', - 'translate(' + (shiftedX1 + halfIconSize + 4) + ', ' + (shiftedY1 + halfIconSize + 2) + ')' + 'translate(' + + (shiftedX1 + halfIconSize + 4) + + ', ' + + (shiftedY1 + halfIconSize + 2) + + ')' ); } } @@ -218,6 +227,7 @@ export const drawServices = function ( }, getConfig() ); + textElem .attr('dy', '1em') .attr('alignment-baseline', 'middle') @@ -234,6 +244,17 @@ export const drawServices = function ( // throw new Error(`Invalid SVG Icon name: "${service.icon}"`); // } 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 { bkgElem .append('path') diff --git a/packages/mermaid/src/rendering-util/svg/blank.ts b/packages/mermaid/src/rendering-util/svg/blank.ts new file mode 100644 index 000000000..2d2b59015 --- /dev/null +++ b/packages/mermaid/src/rendering-util/svg/blank.ts @@ -0,0 +1,11 @@ +/** + * Designer: Nicolas Newman + */ +import { createIcon } from '../svgRegister.js'; + +export default createIcon( + ` + +`, + 80 +); diff --git a/packages/mermaid/src/rendering-util/svg/index.ts b/packages/mermaid/src/rendering-util/svg/index.ts index 21b69b949..12abcb456 100644 --- a/packages/mermaid/src/rendering-util/svg/index.ts +++ b/packages/mermaid/src/rendering-util/svg/index.ts @@ -5,6 +5,7 @@ import disk from './disk.js'; import internet from './internet.js'; import cloud from './cloud.js'; import unknown from './unknown.js'; +import blank from './blank.js'; const defaultIconLibrary: IconLibrary = { database: database, @@ -13,6 +14,7 @@ const defaultIconLibrary: IconLibrary = { internet: internet, cloud: cloud, unknown: unknown, + blank: blank, }; export default defaultIconLibrary; diff --git a/packages/parser/src/language/architecture/architecture.langium b/packages/parser/src/language/architecture/architecture.langium index d3700d49e..101390e84 100644 --- a/packages/parser/src/language/architecture/architecture.langium +++ b/packages/parser/src/language/architecture/architecture.langium @@ -26,7 +26,7 @@ Group: ; 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: @@ -35,6 +35,7 @@ Edge: terminal ARROW_DIRECTION: 'L' | 'R' | 'T' | 'B'; terminal ARCH_ID: /[\w]+/; +terminal ARCH_TEXT_ICON: /\("[^"]+"\)/; terminal ARCH_ICON: /\([\w]+\)/; terminal ARCH_TITLE: /\[[\w ]+\]/; terminal ARROW_INTO: /\(|\)/; \ No newline at end of file diff --git a/packages/parser/src/language/architecture/valueConverter.ts b/packages/parser/src/language/architecture/valueConverter.ts index e6a2049a0..8eda774b8 100644 --- a/packages/parser/src/language/architecture/valueConverter.ts +++ b/packages/parser/src/language/architecture/valueConverter.ts @@ -11,6 +11,8 @@ export class ArchitectureValueConverter extends AbstractMermaidValueConverter { ): ValueType | undefined { if (rule.name === 'ARCH_ICON') { return input.replace(/[()]/g, '').trim(); + } else if (rule.name === 'ARCH_TEXT_ICON') { + return input.replace(/[()"]/g, ''); } else if (rule.name === 'ARCH_TITLE') { return input.replace(/[[\]]/g, '').trim(); }