diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 885868c167..c27b9decd6 120000
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1 +1 @@
-./packages/mermaid/src/docs/community/contributing.md
\ No newline at end of file
+./packages/mermaid/src/docs/community/contributing.md
diff --git a/demos/dev/example.html b/demos/dev/example.html
index cc49ddffb7..b4b3b2ad1c 100644
--- a/demos/dev/example.html
+++ b/demos/dev/example.html
@@ -1,4 +1,4 @@
-
+OB
@@ -28,6 +28,14 @@
b --> d
c --> d
+ Pie Chart Example
+
+ pie title Types of industry trends in the last 12 months
+ "Technology" : 50
+ "Healthcare" : 25
+ "Retail" : 15
+ "Finance" : 10
+
Type code to view diagram:
diff --git a/package.json b/package.json
index c4c692d859..2e1cb9baca 100644
--- a/package.json
+++ b/package.json
@@ -118,7 +118,7 @@
"markdown-table": "^3.0.3",
"nyc": "^15.1.0",
"path-browserify": "^1.0.1",
- "prettier": "^3.2.5",
+ "prettier": "^3.3.3",
"prettier-plugin-jsdoc": "^1.3.0",
"rimraf": "^5.0.5",
"rollup-plugin-visualizer": "^5.12.0",
diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts
index 0f02efa0d6..e2751ca3a6 100644
--- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts
+++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts
@@ -219,10 +219,18 @@ export const addRelations = function (relations: ClassRelation[], g: graphlib.Gr
const conf = getConfig().flowchart;
let cnt = 0;
+ // A set to keep track of rendered edges to avoid duplicates
+ const renderedEdges = new Set();
+
relations.forEach(function (edge) {
cnt++;
+ const isSelfReferencing = edge.id1 === edge.id2; // Check if the edge is self-referencing
+
+ const edgeKey = `${edge.id1}->${edge.id2}`; // Unique key for each edge
+
+ // Edge data setup
const edgeData: EdgeData = {
- //Set relationship style and line type
+ // Set relationship style and line type
classes: 'relation',
pattern: edge.relation.lineType == 1 ? 'dashed' : 'solid',
id: getEdgeId(edge.id1, edge.id2, {
@@ -231,18 +239,23 @@ export const addRelations = function (relations: ClassRelation[], g: graphlib.Gr
}),
// Set link type for rendering
arrowhead: edge.type === 'arrow_open' ? 'none' : 'normal',
- //Set edge extra labels
+ // Set edge extra labels
startLabelRight: edge.relationTitle1 === 'none' ? '' : edge.relationTitle1,
endLabelLeft: edge.relationTitle2 === 'none' ? '' : edge.relationTitle2,
- //Set relation arrow types
+ // Set relation arrow types
arrowTypeStart: getArrowMarker(edge.relation.type1),
arrowTypeEnd: getArrowMarker(edge.relation.type2),
style: 'fill:none',
labelStyle: '',
- curve: interpolateToCurve(conf?.curve, curveLinear),
+ curve: isSelfReferencing
+ ? curveLinear // Apply a specific curve for self-referencing relations
+ : interpolateToCurve(conf?.curve, curveLinear),
};
- log.info(edgeData, edge);
+ // Style adjustments
+ if (!edgeData.style) {
+ edgeData.style = 'stroke: #999; fill: none;';
+ }
if (edge.style !== undefined) {
const styles = getStylesFromArray(edge.style);
@@ -259,7 +272,7 @@ export const addRelations = function (relations: ClassRelation[], g: graphlib.Gr
edgeData.arrowheadStyle = 'fill: #333';
edgeData.labelpos = 'c';
- // TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
+ // Handle HTML labels for flowchart compatibility
if (getConfig().flowchart?.htmlLabels ?? getConfig().htmlLabels) {
edgeData.labelType = 'html';
edgeData.label = '' + edge.text + '';
@@ -274,8 +287,22 @@ export const addRelations = function (relations: ClassRelation[], g: graphlib.Gr
edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
}
}
- // Add the edge to the graph
- g.setEdge(edge.id1, edge.id2, edgeData, cnt);
+
+ // Add specific adjustments for self-referencing edges
+ if (isSelfReferencing) {
+ edgeData.points = [
+ { x: 50, y: 100 }, // Starting point
+ { x: 70, y: 70 }, // Control point 1
+ { x: 50, y: 40 }, // Control point 2
+ ];
+ edgeData.arrowheadStyle = 'fill: #555'; // Darker arrow for visibility
+ }
+
+ // Check and render edge if it hasn't already been rendered
+ if (!renderedEdges.has(edgeKey)) {
+ g.setEdge(edge.id1, edge.id2, edgeData, cnt);
+ renderedEdges.add(edgeKey); // Mark this edge as rendered
+ }
});
};
@@ -386,14 +413,15 @@ export const draw = async function (text: string, id: string, _version: string,
* @param type - The type to look for
* @returns The arrow marker
*/
-function getArrowMarker(type: number) {
+function getArrowMarker(type: number | string) {
let marker;
switch (type) {
case 0:
- marker = 'aggregation';
+ case 'none': // Ensure "none" is explicitly handled
+ marker = 'none';
break;
case 1:
- marker = 'extension';
+ marker = 'aggregation';
break;
case 2:
marker = 'composition';
@@ -405,7 +433,7 @@ function getArrowMarker(type: number) {
marker = 'lollipop';
break;
default:
- marker = 'none';
+ marker = 'none'; // Fallback
}
return marker;
}
diff --git a/packages/mermaid/src/diagrams/pie/pieRenderer.ts b/packages/mermaid/src/diagrams/pie/pieRenderer.ts
index a0cdce3df7..6b1bd3b268 100644
--- a/packages/mermaid/src/diagrams/pie/pieRenderer.ts
+++ b/packages/mermaid/src/diagrams/pie/pieRenderer.ts
@@ -10,41 +10,32 @@ 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 pieData: D3Section[] = [...sections.entries()]
- .map((element: [string, number]): D3Section => {
- return {
+ .map(
+ (element: [string, number]): D3Section => ({
label: element[0],
value: element[1],
- };
- })
- .sort((a: D3Section, b: D3Section): number => {
- return b.value - a.value;
- });
+ })
+ )
+ .sort((a: D3Section, b: D3Section): number => b.value - a.value);
const pie: d3.Pie = d3pie().value(
(d3Section: D3Section): number => d3Section.value
);
return pie(pieData);
};
-/**
- * Draws a Pie Chart with the data given in text.
- *
- * @param text - pie chart code
- * @param id - diagram id
- * @param _version - MermaidJS version from package.json.
- * @param diagObj - A standard diagram containing the DB and the text and type etc of the diagram.
- */
export const draw: DrawDefinition = (text, id, _version, diagObj) => {
- log.debug('rendering pie chart\n' + text);
+ log.debug('Rendering pie chart\n' + text);
const db = diagObj.db as PieDB;
const globalConfig: MermaidConfig = getConfig();
const pieConfig: Required = cleanAndMerge(db.getConfig(), globalConfig.pie);
+
const MARGIN = 40;
const LEGEND_RECT_SIZE = 18;
const LEGEND_SPACING = 4;
const height = 450;
const pieWidth: number = height;
+
const svg: SVG = selectSvgElement(id);
const group: SVGGroup = svg.append('g');
group.attr('transform', 'translate(' + pieWidth / 2 + ',' + height / 2 + ')');
@@ -55,7 +46,7 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
const textPosition: number = pieConfig.textPosition;
const radius: number = Math.min(pieWidth, height) / 2 - MARGIN;
- // Shape helper to build arcs:
+
const arcGenerator: d3.Arc> = arc>()
.innerRadius(0)
.outerRadius(radius);
@@ -89,50 +80,63 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
themeVariables.pie11,
themeVariables.pie12,
];
- // 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)
.enter()
.append('path')
.attr('d', arcGenerator)
- .attr('fill', (datum: d3.PieArcDatum) => {
- return color(datum.data.label);
- })
+ .attr('fill', (datum: d3.PieArcDatum) => color(datum.data.label))
.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)
.enter()
.append('text')
.text((datum: d3.PieArcDatum): string => {
- return ((datum.data.value / sum) * 100).toFixed(0) + '%';
+ return `${((datum.data.value / sum) * 100).toFixed(0)}%`;
})
.attr('transform', (datum: d3.PieArcDatum): string => {
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
- return 'translate(' + labelArcGenerator.centroid(datum) + ')';
+ const [x, y] = labelArcGenerator.centroid(datum);
+ return `translate(${x}, ${y})`;
})
.style('text-anchor', 'middle')
.attr('class', 'slice');
- group
+ const titleGroup = group.append('g');
+ const titleText = db.getDiagramTitle();
+
+ // Adjust title font size dynamically
+ let fontSize = 25; // Start with a larger font size
+ const minFontSize = 8; // Set a minimum font size
+ const maxAvailableWidth = pieWidth - MARGIN;
+
+ const titleElement = titleGroup
.append('text')
- .text(db.getDiagramTitle())
+ .text(titleText)
.attr('x', 0)
.attr('y', -(height - 50) / 2)
- .attr('class', 'pieTitleText');
+ .attr('class', 'pieTitleText')
+ .style('text-anchor', 'middle');
+
+ // Reduce font size dynamically until it fits
+ while (
+ titleElement.node()?.getBBox()?.width > maxAvailableWidth &&
+ fontSize > minFontSize
+ ) {
+ fontSize -= 1;
+ titleElement.style('font-size', `${fontSize}px`);
+ }
- // Add the legends/annotations for each section
const legend = group
.selectAll('.legend')
.data(color.domain())
@@ -161,10 +165,7 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
.attr('y', LEGEND_RECT_SIZE - LEGEND_SPACING)
.text((datum: d3.PieArcDatum): string => {
const { label, value } = datum.data;
- if (db.getShowData()) {
- return `${label} [${value}]`;
- }
- return label;
+ return db.getShowData() ? `${label} [${value}]` : label;
});
const longestTextWidth = Math.max(
@@ -176,7 +177,6 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
const totalWidth = pieWidth + MARGIN + LEGEND_RECT_SIZE + LEGEND_SPACING + longestTextWidth;
- // Set viewBox
svg.attr('viewBox', `0 0 ${totalWidth} ${height}`);
configureSvgSize(svg, height, totalWidth, pieConfig.useMaxWidth);
};
diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts
index 5bd1b1dfcf..9064208d92 100644
--- a/packages/mermaid/src/mermaidAPI.spec.ts
+++ b/packages/mermaid/src/mermaidAPI.spec.ts
@@ -12,8 +12,12 @@ vi.mock('dagre-d3');
// mermaidAPI.spec.ts:
import * as accessibility from './accessibility.js'; // Import it this way so we can use spyOn(accessibility,...)
vi.mock('./accessibility.js', () => ({
- setA11yDiagramInfo: vi.fn(),
- addSVGa11yTitleDescription: vi.fn(),
+ setA11yDiagramInfo: vi.fn(() => {
+ return 'setA11yDiagramInfo called';
+ }),
+ addSVGa11yTitleDescription: vi.fn(() => {
+ return 'addSVGa11yTitleDescription called';
+ }),
}));
// Mock the renderers specifically so we can test render(). Need to mock draw() for each renderer
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7f3f4fa5f9..3146c0a42b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -63,7 +63,7 @@ importers:
version: 4.2.4
'@vitest/coverage-v8':
specifier: ^1.4.0
- version: 1.6.0(vitest@1.6.0(@types/node@20.16.11)(@vitest/ui@1.6.0)(jsdom@24.1.3)(terser@5.34.1))
+ version: 1.6.0(vitest@1.6.0)
'@vitest/spy':
specifier: ^1.4.0
version: 1.6.0
@@ -179,7 +179,7 @@ importers:
specifier: ^1.0.1
version: 1.0.1
prettier:
- specifier: ^3.2.5
+ specifier: ^3.3.3
version: 3.3.3
prettier-plugin-jsdoc:
specifier: ^1.3.0
@@ -12610,7 +12610,7 @@ snapshots:
vite: 5.4.8(@types/node@20.16.11)(terser@5.34.1)
vue: 3.5.11(typescript@5.6.2)
- '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.16.11)(@vitest/ui@1.6.0)(jsdom@24.1.3)(terser@5.34.1))':
+ '@vitest/coverage-v8@1.6.0(vitest@1.6.0)':
dependencies:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 0.2.3
@@ -12927,17 +12927,17 @@ snapshots:
'@webassemblyjs/ast': 1.12.1
'@xtuc/long': 4.2.2
- '@webpack-cli/configtest@1.2.0(webpack-cli@4.10.0(webpack-dev-server@4.15.2)(webpack@5.95.0))(webpack@5.95.0(esbuild@0.21.5)(webpack-cli@4.10.0))':
+ '@webpack-cli/configtest@1.2.0(webpack-cli@4.10.0)(webpack@5.95.0)':
dependencies:
webpack: 5.95.0(esbuild@0.21.5)(webpack-cli@4.10.0)
webpack-cli: 4.10.0(webpack-dev-server@4.15.2)(webpack@5.95.0)
- '@webpack-cli/info@1.5.0(webpack-cli@4.10.0(webpack-dev-server@4.15.2)(webpack@5.95.0))':
+ '@webpack-cli/info@1.5.0(webpack-cli@4.10.0)':
dependencies:
envinfo: 7.14.0
webpack-cli: 4.10.0(webpack-dev-server@4.15.2)(webpack@5.95.0)
- '@webpack-cli/serve@1.7.0(webpack-cli@4.10.0(webpack-dev-server@4.15.2)(webpack@5.95.0))(webpack-dev-server@4.15.2(webpack-cli@4.10.0)(webpack@5.95.0))':
+ '@webpack-cli/serve@1.7.0(webpack-cli@4.10.0)(webpack-dev-server@4.15.2)':
dependencies:
webpack-cli: 4.10.0(webpack-dev-server@4.15.2)(webpack@5.95.0)
optionalDependencies:
@@ -18949,25 +18949,25 @@ snapshots:
term-size@2.2.1: {}
- terser-webpack-plugin@5.3.10(esbuild@0.21.5)(webpack@5.95.0(esbuild@0.21.5)(webpack-cli@4.10.0)):
+ terser-webpack-plugin@5.3.10(esbuild@0.21.5)(webpack@5.95.0(esbuild@0.21.5)):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.2
terser: 5.34.1
- webpack: 5.95.0(esbuild@0.21.5)(webpack-cli@4.10.0)
+ webpack: 5.95.0(esbuild@0.21.5)
optionalDependencies:
esbuild: 0.21.5
- terser-webpack-plugin@5.3.10(esbuild@0.21.5)(webpack@5.95.0(esbuild@0.21.5)):
+ terser-webpack-plugin@5.3.10(esbuild@0.21.5)(webpack@5.95.0):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.2
terser: 5.34.1
- webpack: 5.95.0(esbuild@0.21.5)
+ webpack: 5.95.0(esbuild@0.21.5)(webpack-cli@4.10.0)
optionalDependencies:
esbuild: 0.21.5
@@ -19732,9 +19732,9 @@ snapshots:
webpack-cli@4.10.0(webpack-dev-server@4.15.2)(webpack@5.95.0):
dependencies:
'@discoveryjs/json-ext': 0.5.7
- '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0(webpack-dev-server@4.15.2)(webpack@5.95.0))(webpack@5.95.0(esbuild@0.21.5)(webpack-cli@4.10.0))
- '@webpack-cli/info': 1.5.0(webpack-cli@4.10.0(webpack-dev-server@4.15.2)(webpack@5.95.0))
- '@webpack-cli/serve': 1.7.0(webpack-cli@4.10.0(webpack-dev-server@4.15.2)(webpack@5.95.0))(webpack-dev-server@4.15.2(webpack-cli@4.10.0)(webpack@5.95.0))
+ '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0)(webpack@5.95.0)
+ '@webpack-cli/info': 1.5.0(webpack-cli@4.10.0)
+ '@webpack-cli/serve': 1.7.0(webpack-cli@4.10.0)(webpack-dev-server@4.15.2)
colorette: 2.0.20
commander: 7.2.0
cross-spawn: 7.0.3
@@ -19747,7 +19747,7 @@ snapshots:
optionalDependencies:
webpack-dev-server: 4.15.2(webpack-cli@4.10.0)(webpack@5.95.0)
- webpack-dev-middleware@5.3.4(webpack@5.95.0(esbuild@0.21.5)(webpack-cli@4.10.0)):
+ webpack-dev-middleware@5.3.4(webpack@5.95.0):
dependencies:
colorette: 2.0.20
memfs: 3.5.3
@@ -19786,7 +19786,7 @@ snapshots:
serve-index: 1.9.1
sockjs: 0.3.24
spdy: 4.0.2
- webpack-dev-middleware: 5.3.4(webpack@5.95.0(esbuild@0.21.5)(webpack-cli@4.10.0))
+ webpack-dev-middleware: 5.3.4(webpack@5.95.0)
ws: 8.18.0
optionalDependencies:
webpack: 5.95.0(esbuild@0.21.5)(webpack-cli@4.10.0)
@@ -19859,7 +19859,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.2.1
- terser-webpack-plugin: 5.3.10(esbuild@0.21.5)(webpack@5.95.0(esbuild@0.21.5)(webpack-cli@4.10.0))
+ terser-webpack-plugin: 5.3.10(esbuild@0.21.5)(webpack@5.95.0)
watchpack: 2.4.2
webpack-sources: 3.2.3
optionalDependencies: