Merge from Upstream

This commit is contained in:
Ashish Jain
2024-08-27 14:21:46 +02:00
20 changed files with 587 additions and 374 deletions

View File

@@ -752,14 +752,34 @@ export const render = async (
'nodePlacement.strategy': data4Layout.config.elk.nodePlacementStrategy,
'elk.layered.mergeEdges': data4Layout.config.elk.mergeEdges,
'elk.direction': 'DOWN',
'spacing.baseValue': 30,
// 'spacing.nodeNode': 40,
// 'spacing.nodeNodeBetweenLayers': 45,
// 'spacing.edgeNode': 40,
// 'spacing.edgeNodeBetweenLayers': 30,
// 'spacing.edgeEdge': 30,
// 'spacing.edgeEdgeBetweenLayers': 40,
// 'spacing.nodeSelfLoop': 50,
'spacing.baseValue': 35,
'elk.layered.unnecessaryBendpoints': true,
'elk.layered.cycleBreaking.strategy': data4Layout.config.elk.cycleBreakingStrategy,
// 'spacing.nodeNode': 20,
// 'spacing.nodeNodeBetweenLayers': 25,
// 'spacing.edgeNode': 20,
// 'spacing.edgeNodeBetweenLayers': 10,
// 'spacing.edgeEdge': 10,
// 'spacing.edgeEdgeBetweenLayers': 20,
// 'spacing.nodeSelfLoop': 20,
// Tweaking options
// 'elk.layered.nodePlacement.favorStraightEdges': true,
// 'nodePlacement.feedbackEdges': true,
// 'elk.layered.wrapping.multiEdge.improveCuts': true,
// 'elk.layered.wrapping.multiEdge.improveWrappedEdges': true,
// 'elk.layered.wrapping.strategy': 'MULTI_EDGE',
// 'elk.layered.edgeRouting.selfLoopDistribution': 'EQUALLY',
// 'elk.layered.mergeHierarchyEdges': true,
// 'elk.layered.feedbackEdges': true,
// 'elk.layered.crossingMinimization.semiInteractive': true,
// 'elk.layered.edgeRouting.splines.sloppy.layerSpacingFactor': 1,
// 'elk.layered.edgeRouting.polyline.slopedEdgeZoneWidth': 4.0,
// 'elk.layered.wrapping.validify.strategy': 'LOOK_BACK',
// 'elk.insideSelfLoops.activate': true,
// 'elk.alg.layered.options.EdgeStraighteningStrategy': 'NONE',
// 'elk.layered.considerModelOrder.strategy': 'NODES_AND_EDGES', // NODES_AND_EDGES
// 'elk.layered.wrapping.cutting.strategy': 'ARD', // NODES_AND_EDGES
},
children: [],
edges: [],

View File

@@ -99,6 +99,16 @@ export interface MermaidConfig {
*
*/
nodePlacementStrategy?: 'SIMPLE' | 'NETWORK_SIMPLEX' | 'LINEAR_SEGMENTS' | 'BRANDES_KOEPF';
/**
* This strategy decides how to find cycles in the graph and deciding which edges need adjustment to break loops.
*
*/
cycleBreakingStrategy?:
| 'GREEDY'
| 'DEPTH_FIRST'
| 'INTERACTIVE'
| 'MODEL_ORDER'
| 'GREEDY_MODEL_ORDER';
};
darkMode?: boolean;
htmlLabels?: boolean;

View File

