refactor(arch): reorganized code and added more documentation

This commit is contained in:
NicolasNewman
2024-04-06 21:46:37 -05:00
parent aef991bc49
commit 22c0090979
5 changed files with 319 additions and 287 deletions

View File

@@ -119,82 +119,7 @@ const getGroups = (): ArchitectureGroup[] => {
return groups; return groups;
}; };
const getDataStructures = () => { const addEdge = function (
if (datastructures === undefined) {
// Create an adjacency list of the diagram to perform BFS on
// Outer reduce applied on all services
// Inner reduce applied on the edges for a service
const adjList = Object.entries(services).reduce<{ [id: string]: ArchitectureDirectionPairMap }>(
(prev, [id, service]) => {
prev[id] = service.edges.reduce<ArchitectureDirectionPairMap>((prev, edge) => {
if (edge.lhs_id === id) {
// source is LHS
const pair = getArchitectureDirectionPair(edge.lhs_dir, edge.rhs_dir);
if (pair) {
prev[pair] = edge.rhs_id;
}
} else {
// source is RHS
const pair = getArchitectureDirectionPair(edge.rhs_dir, edge.lhs_dir);
if (pair) {
prev[pair] = edge.lhs_id;
}
}
return prev;
}, {});
return prev;
},
{}
);
// Configuration for the initial pass of BFS
const [firstId, _] = Object.entries(adjList)[0];
const visited = { [firstId]: 1 };
const notVisited = Object.keys(adjList).reduce(
(prev, id) => (id === firstId ? prev : { ...prev, [id]: 1 }),
{} as Record<string, number>
);
// Perform BFS on adjacency list
const BFS = (startingId: string): ArchitectureSpatialMap => {
const spatialMap = { [startingId]: [0, 0] };
const queue = [startingId];
while (queue.length > 0) {
const id = queue.shift();
if (id) {
visited[id] = 1;
delete notVisited[id];
const adj = adjList[id];
const [posX, posY] = spatialMap[id];
Object.entries(adj).forEach(([dir, rhsId]) => {
if (!visited[rhsId]) {
spatialMap[rhsId] = shiftPositionByArchitectureDirectionPair(
[posX, posY],
dir as ArchitectureDirectionPair
);
queue.push(rhsId);
}
});
}
}
return spatialMap;
};
const spatialMaps = [BFS(firstId)];
// If our diagram is disconnected, keep adding additional spatial maps until all disconnected graphs have been found
while (Object.keys(notVisited).length > 0) {
spatialMaps.push(BFS(Object.keys(notVisited)[0]));
}
datastructures = {
adjList,
spatialMaps,
};
console.log(datastructures);
}
return datastructures;
};
const addLine = function (
lhs_id: string, lhs_id: string,
lhs_dir: ArchitectureDirection, lhs_dir: ArchitectureDirection,
rhs_id: string, rhs_id: string,
@@ -238,7 +163,87 @@ const addLine = function (
services[lhs_id].edges.push(lines[lines.length - 1]); services[lhs_id].edges.push(lines[lines.length - 1]);
services[rhs_id].edges.push(lines[lines.length - 1]); services[rhs_id].edges.push(lines[lines.length - 1]);
}; };
const getLines = (): ArchitectureLine[] => lines; const getEdges = (): ArchitectureLine[] => lines;
/**
* Returns the current diagram's adjacency list & spatial map.
* If they have not been created, run the algorithms to generate them.
* @returns
*/
const getDataStructures = () => {
if (datastructures === undefined) {
// Create an adjacency list of the diagram to perform BFS on
// Outer reduce applied on all services
// Inner reduce applied on the edges for a service
const adjList = Object.entries(services).reduce<{ [id: string]: ArchitectureDirectionPairMap }>(
(prev, [id, service]) => {
prev[id] = service.edges.reduce<ArchitectureDirectionPairMap>((prev, edge) => {
if (edge.lhs_id === id) {
// source is LHS
const pair = getArchitectureDirectionPair(edge.lhs_dir, edge.rhs_dir);
if (pair) {
prev[pair] = edge.rhs_id;
}
} else {
// source is RHS
const pair = getArchitectureDirectionPair(edge.rhs_dir, edge.lhs_dir);
if (pair) {
prev[pair] = edge.lhs_id;
}
}
return prev;
}, {});
return prev;
},
{}
);
// Configuration for the initial pass of BFS
const [firstId, _] = Object.entries(adjList)[0];
const visited = { [firstId]: 1 };
const notVisited = Object.keys(adjList).reduce(
(prev, id) => (id === firstId ? prev : { ...prev, [id]: 1 }),
{} as Record<string, number>
);
// Perform BFS on the adjacency list
const BFS = (startingId: string): ArchitectureSpatialMap => {
const spatialMap = { [startingId]: [0, 0] };
const queue = [startingId];
while (queue.length > 0) {
const id = queue.shift();
if (id) {
visited[id] = 1;
delete notVisited[id];
const adj = adjList[id];
const [posX, posY] = spatialMap[id];
Object.entries(adj).forEach(([dir, rhsId]) => {
if (!visited[rhsId]) {
spatialMap[rhsId] = shiftPositionByArchitectureDirectionPair(
[posX, posY],
dir as ArchitectureDirectionPair
);
queue.push(rhsId);
}
});
}
}
return spatialMap;
};
const spatialMaps = [BFS(firstId)];
// If our diagram is disconnected, keep adding additional spatial maps until all disconnected graphs have been found
while (Object.keys(notVisited).length > 0) {
spatialMaps.push(BFS(Object.keys(notVisited)[0]));
}
datastructures = {
adjList,
spatialMaps,
};
console.log(datastructures);
}
return datastructures;
};
const setElementForId = (id: string, element: D3Element) => { const setElementForId = (id: string, element: D3Element) => {
elements[id] = element; elements[id] = element;
@@ -258,13 +263,18 @@ export const db: ArchitectureDB = {
getServices, getServices,
addGroup, addGroup,
getGroups, getGroups,
addLine, addEdge: addEdge,
getLines, getEdges: getEdges,
setElementForId, setElementForId,
getElementById, getElementById,
getDataStructures, getDataStructures,
}; };
/**
* Typed wrapper for resolving an architecture diagram's config fields. Returns the default value if undefined
* @param field
* @returns
*/
function getConfigField<T extends keyof ArchitectureDiagramConfig>( function getConfigField<T extends keyof ArchitectureDiagramConfig>(
field: T field: T
): Required<ArchitectureDiagramConfig>[T] { ): Required<ArchitectureDiagramConfig>[T] {

View File

@@ -17,11 +17,12 @@ import {
getOppositeArchitectureDirection, getOppositeArchitectureDirection,
isArchitectureDirectionXY, isArchitectureDirectionXY,
isArchitectureDirectionY, isArchitectureDirectionY,
ArchitectureSpatialMap,
} from './architectureTypes.js'; } from './architectureTypes.js';
import { select } from 'd3'; import { select } from 'd3';
import { setupGraphViewbox } from '../../setupGraphViewbox.js'; import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import type { D3Element } from '../../mermaidAPI.js'; import type { D3Element } from '../../mermaidAPI.js';
import { drawEdges, drawGroups, drawService } from './svgDraw.js'; import { drawEdges, drawGroups, drawServices } from './svgDraw.js';
import { getConfigField } from './architectureDb.js'; import { getConfigField } from './architectureDb.js';
cytoscape.use(fcose); cytoscape.use(fcose);
@@ -44,15 +45,6 @@ function addServices(services: ArchitectureService[], cy: cytoscape.Core) {
}); });
} }
function drawServices(
db: ArchitectureDB,
svg: D3Element,
services: ArchitectureService[],
conf: MermaidConfig
) {
services.forEach((service) => drawService(db, svg, service, conf));
}
function positionServices(db: ArchitectureDB, cy: cytoscape.Core) { function positionServices(db: ArchitectureDB, cy: cytoscape.Core) {
cy.nodes().map((node, id) => { cy.nodes().map((node, id) => {
const data = node.data(); const data = node.data();
@@ -65,7 +57,6 @@ function positionServices(db: ArchitectureDB, cy: cytoscape.Core) {
}); });
} }
function addGroups(groups: ArchitectureGroup[], cy: cytoscape.Core) { function addGroups(groups: ArchitectureGroup[], cy: cytoscape.Core) {
groups.forEach((group) => { groups.forEach((group) => {
cy.add({ cy.add({
@@ -121,6 +112,92 @@ function addEdges(lines: ArchitectureLine[], cy: cytoscape.Core) {
}); });
} }
function getAlignments(spatialMaps: ArchitectureSpatialMap[]): fcose.FcoseAlignmentConstraint {
const alignments = spatialMaps.map((spatialMap) => {
const horizontalAlignments: Record<number, string[]> = {};
const verticalAlignments: Record<number, string[]> = {};
// Group service ids in an object with their x and y coordinate as the key
Object.entries(spatialMap).forEach(([id, [x, y]]) => {
if (!horizontalAlignments[y]) horizontalAlignments[y] = [];
if (!verticalAlignments[x]) verticalAlignments[x] = [];
horizontalAlignments[y].push(id);
verticalAlignments[x].push(id);
});
// Merge the values of each object into a list if the inner list has at least 2 elements
return {
horiz: Object.values(horizontalAlignments).filter((arr) => arr.length > 1),
vert: Object.values(verticalAlignments).filter((arr) => arr.length > 1),
};
});
// Merge the alginment lists for each spatial map into one 2d array per axis
const [horizontal, vertical] = alignments.reduce(
([prevHoriz, prevVert], { horiz, vert }) => {
return [
[...prevHoriz, ...horiz],
[...prevVert, ...vert],
];
},
[[] as string[][], [] as string[][]]
);
return {
horizontal,
vertical,
};
}
function getRelativeConstraints(
spatialMaps: ArchitectureSpatialMap[]
): fcose.FcoseRelativePlacementConstraint[] {
const relativeConstraints: fcose.FcoseRelativePlacementConstraint[] = [];
const posToStr = (pos: number[]) => `${pos[0]},${pos[1]}`;
const strToPos = (pos: string) => pos.split(',').map((p) => parseInt(p));
spatialMaps.forEach((spatialMap) => {
const invSpatialMap = Object.fromEntries(
Object.entries(spatialMap).map(([id, pos]) => [posToStr(pos), id])
);
// perform BFS
const queue = [posToStr([0, 0])];
const visited: Record<string, number> = {};
const directions: Record<ArchitectureDirection, number[]> = {
L: [-1, 0],
R: [1, 0],
T: [0, 1],
B: [0, -1],
};
while (queue.length > 0) {
const curr = queue.shift();
if (curr) {
visited[curr] = 1;
const currId = invSpatialMap[curr];
if (currId) {
const currPos = strToPos(curr);
Object.entries(directions).forEach(([dir, shift]) => {
const newPos = posToStr([currPos[0] + shift[0], currPos[1] + shift[1]]);
const newId = invSpatialMap[newPos];
// If there is an adjacent service to the current one and it has not yet been visited
if (newId && !visited[newPos]) {
queue.push(newPos);
// @ts-ignore cannot determine if left/right or top/bottom are paired together
relativeConstraints.push({
[ArchitectureDirectionName[dir as ArchitectureDirection]]: newId,
[ArchitectureDirectionName[
getOppositeArchitectureDirection(dir as ArchitectureDirection)
]]: currId,
gap: 1.5 * getConfigField('iconSize'),
});
}
});
}
}
}
});
return relativeConstraints;
}
function layoutArchitecture( function layoutArchitecture(
services: ArchitectureService[], services: ArchitectureService[],
groups: ArchitectureGroup[], groups: ArchitectureGroup[],
@@ -192,91 +269,17 @@ function layoutArchitecture(
addEdges(lines, cy); addEdges(lines, cy);
// Use the spatial map to create alignment arrays for fcose // Use the spatial map to create alignment arrays for fcose
const [horizontalAlignments, verticalAlignments] = (() => { const alignmentConstraint = getAlignments(spatialMaps);
const alignments = spatialMaps.map((spatialMap) => {
const _horizontalAlignments: Record<number, string[]> = {};
const _verticalAlignments: Record<number, string[]> = {};
// Group service ids in an object with their x and y coordinate as the key
Object.entries(spatialMap).forEach(([id, [x, y]]) => {
if (!_horizontalAlignments[y]) _horizontalAlignments[y] = [];
if (!_verticalAlignments[x]) _verticalAlignments[x] = [];
_horizontalAlignments[y].push(id);
_verticalAlignments[x].push(id);
});
// Merge the values of each object into a list if the inner list has at least 2 elements
return {
horiz: Object.values(_horizontalAlignments).filter((arr) => arr.length > 1),
vert: Object.values(_verticalAlignments).filter((arr) => arr.length > 1),
};
});
// Merge the alginment lists for each spatial map into one 2d array per axis
return alignments.reduce(
([prevHoriz, prevVert], { horiz, vert }) => {
return [
[...prevHoriz, ...horiz],
[...prevVert, ...vert],
];
},
[[] as string[][], [] as string[][]]
);
})();
// Create the relative constraints for fcose by using an inverse of the spatial map and performing BFS on it // Create the relative constraints for fcose by using an inverse of the spatial map and performing BFS on it
const relativeConstraints = (() => { const relativePlacementConstraint = getRelativeConstraints(spatialMaps);
const _relativeConstraints: fcose.FcoseRelativePlacementConstraint[] = [];
const posToStr = (pos: number[]) => `${pos[0]},${pos[1]}`;
const strToPos = (pos: string) => pos.split(',').map((p) => parseInt(p));
spatialMaps.forEach((spatialMap) => {
const invSpatialMap = Object.fromEntries(
Object.entries(spatialMap).map(([id, pos]) => [posToStr(pos), id])
);
// perform BFS
const queue = [posToStr([0, 0])];
const visited: Record<string, number> = {};
const directions: Record<ArchitectureDirection, number[]> = {
L: [-1, 0],
R: [1, 0],
T: [0, 1],
B: [0, -1],
};
while (queue.length > 0) {
const curr = queue.shift();
if (curr) {
visited[curr] = 1;
const currId = invSpatialMap[curr];
if (currId) {
const currPos = strToPos(curr);
Object.entries(directions).forEach(([dir, shift]) => {
const newPos = posToStr([currPos[0] + shift[0], currPos[1] + shift[1]]);
const newId = invSpatialMap[newPos];
// If there is an adjacent service to the current one and it has not yet been visited
if (newId && !visited[newPos]) {
queue.push(newPos);
// @ts-ignore cannot determine if left/right or top/bottom are paired together
_relativeConstraints.push({
[ArchitectureDirectionName[dir as ArchitectureDirection]]: newId,
[ArchitectureDirectionName[
getOppositeArchitectureDirection(dir as ArchitectureDirection)
]]: currId,
gap: 1.5 * getConfigField('iconSize'),
});
}
});
}
}
}
});
return _relativeConstraints;
})();
console.log(`Horizontal Alignments:`); console.log(`Horizontal Alignments:`);
console.log(horizontalAlignments); console.log(alignmentConstraint.horizontal);
console.log(`Vertical Alignments:`); console.log(`Vertical Alignments:`);
console.log(verticalAlignments); console.log(alignmentConstraint.vertical);
console.log(`Relative Alignments:`); console.log(`Relative Alignments:`);
console.log(relativeConstraints); console.log(relativePlacementConstraint);
const layout = cy.layout({ const layout = cy.layout({
name: 'fcose', name: 'fcose',
@@ -301,13 +304,11 @@ function layoutArchitecture(
const elasticity = parentA === parentB ? 0.45 : 0.001; const elasticity = parentA === parentB ? 0.45 : 0.001;
return elasticity; return elasticity;
}, },
alignmentConstraint: { alignmentConstraint,
horizontal: horizontalAlignments, relativePlacementConstraint,
vertical: verticalAlignments,
},
relativePlacementConstraint: relativeConstraints,
} as FcoseLayoutOptions); } as FcoseLayoutOptions);
// Once the diagram has been generated and the service's position cords are set, adjust the XY edges to have a 90deg bend
layout.one('layoutstop', (_event) => { layout.one('layoutstop', (_event) => {
function getSegmentWeights( function getSegmentWeights(
source: Position, source: Position,
@@ -392,7 +393,7 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram)
const services = db.getServices(); const services = db.getServices();
const groups = db.getGroups(); const groups = db.getGroups();
const lines = db.getLines(); const lines = db.getEdges();
const ds = db.getDataStructures(); const ds = db.getDataStructures();
console.log('Services: ', services); console.log('Services: ', services);
console.log('Lines: ', lines); console.log('Lines: ', lines);
@@ -409,7 +410,7 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram)
const groupElem = svg.append('g'); const groupElem = svg.append('g');
groupElem.attr('class', 'architecture-groups'); groupElem.attr('class', 'architecture-groups');
drawServices(db, servicesElem, services, conf); drawServices(db, servicesElem, services);
const cy = await layoutArchitecture(services, groups, lines, ds); const cy = await layoutArchitecture(services, groups, lines, ds);
// console.log(cy.nodes().map((node) => ({ a: node.data() }))); // console.log(cy.nodes().map((node) => ({ a: node.data() })));

View File

@@ -2,6 +2,10 @@ import type { DiagramDB } from '../../diagram-api/types.js';
import type { ArchitectureDiagramConfig } from '../../config.type.js'; import type { ArchitectureDiagramConfig } from '../../config.type.js';
import type { D3Element } from '../../mermaidAPI.js'; import type { D3Element } from '../../mermaidAPI.js';
/*=======================================*\
| Architecture Diagram Types |
\*=======================================*/
export type ArchitectureDirection = 'L' | 'R' | 'T' | 'B'; export type ArchitectureDirection = 'L' | 'R' | 'T' | 'B';
export type ArchitectureDirectionX = Extract<ArchitectureDirection, 'L' | 'R'>; export type ArchitectureDirectionX = Extract<ArchitectureDirection, 'L' | 'R'>;
export type ArchitectureDirectionY = Extract<ArchitectureDirection, 'T' | 'B'>; export type ArchitectureDirectionY = Extract<ArchitectureDirection, 'T' | 'B'>;
@@ -66,13 +70,18 @@ export const isArchitectureDirectionXY = function (
}; };
/** /**
* Contains LL, RR, TT, BB which are impossible conections * Contains LL, RR, TT, BB which are impossible connections
*/ */
export type InvalidArchitectureDirectionPair = `${ArchitectureDirection}${ArchitectureDirection}`; export type InvalidArchitectureDirectionPair = `${ArchitectureDirection}${ArchitectureDirection}`;
export type ArchitectureDirectionPair = Exclude< export type ArchitectureDirectionPair = Exclude<
InvalidArchitectureDirectionPair, InvalidArchitectureDirectionPair,
'LL' | 'RR' | 'TT' | 'BB' 'LL' | 'RR' | 'TT' | 'BB'
>; >;
/**
* Verifies that the architecture direction pair does not contain an invalid match (LL, RR, TT, BB)
* @param x architecture direction pair which could potentially be invalid
* @returns true if the pair is not LL, RR, TT, or BB
*/
export const isValidArchitectureDirectionPair = function ( export const isValidArchitectureDirectionPair = function (
x: InvalidArchitectureDirectionPair x: InvalidArchitectureDirectionPair
): x is ArchitectureDirectionPair { ): x is ArchitectureDirectionPair {
@@ -98,6 +107,12 @@ export const getArchitectureDirectionPair = function (
return isValidArchitectureDirectionPair(pair) ? pair : undefined; return isValidArchitectureDirectionPair(pair) ? pair : undefined;
}; };
/**
* Given an x,y position for an arrow and the direction of the edge it belongs to, return a factor for slightly shifting the edge
* @param param0 [x, y] coordinate pair
* @param pair architecture direction pair
* @returns a new [x, y] coordinate pair
*/
export const shiftPositionByArchitectureDirectionPair = function ( export const shiftPositionByArchitectureDirectionPair = function (
[x, y]: number[], [x, y]: number[],
pair: ArchitectureDirectionPair pair: ArchitectureDirectionPair
@@ -156,14 +171,14 @@ export interface ArchitectureDB extends DiagramDB {
getServices: () => ArchitectureService[]; getServices: () => ArchitectureService[];
addGroup: (id: string, opts: Omit<ArchitectureGroup, 'id'>) => void; addGroup: (id: string, opts: Omit<ArchitectureGroup, 'id'>) => void;
getGroups: () => ArchitectureGroup[]; getGroups: () => ArchitectureGroup[];
addLine: ( addEdge: (
lhs_id: string, lhs_id: string,
lhs_dir: ArchitectureDirection, lhs_dir: ArchitectureDirection,
rhs_id: string, rhs_id: string,
rhs_dir: ArchitectureDirection, rhs_dir: ArchitectureDirection,
opts: Omit<ArchitectureLine, 'lhs_id' | 'lhs_dir' | 'rhs_id' | 'rhs_dir'> opts: Omit<ArchitectureLine, 'lhs_id' | 'lhs_dir' | 'rhs_id' | 'rhs_dir'>
) => void; ) => void;
getLines: () => ArchitectureLine[]; getEdges: () => ArchitectureLine[];
setElementForId: (id: string, element: D3Element) => void; setElementForId: (id: string, element: D3Element) => void;
getElementById: (id: string) => D3Element; getElementById: (id: string) => D3Element;
getDataStructures: () => ArchitectureDataStructures; getDataStructures: () => ArchitectureDataStructures;
@@ -184,3 +199,67 @@ export interface ArchitectureFields {
datastructures?: ArchitectureDataStructures; datastructures?: ArchitectureDataStructures;
config: ArchitectureDiagramConfig; config: ArchitectureDiagramConfig;
} }
/*=======================================*\
| Cytoscape Override Types |
\*=======================================*/
declare module 'cytoscape' {
type _EdgeSingularData = {
id: string;
source: string;
sourceDir: ArchitectureDirection;
sourceArrow?: boolean;
target: string;
targetDir: ArchitectureDirection;
targetArrow?: boolean;
[key: string]: any;
};
interface EdgeSingular {
_private: {
bodyBounds: unknown;
rscratch: {
startX: number;
startY: number;
midX: number;
midY: number;
endX: number;
endY: number;
};
};
data(): _EdgeSingularData;
data<T extends keyof _EdgeSingularData>(key: T): _EdgeSingularData[T];
}
interface NodeSingular {
_private: {
bodyBounds: {
h: number;
w: number;
x1: number;
x2: number;
y1: number;
y2: number;
};
children: cytoscape.NodeSingular[];
};
data: () =>
| {
type: 'service';
id: string;
icon?: string;
label?: string;
parent?: string;
width: number;
height: number;
[key: string]: any;
}
| {
type: 'group';
id: string;
icon?: string;
label?: string;
parent?: string;
[key: string]: any;
};
}
}

View File

@@ -55,21 +55,21 @@ statement
line_statement line_statement
: id ARROW_LEFT_INTO ARROW_RIGHT_INTO id : id ARROW_LEFT_INTO ARROW_RIGHT_INTO id
{ yy.addLine($1, $2[1], $4, $3[1], {lhs_into: true, rhs_into: true}) } { yy.addEdge($1, $2[1], $4, $3[1], {lhs_into: true, rhs_into: true}) }
| id ARROW_LEFT_INTO ARROW_RIGHT id | id ARROW_LEFT_INTO ARROW_RIGHT id
{ yy.addLine($1, $2[1], $4, $3[1], {lhs_into: true}) } { yy.addEdge($1, $2[1], $4, $3[1], {lhs_into: true}) }
| id ARROW_LEFT ARROW_RIGHT_INTO id | id ARROW_LEFT ARROW_RIGHT_INTO id
{ yy.addLine($1, $2[0], $4, $3[1], {rhs_into: true}) } { yy.addEdge($1, $2[0], $4, $3[1], {rhs_into: true}) }
| id ARROW_LEFT ARROW_RIGHT id | id ARROW_LEFT ARROW_RIGHT id
{ yy.addLine($1, $2[0], $4, $3[1]) } { yy.addEdge($1, $2[0], $4, $3[1]) }
| id ARROW_LEFT_INTO title ARROW_RIGHT_INTO id | id ARROW_LEFT_INTO title ARROW_RIGHT_INTO id
{ yy.addLine($1, $2[1], $5, $4[1], { title: $3.slice(1,-1), lhs_into: true, rhs_into: true }) } { yy.addEdge($1, $2[1], $5, $4[1], { title: $3.slice(1,-1), lhs_into: true, rhs_into: true }) }
| id ARROW_LEFT_INTO title ARROW_RIGHT id | id ARROW_LEFT_INTO title ARROW_RIGHT id
{ yy.addLine($1, $2[1], $5, $4[1], { title: $3.slice(1,-1), lhs_into: true }) } { yy.addEdge($1, $2[1], $5, $4[1], { title: $3.slice(1,-1), lhs_into: true }) }
| id ARROW_LEFT title ARROW_RIGHT_INTO id | id ARROW_LEFT title ARROW_RIGHT_INTO id
{ yy.addLine($1, $2[0], $5, $4[1], { title: $3.slice(1,-1), rhs_into: true }) } { yy.addEdge($1, $2[0], $5, $4[1], { title: $3.slice(1,-1), rhs_into: true }) }
| id ARROW_LEFT title ARROW_RIGHT id | id ARROW_LEFT title ARROW_RIGHT id
{ yy.addLine($1, $2[0], $5, $4[1], { title: $3.slice(1,-1) }) } { yy.addEdge($1, $2[0], $5, $4[1], { title: $3.slice(1,-1) }) }
; ;
group_statement group_statement

View File

@@ -3,78 +3,16 @@ import { createText } from '../../rendering-util/createText.js';
import { import {
ArchitectureDirectionArrow, ArchitectureDirectionArrow,
type ArchitectureDB, type ArchitectureDB,
type ArchitectureDirection,
type ArchitectureService, type ArchitectureService,
ArchitectureDirectionArrowShift, ArchitectureDirectionArrowShift,
isArchitectureDirectionX, isArchitectureDirectionX,
isArchitectureDirectionY, isArchitectureDirectionY,
} from './architectureTypes.js'; } from './architectureTypes.js';
import type { MermaidConfig } from '../../config.type.js';
import type cytoscape from 'cytoscape'; import type cytoscape from 'cytoscape';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import { getIcon } from '../../rendering-util/svgRegister.js'; import { getIcon } from '../../rendering-util/svgRegister.js';
import { getConfigField } from './architectureDb.js'; import { getConfigField } from './architectureDb.js';
declare module 'cytoscape' {
type _EdgeSingularData = {
id: string;
source: string;
sourceDir: ArchitectureDirection;
sourceArrow?: boolean;
target: string;
targetDir: ArchitectureDirection;
targetArrow?: boolean;
[key: string]: any;
};
interface EdgeSingular {
_private: {
bodyBounds: unknown;
rscratch: {
startX: number;
startY: number;
midX: number;
midY: number;
endX: number;
endY: number;
};
};
data(): _EdgeSingularData;
data<T extends keyof _EdgeSingularData>(key: T): _EdgeSingularData[T];
}
interface NodeSingular {
_private: {
bodyBounds: {
h: number;
w: number;
x1: number;
x2: number;
y1: number;
y2: number;
};
children: cytoscape.NodeSingular[];
};
data: () =>
| {
type: 'service';
id: string;
icon?: string;
label?: string;
parent?: string;
width: number;
height: number;
[key: string]: any;
}
| {
type: 'group';
id: string;
icon?: string;
label?: string;
parent?: string;
[key: string]: any;
};
}
}
export const drawEdges = function (edgesEl: D3Element, cy: cytoscape.Core) { export const drawEdges = function (edgesEl: D3Element, cy: cytoscape.Core) {
const iconSize = getConfigField('iconSize'); const iconSize = getConfigField('iconSize');
const arrowSize = iconSize / 6; const arrowSize = iconSize / 6;
@@ -161,12 +99,12 @@ export const drawGroups = function (groupsEl: D3Element, cy: cytoscape.Core) {
}); });
}; };
export const drawService = function ( export const drawServices = function (
db: ArchitectureDB, db: ArchitectureDB,
elem: D3Element, elem: D3Element,
service: ArchitectureService, services: ArchitectureService[]
conf: MermaidConfig
): number { ): number {
services.forEach((service) => {
const serviceElem = elem.append('g'); const serviceElem = elem.append('g');
const iconSize = getConfigField('iconSize'); const iconSize = getConfigField('iconSize');
@@ -198,7 +136,10 @@ export const drawService = function (
.append('path') .append('path')
.attr('class', 'node-bkg') .attr('class', 'node-bkg')
.attr('id', 'node-' + service.id) .attr('id', 'node-' + service.id)
.attr('d', `M0 ${iconSize} v${-iconSize} q0,-5 5,-5 h${iconSize} q5,0 5,5 v${iconSize} H0 Z`); .attr(
'd',
`M0 ${iconSize} v${-iconSize} q0,-5 5,-5 h${iconSize} q5,0 5,5 v${iconSize} H0 Z`
);
} }
serviceElem.attr('class', 'architecture-service'); serviceElem.attr('class', 'architecture-service');
@@ -207,5 +148,6 @@ export const drawService = function (
service.width = width; service.width = width;
service.height = height; service.height = height;
db.setElementForId(service.id, serviceElem); db.setElementForId(service.id, serviceElem);
});
return 0; return 0;
}; };