mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-28 19:59:43 +02:00
Adding new-shapes and picking up node data from node data statement
This commit is contained in:
@@ -120,10 +120,31 @@ export const addVertex = function (
|
||||
}
|
||||
|
||||
if (shapeData !== undefined) {
|
||||
console.log('HERE - shapeData: ', shapeData);
|
||||
// const doc = yaml.load(shapeData);
|
||||
// console.log('doc', doc);
|
||||
// vertex.type = shapeData?.shape || 'roundedRect';
|
||||
let yamlData;
|
||||
// detect if shapeData contains a newline character
|
||||
|
||||
if (!shapeData.includes('\n')) {
|
||||
console.log('yamlData shapeData has no new lines', shapeData);
|
||||
yamlData = '{\n' + shapeData + '\n';
|
||||
} else {
|
||||
console.log('yamlData shapeData has new lines', shapeData);
|
||||
yamlData = shapeData + '\n';
|
||||
// Find the position of the last } and replace it with a newline
|
||||
const lastPos = yamlData.lastIndexOf('}');
|
||||
if (lastPos !== -1) {
|
||||
yamlData = yamlData.substring(0, lastPos) + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
console.log('yamlData', yamlData);
|
||||
const doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA });
|
||||
console.log('yamlData doc', doc);
|
||||
if (doc?.shape) {
|
||||
vertex.type = doc?.shape;
|
||||
}
|
||||
if (doc?.label) {
|
||||
vertex.text = doc?.label;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -75,13 +75,20 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.node .label {
|
||||
.rough-node .label,.node .label {
|
||||
text-align: center;
|
||||
}
|
||||
.node.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.root .anchor path {
|
||||
fill: ${options.lineColor} !important;
|
||||
stroke-width: 0;
|
||||
stroke: ${options.lineColor};
|
||||
}
|
||||
|
||||
.arrowheadPath {
|
||||
fill: ${options.arrowheadColor};
|
||||
}
|
||||
@@ -151,6 +158,11 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
||||
font-size: 18px;
|
||||
fill: ${options.textColor};
|
||||
}
|
||||
|
||||
rect.text {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export default getStyles;
|
||||
|
@@ -17,6 +17,10 @@ import { doublecircle } from './shapes/doubleCircle.js';
|
||||
import { rect_left_inv_arrow } from './shapes/rectLeftInvArrow.js';
|
||||
import { question } from './shapes/question.js';
|
||||
import { hexagon } from './shapes/hexagon.js';
|
||||
import { text } from './shapes/text.js';
|
||||
import { card } from './shapes/card.js';
|
||||
import { shadedProcess } from './shapes/shadedProcess.js';
|
||||
import { anchor } from './shapes/anchor.js';
|
||||
import { lean_right } from './shapes/leanRight.js';
|
||||
import { lean_left } from './shapes/leanLeft.js';
|
||||
import { trapezoid } from './shapes/trapezoid.js';
|
||||
@@ -47,6 +51,10 @@ const shapes = {
|
||||
trapezoid,
|
||||
inv_trapezoid,
|
||||
labelRect,
|
||||
text,
|
||||
card,
|
||||
shadedProcess,
|
||||
anchor,
|
||||
};
|
||||
|
||||
const nodeElems = new Map();
|
||||
|
@@ -0,0 +1,61 @@
|
||||
import { log } from '$root/logger.js';
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '$root/rendering-util/types.d.ts';
|
||||
import {
|
||||
styles2String,
|
||||
userNodeOverrides,
|
||||
} from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
export const anchor = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
// const { shapeSvg, bbox, halfPadding } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const classes = getNodeClasses(node);
|
||||
let cssClasses = classes;
|
||||
if (!classes) {
|
||||
cssClasses = 'anchor';
|
||||
}
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', cssClasses)
|
||||
.attr('id', node.domId || node.id);
|
||||
|
||||
const radius = 1;
|
||||
let circleElem;
|
||||
const { cssStyles } = node;
|
||||
|
||||
// if (node.look === 'handdrawn') {
|
||||
// @ts-ignore - rough is not typed
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, { fill: 'black', stroke: 'none', fillStyle: 'solid' });
|
||||
|
||||
if (node.look !== 'handdrawn') {
|
||||
options.roughness = 0;
|
||||
}
|
||||
const roughNode = rc.circle(0, 0, radius * 2, options);
|
||||
|
||||
console.log('IPI roughNode:', options);
|
||||
|
||||
circleElem = shapeSvg.insert(() => roughNode, ':first-child');
|
||||
circleElem.attr('class', 'anchor').attr('style', cssStyles);
|
||||
// } else {
|
||||
// circleElem = shapeSvg
|
||||
// .insert('circle', ':first-child')
|
||||
// .attr('class', 'basic label-container')
|
||||
// .attr('style', nodeStyles)
|
||||
// .attr('r', radius)
|
||||
// .attr('cx', 0)
|
||||
// .attr('cy', 0);
|
||||
// }
|
||||
|
||||
updateNodeBounds(node, circleElem);
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('Circle intersect', node, radius, point);
|
||||
return intersect.circle(node, radius, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,76 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '$root/rendering-util/types.d.ts';
|
||||
import {
|
||||
styles2String,
|
||||
userNodeOverrides,
|
||||
} from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
import { insertPolygonShape } from './insertPolygonShape.js';
|
||||
import { createPathFromPoints } from './util.js';
|
||||
|
||||
// const createPathFromPoints = (points: { x: number; y: number }[]): string => {
|
||||
// const pointStrings = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`);
|
||||
// pointStrings.push('Z');
|
||||
// return pointStrings.join(' ');
|
||||
// };
|
||||
|
||||
export async function card(parent: SVGAElement, node: Node): Promise<SVGAElement> {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
|
||||
const f = 4;
|
||||
const h = bbox.height + node.padding;
|
||||
// const m = h / f;
|
||||
const m = 10;
|
||||
const padding = 12;
|
||||
const w = bbox.width + node.padding + padding;
|
||||
const left = 0;
|
||||
const right = w;
|
||||
const top = -h;
|
||||
const bottom = 0;
|
||||
// const w = bbox.width + 2 * m + node.padding;
|
||||
const points = [
|
||||
{ x: left + padding, y: top },
|
||||
{ x: right, y: top },
|
||||
{ x: right, y: bottom },
|
||||
{ x: left, y: bottom },
|
||||
{ x: left, y: top + padding },
|
||||
{ x: left + padding, y: top },
|
||||
];
|
||||
|
||||
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
|
||||
const { cssStyles } = node;
|
||||
|
||||
if (node.look === 'handdrawn') {
|
||||
// @ts-ignore - rough is not typed
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
const pathData = createPathFromPoints(points);
|
||||
const roughNode = rc.path(pathData, options);
|
||||
|
||||
polygon = shapeSvg
|
||||
.insert(() => roughNode, ':first-child')
|
||||
.attr('transform', `translate(${-w / 2}, ${h / 2})`);
|
||||
|
||||
if (cssStyles) {
|
||||
polygon.attr('style', cssStyles);
|
||||
}
|
||||
} else {
|
||||
polygon = insertPolygonShape(shapeSvg, w, h, points);
|
||||
}
|
||||
|
||||
if (nodeStyles) {
|
||||
polygon.attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
updateNodeBounds(node, polygon);
|
||||
|
||||
node.intersect = function (point) {
|
||||
return intersect.polygon(node, points, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
}
|
@@ -0,0 +1,121 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '$root/rendering-util/types.d.ts';
|
||||
import {
|
||||
styles2String,
|
||||
userNodeOverrides,
|
||||
} from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
export const createCylinderPathD = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
rx: number,
|
||||
ry: number
|
||||
): string => {
|
||||
return [
|
||||
`M${x},${y + ry}`,
|
||||
`a${rx},${ry} 0,0,0 ${width},0`,
|
||||
`a${rx},${ry} 0,0,0 ${-width},0`,
|
||||
`l0,${height}`,
|
||||
`a${rx},${ry} 0,0,0 ${width},0`,
|
||||
`l0,${-height}`,
|
||||
].join(' ');
|
||||
};
|
||||
export const createOuterCylinderPathD = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
rx: number,
|
||||
ry: number
|
||||
): string => {
|
||||
return [
|
||||
`M${x},${y + ry}`,
|
||||
`M${x + width},${y + ry}`,
|
||||
`a${rx},${ry} 0,0,0 ${-width},0`,
|
||||
`l0,${height}`,
|
||||
`a${rx},${ry} 0,0,0 ${width},0`,
|
||||
`l0,${-height}`,
|
||||
].join(' ');
|
||||
};
|
||||
export const createInnerCylinderPathD = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
rx: number,
|
||||
ry: number
|
||||
): string => {
|
||||
return [`M${x - width / 2},${-height / 2}`, `a${rx},${ry} 0,0,0 ${width},0`].join(' ');
|
||||
};
|
||||
export const cylinder = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = bbox.width + node.padding;
|
||||
const rx = w / 2;
|
||||
const ry = rx / (2.5 + w / 50);
|
||||
const h = bbox.height + ry + node.padding;
|
||||
|
||||
let cylinder: d3.Selection<SVGPathElement | SVGGElement, unknown, null, undefined>;
|
||||
const { cssStyles } = node;
|
||||
|
||||
if (node.look === 'handdrawn') {
|
||||
// @ts-ignore - rough is not typed
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry);
|
||||
const innerPathData = createInnerCylinderPathD(0, ry, w, h, rx, ry);
|
||||
const outerNode = rc.path(outerPathData, userNodeOverrides(node, {}));
|
||||
const innerLine = rc.path(innerPathData, userNodeOverrides(node, { fill: 'none' }));
|
||||
|
||||
cylinder = shapeSvg.insert(() => innerLine, ':first-child');
|
||||
cylinder = shapeSvg.insert(() => outerNode, ':first-child');
|
||||
cylinder.attr('class', 'basic label-container');
|
||||
if (cssStyles) {
|
||||
cylinder.attr('style', cssStyles);
|
||||
}
|
||||
} else {
|
||||
const pathData = createCylinderPathD(0, 0, w, h, rx, ry);
|
||||
cylinder = shapeSvg
|
||||
.insert('path', ':first-child')
|
||||
.attr('d', pathData)
|
||||
.attr('class', 'basic label-container')
|
||||
.attr('style', cssStyles)
|
||||
.attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
cylinder.attr('label-offset-y', ry);
|
||||
cylinder.attr('transform', `translate(${-w / 2}, ${-(h / 2 + ry)})`);
|
||||
|
||||
updateNodeBounds(node, cylinder);
|
||||
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.rect(node, point);
|
||||
const x = pos.x - (node.x ?? 0);
|
||||
|
||||
if (
|
||||
rx != 0 &&
|
||||
(Math.abs(x) < (node.width ?? 0) / 2 ||
|
||||
(Math.abs(x) == (node.width ?? 0) / 2 &&
|
||||
Math.abs(pos.y - (node.y ?? 0)) > (node.height ?? 0) / 2 - ry))
|
||||
) {
|
||||
let y = ry * ry * (1 - (x * x) / (rx * rx));
|
||||
if (y != 0) {
|
||||
y = Math.sqrt(y);
|
||||
}
|
||||
y = ry - y;
|
||||
if (point.y - (node.y ?? 0) > 0) {
|
||||
y = -y;
|
||||
}
|
||||
|
||||
pos.y += y;
|
||||
}
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -50,8 +50,6 @@ export const drawRect = async (parent: SVGAElement, node: Node, options: RectOpt
|
||||
.attr('class', 'basic label-container')
|
||||
.attr('style', nodeStyles)
|
||||
.attr('rx', rx)
|
||||
.attr('data-id', 'abc')
|
||||
.attr('data-et', 'node')
|
||||
.attr('ry', ry)
|
||||
.attr('x', x)
|
||||
.attr('y', y)
|
||||
|
@@ -0,0 +1,66 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '$root/rendering-util/types.d.ts';
|
||||
import {
|
||||
styles2String,
|
||||
userNodeOverrides,
|
||||
} from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
|
||||
import rough from 'roughjs';
|
||||
import { insertPolygonShape } from './insertPolygonShape.js';
|
||||
import { createPathFromPoints } from './util.js';
|
||||
|
||||
export const shadedProcess = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const halfPadding = (node?.padding || 0) / 2;
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
const x = -bbox.width / 2 - halfPadding;
|
||||
const y = -bbox.height / 2 - halfPadding;
|
||||
let rect;
|
||||
const { cssStyles } = node;
|
||||
const points = [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: w, y: 0 },
|
||||
{ x: w, y: -h },
|
||||
{ x: 0, y: -h },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: -8, y: 0 },
|
||||
{ x: w + 8, y: 0 },
|
||||
{ x: w + 8, y: -h },
|
||||
{ x: -8, y: -h },
|
||||
{ x: -8, y: 0 },
|
||||
];
|
||||
|
||||
// @ts-ignore - rough is not typed
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
if (node.look !== 'handdrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const roughNode = rc.rectangle(x - 8, y, w + 16, h, options);
|
||||
const l1 = rc.line(x, y, x, y + h, options);
|
||||
|
||||
shapeSvg.insert(() => l1, ':first-child');
|
||||
rect = shapeSvg.insert(() => roughNode, ':first-child');
|
||||
|
||||
rect.attr('class', 'basic label-container').attr('style', cssStyles);
|
||||
|
||||
if (nodeStyles) {
|
||||
rect.attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
updateNodeBounds(node, rect);
|
||||
|
||||
node.intersect = function (point) {
|
||||
return intersect.rect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
export default shadedProcess;
|
@@ -0,0 +1,41 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '$root/rendering-util/types.d.ts';
|
||||
import { styles2String } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
|
||||
|
||||
export async function text(parent: SVGAElement, node: Node): Promise<SVGAElement> {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
// console.log('IPI labelStyles:', labelStyles);
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
|
||||
const totalWidth = Math.max(bbox.width + node.padding, node?.width || 0);
|
||||
const totalHeight = Math.max(bbox.height + node.padding, node?.height || 0);
|
||||
const x = -totalWidth / 2;
|
||||
const y = -totalHeight / 2;
|
||||
|
||||
// log.info('IPI node = ', node);
|
||||
|
||||
let rect;
|
||||
const { cssStyles } = node;
|
||||
|
||||
rect = shapeSvg.insert('rect', ':first-child');
|
||||
|
||||
rect
|
||||
.attr('class', 'text')
|
||||
.attr('style', nodeStyles)
|
||||
.attr('rx', 0)
|
||||
.attr('ry', 0)
|
||||
.attr('x', x)
|
||||
.attr('y', y)
|
||||
.attr('width', totalWidth)
|
||||
.attr('height', totalHeight);
|
||||
|
||||
updateNodeBounds(node, rect);
|
||||
|
||||
node.intersect = function (point) {
|
||||
return intersect.rect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
}
|
@@ -134,3 +134,9 @@ export function insertPolygonShape(parent, w, h, points) {
|
||||
|
||||
export const getNodeClasses = (node, extra) =>
|
||||
(node.look === 'handdrawn' ? 'rough-node' : 'node') + ' ' + node.cssClasses + ' ' + (extra || '');
|
||||
|
||||
export function createPathFromPoints(points) {
|
||||
const pointStrings = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`);
|
||||
pointStrings.push('Z');
|
||||
return pointStrings.join(' ');
|
||||
}
|
||||
|
Reference in New Issue
Block a user