Merge commit '16a5fc05d65165f02966eb7f33154f136394a534' into merge-refactor/improving-rendering-shape-types

See https://github.com/mermaid-js/mermaid/pull/5974

There were a lot of merge conflicts!

Conflicts:
	packages/mermaid/src/rendering-util/rendering-elements/shapes/bowTieRect.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/card.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/crossedCircle.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/cylinder.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/drawRect.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/filledCircle.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/flippedTriangle.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/hourglass.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/iconRounded.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/iconSquare.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/imageSquare.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/lightningBolt.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/linedCylinder.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/roundedRect.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/shadedProcess.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/stateStart.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/subroutine.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/taggedRect.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/taggedWaveEdgedRectangle.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/tiltedCylinder.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/triangle.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/util.ts
	packages/mermaid/src/rendering-util/rendering-elements/shapes/windowPane.ts
This commit is contained in:
Alois Klink
2024-10-18 02:49:26 +09:00
68 changed files with 598 additions and 337 deletions

View File

@@ -10,6 +10,14 @@ import { curveLinear } from 'd3';
import ELK from 'elkjs/lib/elk.bundled.js'; import ELK from 'elkjs/lib/elk.bundled.js';
import { type TreeData, findCommonAncestor } from './find-common-ancestor.js'; import { type TreeData, findCommonAncestor } from './find-common-ancestor.js';
type Node = LayoutData['nodes'][0];
interface NodeWithVertex extends Omit<Node, 'domId'> {
children?: unknown[];
labelData?: any;
domId?: Node['domId'] | SVGGroup | d3.Selection<SVGAElement, unknown, Element | null, unknown>;
}
export const render = async ( export const render = async (
data4Layout: LayoutData, data4Layout: LayoutData,
svg: SVG, svg: SVG,
@@ -31,27 +39,37 @@ export const render = async (
const nodeDb: Record<string, any> = {}; const nodeDb: Record<string, any> = {};
const clusterDb: Record<string, any> = {}; const clusterDb: Record<string, any> = {};
const addVertex = async (nodeEl: any, graph: { children: any[] }, nodeArr: any, node: any) => { const addVertex = async (
nodeEl: SVGGroup,
graph: { children: NodeWithVertex[] },
nodeArr: Node[],
node: Node
) => {
const labelData: any = { width: 0, height: 0 }; const labelData: any = { width: 0, height: 0 };
let boundingBox;
const child = {
...node,
};
graph.children.push(child);
nodeDb[node.id] = child;
const config = getConfig(); const config = getConfig();
// Add the element to the DOM // Add the element to the DOM
if (!node.isGroup) { if (!node.isGroup) {
const child: NodeWithVertex = {
...node,
};
graph.children.push(child);
nodeDb[node.id] = child;
const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir }); const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir });
boundingBox = childNodeEl.node().getBBox(); const boundingBox = childNodeEl.node()!.getBBox();
child.domId = childNodeEl; child.domId = childNodeEl;
child.width = boundingBox.width; child.width = boundingBox.width;
child.height = boundingBox.height; child.height = boundingBox.height;
} else { } else {
// A subgraph // A subgraph
child.children = []; const child: NodeWithVertex & { children: NodeWithVertex[] } = {
...node,
children: [],
};
graph.children.push(child);
nodeDb[node.id] = child;
await addVertices(nodeEl, nodeArr, child, node.id); await addVertices(nodeEl, nodeArr, child, node.id);
if (node.label) { if (node.label) {
@@ -75,28 +93,16 @@ export const render = async (
}; };
const addVertices = async function ( const addVertices = async function (
nodeEl: any, nodeEl: SVGGroup,
nodeArr: any[], nodeArr: Node[],
graph: { graph: { children: NodeWithVertex[] },
id: string; parentId?: string
layoutOptions: {
'elk.hierarchyHandling': string;
'elk.algorithm': any;
'nodePlacement.strategy': any;
'elk.layered.mergeEdges': any;
'elk.direction': string;
'spacing.baseValue': number;
};
children: never[];
edges: never[];
},
parentId?: undefined
) { ) {
const siblings = nodeArr.filter((node: { parentId: any }) => node.parentId === parentId); const siblings = nodeArr.filter((node) => node?.parentId === parentId);
log.info('addVertices APA12', siblings, parentId); log.info('addVertices APA12', siblings, parentId);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
await Promise.all( await Promise.all(
siblings.map(async (node: any) => { siblings.map(async (node) => {
await addVertex(nodeEl, graph, nodeArr, node); await addVertex(nodeEl, graph, nodeArr, node);
}) })
); );

View File

@@ -207,7 +207,7 @@ export const createText = async (
width = 200, width = 200,
addSvgBackground = false, addSvgBackground = false,
} = {}, } = {},
config: MermaidConfig config?: MermaidConfig
) => { ) => {
log.debug( log.debug(
'XYZ createText', 'XYZ createText',

View File

@@ -84,7 +84,7 @@ describe('Test Alias for shapes', function () {
}); });
it('should support alias for shadedProcess shape ', function () { it('should support alias for shadedProcess shape ', function () {
const aliases = ['lined-process', 'lined-rectangle', 'lin-proc', 'lin-rect']; const aliases = ['lined-process', 'lined-rectangle', 'lin-proc', 'lin-rect'] as const;
for (const alias of aliases) { for (const alias of aliases) {
expect(shapes[alias]).toBe(shapes['shaded-process']); expect(shapes[alias]).toBe(shapes['shaded-process']);
} }

View File

@@ -1,10 +1,22 @@
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import { shapes } from './shapes.js'; import { shapes } from './shapes.js';
import type { Node } from '../types.js';
import type { MermaidConfig, SVGGroup } from '../../mermaid.js';
import type { D3Selection } from '../../types.js';
import { handleUndefinedAttr } from '../../utils.js';
import type { graphlib } from 'dagre-d3-es';
const nodeElems = new Map(); type ShapeHandler = (typeof shapes)[keyof typeof shapes];
type NodeElement = D3Selection<SVGAElement> | Awaited<ReturnType<ShapeHandler>>;
export const insertNode = async (elem, node, renderOptions) => { const nodeElems = new Map<string, NodeElement>();
let newEl;
export async function insertNode(
elem: SVGGroup,
node: Node,
renderOptions: { config: MermaidConfig; dir: Node['dir'] }
) {
let newEl: NodeElement | undefined;
let el; let el;
//special check for rect shape (with or without rounded corners) //special check for rect shape (with or without rounded corners)
@@ -16,7 +28,7 @@ export const insertNode = async (elem, node, renderOptions) => {
} }
} }
const shapeHandler = shapes[node.shape]; const shapeHandler = shapes[(node.shape ?? 'undefined') as keyof typeof shapes];
if (!shapeHandler) { if (!shapeHandler) {
throw new Error(`No such shape: ${node.shape}. Please check your syntax.`); throw new Error(`No such shape: ${node.shape}. Please check your syntax.`);
@@ -30,7 +42,10 @@ export const insertNode = async (elem, node, renderOptions) => {
} else if (node.linkTarget) { } else if (node.linkTarget) {
target = node.linkTarget || '_blank'; target = node.linkTarget || '_blank';
} }
newEl = elem.insert('svg:a').attr('xlink:href', node.link).attr('target', target); newEl = elem
.insert<SVGAElement>('svg:a')
.attr('xlink:href', node.link)
.attr('target', target ?? null);
el = await shapeHandler(newEl, node, renderOptions); el = await shapeHandler(newEl, node, renderOptions);
} else { } else {
el = await shapeHandler(elem, node, renderOptions); el = await shapeHandler(elem, node, renderOptions);
@@ -40,7 +55,7 @@ export const insertNode = async (elem, node, renderOptions) => {
newEl.attr('data-id', node.id); newEl.attr('data-id', node.id);
newEl.attr('data-node', true); newEl.attr('data-node', true);
newEl.attr('data-et', 'node'); newEl.attr('data-et', 'node');
newEl.attr('data-look', node.look); newEl.attr('data-look', handleUndefinedAttr(node.look));
if (node.tooltip) { if (node.tooltip) {
el.attr('title', node.tooltip); el.attr('title', node.tooltip);
@@ -49,12 +64,12 @@ export const insertNode = async (elem, node, renderOptions) => {
nodeElems.set(node.id, newEl); nodeElems.set(node.id, newEl);
if (node.haveCallback) { if (node.haveCallback) {
nodeElems.get(node.id).attr('class', nodeElems.get(node.id).attr('class') + ' clickable'); newEl.attr('class', newEl.attr('class') + ' clickable');
} }
return newEl; return newEl;
}; }
export const setNodeElem = (elem, node) => { export const setNodeElem = (elem: NodeElement, node: Pick<Node, 'id'>) => {
nodeElems.set(node.id, elem); nodeElems.set(node.id, elem);
}; };
@@ -62,8 +77,8 @@ export const clear = () => {
nodeElems.clear(); nodeElems.clear();
}; };
export const positionNode = (node) => { export const positionNode = (node: ReturnType<graphlib.Graph['node']>) => {
const el = nodeElems.get(node.id); const el = nodeElems.get(node.id)!;
log.trace( log.trace(
'Transforming node', 'Transforming node',
node.diff, node.diff,

View File

@@ -1,3 +1,5 @@
import type { Entries } from 'type-fest';
import type { D3Selection } from '../../types.js';
import type { Node, ShapeRenderOptions } from '../types.js'; import type { Node, ShapeRenderOptions } from '../types.js';
import { anchor } from './shapes/anchor.js'; import { anchor } from './shapes/anchor.js';
import { bowTieRect } from './shapes/bowTieRect.js'; import { bowTieRect } from './shapes/bowTieRect.js';
@@ -56,8 +58,12 @@ import { waveEdgedRectangle } from './shapes/waveEdgedRectangle.js';
import { waveRectangle } from './shapes/waveRectangle.js'; import { waveRectangle } from './shapes/waveRectangle.js';
import { windowPane } from './shapes/windowPane.js'; import { windowPane } from './shapes/windowPane.js';
// eslint-disable-next-line @typescript-eslint/no-explicit-any type MaybePromise<T> = T | Promise<T>;
type ShapeHandler = (parent: any, node: Node, options: ShapeRenderOptions) => unknown; type ShapeHandler = <T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node,
options: ShapeRenderOptions
) => MaybePromise<D3Selection<SVGGElement>>;
export interface ShapeDefinition { export interface ShapeDefinition {
semanticName: string; semanticName: string;
@@ -75,7 +81,7 @@ export interface ShapeDefinition {
handler: ShapeHandler; handler: ShapeHandler;
} }
export const shapesDefs: ShapeDefinition[] = [ export const shapesDefs = [
{ {
semanticName: 'Process', semanticName: 'Process',
name: 'Rectangle', name: 'Rectangle',
@@ -442,11 +448,11 @@ export const shapesDefs: ShapeDefinition[] = [
aliases: ['lined-document'], aliases: ['lined-document'],
handler: linedWaveEdgedRect, handler: linedWaveEdgedRect,
}, },
]; ] as const satisfies ShapeDefinition[];
const generateShapeMap = () => { const generateShapeMap = () => {
// These are the shapes that didn't have documentation present // These are the shapes that didn't have documentation present
const shapeMap: Record<string, ShapeHandler> = { const undocumentedShapes = {
// States // States
state, state,
choice, choice,
@@ -464,18 +470,25 @@ const generateShapeMap = () => {
imageSquare, imageSquare,
anchor, anchor,
}; } as const;
for (const shape of shapesDefs) { const entries = [
for (const alias of [ ...(Object.entries(undocumentedShapes) as Entries<typeof undocumentedShapes>),
shape.shortName, ...shapesDefs.flatMap((shape) => {
...(shape.aliases ?? []), const aliases = [
...(shape.internalAliases ?? []), shape.shortName,
]) { ...('aliases' in shape ? shape.aliases : []),
shapeMap[alias] = shape.handler; ...('internalAliases' in shape ? shape.internalAliases : []),
} ];
} return aliases.map((alias) => [alias, shape.handler] as const);
return shapeMap; }),
];
return Object.fromEntries(entries) as Record<
(typeof entries)[number][0],
(typeof entries)[number][1]
> satisfies Record<string, ShapeHandler>;
}; };
export const shapes = generateShapeMap(); export const shapes = generateShapeMap();
export type ShapeID = keyof typeof shapes;

View File

@@ -1,11 +1,13 @@
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import { updateNodeBounds, getNodeClasses } from './util.js'; import { updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { handleUndefinedAttr } from '../../../utils.js';
import type { D3Selection } from '../../../types.js';
export const anchor = (parent: SVGAElement, node: Node): Promise<SVGAElement> => { export function anchor<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles } = styles2String(node); const { labelStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const classes = getNodeClasses(node); const classes = getNodeClasses(node);
@@ -14,7 +16,6 @@ export const anchor = (parent: SVGAElement, node: Node): Promise<SVGAElement> =>
cssClasses = 'anchor'; cssClasses = 'anchor';
} }
const shapeSvg = parent const shapeSvg = parent
// @ts-ignore - SVGElement is not typed
.insert('g') .insert('g')
.attr('class', cssClasses) .attr('class', cssClasses)
.attr('id', node.domId || node.id); .attr('id', node.domId || node.id);
@@ -23,6 +24,7 @@ export const anchor = (parent: SVGAElement, node: Node): Promise<SVGAElement> =>
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { fill: 'black', stroke: 'none', fillStyle: 'solid' }); const options = userNodeOverrides(node, { fill: 'black', stroke: 'none', fillStyle: 'solid' });
@@ -31,7 +33,7 @@ export const anchor = (parent: SVGAElement, node: Node): Promise<SVGAElement> =>
} }
const roughNode = rc.circle(0, 0, radius * 2, options); const roughNode = rc.circle(0, 0, radius * 2, options);
const circleElem = shapeSvg.insert(() => roughNode, ':first-child'); const circleElem = shapeSvg.insert(() => roughNode, ':first-child');
circleElem.attr('class', 'anchor').attr('style', cssStyles); circleElem.attr('class', 'anchor').attr('style', handleUndefinedAttr(cssStyles));
updateNodeBounds(node, circleElem); updateNodeBounds(node, circleElem);
@@ -41,4 +43,4 @@ export const anchor = (parent: SVGAElement, node: Node): Promise<SVGAElement> =>
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,8 +1,9 @@
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
function generateArcPoints( function generateArcPoints(
x1: number, x1: number,
@@ -84,7 +85,7 @@ function calculateArcSagitta(chord: number, radiusX: number, radiusY: number) {
return semiMinorAxis * (1 - Math.sqrt(1 - (chord / semiMajorAxis / 2) ** 2)); return semiMinorAxis * (1 - Math.sqrt(1 - (chord / semiMajorAxis / 2) ** 2));
} }
export const bowTieRect = async (parent: SVGAElement, node: Node) => { export async function bowTieRect<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -132,6 +133,7 @@ export const bowTieRect = async (parent: SVGAElement, node: Node) => {
...generateArcPoints(w / 2, h / 2, w / 2, -h / 2, rx, ry, true), ...generateArcPoints(w / 2, h / 2, w / 2, -h / 2, rx, ry, true),
]; ];
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -163,4 +165,4 @@ export const bowTieRect = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,11 +1,12 @@
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js'; import { insertPolygonShape } from './insertPolygonShape.js';
import { createPathFromPoints } from './util.js'; import { createPathFromPoints } from './util.js';
import type { D3Selection } from '../../../types.js';
// const createPathFromPoints = (points: { x: number; y: number }[]): string => { // const createPathFromPoints = (points: { x: number; y: number }[]): string => {
// const pointStrings = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`); // const pointStrings = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`);
@@ -16,7 +17,7 @@ import { createPathFromPoints } from './util.js';
/// Size of the notch on the top left corner /// Size of the notch on the top left corner
const NOTCH_SIZE = 12; const NOTCH_SIZE = 12;
export async function card(parent: SVGAElement, node: Node): Promise<SVGAElement> { export async function card<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
@@ -49,10 +50,11 @@ export async function card(parent: SVGAElement, node: Node): Promise<SVGAElement
{ x: left + NOTCH_SIZE, y: top }, { x: left + NOTCH_SIZE, y: top },
]; ];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>; let polygon: D3Selection<SVGGElement> | Awaited<ReturnType<typeof insertPolygonShape>>;
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
const pathData = createPathFromPoints(points); const pathData = createPathFromPoints(points);

View File

@@ -1,12 +1,11 @@
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.js'; import type { Node } from '../../types.js';
import type { SVG } from '../../../diagram-api/types.js';
// @ts-ignore TODO: Fix rough typings
import rough from 'roughjs'; import rough from 'roughjs';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { createPathFromPoints, getNodeClasses } from './util.js'; import { createPathFromPoints, getNodeClasses } from './util.js';
import type { D3Selection } from '../../../types.js';
export const choice = (parent: SVG, node: Node) => { export function choice<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { nodeStyles } = styles2String(node); const { nodeStyles } = styles2String(node);
node.label = ''; node.label = '';
const shapeSvg = parent const shapeSvg = parent
@@ -24,7 +23,7 @@ export const choice = (parent: SVG, node: Node) => {
{ x: -s / 2, y: 0 }, { x: -s / 2, y: 0 },
]; ];
// @ts-ignore TODO: Fix rough typings // @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -53,4 +52,4 @@ export const choice = (parent: SVG, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -4,8 +4,10 @@ import intersect from '../intersect/index.js';
import type { Node } from '../../types.js'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
export const circle = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => { export async function circle<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
// If incoming height & width are present, subtract the padding from them // If incoming height & width are present, subtract the padding from them
@@ -24,12 +26,13 @@ export const circle = async (parent: SVGAElement, node: Node): Promise<SVGAEleme
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
const roughNode = rc.circle(0, 0, radius * 2, options); const roughNode = rc.circle(0, 0, radius * 2, options);
circleElem = shapeSvg.insert(() => roughNode, ':first-child'); circleElem = shapeSvg.insert(() => roughNode, ':first-child');
circleElem.attr('class', 'basic label-container').attr('style', cssStyles); circleElem.attr('class', 'basic label-container').attr('style', handleUndefinedAttr(cssStyles));
} else { } else {
circleElem = shapeSvg circleElem = shapeSvg
.insert('circle', ':first-child') .insert('circle', ':first-child')
@@ -48,4 +51,4 @@ export const circle = async (parent: SVGAElement, node: Node): Promise<SVGAEleme
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,10 +1,10 @@
import rough from 'roughjs';
import type { SVG } from '../../../diagram-api/types.js';
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import type { Node } from '../../types.d.ts'; import { getNodeClasses, updateNodeBounds } from './util.js';
import type { Node } from '../../types.js';
import rough from 'roughjs';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { userNodeOverrides } from './handDrawnShapeStyles.js'; import { userNodeOverrides } from './handDrawnShapeStyles.js';
import { getNodeClasses, updateNodeBounds } from './util.js'; import type { D3Selection } from '../../../types.js';
function createLine(r: number) { function createLine(r: number) {
const axis45 = Math.SQRT1_2; // cosine of 45 degrees = 1/sqrt(2) const axis45 = Math.SQRT1_2; // cosine of 45 degrees = 1/sqrt(2)
@@ -19,7 +19,7 @@ function createLine(r: number) {
M ${pointQ1.x},${pointQ1.y} L ${pointQ3.x},${pointQ3.y}`; M ${pointQ1.x},${pointQ1.y} L ${pointQ3.x},${pointQ3.y}`;
} }
export const crossedCircle = (parent: SVG, node: Node) => { export function crossedCircle<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
node.label = ''; node.label = '';
const shapeSvg = parent const shapeSvg = parent
.insert('g') .insert('g')
@@ -28,7 +28,7 @@ export const crossedCircle = (parent: SVG, node: Node) => {
const radius = node?.width ? node?.width / 2 : node?.height ? node?.height / 2 : 25; const radius = node?.width ? node?.width / 2 : node?.height ? node?.height / 2 : 25;
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error shapeSvg d3 class is incorrect? // @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -58,4 +58,4 @@ export const crossedCircle = (parent: SVG, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,8 +1,9 @@
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
function generateCirclePoints( function generateCirclePoints(
centerX: number, centerX: number,
@@ -34,7 +35,10 @@ function generateCirclePoints(
return points; return points;
} }
export const curlyBraceLeft = async (parent: SVGAElement, node: Node) => { export async function curlyBraceLeft<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node)); const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
@@ -84,6 +88,7 @@ export const curlyBraceLeft = async (parent: SVGAElement, node: Node) => {
{ x: w / 2, y: h / 2 + radius }, { x: w / 2, y: h / 2 + radius },
]; ];
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { fill: 'none' }); const options = userNodeOverrides(node, { fill: 'none' });
@@ -126,4 +131,4 @@ export const curlyBraceLeft = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,8 +1,9 @@
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
function generateCirclePoints( function generateCirclePoints(
centerX: number, centerX: number,
@@ -34,7 +35,10 @@ function generateCirclePoints(
return points; return points;
} }
export const curlyBraceRight = async (parent: SVGAElement, node: Node) => { export async function curlyBraceRight<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node)); const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
@@ -87,6 +91,7 @@ export const curlyBraceRight = async (parent: SVGAElement, node: Node) => {
{ x: -w / 2, y: h / 2 + radius }, { x: -w / 2, y: h / 2 + radius },
]; ];
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { fill: 'none' }); const options = userNodeOverrides(node, { fill: 'none' });
@@ -130,4 +135,4 @@ export const curlyBraceRight = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,8 +1,9 @@
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
function generateCirclePoints( function generateCirclePoints(
centerX: number, centerX: number,
@@ -34,7 +35,10 @@ function generateCirclePoints(
return points; return points;
} }
export const curlyBraces = async (parent: SVGAElement, node: Node) => { export async function curlyBraces<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node)); const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
@@ -101,6 +105,7 @@ export const curlyBraces = async (parent: SVGAElement, node: Node) => {
...generateCirclePoints(-w / 2 + radius + radius / 2, h / 2, radius, 30, -180, -270), ...generateCirclePoints(-w / 2 + radius + radius / 2, h / 2, radius, 30, -180, -270),
]; ];
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { fill: 'transparent' }); const options = userNodeOverrides(node, { fill: 'transparent' });
@@ -142,4 +147,4 @@ export const curlyBraces = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -6,11 +6,15 @@ import {
generateCirclePoints, generateCirclePoints,
} from './util.js'; } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
export const curvedTrapezoid = async (parent: SVGAElement, node: Node) => { export async function curvedTrapezoid<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -39,6 +43,7 @@ export const curvedTrapezoid = async (parent: SVGAElement, node: Node) => {
const radius = h / 2; const radius = h / 2;
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -85,4 +90,4 @@ export const curvedTrapezoid = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -4,6 +4,8 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { getConfig } from '../../../config.js'; import { getConfig } from '../../../config.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
export const createCylinderPathD = ( export const createCylinderPathD = (
x: number, x: number,
@@ -52,7 +54,7 @@ export const createInnerCylinderPathD = (
const MIN_HEIGHT = 8; const MIN_HEIGHT = 8;
const MIN_WIDTH = 8; const MIN_WIDTH = 8;
export const cylinder = async (parent: SVGAElement, node: Node) => { export async function cylinder<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { themeVariables } = getConfig(); const { themeVariables } = getConfig();
const { useGradient } = themeVariables; const { useGradient } = themeVariables;
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
@@ -85,10 +87,11 @@ export const cylinder = async (parent: SVGAElement, node: Node) => {
const ry = rx / (2.5 + w / 50); const ry = rx / (2.5 + w / 50);
const h = (node.height ? node.height : bbox.height) + labelPaddingX + ry; const h = (node.height ? node.height : bbox.height) + labelPaddingX + ry;
let cylinder: d3.Selection<SVGPathElement | SVGGElement, unknown, null, undefined>; let cylinder: D3Selection<SVGPathElement> | D3Selection<SVGGElement>;
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn' || (node.look === 'neo' && !useGradient)) { if (node.look === 'handDrawn' || (node.look === 'neo' && !useGradient)) {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry); const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry);
const innerPathData = createInnerCylinderPathD(0, ry, w, h, rx, ry); const innerPathData = createInnerCylinderPathD(0, ry, w, h, rx, ry);
@@ -118,7 +121,7 @@ export const cylinder = async (parent: SVGAElement, node: Node) => {
.insert('path', ':first-child') .insert('path', ':first-child')
.attr('d', pathData) .attr('d', pathData)
.attr('class', 'basic label-container') .attr('class', 'basic label-container')
.attr('style', cssStyles) .attr('style', handleUndefinedAttr(cssStyles))
.attr('style', nodeStyles); .attr('style', nodeStyles);
} }
@@ -159,4 +162,4 @@ export const cylinder = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,10 +1,14 @@
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
export const dividedRectangle = async (parent: SVGAElement, node: Node) => { export async function dividedRectangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
@@ -34,6 +38,7 @@ export const dividedRectangle = async (parent: SVGAElement, node: Node) => {
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
if (node.look !== 'handDrawn') { if (node.look !== 'handDrawn') {
@@ -81,4 +86,4 @@ export const dividedRectangle = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,8 +1,10 @@
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
export const createCylinderPathD = ( export const createCylinderPathD = (
x: number, x: number,
@@ -48,7 +50,7 @@ export const createInnerCylinderPathD = (
): string => { ): string => {
return [`M${x - width / 2},${-height / 2}`, `a${rx},${ry} 0,0,0 ${width},0`].join(' '); return [`M${x - width / 2},${-height / 2}`, `a${rx},${ry} 0,0,0 ${width},0`].join(' ');
}; };
export const cylinder = async (parent: SVGAElement, node: Node) => { export async function cylinder<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node)); const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
@@ -57,10 +59,11 @@ export const cylinder = async (parent: SVGAElement, node: Node) => {
const ry = rx / (2.5 + w / 50); const ry = rx / (2.5 + w / 50);
const h = bbox.height + ry + node.padding; const h = bbox.height + ry + node.padding;
let cylinder: d3.Selection<SVGPathElement | SVGGElement, unknown, null, undefined>; let cylinder: D3Selection<SVGPathElement> | D3Selection<SVGGElement>;
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry); const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry);
const innerPathData = createInnerCylinderPathD(0, ry, w, h, rx, ry); const innerPathData = createInnerCylinderPathD(0, ry, w, h, rx, ry);
@@ -79,7 +82,7 @@ export const cylinder = async (parent: SVGAElement, node: Node) => {
.insert('path', ':first-child') .insert('path', ':first-child')
.attr('d', pathData) .attr('d', pathData)
.attr('class', 'basic label-container') .attr('class', 'basic label-container')
.attr('style', cssStyles) .attr('style', handleUndefinedAttr(cssStyles))
.attr('style', nodeStyles); .attr('style', nodeStyles);
} }
@@ -114,4 +117,4 @@ export const cylinder = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -4,8 +4,13 @@ import intersect from '../intersect/index.js';
import type { Node } from '../../types.js'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
export const doublecircle = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => { export async function doublecircle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
const gap = 5; const gap = 5;
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
@@ -30,6 +35,7 @@ export const doublecircle = async (parent: SVGAElement, node: Node): Promise<SVG
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const outerOptions = userNodeOverrides(node, { roughness: 0.2, strokeWidth: 2.5 }); const outerOptions = userNodeOverrides(node, { roughness: 0.2, strokeWidth: 2.5 });
@@ -39,7 +45,9 @@ export const doublecircle = async (parent: SVGAElement, node: Node): Promise<SVG
circleGroup = shapeSvg.insert('g', ':first-child'); circleGroup = shapeSvg.insert('g', ':first-child');
// circleGroup = circleGroup.insert(() => outerRoughNode, ':first-child'); // circleGroup = circleGroup.insert(() => outerRoughNode, ':first-child');
circleGroup.attr('class', node.cssClasses).attr('style', cssStyles); circleGroup
.attr('class', handleUndefinedAttr(node.cssClasses))
.attr('style', handleUndefinedAttr(cssStyles));
circleGroup.node()?.appendChild(outerRoughNode); circleGroup.node()?.appendChild(outerRoughNode);
circleGroup.node()?.appendChild(innerRoughNode); circleGroup.node()?.appendChild(innerRoughNode);
@@ -73,4 +81,4 @@ export const doublecircle = async (parent: SVGAElement, node: Node): Promise<SVG
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -4,8 +4,14 @@ import type { Node, RectOptions } from '../../types.js';
import { createRoundedRectPathD } from './roundedRectPath.js'; import { createRoundedRectPathD } from './roundedRectPath.js';
import { userNodeOverrides, styles2String } from './handDrawnShapeStyles.js'; import { userNodeOverrides, styles2String } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
export const drawRect = async (parent: SVGAElement, node: Node, options: RectOptions) => { export async function drawRect<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node,
options: RectOptions
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
// If incoming height & width are present, subtract the padding from them // If incoming height & width are present, subtract the padding from them
@@ -45,7 +51,7 @@ export const drawRect = async (parent: SVGAElement, node: Node, options: RectOpt
: rc.rectangle(x, y, totalWidth, totalHeight, options); : rc.rectangle(x, y, totalWidth, totalHeight, options);
rect = shapeSvg.insert(() => roughNode, ':first-child'); rect = shapeSvg.insert(() => roughNode, ':first-child');
rect.attr('class', 'basic label-container').attr('style', cssStyles); rect.attr('class', 'basic label-container').attr('style', handleUndefinedAttr(cssStyles));
} else { } else {
rect = shapeSvg.insert('rect', ':first-child'); rect = shapeSvg.insert('rect', ':first-child');
@@ -54,9 +60,9 @@ export const drawRect = async (parent: SVGAElement, node: Node, options: RectOpt
rect rect
.attr('class', rectClass) .attr('class', rectClass)
.attr('style', nodeStyles) .attr('style', nodeStyles)
.attr('rx', rx) .attr('rx', handleUndefinedAttr(rx))
.attr('data-id', node.id) .attr('data-id', node.id)
.attr('ry', ry) .attr('ry', handleUndefinedAttr(ry))
.attr('x', x) .attr('x', x)
.attr('y', y) .attr('y', y)
.attr('width', totalWidth) .attr('width', totalWidth)
@@ -71,4 +77,4 @@ export const drawRect = async (parent: SVGAElement, node: Node, options: RectOpt
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,16 +1,16 @@
import rough from 'roughjs'; import rough from 'roughjs';
import type { SVG } from '../../../diagram-api/types.js';
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import type { Node, ShapeRenderOptions } from '../../types.ts'; import type { Node, ShapeRenderOptions } from '../../types.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { userNodeOverrides } from './handDrawnShapeStyles.js'; import { userNodeOverrides } from './handDrawnShapeStyles.js';
import { getNodeClasses, updateNodeBounds } from './util.js'; import { getNodeClasses, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
export const filledCircle = ( export function filledCircle<T extends SVGGraphicsElement>(
parent: SVG, parent: D3Selection<T>,
node: Node, node: Node,
{ config: { themeVariables } }: ShapeRenderOptions { config: { themeVariables } }: ShapeRenderOptions
) => { ) {
node.label = ''; node.label = '';
// If incoming height & width are present, subtract the padding from them // If incoming height & width are present, subtract the padding from them
@@ -42,7 +42,7 @@ export const filledCircle = (
const radius = (node.width ?? 0) / 2; const radius = (node.width ?? 0) / 2;
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error shapeSvg d3 class is incorrect? // @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const { nodeBorder } = themeVariables; const { nodeBorder } = themeVariables;
const options = userNodeOverrides(node, { fillStyle: 'solid' }); const options = userNodeOverrides(node, { fillStyle: 'solid' });
@@ -70,4 +70,4 @@ export const filledCircle = (
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,14 +1,18 @@
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { createPathFromPoints } from './util.js'; import { createPathFromPoints } from './util.js';
import type { D3Selection } from '../../../types.js';
const MIN_HEIGHT = 10; const MIN_HEIGHT = 10;
const MIN_WIDTH = 10; const MIN_WIDTH = 10;
export const flippedTriangle = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => { export async function flippedTriangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
@@ -41,6 +45,7 @@ export const flippedTriangle = async (parent: SVGAElement, node: Node): Promise<
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
if (node.look !== 'handDrawn') { if (node.look !== 'handDrawn') {
@@ -78,4 +83,4 @@ export const flippedTriangle = async (parent: SVGAElement, node: Node): Promise<
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,15 +1,15 @@
import rough from 'roughjs'; import rough from 'roughjs';
import type { SVG } from '../../../diagram-api/types.js';
import type { Node, ShapeRenderOptions } from '../../types.js'; import type { Node, ShapeRenderOptions } from '../../types.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { getNodeClasses, updateNodeBounds } from './util.js'; import { getNodeClasses, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
export const forkJoin = ( export function forkJoin<T extends SVGGraphicsElement>(
parent: SVG, parent: D3Selection<T>,
node: Node, node: Node,
{ dir, config: { state, themeVariables } }: ShapeRenderOptions { dir, config: { state, themeVariables } }: ShapeRenderOptions
) => { ) {
const { nodeStyles } = styles2String(node); const { nodeStyles } = styles2String(node);
node.label = ''; node.label = '';
const shapeSvg = parent const shapeSvg = parent
@@ -29,7 +29,7 @@ export const forkJoin = (
const x = (-1 * width) / 2; const x = (-1 * width) / 2;
const y = (-1 * height) / 2; const y = (-1 * height) / 2;
// @ts-ignore TODO: Fix rough typings // @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { const options = userNodeOverrides(node, {
stroke: themeVariables.lineColor, stroke: themeVariables.lineColor,
@@ -63,4 +63,4 @@ export const forkJoin = (
return intersect.rect(node, point); return intersect.rect(node, point);
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -7,11 +7,15 @@ import {
generateCirclePoints, generateCirclePoints,
} from './util.js'; } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
export const halfRoundedRectangle = async (parent: SVGAElement, node: Node) => { export async function halfRoundedRectangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const minWidth = 15, const minWidth = 15,
@@ -39,6 +43,7 @@ export const halfRoundedRectangle = async (parent: SVGAElement, node: Node) => {
const radius = h / 2; const radius = h / 2;
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -81,4 +86,4 @@ export const halfRoundedRectangle = async (parent: SVGAElement, node: Node) => {
return pos; return pos;
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -3,8 +3,8 @@ import intersect from '../intersect/index.js';
import type { Node } from '../../types.js'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js'; import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
export const createHexagonPathD = ( export const createHexagonPathD = (
x: number, x: number,
@@ -24,7 +24,7 @@ export const createHexagonPathD = (
].join(' '); ].join(' ');
}; };
export const hexagon = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => { export async function hexagon<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
const f = 4; const f = 4;
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
@@ -53,10 +53,11 @@ export const hexagon = async (parent: SVGAElement, node: Node): Promise<SVGAElem
{ x: 0, y: -h / 2 }, { x: 0, y: -h / 2 },
]; ];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>; let polygon: D3Selection<SVGGElement> | Awaited<ReturnType<typeof insertPolygonShape>>;
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
const pathData = createHexagonPathD(0, 0, w, h, m); const pathData = createHexagonPathD(0, 0, w, h, m);
@@ -87,4 +88,4 @@ export const hexagon = async (parent: SVGAElement, node: Node): Promise<SVGAElem
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -2,10 +2,11 @@ import rough from 'roughjs';
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { userNodeOverrides } from './handDrawnShapeStyles.js'; import { userNodeOverrides } from './handDrawnShapeStyles.js';
import type { D3Selection } from '../../../types.js';
export const hourglass = async (parent: SVGAElement, node: Node) => { export async function hourglass<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
node.label = ''; node.label = '';
const { shapeSvg } = await labelHelper(parent, node, getNodeClasses(node)); const { shapeSvg } = await labelHelper(parent, node, getNodeClasses(node));
@@ -14,6 +15,7 @@ export const hourglass = async (parent: SVGAElement, node: Node) => {
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -49,4 +51,4 @@ export const hourglass = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,17 +1,17 @@
import rough from 'roughjs'; import rough from 'roughjs';
import type { SVG } from '../../../diagram-api/types.js';
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import { getIconSVG } from '../../icons.js'; import { getIconSVG } from '../../icons.js';
import type { Node, ShapeRenderOptions } from '../../types.ts'; import type { Node, ShapeRenderOptions } from '../../types.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { labelHelper, updateNodeBounds } from './util.js'; import { labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
export const icon = async ( export async function icon<T extends SVGGraphicsElement>(
parent: SVG, parent: D3Selection<T>,
node: Node, node: Node,
{ config: { themeVariables, flowchart } }: ShapeRenderOptions { config: { themeVariables, flowchart } }: ShapeRenderOptions
) => { ) {
const { labelStyles } = styles2String(node); const { labelStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const assetHeight = node.assetHeight ?? 48; const assetHeight = node.assetHeight ?? 48;
@@ -33,6 +33,7 @@ export const icon = async (
const labelPadding = node.label ? 8 : 0; const labelPadding = node.label ? 8 : 0;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { stroke: 'none', fill: 'none' }); const options = userNodeOverrides(node, { stroke: 'none', fill: 'none' });
@@ -64,7 +65,7 @@ export const icon = async (
fallbackPrefix: '', fallbackPrefix: '',
})}</g>` })}</g>`
); );
const iconBBox = iconElem.node().getBBox(); const iconBBox = iconElem.node()!.getBBox();
const iconWidth = iconBBox.width; const iconWidth = iconBBox.width;
const iconHeight = iconBBox.height; const iconHeight = iconBBox.height;
const iconX = iconBBox.x; const iconX = iconBBox.x;
@@ -79,9 +80,9 @@ export const icon = async (
); );
iconElem.attr('style', `color : ${stylesMap.get('stroke') ?? nodeBorder};`); iconElem.attr('style', `color : ${stylesMap.get('stroke') ?? nodeBorder};`);
iconElem iconElem
.selectAll('path') .selectAll<SVGPathElement, unknown>('path')
.nodes() .nodes()
.forEach((path: SVGPathElement) => { .forEach((path) => {
if (path.getAttribute('fill') === 'currentColor') { if (path.getAttribute('fill') === 'currentColor') {
path.setAttribute('class', 'icon'); path.setAttribute('class', 'icon');
} }
@@ -150,4 +151,4 @@ export const icon = async (
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,17 +1,17 @@
import rough from 'roughjs'; import rough from 'roughjs';
import type { SVG } from '../../../diagram-api/types.js';
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import { getIconSVG } from '../../icons.js'; import { getIconSVG } from '../../icons.js';
import type { Node, ShapeRenderOptions } from '../../types.ts'; import type { Node, ShapeRenderOptions } from '../../types.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { labelHelper, updateNodeBounds } from './util.js'; import { labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
export const iconCircle = async ( export async function iconCircle<T extends SVGGraphicsElement>(
parent: SVG, parent: D3Selection<T>,
node: Node, node: Node,
{ config: { themeVariables, flowchart } }: ShapeRenderOptions { config: { themeVariables, flowchart } }: ShapeRenderOptions
) => { ) {
const { labelStyles } = styles2String(node); const { labelStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const defaultWidth = flowchart?.wrappingWidth; const defaultWidth = flowchart?.wrappingWidth;
@@ -48,6 +48,7 @@ export const iconCircle = async (
const { nodeBorder } = themeVariables; const { nodeBorder } = themeVariables;
const { stylesMap } = compileStyles(node); const { stylesMap } = compileStyles(node);
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { stroke: 'transparent' }); const options = userNodeOverrides(node, { stroke: 'transparent' });
@@ -66,7 +67,7 @@ export const iconCircle = async (
})}</g>` })}</g>`
); );
} }
const iconBBox = iconElem.node().getBBox(); const iconBBox = iconElem.node()!.getBBox();
const iconWidth = iconBBox.width; const iconWidth = iconBBox.width;
// const iconHeight = iconBBox.height; // const iconHeight = iconBBox.height;
const iconX = iconBBox.x; const iconX = iconBBox.x;
@@ -96,9 +97,9 @@ export const iconCircle = async (
); );
iconElem.attr('style', `color : ${stylesMap.get('stroke') ?? nodeBorder};`); iconElem.attr('style', `color : ${stylesMap.get('stroke') ?? nodeBorder};`);
iconElem iconElem
.selectAll('path') .selectAll<SVGPathElement, unknown>('path')
.nodes() .nodes()
.forEach((path: SVGPathElement) => { .forEach((path) => {
if (path.getAttribute('fill') === 'currentColor') { if (path.getAttribute('fill') === 'currentColor') {
path.setAttribute('class', 'icon'); path.setAttribute('class', 'icon');
} }
@@ -136,4 +137,4 @@ export const iconCircle = async (
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,18 +1,18 @@
import rough from 'roughjs'; import rough from 'roughjs';
import type { SVG } from '../../../diagram-api/types.js';
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import { getIconSVG } from '../../icons.js'; import { getIconSVG } from '../../icons.js';
import type { Node, ShapeRenderOptions } from '../../types.ts'; import type { Node, ShapeRenderOptions } from '../../types.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { createRoundedRectPathD } from './roundedRectPath.js'; import { createRoundedRectPathD } from './roundedRectPath.js';
import { labelHelper, updateNodeBounds } from './util.js'; import { labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
export const iconRounded = async ( export async function iconRounded<T extends SVGGraphicsElement>(
parent: SVG, parent: D3Selection<T>,
node: Node, node: Node,
{ config: { themeVariables, flowchart } }: ShapeRenderOptions { config: { themeVariables, flowchart } }: ShapeRenderOptions
) => { ) {
const { labelStyles } = styles2String(node); const { labelStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const defaultWidth = flowchart?.wrappingWidth; const defaultWidth = flowchart?.wrappingWidth;
@@ -51,6 +51,7 @@ export const iconRounded = async (
const x = -width / 2; const x = -width / 2;
const y = -height / 2; const y = -height / 2;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { stroke: 'transparent' }); const options = userNodeOverrides(node, { stroke: 'transparent' });
@@ -78,7 +79,7 @@ export const iconRounded = async (
iconElem.html( iconElem.html(
`<g>${await getIconSVG(node.icon, { height: iconSize, fallbackPrefix: '' })}</g>` `<g>${await getIconSVG(node.icon, { height: iconSize, fallbackPrefix: '' })}</g>`
); );
const iconBBox = iconElem.node().getBBox(); const iconBBox = iconElem.node()!.getBBox();
const iconWidth = iconBBox.width; const iconWidth = iconBBox.width;
// const iconHeight = iconBBox.height; // const iconHeight = iconBBox.height;
const iconX = iconBBox.x; const iconX = iconBBox.x;
@@ -89,9 +90,9 @@ export const iconRounded = async (
); );
iconElem.attr('style', `color : ${stylesMap.get('stroke') ?? nodeBorder};`); iconElem.attr('style', `color : ${stylesMap.get('stroke') ?? nodeBorder};`);
iconElem iconElem
.selectAll('path') .selectAll<SVGPathElement, unknown>('path')
.nodes() .nodes()
.forEach((path: SVGPathElement) => { .forEach((path) => {
if (path.getAttribute('fill') === 'currentColor') { if (path.getAttribute('fill') === 'currentColor') {
path.setAttribute('class', 'icon'); path.setAttribute('class', 'icon');
} }
@@ -157,4 +158,4 @@ export const iconRounded = async (
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,17 +1,17 @@
import rough from 'roughjs'; import rough from 'roughjs';
import type { SVG } from '../../../diagram-api/types.js';
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import { getIconSVG } from '../../icons.js'; import { getIconSVG } from '../../icons.js';
import type { Node, ShapeRenderOptions } from '../../types.ts'; import type { Node, ShapeRenderOptions } from '../../types.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { labelHelper, updateNodeBounds } from './util.js'; import { labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
export const iconSquare = async ( export async function iconSquare<T extends SVGGraphicsElement>(
parent: SVG, parent: D3Selection<T>,
node: Node, node: Node,
{ config: { themeVariables, flowchart } }: ShapeRenderOptions { config: { themeVariables, flowchart } }: ShapeRenderOptions
) => { ) {
const { labelStyles } = styles2String(node); const { labelStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const defaultWidth = flowchart?.wrappingWidth; const defaultWidth = flowchart?.wrappingWidth;
@@ -54,6 +54,7 @@ export const iconSquare = async (
const x = -width / 2; const x = -width / 2;
const y = -height / 2; const y = -height / 2;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { stroke: 'transparent' }); const options = userNodeOverrides(node, { stroke: 'transparent' });
@@ -80,7 +81,7 @@ export const iconSquare = async (
iconElem.html( iconElem.html(
`<g>${await getIconSVG(node.icon, { height: iconSize, fallbackPrefix: '' })}</g>` `<g>${await getIconSVG(node.icon, { height: iconSize, fallbackPrefix: '' })}</g>`
); );
const iconBBox = iconElem.node().getBBox(); const iconBBox = iconElem.node()!.getBBox();
const iconWidth = iconBBox.width; const iconWidth = iconBBox.width;
// const iconHeight = iconBBox.height; // const iconHeight = iconBBox.height;
const iconX = iconBBox.x; const iconX = iconBBox.x;
@@ -91,9 +92,9 @@ export const iconSquare = async (
); );
iconElem.attr('style', `color : ${stylesMap.get('stroke') ?? nodeBorder};`); iconElem.attr('style', `color : ${stylesMap.get('stroke') ?? nodeBorder};`);
iconElem iconElem
.selectAll('path') .selectAll<SVGPathElement, unknown>('path')
.nodes() .nodes()
.forEach((path: SVGPathElement) => { .forEach((path) => {
if (path.getAttribute('fill') === 'currentColor') { if (path.getAttribute('fill') === 'currentColor') {
path.setAttribute('class', 'icon'); path.setAttribute('class', 'icon');
} }
@@ -169,4 +170,4 @@ export const iconSquare = async (
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,15 +1,15 @@
import type { SVG } from '../../../diagram-api/types.js';
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import type { Node, ShapeRenderOptions } from '../../types.ts'; import type { Node, ShapeRenderOptions } from '../../types.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { labelHelper, updateNodeBounds } from './util.js'; import { labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
export const imageSquare = async ( export async function imageSquare<T extends SVGGraphicsElement>(
parent: SVG, parent: D3Selection<T>,
node: Node, node: Node,
{ config: { flowchart } }: ShapeRenderOptions { config: { flowchart } }: ShapeRenderOptions
) => { ) {
const img = new Image(); const img = new Image();
img.src = node?.img ?? ''; img.src = node?.img ?? '';
await img.decode(); await img.decode();
@@ -161,4 +161,4 @@ export const imageSquare = async (
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,5 +1,7 @@
export function insertPolygonShape( import type { D3Selection } from '../../../types.js';
parent: any,
export function insertPolygonShape<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
w: number, w: number,
h: number, h: number,
points: { x: number; y: number }[] points: { x: number; y: number }[]

View File

@@ -4,6 +4,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js'; import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
// export const createInvertedTrapezoidPathD = ( // export const createInvertedTrapezoidPathD = (
// x: number, // x: number,
@@ -20,7 +21,10 @@ import { insertPolygonShape } from './insertPolygonShape.js';
// ].join(' '); // ].join(' ');
// }; // };
export const inv_trapezoid = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => { export async function inv_trapezoid<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
@@ -51,10 +55,11 @@ export const inv_trapezoid = async (parent: SVGAElement, node: Node): Promise<SV
{ x: (-3 * h) / 6, y: -h }, { x: (-3 * h) / 6, y: -h },
]; ];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>; let polygon: typeof shapeSvg | ReturnType<typeof insertPolygonShape>;
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
const pathData = createPathFromPoints(points); const pathData = createPathFromPoints(points);
@@ -86,4 +91,4 @@ export const inv_trapezoid = async (parent: SVGAElement, node: Node): Promise<SV
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -2,8 +2,12 @@ import type { Node, RectOptions } from '../../types.js';
import { drawRect } from './drawRect.js'; import { drawRect } from './drawRect.js';
import { labelHelper, updateNodeBounds } from './util.js'; import { labelHelper, updateNodeBounds } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { D3Selection } from '../../../types.js';
export const roundedRect = async (parent: SVGAElement, node: Node) => { export async function roundedRect<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const options = { const options = {
rx: 5, rx: 5,
ry: 5, ry: 5,
@@ -13,9 +17,9 @@ export const roundedRect = async (parent: SVGAElement, node: Node) => {
} as RectOptions; } as RectOptions;
return drawRect(parent, node, options); return drawRect(parent, node, options);
}; }
export const labelRect = async (parent: SVGElement, node: Node) => { export async function labelRect<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { shapeSvg, bbox, label } = await labelHelper(parent, node, 'label'); const { shapeSvg, bbox, label } = await labelHelper(parent, node, 'label');
// log.trace('Classes = ', node.class); // log.trace('Classes = ', node.class);
@@ -52,4 +56,4 @@ export const labelRect = async (parent: SVGElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -4,8 +4,9 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js'; import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
export const lean_left = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => { export async function lean_left<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -36,10 +37,11 @@ export const lean_left = async (parent: SVGAElement, node: Node): Promise<SVGAEl
{ x: -(3 * h) / 6, y: -h }, { x: -(3 * h) / 6, y: -h },
]; ];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>; let polygon: typeof shapeSvg | ReturnType<typeof insertPolygonShape>;
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
const pathData = createPathFromPoints(points); const pathData = createPathFromPoints(points);
@@ -71,4 +73,4 @@ export const lean_left = async (parent: SVGAElement, node: Node): Promise<SVGAEl
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -4,8 +4,9 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js'; import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
export const lean_right = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => { export async function lean_right<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -38,10 +39,11 @@ export const lean_right = async (parent: SVGAElement, node: Node): Promise<SVGAE
{ x: 0, y: -h }, { x: 0, y: -h },
]; ];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>; let polygon: typeof shapeSvg | ReturnType<typeof insertPolygonShape>;
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
const pathData = createPathFromPoints(points); const pathData = createPathFromPoints(points);
@@ -72,4 +74,4 @@ export const lean_right = async (parent: SVGAElement, node: Node): Promise<SVGAE
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,12 +1,13 @@
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import { createPathFromPoints, getNodeClasses, updateNodeBounds } from './util.js'; import { getNodeClasses, updateNodeBounds } from './util.js';
import type { Node } from '../../types.d.ts'; import type { Node } from '../../types.js';
import type { SVG } from '../../../diagram-api/types.js';
import { userNodeOverrides } from './handDrawnShapeStyles.js'; import { userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { createPathFromPoints } from './util.js';
import type { D3Selection } from '../../../types.js';
export const lightningBolt = (parent: SVG, node: Node) => { export function lightningBolt<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
node.label = ''; node.label = '';
const shapeSvg = parent const shapeSvg = parent
.insert('g') .insert('g')
@@ -58,4 +59,4 @@ export const lightningBolt = (parent: SVG, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -3,6 +3,8 @@ import intersect from '../intersect/index.js';
import type { Node } from '../../types.js'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
export const createCylinderPathD = ( export const createCylinderPathD = (
x: number, x: number,
@@ -58,7 +60,10 @@ export const createInnerCylinderPathD = (
const MIN_HEIGHT = 10; const MIN_HEIGHT = 10;
const MIN_WIDTH = 10; const MIN_WIDTH = 10;
export const linedCylinder = async (parent: SVGAElement, node: Node) => { export async function linedCylinder<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -89,10 +94,11 @@ export const linedCylinder = async (parent: SVGAElement, node: Node) => {
const h = (node?.height ? node?.height : bbox.height) + ry + labelPaddingY; const h = (node?.height ? node?.height : bbox.height) + ry + labelPaddingY;
const outerOffset = h * 0.1; // 10% of height const outerOffset = h * 0.1; // 10% of height
let cylinder: d3.Selection<SVGPathElement | SVGGElement, unknown, null, undefined>; let cylinder: typeof shapeSvg | D3Selection<SVGPathElement>;
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry, outerOffset); const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry, outerOffset);
const innerPathData = createInnerCylinderPathD(0, ry, w, h, rx, ry); const innerPathData = createInnerCylinderPathD(0, ry, w, h, rx, ry);
@@ -114,7 +120,7 @@ export const linedCylinder = async (parent: SVGAElement, node: Node) => {
.insert('path', ':first-child') .insert('path', ':first-child')
.attr('d', pathData) .attr('d', pathData)
.attr('class', 'basic label-container') .attr('class', 'basic label-container')
.attr('style', cssStyles) .attr('style', handleUndefinedAttr(cssStyles))
.attr('style', nodeStyles); .attr('style', nodeStyles);
} }
@@ -155,4 +161,4 @@ export const linedCylinder = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -5,11 +5,15 @@ import {
generateFullSineWavePoints, generateFullSineWavePoints,
} from './util.js'; } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import type { D3Selection } from '../../../types.js';
export const linedWaveEdgedRect = async (parent: SVGAElement, node: Node) => { export async function linedWaveEdgedRect<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
@@ -37,6 +41,7 @@ export const linedWaveEdgedRect = async (parent: SVGAElement, node: Node) => {
const finalH = h + waveAmplitude; const finalH = h + waveAmplitude;
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -93,4 +98,4 @@ export const linedWaveEdgedRect = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,10 +1,11 @@
import { labelHelper, getNodeClasses, updateNodeBounds, createPathFromPoints } from './util.js'; import { labelHelper, getNodeClasses, updateNodeBounds, createPathFromPoints } from './util.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { D3Selection } from '../../../types.js';
export const multiRect = async (parent: SVGAElement, node: Node) => { export async function multiRect<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -34,6 +35,7 @@ export const multiRect = async (parent: SVGAElement, node: Node) => {
const y = -h / 2; const y = -h / 2;
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -98,4 +100,4 @@ export const multiRect = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -6,11 +6,15 @@ import {
generateFullSineWavePoints, generateFullSineWavePoints,
} from './util.js'; } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import type { D3Selection } from '../../../types.js';
export const multiWaveEdgedRectangle = async (parent: SVGAElement, node: Node) => { export async function multiWaveEdgedRectangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node)); const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
@@ -84,6 +88,7 @@ export const multiWaveEdgedRectangle = async (parent: SVGAElement, node: Node) =
{ x, y }, { x, y },
]; ];
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -126,4 +131,4 @@ export const multiWaveEdgedRectangle = async (parent: SVGAElement, node: Node) =
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -3,12 +3,13 @@ import type { Node, ShapeRenderOptions } from '../../types.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js'; import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
export const note = async ( export async function note<T extends SVGGraphicsElement>(
parent: SVGAElement, parent: D3Selection<T>,
node: Node, node: Node,
{ config: { themeVariables } }: ShapeRenderOptions { config: { themeVariables } }: ShapeRenderOptions
) => { ) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node)); const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
@@ -55,4 +56,4 @@ export const note = async (
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -5,6 +5,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js'; import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
export const createDecisionBoxPathD = (x: number, y: number, size: number): string => { export const createDecisionBoxPathD = (x: number, y: number, size: number): string => {
return [ return [
@@ -16,7 +17,7 @@ export const createDecisionBoxPathD = (x: number, y: number, size: number): stri
].join(' '); ].join(' ');
}; };
export const question = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => { export async function question<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const padding = (node.padding ?? 0) * 4; const padding = (node.padding ?? 0) * 4;
@@ -44,10 +45,11 @@ export const question = async (parent: SVGAElement, node: Node): Promise<SVGAEle
{ x: 0, y: -s / 2 }, { x: 0, y: -s / 2 },
]; ];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>; let polygon: typeof shapeSvg | ReturnType<typeof insertPolygonShape>;
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
const pathData = createDecisionBoxPathD(0, 0, s); const pathData = createDecisionBoxPathD(0, 0, s);
@@ -83,4 +85,4 @@ export const question = async (parent: SVGAElement, node: Node): Promise<SVGAEle
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -3,11 +3,12 @@ import intersect from '../intersect/index.js';
import type { Node } from '../../types.js'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
export const rect_left_inv_arrow = async ( export async function rect_left_inv_arrow<T extends SVGGraphicsElement>(
parent: SVGAElement, parent: D3Selection<T>,
node: Node node: Node
): Promise<SVGAElement> => { ) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -36,6 +37,7 @@ export const rect_left_inv_arrow = async (
]; ];
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -71,4 +73,4 @@ export const rect_left_inv_arrow = async (
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -9,8 +9,12 @@ import rough from 'roughjs';
import { getConfig } from '../../../diagram-api/diagramAPI.js'; import { getConfig } from '../../../diagram-api/diagramAPI.js';
import { createRoundedRectPathD } from './roundedRectPath.js'; import { createRoundedRectPathD } from './roundedRectPath.js';
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import type { D3Selection } from '../../../types.js';
export const rectWithTitle = async (parent: SVGElement, node: Node) => { export async function rectWithTitle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
let classes; let classes;
@@ -36,7 +40,7 @@ export const rectWithTitle = async (parent: SVGElement, node: Node) => {
const title = node.label; const title = node.label;
const text = label.node().appendChild(await createLabel(title, node.labelStyle, true, true)); const text = label.node()!.appendChild(await createLabel(title, node.labelStyle, true, true));
let bbox = { width: 0, height: 0 }; let bbox = { width: 0, height: 0 };
if (evaluate(getConfig()?.flowchart?.htmlLabels)) { if (evaluate(getConfig()?.flowchart?.htmlLabels)) {
const div = text.children[0]; const div = text.children[0];
@@ -49,7 +53,7 @@ export const rectWithTitle = async (parent: SVGElement, node: Node) => {
const textRows = description || []; const textRows = description || [];
const titleBox = text.getBBox(); const titleBox = text.getBBox();
const descr = label const descr = label
.node() .node()!
.appendChild( .appendChild(
await createLabel( await createLabel(
textRows.join ? textRows.join('<br/>') : textRows, textRows.join ? textRows.join('<br/>') : textRows,
@@ -87,7 +91,7 @@ export const rectWithTitle = async (parent: SVGElement, node: Node) => {
// Get the size of the label // Get the size of the label
// Bounding box for title and text // Bounding box for title and text
bbox = label.node().getBBox(); bbox = label.node()!.getBBox();
// Center the label // Center the label
label.attr( label.attr(
@@ -151,4 +155,4 @@ export const rectWithTitle = async (parent: SVGElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,7 +1,11 @@
import type { Node, RectOptions } from '../../types.js'; import type { Node, RectOptions } from '../../types.js';
import type { D3Selection } from '../../../types.js';
import { drawRect } from './drawRect.js'; import { drawRect } from './drawRect.js';
export const roundedRect = async (parent: SVGAElement, node: Node) => { export async function roundedRect<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
const options = { const options = {
rx: node.look === 'neo' ? 3 : 5, rx: node.look === 'neo' ? 3 : 5,
@@ -13,4 +17,4 @@ export const roundedRect = async (parent: SVGAElement, node: Node) => {
} as RectOptions; } as RectOptions;
return drawRect(parent, node, options); return drawRect(parent, node, options);
}; }

View File

@@ -1,13 +1,18 @@
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
/// Width of the frame on the left of the shape /// Width of the frame on the left of the shape
const FRAME_WIDTH = 8; const FRAME_WIDTH = 8;
export const shadedProcess = async (parent: SVGAElement, node: Node) => { export async function shadedProcess<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
@@ -33,6 +38,7 @@ export const shadedProcess = async (parent: SVGAElement, node: Node) => {
const y = -totalHeight / 2; const y = -totalHeight / 2;
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -58,7 +64,7 @@ export const shadedProcess = async (parent: SVGAElement, node: Node) => {
const rect = shapeSvg.insert(() => roughNode, ':first-child'); const rect = shapeSvg.insert(() => roughNode, ':first-child');
rect.attr('class', 'basic label-container').attr('style', cssStyles); rect.attr('class', 'basic label-container').attr('style', handleUndefinedAttr(cssStyles));
if (nodeStyles && node.look !== 'handDrawn') { if (nodeStyles && node.look !== 'handDrawn') {
rect.selectAll('path').attr('style', nodeStyles); rect.selectAll('path').attr('style', nodeStyles);
@@ -75,4 +81,4 @@ export const shadedProcess = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,10 +1,11 @@
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
export const slopedRect = async (parent: SVGAElement, node: Node) => { export async function slopedRect<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -32,6 +33,7 @@ export const slopedRect = async (parent: SVGAElement, node: Node) => {
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -75,4 +77,4 @@ export const slopedRect = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,7 +1,8 @@
import type { Node, RectOptions } from '../../types.js'; import type { Node, RectOptions } from '../../types.js';
import type { D3Selection } from '../../../types.js';
import { drawRect } from './drawRect.js'; import { drawRect } from './drawRect.js';
export const squareRect = async (parent: SVGAElement, node: Node) => { export async function squareRect<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const options = { const options = {
rx: 0, rx: 0,
ry: 0, ry: 0,
@@ -10,4 +11,4 @@ export const squareRect = async (parent: SVGAElement, node: Node) => {
labelPaddingY: (node?.padding || 0) * 1, labelPaddingY: (node?.padding || 0) * 1,
} as RectOptions; } as RectOptions;
return drawRect(parent, node, options); return drawRect(parent, node, options);
}; }

View File

@@ -4,6 +4,8 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { createRoundedRectPathD } from './roundedRectPath.js'; import { createRoundedRectPathD } from './roundedRectPath.js';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
export const createStadiumPathD = ( export const createStadiumPathD = (
x: number, x: number,
@@ -50,7 +52,7 @@ export const createStadiumPathD = (
].join(' '); ].join(' ');
}; };
export const stadium = async (parent: SVGAElement, node: Node) => { export async function stadium<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -82,6 +84,7 @@ export const stadium = async (parent: SVGAElement, node: Node) => {
let rect; let rect;
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -89,7 +92,7 @@ export const stadium = async (parent: SVGAElement, node: Node) => {
const roughNode = rc.path(pathData, options); const roughNode = rc.path(pathData, options);
rect = shapeSvg.insert(() => roughNode, ':first-child'); rect = shapeSvg.insert(() => roughNode, ':first-child');
rect.attr('class', 'basic label-container').attr('style', cssStyles); rect.attr('class', 'basic label-container').attr('style', handleUndefinedAttr(cssStyles));
} else { } else {
rect = shapeSvg.insert('rect', ':first-child'); rect = shapeSvg.insert('rect', ':first-child');
@@ -111,4 +114,4 @@ export const stadium = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,11 +1,12 @@
import type { Node, RectOptions } from '../../types.js'; import type { Node, RectOptions } from '../../types.js';
import type { D3Selection } from '../../../types.js';
import { drawRect } from './drawRect.js'; import { drawRect } from './drawRect.js';
export const state = async (parent: SVGAElement, node: Node) => { export async function state<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const options = { const options = {
rx: node.look === 'neo' ? 3 : 5, rx: node.look === 'neo' ? 3 : 5,
ry: node.look === 'neo' ? 3 : 5, ry: node.look === 'neo' ? 3 : 5,
classes: 'flowchart-node', classes: 'flowchart-node',
} as RectOptions; } as RectOptions;
return drawRect(parent, node, options); return drawRect(parent, node, options);
}; }

View File

@@ -1,15 +1,15 @@
import rough from 'roughjs'; import rough from 'roughjs';
import type { SVG } from '../../../diagram-api/types.js';
import type { Node, ShapeRenderOptions } from '../../types.js'; import type { Node, ShapeRenderOptions } from '../../types.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { updateNodeBounds } from './util.js'; import { updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
export const stateEnd = ( export function stateEnd<T extends SVGGraphicsElement>(
parent: SVG, parent: D3Selection<T>,
node: Node, node: Node,
{ config: { themeVariables } }: ShapeRenderOptions { config: { themeVariables } }: ShapeRenderOptions
) => { ) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const { cssStyles } = node; const { cssStyles } = node;
@@ -83,4 +83,4 @@ export const stateEnd = (
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,15 +1,15 @@
import rough from 'roughjs'; import rough from 'roughjs';
import type { SVG } from '../../../diagram-api/types.js';
import type { Node, ShapeRenderOptions } from '../../types.js'; import type { Node, ShapeRenderOptions } from '../../types.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { solidStateFill } from './handDrawnShapeStyles.js'; import { solidStateFill } from './handDrawnShapeStyles.js';
import { updateNodeBounds } from './util.js'; import { updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
export const stateStart = ( export function stateStart<T extends SVGGraphicsElement>(
parent: SVG, parent: D3Selection<T>,
node: Node, node: Node,
{ config: { themeVariables } }: ShapeRenderOptions { config: { themeVariables } }: ShapeRenderOptions
) => { ) {
const { lineColor } = themeVariables; const { lineColor } = themeVariables;
// If incoming height & width are present, subtract the padding from them // If incoming height & width are present, subtract the padding from them
@@ -39,25 +39,29 @@ export const stateStart = (
.attr('class', 'node default') .attr('class', 'node default')
.attr('id', node.domId || node.id); .attr('id', node.domId || node.id);
let circle: d3.Selection<SVGCircleElement, unknown, Element | null, unknown>; let circle: D3Selection<SVGCircleElement> | D3Selection<SVGGElement>;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-ignore TODO: Fix rough typings // @ts-ignore TODO: Fix rough typings
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const roughNode = rc.circle(0, 0, node.width, solidStateFill(lineColor)); const roughNode = rc.circle(0, 0, node.width, solidStateFill(lineColor));
// @ts-ignore TODO: Fix typings // @ts-ignore TODO: Fix typings
circle = shapeSvg.insert(() => roughNode); circle = shapeSvg.insert(() => roughNode);
// center the circle around its coordinate
circle
.attr('class', 'state-start')
.attr('r', (node.width ?? 7) / 2)
.attr('width', node.width ?? 14)
.attr('height', node.height ?? 14);
} else { } else {
circle = shapeSvg.insert('circle', ':first-child'); circle = shapeSvg.insert('circle', ':first-child');
// center the circle around its coordinate
circle
.attr('class', 'state-start')
.attr('r', (node.width ?? 7) / 2)
.attr('width', node.width ?? 14)
.attr('height', node.height ?? 14);
} }
// center the circle around its coordinate
// @ts-ignore TODO: Fix typings
circle
.attr('class', 'state-start')
.attr('r', (node.width ?? 7) / 2)
.attr('width', node.width ?? 14)
.attr('height', node.height ?? 14);
updateNodeBounds(node, circle); updateNodeBounds(node, circle);
node.intersect = function (point) { node.intersect = function (point) {
@@ -65,4 +69,4 @@ export const stateStart = (
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -5,6 +5,8 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js'; import { insertPolygonShape } from './insertPolygonShape.js';
import { getConfig } from '../../../config.js'; import { getConfig } from '../../../config.js';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
export const createSubroutinePathD = ( export const createSubroutinePathD = (
x: number, x: number,
@@ -35,7 +37,7 @@ export const createSubroutinePathD = (
// width of the frame on the left and right side of the shape // width of the frame on the left and right side of the shape
const FRAME_WIDTH = 8; const FRAME_WIDTH = 8;
export const subroutine = async (parent: SVGAElement, node: Node) => { export async function subroutine<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { themeVariables } = getConfig(); const { themeVariables } = getConfig();
const { useGradient } = themeVariables; const { useGradient } = themeVariables;
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
@@ -78,6 +80,7 @@ export const subroutine = async (parent: SVGAElement, node: Node) => {
]; ];
if (node.look === 'handDrawn' || (node.look === 'neo' && !useGradient)) { if (node.look === 'handDrawn' || (node.look === 'neo' && !useGradient)) {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -96,7 +99,7 @@ export const subroutine = async (parent: SVGAElement, node: Node) => {
l2El.attr('class', 'neo-line'); l2El.attr('class', 'neo-line');
const rect = shapeSvg.insert(() => roughNode, ':first-child'); const rect = shapeSvg.insert(() => roughNode, ':first-child');
const { cssStyles } = node; const { cssStyles } = node;
rect.attr('class', 'basic label-container').attr('style', cssStyles); rect.attr('class', 'basic label-container').attr('style', handleUndefinedAttr(cssStyles));
updateNodeBounds(node, rect); updateNodeBounds(node, rect);
} else { } else {
const el = insertPolygonShape(shapeSvg, w, h, points); const el = insertPolygonShape(shapeSvg, w, h, points);
@@ -111,4 +114,4 @@ export const subroutine = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,13 +1,14 @@
import { labelHelper, getNodeClasses, updateNodeBounds, createPathFromPoints } from './util.js'; import { labelHelper, getNodeClasses, updateNodeBounds, createPathFromPoints } from './util.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { D3Selection } from '../../../types.js';
/// The width/height of the tag in comparison to the height of the node /// The width/height of the tag in comparison to the height of the node
const TAG_RATIO = 0.2; const TAG_RATIO = 0.2;
export const taggedRect = async (parent: SVGAElement, node: Node) => { export async function taggedRect<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
@@ -41,6 +42,7 @@ export const taggedRect = async (parent: SVGAElement, node: Node) => {
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -90,4 +92,4 @@ export const taggedRect = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -6,11 +6,15 @@ import {
createPathFromPoints, createPathFromPoints,
} from './util.js'; } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import type { D3Selection } from '../../../types.js';
export const taggedWaveEdgedRectangle = async (parent: SVGAElement, node: Node) => { export async function taggedWaveEdgedRectangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
@@ -46,6 +50,7 @@ export const taggedWaveEdgedRectangle = async (parent: SVGAElement, node: Node)
const widthDif = minWidth - w; const widthDif = minWidth - w;
const extraW = widthDif > 0 ? widthDif / 2 : 0; const extraW = widthDif > 0 ? widthDif / 2 : 0;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -120,4 +125,4 @@ export const taggedWaveEdgedRectangle = async (parent: SVGAElement, node: Node)
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,9 +1,10 @@
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String } from './handDrawnShapeStyles.js'; import { styles2String } from './handDrawnShapeStyles.js';
import type { D3Selection } from '../../../types.js';
export async function text(parent: SVGAElement, node: Node): Promise<SVGAElement> { export async function text<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;

View File

@@ -1,8 +1,10 @@
import { labelHelper, getNodeClasses, updateNodeBounds } from './util.js'; import { labelHelper, getNodeClasses, updateNodeBounds } from './util.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
export const createCylinderPathD = ( export const createCylinderPathD = (
x: number, x: number,
@@ -52,7 +54,10 @@ export const createInnerCylinderPathD = (
const MIN_HEIGHT = 5; const MIN_HEIGHT = 5;
const MIN_WIDTH = 10; const MIN_WIDTH = 10;
export const tiltedCylinder = async (parent: SVGAElement, node: Node) => { export async function tiltedCylinder<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -80,9 +85,10 @@ export const tiltedCylinder = async (parent: SVGAElement, node: Node) => {
const w = (node.width ? node.width : bbox.width) + rx + labelPadding; const w = (node.width ? node.width : bbox.width) + rx + labelPadding;
const { cssStyles } = node; const { cssStyles } = node;
let cylinder: d3.Selection<SVGPathElement | SVGGElement, unknown, null, undefined>; let cylinder: D3Selection<SVGGElement> | D3Selection<SVGPathElement>;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry); const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry);
const innerPathData = createInnerCylinderPathD(0, 0, w, h, rx, ry); const innerPathData = createInnerCylinderPathD(0, 0, w, h, rx, ry);
@@ -100,19 +106,19 @@ export const tiltedCylinder = async (parent: SVGAElement, node: Node) => {
.insert('path', ':first-child') .insert('path', ':first-child')
.attr('d', pathData) .attr('d', pathData)
.attr('class', 'basic label-container') .attr('class', 'basic label-container')
.attr('style', cssStyles) .attr('style', handleUndefinedAttr(cssStyles))
.attr('style', nodeStyles); .attr('style', nodeStyles);
cylinder.attr('class', 'basic label-container');
if (cssStyles) {
cylinder.selectAll('path').attr('style', cssStyles);
}
if (nodeStyles) {
cylinder.selectAll('path').attr('style', nodeStyles);
}
} }
cylinder.attr('class', 'basic label-container');
if (cssStyles && node.look !== 'handDrawn') {
cylinder.selectAll('path').attr('style', cssStyles);
}
if (nodeStyles && node.look !== 'handDrawn') {
cylinder.selectAll('path').attr('style', nodeStyles);
}
cylinder.attr('label-offset-x', rx); cylinder.attr('label-offset-x', rx);
cylinder.attr('transform', `translate(${-w / 2}, ${h / 2} )`); cylinder.attr('transform', `translate(${-w / 2}, ${h / 2} )`);
@@ -149,4 +155,4 @@ export const tiltedCylinder = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -4,6 +4,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js'; import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
// export const createTrapezoidPathD = ( // export const createTrapezoidPathD = (
// x: number, // x: number,
@@ -20,7 +21,7 @@ import { insertPolygonShape } from './insertPolygonShape.js';
// ].join(' '); // ].join(' ');
// }; // };
export const trapezoid = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => { export async function trapezoid<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -51,10 +52,11 @@ export const trapezoid = async (parent: SVGAElement, node: Node): Promise<SVGAEl
{ x: 0, y: -h }, { x: 0, y: -h },
]; ];
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>; let polygon: typeof shapeSvg | ReturnType<typeof insertPolygonShape>;
const { cssStyles } = node; const { cssStyles } = node;
if (node.look === 'handDrawn') { if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
const pathData = createPathFromPoints(points); const pathData = createPathFromPoints(points);
@@ -85,4 +87,4 @@ export const trapezoid = async (parent: SVGAElement, node: Node): Promise<SVGAEl
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,10 +1,14 @@
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
export const trapezoidalPentagon = async (parent: SVGAElement, node: Node) => { export async function trapezoidalPentagon<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -31,6 +35,7 @@ export const trapezoidalPentagon = async (parent: SVGAElement, node: Node) => {
(node?.height ? node?.height : Math.max(minHeight, bbox.height)) + (labelPaddingY ?? 0) * 2; (node?.height ? node?.height : Math.max(minHeight, bbox.height)) + (labelPaddingY ?? 0) * 2;
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -70,4 +75,4 @@ export const trapezoidalPentagon = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,17 +1,18 @@
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js'; import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { createPathFromPoints } from './util.js'; import { createPathFromPoints } from './util.js';
import { evaluate } from '../../../diagrams/common/common.js'; import { evaluate } from '../../../diagrams/common/common.js';
import { getConfig } from '../../../diagram-api/diagramAPI.js'; import { getConfig } from '../../../diagram-api/diagramAPI.js';
import type { D3Selection } from '../../../types.js';
const MIN_HEIGHT = 10; const MIN_HEIGHT = 10;
const MIN_WIDTH = 10; const MIN_WIDTH = 10;
export const triangle = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => { export async function triangle<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -40,6 +41,7 @@ export const triangle = async (parent: SVGAElement, node: Node): Promise<SVGAEle
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
if (node.look !== 'handDrawn') { if (node.look !== 'handDrawn') {
@@ -77,4 +79,4 @@ export const triangle = async (parent: SVGAElement, node: Node): Promise<SVGAEle
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,13 +1,19 @@
import { createText } from '../../createText.js'; import { createText } from '../../createText.js';
import type { Node } from '../../types.js';
import { getConfig } from '../../../diagram-api/diagramAPI.js'; import { getConfig } from '../../../diagram-api/diagramAPI.js';
import { select } from 'd3'; import { select } from 'd3';
import { evaluate, sanitizeText } from '../../../diagrams/common/common.js'; import { evaluate, sanitizeText } from '../../../diagrams/common/common.js';
import { decodeEntities } from '../../../utils.js';
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import { decodeEntities, handleUndefinedAttr } from '../../../utils.js';
import type { D3Selection, Point } from '../../../types.js';
export const labelHelper = async (parent, node, _classes) => { export const labelHelper = async <T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node,
_classes?: string
) => {
let cssClasses; let cssClasses;
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig().flowchart.htmlLabels); const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig()?.flowchart?.htmlLabels);
if (!_classes) { if (!_classes) {
cssClasses = 'node default'; cssClasses = 'node default';
} else { } else {
@@ -21,7 +27,10 @@ export const labelHelper = async (parent, node, _classes) => {
.attr('id', node.domId || node.id); .attr('id', node.domId || node.id);
// Create the label and insert it after the rect // Create the label and insert it after the rect
const labelEl = shapeSvg.insert('g').attr('class', 'label').attr('style', node.labelStyle); const labelEl = shapeSvg
.insert('g')
.attr('class', 'label')
.attr('style', handleUndefinedAttr(node.labelStyle));
// Replace label with default value if undefined // Replace label with default value if undefined
let label; let label;
@@ -31,19 +40,19 @@ export const labelHelper = async (parent, node, _classes) => {
label = typeof node.label === 'string' ? node.label : node.label[0]; label = typeof node.label === 'string' ? node.label : node.label[0];
} }
let text; const text = await createText(labelEl, sanitizeText(decodeEntities(label), getConfig()), {
text = await createText(labelEl, sanitizeText(decodeEntities(label), getConfig()), {
useHtmlLabels, useHtmlLabels,
width: node.width < 0 ? 0 : node.width || getConfig().flowchart.wrappingWidth, width: (node.width ?? 0) < 0 ? 0 : node.width || getConfig().flowchart?.wrappingWidth,
// @ts-expect-error -- This is currently not used. Should this be `classes` instead?
cssClasses: 'markdown-node-label', cssClasses: 'markdown-node-label',
style: node.labelStyle, style: node.labelStyle,
addSvgBackground: !!node.icon || !!node.img, addSvgBackground: !!node.icon || !!node.img,
}); });
// Get the size of the label // Get the size of the label
let bbox = text.getBBox(); let bbox = text.getBBox();
const halfPadding = node.padding / 2; const halfPadding = (node?.padding ?? 0) / 2;
if (evaluate(getConfig().flowchart.htmlLabels)) { if (evaluate(getConfig().flowchart?.htmlLabels)) {
const div = text.children[0]; const div = text.children[0];
const dv = select(text); const dv = select(text);
@@ -69,7 +78,11 @@ export const labelHelper = async (parent, node, _classes) => {
? getConfig().fontSize ? getConfig().fontSize
: window.getComputedStyle(document.body).fontSize; : window.getComputedStyle(document.body).fontSize;
const enlargingFactor = 5; const enlargingFactor = 5;
const width = parseInt(bodyFontSize, 10) * enlargingFactor + 'px'; const parsedBodyFontSize =
typeof bodyFontSize === 'number'
? bodyFontSize
: parseInt(bodyFontSize ?? '', 10);
const width = parsedBodyFontSize * enlargingFactor + 'px';
img.style.minWidth = width; img.style.minWidth = width;
img.style.maxWidth = width; img.style.maxWidth = width;
} else { } else {
@@ -109,8 +122,12 @@ export const labelHelper = async (parent, node, _classes) => {
return { shapeSvg, bbox, halfPadding, label: labelEl }; return { shapeSvg, bbox, halfPadding, label: labelEl };
}; };
export const updateNodeBounds = (node, element) => { export const updateNodeBounds = <T extends SVGGraphicsElement>(
const bbox = element.node().getBBox(); node: Node,
// D3Selection<SVGGElement> is for the roughjs case, D3Selection<T> is for the non-roughjs case
element: D3Selection<SVGGElement> | D3Selection<T>
) => {
const bbox = element.node()!.getBBox();
node.width = bbox.width; node.width = bbox.width;
node.height = bbox.height; node.height = bbox.height;
log.debug('updateNodeBounds: #####################################'); log.debug('updateNodeBounds: #####################################');
@@ -119,12 +136,17 @@ export const updateNodeBounds = (node, element) => {
}; };
/** /**
* @param parent * @param parent - Parent element to append the polygon to
* @param w * @param w - Width of the polygon
* @param h * @param h - Height of the polygon
* @param points * @param points - Array of points to create the polygon
*/ */
export function insertPolygonShape(parent, w, h, points) { export function insertPolygonShape(
parent: D3Selection<SVGGElement>,
w: number,
h: number,
points: Point[]
) {
return parent return parent
.insert('polygon', ':first-child') .insert('polygon', ':first-child')
.attr( .attr(
@@ -139,16 +161,23 @@ export function insertPolygonShape(parent, w, h, points) {
.attr('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')'); .attr('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')');
} }
export const getNodeClasses = (node, extra) => export const getNodeClasses = (node: Node, extra?: string) =>
(node.look === 'handDrawn' ? 'rough-node' : 'node') + ' ' + node.cssClasses + ' ' + (extra || ''); (node.look === 'handDrawn' ? 'rough-node' : 'node') + ' ' + node.cssClasses + ' ' + (extra || '');
export function createPathFromPoints(points) { export function createPathFromPoints(points: Point[]) {
const pointStrings = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`); const pointStrings = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`);
pointStrings.push('Z'); pointStrings.push('Z');
return pointStrings.join(' '); return pointStrings.join(' ');
} }
export function generateFullSineWavePoints(x1, y1, x2, y2, amplitude, numCycles) { export function generateFullSineWavePoints(
x1: number,
y1: number,
x2: number,
y2: number,
amplitude: number,
numCycles: number
) {
const points = []; const points = [];
const steps = 50; // Number of segments to create a smooth curve const steps = 50; // Number of segments to create a smooth curve
const deltaX = x2 - x1; const deltaX = x2 - x1;
@@ -170,13 +199,21 @@ export function generateFullSineWavePoints(x1, y1, x2, y2, amplitude, numCycles)
return points; return points;
} }
/**
* @param centerX - x-coordinate of center of circle
* @param centerY - y-coordinate of center of circle
* @param radius - radius of circle
* @param numPoints - total points required
* @param startAngle - angle where arc will start
* @param endAngle - angle where arc will end
*/
export function generateCirclePoints( export function generateCirclePoints(
centerX, // x-coordinate of center of circle centerX: number,
centerY, // x-coordinate of center of circle centerY: number,
radius, // radius of circle radius: number,
numPoints, // total points required numPoints: number,
startAngle, // angle where arc will start startAngle: number,
endAngle // angle where arc will end endAngle: number
) { ) {
const points = []; const points = [];

View File

@@ -6,11 +6,15 @@ import {
createPathFromPoints, createPathFromPoints,
} from './util.js'; } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import rough from 'roughjs'; import rough from 'roughjs';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import type { D3Selection } from '../../../types.js';
export const waveEdgedRectangle = async (parent: SVGAElement, node: Node) => { export async function waveEdgedRectangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
@@ -44,6 +48,7 @@ export const waveEdgedRectangle = async (parent: SVGAElement, node: Node) => {
const widthDif = minWidth - w; const widthDif = minWidth - w;
const extraW = widthDif > 0 ? widthDif / 2 : 0; const extraW = widthDif > 0 ? widthDif / 2 : 0;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -94,4 +99,4 @@ export const waveEdgedRectangle = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -6,11 +6,15 @@ import {
generateFullSineWavePoints, generateFullSineWavePoints,
} from './util.js'; } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
export const waveRectangle = async (parent: SVGAElement, node: Node) => { export async function waveRectangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const nodePadding = node.padding ?? 0; const nodePadding = node.padding ?? 0;
@@ -43,6 +47,7 @@ export const waveRectangle = async (parent: SVGAElement, node: Node) => {
const finalH = h + waveAmplitude * 2; const finalH = h + waveAmplitude * 2;
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -83,4 +88,4 @@ export const waveRectangle = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -1,13 +1,14 @@
import { labelHelper, getNodeClasses, updateNodeBounds } from './util.js'; import { labelHelper, getNodeClasses, updateNodeBounds } from './util.js';
import type { Node } from '../../types.ts'; import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { D3Selection } from '../../../types.js';
/// Width of the frame on the top and left of the shape /// Width of the frame on the top and left of the shape
const rectOffset = 5; const rectOffset = 5;
export const windowPane = async (parent: SVGAElement, node: Node) => { export async function windowPane<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
@@ -34,6 +35,7 @@ export const windowPane = async (parent: SVGAElement, node: Node) => {
const y = -h / 2; const y = -h / 2;
const { cssStyles } = node; const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {}); const options = userNodeOverrides(node, {});
@@ -81,4 +83,4 @@ export const windowPane = async (parent: SVGAElement, node: Node) => {
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@@ -147,5 +147,6 @@ export type LayoutMethod =
export interface ShapeRenderOptions { export interface ShapeRenderOptions {
config: MermaidConfig; config: MermaidConfig;
dir: string; /** Some shapes render differently if a diagram has a direction `LR` */
dir?: Node['dir'];
} }

View File

@@ -79,6 +79,11 @@ export interface ParseResult {
// This makes it clear that we're working with a d3 selected element of some kind, even though it's hard to specify the exact type. // This makes it clear that we're working with a d3 selected element of some kind, even though it's hard to specify the exact type.
export type D3Element = any; export type D3Element = any;
/**
* Helper type for d3 selections.
*/
export type D3Selection<T extends SVGElement> = d3.Selection<T, unknown, Element | null, unknown>;
export interface RenderResult { export interface RenderResult {
/** /**
* The svg code for the rendered graph. * The svg code for the rendered graph.

View File

@@ -1,5 +1,5 @@
import { sanitizeUrl } from '@braintree/sanitize-url'; import { sanitizeUrl } from '@braintree/sanitize-url';
import type { CurveFactory } from 'd3'; import type { BaseType, CurveFactory } from 'd3';
import { import {
curveBasis, curveBasis,
curveBasisClosed, curveBasisClosed,
@@ -940,3 +940,15 @@ export const getEdgeId = (
) => { ) => {
return `${prefix ? `${prefix}_` : ''}${from}_${to}_${counter}${suffix ? `_${suffix}` : ''}`; return `${prefix ? `${prefix}_` : ''}${from}_${to}_${counter}${suffix ? `_${suffix}` : ''}`;
}; };
/**
* D3's `selection.attr` method doesn't officially support `undefined`.
*
* However, it seems if you do pass `undefined`, it seems to be treated as `null`
* (e.g. it removes the attribute).
*/
export function handleUndefinedAttr(
attrValue: Parameters<d3.Selection<BaseType, unknown, HTMLElement, any>['attr']>[1] | undefined
) {
return attrValue ?? null;
}