mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-15 06:19:24 +02:00
add support for cloud and bang shape
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
This commit is contained in:
@@ -105,6 +105,126 @@ root))bang((
|
||||
);
|
||||
});
|
||||
|
||||
it('Bang and cloud shape with dagre layout', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
layout: dagre
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
Origins
|
||||
Long history
|
||||
::icon(fa fa-book)
|
||||
Popularisation
|
||||
British popular psychology author Tony Buzan
|
||||
Research
|
||||
On effectiveness<br/>and features
|
||||
On Automatic creation
|
||||
Uses
|
||||
Creative techniques
|
||||
Strategic planning
|
||||
Argument mapping
|
||||
Tools
|
||||
Pen and paper
|
||||
Mermaid
|
||||
`,
|
||||
{},
|
||||
undefined,
|
||||
shouldHaveRoot
|
||||
);
|
||||
});
|
||||
|
||||
it('Bang and cloud shape with elk layout', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
Origins
|
||||
Long history
|
||||
::icon(fa fa-book)
|
||||
Popularisation
|
||||
British popular psychology author Tony Buzan
|
||||
Research
|
||||
On effectiveness<br/>and features
|
||||
On Automatic creation
|
||||
Uses
|
||||
Creative techniques
|
||||
Strategic planning
|
||||
Argument mapping
|
||||
Tools
|
||||
Pen and paper
|
||||
Mermaid
|
||||
`,
|
||||
{},
|
||||
undefined,
|
||||
shouldHaveRoot
|
||||
);
|
||||
});
|
||||
|
||||
it('Bang and cloud shape with cose-bilkent layout', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
layout: cose-bilkent
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
Origins
|
||||
Long history
|
||||
::icon(fa fa-book)
|
||||
Popularisation
|
||||
British popular psychology author Tony Buzan
|
||||
Research
|
||||
On effectiveness<br/>and features
|
||||
On Automatic creation
|
||||
Uses
|
||||
Creative techniques
|
||||
Strategic planning
|
||||
Argument mapping
|
||||
Tools
|
||||
Pen and paper
|
||||
Mermaid
|
||||
`,
|
||||
{},
|
||||
undefined,
|
||||
shouldHaveRoot
|
||||
);
|
||||
});
|
||||
|
||||
it('Bang and cloud shape with tidy-tree layout', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
Origins
|
||||
Long history
|
||||
::icon(fa fa-book)
|
||||
Popularisation
|
||||
British popular psychology author Tony Buzan
|
||||
Research
|
||||
On effectiveness<br/>and features
|
||||
On Automatic creation
|
||||
Uses
|
||||
Creative techniques
|
||||
Strategic planning
|
||||
Argument mapping
|
||||
Tools
|
||||
Pen and paper
|
||||
Mermaid
|
||||
`,
|
||||
{},
|
||||
undefined,
|
||||
shouldHaveRoot
|
||||
);
|
||||
});
|
||||
|
||||
it('braches', () => {
|
||||
imgSnapshotTest(
|
||||
`mindmap
|
||||
|
@@ -206,9 +206,9 @@ const flattenNodes = (node: MindmapNode, processedNodes: MindmapLayoutNode[]): v
|
||||
case nodeType.ROUNDED_RECT:
|
||||
return 'rounded';
|
||||
case nodeType.CLOUD:
|
||||
return 'rounded'; // Map cloud to rounded for now
|
||||
return 'cloud';
|
||||
case nodeType.BANG:
|
||||
return 'circle'; // Map bang to circle for now
|
||||
return 'bang';
|
||||
case nodeType.HEXAGON:
|
||||
return 'hexagon';
|
||||
case nodeType.DEFAULT:
|
||||
|
@@ -61,6 +61,8 @@ import { erBox } from './shapes/erBox.js';
|
||||
import { classBox } from './shapes/classBox.js';
|
||||
import { requirementBox } from './shapes/requirementBox.js';
|
||||
import { kanbanItem } from './shapes/kanbanItem.js';
|
||||
import { bang } from './shapes/bang.js';
|
||||
import { cloud } from './shapes/cloud.js';
|
||||
|
||||
type ShapeHandler = <T extends SVGGraphicsElement>(
|
||||
parent: D3Selection<T>,
|
||||
@@ -135,6 +137,22 @@ export const shapesDefs = [
|
||||
aliases: ['circ'],
|
||||
handler: circle,
|
||||
},
|
||||
{
|
||||
semanticName: 'Bang',
|
||||
name: 'Bang',
|
||||
shortName: 'bang',
|
||||
description: 'Bang',
|
||||
aliases: ['bang'],
|
||||
handler: bang,
|
||||
},
|
||||
{
|
||||
semanticName: 'Cloud',
|
||||
name: 'Cloud',
|
||||
shortName: 'cloud',
|
||||
description: 'cloud',
|
||||
aliases: ['cloud'],
|
||||
handler: cloud,
|
||||
},
|
||||
{
|
||||
semanticName: 'Decision',
|
||||
name: 'Diamond',
|
||||
|
@@ -0,0 +1,77 @@
|
||||
import { log } from '../../../logger.js';
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.js';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
import type { D3Selection } from '../../../types.js';
|
||||
import { handleUndefinedAttr } from '../../../utils.js';
|
||||
import type { Bounds, Point } from '../../../types.js';
|
||||
|
||||
export async function bang<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox, halfPadding, label } = await labelHelper(
|
||||
parent,
|
||||
node,
|
||||
getNodeClasses(node)
|
||||
);
|
||||
|
||||
const w = bbox.width + 2 * halfPadding;
|
||||
const h = bbox.height + 2 * halfPadding;
|
||||
const r = 0.15 * w;
|
||||
const { cssStyles } = node;
|
||||
|
||||
// Label centered around (0,0)
|
||||
label.attr('transform', `translate(${-bbox.width / 2}, ${-bbox.height / 2})`);
|
||||
|
||||
let bangElem;
|
||||
const path = `M0 0
|
||||
a${r},${r} 1 0,0 ${w * 0.25},${-1 * h * 0.1}
|
||||
a${r},${r} 1 0,0 ${w * 0.25},${0}
|
||||
a${r},${r} 1 0,0 ${w * 0.25},${0}
|
||||
a${r},${r} 1 0,0 ${w * 0.25},${h * 0.1}
|
||||
|
||||
a${r},${r} 1 0,0 ${w * 0.15},${h * 0.33}
|
||||
a${r * 0.8},${r * 0.8} 1 0,0 0,${h * 0.34}
|
||||
a${r},${r} 1 0,0 ${-1 * w * 0.15},${h * 0.33}
|
||||
|
||||
a${r},${r} 1 0,0 ${-1 * w * 0.25},${h * 0.15}
|
||||
a${r},${r} 1 0,0 ${-1 * w * 0.25},0
|
||||
a${r},${r} 1 0,0 ${-1 * w * 0.25},0
|
||||
a${r},${r} 1 0,0 ${-1 * w * 0.25},${-1 * h * 0.15}
|
||||
|
||||
a${r},${r} 1 0,0 ${-1 * w * 0.1},${-1 * h * 0.33}
|
||||
a${r * 0.8},${r * 0.8} 1 0,0 0,${-1 * h * 0.34}
|
||||
a${r},${r} 1 0,0 ${w * 0.1},${-1 * h * 0.33}
|
||||
H0 V0 Z`;
|
||||
|
||||
if (node.look === 'handDrawn') {
|
||||
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
const roughNode = rc.path(path, options);
|
||||
bangElem = shapeSvg.insert(() => roughNode, ':first-child');
|
||||
bangElem.attr('class', 'basic label-container').attr('style', handleUndefinedAttr(cssStyles));
|
||||
} else {
|
||||
bangElem = shapeSvg
|
||||
.insert('path', ':first-child')
|
||||
.attr('class', 'basic label-container')
|
||||
.attr('style', nodeStyles)
|
||||
.attr('d', path);
|
||||
}
|
||||
|
||||
// Translate the path (center the shape)
|
||||
bangElem.attr('transform', `translate(${-w / 2}, ${-h / 2})`);
|
||||
|
||||
updateNodeBounds(node, bangElem);
|
||||
node.calcIntersect = function (bounds: Bounds, point: Point) {
|
||||
return intersect.rect(bounds, point);
|
||||
};
|
||||
node.intersect = function (point) {
|
||||
log.info('Bang intersect', node, point);
|
||||
return intersect.rect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
import { log } from '../../../logger.js';
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.js';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
import type { D3Selection } from '../../../types.js';
|
||||
import { handleUndefinedAttr } from '../../../utils.js';
|
||||
import type { Bounds, Point } from '../../../types.js';
|
||||
|
||||
export async function cloud<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
|
||||
const { shapeSvg, bbox, halfPadding, label } = await labelHelper(
|
||||
parent,
|
||||
node,
|
||||
getNodeClasses(node)
|
||||
);
|
||||
|
||||
const w = bbox.width + 2 * halfPadding;
|
||||
const h = bbox.height + 2 * halfPadding;
|
||||
|
||||
// Cloud radii
|
||||
const r1 = 0.15 * w;
|
||||
const r2 = 0.25 * w;
|
||||
const r3 = 0.35 * w;
|
||||
const r4 = 0.2 * w;
|
||||
|
||||
const { cssStyles } = node;
|
||||
let cloudElem;
|
||||
|
||||
// Cloud path
|
||||
const path = `M0 0
|
||||
a${r1},${r1} 0 0,1 ${w * 0.25},${-1 * w * 0.1}
|
||||
a${r3},${r3} 1 0,1 ${w * 0.4},${-1 * w * 0.1}
|
||||
a${r2},${r2} 1 0,1 ${w * 0.35},${w * 0.2}
|
||||
|
||||
a${r1},${r1} 1 0,1 ${w * 0.15},${h * 0.35}
|
||||
a${r4},${r4} 1 0,1 ${-1 * w * 0.15},${h * 0.65}
|
||||
|
||||
a${r2},${r1} 1 0,1 ${-1 * w * 0.25},${w * 0.15}
|
||||
a${r3},${r3} 1 0,1 ${-1 * w * 0.5},0
|
||||
a${r1},${r1} 1 0,1 ${-1 * w * 0.25},${-1 * w * 0.15}
|
||||
|
||||
a${r1},${r1} 1 0,1 ${-1 * w * 0.1},${-1 * h * 0.35}
|
||||
a${r4},${r4} 1 0,1 ${w * 0.1},${-1 * h * 0.65}
|
||||
H0 V0 Z`;
|
||||
|
||||
if (node.look === 'handDrawn') {
|
||||
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
const roughNode = rc.path(path, options);
|
||||
cloudElem = shapeSvg.insert(() => roughNode, ':first-child');
|
||||
cloudElem.attr('class', 'basic label-container').attr('style', handleUndefinedAttr(cssStyles));
|
||||
} else {
|
||||
cloudElem = shapeSvg
|
||||
.insert('path', ':first-child')
|
||||
.attr('class', 'basic label-container')
|
||||
.attr('style', nodeStyles)
|
||||
.attr('d', path);
|
||||
}
|
||||
|
||||
label.attr('transform', `translate(${-bbox.width / 2}, ${-bbox.height / 2})`);
|
||||
|
||||
// Center the shape
|
||||
cloudElem.attr('transform', `translate(${-w / 2}, ${-h / 2})`);
|
||||
|
||||
updateNodeBounds(node, cloudElem);
|
||||
|
||||
node.calcIntersect = function (bounds: Bounds, point: Point) {
|
||||
return intersect.rect(bounds, point);
|
||||
};
|
||||
node.intersect = function (point) {
|
||||
log.info('Cloud intersect', node, point);
|
||||
return intersect.rect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
}
|
Reference in New Issue
Block a user