refactor(arch): refactored code to be consistent with other diagrams

This commit is contained in:
NicolasNewman 2024-04-07 17:27:01 -05:00
parent 0a14f2cffc
commit 6bd1da219a
4 changed files with 115 additions and 126 deletions

View File

@ -1,10 +1,10 @@
import type {
ArchitectureFields,
ArchitectureState,
ArchitectureDB,
ArchitectureService,
ArchitectureGroup,
ArchitectureDirection,
ArchitectureLine,
ArchitectureEdge,
ArchitectureDirectionPairMap,
ArchitectureDirectionPair,
ArchitectureSpatialMap,
@ -27,56 +27,48 @@ import {
import type { ArchitectureDiagramConfig } from '../../config.type.js';
import DEFAULT_CONFIG from '../../defaultConfig.js';
import type { D3Element } from '../../mermaidAPI.js';
import { ImperativeState } from '../../utils/imperativeState.js';
export const DEFAULT_ARCHITECTURE_CONFIG: Required<ArchitectureDiagramConfig> =
const DEFAULT_ARCHITECTURE_CONFIG: Required<ArchitectureDiagramConfig> =
DEFAULT_CONFIG.architecture;
export const DEFAULT_ARCHITECTURE_DB: ArchitectureFields = {
const state = new ImperativeState<ArchitectureState>(() => ({
services: {},
groups: [],
lines: [],
edges: [],
registeredIds: {},
config: DEFAULT_ARCHITECTURE_CONFIG,
} as const;
let services = DEFAULT_ARCHITECTURE_DB.services;
let groups = DEFAULT_ARCHITECTURE_DB.groups;
let lines = DEFAULT_ARCHITECTURE_DB.lines;
let registeredIds = DEFAULT_ARCHITECTURE_DB.registeredIds;
let datastructures = DEFAULT_ARCHITECTURE_DB.datastructures;
let elements: Record<string, D3Element> = {};
datastructures: undefined,
elements: {}
}))
const clear = (): void => {
services = structuredClone(DEFAULT_ARCHITECTURE_DB.services);
groups = structuredClone(DEFAULT_ARCHITECTURE_DB.groups);
lines = structuredClone(DEFAULT_ARCHITECTURE_DB.lines);
registeredIds = structuredClone(DEFAULT_ARCHITECTURE_DB.registeredIds);
datastructures = undefined;
elements = {};
state.reset()
commonClear();
};
const addService = function (id: string, opts: Omit<ArchitectureService, 'id' | 'edges'> = {}) {
const { icon, in: inside, title } = opts;
if (registeredIds[id] !== undefined) {
throw new Error(`The service id [${id}] is already in use by another ${registeredIds[id]}`);
if (state.records.registeredIds[id] !== undefined) {
throw new Error(`The service id [${id}] is already in use by another ${state.records.registeredIds[id]}`);
}
if (inside !== undefined) {
if (id === inside) {
throw new Error(`The service [${id}] cannot be placed within itself`);
}
if (registeredIds[inside] === undefined) {
if (state.records.registeredIds[inside] === undefined) {
throw new Error(
`The service [${id}]'s parent does not exist. Please make sure the parent is created before this service`
);
}
if (registeredIds[inside] === 'service') {
if (state.records.registeredIds[inside] === 'service') {
throw new Error(`The service [${id}]'s parent is not a group`);
}
}
registeredIds[id] = 'service';
state.records.registeredIds[id] = 'service';
services[id] = {
state.records.services[id] = {
id,
icon,
title,
@ -85,30 +77,30 @@ const addService = function (id: string, opts: Omit<ArchitectureService, 'id' |
};
};
const getServices = (): ArchitectureService[] => Object.values(services);
const getServices = (): ArchitectureService[] => Object.values(state.records.services);
const addGroup = function (id: string, opts: Omit<ArchitectureGroup, 'id'> = {}) {
const { icon, in: inside, title } = opts;
if (registeredIds[id] !== undefined) {
throw new Error(`The group id [${id}] is already in use by another ${registeredIds[id]}`);
if (state.records.registeredIds[id] !== undefined) {
throw new Error(`The group id [${id}] is already in use by another ${state.records.registeredIds[id]}`);
}
if (inside !== undefined) {
if (id === inside) {
throw new Error(`The group [${id}] cannot be placed within itself`);
}
if (registeredIds[inside] === undefined) {
if (state.records.registeredIds[inside] === undefined) {
throw new Error(
`The group [${id}]'s parent does not exist. Please make sure the parent is created before this group`
);
}
if (registeredIds[inside] === 'service') {
if (state.records.registeredIds[inside] === 'service') {
throw new Error(`The group [${id}]'s parent is not a group`);
}
}
registeredIds[id] = 'group';
state.records.registeredIds[id] = 'group';
groups.push({
state.records.groups.push({
id,
icon,
title,
@ -116,54 +108,55 @@ const addGroup = function (id: string, opts: Omit<ArchitectureGroup, 'id'> = {})
});
};
const getGroups = (): ArchitectureGroup[] => {
return groups;
return state.records.groups;
};
const addEdge = function (
lhs_id: string,
lhs_dir: ArchitectureDirection,
rhs_id: string,
rhs_dir: ArchitectureDirection,
opts: Omit<ArchitectureLine, 'lhs_id' | 'lhs_dir' | 'rhs_id' | 'rhs_dir'> = {}
lhsId: string,
lhsDir: ArchitectureDirection,
rhsId: string,
rhsDir: ArchitectureDirection,
opts: Omit<ArchitectureEdge, 'lhsId' | 'lhsDir' | 'rhsId' | 'rhsDir'> = {}
) {
const { title, lhs_into, rhs_into } = opts;
if (!isArchitectureDirection(lhs_dir)) {
const { title, lhsInto: lhsInto, rhsInto: rhsInto } = opts;
if (!isArchitectureDirection(lhsDir)) {
throw new Error(
`Invalid direction given for left hand side of line ${lhs_id}--${rhs_id}. Expected (L,R,T,B) got ${lhs_dir}`
`Invalid direction given for left hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${lhsDir}`
);
}
if (!isArchitectureDirection(rhs_dir)) {
if (!isArchitectureDirection(rhsDir)) {
throw new Error(
`Invalid direction given for right hand side of line ${lhs_id}--${rhs_id}. Expected (L,R,T,B) got ${rhs_dir}`
`Invalid direction given for right hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${rhsDir}`
);
}
if (services[lhs_id] === undefined) {
if (state.records.services[lhsId] === undefined) {
throw new Error(
`The left-hand service [${lhs_id}] does not yet exist. Please create the service before declaring an edge to it.`
`The left-hand service [${lhsId}] does not yet exist. Please create the service before declaring an edge to it.`
);
}
if (services[rhs_id] === undefined) {
if (state.records.services[rhsId] === undefined) {
throw new Error(
`The right-hand service [${rhs_id}] does not yet exist. Please create the service before declaring an edge to it.`
`The right-hand service [${rhsId}] does not yet exist. Please create the service before declaring an edge to it.`
);
}
const edge = {
lhs_id,
lhs_dir,
rhs_id,
rhs_dir,
lhsId,
lhsDir,
rhsId,
rhsDir,
title,
lhs_into,
rhs_into,
lhsInto,
rhsInto,
};
lines.push(edge);
state.records.edges.push(edge);
services[lhs_id].edges.push(lines[lines.length - 1]);
services[rhs_id].edges.push(lines[lines.length - 1]);
state.records.services[lhsId].edges.push(state.records.edges[state.records.edges.length - 1]);
state.records.services[rhsId].edges.push(state.records.edges[state.records.edges.length - 1]);
};
const getEdges = (): ArchitectureLine[] => lines;
const getEdges = (): ArchitectureEdge[] => state.records.edges;
/**
* Returns the current diagram's adjacency list & spatial map.
@ -171,24 +164,24 @@ const getEdges = (): ArchitectureLine[] => lines;
* @returns
*/
const getDataStructures = () => {
if (datastructures === undefined) {
if (state.records.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 }>(
const adjList = Object.entries(state.records.services).reduce<{ [id: string]: ArchitectureDirectionPairMap }>(
(prev, [id, service]) => {
prev[id] = service.edges.reduce<ArchitectureDirectionPairMap>((prev, edge) => {
if (edge.lhs_id === id) {
if (edge.lhsId === id) {
// source is LHS
const pair = getArchitectureDirectionPair(edge.lhs_dir, edge.rhs_dir);
const pair = getArchitectureDirectionPair(edge.lhsDir, edge.rhsDir);
if (pair) {
prev[pair] = edge.rhs_id;
prev[pair] = edge.rhsId;
}
} else {
// source is RHS
const pair = getArchitectureDirectionPair(edge.rhs_dir, edge.lhs_dir);
const pair = getArchitectureDirectionPair(edge.rhsDir, edge.lhsDir);
if (pair) {
prev[pair] = edge.lhs_id;
prev[pair] = edge.lhsId;
}
}
return prev;
@ -236,19 +229,19 @@ const getDataStructures = () => {
while (Object.keys(notVisited).length > 0) {
spatialMaps.push(BFS(Object.keys(notVisited)[0]));
}
datastructures = {
state.records.datastructures = {
adjList,
spatialMaps,
};
console.log(datastructures);
console.log(state.records.datastructures);
}
return datastructures;
return state.records.datastructures;
};
const setElementForId = (id: string, element: D3Element) => {
elements[id] = element;
state.records.elements[id] = element;
};
const getElementById = (id: string) => elements[id];
const getElementById = (id: string) => state.records.elements[id];
export const db: ArchitectureDB = {
clear,
@ -275,7 +268,7 @@ export const db: ArchitectureDB = {
* @param field
* @returns
*/
function getConfigField<T extends keyof ArchitectureDiagramConfig>(
export function getConfigField<T extends keyof ArchitectureDiagramConfig>(
field: T
): Required<ArchitectureDiagramConfig>[T] {
const arch = getConfig().architecture;
@ -285,5 +278,3 @@ function getConfigField<T extends keyof ArchitectureDiagramConfig>(
}
return DEFAULT_ARCHITECTURE_CONFIG[field];
}
export { getConfigField };

View File

@ -10,7 +10,7 @@ import {
type ArchitectureDB,
type ArchitectureDirection,
type ArchitectureGroup,
type ArchitectureLine,
type ArchitectureEdge,
type ArchitectureService,
ArchitectureDataStructures,
ArchitectureDirectionName,
@ -20,14 +20,11 @@ import {
ArchitectureSpatialMap,
EdgeSingularData,
EdgeSingular,
NodeSingular,
NodeSingularData,
nodeData,
edgeData
} from './architectureTypes.js';
import { select } from 'd3';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import type { D3Element } from '../../mermaidAPI.js';
import { drawEdges, drawGroups, drawServices } from './svgDraw.js';
import { getConfigField } from './architectureDb.js';
@ -79,34 +76,34 @@ function addGroups(groups: ArchitectureGroup[], cy: cytoscape.Core) {
});
}
function addEdges(lines: ArchitectureLine[], cy: cytoscape.Core) {
lines.forEach((line) => {
const { lhs_id, rhs_id, lhs_into, rhs_into, lhs_dir, rhs_dir } = line;
const edgeType = isArchitectureDirectionXY(line.lhs_dir, line.rhs_dir)
function addEdges(edges: ArchitectureEdge[], cy: cytoscape.Core) {
edges.forEach((parsedEdge) => {
const { lhsId, rhsId, lhsInto, rhsInto, lhsDir, rhsDir } = parsedEdge;
const edgeType = isArchitectureDirectionXY(parsedEdge.lhsDir, parsedEdge.rhsDir)
? 'segments'
: 'straight';
const edge: EdgeSingularData = {
id: `${lhs_id}-${rhs_id}`,
source: lhs_id,
sourceDir: lhs_dir,
sourceArrow: lhs_into,
id: `${lhsId}-${rhsId}`,
source: lhsId,
sourceDir: lhsDir,
sourceArrow: lhsInto,
sourceEndpoint:
lhs_dir === 'L'
lhsDir === 'L'
? '0 50%'
: lhs_dir === 'R'
: lhsDir === 'R'
? '100% 50%'
: lhs_dir === 'T'
: lhsDir === 'T'
? '50% 0'
: '50% 100%',
target: rhs_id,
targetDir: rhs_dir,
targetArrow: rhs_into,
target: rhsId,
targetDir: rhsDir,
targetArrow: rhsInto,
targetEndpoint:
rhs_dir === 'L'
rhsDir === 'L'
? '0 50%'
: rhs_dir === 'R'
: rhsDir === 'R'
? '100% 50%'
: rhs_dir === 'T'
: rhsDir === 'T'
? '50% 0'
: '50% 100%',
};
@ -207,7 +204,7 @@ function getRelativeConstraints(
function layoutArchitecture(
services: ArchitectureService[],
groups: ArchitectureGroup[],
lines: ArchitectureLine[],
edges: ArchitectureEdge[],
{ spatialMaps }: ArchitectureDataStructures
): Promise<cytoscape.Core> {
return new Promise((resolve) => {
@ -272,7 +269,7 @@ function layoutArchitecture(
addGroups(groups, cy);
addServices(services, cy);
addEdges(lines, cy);
addEdges(edges, cy);
// Use the spatial map to create alignment arrays for fcose
const alignmentConstraint = getAlignments(spatialMaps);
@ -366,9 +363,9 @@ function layoutArchitecture(
cy.startBatch();
for (let edge of Object.values(cy.edges())) {
if (edge.data?.()) {
let { x: s_x, y: s_y } = edge.source().position();
let { x: t_x, y: t_y } = edge.target().position();
if (s_x !== t_x && s_y !== t_y) {
let { x: sX, y: sY } = edge.source().position();
let { x: tX, y: tY } = edge.target().position();
if (sX !== tX && sY !== tY) {
let sEP = edge.sourceEndpoint();
let tEP = edge.targetEndpoint();
const { sourceDir } = edgeData(edge)
@ -395,14 +392,13 @@ function layoutArchitecture(
export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram) => {
const db = diagObj.db as ArchitectureDB;
const conf: MermaidConfig = getConfig();
const services = db.getServices();
const groups = db.getGroups();
const lines = db.getEdges();
const edges = db.getEdges();
const ds = db.getDataStructures();
console.log('Services: ', services);
console.log('Lines: ', lines);
console.log('Edges: ', edges);
console.log('Groups: ', groups);
const svg: SVG = selectSvgElement(id);
@ -418,7 +414,7 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram)
drawServices(db, servicesElem, services);
const cy = await layoutArchitecture(services, groups, lines, ds);
const cy = await layoutArchitecture(services, groups, edges, ds);
drawEdges(edgesElem, cy);
drawGroups(groupElem, cy);

View File

@ -141,7 +141,7 @@ export interface ArchitectureStyleOptions {
export interface ArchitectureService {
id: string;
edges: ArchitectureLine[];
edges: ArchitectureEdge[];
icon?: string;
title?: string;
in?: string;
@ -156,14 +156,14 @@ export interface ArchitectureGroup {
in?: string;
}
export interface ArchitectureLine {
lhs_id: string;
lhs_dir: ArchitectureDirection;
export interface ArchitectureEdge {
lhsId: string;
lhsDir: ArchitectureDirection;
title?: string;
rhs_id: string;
rhs_dir: ArchitectureDirection;
lhs_into?: boolean;
rhs_into?: boolean;
rhsId: string;
rhsDir: ArchitectureDirection;
lhsInto?: boolean;
rhsInto?: boolean;
}
export interface ArchitectureDB extends DiagramDB {
@ -173,13 +173,13 @@ export interface ArchitectureDB extends DiagramDB {
addGroup: (id: string, opts: Omit<ArchitectureGroup, 'id'>) => void;
getGroups: () => ArchitectureGroup[];
addEdge: (
lhs_id: string,
lhs_dir: ArchitectureDirection,
rhs_id: string,
rhs_dir: ArchitectureDirection,
opts: Omit<ArchitectureLine, 'lhs_id' | 'lhs_dir' | 'rhs_id' | 'rhs_dir'>
lhsId: string,
lhsDir: ArchitectureDirection,
rhsId: string,
rhsDir: ArchitectureDirection,
opts: Omit<ArchitectureEdge, 'lhsId' | 'lhsDir' | 'rhsId' | 'rhsDir'>
) => void;
getEdges: () => ArchitectureLine[];
getEdges: () => ArchitectureEdge[];
setElementForId: (id: string, element: D3Element) => void;
getElementById: (id: string) => D3Element;
getDataStructures: () => ArchitectureDataStructures;
@ -192,18 +192,20 @@ export type ArchitectureDataStructures = {
spatialMaps: ArchitectureSpatialMap[];
};
export interface ArchitectureFields {
export interface ArchitectureState extends Record<string, unknown> {
services: Record<string, ArchitectureService>;
groups: ArchitectureGroup[];
lines: ArchitectureLine[];
edges: ArchitectureEdge[];
registeredIds: Record<string, 'service' | 'group'>;
datastructures?: ArchitectureDataStructures;
elements: Record<string, D3Element>;
config: ArchitectureDiagramConfig;
}
/*=======================================*\
| Cytoscape Override Types |
\*=======================================*/
export type EdgeSingularData = {
id: string;
source: string;
@ -215,7 +217,7 @@ export type EdgeSingularData = {
[key: string]: any;
};
export function edgeData(edge: cytoscape.EdgeSingular) {
export const edgeData = (edge: cytoscape.EdgeSingular) => {
return edge.data() as EdgeSingularData;
}
@ -254,7 +256,7 @@ export type NodeSingularData = {
[key: string]: any;
};
export function nodeData(node: cytoscape.NodeSingular) {
export const nodeData = (node: cytoscape.NodeSingular) => {
return node.data() as NodeSingularData;
}

View File

@ -55,19 +55,19 @@ statement
line_statement
: id ARROW_LEFT_INTO ARROW_RIGHT_INTO id
{ yy.addEdge($1, $2[1], $4, $3[1], {lhs_into: true, rhs_into: true}) }
{ yy.addEdge($1, $2[1], $4, $3[1], {lhsInto: true, rhsInto: true}) }
| id ARROW_LEFT_INTO ARROW_RIGHT id
{ yy.addEdge($1, $2[1], $4, $3[1], {lhs_into: true}) }
{ yy.addEdge($1, $2[1], $4, $3[1], {lhsInto: true}) }
| id ARROW_LEFT ARROW_RIGHT_INTO id
{ yy.addEdge($1, $2[0], $4, $3[1], {rhs_into: true}) }
{ yy.addEdge($1, $2[0], $4, $3[1], {rhsInto: true}) }
| id ARROW_LEFT ARROW_RIGHT id
{ yy.addEdge($1, $2[0], $4, $3[1]) }
| id ARROW_LEFT_INTO title ARROW_RIGHT_INTO id
{ yy.addEdge($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), lhsInto: true, rhsInto: true }) }
| id ARROW_LEFT_INTO title ARROW_RIGHT id
{ yy.addEdge($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), lhsInto: true }) }
| id ARROW_LEFT title ARROW_RIGHT_INTO id
{ yy.addEdge($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), rhsInto: true }) }
| id ARROW_LEFT title ARROW_RIGHT id
{ yy.addEdge($1, $2[0], $5, $4[1], { title: $3.slice(1,-1) }) }
;