Merge pull request #5247 from mermaid-js/sidv/mindmapToTs

Convert Mindmap to TS
This commit is contained in:
Knut Sveidqvist 2024-02-01 13:23:13 +00:00 committed by GitHub
commit 5c9857c4eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 292 additions and 350 deletions

View File

@ -28,6 +28,7 @@
"codedoc",
"codemia",
"colour",
"colours",
"commitlint",
"cpettitt",
"customizability",

View File

@ -124,5 +124,10 @@
},
"nyc": {
"report-dir": "coverage/cypress"
},
"pnpm": {
"patchedDependencies": {
"cytoscape@3.28.1": "patches/cytoscape@3.28.1.patch"
}
}
}

View File

@ -39,15 +39,10 @@
},
"dependencies": {
"@braintree/sanitize-url": "^6.0.1",
"cytoscape": "^3.23.0",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.1.0",
"d3": "^7.0.0",
"khroma": "^2.0.0",
"non-layered-tidy-tree-layout": "^2.0.2"
"khroma": "^2.0.0"
},
"devDependencies": {
"@types/cytoscape": "^3.19.9",
"concurrently": "^8.0.0",
"rimraf": "^5.0.0",
"mermaid": "workspace:*"

View File

@ -62,9 +62,8 @@
"@braintree/sanitize-url": "^6.0.1",
"@types/d3-scale": "^4.0.3",
"@types/d3-scale-chromatic": "^3.0.0",
"cytoscape": "^3.23.0",
"cytoscape": "^3.28.1",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.1.0",
"d3": "^7.4.0",
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.10",

View File

@ -1,12 +1,13 @@
// @ts-ignore: JISON doesn't support types
import mindmapParser from './parser/mindmap.jison';
import * as mindmapDb from './mindmapDb.js';
import mindmapRenderer from './mindmapRenderer.js';
import mindmapStyles from './styles.js';
import parser from './parser/mindmap.jison';
import db from './mindmapDb.js';
import renderer from './mindmapRenderer.js';
import styles from './styles.js';
import type { DiagramDefinition } from '../../diagram-api/types.js';
export const diagram = {
db: mindmapDb,
renderer: mindmapRenderer,
parser: mindmapParser,
styles: mindmapStyles,
export const diagram: DiagramDefinition = {
db,
renderer,
parser,
styles,
};

View File

@ -1,5 +1,6 @@
// @ts-expect-error No types available for JISON
import { parser as mindmap } from './parser/mindmap.jison';
import * as mindmapDB from './mindmapDb.js';
import mindmapDB from './mindmapDb.js';
// Todo fix utils functions for tests
import { setLogLevel } from '../../diagram-api/diagramAPI.js';
@ -11,7 +12,7 @@ describe('when parsing a mindmap ', function () {
});
describe('hiearchy', function () {
it('MMP-1 should handle a simple root definition abc122', function () {
let str = `mindmap
const str = `mindmap
root`;
mindmap.parse(str);
@ -19,7 +20,7 @@ describe('when parsing a mindmap ', function () {
expect(mindmap.yy.getMindmap().descr).toEqual('root');
});
it('MMP-2 should handle a hierachial mindmap definition', function () {
let str = `mindmap
const str = `mindmap
root
child1
child2
@ -34,7 +35,7 @@ describe('when parsing a mindmap ', function () {
});
it('3 should handle a simple root definition with a shape and without an id abc123', function () {
let str = `mindmap
const str = `mindmap
(root)`;
mindmap.parse(str);
@ -43,7 +44,7 @@ describe('when parsing a mindmap ', function () {
});
it('MMP-4 should handle a deeper hierachial mindmap definition', function () {
let str = `mindmap
const str = `mindmap
root
child1
leaf1
@ -58,40 +59,27 @@ describe('when parsing a mindmap ', function () {
expect(mm.children[1].descr).toEqual('child2');
});
it('5 Multiple roots are illegal', function () {
let str = `mindmap
const str = `mindmap
root
fakeRoot`;
try {
mindmap.parse(str);
// Fail test if above expression doesn't throw anything.
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe(
expect(() => mindmap.parse(str)).toThrow(
'There can be only one root. No parent could be found for ("fakeRoot")'
);
}
});
it('MMP-6 real root in wrong place', function () {
let str = `mindmap
const str = `mindmap
root
fakeRoot
realRootWrongPlace`;
try {
mindmap.parse(str);
// Fail test if above expression doesn't throw anything.
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe(
expect(() => mindmap.parse(str)).toThrow(
'There can be only one root. No parent could be found for ("fakeRoot")'
);
}
});
});
describe('nodes', function () {
it('MMP-7 should handle an id and type for a node definition', function () {
let str = `mindmap
const str = `mindmap
root[The root]
`;
@ -102,7 +90,7 @@ describe('when parsing a mindmap ', function () {
expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
});
it('MMP-8 should handle an id and type for a node definition', function () {
let str = `mindmap
const str = `mindmap
root
theId(child1)`;
@ -116,7 +104,7 @@ describe('when parsing a mindmap ', function () {
expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT);
});
it('MMP-9 should handle an id and type for a node definition', function () {
let str = `mindmap
const str = `mindmap
root
theId(child1)`;
@ -130,7 +118,7 @@ root
expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT);
});
it('MMP-10 multiple types (circle)', function () {
let str = `mindmap
const str = `mindmap
root((the root))
`;
@ -142,7 +130,7 @@ root
});
it('MMP-11 multiple types (cloud)', function () {
let str = `mindmap
const str = `mindmap
root)the root(
`;
@ -153,7 +141,7 @@ root
expect(mm.type).toEqual(mindmap.yy.nodeType.CLOUD);
});
it('MMP-12 multiple types (bang)', function () {
let str = `mindmap
const str = `mindmap
root))the root((
`;
@ -165,7 +153,7 @@ root
});
it('MMP-12-a multiple types (hexagon)', function () {
let str = `mindmap
const str = `mindmap
root{{the root}}
`;
@ -178,7 +166,7 @@ root
});
describe('decorations', function () {
it('MMP-13 should be possible to set an icon for the node', function () {
let str = `mindmap
const str = `mindmap
root[The root]
::icon(bomb)
`;
@ -192,7 +180,7 @@ root
expect(mm.icon).toEqual('bomb');
});
it('MMP-14 should be possible to set classes for the node', function () {
let str = `mindmap
const str = `mindmap
root[The root]
:::m-4 p-8
`;
@ -206,7 +194,7 @@ root
expect(mm.class).toEqual('m-4 p-8');
});
it('MMP-15 should be possible to set both classes and icon for the node', function () {
let str = `mindmap
const str = `mindmap
root[The root]
:::m-4 p-8
::icon(bomb)
@ -222,7 +210,7 @@ root
expect(mm.icon).toEqual('bomb');
});
it('MMP-16 should be possible to set both classes and icon for the node', function () {
let str = `mindmap
const str = `mindmap
root[The root]
::icon(bomb)
:::m-4 p-8
@ -240,7 +228,7 @@ root
});
describe('descriptions', function () {
it('MMP-17 should be possible to use node syntax in the descriptions', function () {
let str = `mindmap
const str = `mindmap
root["String containing []"]
`;
mindmap.parse(str);
@ -249,7 +237,7 @@ root
expect(mm.descr).toEqual('String containing []');
});
it('MMP-18 should be possible to use node syntax in the descriptions in children', function () {
let str = `mindmap
const str = `mindmap
root["String containing []"]
child1["String containing ()"]
`;
@ -261,7 +249,7 @@ root
expect(mm.children[0].descr).toEqual('String containing ()');
});
it('MMP-19 should be possible to have a child after a class assignment', function () {
let str = `mindmap
const str = `mindmap
root(Root)
Child(Child)
:::hot
@ -281,7 +269,7 @@ root
});
});
it('MMP-20 should be possible to have meaningless empty rows in a mindmap abc124', function () {
let str = `mindmap
const str = `mindmap
root(Root)
Child(Child)
a(a)
@ -300,7 +288,7 @@ root
expect(child.children[1].nodeId).toEqual('b');
});
it('MMP-21 should be possible to have comments in a mindmap', function () {
let str = `mindmap
const str = `mindmap
root(Root)
Child(Child)
a(a)
@ -321,7 +309,7 @@ root
});
it('MMP-22 should be possible to have comments at the end of a line', function () {
let str = `mindmap
const str = `mindmap
root(Root)
Child(Child)
a(a) %% This is a comment
@ -339,7 +327,7 @@ root
expect(child.children[1].nodeId).toEqual('b');
});
it('MMP-23 Rows with only spaces should not interfere', function () {
let str = 'mindmap\nroot\n A\n \n\n B';
const str = 'mindmap\nroot\n A\n \n\n B';
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
@ -351,7 +339,7 @@ root
expect(child2.nodeId).toEqual('B');
});
it('MMP-24 Handle rows above the mindmap declarations', function () {
let str = '\n \nmindmap\nroot\n A\n \n\n B';
const str = '\n \nmindmap\nroot\n A\n \n\n B';
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
@ -363,7 +351,7 @@ root
expect(child2.nodeId).toEqual('B');
});
it('MMP-25 Handle rows above the mindmap declarations, no space', function () {
let str = '\n\n\nmindmap\nroot\n A\n \n\n B';
const str = '\n\n\nmindmap\nroot\n A\n \n\n B';
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');

View File

@ -1,19 +1,21 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { sanitizeText as _sanitizeText } from '../../diagrams/common/common.js';
import type { D3Element } from '../../mermaidAPI.js';
import { sanitizeText } from '../../diagrams/common/common.js';
import { log } from '../../logger.js';
import type { MindmapNode } from './mindmapTypes.js';
import defaultConfig from '../../defaultConfig.js';
export const sanitizeText = (text) => _sanitizeText(text, getConfig());
let nodes = [];
let nodes: MindmapNode[] = [];
let cnt = 0;
let elements = {};
export const clear = () => {
let elements: Record<number, D3Element> = {};
const clear = () => {
nodes = [];
cnt = 0;
elements = {};
};
const getParent = function (level) {
const getParent = function (level: number) {
for (let i = nodes.length - 1; i >= 0; i--) {
if (nodes[i].level < level) {
return nodes[i];
@ -23,34 +25,32 @@ const getParent = function (level) {
return null;
};
export const getMindmap = () => {
const getMindmap = () => {
return nodes.length > 0 ? nodes[0] : null;
};
export const addNode = (level, id, descr, type) => {
const addNode = (level: number, id: string, descr: string, type: number) => {
log.info('addNode', level, id, descr, type);
const conf = getConfig();
let padding: number = conf.mindmap?.padding ?? defaultConfig.mindmap.padding;
switch (type) {
case nodeType.ROUNDED_RECT:
case nodeType.RECT:
case nodeType.HEXAGON:
padding *= 2;
}
const node = {
id: cnt++,
nodeId: sanitizeText(id),
nodeId: sanitizeText(id, conf),
level,
descr: sanitizeText(descr),
descr: sanitizeText(descr, conf),
type,
children: [],
width: getConfig().mindmap.maxNodeWidth,
};
switch (node.type) {
case nodeType.ROUNDED_RECT:
node.padding = 2 * conf.mindmap.padding;
break;
case nodeType.RECT:
node.padding = 2 * conf.mindmap.padding;
break;
case nodeType.HEXAGON:
node.padding = 2 * conf.mindmap.padding;
break;
default:
node.padding = conf.mindmap.padding;
}
width: conf.mindmap?.maxNodeWidth ?? defaultConfig.mindmap.maxNodeWidth,
padding,
} satisfies MindmapNode;
const parent = getParent(level);
if (parent) {
parent.children.push(node);
@ -62,22 +62,14 @@ export const addNode = (level, id, descr, type) => {
nodes.push(node);
} else {
// Syntax error ... there can only bee one root
let error = new Error(
throw new Error(
'There can be only one root. No parent could be found for ("' + node.descr + '")'
);
error.hash = {
text: 'branch ' + name,
token: 'branch ' + name,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['"checkout ' + name + '"'],
};
throw error;
}
}
};
export const nodeType = {
const nodeType = {
DEFAULT: 0,
NO_BORDER: 0,
ROUNDED_RECT: 1,
@ -88,7 +80,7 @@ export const nodeType = {
HEXAGON: 6,
};
export const getType = (startStr, endStr) => {
const getType = (startStr: string, endStr: string): number => {
log.debug('In get type', startStr, endStr);
switch (startStr) {
case '[':
@ -108,21 +100,25 @@ export const getType = (startStr, endStr) => {
}
};
export const setElementForId = (id, element) => {
const setElementForId = (id: number, element: D3Element) => {
elements[id] = element;
};
export const decorateNode = (decoration) => {
const node = nodes[nodes.length - 1];
if (decoration && decoration.icon) {
node.icon = sanitizeText(decoration.icon);
const decorateNode = (decoration?: { class?: string; icon?: string }) => {
if (!decoration) {
return;
}
if (decoration && decoration.class) {
node.class = sanitizeText(decoration.class);
const config = getConfig();
const node = nodes[nodes.length - 1];
if (decoration.icon) {
node.icon = sanitizeText(decoration.icon, config);
}
if (decoration.class) {
node.class = sanitizeText(decoration.class, config);
}
};
export const type2Str = (type) => {
const type2Str = (type: number) => {
switch (type) {
case nodeType.DEFAULT:
return 'no-border';
@ -143,13 +139,21 @@ export const type2Str = (type) => {
}
};
export let parseError;
export const setErrorHandler = (handler) => {
parseError = handler;
};
// Expose logger to grammar
export const getLogger = () => log;
const getLogger = () => log;
const getElementById = (id: number) => elements[id];
export const getNodeById = (id) => nodes[id];
export const getElementById = (id) => elements[id];
const db = {
clear,
addNode,
getMindmap,
nodeType,
getType,
setElementForId,
decorateNode,
type2Str,
getLogger,
getElementById,
} as const;
export default db;

View File

@ -1,36 +1,53 @@
/** Created by knut on 14-12-11. */
import { select } from 'd3';
import { log } from '../../logger.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import svgDraw from './svgDraw.js';
import cytoscape from 'cytoscape/dist/cytoscape.umd.js';
import cytoscape from 'cytoscape';
// @ts-expect-error No types available
import coseBilkent from 'cytoscape-cose-bilkent';
import * as db from './mindmapDb.js';
import { select } from 'd3';
import type { MermaidConfig } from '../../config.type.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import type { DrawDefinition } from '../../diagram-api/types.js';
import { log } from '../../logger.js';
import type { D3Element } from '../../mermaidAPI.js';
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import type { FilledMindMapNode, MindmapDB, MindmapNode } from './mindmapTypes.js';
import { drawNode, positionNode } from './svgDraw.js';
import defaultConfig from '../../defaultConfig.js';
// Inject the layout algorithm into cytoscape
cytoscape.use(coseBilkent);
/**
* @param {any} svg The svg element to draw the diagram onto
* @param {object} mindmap The mindmap data and hierarchy
* @param section
* @param {object} conf The configuration object
*/
function drawNodes(svg, mindmap, section, conf) {
svgDraw.drawNode(svg, mindmap, section, conf);
function drawNodes(
db: MindmapDB,
svg: D3Element,
mindmap: FilledMindMapNode,
section: number,
conf: MermaidConfig
) {
drawNode(db, svg, mindmap, section, conf);
if (mindmap.children) {
mindmap.children.forEach((child, index) => {
drawNodes(svg, child, section < 0 ? index : section, conf);
drawNodes(db, svg, child, section < 0 ? index : section, conf);
});
}
}
/**
* @param edgesEl
* @param cy
*/
function drawEdges(edgesEl, cy) {
declare module 'cytoscape' {
interface EdgeSingular {
_private: {
bodyBounds: unknown;
rscratch: {
startX: number;
startY: number;
midX: number;
midY: number;
endX: number;
endY: number;
};
};
}
}
function drawEdges(edgesEl: D3Element, cy: cytoscape.Core) {
cy.edges().map((edge, id) => {
const data = edge.data();
if (edge[0]._private.bodyBounds) {
@ -47,17 +64,11 @@ function drawEdges(edgesEl, cy) {
});
}
/**
* @param mindmap The mindmap data and hierarchy
* @param cy
* @param conf The configuration object
* @param level
*/
function addNodes(mindmap, cy, conf, level) {
function addNodes(mindmap: MindmapNode, cy: cytoscape.Core, conf: MermaidConfig, level: number) {
cy.add({
group: 'nodes',
data: {
id: mindmap.id,
id: mindmap.id.toString(),
labelText: mindmap.descr,
height: mindmap.height,
width: mindmap.width,
@ -67,8 +78,8 @@ function addNodes(mindmap, cy, conf, level) {
type: mindmap.type,
},
position: {
x: mindmap.x,
y: mindmap.y,
x: mindmap.x!,
y: mindmap.y!,
},
});
if (mindmap.children) {
@ -88,12 +99,7 @@ function addNodes(mindmap, cy, conf, level) {
}
}
/**
* @param node
* @param conf
* @param cy
*/
function layoutMindmap(node, conf) {
function layoutMindmap(node: MindmapNode, conf: MermaidConfig): Promise<cytoscape.Core> {
return new Promise((resolve) => {
// Add temporary render element
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
@ -122,8 +128,8 @@ function layoutMindmap(node, conf) {
cy.layout({
name: 'cose-bilkent',
// @ts-ignore Types for cose-bilkent are not correct?
quality: 'proof',
// headless: true,
styleEnabled: false,
animate: false,
}).run();
@ -133,18 +139,13 @@ function layoutMindmap(node, conf) {
});
});
}
/**
* @param node
* @param cy
* @param positionedMindmap
* @param conf
*/
function positionNodes(cy) {
function positionNodes(db: MindmapDB, cy: cytoscape.Core) {
cy.nodes().map((node, id) => {
const data = node.data();
data.x = node.position().x;
data.y = node.position().y;
svgDraw.positionNode(data);
positionNode(db, data);
const el = db.getElementById(data.nodeId);
log.info('Id:', id, 'Position: (', node.position().x, ', ', node.position().y, ')', data);
el.attr(
@ -155,38 +156,19 @@ function positionNodes(cy) {
});
}
/**
* Draws a an info picture in the tag with id: id based on the graph definition in text.
*
* @param {any} text
* @param {any} id
* @param {any} version
* @param diagObj
*/
export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
log.debug('Rendering mindmap diagram\n' + text);
const db = diagObj.db as MindmapDB;
const mm = db.getMindmap();
if (!mm) {
return;
}
export const draw = async (text, id, version, diagObj) => {
const conf = getConfig();
conf.htmlLabels = false;
log.debug('Rendering mindmap diagram\n' + text, diagObj.parser);
const securityLevel = getConfig().securityLevel;
// Handle root and Document for when rendering in sandbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
// Parse the graph definition
const svg = root.select('#' + id);
svg.append('g');
const mm = diagObj.db.getMindmap();
const svg = selectSvgElement(id);
// Draw the graph and start with drawing the nodes without proper position
// this gives us the size of the nodes and we can set the positions later
@ -195,18 +177,23 @@ export const draw = async (text, id, version, diagObj) => {
edgesElem.attr('class', 'mindmap-edges');
const nodesElem = svg.append('g');
nodesElem.attr('class', 'mindmap-nodes');
drawNodes(nodesElem, mm, -1, conf);
drawNodes(db, nodesElem, mm as FilledMindMapNode, -1, conf);
// Next step is to layout the mindmap, giving each node a position
const cy = await layoutMindmap(mm, conf);
// // After this we can draw, first the edges and the then nodes with the correct position
drawEdges(edgesElem, cy, conf);
positionNodes(cy, conf);
// After this we can draw, first the edges and the then nodes with the correct position
drawEdges(edgesElem, cy);
positionNodes(db, cy);
// Setup the view box and size of the svg element
setupGraphViewbox(undefined, svg, conf.mindmap.padding, conf.mindmap.useMaxWidth);
setupGraphViewbox(
undefined,
svg,
conf.mindmap?.padding ?? defaultConfig.mindmap.padding,
conf.mindmap?.useMaxWidth ?? defaultConfig.mindmap.useMaxWidth
);
};
export default {

View File

@ -0,0 +1,22 @@
import type { RequiredDeep } from 'type-fest';
import type mindmapDb from './mindmapDb.js';
export interface MindmapNode {
id: number;
nodeId: string;
level: number;
descr: string;
type: number;
children: MindmapNode[];
width: number;
padding: number;
section?: number;
height?: number;
class?: string;
icon?: string;
x?: number;
y?: number;
}
export type FilledMindMapNode = RequiredDeep<MindmapNode>;
export type MindmapDB = typeof mindmapDb;

View File

@ -1,6 +1,8 @@
// @ts-expect-error Incorrect khroma types
import { darken, lighten, isDark } from 'khroma';
import type { DiagramStylesProvider } from '../../diagram-api/types.js';
const genSections = (options) => {
const genSections: DiagramStylesProvider = (options) => {
let sections = '';
for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) {
@ -49,7 +51,8 @@ const genSections = (options) => {
return sections;
};
const getStyles = (options) =>
// TODO: These options seem incorrect.
const getStyles: DiagramStylesProvider = (options) =>
`
.edge {
stroke-width: 3;

View File

@ -1,55 +1,20 @@
import { select } from 'd3';
import * as db from './mindmapDb.js';
import type { D3Element } from '../../mermaidAPI.js';
import { createText } from '../../rendering-util/createText.js';
import type { FilledMindMapNode, MindmapDB } from './mindmapTypes.js';
import type { Point } from '../../types.js';
import { parseFontSize } from '../../utils.js';
import type { MermaidConfig } from '../../config.type.js';
const MAX_SECTIONS = 12;
/**
* @param {string} text The text to be wrapped
* @param {number} width The max width of the text
*/
function wrap(text, width) {
text.each(function () {
var text = select(this),
words = text
.text()
.split(/(\s+|<br\/>)/)
.reverse(),
word,
line = [],
lineHeight = 1.1, // ems
y = text.attr('y'),
dy = parseFloat(text.attr('dy')),
tspan = text
.text(null)
.append('tspan')
.attr('x', 0)
.attr('y', y)
.attr('dy', dy + 'em');
for (let j = 0; j < words.length; j++) {
word = words[words.length - 1 - j];
line.push(word);
tspan.text(line.join(' ').trim());
if (tspan.node().getComputedTextLength() > width || word === '<br/>') {
line.pop();
tspan.text(line.join(' ').trim());
if (word === '<br/>') {
line = [''];
} else {
line = [word];
}
type ShapeFunction = (
db: MindmapDB,
elem: D3Element,
node: FilledMindMapNode,
section?: number
) => void;
tspan = text
.append('tspan')
.attr('x', 0)
.attr('y', y)
.attr('dy', lineHeight + 'em')
.text(word);
}
}
});
}
const defaultBkg = function (elem, node, section) {
const defaultBkg: ShapeFunction = function (db, elem, node, section) {
const rd = 5;
elem
.append('path')
@ -71,7 +36,7 @@ const defaultBkg = function (elem, node, section) {
.attr('y2', node.height);
};
const rectBkg = function (elem, node) {
const rectBkg: ShapeFunction = function (db, elem, node) {
elem
.append('rect')
.attr('id', 'node-' + node.id)
@ -80,7 +45,7 @@ const rectBkg = function (elem, node) {
.attr('width', node.width);
};
const cloudBkg = function (elem, node) {
const cloudBkg: ShapeFunction = function (db, elem, node) {
const w = node.width;
const h = node.height;
const r1 = 0.15 * w;
@ -111,7 +76,7 @@ const cloudBkg = function (elem, node) {
);
};
const bangBkg = function (elem, node) {
const bangBkg: ShapeFunction = function (db, elem, node) {
const w = node.width;
const h = node.height;
const r = 0.15 * w;
@ -143,7 +108,7 @@ const bangBkg = function (elem, node) {
);
};
const circleBkg = function (elem, node) {
const circleBkg: ShapeFunction = function (db, elem, node) {
elem
.append('circle')
.attr('id', 'node-' + node.id)
@ -151,15 +116,13 @@ const circleBkg = function (elem, node) {
.attr('r', node.width / 2);
};
/**
*
* @param parent
* @param w
* @param h
* @param points
* @param node
*/
function insertPolygonShape(parent, w, h, points, node) {
function insertPolygonShape(
parent: D3Element,
w: number,
h: number,
points: Point[],
node: FilledMindMapNode
) {
return parent
.insert('polygon', ':first-child')
.attr(
@ -173,12 +136,16 @@ function insertPolygonShape(parent, w, h, points, node) {
.attr('transform', 'translate(' + (node.width - w) / 2 + ', ' + h + ')');
}
const hexagonBkg = function (elem, node) {
const hexagonBkg: ShapeFunction = function (
_db: MindmapDB,
elem: D3Element,
node: FilledMindMapNode
) {
const h = node.height;
const f = 4;
const m = h / f;
const w = node.width - node.padding + 2 * m;
const points = [
const points: Point[] = [
{ x: m, y: 0 },
{ x: w - m, y: 0 },
{ x: w, y: -h / 2 },
@ -186,10 +153,10 @@ const hexagonBkg = function (elem, node) {
{ x: m, y: -h },
{ x: 0, y: -h / 2 },
];
const shapeSvg = insertPolygonShape(elem, w, h, points, node);
insertPolygonShape(elem, w, h, points, node);
};
const roundedRectBkg = function (elem, node) {
const roundedRectBkg: ShapeFunction = function (db, elem, node) {
elem
.append('rect')
.attr('id', 'node-' + node.id)
@ -201,13 +168,20 @@ const roundedRectBkg = function (elem, node) {
};
/**
* @param {object} elem The D3 dom element in which the node is to be added
* @param {object} node The node to be added
* @param fullSection
* @param {object} conf The configuration object
* @returns {number} The height nodes dom element
* @param db - The database
* @param elem - The D3 dom element in which the node is to be added
* @param node - The node to be added
* @param fullSection - ?
* @param conf - The configuration object
* @returns The height nodes dom element
*/
export const drawNode = function (elem, node, fullSection, conf) {
export const drawNode = function (
db: MindmapDB,
elem: D3Element,
node: FilledMindMapNode,
fullSection: number,
conf: MermaidConfig
): number {
const htmlLabels = conf.htmlLabels;
const section = fullSection % (MAX_SECTIONS - 1);
const nodeElem = elem.append('g');
@ -235,10 +209,9 @@ export const drawNode = function (elem, node, fullSection, conf) {
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle');
}
// .call(wrap, node.width);
const bbox = textElem.node().getBBox();
const fontSize = conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
const [fontSize] = parseFontSize(conf.fontSize);
node.height = bbox.height + fontSize! * 1.1 * 0.5 + node.padding;
node.width = bbox.width + 2 * node.padding;
if (node.icon) {
if (node.type === db.nodeType.CIRCLE) {
@ -294,60 +267,34 @@ export const drawNode = function (elem, node, fullSection, conf) {
switch (node.type) {
case db.nodeType.DEFAULT:
defaultBkg(bkgElem, node, section, conf);
defaultBkg(db, bkgElem, node, section);
break;
case db.nodeType.ROUNDED_RECT:
roundedRectBkg(bkgElem, node, section, conf);
roundedRectBkg(db, bkgElem, node, section);
break;
case db.nodeType.RECT:
rectBkg(bkgElem, node, section, conf);
rectBkg(db, bkgElem, node, section);
break;
case db.nodeType.CIRCLE:
bkgElem.attr('transform', 'translate(' + node.width / 2 + ', ' + +node.height / 2 + ')');
circleBkg(bkgElem, node, section, conf);
circleBkg(db, bkgElem, node, section);
break;
case db.nodeType.CLOUD:
cloudBkg(bkgElem, node, section, conf);
cloudBkg(db, bkgElem, node, section);
break;
case db.nodeType.BANG:
bangBkg(bkgElem, node, section, conf);
bangBkg(db, bkgElem, node, section);
break;
case db.nodeType.HEXAGON:
hexagonBkg(bkgElem, node, section, conf);
hexagonBkg(db, bkgElem, node, section);
break;
}
// Position the node to its coordinate
// if (typeof node.x !== 'undefined' && typeof node.y !== 'undefined') {
// nodeElem.attr('transform', 'translate(' + node.x + ',' + node.y + ')');
// }
db.setElementForId(node.id, nodeElem);
return node.height;
};
export const drawEdge = function drawEdge(edgesElem, mindmap, parent, depth, fullSection) {
const section = fullSection % (MAX_SECTIONS - 1);
const sx = parent.x + parent.width / 2;
const sy = parent.y + parent.height / 2;
const ex = mindmap.x + mindmap.width / 2;
const ey = mindmap.y + mindmap.height / 2;
const mx = ex > sx ? sx + Math.abs(sx - ex) / 2 : sx - Math.abs(sx - ex) / 2;
const my = ey > sy ? sy + Math.abs(sy - ey) / 2 : sy - Math.abs(sy - ey) / 2;
const qx = ex > sx ? Math.abs(sx - mx) / 2 + sx : -Math.abs(sx - mx) / 2 + sx;
const qy = ey > sy ? Math.abs(sy - my) / 2 + sy : -Math.abs(sy - my) / 2 + sy;
edgesElem
.append('path')
.attr(
'd',
parent.direction === 'TB' || parent.direction === 'BT'
? `M${sx},${sy} Q${sx},${qy} ${mx},${my} T${ex},${ey}`
: `M${sx},${sy} Q${qx},${sy} ${mx},${my} T${ex},${ey}`
)
.attr('class', 'edge section-edge-' + section + ' edge-depth-' + depth);
};
export const positionNode = function (node) {
export const positionNode = function (db: MindmapDB, node: FilledMindMapNode) {
const nodeElem = db.getElementById(node.id);
const x = node.x || 0;
@ -355,5 +302,3 @@ export const positionNode = function (node) {
// Position the node to its coordinate
nodeElem.attr('transform', 'translate(' + x + ',' + y + ')');
};
export default { drawNode, positionNode, drawEdge };

View File

@ -1,6 +1,5 @@
import type { Diagram } from '../../Diagram.js';
import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js';
import {
select as d3select,
scaleOrdinal as d3scaleOrdinal,

View File

@ -925,3 +925,7 @@ export const encodeEntities = function (text: string): string {
export const decodeEntities = function (text: string): string {
return text.replace(/fl°°/g, '&#').replace(/fl°/g, '&').replace(/¶ß/g, ';');
};
export const isString = (value: unknown): value is string => {
return typeof value === 'string';
};

View File

@ -0,0 +1,20 @@
diff --git a/package.json b/package.json
index f2f77fa79c99382b079f4051ed51eafe8d2379c8..0bfddf55394e86f3a386eb7ab681369d410bae07 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,15 @@
"engines": {
"node": ">=0.10"
},
+ "exports": {
+ ".": {
+ "import": "./dist/cytoscape.umd.js",
+ "default": "./dist/cytoscape.cjs.js"
+ },
+ "./*": "./*"
+ },
"main": "dist/cytoscape.cjs.js",
+ "module": "dist/cytoscape.umd.js",
"unpkg": "dist/cytoscape.min.js",
"jsdelivr": "dist/cytoscape.min.js",
"scripts": {

51
pnpm-lock.yaml generated
View File

@ -4,6 +4,11 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies:
cytoscape@3.28.1:
hash: claipxynndhyqyu2csninuoh5e
path: patches/cytoscape@3.28.1.patch
importers:
.:
@ -201,14 +206,11 @@ importers:
specifier: ^3.0.0
version: 3.0.0
cytoscape:
specifier: ^3.23.0
version: 3.28.1
specifier: ^3.28.1
version: 3.28.1(patch_hash=claipxynndhyqyu2csninuoh5e)
cytoscape-cose-bilkent:
specifier: ^4.1.0
version: 4.1.0(cytoscape@3.28.1)
cytoscape-fcose:
specifier: ^2.1.0
version: 2.2.0(cytoscape@3.28.1)
d3:
specifier: ^7.4.0
version: 7.4.0
@ -384,28 +386,13 @@ importers:
'@braintree/sanitize-url':
specifier: ^6.0.1
version: 6.0.2
cytoscape:
specifier: ^3.23.0
version: 3.28.1
cytoscape-cose-bilkent:
specifier: ^4.1.0
version: 4.1.0(cytoscape@3.28.1)
cytoscape-fcose:
specifier: ^2.1.0
version: 2.2.0(cytoscape@3.28.1)
d3:
specifier: ^7.0.0
version: 7.0.0
khroma:
specifier: ^2.0.0
version: 2.0.0
non-layered-tidy-tree-layout:
specifier: ^2.0.2
version: 2.0.2
devDependencies:
'@types/cytoscape':
specifier: ^3.19.9
version: 3.19.9
concurrently:
specifier: ^8.0.0
version: 8.0.0
@ -7607,12 +7594,6 @@ packages:
layout-base: 1.0.2
dev: false
/cose-base@2.2.0:
resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
dependencies:
layout-base: 2.0.1
dev: false
/cosmiconfig-typescript-loader@4.4.0(@types/node@20.4.7)(cosmiconfig@8.2.0)(ts-node@10.9.1)(typescript@5.1.6):
resolution: {integrity: sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==}
engines: {node: '>=v14.21.3'}
@ -8008,25 +7989,17 @@ packages:
cytoscape: ^3.2.0
dependencies:
cose-base: 1.0.3
cytoscape: 3.28.1
cytoscape: 3.28.1(patch_hash=claipxynndhyqyu2csninuoh5e)
dev: false
/cytoscape-fcose@2.2.0(cytoscape@3.28.1):
resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==}
peerDependencies:
cytoscape: ^3.2.0
dependencies:
cose-base: 2.2.0
cytoscape: 3.28.1
dev: false
/cytoscape@3.28.1:
/cytoscape@3.28.1(patch_hash=claipxynndhyqyu2csninuoh5e):
resolution: {integrity: sha512-xyItz4O/4zp9/239wCcH8ZcFuuZooEeF8KHRmzjDfGdXsj3OG9MFSMA0pJE0uX3uCN/ygof6hHf4L7lst+JaDg==}
engines: {node: '>=0.10'}
dependencies:
heap: 0.2.7
lodash: 4.17.21
dev: false
patched: true
/d3-array@2.12.1:
resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==}
@ -11866,10 +11839,6 @@ packages:
resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
dev: false
/layout-base@2.0.1:
resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}
dev: false
/lazy-ass@1.6.0:
resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==}
engines: {node: '> 0.8'}