Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Adjust pie chart title to prevent cropping and added example for testing #6242

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
10 changes: 9 additions & 1 deletion demos/dev/example.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--Do not edit this file-->
OB<!--Do not edit this file-->
<!--Duplicate this file to any name you like, run `pnpm dev`, open http://localhost:9000/dev/name.html to view-->
<html>
<head>
Expand Down Expand Up @@ -28,6 +28,14 @@
b --> d
c --> d
</pre>
<h2>Pie Chart Example</h2>
<pre class="mermaid">
pie title Types of industry trends in the last 12 months
"Technology" : 50
"Healthcare" : 25
"Retail" : 15
"Finance" : 10
</pre>

<hr />
Type code to view diagram:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
52 changes: 40 additions & 12 deletions packages/mermaid/src/diagrams/class/classRenderer-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand All @@ -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);
Expand All @@ -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 = '<span class="edgeLabel">' + edge.text + '</span>';
Expand All @@ -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
}
});
};

Expand Down Expand Up @@ -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';
Expand All @@ -405,7 +433,7 @@ function getArrowMarker(type: number) {
marker = 'lollipop';
break;
default:
marker = 'none';
marker = 'none'; // Fallback
}
return marker;
}
Expand Down
74 changes: 37 additions & 37 deletions packages/mermaid/src/diagrams/pie/pieRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,32 @@ import { cleanAndMerge, parseFontSize } from '../../utils.js';
import type { D3Section, PieDB, Sections } from './pieTypes.js';

const createPieArcs = (sections: Sections): d3.PieArcDatum<D3Section>[] => {
// 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<unknown, D3Section> = d3pie<D3Section>().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<PieDiagramConfig> = 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 + ')');
Expand All @@ -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<unknown, d3.PieArcDatum<D3Section>> = arc<d3.PieArcDatum<D3Section>>()
.innerRadius(0)
.outerRadius(radius);
Expand Down Expand Up @@ -89,50 +80,63 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
themeVariables.pie11,
themeVariables.pie12,
];
// Set the color scale

const color: d3.ScaleOrdinal<string, 12, never> = 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<D3Section>) => {
return color(datum.data.label);
})
.attr('fill', (datum: d3.PieArcDatum<D3Section>) => 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<D3Section>): string => {
return ((datum.data.value / sum) * 100).toFixed(0) + '%';
return `${((datum.data.value / sum) * 100).toFixed(0)}%`;
})
.attr('transform', (datum: d3.PieArcDatum<D3Section>): 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())
Expand Down Expand Up @@ -161,10 +165,7 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
.attr('y', LEGEND_RECT_SIZE - LEGEND_SPACING)
.text((datum: d3.PieArcDatum<D3Section>): string => {
const { label, value } = datum.data;
if (db.getShowData()) {
return `${label} [${value}]`;
}
return label;
return db.getShowData() ? `${label} [${value}]` : label;
});

const longestTextWidth = Math.max(
Expand All @@ -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);
};
Expand Down
8 changes: 6 additions & 2 deletions packages/mermaid/src/mermaidAPI.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading