From f791cd2b24616804b7950e09585e4f4ad5ba429c Mon Sep 17 00:00:00 2001 From: Frederic Lavigne Date: Wed, 8 Feb 2023 22:02:19 -0600 Subject: [PATCH 01/39] =?UTF-8?q?=F0=9F=92=84=20section=20width=20now=20co?= =?UTF-8?q?vers=20all=20tasks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/diagrams/user-journey/journeyRenderer.ts | 12 ++++++++++++ .../mermaid/src/diagrams/user-journey/svgDraw.js | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts b/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts index df46fc9c6..c34f8f5b2 100644 --- a/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts +++ b/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts @@ -224,6 +224,17 @@ export const drawTasks = function (diagram, tasks, verticalPos) { num = sectionNumber % fills.length; colour = textColours[sectionNumber % textColours.length]; + // count how many consecutive tasks have the same section + let taskInSectionCount = 0; + const currentSection = task.section; + for (let taskIndex = i; taskIndex < tasks.length; taskIndex++) { + if (tasks[taskIndex].section == currentSection) { + taskInSectionCount = taskInSectionCount + 1; + } else { + break; + } + } + const section = { x: i * conf.taskMargin + i * conf.width + LEFT_MARGIN, y: 50, @@ -231,6 +242,7 @@ export const drawTasks = function (diagram, tasks, verticalPos) { fill, num, colour, + taskCount: taskInSectionCount, }; svgDraw.drawSection(diagram, section, conf); diff --git a/packages/mermaid/src/diagrams/user-journey/svgDraw.js b/packages/mermaid/src/diagrams/user-journey/svgDraw.js index 74d5d2a02..f6dbe71e1 100644 --- a/packages/mermaid/src/diagrams/user-journey/svgDraw.js +++ b/packages/mermaid/src/diagrams/user-journey/svgDraw.js @@ -196,7 +196,10 @@ export const drawSection = function (elem, section, conf) { rect.x = section.x; rect.y = section.y; rect.fill = section.fill; - rect.width = conf.width; + // section width covers all nested tasks + rect.width = + conf.width * section.taskCount + // width of the tasks + conf.diagramMarginX * (section.taskCount - 1); // width of space between tasks rect.height = conf.height; rect.class = 'journey-section section-type-' + section.num; rect.rx = 3; From b9c2f62b47bb23cfe373e31addab0d9da18c8b67 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Mon, 13 Feb 2023 21:31:43 +0530 Subject: [PATCH 02/39] Cleanup classDB --- .../diagrams/class/{classDb.js => classDb.ts} | 156 ++++++++++-------- packages/mermaid/src/utils.ts | 2 +- 2 files changed, 90 insertions(+), 68 deletions(-) rename packages/mermaid/src/diagrams/class/{classDb.js => classDb.ts} (68%) diff --git a/packages/mermaid/src/diagrams/class/classDb.js b/packages/mermaid/src/diagrams/class/classDb.ts similarity index 68% rename from packages/mermaid/src/diagrams/class/classDb.js rename to packages/mermaid/src/diagrams/class/classDb.ts index 2c6690e39..b4a67674e 100644 --- a/packages/mermaid/src/diagrams/class/classDb.js +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -1,4 +1,4 @@ -import { select } from 'd3'; +import { select, Selection } from 'd3'; import { log } from '../../logger'; import * as configApi from '../../config'; import common from '../common/common'; @@ -16,25 +16,48 @@ import { const MERMAID_DOM_ID_PREFIX = 'classid-'; -let relations = []; -let classes = {}; -let notes = []; +interface ClassNode { + id: string; + type: string; + cssClasses: string[]; + methods: string[]; + members: string[]; + annotations: string[]; + domId: string; + link?: string; + linkTarget?: string; + haveCallback?: boolean; + tooltip?: string; +} + +interface ClassNote { + id: string; + class: string; + text: string; +} + +type ClassRelation = any; + +let relations: ClassRelation[] = []; +let classes: Record = {}; +let notes: ClassNote[] = []; let classCounter = 0; -let funs = []; +let functions: any[] = []; -const sanitizeText = (txt) => common.sanitizeText(txt, configApi.getConfig()); +const sanitizeText = (txt: string) => common.sanitizeText(txt, configApi.getConfig()); -export const parseDirective = function (statement, context, type) { +export const parseDirective = function (statement: string, context: string, type: string) { + // @ts-ignore Don't wanna mess it up mermaidAPI.parseDirective(this, statement, context, type); }; -const splitClassNameAndType = function (id) { +const splitClassNameAndType = function (id: string) { let genericType = ''; let className = id; if (id.indexOf('~') > 0) { - let split = id.split('~'); + const split = id.split('~'); className = split[0]; genericType = common.sanitizeText(split[1], configApi.getConfig()); @@ -46,11 +69,11 @@ const splitClassNameAndType = function (id) { /** * Function called by parser when a node definition has been found. * - * @param id + * @param id - Id of the class to add * @public */ -export const addClass = function (id) { - let classId = splitClassNameAndType(id); +export const addClass = function (id: string) { + const classId = splitClassNameAndType(id); // Only add class if not exists if (classes[classId.className] !== undefined) { return; @@ -64,7 +87,7 @@ export const addClass = function (id) { members: [], annotations: [], domId: MERMAID_DOM_ID_PREFIX + classId.className + '-' + classCounter, - }; + } as ClassNode; classCounter++; }; @@ -72,28 +95,26 @@ export const addClass = function (id) { /** * Function to lookup domId from id in the graph definition. * - * @param id + * @param id - class ID to lookup * @public */ -export const lookUpDomId = function (id) { - const classKeys = Object.keys(classes); - for (const classKey of classKeys) { - if (classes[classKey].id === id) { - return classes[classKey].domId; - } +export const lookUpDomId = function (id: string): string { + if (id in classes) { + return classes[id].domId; } + throw new Error('Class not found: ' + id); }; export const clear = function () { relations = []; classes = {}; notes = []; - funs = []; - funs.push(setupToolTips); + functions = []; + functions.push(setupToolTips); commonClear(); }; -export const getClass = function (id) { +export const getClass = function (id: string) { return classes[id]; }; export const getClasses = function () { @@ -108,7 +129,7 @@ export const getNotes = function () { return notes; }; -export const addRelation = function (relation) { +export const addRelation = function (relation: ClassRelation) { log.debug('Adding relation: ' + JSON.stringify(relation)); addClass(relation.id1); addClass(relation.id2); @@ -133,11 +154,11 @@ export const addRelation = function (relation) { * Adds an annotation to the specified class Annotations mark special properties of the given type * (like 'interface' or 'service') * - * @param className The class name - * @param annotation The name of the annotation without any brackets + * @param className - The class name + * @param annotation - The name of the annotation without any brackets * @public */ -export const addAnnotation = function (className, annotation) { +export const addAnnotation = function (className: string, annotation: string) { const validatedClassName = splitClassNameAndType(className).className; classes[validatedClassName].annotations.push(annotation); }; @@ -145,13 +166,13 @@ export const addAnnotation = function (className, annotation) { /** * Adds a member to the specified class * - * @param className The class name - * @param member The full name of the member. If the member is enclosed in <> it is + * @param className - The class name + * @param member - The full name of the member. If the member is enclosed in `<>` it is * treated as an annotation If the member is ending with a closing bracket ) it is treated as a * method Otherwise the member will be treated as a normal property * @public */ -export const addMember = function (className, member) { +export const addMember = function (className: string, member: string) { const validatedClassName = splitClassNameAndType(className).className; const theClass = classes[validatedClassName]; @@ -171,14 +192,14 @@ export const addMember = function (className, member) { } }; -export const addMembers = function (className, members) { +export const addMembers = function (className: string, members: string[]) { if (Array.isArray(members)) { members.reverse(); members.forEach((member) => addMember(className, member)); } }; -export const addNote = function (text, className) { +export const addNote = function (text: string, className: string) { const note = { id: `note${notes.length}`, class: className, @@ -187,21 +208,20 @@ export const addNote = function (text, className) { notes.push(note); }; -export const cleanupLabel = function (label) { - if (label.substring(0, 1) === ':') { - return common.sanitizeText(label.substr(1).trim(), configApi.getConfig()); - } else { - return sanitizeText(label.trim()); +export const cleanupLabel = function (label: string) { + if (label.startsWith(':')) { + label = label.substring(1); } + return sanitizeText(label.trim()); }; /** * Called by parser when a special node is found, e.g. a clickable element. * - * @param ids Comma separated list of ids - * @param className Class to add + * @param ids - Comma separated list of ids + * @param className - Class to add */ -export const setCssClass = function (ids, className) { +export const setCssClass = function (ids: string, className: string) { ids.split(',').forEach(function (_id) { let id = _id; if (_id[0].match(/\d/)) { @@ -216,28 +236,27 @@ export const setCssClass = function (ids, className) { /** * Called by parser when a tooltip is found, e.g. a clickable element. * - * @param ids Comma separated list of ids - * @param tooltip Tooltip to add + * @param ids - Comma separated list of ids + * @param tooltip - Tooltip to add */ -const setTooltip = function (ids, tooltip) { - const config = configApi.getConfig(); +const setTooltip = function (ids: string, tooltip?: string) { ids.split(',').forEach(function (id) { if (tooltip !== undefined) { - classes[id].tooltip = common.sanitizeText(tooltip, config); + classes[id].tooltip = sanitizeText(tooltip); } }); }; -export const getTooltip = function (id) { +export const getTooltip = function (id: string) { return classes[id].tooltip; }; /** * Called by parser when a link is found. Adds the URL to the vertex data. * - * @param ids Comma separated list of ids - * @param linkStr URL to create a link for - * @param target Target of the link, _blank by default as originally defined in the svgDraw.js file + * @param ids - Comma separated list of ids + * @param linkStr - URL to create a link for + * @param target - Target of the link, _blank by default as originally defined in the svgDraw.js file */ -export const setLink = function (ids, linkStr, target) { +export const setLink = function (ids: string, linkStr: string, target: string) { const config = configApi.getConfig(); ids.split(',').forEach(function (_id) { let id = _id; @@ -261,11 +280,11 @@ export const setLink = function (ids, linkStr, target) { /** * Called by parser when a click definition is found. Registers an event handler. * - * @param ids Comma separated list of ids - * @param functionName Function to be called on click - * @param functionArgs Function args the function should be called with + * @param ids - Comma separated list of ids + * @param functionName - Function to be called on click + * @param functionArgs - Function args the function should be called with */ -export const setClickEvent = function (ids, functionName, functionArgs) { +export const setClickEvent = function (ids: string, functionName: string, functionArgs: string) { ids.split(',').forEach(function (id) { setClickFunc(id, functionName, functionArgs); classes[id].haveCallback = true; @@ -273,19 +292,19 @@ export const setClickEvent = function (ids, functionName, functionArgs) { setCssClass(ids, 'clickable'); }; -const setClickFunc = function (domId, functionName, functionArgs) { +const setClickFunc = function (domId: string, functionName: string, functionArgs: string) { const config = configApi.getConfig(); - let id = domId; - let elemId = lookUpDomId(id); - if (config.securityLevel !== 'loose') { return; } if (functionName === undefined) { return; } + + const id = domId; if (classes[id] !== undefined) { - let argList = []; + const elemId = lookUpDomId(id); + let argList: string[] = []; if (typeof functionArgs === 'string') { /* Splits functionArgs by ',', ignoring all ',' in double quoted strings */ argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/); @@ -305,7 +324,7 @@ const setClickFunc = function (domId, functionName, functionArgs) { argList.push(elemId); } - funs.push(function () { + functions.push(function () { const elem = document.querySelector(`[id="${elemId}"]`); if (elem !== null) { elem.addEventListener( @@ -320,8 +339,8 @@ const setClickFunc = function (domId, functionName, functionArgs) { } }; -export const bindFunctions = function (element) { - funs.forEach(function (fun) { +export const bindFunctions = function (element: Element) { + functions.forEach(function (fun) { fun(element); }); }; @@ -339,8 +358,10 @@ export const relationType = { LOLLIPOP: 4, }; -const setupToolTips = function (element) { - let tooltipElem = select('.mermaidTooltip'); +const setupToolTips = function (element: Element) { + let tooltipElem: Selection = + select('.mermaidTooltip'); + // @ts-ignore - _groups is a dynamic property if ((tooltipElem._groups || tooltipElem)[0][0] === null) { tooltipElem = select('body').append('div').attr('class', 'mermaidTooltip').style('opacity', 0); } @@ -352,10 +373,11 @@ const setupToolTips = function (element) { .on('mouseover', function () { const el = select(this); const title = el.attr('title'); - // Dont try to draw a tooltip if no data is provided + // Don't try to draw a tooltip if no data is provided if (title === null) { return; } + // @ts-ignore - getBoundingClientRect is not part of the d3 type definition const rect = this.getBoundingClientRect(); tooltipElem.transition().duration(200).style('opacity', '.9'); @@ -372,11 +394,11 @@ const setupToolTips = function (element) { el.classed('hover', false); }); }; -funs.push(setupToolTips); +functions.push(setupToolTips); let direction = 'TB'; const getDirection = () => direction; -const setDirection = (dir) => { +const setDirection = (dir: string) => { direction = dir; }; diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index 76fce1999..69d0ac2ef 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -230,7 +230,7 @@ export function interpolateToCurve( * @param config - Configuration passed to MermaidJS * @returns The formatted URL or `undefined`. */ -export function formatUrl(linkStr: string, config: { securityLevel: string }): string | undefined { +export function formatUrl(linkStr: string, config: MermaidConfig): string | undefined { const url = linkStr.trim(); if (url) { From 46f2aebabce9cf59ef9715b5e2fb5f4bb5a071c6 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Mon, 13 Feb 2023 22:48:11 +0530 Subject: [PATCH 03/39] Cleanup Renderer --- packages/mermaid/src/config.type.ts | 6 +- .../src/diagrams/class/classRenderer-v2.js | 521 ------------------ .../src/diagrams/class/classRenderer-v2.ts | 359 ++++++++++++ .../mermaid/src/diagrams/class/classTypes.ts | 55 ++ 4 files changed, 419 insertions(+), 522 deletions(-) delete mode 100644 packages/mermaid/src/diagrams/class/classRenderer-v2.js create mode 100644 packages/mermaid/src/diagrams/class/classRenderer-v2.ts create mode 100644 packages/mermaid/src/diagrams/class/classTypes.ts diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 474075824..22781411e 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -268,6 +268,10 @@ export interface ClassDiagramConfig extends BaseDiagramConfig { padding?: number; textHeight?: number; defaultRenderer?: string; + nodeSpacing?: number; + rankSpacing?: number; + diagramPadding?: number; + htmlLabels?: boolean; } export interface JourneyDiagramConfig extends BaseDiagramConfig { @@ -391,4 +395,4 @@ export interface FontConfig { export type FontCalculator = () => Partial; -export {}; +export { }; diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.js b/packages/mermaid/src/diagrams/class/classRenderer-v2.js deleted file mode 100644 index d95c29fd5..000000000 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.js +++ /dev/null @@ -1,521 +0,0 @@ -import { select } from 'd3'; -import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; -import { log } from '../../logger'; -import { getConfig } from '../../config'; -import { render } from '../../dagre-wrapper/index.js'; -import utils from '../../utils'; -import { curveLinear } from 'd3'; -import { interpolateToCurve, getStylesFromArray } from '../../utils'; -import { setupGraphViewbox } from '../../setupGraphViewbox'; -import common from '../common/common'; - -const sanitizeText = (txt) => common.sanitizeText(txt, getConfig()); - -let conf = { - dividerMargin: 10, - padding: 5, - textHeight: 10, -}; - -/** - * Function that adds the vertices found during parsing to the graph to be rendered. - * - * @param {Object< - * string, - * { cssClasses: string[]; text: string; id: string; type: string; domId: string } - * >} classes - * Object containing the vertices. - * @param {SVGGElement} g The graph that is to be drawn. - * @param _id - * @param diagObj - */ -export const addClasses = function (classes, g, _id, diagObj) { - // const svg = select(`[id="${svgId}"]`); - const keys = Object.keys(classes); - log.info('keys:', keys); - log.info(classes); - - // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition - keys.forEach(function (id) { - const vertex = classes[id]; - - /** - * Variable for storing the classes for the vertex - * - * @type {string} - */ - let cssClassStr = ''; - if (vertex.cssClasses.length > 0) { - cssClassStr = cssClassStr + ' ' + vertex.cssClasses.join(' '); - } - // if (vertex.classes.length > 0) { - // classStr = vertex.classes.join(' '); - // } - - const styles = { labelStyle: '' }; //getStylesFromArray(vertex.styles); - - // Use vertex id as text in the box if no text is provided by the graph definition - let vertexText = vertex.text !== undefined ? vertex.text : vertex.id; - - // We create a SVG label, either by delegating to addHtmlLabel or manually - // let vertexNode; - // if (evaluate(getConfig().flowchart.htmlLabels)) { - // const node = { - // label: vertexText.replace( - // eslint-disable-next-line @cspell/spellchecker - // /fa[lrsb]?:fa-[\w-]+/g, - // s => `` - // ) - // }; - // vertexNode = addHtmlLabel(svg, node).node(); - // vertexNode.parentNode.removeChild(vertexNode); - // } else { - // const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); - // svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:')); - - // const rows = vertexText.split(common.lineBreakRegex); - - // for (let j = 0; j < rows.length; j++) { - // const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan'); - // tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); - // tspan.setAttribute('dy', '1em'); - // tspan.setAttribute('x', '1'); - // tspan.textContent = rows[j]; - // svgLabel.appendChild(tspan); - // } - // vertexNode = svgLabel; - // } - - let radious = 0; - let _shape = ''; - // Set the shape based parameters - switch (vertex.type) { - case 'class': - _shape = 'class_box'; - break; - default: - _shape = 'class_box'; - } - // Add the node - g.setNode(vertex.id, { - labelStyle: styles.labelStyle, - shape: _shape, - labelText: sanitizeText(vertexText), - classData: vertex, - rx: radious, - ry: radious, - class: cssClassStr, - style: styles.style, - id: vertex.id, - domId: vertex.domId, - tooltip: diagObj.db.getTooltip(vertex.id) || '', - haveCallback: vertex.haveCallback, - link: vertex.link, - width: vertex.type === 'group' ? 500 : undefined, - type: vertex.type, - padding: getConfig().flowchart.padding, - }); - - log.info('setNode', { - labelStyle: styles.labelStyle, - shape: _shape, - labelText: vertexText, - rx: radious, - ry: radious, - class: cssClassStr, - style: styles.style, - id: vertex.id, - width: vertex.type === 'group' ? 500 : undefined, - type: vertex.type, - padding: getConfig().flowchart.padding, - }); - }); -}; - -/** - * Function that adds the additional vertices (notes) found during parsing to the graph to be rendered. - * - * @param {{text: string; class: string; placement: number}[]} notes - * Object containing the additional vertices (notes). - * @param {SVGGElement} g The graph that is to be drawn. - * @param {number} startEdgeId starting index for note edge - * @param classes - */ -export const addNotes = function (notes, g, startEdgeId, classes) { - log.info(notes); - - // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition - notes.forEach(function (note, i) { - const vertex = note; - - /** - * Variable for storing the classes for the vertex - * - * @type {string} - */ - let cssNoteStr = ''; - - const styles = { labelStyle: '', style: '' }; - - // Use vertex id as text in the box if no text is provided by the graph definition - let vertexText = vertex.text; - - let radious = 0; - let _shape = 'note'; - // Add the node - g.setNode(vertex.id, { - labelStyle: styles.labelStyle, - shape: _shape, - labelText: sanitizeText(vertexText), - noteData: vertex, - rx: radious, - ry: radious, - class: cssNoteStr, - style: styles.style, - id: vertex.id, - domId: vertex.id, - tooltip: '', - type: 'note', - padding: getConfig().flowchart.padding, - }); - - log.info('setNode', { - labelStyle: styles.labelStyle, - shape: _shape, - labelText: vertexText, - rx: radious, - ry: radious, - style: styles.style, - id: vertex.id, - type: 'note', - padding: getConfig().flowchart.padding, - }); - - if (!vertex.class || !(vertex.class in classes)) { - return; - } - const edgeId = startEdgeId + i; - const edgeData = {}; - //Set relationship style and line type - edgeData.classes = 'relation'; - edgeData.pattern = 'dotted'; - - edgeData.id = `edgeNote${edgeId}`; - // Set link type for rendering - edgeData.arrowhead = 'none'; - - log.info(`Note edge: ${JSON.stringify(edgeData)}, ${JSON.stringify(vertex)}`); - //Set edge extra labels - edgeData.startLabelRight = ''; - edgeData.endLabelLeft = ''; - - //Set relation arrow types - edgeData.arrowTypeStart = 'none'; - edgeData.arrowTypeEnd = 'none'; - let style = 'fill:none'; - let labelStyle = ''; - - edgeData.style = style; - edgeData.labelStyle = labelStyle; - - edgeData.curve = interpolateToCurve(conf.curve, curveLinear); - - // Add the edge to the graph - g.setEdge(vertex.id, vertex.class, edgeData, edgeId); - }); -}; - -/** - * Add edges to graph based on parsed graph definition - * - * @param relations - * @param {object} g The graph object - */ -export const addRelations = function (relations, g) { - const conf = getConfig().flowchart; - let cnt = 0; - - let defaultStyle; - let defaultLabelStyle; - - // if (typeof relations.defaultStyle !== 'undefined') { - // const defaultStyles = getStylesFromArray(relations.defaultStyle); - // defaultStyle = defaultStyles.style; - // defaultLabelStyle = defaultStyles.labelStyle; - // } - - relations.forEach(function (edge) { - cnt++; - const edgeData = {}; - //Set relationship style and line type - edgeData.classes = 'relation'; - edgeData.pattern = edge.relation.lineType == 1 ? 'dashed' : 'solid'; - - edgeData.id = 'id' + cnt; - // Set link type for rendering - if (edge.type === 'arrow_open') { - edgeData.arrowhead = 'none'; - } else { - edgeData.arrowhead = 'normal'; - } - - log.info(edgeData, edge); - //Set edge extra labels - //edgeData.startLabelLeft = edge.relationTitle1; - edgeData.startLabelRight = edge.relationTitle1 === 'none' ? '' : edge.relationTitle1; - edgeData.endLabelLeft = edge.relationTitle2 === 'none' ? '' : edge.relationTitle2; - //edgeData.endLabelRight = edge.relationTitle2; - - //Set relation arrow types - edgeData.arrowTypeStart = getArrowMarker(edge.relation.type1); - edgeData.arrowTypeEnd = getArrowMarker(edge.relation.type2); - let style = ''; - let labelStyle = ''; - - if (edge.style !== undefined) { - const styles = getStylesFromArray(edge.style); - style = styles.style; - labelStyle = styles.labelStyle; - } else { - style = 'fill:none'; - if (defaultStyle !== undefined) { - style = defaultStyle; - } - if (defaultLabelStyle !== undefined) { - labelStyle = defaultLabelStyle; - } - } - - edgeData.style = style; - edgeData.labelStyle = labelStyle; - - if (edge.interpolate !== undefined) { - edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear); - } else if (relations.defaultInterpolate !== undefined) { - edgeData.curve = interpolateToCurve(relations.defaultInterpolate, curveLinear); - } else { - edgeData.curve = interpolateToCurve(conf.curve, curveLinear); - } - - edge.text = edge.title; - if (edge.text === undefined) { - if (edge.style !== undefined) { - edgeData.arrowheadStyle = 'fill: #333'; - } - } else { - edgeData.arrowheadStyle = 'fill: #333'; - edgeData.labelpos = 'c'; - - if (getConfig().flowchart.htmlLabels) { - edgeData.labelType = 'html'; - edgeData.label = '' + edge.text + ''; - } else { - edgeData.labelType = 'text'; - edgeData.label = edge.text.replace(common.lineBreakRegex, '\n'); - - if (edge.style === undefined) { - edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none'; - } - - edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:'); - } - } - // Add the edge to the graph - g.setEdge(edge.id1, edge.id2, edgeData, cnt); - }); -}; - -/** - * Merges the value of `conf` with the passed `cnf` - * - * @param {object} cnf Config to merge - */ -export const setConf = function (cnf) { - const keys = Object.keys(cnf); - - keys.forEach(function (key) { - conf[key] = cnf[key]; - }); -}; - -/** - * Draws a flowchart in the tag with id: id based on the graph definition in text. - * - * @param {string} text - * @param {string} id - * @param _version - * @param diagObj - */ -export const draw = function (text, id, _version, diagObj) { - log.info('Drawing class - ', id); - // diagObj.db.clear(); - // const parser = diagObj.db.parser; - // parser.yy = classDb; - - // Parse the graph definition - // try { - // parser.parse(text); - // } catch (err) { - // log.debug('Parsing failed'); - // } - - // Fetch the default direction, use TD if none was found - //let dir = 'TD'; - - const conf = getConfig().flowchart; - const securityLevel = getConfig().securityLevel; - log.info('config:', conf); - const nodeSpacing = conf.nodeSpacing || 50; - const rankSpacing = conf.rankSpacing || 50; - - // Create the input mermaid.graph - const g = new graphlib.Graph({ - multigraph: true, - compound: true, - }) - .setGraph({ - rankdir: diagObj.db.getDirection(), - nodesep: nodeSpacing, - ranksep: rankSpacing, - marginx: 8, - marginy: 8, - }) - .setDefaultEdgeLabel(function () { - return {}; - }); - - // let subG; - // const subGraphs = flowDb.getSubGraphs(); - // log.info('Subgraphs - ', subGraphs); - // for (let i = subGraphs.length - 1; i >= 0; i--) { - // subG = subGraphs[i]; - // log.info('Subgraph - ', subG); - // flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes); - // } - - // Fetch the vertices/nodes and edges/links from the parsed graph definition - const classes = diagObj.db.getClasses(); - const relations = diagObj.db.getRelations(); - const notes = diagObj.db.getNotes(); - - log.info(relations); - addClasses(classes, g, id, diagObj); - addRelations(relations, g); - addNotes(notes, g, relations.length + 1, classes); - - // Add custom shapes - // flowChartShapes.addToRenderV2(addShape); - - // Set up an SVG group so that we can translate the final graph. - let sandboxElement; - if (securityLevel === 'sandbox') { - sandboxElement = select('#i' + id); - } - const root = - securityLevel === 'sandbox' - ? select(sandboxElement.nodes()[0].contentDocument.body) - : select('body'); - const svg = root.select(`[id="${id}"]`); - - // Run the renderer. This is what draws the final graph. - const element = root.select('#' + id + ' g'); - render( - element, - g, - ['aggregation', 'extension', 'composition', 'dependency', 'lollipop'], - 'classDiagram', - id - ); - - utils.insertTitle(svg, 'classTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle()); - - setupGraphViewbox(g, svg, conf.diagramPadding, conf.useMaxWidth); - - // Add label rects for non html labels - if (!conf.htmlLabels) { - const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; - const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); - for (const label of labels) { - // Get dimensions of label - const dim = label.getBBox(); - - const rect = doc.createElementNS('http://www.w3.org/2000/svg', 'rect'); - rect.setAttribute('rx', 0); - rect.setAttribute('ry', 0); - rect.setAttribute('width', dim.width); - rect.setAttribute('height', dim.height); - // rect.setAttribute('style', 'fill:#e8e8e8;'); - - label.insertBefore(rect, label.firstChild); - } - } - - // If node has a link, wrap it in an anchor SVG object. - // const keys = Object.keys(classes); - // keys.forEach(function(key) { - // const vertex = classes[key]; - - // if (vertex.link) { - // const node = select('#' + id + ' [id="' + key + '"]'); - // if (node) { - // const link = document.createElementNS('http://www.w3.org/2000/svg', 'a'); - // link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' ')); - // link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link); - // link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener'); - - // const linkNode = node.insert(function() { - // return link; - // }, ':first-child'); - - // const shape = node.select('.label-container'); - // if (shape) { - // linkNode.append(function() { - // return shape.node(); - // }); - // } - - // const label = node.select('.label'); - // if (label) { - // linkNode.append(function() { - // return label.node(); - // }); - // } - // } - // } - // }); -}; - -/** - * Gets the arrow marker for a type index - * - * @param {number} type The type to look for - * @returns {'aggregation' | 'extension' | 'composition' | 'dependency'} The arrow marker - */ -function getArrowMarker(type) { - let marker; - switch (type) { - case 0: - marker = 'aggregation'; - break; - case 1: - marker = 'extension'; - break; - case 2: - marker = 'composition'; - break; - case 3: - marker = 'dependency'; - break; - case 4: - marker = 'lollipop'; - break; - default: - marker = 'none'; - } - return marker; -} - -export default { - setConf, - draw, -}; diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts new file mode 100644 index 000000000..0517b800c --- /dev/null +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -0,0 +1,359 @@ +import { select } from 'd3'; +import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; +import { log } from '../../logger'; +import { getConfig } from '../../config'; +import { render } from '../../dagre-wrapper/index.js'; +import utils from '../../utils'; +import { curveLinear } from 'd3'; +import { interpolateToCurve, getStylesFromArray } from '../../utils'; +import { setupGraphViewbox } from '../../setupGraphViewbox'; +import common from '../common/common'; +import { ClassRelation, ClassNode, ClassNote, ClassMap, EdgeData } from './classTypes'; + +const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig()); + +let conf = { + dividerMargin: 10, + padding: 5, + textHeight: 10, + curve: undefined +}; + +/** + * Function that adds the vertices found during parsing to the graph to be rendered. + * + * @param classes - Object containing the vertices. + * @param g - The graph that is to be drawn. + * @param _id - id of the graph + * @param diagObj - The diagram object + */ +export const addClasses = function (classes: ClassMap, g: graphlib.Graph, _id: string, diagObj: any) { + const keys = Object.keys(classes); + log.info('keys:', keys); + log.info(classes); + + // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition + keys.forEach(function (id) { + const vertex = classes[id]; + + /** + * Variable for storing the classes for the vertex + */ + let cssClassStr = ''; + if (vertex.cssClasses.length > 0) { + cssClassStr = cssClassStr + ' ' + vertex.cssClasses.join(' '); + } + + const styles = { labelStyle: '', style: '' }; //getStylesFromArray(vertex.styles); + + // Use vertex id as text in the box if no text is provided by the graph definition + const vertexText = vertex.label ?? vertex.id; + const radius = 0; + const shape = 'class_box'; + // Add the node + const node = { + labelStyle: styles.labelStyle, + shape: shape, + labelText: sanitizeText(vertexText), + classData: vertex, + rx: radius, + ry: radius, + class: cssClassStr, + style: styles.style, + id: vertex.id, + domId: vertex.domId, + tooltip: diagObj.db.getTooltip(vertex.id) || '', + haveCallback: vertex.haveCallback, + link: vertex.link, + width: vertex.type === 'group' ? 500 : undefined, + type: vertex.type, + // TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release + padding: getConfig().flowchart?.padding ?? getConfig().class?.padding, + }; + g.setNode(vertex.id, node); + log.info('setNode', node); + }); +}; + +/** + * Function that adds the additional vertices (notes) found during parsing to the graph to be rendered. + * + * @param notes - Object containing the additional vertices (notes). + * @param g - The graph that is to be drawn. + * @param startEdgeId - starting index for note edge + * @param classes - Classes + */ +export const addNotes = function (notes: ClassNote[], g: graphlib.Graph, startEdgeId: number, classes: ClassMap) { + log.info(notes); + + // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition + notes.forEach(function (note, i) { + const vertex = note; + + /** + * Variable for storing the classes for the vertex + * + */ + const cssNoteStr = ''; + + const styles = { labelStyle: '', style: '' }; + + // Use vertex id as text in the box if no text is provided by the graph definition + const vertexText = vertex.text; + + const radius = 0; + const shape = 'note'; + // Add the node + const node = { + labelStyle: styles.labelStyle, + shape: shape, + labelText: sanitizeText(vertexText), + noteData: vertex, + rx: radius, + ry: radius, + class: cssNoteStr, + style: styles.style, + id: vertex.id, + domId: vertex.id, + tooltip: '', + type: 'note', + // TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release + padding: getConfig().flowchart?.padding ?? getConfig().class?.padding, + }; + g.setNode(vertex.id, node); + log.info('setNode', node); + + if (!vertex.class || !(vertex.class in classes)) { + return; + } + const edgeId = startEdgeId + i; + + const edgeData: EdgeData = { + id: `edgeNote${edgeId}`, + //Set relationship style and line type + classes: 'relation', + pattern: 'dotted', + // Set link type for rendering + arrowhead: 'none', + //Set edge extra labels + startLabelRight: '', + endLabelLeft: '', + //Set relation arrow types + arrowTypeStart: 'none', + arrowTypeEnd: 'none', + style: 'fill:none', + labelStyle: '', + curve: interpolateToCurve(conf.curve, curveLinear), + }; + + // Add the edge to the graph + g.setEdge(vertex.id, vertex.class, edgeData, edgeId); + }); +}; + +/** + * Add edges to graph based on parsed graph definition + * + * @param relations - + * @param g - The graph object + */ +export const addRelations = function (relations: ClassRelation[], g: graphlib.Graph) { + const conf = getConfig().flowchart; + let cnt = 0; + + relations.forEach(function (edge) { + cnt++; + const edgeData: EdgeData = { + //Set relationship style and line type + classes: 'relation', + pattern: edge.relation.lineType == 1 ? 'dashed' : 'solid', + id: 'id' + cnt, + // Set link type for rendering + arrowhead: edge.type === 'arrow_open' ? 'none' : 'normal', + //Set edge extra labels + startLabelRight: edge.relationTitle1 === 'none' ? '' : edge.relationTitle1, + endLabelLeft: edge.relationTitle2 === 'none' ? '' : edge.relationTitle2, + //Set relation arrow types + arrowTypeStart: getArrowMarker(edge.relation.type1), + arrowTypeEnd: getArrowMarker(edge.relation.type2), + style: 'fill:none', + labelStyle: '', + curve: interpolateToCurve(conf?.curve, curveLinear) + } + + log.info(edgeData, edge); + + + if (edge.style !== undefined) { + const styles = getStylesFromArray(edge.style); + edgeData.style = styles.style; + edgeData.labelStyle = styles.labelStyle; + } + + + edge.text = edge.title; + if (edge.text === undefined) { + if (edge.style !== undefined) { + edgeData.arrowheadStyle = 'fill: #333'; + } + } else { + edgeData.arrowheadStyle = 'fill: #333'; + edgeData.labelpos = 'c'; + + // TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release + if (getConfig().flowchart?.htmlLabels ?? getConfig().htmlLabels) { + edgeData.labelType = 'html'; + edgeData.label = '' + edge.text + ''; + } else { + edgeData.labelType = 'text'; + edgeData.label = edge.text.replace(common.lineBreakRegex, '\n'); + + if (edge.style === undefined) { + edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none'; + } + + edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:'); + } + } + // Add the edge to the graph + g.setEdge(edge.id1, edge.id2, edgeData, cnt); + }); +}; + +/** + * Merges the value of `conf` with the passed `cnf` + * + * @param cnf - Config to merge + */ +export const setConf = function (cnf: any) { + conf = { + ...conf, ...cnf + } +}; + +/** + * Draws a flowchart in the tag with id: id based on the graph definition in text. + * + * @param text - + * @param id - + * @param _version - + * @param diagObj - + */ +export const draw = function (text: string, id: string, _version: string, diagObj: any) { + log.info('Drawing class - ', id); + + // TODO V10: Why flowchart? Might be a mistake when copying. + const conf = getConfig().flowchart ?? getConfig().class; + const securityLevel = getConfig().securityLevel; + log.info('config:', conf); + const nodeSpacing = conf?.nodeSpacing ?? 50; + const rankSpacing = conf?.rankSpacing ?? 50; + + // Create the input mermaid.graph + const g: graphlib.Graph = new graphlib.Graph({ + multigraph: true, + compound: true, + }) + .setGraph({ + rankdir: diagObj.db.getDirection(), + nodesep: nodeSpacing, + ranksep: rankSpacing, + marginx: 8, + marginy: 8, + }) + .setDefaultEdgeLabel(function () { + return {}; + }); + + // Fetch the vertices/nodes and edges/links from the parsed graph definition + const classes: ClassMap = diagObj.db.getClasses(); + const relations: ClassRelation[] = diagObj.db.getRelations(); + const notes: ClassNote[] = diagObj.db.getNotes(); + log.info(relations); + addClasses(classes, g, id, diagObj); + addRelations(relations, g); + addNotes(notes, g, relations.length + 1, classes); + + // Set up an SVG group so that we can translate the final graph. + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + // @ts-ignore Ignore type error for now + + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + // @ts-ignore Ignore type error for now + const svg = root.select(`[id="${id}"]`); + + // Run the renderer. This is what draws the final graph. + // @ts-ignore Ignore type error for now + const element = root.select('#' + id + ' g'); + render( + element, + g, + ['aggregation', 'extension', 'composition', 'dependency', 'lollipop'], + 'classDiagram', + id + ); + + utils.insertTitle(svg, 'classTitleText', conf?.titleTopMargin ?? 5, diagObj.db.getDiagramTitle()); + + setupGraphViewbox(g, svg, conf?.diagramPadding, conf?.useMaxWidth); + + // Add label rects for non html labels + if (!conf?.htmlLabels) { + // @ts-ignore Ignore type error for now + const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; + const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); + for (const label of labels) { + // Get dimensions of label + const dim = label.getBBox(); + + const rect = doc.createElementNS('http://www.w3.org/2000/svg', 'rect'); + rect.setAttribute('rx', 0); + rect.setAttribute('ry', 0); + rect.setAttribute('width', dim.width); + rect.setAttribute('height', dim.height); + + label.insertBefore(rect, label.firstChild); + } + } +}; + +/** + * Gets the arrow marker for a type index + * + * @param type - The type to look for + * @returns The arrow marker + */ +function getArrowMarker(type: number) { + let marker; + switch (type) { + case 0: + marker = 'aggregation'; + break; + case 1: + marker = 'extension'; + break; + case 2: + marker = 'composition'; + break; + case 3: + marker = 'dependency'; + break; + case 4: + marker = 'lollipop'; + break; + default: + marker = 'none'; + } + return marker; +} + +export default { + setConf, + draw, +}; diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts new file mode 100644 index 000000000..3d7ed1488 --- /dev/null +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -0,0 +1,55 @@ +export interface ClassNode { + id: string; + type: string; + label: string; + cssClasses: string[]; + methods: string[]; + members: string[]; + annotations: string[]; + domId: string; + link?: string; + linkTarget?: string; + haveCallback?: boolean; + tooltip?: string; +} + +export interface ClassNote { + id: string; + class: string; + text: string; +} + +export interface EdgeData { + arrowheadStyle?: string; + labelpos?: string; + labelType?: string; + label?: string; + classes: string; + pattern: string; + id: string; + arrowhead: string; + startLabelRight: string; + endLabelLeft: string; + arrowTypeStart: string; + arrowTypeEnd: string; + style: string; + labelStyle: string; + curve: any; +} + +export type ClassRelation = { + id1: string, + id2: string, + relationTitle1: string, + relationTitle2: string, + type: string, + title: string, + text: string, + style: string[], + relation: { + type1: number, + type2: number, + lineType: number + } +}; +export type ClassMap = Record \ No newline at end of file From 102900749ec3d77f73eb21bb8309eac180995e6f Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Tue, 14 Feb 2023 00:36:43 +0530 Subject: [PATCH 04/39] Add support for classDiagram labels --- cSpell.json | 24 ++++++++-- cypress/helpers/util.js | 2 +- packages/mermaid/src/dagre-wrapper/nodes.js | 2 +- .../mermaid/src/diagrams/class/classDb.ts | 46 ++++++------------- .../src/diagrams/class/classRenderer-v2.ts | 2 +- .../diagrams/class/parser/classDiagram.jison | 19 ++++++-- 6 files changed, 52 insertions(+), 43 deletions(-) diff --git a/cSpell.json b/cSpell.json index 6f93af103..ec2daed59 100644 --- a/cSpell.json +++ b/cSpell.json @@ -19,6 +19,7 @@ "brkt", "brolin", "brotli", + "Città", "classdef", "codedoc", "colour", @@ -109,7 +110,11 @@ "yash" ], "patterns": [ - { "name": "Markdown links", "pattern": "\\((.*)\\)", "description": "" }, + { + "name": "Markdown links", + "pattern": "\\((.*)\\)", + "description": "" + }, { "name": "Markdown code blocks", "pattern": "/^(\\s*`{3,}).*[\\s\\S]*?^\\1/gmx", @@ -120,14 +125,25 @@ "pattern": "\\`([^\\`\\r\\n]+?)\\`", "description": "https://stackoverflow.com/questions/41274241/how-to-capture-inline-markdown-code-but-not-a-markdown-code-fence-with-regex" }, - { "name": "Link contents", "pattern": "\\", "description": "" }, - { "name": "Snippet references", "pattern": "-- snippet:(.*)", "description": "" }, + { + "name": "Link contents", + "pattern": "\\", + "description": "" + }, + { + "name": "Snippet references", + "pattern": "-- snippet:(.*)", + "description": "" + }, { "name": "Snippet references 2", "pattern": "\\<\\[sample:(.*)", "description": "another kind of snippet reference" }, - { "name": "Multi-line code blocks", "pattern": "/^\\s*```[\\s\\S]*?^\\s*```/gm" }, + { + "name": "Multi-line code blocks", + "pattern": "/^\\s*```[\\s\\S]*?^\\s*```/gm" + }, { "name": "HTML Tags", "pattern": "<[^>]*>", diff --git a/cypress/helpers/util.js b/cypress/helpers/util.js index 533cca499..7ec960b97 100644 --- a/cypress/helpers/util.js +++ b/cypress/helpers/util.js @@ -22,7 +22,7 @@ export const mermaidUrl = (graphStr, options, api) => { return url; }; -export const imgSnapshotTest = (graphStr, _options, api = false, validation) => { +export const imgSnapshotTest = (graphStr, _options = {}, api = false, validation = undefined) => { cy.log(_options); const options = Object.assign(_options); if (!options.fontFamily) { diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js index 694ba074d..f684ad110 100644 --- a/packages/mermaid/src/dagre-wrapper/nodes.js +++ b/packages/mermaid/src/dagre-wrapper/nodes.js @@ -772,7 +772,7 @@ const class_box = (parent, node) => { maxWidth += interfaceBBox.width; } - let classTitleString = node.classData.id; + let classTitleString = node.classData.label; if (node.classData.type !== undefined && node.classData.type !== '') { if (getConfig().flowchart.htmlLabels) { diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index b4a67674e..451e66f0a 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -13,33 +13,12 @@ import { setDiagramTitle, getDiagramTitle, } from '../../commonDb'; +import { ClassRelation, ClassNode, ClassNote, ClassMap } from './classTypes'; -const MERMAID_DOM_ID_PREFIX = 'classid-'; - -interface ClassNode { - id: string; - type: string; - cssClasses: string[]; - methods: string[]; - members: string[]; - annotations: string[]; - domId: string; - link?: string; - linkTarget?: string; - haveCallback?: boolean; - tooltip?: string; -} - -interface ClassNote { - id: string; - class: string; - text: string; -} - -type ClassRelation = any; +const MERMAID_DOM_ID_PREFIX = 'classId-'; let relations: ClassRelation[] = []; -let classes: Record = {}; +let classes: ClassMap = {}; let notes: ClassNote[] = []; let classCounter = 0; @@ -58,9 +37,8 @@ const splitClassNameAndType = function (id: string) { if (id.indexOf('~') > 0) { const split = id.split('~'); - className = split[0]; - - genericType = common.sanitizeText(split[1], configApi.getConfig()); + className = sanitizeText(split[0]); + genericType = sanitizeText(split[1]); } return { className: className, type: genericType }; @@ -70,18 +48,24 @@ const splitClassNameAndType = function (id: string) { * Function called by parser when a node definition has been found. * * @param id - Id of the class to add + * @param label - Optional label of the class * @public */ -export const addClass = function (id: string) { +export const addClass = function (id: string, label?: string) { const classId = splitClassNameAndType(id); // Only add class if not exists if (classes[classId.className] !== undefined) { return; } + if (label) { + label = sanitizeText(label); + } + classes[classId.className] = { id: classId.className, type: classId.type, + label: label ?? classId.className, cssClasses: [], methods: [], members: [], @@ -121,7 +105,7 @@ export const getClasses = function () { return classes; }; -export const getRelations = function () { +export const getRelations = function (): ClassRelation[] { return relations; }; @@ -182,7 +166,6 @@ export const addMember = function (className: string, member: string) { if (memberString.startsWith('<<') && memberString.endsWith('>>')) { // Remove leading and trailing brackets - // theClass.annotations.push(memberString.substring(2, memberString.length - 2)); theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2))); } else if (memberString.indexOf(')') > 0) { theClass.methods.push(sanitizeText(memberString)); @@ -359,8 +342,7 @@ export const relationType = { }; const setupToolTips = function (element: Element) { - let tooltipElem: Selection = - select('.mermaidTooltip'); + let tooltipElem: Selection = select('.mermaidTooltip'); // @ts-ignore - _groups is a dynamic property if ((tooltipElem._groups || tooltipElem)[0][0] === null) { tooltipElem = select('body').append('div').attr('class', 'mermaidTooltip').style('opacity', 0); diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index 0517b800c..12f76ec35 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -8,7 +8,7 @@ import { curveLinear } from 'd3'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import common from '../common/common'; -import { ClassRelation, ClassNode, ClassNote, ClassMap, EdgeData } from './classTypes'; +import { ClassRelation, ClassNote, ClassMap, EdgeData } from './classTypes'; const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig()); diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 157e3d7d8..dec487b11 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -119,6 +119,8 @@ Function arguments are optional: 'call ()' simply executes 'callb "=" return 'EQUALS'; \= return 'EQUALS'; \w+ return 'ALPHA'; +"[" return 'SQS'; +"]" return 'SQE'; [!"#$%&'*+,-.`?\\/] return 'PUNCTUATION'; [0-9]+ return 'NUM'; [\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]| @@ -249,6 +251,10 @@ statements | statement NEWLINE statements ; +classLabel + : SQS STR SQE { $$=$2; } + ; + className : alphaNumToken { $$=$1; } | classLiteralName { $$=$1; } @@ -274,10 +280,15 @@ statement ; classStatement - : CLASS className {yy.addClass($2);} - | CLASS className STYLE_SEPARATOR alphaNumToken {yy.addClass($2);yy.setCssClass($2, $4);} - | CLASS className STRUCT_START members STRUCT_STOP {/*console.log($2,JSON.stringify($4));*/yy.addClass($2);yy.addMembers($2,$4);} - | CLASS className STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.addClass($2);yy.setCssClass($2, $4);yy.addMembers($2,$6);} + : classIdentifier + | classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);} + | classIdentifier STRUCT_START members STRUCT_STOP {yy.addMembers($1,$3);} + | classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$3);} + ; + +classIdentifier + : CLASS className {$$=$2; yy.addClass($2);} + | CLASS className classLabel {$$=$2; yy.addClass($2, $3);} ; annotationStatement From 17e317385afcbb816b9f23ee995a8c2be316f97e Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Tue, 14 Feb 2023 00:37:17 +0530 Subject: [PATCH 05/39] Formatting --- packages/mermaid/src/diagrams/class/classRenderer-v2.ts | 4 ++-- packages/mermaid/src/diagrams/class/parser/classDiagram.jison | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index 12f76ec35..14af5308a 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -154,7 +154,7 @@ export const addNotes = function (notes: ClassNote[], g: graphlib.Graph, startEd /** * Add edges to graph based on parsed graph definition * - * @param relations - + * @param relations - * @param g - The graph object */ export const addRelations = function (relations: ClassRelation[], g: graphlib.Graph) { @@ -234,7 +234,7 @@ export const setConf = function (cnf: any) { /** * Draws a flowchart in the tag with id: id based on the graph definition in text. * - * @param text - + * @param text - * @param id - * @param _version - * @param diagObj - diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index dec487b11..bf06a81dc 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -280,7 +280,7 @@ statement ; classStatement - : classIdentifier + : classIdentifier | classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);} | classIdentifier STRUCT_START members STRUCT_STOP {yy.addMembers($1,$3);} | classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$3);} From ef4fbd8bb3aa3e0b14646bc2861dfb177fd4449e Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Tue, 14 Feb 2023 00:54:42 +0530 Subject: [PATCH 06/39] classLabel tests --- .../rendering/classDiagram-v2.spec.js | 82 +++++--- cypress/platform/class.html | 9 - ...ssDiagram.spec.js => classDiagram.spec.ts} | 194 +++++++++++++++++- 3 files changed, 248 insertions(+), 37 deletions(-) rename packages/mermaid/src/diagrams/class/{classDiagram.spec.js => classDiagram.spec.ts} (81%) diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js index 9536a074d..26b382da4 100644 --- a/cypress/integration/rendering/classDiagram-v2.spec.js +++ b/cypress/integration/rendering/classDiagram-v2.spec.js @@ -13,7 +13,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('1: should render a simple class diagram', () => { @@ -47,7 +46,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('2: should render a simple class diagrams with cardinality', () => { @@ -76,7 +74,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('should render a simple class diagram with different visibilities', () => { @@ -94,7 +91,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('should render multiple class diagrams', () => { @@ -147,7 +143,6 @@ describe('Class diagram V2', () => { ], { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('4: should render a simple class diagram with comments', () => { @@ -177,7 +172,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('5: should render a simple class diagram with abstract method', () => { @@ -189,7 +183,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('6: should render a simple class diagram with static method', () => { @@ -201,7 +194,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('7: should render a simple class diagram with Generic class', () => { @@ -221,7 +213,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('8: should render a simple class diagram with Generic class and relations', () => { @@ -242,7 +233,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('9: should render a simple class diagram with clickable link', () => { @@ -264,7 +254,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('10: should render a simple class diagram with clickable callback', () => { @@ -286,7 +275,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('11: should render a simple class diagram with return type on method', () => { @@ -301,7 +289,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('12: should render a simple class diagram with generic types', () => { @@ -317,7 +304,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('13: should render a simple class diagram with css classes applied', () => { @@ -335,7 +321,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('14: should render a simple class diagram with css classes applied directly', () => { @@ -351,7 +336,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('15: should render a simple class diagram with css classes applied two multiple classes', () => { @@ -365,7 +349,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('16a: should render a simple class diagram with static field', () => { @@ -378,7 +361,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('16b: should handle the direction statement with TB', () => { @@ -403,7 +385,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('18: should handle the direction statement with LR', () => { @@ -428,7 +409,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('17a: should handle the direction statement with BT', () => { imgSnapshotTest( @@ -452,7 +432,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('17b: should handle the direction statement with RL', () => { imgSnapshotTest( @@ -476,7 +455,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('18: should render a simple class diagram with notes', () => { @@ -493,7 +471,6 @@ describe('Class diagram V2', () => { `, { logLevel: 1, flowchart: { htmlLabels: false } } ); - cy.get('svg'); }); it('1433: should render a simple class with a title', () => { @@ -503,8 +480,63 @@ title: simple class diagram --- classDiagram-v2 class Class10 -`, - {} +` + ); + }); + + it('should render a class with text label', () => { + imgSnapshotTest( + `classDiagram + class C1["Class 1 with text label"] + C1 --> C2` + ); + }); + + it('should render two classes with text labels', () => { + imgSnapshotTest( + `classDiagram + class C1["Class 1 with text label"] + class C2["Class 2 with chars @?"] + C1 --> C2` + ); + }); + it('should render a class with a text label, members and annotation', () => { + imgSnapshotTest( + `classDiagram + class C1["Class 1 with text label"] { + <> + +member1 + } + C1 --> C2` + ); + }); + it('should render multiple classes with same text labels', () => { + imgSnapshotTest( + `classDiagram +class C1["Class with text label"] +class C2["Class with text label"] +class C3["Class with text label"] +C1 --> C2 +C3 ..> C2 + ` + ); + }); + it('should render classes with different text labels', () => { + imgSnapshotTest( + `classDiagram +class C1["OneWord"] +class C2["With, Comma"] +class C3["With (Brackets)"] +class C4["With [Brackets]"] +class C5["With {Brackets}"] +class C7["With 1 number"] +class C8["With . period..."] +class C9["With - dash"] +class C10["With _ underscore"] +class C11["With ' single quote"] +class C12["With ~!@#$%^&*()_+=-/?"] +class C13["With Città foreign language"] + ` ); }); }); diff --git a/cypress/platform/class.html b/cypress/platform/class.html index 1d72c34a5..ea0914c6d 100644 --- a/cypress/platform/class.html +++ b/cypress/platform/class.html @@ -12,7 +12,6 @@ -
-%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
 graph TB
       a --> b
       a --> c
       b --> d
       c --> d
     
-
-flowchart-elk LR
-  subgraph A
-  a --> b
-  end
-  subgraph B
-  b
-  end
-    
-
-%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
-flowchart TB
-  %% I could not figure out how to use double quotes in labels in Mermaid
-  subgraph ibm[IBM Espresso CPU]
-    core0[IBM PowerPC Broadway Core 0]
-    core1[IBM PowerPC Broadway Core 1]
-    core2[IBM PowerPC Broadway Core 2]
 
-    rom[16 KB ROM]
-
-    core0 --- core2
-
-    rom --> core2
-  end
-
-  subgraph amd[AMD Latte GPU]
-    mem[Memory & I/O Bridge]
-    dram[DRAM Controller]
-    edram[32 MB EDRAM MEM1]
-    rom[512 B SEEPROM]
-
-    sata[SATA IF]
-    exi[EXI]
-
-    subgraph gx[GX]
-      sram[3 MB 1T-SRAM]
-    end
-
-    radeon[AMD Radeon R7xx GX2]
-
-    mem --- gx
-    mem --- radeon
-
-    rom --- mem
-
-    mem --- sata
-    mem --- exi
-
-    dram --- sata
-    dram --- exi
-  end
-
-  ddr3[2 GB DDR3 RAM MEM2]
-
-  mem --- ddr3
-  dram --- ddr3
-  edram --- ddr3
-
-  core1 --- mem
-
-  exi --- rtc
-  rtc{{rtc}}
-
-
-
-flowchart TB
-  %% I could not figure out how to use double quotes in labels in Mermaid
-  subgraph ibm[IBM Espresso CPU]
-    core0[IBM PowerPC Broadway Core 0]
-    core1[IBM PowerPC Broadway Core 1]
-    core2[IBM PowerPC Broadway Core 2]
-
-    rom[16 KB ROM]
-
-    core0 --- core2
-
-    rom --> core2
-  end
-
-  subgraph amd[AMD Latte GPU]
-    mem[Memory & I/O Bridge]
-    dram[DRAM Controller]
-    edram[32 MB EDRAM MEM1]
-    rom[512 B SEEPROM]
-
-    sata[SATA IF]
-    exi[EXI]
-
-    subgraph gx[GX]
-      sram[3 MB 1T-SRAM]
-    end
-
-    radeon[AMD Radeon R7xx GX2]
-
-    mem --- gx
-    mem --- radeon
-
-    rom --- mem
-
-    mem --- sata
-    mem --- exi
-
-    dram --- sata
-    dram --- exi
-  end
-
-  ddr3[2 GB DDR3 RAM MEM2]
-
-  mem --- ddr3
-  dram --- ddr3
-  edram --- ddr3
-
-  core1 --- mem
-
-  exi --- rtc
-  rtc{{rtc}}
-
-
-   -
-      flowchart LR
-  B1 --be be--x B2
-  B1 --bo bo--o B3
-  subgraph Ugge
-      B2
-      B3
-      subgraph inner
-          B4
-          B5
-      end
-      subgraph inner2
-        subgraph deeper
-          C4
-          C5
-        end
-        C6
-      end
-
-      B4 --> C4
-
-      B3 -- X --> B4
-      B2 --> inner
-
-      C4 --> C5
-  end
-
-  subgraph outer
-      B6
-  end
-  B6 --> B5
-  
-
-sequenceDiagram
-    Customer->>+Stripe: Makes a payment request
-    Stripe->>+Bank: Forwards the payment request to the bank
-    Bank->>+Customer: Asks for authorization
-    Customer->>+Bank: Provides authorization
-    Bank->>+Stripe: Sends a response with payment details
-    Stripe->>+Merchant: Sends a notification of payment receipt
-    Merchant->>+Stripe: Confirms the payment
-    Stripe->>+Customer: Sends a confirmation of payment
-    Customer->>+Merchant: Receives goods or services
-        
-
-      gantt
-        title Style today marker (vertical line should be 5px wide and half-transparent blue)
-        dateFormat YYYY-MM-DD
-        axisFormat %d
-        todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
-        section Section1
-        Today: 1, -1h
-    
+
+ From 716a4d2cbcdf4e55fd3bfd4c1fe06cd6bceb696b Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 24 Feb 2023 13:29:34 +0530 Subject: [PATCH 22/39] Update import --- packages/mermaid/src/diagrams/class/classRenderer-v2.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index 637ff9392..e308990c6 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -1,10 +1,10 @@ -import { select } from 'd3'; +// @ts-ignore d3 types are not available +import { select, curveLinear } from 'd3'; import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { log } from '../../logger'; import { getConfig } from '../../config'; import { render } from '../../dagre-wrapper/index.js'; import utils from '../../utils'; -import { curveLinear } from 'd3'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import common from '../common/common'; From b13707fa7b10c782f4ecdcbeff42795f7dd3c0a0 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 24 Feb 2023 13:46:56 +0530 Subject: [PATCH 23/39] fix: Class label not visible if class is already defined --- .../rendering/classDiagram-v2.spec.js | 9 ++++++++ demos/pie.html | 2 +- .../mermaid/src/diagrams/class/classDb.ts | 22 +++++++++++++------ .../diagrams/class/parser/classDiagram.jison | 2 +- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js index 26b382da4..a9b461c48 100644 --- a/cypress/integration/rendering/classDiagram-v2.spec.js +++ b/cypress/integration/rendering/classDiagram-v2.spec.js @@ -539,4 +539,13 @@ class C13["With Città foreign language"] ` ); }); + + it('should render classLabel if class has already been defined earlier', () => { + imgSnapshotTest( + `classDiagram + Animal <|-- Duck + class Duck["Duck with text label"] +` + ); + }); }); diff --git a/demos/pie.html b/demos/pie.html index 333ef9491..51c5fe7b5 100644 --- a/demos/pie.html +++ b/demos/pie.html @@ -37,7 +37,7 @@ ``` -It can also be be turned on via the diagram code as in the diagram: +It can also be turned on via the diagram code as in the diagram: ```mermaid-example sequenceDiagram From 4bf5c9f3d844905bf0664c8116bb41dd5fc1ffd7 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 24 Feb 2023 17:27:24 +0530 Subject: [PATCH 25/39] feat: Ensure proper detection for flowcharts --- .../mermaid/src/diagram-api/detectType.ts | 4 +- .../diagram-api/diagram-orchestration.spec.ts | 37 +++++++++++++++++++ .../src/diagram-api/diagram-orchestration.ts | 4 +- .../src/diagram-api/diagramAPI.spec.ts | 4 +- packages/mermaid/src/diagram.spec.ts | 4 +- .../src/diagrams/flowchart/flowDetector-v2.ts | 10 ++--- .../src/diagrams/flowchart/flowDetector.ts | 8 ++-- packages/mermaid/src/mermaid.ts | 2 + packages/mermaid/src/mermaidAPI.spec.ts | 2 +- 9 files changed, 57 insertions(+), 18 deletions(-) diff --git a/packages/mermaid/src/diagram-api/detectType.ts b/packages/mermaid/src/diagram-api/detectType.ts index 6ffe5df85..3d55dc78f 100644 --- a/packages/mermaid/src/diagram-api/detectType.ts +++ b/packages/mermaid/src/diagram-api/detectType.ts @@ -46,7 +46,9 @@ export const detectType = function (text: string, config?: MermaidConfig): strin } } - throw new UnknownDiagramError(`No diagram type detected for text: ${text}`); + throw new UnknownDiagramError( + `No diagram type detected matching given configuration for text: ${text}` + ); }; export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinition[]) => { diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.spec.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.spec.ts index f08f32391..81909fe5e 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.spec.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.spec.ts @@ -41,5 +41,42 @@ describe('diagram-orchestration', () => { expect(detectType(text)).toBe(expected); } ); + + it('should detect proper flowchart type based on config', () => { + // graph & dagre-d3 => flowchart + expect(detectType('graph TD; A-->B')).toBe('flowchart'); + // graph & dagre-d3 => flowchart + expect(detectType('graph TD; A-->B', { flowchart: { defaultRenderer: 'dagre-d3' } })).toBe( + 'flowchart' + ); + // flowchart & dagre-d3 => error + expect(() => + detectType('flowchart TD; A-->B', { flowchart: { defaultRenderer: 'dagre-d3' } }) + ).toThrowErrorMatchingInlineSnapshot( + '"No diagram type detected matching given configuration for text: flowchart TD; A-->B"' + ); + + // graph & dagre-wrapper => flowchart-v2 + expect( + detectType('graph TD; A-->B', { flowchart: { defaultRenderer: 'dagre-wrapper' } }) + ).toBe('flowchart-v2'); + // flowchart ==> flowchart-v2 + expect(detectType('flowchart TD; A-->B')).toBe('flowchart-v2'); + // flowchart && dagre-wrapper ==> flowchart-v2 + expect( + detectType('flowchart TD; A-->B', { flowchart: { defaultRenderer: 'dagre-wrapper' } }) + ).toBe('flowchart-v2'); + // flowchart && elk ==> flowchart-elk + expect(detectType('flowchart TD; A-->B', { flowchart: { defaultRenderer: 'elk' } })).toBe( + 'flowchart-elk' + ); + }); + + it('should not detect flowchart if pie contains flowchart', () => { + expect( + detectType(`pie title: "flowchart" + flowchart: 1 "pie" pie: 2 "pie"`) + ).toBe('pie'); + }); }); }); diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index da4c4d081..73bfcf084 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -68,10 +68,8 @@ export const addDiagrams = () => { requirement, sequence, flowchartElk, - // TODO @knsv: Should v2 come before flowchart? - // This will fail few unit tests as they expect graph to be detected as flowchart, but it is detected as flowchart-v2. - flowchart, flowchartV2, + flowchart, mindmap, timeline, git, diff --git a/packages/mermaid/src/diagram-api/diagramAPI.spec.ts b/packages/mermaid/src/diagram-api/diagramAPI.spec.ts index 9e04c861f..73453fa51 100644 --- a/packages/mermaid/src/diagram-api/diagramAPI.spec.ts +++ b/packages/mermaid/src/diagram-api/diagramAPI.spec.ts @@ -20,8 +20,8 @@ describe('DiagramAPI', () => { it('should handle diagram registrations', () => { expect(() => getDiagram('loki')).toThrow(); - expect(() => detectType('loki diagram')).toThrow( - 'No diagram type detected for text: loki diagram' + expect(() => detectType('loki diagram')).toThrowErrorMatchingInlineSnapshot( + '"No diagram type detected matching given configuration for text: loki diagram"' ); const detector: DiagramDetector = (str: string) => { return str.match('loki') !== null; diff --git a/packages/mermaid/src/diagram.spec.ts b/packages/mermaid/src/diagram.spec.ts index a862c7936..5a4718d0b 100644 --- a/packages/mermaid/src/diagram.spec.ts +++ b/packages/mermaid/src/diagram.spec.ts @@ -61,8 +61,8 @@ Expecting 'TXT', got 'NEWLINE'" }); test('should throw the right error for unregistered diagrams', async () => { - await expect(getDiagramFromText('thor TD; A-->B')).rejects.toThrowError( - 'No diagram type detected for text: thor TD; A-->B' + await expect(getDiagramFromText('thor TD; A-->B')).rejects.toThrowErrorMatchingInlineSnapshot( + '"No diagram type detected matching given configuration for text: thor TD; A-->B"' ); }); }); diff --git a/packages/mermaid/src/diagrams/flowchart/flowDetector-v2.ts b/packages/mermaid/src/diagrams/flowchart/flowDetector-v2.ts index 6162cc828..5b2aaf1bd 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDetector-v2.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowDetector-v2.ts @@ -4,15 +4,15 @@ import type { ExternalDiagramDefinition } from '../../diagram-api/types'; const id = 'flowchart-v2'; const detector: DiagramDetector = (txt, config) => { - if (config?.flowchart?.defaultRenderer === 'dagre-d3') { - return false; - } - if (config?.flowchart?.defaultRenderer === 'elk') { + if ( + config?.flowchart?.defaultRenderer === 'dagre-d3' || + config?.flowchart?.defaultRenderer === 'elk' + ) { return false; } // If we have configured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram - if (txt.match(/^\s*graph/) !== null) { + if (txt.match(/^\s*graph/) !== null && config?.flowchart?.defaultRenderer === 'dagre-wrapper') { return true; } return txt.match(/^\s*flowchart/) !== null; diff --git a/packages/mermaid/src/diagrams/flowchart/flowDetector.ts b/packages/mermaid/src/diagrams/flowchart/flowDetector.ts index 9457ff469..a8b116ccd 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDetector.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowDetector.ts @@ -5,10 +5,10 @@ const id = 'flowchart'; const detector: DiagramDetector = (txt, config) => { // If we have conferred to only use new flow charts this function should always return false // as in not signalling true for a legacy flowchart - if (config?.flowchart?.defaultRenderer === 'dagre-wrapper') { - return false; - } - if (config?.flowchart?.defaultRenderer === 'elk') { + if ( + config?.flowchart?.defaultRenderer === 'dagre-wrapper' || + config?.flowchart?.defaultRenderer === 'elk' + ) { return false; } return txt.match(/^\s*graph/) !== null; diff --git a/packages/mermaid/src/mermaid.ts b/packages/mermaid/src/mermaid.ts index 0b6807b3c..edcc5aef7 100644 --- a/packages/mermaid/src/mermaid.ts +++ b/packages/mermaid/src/mermaid.ts @@ -12,6 +12,7 @@ import type { ParseErrorFunction } from './Diagram'; import { isDetailedError } from './utils'; import type { DetailedError } from './utils'; import { ExternalDiagramDefinition } from './diagram-api/types'; +import { UnknownDiagramError } from './errors'; export type { MermaidConfig, @@ -20,6 +21,7 @@ export type { ParseErrorFunction, RenderResult, ParseOptions, + UnknownDiagramError, }; export interface RunOptions { diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts index d87f035d0..1d54332fc 100644 --- a/packages/mermaid/src/mermaidAPI.spec.ts +++ b/packages/mermaid/src/mermaidAPI.spec.ts @@ -678,7 +678,7 @@ describe('mermaidAPI', () => { await expect( mermaidAPI.parse('this is not a mermaid diagram definition') ).rejects.toThrowErrorMatchingInlineSnapshot( - '"No diagram type detected for text: this is not a mermaid diagram definition"' + '"No diagram type detected matching given configuration for text: this is not a mermaid diagram definition"' ); }); it('returns false for invalid definition with silent option', async () => { From 7b4ce7c6ea41241f7250018a1accb27fb3b8ce2c Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 24 Feb 2023 17:32:39 +0530 Subject: [PATCH 26/39] chore: Add tsdoc for registerLazyLoadedDiagrams Co-authored-by: Alois Klink --- packages/mermaid/src/diagram-api/detectType.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/mermaid/src/diagram-api/detectType.ts b/packages/mermaid/src/diagram-api/detectType.ts index 3d55dc78f..6580cb784 100644 --- a/packages/mermaid/src/diagram-api/detectType.ts +++ b/packages/mermaid/src/diagram-api/detectType.ts @@ -51,6 +51,19 @@ export const detectType = function (text: string, config?: MermaidConfig): strin ); }; +/** + * Registers lazy-loaded diagrams to Mermaid. + * + * The diagram function is loaded asynchronously, so that diagrams are only loaded + * if the diagram is detected. + * + * @remarks + * Please note that the order of diagram detectors is important. + * The first detector to return `true` is the diagram that will be loaded + * and used, so put more specific detectors at the beginning! + * + * @param diagrams - Diagrams to lazy load, and their detectors, in order of importance. + */ export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinition[]) => { for (const { id, detector, loader } of diagrams) { addDetector(id, detector, loader); From 275a54a562f969e9db61d036f0027696c07c961e Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 24 Feb 2023 23:10:01 +0530 Subject: [PATCH 27/39] core: Fix render tsdoc Co-authored-by: Dmitry Stratiychuk --- packages/mermaid/src/mermaid.ts | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/mermaid/src/mermaid.ts b/packages/mermaid/src/mermaid.ts index db72aeedb..89d4286ef 100644 --- a/packages/mermaid/src/mermaid.ts +++ b/packages/mermaid/src/mermaid.ts @@ -341,27 +341,21 @@ const parse = async (text: string, parseOptions?: ParseOptions): Promise