@@ -28,7 +28,7 @@ import { getSubGraphTitleMargins } from '../../../utils/subGraphTitleMargins.js'
import { getConfig } from '../../../diagram-api/diagramAPI.js';
const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, siteConfig) => {
log.info('Graph in recursive render: IPI', id, graphlibJson.write(graph), parentCluster);
log.warn('Graph in recursive render:XAX', graphlibJson.write(graph), parentCluster);
const dir = graph.graph().rankdir;
log.trace('Dir in recursive render - dir:', dir);
@@ -124,7 +124,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
clusterDb.set(node.id, { id: findNonClusterChild(node.id, graph), node });
// insertCluster(clusters, graph.node(v));
} else {
log.trace('Node - the non recursive path XAX', v, node.id, node);
log.warn('Node - the non recursive path XAX', v, nodes, graph.node(v), dir);
await insertNode(nodes, graph.node(v), dir);
}
}
@@ -305,12 +305,64 @@ export const render = async (data4Layout, svg) => {
log.debug('Edges:', data4Layout.edges);
data4Layout.edges.forEach((edge) => {
graph.setEdge(edge.start, edge.end, { ...edge }, edge.id);
// Handle self-loops
if (edge.start === edge.end) {
const nodeId = edge.start;
const specialId1 = nodeId + '---' + nodeId + '---1';
const specialId2 = nodeId + '---' + nodeId + '---2';
const node = graph.node(nodeId);
graph.setNode(specialId1, {
domId: specialId1,
id: specialId1,
parentId: node.parentId,
labelStyle: '',
label: '',
padding: 0,
shape: 'labelRect',
// shape: 'rect',
style: '',
width: 10,
height: 10,
});
graph.setParent(specialId1, node.parentId);
graph.setNode(specialId2, {
domId: specialId2,
id: specialId2,
parentId: node.parentId,
labelStyle: '',
padding: 0,
// shape: 'rect',
shape: 'labelRect',
label: '',
style: '',
width: 10,
height: 10,
});
graph.setParent(specialId2, node.parentId);
const edge1 = structuredClone(edge);
const edgeMid = structuredClone(edge);
const edge2 = structuredClone(edge);
edge1.label = '';
edge1.arrowTypeEnd = 'none';
edge1.id = nodeId + '-cyclic-special-1';
edgeMid.arrowTypeEnd = 'none';
edgeMid.id = nodeId + '-cyclic-special-mid';
edge2.label = '';
edge1.fromCluster = nodeId;
edge2.toCluster = nodeId;
edge2.id = nodeId + '-cyclic-special-2';
graph.setEdge(nodeId, specialId1, edge1, nodeId + '-cyclic-special-0');
graph.setEdge(specialId1, specialId2, edgeMid, nodeId + '-cyclic-special-1');
graph.setEdge(specialId2, nodeId, edge2, nodeId + '-cyc<lic-special-2');
} else {
graph.setEdge(edge.start, edge.end, { ...edge }, edge.id);
}
});
log.warn('Graph at first:', JSON.stringify(graphlibJson.write(graph)));
adjustClustersAndEdges(graph);
log.warn('Graph after:', JSON.stringify(graphlibJson.write(graph)));
log.warn('Graph after XAX:', JSON.stringify(graphlibJson.write(graph)));
const siteConfig = getConfig();
await recursiveRender(
element,

View File

@@ -267,51 +267,7 @@ export const adjustClustersAndEdges = (graph, depth) => {
' --- ',
clusterDb.get(e.w)
);
if (clusterDb.get(e.v) && clusterDb.get(e.w) && clusterDb.get(e.v) === clusterDb.get(e.w)) {
log.warn('Fixing and trying link to self - removing XXX', e.v, e.w, e.name);
log.warn('Fixing and trying - removing XXX', e.v, e.w, e.name);
v = getAnchorId(e.v);
w = getAnchorId(e.w);
graph.removeEdge(e.v, e.w, e.name);
const specialId1 = e.w + '---' + e.v + '---1';
const specialId2 = e.w + '---' + e.v + '---2';
graph.setNode(specialId1, {
domId: specialId1,
id: specialId1,
labelStyle: '',
label: '',
padding: 0,
shape: 'labelRect',
style: '',
width: 10,
height: 10,
});
graph.setNode(specialId2, {
domId: specialId2,
id: specialId2,
labelStyle: '',
padding: 0,
shape: 'labelRect',
style: '',
width: 10,
height: 10,
});
const edge1 = structuredClone(edge);
const edgeMid = structuredClone(edge);
const edge2 = structuredClone(edge);
edge1.label = '';
edge1.arrowTypeEnd = 'none';
edge1.id = e.name + '-cyclic-special-1';
edgeMid.arrowTypeEnd = 'none';
edgeMid.id = e.name + '-cyclic-special-mid';
edge2.label = '';
edge1.fromCluster = e.v;
edge2.toCluster = e.v;
edge2.id = e.name + '-cyclic-special-2';
graph.setEdge(v, specialId1, edge1, e.name + '-cyclic-special-0');
graph.setEdge(specialId1, specialId2, edgeMid, e.name + '-cyclic-special-1');
graph.setEdge(specialId2, w, edge2, e.name + '-cyclic-special-2');
} else if (clusterDb.get(e.v) || clusterDb.get(e.w)) {
if (clusterDb.get(e.v) || clusterDb.get(e.w)) {
log.warn('Fixing and trying - removing XXX', e.v, e.w, e.name);
v = getAnchorId(e.v);
w = getAnchorId(e.w);
@@ -334,13 +290,6 @@ export const adjustClustersAndEdges = (graph, depth) => {
extractor(graph, 0);
log.trace(clusterDb);
// Remove references to extracted cluster
// graph.edges().forEach((edge) => {
// if (isDescendant(edge.v, clusterId) || isDescendant(edge.w, clusterId)) {
// graph.removeEdge(edge);
// }
// });
};
export const extractor = (graph, depth) => {
@@ -441,7 +390,7 @@ export const extractor = (graph, depth) => {
for (const node of nodes) {
const data = graph.node(node);
log.warn(' Now next level', node, data);
if (data.clusterNode) {
if (data?.clusterNode) {
extractor(data.graph, depth + 1);
}
}

View File

@@ -3,20 +3,17 @@ import type { Node } from '$root/rendering-util/types.d.ts';
import type { SVG } from '$root/diagram-api/types.js';
// @ts-ignore TODO: Fix rough typings
import rough from 'roughjs';
import { solidStateFill, styles2String } from './handDrawnShapeStyles.js';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { createPathFromPoints, getNodeClasses, labelHelper } from './util.js';
export const choice = (parent: SVG, node: Node) => {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { themeVariables } = getConfig();
const { lineColor } = themeVariables;
const shapeSvg = parent
.insert('g')
.attr('class', 'node default')
.attr('id', node.domId || node.id);
export const choice = async (parent: SVG, node: Node) => {
const { nodeStyles } = styles2String(node);
node.label = '';
const { shapeSvg } = await labelHelper(parent, node, getNodeClasses(node));
const { cssStyles } = node;
const s = Math.max(28, node.width ?? 0);
const s = 28;
const points = [
{ x: 0, y: s / 2 },
{ x: s / 2, y: 0 },
@@ -24,40 +21,34 @@ export const choice = (parent: SVG, node: Node) => {
{ x: -s / 2, y: 0 },
];
let choice;
if (node.look === 'handDrawn') {
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const pointArr = points.map(function (d) {
return [d.x, d.y];
});
const roughNode = rc.polygon(pointArr, solidStateFill(lineColor));
choice = shapeSvg.insert(() => roughNode);
} else {
choice = shapeSvg.insert('polygon', ':first-child').attr(
'points',
points
.map(function (d) {
return d.x + ',' + d.y;
})
.join(' ')
);
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
if (node.look !== 'handDrawn') {
options.roughness = 0;
options.fillStyle = 'solid';
}
// center the circle around its coordinate
choice
.attr('class', 'state-start')
// @ts-ignore TODO: Fix rough typings
.attr('r', 7)
.attr('width', 28)
.attr('height', 28)
.attr('style', nodeStyles);
const choicePath = createPathFromPoints(points);
const roughNode = rc.path(choicePath, options);
const choiceShape = shapeSvg.insert(() => roughNode, ':first-child');
choiceShape.attr('class', 'basic label-container');
if (cssStyles && node.look !== 'handDrawn') {
choiceShape.selectAll('path').attr('style', cssStyles);
}
if (nodeStyles && node.look !== 'handDrawn') {
choiceShape.selectAll('path').attr('style', nodeStyles);
}
node.width = 28;
node.height = 28;
node.intersect = function (point) {
return intersect.circle(node, 14, point);
return intersect.polygon(node, points, point);
};
return shapeSvg;

View File

@@ -109,7 +109,10 @@ export const cylinder = async (parent: SVGAElement, node: Node) => {
updateNodeBounds(node, cylinder);
label.attr('transform', `translate(${-bbox.width / 2}, ${h / 2 - bbox.height})`);
label.attr(
'transform',
`translate(${-bbox.width / 2 - (bbox.x - (bbox.left ?? 0))}, ${h / 2 - bbox.height - (bbox.y - (bbox.top ?? 0))})`
);
node.intersect = function (point) {
const pos = intersect.rect(node, point);

View File

@@ -1,62 +1,50 @@
import { updateNodeBounds } from './util.js';
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import type { SVG } from '$root/diagram-api/types.js';
import rough from 'roughjs';
import { solidStateFill } from './handDrawnShapeStyles.js';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
export const forkJoin = (parent: SVG, node: Node, dir: string) => {
const { themeVariables } = getConfig();
const { lineColor } = themeVariables;
const shapeSvg = parent
.insert('g')
.attr('class', 'node default')
.attr('id', node.domId || node.id);
export const forkJoin = async (parent: SVG, node: Node, dir: string) => {
const { nodeStyles } = styles2String(node);
node.label = '';
const { shapeSvg } = await labelHelper(parent, node, getNodeClasses(node));
let width = 70;
let height = 10;
const { cssStyles } = node;
let width = Math.max(70, node?.width ?? 0);
let height = Math.max(10, node?.height ?? 0);
if (dir === 'LR') {
width = 10;
height = 70;
width = Math.max(10, node?.width ?? 0);
height = Math.max(70, node?.height ?? 0);
}
const x = (-1 * width) / 2;
const y = (-1 * height) / 2;
let shape;
if (node.look === 'handDrawn') {
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const roughNode = rc.rectangle(x, y, width, height, solidStateFill(lineColor));
shape = shapeSvg.insert(() => roughNode);
} else {
shape = shapeSvg
.append('rect')
.attr('x', x)
.attr('y', y)
.attr('width', width)
.attr('height', height)
.attr('class', 'fork-join');
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
if (node.look !== 'handDrawn') {
options.roughness = 0;
options.fillStyle = 'solid';
}
const roughNode = rc.rectangle(x, y, width, height, options);
const shape = shapeSvg.insert(() => roughNode, ':first-child');
if (cssStyles && node.look !== 'handDrawn') {
shape.selectAll('path').attr('style', cssStyles);
}
if (nodeStyles && node.look !== 'handDrawn') {
shape.selectAll('path').attr('style', nodeStyles);
}
updateNodeBounds(node, shape);
let nodeHeight = 0;
let nodeWidth = 0;
let nodePadding = 10;
if (node.height) {
nodeHeight = node.height;
}
if (node.width) {
nodeWidth = node.width;
}
if (node.padding) {
nodePadding = node.padding;
}
node.height = nodeHeight + nodePadding / 2;
node.width = nodeWidth + nodePadding / 2;
node.intersect = function (point) {
return intersect.rect(node, point);
};

View File

@@ -1,4 +1,4 @@
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import {
@@ -8,34 +8,37 @@ import {
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
export const createInvertedTrapezoidPathD = (
x: number,
y: number,
width: number,
height: number
): string => {
return [
`M${x + height / 6},${y}`,
`L${x + width - height / 6},${y}`,
`L${x + width + (2 * height) / 6},${y - height}`,
`L${x - (2 * height) / 6},${y - height}`,
'Z',
].join(' ');
};
// export const createInvertedTrapezoidPathD = (
// x: number,
// y: number,
// width: number,
// height: number
// ): string => {
// return [
// `M${x + height / 6},${y}`,
// `L${x + width - height / 6},${y}`,
// `L${x + width + (2 * height) / 6},${y - height}`,
// `L${x - (2 * height) / 6},${y - height}`,
// 'Z',
// ].join(' ');
// };
export const inv_trapezoid = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
const labelPaddingX = node.look === 'neo' ? node.padding * 3 : node.padding;
const labelPaddingY = node.look === 'neo' ? node.padding * 1.5 : node.padding;
const w = bbox.width + labelPaddingY;
const h = bbox.height + labelPaddingX;
const nodePadding = node.padding ?? 0;
const labelPaddingX = node.look === 'neo' ? nodePadding * 3 : nodePadding * 2;
const labelPaddingY = node.look === 'neo' ? nodePadding * 1.5 : nodePadding * 2;
const w = Math.max(bbox.width + labelPaddingY, node?.width ?? 0);
const h = Math.max(bbox.height + labelPaddingX, node?.height ?? 0);
const points = [
{ x: h / 6, y: 0 },
{ x: w - h / 6, y: 0 },
{ x: w + (2 * h) / 6, y: -h },
{ x: (-2 * h) / 6, y: -h },
{ x: 0, y: 0 },
{ x: w, y: 0 },
{ x: w + (3 * h) / 6, y: -h },
{ x: (-3 * h) / 6, y: -h },
];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
@@ -45,7 +48,8 @@ export const inv_trapezoid = async (parent: SVGAElement, node: Node): Promise<SV
// @ts-ignore - rough is not typed
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createInvertedTrapezoidPathD(0, 0, w, h);
const pathData = createPathFromPoints(points);
// const pathData = createInvertedTrapezoidPathD(0, 0, w, h);
const roughNode = rc.path(pathData, options);
polygon = shapeSvg

View File

@@ -1,4 +1,4 @@
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import {
@@ -8,34 +8,19 @@ import {
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
export const createLeanLeftPathD = (
x: number,
y: number,
width: number,
height: number
): string => {
return [
`M${x + (2 * height) / 6},${y}`,
`L${x + width + height / 6},${y}`,
`L${x + width - (2 * height) / 6},${y - height}`,
`L${x - height / 6},${y - height}`,
'Z',
].join(' ');
};
export const lean_left = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
const labelPaddingX = node.look === 'neo' ? node.padding * 3 : node.padding;
const labelPaddingY = node.look === 'neo' ? node.padding * 1.5 : node.padding;
const w = bbox.width + labelPaddingY;
const h = bbox.height + labelPaddingX;
const w = Math.max(bbox.width + (labelPaddingY ?? 0), node?.width ?? 0);
const h = Math.max(bbox.height + (labelPaddingX ?? 0), node?.height ?? 0);
const points = [
{ x: (2 * h) / 6, y: 0 },
{ x: w + h / 6, y: 0 },
{ x: w - (2 * h) / 6, y: -h },
{ x: -h / 6, y: -h },
{ x: 0, y: 0 },
{ x: w + (3 * h) / 6, y: 0 },
{ x: w, y: -h },
{ x: -(3 * h) / 6, y: -h },
];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
@@ -45,7 +30,8 @@ export const lean_left = async (parent: SVGAElement, node: Node): Promise<SVGAEl
// @ts-ignore - rough is not typed
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createLeanLeftPathD(0, 0, w, h);
const pathData = createPathFromPoints(points);
// const pathData = createLeanLeftPathD(0, 0, w, h);
const roughNode = rc.path(pathData, options);
polygon = shapeSvg

View File

@@ -1,4 +1,4 @@
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import {
@@ -8,21 +8,6 @@ import {
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
export const createLeanRightPathD = (
x: number,
y: number,
width: number,
height: number
): string => {
return [
`M${x - (2 * height) / 6},${y}`,
`L${x + width - height / 6},${y}`,
`L${x + width + (2 * height) / 6},${y - height}`,
`L${x + height / 6},${y - height}`,
'Z',
].join(' ');
};
export const lean_right = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
@@ -30,13 +15,14 @@ export const lean_right = async (parent: SVGAElement, node: Node): Promise<SVGAE
const labelPaddingX = node.look === 'neo' ? node.padding * 3 : node.padding;
const labelPaddingY = node.look === 'neo' ? node.padding * 1.5 : node.padding;
const w = bbox.width + labelPaddingY;
const h = bbox.height + labelPaddingX;
const w = Math.max(bbox.width + (labelPaddingY ?? 0), node?.width ?? 0);
const h = Math.max(bbox.height + (labelPaddingX ?? 0), node?.height ?? 0);
const points = [
{ x: (-2 * h) / 6, y: 0 },
{ x: w - h / 6, y: 0 },
{ x: w + (2 * h) / 6, y: -h },
{ x: h / 6, y: -h },
{ x: (-3 * h) / 6, y: 0 },
{ x: w, y: 0 },
{ x: w + (3 * h) / 6, y: -h },
{ x: 0, y: -h },
];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
@@ -46,7 +32,7 @@ export const lean_right = async (parent: SVGAElement, node: Node): Promise<SVGAE
// @ts-ignore - rough is not typed
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createLeanRightPathD(0, 0, w, h);
const pathData = createPathFromPoints(points);
const roughNode = rc.path(pathData, options);
polygon = shapeSvg

View File

@@ -1,52 +1,46 @@
import { log } from '$root/logger.js';
import { labelHelper, updateNodeBounds } from './util.js';
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
import intersect from '../intersect/index.js';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import rough from 'roughjs';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
export const note = async (parent: SVGAElement, node: Node) => {
const { themeVariables, handDrawnSeed } = getConfig();
const { noteBorderColor, noteBkgColor } = themeVariables;
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
const totalWidth = Math.max(bbox.width + (node.padding ?? 0) * 2, node?.width ?? 0);
const totalHeight = Math.max(bbox.height + (node.padding ?? 0) * 2, node?.height ?? 0);
const x = -totalWidth / 2;
const y = -totalHeight / 2;
const { cssStyles } = node;
const useHtmlLabels = node.useHtmlLabels;
if (!useHtmlLabels) {
node.centerLabel = true;
}
const { shapeSvg, bbox } = await labelHelper(parent, node, 'node ' + node.cssClasses);
log.info('Classes = ', node.cssClasses);
const { cssStyles } = node;
let rect;
const totalWidth = bbox.width + node.padding;
const totalHeight = bbox.height + node.padding;
const x = -totalWidth / 2;
const y = -totalHeight / 2;
// add the rect
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
if (node.look === 'handDrawn') {
// add the rect
// @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg);
const roughNode = rc.rectangle(x, y, totalWidth, totalHeight, {
roughness: 0.7,
fill: noteBkgColor,
fillWeight: 3,
seed: handDrawnSeed,
// fillStyle: 'solid', // solid fill'
stroke: noteBorderColor,
});
if (node.look !== 'handDrawn') {
options.roughness = 0;
options.fillStyle = 'solid';
}
rect = shapeSvg.insert(() => roughNode, ':first-child');
rect.attr('class', 'basic label-container').attr('style', cssStyles);
} else {
rect = shapeSvg.insert('rect', ':first-child');
rect
.attr('rx', node.rx)
.attr('ry', node.ry)
.attr('x', x)
.attr('y', y)
.attr('width', totalWidth)
.attr('height', totalHeight);
const noteShapeNode = rc.rectangle(x, y, totalWidth, totalHeight, options);
const rect = shapeSvg.insert(() => noteShapeNode, ':first-child');
rect.attr('class', 'basic label-container');
if (cssStyles && node.look !== 'handDrawn') {
rect.selectAll('path').attr('style', cssStyles);
}
if (nodeStyles && node.look !== 'handDrawn') {
rect.selectAll('path').attr('style', nodeStyles);
}
updateNodeBounds(node, rect);

View File

@@ -1,4 +1,4 @@
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import {
@@ -6,18 +6,6 @@ import {
userNodeOverrides,
} from '$root/rendering-util/rendering-elements/shapes/handDrawnShapeStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
export const createPolygonPathD = (x: number, y: number, width: number, height: number): string => {
return [
`M${x - height / 2},${y}`,
`L${x + width},${y}`,
`L${x + width},${y - height}`,
`L${x - height / 2},${y - height}`,
`L${x},${y - height / 2}`,
'Z',
].join(' ');
};
export const rect_left_inv_arrow = async (
parent: SVGAElement,
@@ -28,42 +16,53 @@ export const rect_left_inv_arrow = async (
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
const labelPaddingX = node.look === 'neo' ? node.padding * 3 : node.padding;
const labelPaddingY = node.look === 'neo' ? node.padding * 1.5 : node.padding;
const w = bbox.width + labelPaddingY;
const h = bbox.height + labelPaddingX;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
const w = Math.max(bbox.width + (labelPaddingY ?? 0), node?.width ?? 0);
const h = Math.max(bbox.height + (labelPaddingX ?? 0), node?.height ?? 0);
const x = -w / 2;
const y = -h / 2;
const notch = y / 2;
const points = [
{ x: -h / 2, y: 0 },
{ x: w, y: 0 },
{ x: w, y: -h },
{ x: -h / 2, y: -h },
{ x: 0, y: -h / 2 },
{ x: x + notch, y },
{ x: x, y: 0 },
{ x: x + notch, y: -y },
{ x: -x, y: -y },
{ x: -x, y },
];
let polygon;
const { cssStyles } = node;
// @ts-ignore - rough is not typed
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
if (node.look === 'handDrawn') {
// @ts-ignore - rough is not typed
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createPolygonPathD(0, 0, w, h);
const roughNode = rc.path(pathData, options);
polygon = shapeSvg
.insert(() => roughNode, ':first-child')
.attr('transform', `translate(${-w / 2}, ${h / 2})`);
if (cssStyles) {
polygon.attr('style', cssStyles);
}
} else {
polygon = insertPolygonShape(shapeSvg, w, h, points);
if (node.look !== 'handDrawn') {
options.roughness = 0;
options.fillStyle = 'solid';
}
if (nodeStyles) {
polygon.attr('style', nodeStyles);
}
node.width = w + h;
node.height = h;
const pathData = createPathFromPoints(points);
const roughNode = rc.path(pathData, options);
const polygon = shapeSvg.insert(() => roughNode, ':first-child');
polygon.attr('class', 'basic label-container');
if (cssStyles && node.look !== 'handDrawn') {
polygon.selectAll('path').attr('style', cssStyles);
}
if (nodeStyles && node.look !== 'handDrawn') {
polygon.selectAll('path').attr('style', nodeStyles);
}
polygon.attr('transform', `translate(${-notch / 2},0)`);
label.attr(
'transform',
`translate(${-w / 2 + -notch / 2 + (node.padding ?? 0) / 2 - (bbox.x - (bbox.left ?? 0))},${-h / 2 + (node.padding ?? 0) / 2 - (bbox.y - (bbox.top ?? 0))})`
);
updateNodeBounds(node, polygon);
node.intersect = function (point) {

View File

@@ -10,10 +10,10 @@ import rough from 'roughjs';
export const shadedProcess = async (parent: SVGAElement, node: Node) => {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
const halfPadding = (node?.padding || 0) / 2;
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
const halfPadding = node?.padding ?? 0;
const w = Math.max(bbox.width + (node.padding ?? 0) * 2, node?.width ?? 0);
const h = Math.max(bbox.height + (node.padding ?? 0) * 2, node?.height ?? 0);
const x = -bbox.width / 2 - halfPadding;
const y = -bbox.height / 2 - halfPadding;
@@ -43,6 +43,11 @@ export const shadedProcess = async (parent: SVGAElement, node: Node) => {
rect.selectAll('path').attr('style', nodeStyles);
}
label.attr(
'transform',
`translate(${-w / 2 + 4 + (node.padding ?? 0) - (bbox.x - (bbox.left ?? 0))},${-h / 2 + (node.padding ?? 0) - (bbox.y - (bbox.top ?? 0))})`
);
updateNodeBounds(node, rect);
node.intersect = function (point) {

View File

@@ -1,4 +1,4 @@
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import {
@@ -8,20 +8,20 @@ import {
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
export const createTrapezoidPathD = (
x: number,
y: number,
width: number,
height: number
): string => {
return [
`M${x - (2 * height) / 6},${y}`,
`L${x + width + (2 * height) / 6},${y}`,
`L${x + width - height / 6},${y - height}`,
`L${x + height / 6},${y - height}`,
'Z',
].join(' ');
};
// export const createTrapezoidPathD = (
// x: number,
// y: number,
// width: number,
// height: number
// ): string => {
// return [
// `M${x - (2 * height) / 6},${y}`,
// `L${x + width + (2 * height) / 6},${y}`,
// `L${x + width - height / 6},${y - height}`,
// `L${x + height / 6},${y - height}`,
// 'Z',
// ].join(' ');
// };
export const trapezoid = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
const { labelStyles, nodeStyles } = styles2String(node);
@@ -32,10 +32,10 @@ export const trapezoid = async (parent: SVGAElement, node: Node): Promise<SVGAEl
const w = bbox.width + labelPaddingY;
const h = bbox.height + labelPaddingX;
const points = [
{ x: (-2 * h) / 6, y: 0 },
{ x: w + (2 * h) / 6, y: 0 },
{ x: w - h / 6, y: -h },
{ x: h / 6, y: -h },
{ x: (-3 * h) / 6, y: 0 },
{ x: w + (3 * h) / 6, y: 0 },
{ x: w, y: -h },
{ x: 0, y: -h },
];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
@@ -45,7 +45,7 @@ export const trapezoid = async (parent: SVGAElement, node: Node): Promise<SVGAEl
// @ts-ignore - rough is not typed
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createTrapezoidPathD(0, 0, w, h);
const pathData = createPathFromPoints(points);
const roughNode = rc.path(pathData, options);
polygon = shapeSvg

View File

@@ -123,6 +123,17 @@ properties:
- LINEAR_SEGMENTS
- BRANDES_KOEPF
default: BRANDES_KOEPF
cycleBreakingStrategy:
description: |
This strategy decides how to find cycles in the graph and deciding which edges need adjustment to break loops.
type: string
enum:
- GREEDY
- DEPTH_FIRST
- INTERACTIVE
- MODEL_ORDER
- GREEDY_MODEL_ORDER
default: GREEDY_MODEL_ORDER
darkMode:
type: boolean
default: false