mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-19 07:19:41 +02:00
refactor(arch): reorganized code and added more documentation
This commit is contained in:
@@ -119,82 +119,7 @@ const getGroups = (): ArchitectureGroup[] => {
|
||||
return groups;
|
||||
};
|
||||
|
||||
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 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 (
|
||||
const addEdge = function (
|
||||
lhs_id: string,
|
||||
lhs_dir: ArchitectureDirection,
|
||||
rhs_id: string,
|
||||
@@ -238,7 +163,87 @@ const addLine = function (
|
||||
services[lhs_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) => {
|
||||
elements[id] = element;
|
||||
@@ -258,13 +263,18 @@ export const db: ArchitectureDB = {
|
||||
getServices,
|
||||
addGroup,
|
||||
getGroups,
|
||||
addLine,
|
||||
getLines,
|
||||
addEdge: addEdge,
|
||||
getEdges: getEdges,
|
||||
setElementForId,
|
||||
getElementById,
|
||||
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>(
|
||||
field: T
|
||||
): Required<ArchitectureDiagramConfig>[T] {
|
||||
|
@@ -17,11 +17,12 @@ import {
|
||||
getOppositeArchitectureDirection,
|
||||
isArchitectureDirectionXY,
|
||||
isArchitectureDirectionY,
|
||||
ArchitectureSpatialMap,
|
||||
} from './architectureTypes.js';
|
||||
import { select } from 'd3';
|
||||
import { setupGraphViewbox } from '../../setupGraphViewbox.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';
|
||||
|
||||
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) {
|
||||
cy.nodes().map((node, id) => {
|
||||
const data = node.data();
|
||||
@@ -65,7 +57,6 @@ function positionServices(db: ArchitectureDB, cy: cytoscape.Core) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function addGroups(groups: ArchitectureGroup[], cy: cytoscape.Core) {
|
||||
groups.forEach((group) => {
|
||||
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(
|
||||
services: ArchitectureService[],
|
||||
groups: ArchitectureGroup[],
|
||||
@@ -192,91 +269,17 @@ function layoutArchitecture(
|
||||
addEdges(lines, cy);
|
||||
|
||||
// Use the spatial map to create alignment arrays for fcose
|
||||
const [horizontalAlignments, verticalAlignments] = (() => {
|
||||
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[][]]
|
||||
);
|
||||
})();
|
||||
const alignmentConstraint = getAlignments(spatialMaps);
|
||||
|
||||
// Create the relative constraints for fcose by using an inverse of the spatial map and performing BFS on it
|
||||
const relativeConstraints = (() => {
|
||||
const _relativeConstraints: fcose.FcoseRelativePlacementConstraint[] = [];
|
||||
const posToStr = (pos: number[]) => `${pos[0]},${pos[1]}`;
|
||||
const strToPos = (pos: string) => pos.split(',').map((p) => parseInt(p));
|
||||
const relativePlacementConstraint = getRelativeConstraints(spatialMaps);
|
||||
|
||||
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(horizontalAlignments);
|
||||
console.log(alignmentConstraint.horizontal);
|
||||
console.log(`Vertical Alignments:`);
|
||||
console.log(verticalAlignments);
|
||||
console.log(alignmentConstraint.vertical);
|
||||
console.log(`Relative Alignments:`);
|
||||
console.log(relativeConstraints);
|
||||
console.log(relativePlacementConstraint);
|
||||
|
||||
const layout = cy.layout({
|
||||
name: 'fcose',
|
||||
@@ -301,13 +304,11 @@ function layoutArchitecture(
|
||||
const elasticity = parentA === parentB ? 0.45 : 0.001;
|
||||
return elasticity;
|
||||
},
|
||||
alignmentConstraint: {
|
||||
horizontal: horizontalAlignments,
|
||||
vertical: verticalAlignments,
|
||||
},
|
||||
relativePlacementConstraint: relativeConstraints,
|
||||
alignmentConstraint,
|
||||
relativePlacementConstraint,
|
||||
} 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) => {
|
||||
function getSegmentWeights(
|
||||
source: Position,
|
||||
@@ -392,7 +393,7 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram)
|
||||
|
||||
const services = db.getServices();
|
||||
const groups = db.getGroups();
|
||||
const lines = db.getLines();
|
||||
const lines = db.getEdges();
|
||||
const ds = db.getDataStructures();
|
||||
console.log('Services: ', services);
|
||||
console.log('Lines: ', lines);
|
||||
@@ -409,7 +410,7 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram)
|
||||
const groupElem = svg.append('g');
|
||||
groupElem.attr('class', 'architecture-groups');
|
||||
|
||||
drawServices(db, servicesElem, services, conf);
|
||||
drawServices(db, servicesElem, services);
|
||||
|
||||
const cy = await layoutArchitecture(services, groups, lines, ds);
|
||||
// console.log(cy.nodes().map((node) => ({ a: node.data() })));
|
||||
|
@@ -2,6 +2,10 @@ import type { DiagramDB } from '../../diagram-api/types.js';
|
||||
import type { ArchitectureDiagramConfig } from '../../config.type.js';
|
||||
import type { D3Element } from '../../mermaidAPI.js';
|
||||
|
||||
/*=======================================*\
|
||||
| Architecture Diagram Types |
|
||||
\*=======================================*/
|
||||
|
||||
export type ArchitectureDirection = 'L' | 'R' | 'T' | 'B';
|
||||
export type ArchitectureDirectionX = Extract<ArchitectureDirection, 'L' | 'R'>;
|
||||
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 ArchitectureDirectionPair = Exclude<
|
||||
InvalidArchitectureDirectionPair,
|
||||
'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 (
|
||||
x: InvalidArchitectureDirectionPair
|
||||
): x is ArchitectureDirectionPair {
|
||||
@@ -98,6 +107,12 @@ export const getArchitectureDirectionPair = function (
|
||||
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 (
|
||||
[x, y]: number[],
|
||||
pair: ArchitectureDirectionPair
|
||||
@@ -156,14 +171,14 @@ export interface ArchitectureDB extends DiagramDB {
|
||||
getServices: () => ArchitectureService[];
|
||||
addGroup: (id: string, opts: Omit<ArchitectureGroup, 'id'>) => void;
|
||||
getGroups: () => ArchitectureGroup[];
|
||||
addLine: (
|
||||
addEdge: (
|
||||
lhs_id: string,
|
||||
lhs_dir: ArchitectureDirection,
|
||||
rhs_id: string,
|
||||
rhs_dir: ArchitectureDirection,
|
||||
opts: Omit<ArchitectureLine, 'lhs_id' | 'lhs_dir' | 'rhs_id' | 'rhs_dir'>
|
||||
) => void;
|
||||
getLines: () => ArchitectureLine[];
|
||||
getEdges: () => ArchitectureLine[];
|
||||
setElementForId: (id: string, element: D3Element) => void;
|
||||
getElementById: (id: string) => D3Element;
|
||||
getDataStructures: () => ArchitectureDataStructures;
|
||||
@@ -184,3 +199,67 @@ export interface ArchitectureFields {
|
||||
datastructures?: ArchitectureDataStructures;
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -55,21 +55,21 @@ statement
|
||||
|
||||
line_statement
|
||||
: 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
|
||||
{ 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
|
||||
{ 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
|
||||
{ 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
|
||||
{ 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
|
||||
{ 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
|
||||
{ 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
|
||||
{ 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
|
||||
|
@@ -3,78 +3,16 @@ import { createText } from '../../rendering-util/createText.js';
|
||||
import {
|
||||
ArchitectureDirectionArrow,
|
||||
type ArchitectureDB,
|
||||
type ArchitectureDirection,
|
||||
type ArchitectureService,
|
||||
ArchitectureDirectionArrowShift,
|
||||
isArchitectureDirectionX,
|
||||
isArchitectureDirectionY,
|
||||
} from './architectureTypes.js';
|
||||
import type { MermaidConfig } from '../../config.type.js';
|
||||
import type cytoscape from 'cytoscape';
|
||||
import { log } from '../../logger.js';
|
||||
import { getIcon } from '../../rendering-util/svgRegister.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) {
|
||||
const iconSize = getConfigField('iconSize');
|
||||
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,
|
||||
elem: D3Element,
|
||||
service: ArchitectureService,
|
||||
conf: MermaidConfig
|
||||
services: ArchitectureService[]
|
||||
): number {
|
||||
services.forEach((service) => {
|
||||
const serviceElem = elem.append('g');
|
||||
const iconSize = getConfigField('iconSize');
|
||||
|
||||
@@ -198,7 +136,10 @@ export const drawService = function (
|
||||
.append('path')
|
||||
.attr('class', 'node-bkg')
|
||||
.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');
|
||||
@@ -207,5 +148,6 @@ export const drawService = function (
|
||||
service.width = width;
|
||||
service.height = height;
|
||||
db.setElementForId(service.id, serviceElem);
|
||||
});
|
||||
return 0;
|
||||
};
|
||||
|
Reference in New Issue
Block a user