From df1e7391945718b0ff1d43fcf2e91a8e4caecc43 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Mon, 12 May 2025 17:40:23 +0200 Subject: [PATCH] Added support for valueFormat directive --- cypress/integration/rendering/treemap.spec.ts | 99 ++++++++++++++++++- cypress/platform/knsv2.html | 12 +-- docs/syntax/treemap.md | 45 ++++++++- packages/mermaid/src/defaultConfig.ts | 1 + .../mermaid/src/diagrams/treemap/renderer.ts | 28 +++++- .../mermaid/src/diagrams/treemap/styles.ts | 72 +++++++------- .../mermaid/src/diagrams/treemap/types.ts | 1 + .../mermaid/src/diagrams/treemap/utils.ts | 13 ++- packages/mermaid/src/docs/syntax/treemap.md | 27 ++++- 9 files changed, 249 insertions(+), 49 deletions(-) diff --git a/cypress/integration/rendering/treemap.spec.ts b/cypress/integration/rendering/treemap.spec.ts index ae41ee5ff..f3e9f6daf 100644 --- a/cypress/integration/rendering/treemap.spec.ts +++ b/cypress/integration/rendering/treemap.spec.ts @@ -113,7 +113,7 @@ classDef secondary fill:#6cf,stroke:#333,stroke-dasharray:5 5; ); }); - it('8: should handle value formatting', () => { + it('8: should handle dollar value formatting with thousands separator', () => { imgSnapshotTest( `%%{init: {'treemap': {'valueFormat': '$0,0'}}}%% treemap @@ -130,6 +130,103 @@ treemap ); }); + it('8a: should handle percentage formatting', () => { + imgSnapshotTest( + `%%{init: {'treemap': {'valueFormat': '.1%'}}}%% +treemap +"Market Share" + "Company A": 0.35 + "Company B": 0.25 + "Company C": 0.15 + "Others": 0.25 + `, + {} + ); + }); + + it('8b: should handle decimal formatting', () => { + imgSnapshotTest( + `%%{init: {'treemap': {'valueFormat': '.2f'}}}%% +treemap +"Metrics" + "Conversion Rate": 0.0567 + "Bounce Rate": 0.6723 + "Click-through Rate": 0.1289 + "Engagement": 0.4521 + `, + {} + ); + }); + + it('8c: should handle dollar sign with decimal places', () => { + imgSnapshotTest( + `%%{init: {'treemap': {'valueFormat': '$.2f'}}}%% +treemap +"Product Prices" + "Basic": 19.99 + "Standard": 49.99 + "Premium": 99.99 + "Enterprise": 199.99 + `, + {} + ); + }); + + it('8d: should handle dollar sign with thousands separator and decimal places', () => { + imgSnapshotTest( + `%%{init: {'treemap': {'valueFormat': '$,.2f'}}}%% +treemap +"Revenue" + "Q1": 1250345.75 + "Q2": 1645789.25 + "Q3": 1845123.50 + "Q4": 2145678.75 + `, + {} + ); + }); + + it('8e: should handle simple thousands separator', () => { + imgSnapshotTest( + `%%{init: {'treemap': {'valueFormat': ','}}}%% +treemap +"User Counts" + "Active Users": 1250345 + "New Signups": 45789 + "Churned": 12350 + "Converted": 78975 + `, + {} + ); + }); + + it('8f: should handle valueFormat set via directive with dollar and thousands separator', () => { + imgSnapshotTest( + `%%{init: {'treemap': {'valueFormat': '$,.0f'}}}%% +treemap +"Sales by Region" + "North": 1234567 + "South": 7654321 + "East": 4567890 + "West": 9876543 + `, + {} + ); + }); + + it('8g: should handle scientific notation format', () => { + imgSnapshotTest( + `%%{init: {'treemap': {'valueFormat': '.2e'}}}%% +treemap +"Scientific Values" + "Value 1": 1234567 + "Value 2": 0.0000123 + "Value 3": 1000000000 + `, + {} + ); + }); + it('9: should handle a complex example with multiple features', () => { imgSnapshotTest( `%%{init: {'theme': 'dark', 'treemap': {'valueFormat': '$0,0'}}}%% diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 17052b1cd..410dbe3c6 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -130,7 +130,7 @@ -
+    
 treemap
 "Section 1"
     "Leaf 1.1": 12
@@ -151,12 +151,12 @@ classDef class1   fill:red,color:blue,stroke:#FFD600;
 treemap
 "Budget"
     "Operations"
-        "Salaries": 700000
-        "Equipment": 200000
-        "Supplies": 100000
+        "Salaries": 7000
+        "Equipment": 2000
+        "Supplies": 1000
     "Marketing"
-        "Advertising": 400000
-        "Events": 100000
+        "Advertising": 4000
+        "Events": 1000
 
 
diff --git a/docs/syntax/treemap.md b/docs/syntax/treemap.md index a025536e9..8ba2995d0 100644 --- a/docs/syntax/treemap.md +++ b/docs/syntax/treemap.md @@ -91,7 +91,8 @@ mermaid.initialize({ nodeHeight: 40, borderWidth: 1, valueFontSize: 12, - labelFontSize: 14 + labelFontSize: 14, + valueFormat: ',' } }); ``` @@ -108,6 +109,48 @@ Key configuration options: | borderWidth | Width of node borders | 1 | | valueFontSize| Font size for values | 12 | | labelFontSize| Font size for node labels | 14 | +| valueFormat | Format string for values (D3 format) | ',' | + +## Value Formatting + +You can customize how values are displayed in the treemap using the `valueFormat` configuration option. This option primarily uses [D3's format specifiers](https://github.com/d3/d3-format#locale_format) to control how numbers are displayed, with some additional special cases for common formats. + +Common format patterns: +- `,` - Thousands separator (default) +- `$` - Add dollar sign +- `.1f` - Show one decimal place +- `.1%` - Show as percentage with one decimal place +- `$0,0` - Dollar sign with thousands separator +- `$.2f` - Dollar sign with 2 decimal places +- `$,.2f` - Dollar sign with thousands separator and 2 decimal places + +The treemap diagram supports both standard D3 format specifiers and some common currency formats that combine the dollar sign with other formatting options. + +Example with currency formatting: + +```mermaid +%%{init: {'treemap': {'valueFormat': '$0,0'}}}%% +treemap + Budget + Development + Frontend: 250000 + Backend: 350000 + Marketing + Digital: 150000 + Print: 50000 +``` + +Example with percentage formatting: + +```mermaid +%%{init: {'treemap': {'valueFormat': '.1%'}}}%% +treemap + Market Share + Company A: 0.35 + Company B: 0.25 + Company C: 0.15 + Others: 0.25 +``` ## Notes and Limitations diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts index 55c63c3c5..85779736c 100644 --- a/packages/mermaid/src/defaultConfig.ts +++ b/packages/mermaid/src/defaultConfig.ts @@ -267,6 +267,7 @@ const config: RequiredDeep = { borderWidth: 1, valueFontSize: 12, labelFontSize: 14, + valueFormat: ',', }, }; diff --git a/packages/mermaid/src/diagrams/treemap/renderer.ts b/packages/mermaid/src/diagrams/treemap/renderer.ts index ab5c3113f..0cf783e2c 100644 --- a/packages/mermaid/src/diagrams/treemap/renderer.ts +++ b/packages/mermaid/src/diagrams/treemap/renderer.ts @@ -47,7 +47,33 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => { configureSvgSize(svg, svgHeight, svgWidth, config.useMaxWidth); // Format for displaying values - const valueFormat = format(','); + let valueFormat; + try { + // Handle special format patterns + const formatStr = config.valueFormat || ','; + + // Handle special cases that aren't directly supported by D3 format + if (formatStr === '$0,0') { + // Currency with thousands separator + valueFormat = (value: number) => '$' + format(',')(value); + } else if (formatStr.startsWith('$') && formatStr.includes(',')) { + // Other dollar formats with commas + const precision = formatStr.match(/\.\d+/); + const precisionStr = precision ? precision[0] : ''; + valueFormat = (value: number) => '$' + format(',' + precisionStr)(value); + } else if (formatStr.startsWith('$')) { + // Simple dollar sign prefix + const restOfFormat = formatStr.substring(1); + valueFormat = (value: number) => '$' + format(restOfFormat || '')(value); + } else { + // Standard D3 format + valueFormat = format(formatStr); + } + } catch (error) { + console.error('Error creating format function:', error); + // Fallback to default format + valueFormat = format(','); + } // Create color scale const colorScale = scaleOrdinal().range([ diff --git a/packages/mermaid/src/diagrams/treemap/styles.ts b/packages/mermaid/src/diagrams/treemap/styles.ts index 20f917bac..6021838c9 100644 --- a/packages/mermaid/src/diagrams/treemap/styles.ts +++ b/packages/mermaid/src/diagrams/treemap/styles.ts @@ -1,53 +1,51 @@ import type { DiagramStylesProvider } from '../../diagram-api/types.js'; import { cleanAndMerge } from '../../utils.js'; -import type { PacketStyleOptions } from './types.js'; +import type { TreemapStyleOptions } from './types.js'; -const defaultPacketStyleOptions: PacketStyleOptions = { - byteFontSize: '10px', - startByteColor: 'black', - endByteColor: 'black', +const defaultTreemapStyleOptions: TreemapStyleOptions = { + sectionStrokeColor: 'black', + sectionStrokeWidth: '1', + sectionFillColor: '#efefef', + leafStrokeColor: 'black', + leafStrokeWidth: '1', + leafFillColor: '#efefef', labelColor: 'black', labelFontSize: '12px', + valueFontSize: '10px', + valueColor: 'black', titleColor: 'black', titleFontSize: '14px', - blockStrokeColor: 'black', - blockStrokeWidth: '1', - blockFillColor: '#efefef', }; export const getStyles: DiagramStylesProvider = ({ - packet, -}: { packet?: PacketStyleOptions } = {}) => { - const options = cleanAndMerge(defaultPacketStyleOptions, packet); + treemap, +}: { treemap?: TreemapStyleOptions } = {}) => { + const options = cleanAndMerge(defaultTreemapStyleOptions, treemap); return ` - .treemapNode { - // stroke: black; - // stroke-width: 1; + .treemapNode.section { + stroke: ${options.sectionStrokeColor}; + stroke-width: ${options.sectionStrokeWidth}; + fill: ${options.sectionFillColor}; } - .packetByte { - font-size: ${options.byteFontSize}; - } - .packetByte.start { - fill: ${options.startByteColor}; - } - .packetByte.end { - fill: ${options.endByteColor}; - } - .packetLabel { - fill: ${options.labelColor}; - font-size: ${options.labelFontSize}; - } - .packetTitle { - fill: ${options.titleColor}; - font-size: ${options.titleFontSize}; - } - .packetBlock { - stroke: ${options.blockStrokeColor}; - stroke-width: ${options.blockStrokeWidth}; - fill: ${options.blockFillColor}; - } - `; + .treemapNode.leaf { + stroke: ${options.leafStrokeColor}; + stroke-width: ${options.leafStrokeWidth}; + fill: ${options.leafFillColor}; + } + .treemapLabel { + fill: ${options.labelColor}; + font-size: ${options.labelFontSize}; + } + .treemapValue { + fill: ${options.valueColor}; + font-size: ${options.valueFontSize}; + } + .treemapTitle { + fill: ${options.titleColor}; + font-size: ${options.titleFontSize}; + } + `; }; export default getStyles; diff --git a/packages/mermaid/src/diagrams/treemap/types.ts b/packages/mermaid/src/diagrams/treemap/types.ts index 7a00b673e..161fd0ec1 100644 --- a/packages/mermaid/src/diagrams/treemap/types.ts +++ b/packages/mermaid/src/diagrams/treemap/types.ts @@ -49,4 +49,5 @@ export interface TreemapDiagramConfig extends BaseDiagramConfig { borderWidth?: number; valueFontSize?: number; labelFontSize?: number; + valueFormat?: string; } diff --git a/packages/mermaid/src/diagrams/treemap/utils.ts b/packages/mermaid/src/diagrams/treemap/utils.ts index 57ec53287..e7e7fcc5c 100644 --- a/packages/mermaid/src/diagrams/treemap/utils.ts +++ b/packages/mermaid/src/diagrams/treemap/utils.ts @@ -6,7 +6,14 @@ import type { TreemapNode } from './types.js'; * @returns A hierarchical tree structure */ export function buildHierarchy( - items: { level: number; name: string; type: string; value?: number; classSelector?: string }[] + items: { + level: number; + name: string; + type: string; + value?: number; + classSelector?: string; + cssCompiledStyles?: string; + }[] ): TreemapNode[] { if (!items.length) { return []; @@ -21,7 +28,9 @@ export function buildHierarchy( children: item.type === 'Leaf' ? undefined : [], }; node.classSelector = item?.classSelector; - node.cssCompiledStyles = item?.cssCompiledStyles; + if (item?.cssCompiledStyles) { + node.cssCompiledStyles = [item.cssCompiledStyles]; + } if (item.type === 'Leaf' && item.value !== undefined) { node.value = item.value; diff --git a/packages/mermaid/src/docs/syntax/treemap.md b/packages/mermaid/src/docs/syntax/treemap.md index 9be6148ed..7b6fa37a1 100644 --- a/packages/mermaid/src/docs/syntax/treemap.md +++ b/packages/mermaid/src/docs/syntax/treemap.md @@ -141,7 +141,20 @@ treemap ### Value Formatting -Values in treemap diagrams can be formatted to display in different ways: +Values in treemap diagrams can be formatted to display in different ways using the `valueFormat` configuration option. This option primarily uses [D3's format specifiers](https://github.com/d3/d3-format#locale_format) to control how numbers are displayed, with some additional special cases for common formats. + +Some common format patterns: +- `,` - Thousands separator (default) +- `$` - Add dollar sign +- `.1f` - Show one decimal place +- `.1%` - Show as percentage with one decimal place +- `$0,0` - Dollar sign with thousands separator +- `$.2f` - Dollar sign with 2 decimal places +- `$,.2f` - Dollar sign with thousands separator and 2 decimal places + +The treemap diagram supports both standard D3 format specifiers and some common currency formats that combine the dollar sign with other formatting options. + +Example with currency formatting: ```mermaid-example %%{init: {'treemap': {'valueFormat': '$0,0'}}}%% @@ -156,6 +169,18 @@ treemap "Events": 100000 ``` +Example with percentage formatting: + +```mermaid-example +%%{init: {'treemap': {'valueFormat': '.1%'}}}%% +treemap +"Market Share" + "Company A": 0.35 + "Company B": 0.25 + "Company C": 0.15 + "Others": 0.25 +``` + ## Common Use Cases Treemap diagrams are commonly used for: