From 7e23f984e65f9b503f5a7a765b91ff88d590d178 Mon Sep 17 00:00:00 2001 From: shubham-mermaid Date: Thu, 10 Jul 2025 20:21:27 +0530 Subject: [PATCH 01/12] Updated code to use class based approach --- packages/mermaid/src/diagrams/treemap/db.ts | 174 +++++++++--------- .../mermaid/src/diagrams/treemap/diagram.ts | 6 +- .../mermaid/src/diagrams/treemap/parser.ts | 8 +- 3 files changed, 95 insertions(+), 93 deletions(-) diff --git a/packages/mermaid/src/diagrams/treemap/db.ts b/packages/mermaid/src/diagrams/treemap/db.ts index 6a68857f7..4aa608e9c 100644 --- a/packages/mermaid/src/diagrams/treemap/db.ts +++ b/packages/mermaid/src/diagrams/treemap/db.ts @@ -1,10 +1,10 @@ -import { getConfig as commonGetConfig } from '../../config.js'; -import DEFAULT_CONFIG from '../../defaultConfig.js'; -import type { DiagramStyleClassDef } from '../../diagram-api/types.js'; -import { isLabelStyle } from '../../rendering-util/rendering-elements/shapes/handDrawnShapeStyles.js'; - -import { cleanAndMerge } from '../../utils.js'; +import type { DiagramDB } from '../../diagram-api/types.js'; import { ImperativeState } from '../../utils/imperativeState.js'; +import type { TreemapData, TreemapDiagramConfig, TreemapNode } from './types.js'; +import DEFAULT_CONFIG from '../../defaultConfig.js'; +import { getConfig as commonGetConfig } from '../../config.js'; +import { cleanAndMerge } from '../../utils.js'; +import { isLabelStyle } from '../../rendering-util/rendering-elements/shapes/handDrawnShapeStyles.js'; import { clear as commonClear, getAccDescription, @@ -14,99 +14,99 @@ import { setAccTitle, setDiagramTitle, } from '../common/commonDb.js'; -import type { TreemapDB, TreemapData, TreemapDiagramConfig, TreemapNode } from './types.js'; +export class TreeMapDB implements DiagramDB { + private data: TreemapData; + private state: ImperativeState; -const defaultTreemapData: TreemapData = { - nodes: [], - levels: new Map(), - outerNodes: [], - classes: new Map(), -}; + constructor() { + this.data = { + nodes: [], + levels: new Map(), + outerNodes: [], + classes: new Map(), + }; -const state = new ImperativeState(() => structuredClone(defaultTreemapData)); - -const getConfig = (): Required => { - // Use type assertion with unknown as intermediate step - const defaultConfig = DEFAULT_CONFIG as unknown as { treemap: Required }; - const userConfig = commonGetConfig() as unknown as { treemap?: Partial }; - - return cleanAndMerge({ - ...defaultConfig.treemap, - ...(userConfig.treemap ?? {}), - }) as Required; -}; - -const getNodes = (): TreemapNode[] => state.records.nodes; - -const addNode = (node: TreemapNode, level: number) => { - const data = state.records; - data.nodes.push(node); - data.levels.set(node, level); - - if (level === 0) { - data.outerNodes.push(node); + this.state = new ImperativeState(() => structuredClone(this.data)); } - // Set the root node if this is a level 0 node and we don't have a root yet - if (level === 0 && !data.root) { - data.root = node; + public getNodes() { + return this.state.records.nodes; } -}; -const getRoot = (): TreemapNode | undefined => ({ name: '', children: state.records.outerNodes }); + public getConfig() { + // Use type assertion with unknown as intermediate step + const defaultConfig = DEFAULT_CONFIG as unknown as { treemap: Required }; + const userConfig = commonGetConfig() as unknown as { treemap?: Partial }; -const addClass = (id: string, _style: string) => { - const classes = state.records.classes; - const styleClass = classes.get(id) ?? { id, styles: [], textStyles: [] }; - classes.set(id, styleClass); + return cleanAndMerge({ + ...defaultConfig.treemap, + ...(userConfig.treemap ?? {}), + }) as Required; + } - const styles = _style.replace(/\\,/g, '§§§').replace(/,/g, ';').replace(/§§§/g, ',').split(';'); + public addNode(node: TreemapNode, level: number) { + const data = this.state.records; + data.nodes.push(node); + data.levels.set(node, level); - if (styles) { - styles.forEach((s) => { - if (isLabelStyle(s)) { - if (styleClass?.textStyles) { - styleClass.textStyles.push(s); - } else { - styleClass.textStyles = [s]; + if (level === 0) { + data.outerNodes.push(node); + } + + // Set the root node if this is a level 0 node and we don't have a root yet + if (level === 0 && !data.root) { + data.root = node; + } + } + + public getRoot() { + return { name: '', children: this.state.records.outerNodes }; + } + + public addClass(id: string, _style: string) { + // const classes = this.state.records.classes; + const styleClass = this.state.records.classes.get(id) ?? { id, styles: [], textStyles: [] }; + this.state.records.classes.set(id, styleClass); + + const styles = _style.replace(/\\,/g, '§§§').replace(/,/g, ';').replace(/§§§/g, ',').split(';'); + + if (styles) { + styles.forEach((s) => { + if (isLabelStyle(s)) { + if (styleClass?.textStyles) { + styleClass.textStyles.push(s); + } else { + styleClass.textStyles = [s]; + } } - } - if (styleClass?.styles) { - styleClass.styles.push(s); - } else { - styleClass.styles = [s]; - } - }); + if (styleClass?.styles) { + styleClass.styles.push(s); + } else { + styleClass.styles = [s]; + } + }); + } + + this.state.records.classes.set(id, styleClass); } - classes.set(id, styleClass); -}; -const getClasses = (): Map => { - return state.records.classes; -}; + public getClasses() { + return this.state.records.classes; + } -const getStylesForClass = (classSelector: string): string[] => { - return state.records.classes.get(classSelector)?.styles ?? []; -}; + public getStylesForClass(classSelector: string): string[] { + return this.state.records.classes.get(classSelector)?.styles ?? []; + } -const clear = () => { - commonClear(); - state.reset(); -}; + public clear = () => { + commonClear(); + this.state.reset(); + }; -export const db: TreemapDB = { - getNodes, - addNode, - getRoot, - getConfig, - clear, - setAccTitle, - getAccTitle, - setDiagramTitle, - getDiagramTitle, - getAccDescription, - setAccDescription, - addClass, - getClasses, - getStylesForClass, -}; + public setAccTitle = setAccTitle; + public setAccDescription = setAccDescription; + public setDiagramTitle = setDiagramTitle; + public getAccTitle = getAccTitle; + public getAccDescription = getAccDescription; + public getDiagramTitle = getDiagramTitle; +} diff --git a/packages/mermaid/src/diagrams/treemap/diagram.ts b/packages/mermaid/src/diagrams/treemap/diagram.ts index dd599174e..2f8ff92f3 100644 --- a/packages/mermaid/src/diagrams/treemap/diagram.ts +++ b/packages/mermaid/src/diagrams/treemap/diagram.ts @@ -1,12 +1,14 @@ import type { DiagramDefinition } from '../../diagram-api/types.js'; -import { db } from './db.js'; +import { TreeMapDB } from './db.js'; import { parser } from './parser.js'; import { renderer } from './renderer.js'; import styles from './styles.js'; export const diagram: DiagramDefinition = { parser, - db, + get db() { + return new TreeMapDB(); + }, renderer, styles, }; diff --git a/packages/mermaid/src/diagrams/treemap/parser.ts b/packages/mermaid/src/diagrams/treemap/parser.ts index 82efb5911..0ed452f45 100644 --- a/packages/mermaid/src/diagrams/treemap/parser.ts +++ b/packages/mermaid/src/diagrams/treemap/parser.ts @@ -2,15 +2,14 @@ import { parse } from '@mermaid-js/parser'; import type { ParserDefinition } from '../../diagram-api/types.js'; import { log } from '../../logger.js'; import { populateCommonDb } from '../common/populateCommonDb.js'; -import { db } from './db.js'; -import type { TreemapNode, TreemapAst } from './types.js'; +import type { TreemapNode, TreemapAst, TreemapDB } from './types.js'; import { buildHierarchy } from './utils.js'; /** * Populates the database with data from the Treemap AST * @param ast - The Treemap AST */ -const populate = (ast: TreemapAst) => { +const populate = (ast: TreemapAst, db: TreemapDB) => { // We need to bypass the type checking for populateCommonDb // eslint-disable-next-line @typescript-eslint/no-explicit-any populateCommonDb(ast as any, db); @@ -84,6 +83,7 @@ const getItemName = (item: { name?: string | number }): string => { }; export const parser: ParserDefinition = { + parser: { yy: undefined }, parse: async (text: string): Promise => { try { // Use a generic parse that accepts any diagram type @@ -91,7 +91,7 @@ export const parser: ParserDefinition = { const parseFunc = parse as (diagramType: string, text: string) => Promise; const ast = await parseFunc('treemap', text); log.debug('Treemap AST:', ast); - populate(ast); + populate(ast, parser.parser?.yy as TreemapDB); } catch (error) { log.error('Error parsing treemap:', error); throw error; From 90707e806262e826e672fc607ced9e2db25a7e8a Mon Sep 17 00:00:00 2001 From: shubham-mermaid Date: Thu, 10 Jul 2025 20:25:46 +0530 Subject: [PATCH 02/12] fix(parser): initialize parser.yy as TreemapDB --- packages/mermaid/src/diagrams/treemap/parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/treemap/parser.ts b/packages/mermaid/src/diagrams/treemap/parser.ts index 0ed452f45..ad9d748d8 100644 --- a/packages/mermaid/src/diagrams/treemap/parser.ts +++ b/packages/mermaid/src/diagrams/treemap/parser.ts @@ -83,7 +83,7 @@ const getItemName = (item: { name?: string | number }): string => { }; export const parser: ParserDefinition = { - parser: { yy: undefined }, + parser: { yy: {} as TreemapDB }, parse: async (text: string): Promise => { try { // Use a generic parse that accepts any diagram type From d90634bf2b09e586b055729e07e9a1a31b21827c Mon Sep 17 00:00:00 2001 From: shubham-mermaid Date: Thu, 10 Jul 2025 20:39:52 +0530 Subject: [PATCH 03/12] added changeset --- .changeset/pretty-falcons-say.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/pretty-falcons-say.md diff --git a/.changeset/pretty-falcons-say.md b/.changeset/pretty-falcons-say.md new file mode 100644 index 000000000..36f10713a --- /dev/null +++ b/.changeset/pretty-falcons-say.md @@ -0,0 +1,5 @@ +--- +'mermaid': chore +--- + +chore: updated treemapdb to use class based approach From 9dfbf1166df965293e2b87b777ed5a3635d1f583 Mon Sep 17 00:00:00 2001 From: shubham-mermaid Date: Fri, 11 Jul 2025 14:04:30 +0530 Subject: [PATCH 04/12] Refactors treemap DB to class-based state management on-behalf-of: @MermaidChart shubham-mermaid@mermaidchart.com --- .changeset/pretty-falcons-say.md | 4 +- packages/mermaid/src/diagrams/treemap/db.ts | 67 ++++++++------------- 2 files changed, 27 insertions(+), 44 deletions(-) diff --git a/.changeset/pretty-falcons-say.md b/.changeset/pretty-falcons-say.md index 36f10713a..05929efb0 100644 --- a/.changeset/pretty-falcons-say.md +++ b/.changeset/pretty-falcons-say.md @@ -1,5 +1,5 @@ --- -'mermaid': chore +'mermaid': patch --- -chore: updated treemapdb to use class based approach +patch: updated treemapdb to use class based approach diff --git a/packages/mermaid/src/diagrams/treemap/db.ts b/packages/mermaid/src/diagrams/treemap/db.ts index 4aa608e9c..9b1571dc4 100644 --- a/packages/mermaid/src/diagrams/treemap/db.ts +++ b/packages/mermaid/src/diagrams/treemap/db.ts @@ -1,6 +1,6 @@ import type { DiagramDB } from '../../diagram-api/types.js'; -import { ImperativeState } from '../../utils/imperativeState.js'; -import type { TreemapData, TreemapDiagramConfig, TreemapNode } from './types.js'; +import type { DiagramStyleClassDef } from '../../diagram-api/types.js'; +import type { TreemapDiagramConfig, TreemapNode } from './types.js'; import DEFAULT_CONFIG from '../../defaultConfig.js'; import { getConfig as commonGetConfig } from '../../config.js'; import { cleanAndMerge } from '../../utils.js'; @@ -15,29 +15,19 @@ import { setDiagramTitle, } from '../common/commonDb.js'; export class TreeMapDB implements DiagramDB { - private data: TreemapData; - private state: ImperativeState; - - constructor() { - this.data = { - nodes: [], - levels: new Map(), - outerNodes: [], - classes: new Map(), - }; - - this.state = new ImperativeState(() => structuredClone(this.data)); - } + private nodes: TreemapNode[] = []; + private levels: Map = new Map(); + private outerNodes: TreemapNode[] = []; + private classes: Map = new Map(); + private root?: TreemapNode; public getNodes() { - return this.state.records.nodes; + return this.nodes; } public getConfig() { - // Use type assertion with unknown as intermediate step const defaultConfig = DEFAULT_CONFIG as unknown as { treemap: Required }; const userConfig = commonGetConfig() as unknown as { treemap?: Partial }; - return cleanAndMerge({ ...defaultConfig.treemap, ...(userConfig.treemap ?? {}), @@ -45,31 +35,21 @@ export class TreeMapDB implements DiagramDB { } public addNode(node: TreemapNode, level: number) { - const data = this.state.records; - data.nodes.push(node); - data.levels.set(node, level); - + this.nodes.push(node); + this.levels.set(node, level); if (level === 0) { - data.outerNodes.push(node); - } - - // Set the root node if this is a level 0 node and we don't have a root yet - if (level === 0 && !data.root) { - data.root = node; + this.outerNodes.push(node); + this.root ??= node; } } public getRoot() { - return { name: '', children: this.state.records.outerNodes }; + return { name: '', children: this.outerNodes }; } public addClass(id: string, _style: string) { - // const classes = this.state.records.classes; - const styleClass = this.state.records.classes.get(id) ?? { id, styles: [], textStyles: [] }; - this.state.records.classes.set(id, styleClass); - + const styleClass = this.classes.get(id) ?? { id, styles: [], textStyles: [] }; const styles = _style.replace(/\\,/g, '§§§').replace(/,/g, ';').replace(/§§§/g, ',').split(';'); - if (styles) { styles.forEach((s) => { if (isLabelStyle(s)) { @@ -86,27 +66,30 @@ export class TreeMapDB implements DiagramDB { } }); } - - this.state.records.classes.set(id, styleClass); + this.classes.set(id, styleClass); } public getClasses() { - return this.state.records.classes; + return this.classes; } public getStylesForClass(classSelector: string): string[] { - return this.state.records.classes.get(classSelector)?.styles ?? []; + return this.classes.get(classSelector)?.styles ?? []; } public clear = () => { commonClear(); - this.state.reset(); + this.nodes = []; + this.levels = new Map(); + this.outerNodes = []; + this.classes = new Map(); + this.root = undefined; }; public setAccTitle = setAccTitle; - public setAccDescription = setAccDescription; - public setDiagramTitle = setDiagramTitle; public getAccTitle = getAccTitle; - public getAccDescription = getAccDescription; + public setDiagramTitle = setDiagramTitle; public getDiagramTitle = getDiagramTitle; + public getAccDescription = getAccDescription; + public setAccDescription = setAccDescription; } From c12aea588c61e3b6b0a8f989da833d2210f01806 Mon Sep 17 00:00:00 2001 From: shubham-mermaid Date: Fri, 11 Jul 2025 15:16:49 +0530 Subject: [PATCH 05/12] Updated code as per suggestions on-behalf-of: @MermaidChart --- packages/mermaid/src/diagrams/treemap/parser.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/treemap/parser.ts b/packages/mermaid/src/diagrams/treemap/parser.ts index ad9d748d8..64a5855db 100644 --- a/packages/mermaid/src/diagrams/treemap/parser.ts +++ b/packages/mermaid/src/diagrams/treemap/parser.ts @@ -4,6 +4,7 @@ import { log } from '../../logger.js'; import { populateCommonDb } from '../common/populateCommonDb.js'; import type { TreemapNode, TreemapAst, TreemapDB } from './types.js'; import { buildHierarchy } from './utils.js'; +import { TreeMapDB } from './db.js'; /** * Populates the database with data from the Treemap AST @@ -83,7 +84,7 @@ const getItemName = (item: { name?: string | number }): string => { }; export const parser: ParserDefinition = { - parser: { yy: {} as TreemapDB }, + parser: { yy: new TreeMapDB() }, parse: async (text: string): Promise => { try { // Use a generic parse that accepts any diagram type @@ -91,7 +92,13 @@ export const parser: ParserDefinition = { const parseFunc = parse as (diagramType: string, text: string) => Promise; const ast = await parseFunc('treemap', text); log.debug('Treemap AST:', ast); - populate(ast, parser.parser?.yy as TreemapDB); + const db = parser.parser?.yy; + if (!(db instanceof TreeMapDB)) { + throw new Error( + 'parser.parser?.yy was not a TreemapDB. This is due to a bug within Mermaid, please report this issue at https://github.com/mermaid-js/mermaid/issues.' + ); + } + populate(ast, db); } catch (error) { log.error('Error parsing treemap:', error); throw error; From ad024b01d69b818c274bd3433cceb9abb1d6c876 Mon Sep 17 00:00:00 2001 From: shubham-mermaid Date: Fri, 11 Jul 2025 15:18:41 +0530 Subject: [PATCH 06/12] Updated the function to use class method on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/diagrams/treemap/db.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/treemap/db.ts b/packages/mermaid/src/diagrams/treemap/db.ts index 9b1571dc4..23326bc85 100644 --- a/packages/mermaid/src/diagrams/treemap/db.ts +++ b/packages/mermaid/src/diagrams/treemap/db.ts @@ -77,14 +77,14 @@ export class TreeMapDB implements DiagramDB { return this.classes.get(classSelector)?.styles ?? []; } - public clear = () => { + public clear() { commonClear(); this.nodes = []; this.levels = new Map(); this.outerNodes = []; this.classes = new Map(); this.root = undefined; - }; + } public setAccTitle = setAccTitle; public getAccTitle = getAccTitle; From 12e3d31437f9eb3283286be50eb10aded653a101 Mon Sep 17 00:00:00 2001 From: shubham-mermaid Date: Fri, 11 Jul 2025 16:54:56 +0530 Subject: [PATCH 07/12] Addresses type incompatibility by setting the parser database to undefined, on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/diagrams/treemap/parser.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/treemap/parser.ts b/packages/mermaid/src/diagrams/treemap/parser.ts index 64a5855db..4d71ff470 100644 --- a/packages/mermaid/src/diagrams/treemap/parser.ts +++ b/packages/mermaid/src/diagrams/treemap/parser.ts @@ -84,7 +84,8 @@ const getItemName = (item: { name?: string | number }): string => { }; export const parser: ParserDefinition = { - parser: { yy: new TreeMapDB() }, + // @ts-expect-error - TreeMapDB is not assignable to DiagramDB + parser: { yy: undefined }, parse: async (text: string): Promise => { try { // Use a generic parse that accepts any diagram type From 2e2e8c41524a481e65f18196d154f18e64cebe0e Mon Sep 17 00:00:00 2001 From: shubham-mermaid Date: Tue, 15 Jul 2025 12:06:22 +0530 Subject: [PATCH 08/12] Added clear function in constructor on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/diagrams/treemap/db.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/mermaid/src/diagrams/treemap/db.ts b/packages/mermaid/src/diagrams/treemap/db.ts index 23326bc85..93793eb1d 100644 --- a/packages/mermaid/src/diagrams/treemap/db.ts +++ b/packages/mermaid/src/diagrams/treemap/db.ts @@ -21,6 +21,10 @@ export class TreeMapDB implements DiagramDB { private classes: Map = new Map(); private root?: TreemapNode; + constructor() { + this.clear(); + } + public getNodes() { return this.nodes; } From 688170558c355f71e37b794b8e99103d63dfc89f Mon Sep 17 00:00:00 2001 From: shubham-mermaid Date: Tue, 15 Jul 2025 12:07:52 +0530 Subject: [PATCH 09/12] Updated changeset on-behalf-of: @Mermaid-Chart --- .changeset/pretty-falcons-say.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pretty-falcons-say.md b/.changeset/pretty-falcons-say.md index 05929efb0..8c490460d 100644 --- a/.changeset/pretty-falcons-say.md +++ b/.changeset/pretty-falcons-say.md @@ -2,4 +2,4 @@ 'mermaid': patch --- -patch: updated treemapdb to use class based approach +patch: Updated treemapdb to use class based approach From e95e4d155afdec89c2cd1b8d0a51b2bde50f4726 Mon Sep 17 00:00:00 2001 From: shubham-mermaid Date: Tue, 15 Jul 2025 12:09:17 +0530 Subject: [PATCH 10/12] Updated changeset on-behalf-of: @Mermaid-Chart --- .changeset/pretty-falcons-say.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pretty-falcons-say.md b/.changeset/pretty-falcons-say.md index 8c490460d..f9768a296 100644 --- a/.changeset/pretty-falcons-say.md +++ b/.changeset/pretty-falcons-say.md @@ -2,4 +2,4 @@ 'mermaid': patch --- -patch: Updated treemapdb to use class based approach +patch: Updated TreeMapDB to use class based approach From 42a3c3487f4c670263f0fa4e9d189f607cd92164 Mon Sep 17 00:00:00 2001 From: shubham-mermaid Date: Tue, 15 Jul 2025 13:44:26 +0530 Subject: [PATCH 11/12] Remove constructor and clear method call from TreeMapDB class on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/diagrams/treemap/db.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/mermaid/src/diagrams/treemap/db.ts b/packages/mermaid/src/diagrams/treemap/db.ts index 93793eb1d..23326bc85 100644 --- a/packages/mermaid/src/diagrams/treemap/db.ts +++ b/packages/mermaid/src/diagrams/treemap/db.ts @@ -21,10 +21,6 @@ export class TreeMapDB implements DiagramDB { private classes: Map = new Map(); private root?: TreemapNode; - constructor() { - this.clear(); - } - public getNodes() { return this.nodes; } From 771801b366f0bfba158db88a40180fba9eaf7667 Mon Sep 17 00:00:00 2001 From: shubham-mermaid Date: Tue, 15 Jul 2025 17:40:59 +0530 Subject: [PATCH 12/12] Updated changeset on-behalf-of: @Mermaid-Chart --- .changeset/pretty-falcons-say.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pretty-falcons-say.md b/.changeset/pretty-falcons-say.md index f9768a296..f1a0a95c3 100644 --- a/.changeset/pretty-falcons-say.md +++ b/.changeset/pretty-falcons-say.md @@ -2,4 +2,4 @@ 'mermaid': patch --- -patch: Updated TreeMapDB to use class based approach +chore: Updated TreeMapDB to use class based approach