diff --git a/.changeset/seven-papayas-film.md b/.changeset/seven-papayas-film.md
new file mode 100644
index 000000000..109743117
--- /dev/null
+++ b/.changeset/seven-papayas-film.md
@@ -0,0 +1,9 @@
+---
+'mermaid': patch
+---
+
+Add validation for negative values in pie charts:
+
+Prevents crashes during parsing by validating values post-parsing.
+
+Provides clearer, user-friendly error messages for invalid negative inputs.
diff --git a/cypress/integration/rendering/pie.spec.ts b/cypress/integration/rendering/pie.spec.ts
index 171a83057..8f6ef7de3 100644
--- a/cypress/integration/rendering/pie.spec.ts
+++ b/cypress/integration/rendering/pie.spec.ts
@@ -82,4 +82,13 @@ describe('pie chart', () => {
`
);
});
+ it('should render pie slices only for non-zero values but shows all legends', () => {
+ imgSnapshotTest(
+ ` pie title Pets adopted by volunteers
+ "Dogs" : 386
+ "Cats" : 85
+ "Rats" : 1
+ `
+ );
+ });
});
diff --git a/cypress/platform/darshan.html b/cypress/platform/darshan.html
new file mode 100644
index 000000000..df5994d7d
--- /dev/null
+++ b/cypress/platform/darshan.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+ Mermaid Quick Test Page
+
+
+
+
+
+
Pie chart demos
+
+ pie title Default text position: Animal adoption
+ accTitle: simple pie char demo
+ accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
+ "dogs" : -60.67
+ "rats" : 40.12
+
+
+
+
+
+
diff --git a/docs/syntax/pie.md b/docs/syntax/pie.md
index b8f452b66..6a2eefb27 100644
--- a/docs/syntax/pie.md
+++ b/docs/syntax/pie.md
@@ -37,6 +37,11 @@ Drawing a pie chart is really simple in mermaid.
- Followed by `:` colon as separator
- Followed by `positive numeric value` (supported up to two decimal places)
+**Note:**
+
+> Pie chart values must be **positive numbers greater than zero**.\
+> **Negative values are not allowed** and will result in an error.
+
\[pie] \[showData] (OPTIONAL)
\[title] \[titlevalue] (OPTIONAL)
"\[datakey1]" : \[dataValue1]
diff --git a/packages/mermaid/src/diagrams/pie/pie.spec.ts b/packages/mermaid/src/diagrams/pie/pie.spec.ts
index f00906cc5..60fff01e1 100644
--- a/packages/mermaid/src/diagrams/pie/pie.spec.ts
+++ b/packages/mermaid/src/diagrams/pie/pie.spec.ts
@@ -139,6 +139,32 @@ describe('pie', () => {
}).rejects.toThrowError();
});
+ it('should handle simple pie with zero slice value', async () => {
+ await parser.parse(`pie title Default text position: Animal adoption
+ accTitle: simple pie char demo
+ accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
+ "dogs" : 0
+ "rats" : 40.12
+ `);
+
+ const sections = db.getSections();
+ expect(sections.get('dogs')).toBe(0);
+ expect(sections.get('rats')).toBe(40.12);
+ });
+
+ it('should handle simple pie with negative slice value', async () => {
+ await expect(async () => {
+ await parser.parse(`pie title Default text position: Animal adoption
+ accTitle: simple pie char demo
+ accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
+ "dogs" : -60.67
+ "rats" : 40.12
+ `);
+ }).rejects.toThrowError(
+ '"dogs" has invalid value: -60.67. Negative values are not allowed in pie charts. All slice values must be >= 0.'
+ );
+ });
+
it('should handle unsafe properties', async () => {
await expect(
parser.parse(`pie title Unsafe props test
diff --git a/packages/mermaid/src/diagrams/pie/pieDb.ts b/packages/mermaid/src/diagrams/pie/pieDb.ts
index 64831495c..083ee97d5 100644
--- a/packages/mermaid/src/diagrams/pie/pieDb.ts
+++ b/packages/mermaid/src/diagrams/pie/pieDb.ts
@@ -34,6 +34,11 @@ const clear = (): void => {
};
const addSection = ({ label, value }: D3Section): void => {
+ if (value < 0) {
+ throw new Error(
+ `"${label}" has invalid value: ${value}. Negative values are not allowed in pie charts. All slice values must be >= 0.`
+ );
+ }
if (!sections.has(label)) {
sections.set(label, value);
log.debug(`added new section: ${label}, with value: ${value}`);
diff --git a/packages/mermaid/src/diagrams/pie/pieRenderer.ts b/packages/mermaid/src/diagrams/pie/pieRenderer.ts
index a0cdce3df..5b87613ff 100644
--- a/packages/mermaid/src/diagrams/pie/pieRenderer.ts
+++ b/packages/mermaid/src/diagrams/pie/pieRenderer.ts
@@ -10,20 +10,14 @@ import { cleanAndMerge, parseFontSize } from '../../utils.js';
import type { D3Section, PieDB, Sections } from './pieTypes.js';
const createPieArcs = (sections: Sections): d3.PieArcDatum[] => {
- // Compute the position of each group on the pie:
+ const sum = [...sections.values()].reduce((acc, val) => acc + val, 0);
+
const pieData: D3Section[] = [...sections.entries()]
- .map((element: [string, number]): D3Section => {
- return {
- label: element[0],
- value: element[1],
- };
- })
- .sort((a: D3Section, b: D3Section): number => {
- return b.value - a.value;
- });
- const pie: d3.Pie = d3pie().value(
- (d3Section: D3Section): number => d3Section.value
- );
+ .map(([label, value]) => ({ label, value }))
+ .filter((d) => (d.value / sum) * 100 >= 1) // Remove values < 1%
+ .sort((a, b) => b.value - a.value);
+
+ const pie: d3.Pie = d3pie().value((d) => d.value);
return pie(pieData);
};
@@ -89,13 +83,21 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
themeVariables.pie11,
themeVariables.pie12,
];
+ let sum = 0;
+ sections.forEach((section) => {
+ sum += section;
+ });
+
+ // Filter out arcs that would render as 0%
+ const filteredArcs = arcs.filter((datum) => ((datum.data.value / sum) * 100).toFixed(0) !== '0');
+
// Set the color scale
const color: d3.ScaleOrdinal = scaleOrdinal(myGeneratedColors);
// Build the pie chart: each part of the pie is a path that we build using the arc function.
group
.selectAll('mySlices')
- .data(arcs)
+ .data(filteredArcs)
.enter()
.append('path')
.attr('d', arcGenerator)
@@ -104,15 +106,11 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
})
.attr('class', 'pieCircle');
- let sum = 0;
- sections.forEach((section) => {
- sum += section;
- });
// Now add the percentage.
// Use the centroid method to get the best coordinates.
group
.selectAll('mySlices')
- .data(arcs)
+ .data(filteredArcs)
.enter()
.append('text')
.text((datum: d3.PieArcDatum): string => {
@@ -133,15 +131,20 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
.attr('class', 'pieTitleText');
// Add the legends/annotations for each section
+ const allSectionData: D3Section[] = [...sections.entries()].map(([label, value]) => ({
+ label,
+ value,
+ }));
+
const legend = group
.selectAll('.legend')
- .data(color.domain())
+ .data(allSectionData)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', (_datum, index: number): string => {
const height = LEGEND_RECT_SIZE + LEGEND_SPACING;
- const offset = (height * color.domain().length) / 2;
+ const offset = (height * allSectionData.length) / 2;
const horizontal = 12 * LEGEND_RECT_SIZE;
const vertical = index * height - offset;
return 'translate(' + horizontal + ',' + vertical + ')';
@@ -151,20 +154,18 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
.append('rect')
.attr('width', LEGEND_RECT_SIZE)
.attr('height', LEGEND_RECT_SIZE)
- .style('fill', color)
- .style('stroke', color);
+ .style('fill', (d) => color(d.label))
+ .style('stroke', (d) => color(d.label));
legend
- .data(arcs)
.append('text')
.attr('x', LEGEND_RECT_SIZE + LEGEND_SPACING)
.attr('y', LEGEND_RECT_SIZE - LEGEND_SPACING)
- .text((datum: d3.PieArcDatum): string => {
- const { label, value } = datum.data;
+ .text((d) => {
if (db.getShowData()) {
- return `${label} [${value}]`;
+ return `${d.label} [${d.value}]`;
}
- return label;
+ return d.label;
});
const longestTextWidth = Math.max(
diff --git a/packages/mermaid/src/docs/syntax/pie.md b/packages/mermaid/src/docs/syntax/pie.md
index 2e7a1799a..416119b5b 100644
--- a/packages/mermaid/src/docs/syntax/pie.md
+++ b/packages/mermaid/src/docs/syntax/pie.md
@@ -24,6 +24,11 @@ Drawing a pie chart is really simple in mermaid.
- Followed by `:` colon as separator
- Followed by `positive numeric value` (supported up to two decimal places)
+**Note:**
+
+> Pie chart values must be **positive numbers greater than zero**.
+> **Negative values are not allowed** and will result in an error.
+
[pie] [showData] (OPTIONAL)
[title] [titlevalue] (OPTIONAL)
"[datakey1]" : [dataValue1]
diff --git a/packages/parser/src/language/pie/pie.langium b/packages/parser/src/language/pie/pie.langium
index a80caa81f..f6802d718 100644
--- a/packages/parser/src/language/pie/pie.langium
+++ b/packages/parser/src/language/pie/pie.langium
@@ -12,5 +12,9 @@ entry Pie:
;
PieSection:
- label=STRING ":" value=NUMBER EOL
+ label=STRING ":" value=NUMBER_PIE EOL
;
+
+terminal FLOAT_PIE returns number: /-?[0-9]+\.[0-9]+(?!\.)/;
+terminal INT_PIE returns number: /-?(0|[1-9][0-9]*)(?!\.)/;
+terminal NUMBER_PIE returns number: FLOAT_PIE | INT_PIE;
\ No newline at end of file