mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-22 17:56:43 +02:00
Merge pull request #4501 from Yokozuna59/standardized-pie-definitions
standardized pie definitions
This commit is contained in:
@@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* Mocked pie (picChart) diagram renderer
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { vi } from 'vitest';
|
|
||||||
|
|
||||||
export const draw = vi.fn().mockImplementation(() => {
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
draw,
|
|
||||||
};
|
|
8
__mocks__/pieRenderer.ts
Normal file
8
__mocks__/pieRenderer.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Mocked pie (picChart) diagram renderer
|
||||||
|
*/
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
|
||||||
|
const draw = vi.fn().mockImplementation(() => '');
|
||||||
|
|
||||||
|
export const renderer = { draw };
|
@@ -1,89 +1,85 @@
|
|||||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||||
|
|
||||||
describe('Pie Chart', () => {
|
describe('pie chart', () => {
|
||||||
it('should render a simple pie diagram', () => {
|
it('should render a simple pie diagram', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
|
`pie title Sports in Sweden
|
||||||
|
"Bandy": 40
|
||||||
|
"Ice-Hockey": 80
|
||||||
|
"Football": 90
|
||||||
`
|
`
|
||||||
pie title Sports in Sweden
|
|
||||||
"Bandy" : 40
|
|
||||||
"Ice-Hockey" : 80
|
|
||||||
"Football" : 90
|
|
||||||
`,
|
|
||||||
{}
|
|
||||||
);
|
);
|
||||||
cy.get('svg');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render a simple pie diagram with long labels', () => {
|
it('should render a simple pie diagram with long labels', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
|
`pie title NETFLIX
|
||||||
|
"Time spent looking for movie": 90
|
||||||
|
"Time spent watching it": 10
|
||||||
`
|
`
|
||||||
pie title NETFLIX
|
|
||||||
"Time spent looking for movie" : 90
|
|
||||||
"Time spent watching it" : 10
|
|
||||||
`,
|
|
||||||
{}
|
|
||||||
);
|
);
|
||||||
cy.get('svg');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render a simple pie diagram with capital letters for labels', () => {
|
it('should render a simple pie diagram with capital letters for labels', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
|
`pie title What Voldemort doesn't have?
|
||||||
|
"FRIENDS": 2
|
||||||
|
"FAMILY": 3
|
||||||
|
"NOSE": 45
|
||||||
`
|
`
|
||||||
pie title What Voldemort doesn't have?
|
|
||||||
"FRIENDS" : 2
|
|
||||||
"FAMILY" : 3
|
|
||||||
"NOSE" : 45
|
|
||||||
`,
|
|
||||||
{}
|
|
||||||
);
|
);
|
||||||
cy.get('svg');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render a pie diagram when useMaxWidth is true (default)', () => {
|
it('should render a pie diagram when useMaxWidth is true (default)', () => {
|
||||||
renderGraph(
|
renderGraph(
|
||||||
`
|
`pie title Sports in Sweden
|
||||||
pie title Sports in Sweden
|
"Bandy": 40
|
||||||
"Bandy" : 40
|
"Ice-Hockey": 80
|
||||||
"Ice-Hockey" : 80
|
"Football": 90
|
||||||
"Football" : 90
|
|
||||||
`,
|
`,
|
||||||
{ pie: { useMaxWidth: true } }
|
{ pie: { useMaxWidth: true } }
|
||||||
);
|
);
|
||||||
cy.get('svg').should((svg) => {
|
cy.get('svg').should((svg) => {
|
||||||
expect(svg).to.have.attr('width', '100%');
|
expect(svg).to.have.attr('width', '100%');
|
||||||
// expect(svg).to.have.attr('height');
|
|
||||||
// const height = parseFloat(svg.attr('height'));
|
|
||||||
// expect(height).to.eq(450);
|
|
||||||
const style = svg.attr('style');
|
const style = svg.attr('style');
|
||||||
expect(style).to.match(/^max-width: [\d.]+px;$/);
|
expect(style).to.match(/^max-width: [\d.]+px;$/);
|
||||||
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
|
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
|
||||||
expect(maxWidthValue).to.eq(984);
|
expect(maxWidthValue).to.eq(984);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render a pie diagram when useMaxWidth is false', () => {
|
it('should render a pie diagram when useMaxWidth is false', () => {
|
||||||
renderGraph(
|
renderGraph(
|
||||||
`
|
`pie title Sports in Sweden
|
||||||
pie title Sports in Sweden
|
"Bandy": 40
|
||||||
"Bandy" : 40
|
"Ice-Hockey": 80
|
||||||
"Ice-Hockey" : 80
|
"Football": 90
|
||||||
"Football" : 90
|
|
||||||
`,
|
`,
|
||||||
{ pie: { useMaxWidth: false } }
|
{ pie: { useMaxWidth: false } }
|
||||||
);
|
);
|
||||||
cy.get('svg').should((svg) => {
|
cy.get('svg').should((svg) => {
|
||||||
// const height = parseFloat(svg.attr('height'));
|
|
||||||
const width = parseFloat(svg.attr('width'));
|
const width = parseFloat(svg.attr('width'));
|
||||||
// expect(height).to.eq(450);
|
|
||||||
expect(width).to.eq(984);
|
expect(width).to.eq(984);
|
||||||
expect(svg).to.not.have.attr('style');
|
expect(svg).to.not.have.attr('style');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should render a pie diagram when textPosition is set', () => {
|
|
||||||
|
it('should render a pie diagram when textPosition is setted', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`
|
`pie
|
||||||
pie
|
"Dogs": 50
|
||||||
"Dogs": 50
|
"Cats": 25
|
||||||
"Cats": 25
|
`,
|
||||||
`,
|
|
||||||
{ logLevel: 1, pie: { textPosition: 0.9 } }
|
{ logLevel: 1, pie: { textPosition: 0.9 } }
|
||||||
);
|
);
|
||||||
cy.get('svg');
|
});
|
||||||
|
|
||||||
|
it('should render a pie diagram with showData', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`pie showData
|
||||||
|
"Dogs": 50
|
||||||
|
"Cats": 25
|
||||||
|
`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
@@ -7,7 +7,6 @@
|
|||||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||||
<style>
|
<style>
|
||||||
div.mermaid {
|
div.mermaid {
|
||||||
/* font-family: 'trebuchet ms', verdana, arial; */
|
|
||||||
font-family: 'Courier New', Courier, monospace !important;
|
font-family: 'Courier New', Courier, monospace !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -17,37 +16,32 @@
|
|||||||
<h1>Pie chart demos</h1>
|
<h1>Pie chart demos</h1>
|
||||||
<pre class="mermaid">
|
<pre class="mermaid">
|
||||||
pie title Pets adopted by volunteers
|
pie title Pets adopted by volunteers
|
||||||
accTitle: simple pie char demo
|
accTitle: simple pie char demo
|
||||||
accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
|
accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
|
||||||
"Dogs" : 386
|
"Dogs": 386
|
||||||
"Cats" : 85
|
"Cats": 85
|
||||||
"Rats" : 15
|
"Rats": 15
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<pre class="mermaid">
|
<pre class="mermaid">
|
||||||
%%{init: {"pie": {"textPosition": 0.9}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
|
%%{init: {"pie": {"textPosition": 0.9}, "themeVariables": {"pieOuterStrokeWidth": "5px"}}}%%
|
||||||
pie
|
pie
|
||||||
title Key elements in Product X
|
title Key elements in Product X
|
||||||
accTitle: Key elements in Product X
|
accTitle: Key elements in Product X
|
||||||
accDescr: This is a pie chart showing the key elements in Product X.
|
accDescr: This is a pie chart showing the key elements in Product X.
|
||||||
"Calcium" : 42.96
|
"Calcium": 42.96
|
||||||
"Potassium" : 50.05
|
"Potassium": 50.05
|
||||||
"Magnesium" : 10.01
|
"Magnesium": 10.01
|
||||||
"Iron" : 5
|
"Iron": 5
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import mermaid from './mermaid.esm.mjs';
|
import mermaid from './mermaid.esm.mjs';
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
theme: 'forest',
|
theme: 'forest',
|
||||||
// themeCSS: '.node rect { fill: red; }',
|
|
||||||
logLevel: 3,
|
logLevel: 3,
|
||||||
securityLevel: 'loose',
|
securityLevel: 'loose',
|
||||||
// flowchart: { curve: 'basis' },
|
|
||||||
// gantt: { axisFormat: '%m/%d/%Y' },
|
|
||||||
sequence: { actorMargin: 50 },
|
|
||||||
// sequenceDiagram: { actorMargin: 300 } // deprecated
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
@@ -14,13 +14,13 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[defaultConfig.ts:266](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L266)
|
[defaultConfig.ts:268](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L268)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### default
|
### default
|
||||||
|
|
||||||
• `Const` **default**: `Partial`<`MermaidConfig`>
|
• `Const` **default**: `RequiredDeep`<`MermaidConfig`>
|
||||||
|
|
||||||
Default mermaid configuration options.
|
Default mermaid configuration options.
|
||||||
|
|
||||||
@@ -30,4 +30,4 @@ Non-JSON JS default values are listed in this file, e.g. functions, or
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[defaultConfig.ts:16](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L16)
|
[defaultConfig.ts:18](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L18)
|
||||||
|
@@ -84,7 +84,9 @@
|
|||||||
"@types/cytoscape": "^3.19.9",
|
"@types/cytoscape": "^3.19.9",
|
||||||
"@types/d3": "^7.4.0",
|
"@types/d3": "^7.4.0",
|
||||||
"@types/d3-sankey": "^0.12.1",
|
"@types/d3-sankey": "^0.12.1",
|
||||||
|
"@types/d3-scale": "^4.0.3",
|
||||||
"@types/d3-selection": "^3.0.5",
|
"@types/d3-selection": "^3.0.5",
|
||||||
|
"@types/d3-shape": "^3.1.1",
|
||||||
"@types/dompurify": "^3.0.2",
|
"@types/dompurify": "^3.0.2",
|
||||||
"@types/jsdom": "^21.1.1",
|
"@types/jsdom": "^21.1.1",
|
||||||
"@types/lodash-es": "^4.17.7",
|
"@types/lodash-es": "^4.17.7",
|
||||||
@@ -113,6 +115,7 @@
|
|||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"rimraf": "^5.0.0",
|
"rimraf": "^5.0.0",
|
||||||
"start-server-and-test": "^2.0.0",
|
"start-server-and-test": "^2.0.0",
|
||||||
|
"type-fest": "^4.1.0",
|
||||||
"typedoc": "^0.24.5",
|
"typedoc": "^0.24.5",
|
||||||
"typedoc-plugin-markdown": "^3.15.2",
|
"typedoc-plugin-markdown": "^3.15.2",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
|
import type { RequiredDeep } from 'type-fest';
|
||||||
|
|
||||||
import theme from './themes/index.js';
|
import theme from './themes/index.js';
|
||||||
import { type MermaidConfig } from './config.type.js';
|
import type { MermaidConfig } from './config.type.js';
|
||||||
|
|
||||||
// Uses our custom Vite jsonSchemaPlugin to load only the default values from
|
// Uses our custom Vite jsonSchemaPlugin to load only the default values from
|
||||||
// our JSON Schema
|
// our JSON Schema
|
||||||
@@ -13,7 +15,7 @@ import defaultConfigJson from './schemas/config.schema.yaml?only-defaults=true';
|
|||||||
* Non-JSON JS default values are listed in this file, e.g. functions, or
|
* Non-JSON JS default values are listed in this file, e.g. functions, or
|
||||||
* `undefined` (explicitly set so that `configKeys` finds them).
|
* `undefined` (explicitly set so that `configKeys` finds them).
|
||||||
*/
|
*/
|
||||||
const config: Partial<MermaidConfig> = {
|
const config: RequiredDeep<MermaidConfig> = {
|
||||||
...defaultConfigJson,
|
...defaultConfigJson,
|
||||||
// Set, even though they're `undefined` so that `configKeys` finds these keys
|
// Set, even though they're `undefined` so that `configKeys` finds these keys
|
||||||
// TODO: Should we replace these with `null` so that they can go in the JSON Schema?
|
// TODO: Should we replace these with `null` so that they can go in the JSON Schema?
|
||||||
@@ -232,7 +234,7 @@ const config: Partial<MermaidConfig> = {
|
|||||||
},
|
},
|
||||||
pie: {
|
pie: {
|
||||||
...defaultConfigJson.pie,
|
...defaultConfigJson.pie,
|
||||||
useWidth: undefined,
|
useWidth: 984,
|
||||||
},
|
},
|
||||||
requirement: {
|
requirement: {
|
||||||
...defaultConfigJson.requirement,
|
...defaultConfigJson.requirement,
|
||||||
|
@@ -5,7 +5,7 @@ import er from '../diagrams/er/erDetector.js';
|
|||||||
import git from '../diagrams/git/gitGraphDetector.js';
|
import git from '../diagrams/git/gitGraphDetector.js';
|
||||||
import gantt from '../diagrams/gantt/ganttDetector.js';
|
import gantt from '../diagrams/gantt/ganttDetector.js';
|
||||||
import { info } from '../diagrams/info/infoDetector.js';
|
import { info } from '../diagrams/info/infoDetector.js';
|
||||||
import pie from '../diagrams/pie/pieDetector.js';
|
import { pie } from '../diagrams/pie/pieDetector.js';
|
||||||
import quadrantChart from '../diagrams/quadrant-chart/quadrantDetector.js';
|
import quadrantChart from '../diagrams/quadrant-chart/quadrantDetector.js';
|
||||||
import requirement from '../diagrams/requirement/requirementDetector.js';
|
import requirement from '../diagrams/requirement/requirementDetector.js';
|
||||||
import sequence from '../diagrams/sequence/sequenceDetector.js';
|
import sequence from '../diagrams/sequence/sequenceDetector.js';
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Diagram } from '../Diagram.js';
|
import { Diagram } from '../Diagram.js';
|
||||||
import type { MermaidConfig } from '../config.type.js';
|
import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js';
|
||||||
import type * as d3 from 'd3';
|
import type * as d3 from 'd3';
|
||||||
|
|
||||||
export interface InjectUtils {
|
export interface InjectUtils {
|
||||||
@@ -16,11 +16,19 @@ export interface InjectUtils {
|
|||||||
* Generic Diagram DB that may apply to any diagram type.
|
* Generic Diagram DB that may apply to any diagram type.
|
||||||
*/
|
*/
|
||||||
export interface DiagramDB {
|
export interface DiagramDB {
|
||||||
|
// config
|
||||||
|
getConfig?: () => BaseDiagramConfig | undefined;
|
||||||
|
|
||||||
|
// db
|
||||||
clear?: () => void;
|
clear?: () => void;
|
||||||
setDiagramTitle?: (title: string) => void;
|
setDiagramTitle?: (title: string) => void;
|
||||||
setDisplayMode?: (title: string) => void;
|
getDiagramTitle?: () => string;
|
||||||
|
setAccTitle?: (title: string) => void;
|
||||||
getAccTitle?: () => string;
|
getAccTitle?: () => string;
|
||||||
|
setAccDescription?: (describetion: string) => void;
|
||||||
getAccDescription?: () => string;
|
getAccDescription?: () => string;
|
||||||
|
|
||||||
|
setDisplayMode?: (title: string) => void;
|
||||||
bindFunctions?: (element: Element) => void;
|
bindFunctions?: (element: Element) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
name,amounts
|
|
||||||
Foo, 33
|
|
||||||
Rishab, 12
|
|
||||||
Alexis, 41
|
|
||||||
Tom, 16
|
|
||||||
Courtney, 59
|
|
||||||
Christina, 38
|
|
||||||
Jack, 21
|
|
||||||
Mickey, 25
|
|
||||||
Paul, 30
|
|
|
@@ -1,132 +0,0 @@
|
|||||||
import pieDb from '../pieDb.js';
|
|
||||||
import pie from './pie.jison';
|
|
||||||
import { setConfig } from '../../../config.js';
|
|
||||||
|
|
||||||
setConfig({
|
|
||||||
securityLevel: 'strict',
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when parsing pie', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
pie.parser.yy = pieDb;
|
|
||||||
pie.parser.yy.clear();
|
|
||||||
});
|
|
||||||
it('should handle very simple pie', function () {
|
|
||||||
const res = pie.parser.parse(`pie
|
|
||||||
"ash" : 100
|
|
||||||
`);
|
|
||||||
const sections = pieDb.getSections();
|
|
||||||
const section1 = sections['ash'];
|
|
||||||
expect(section1).toBe(100);
|
|
||||||
});
|
|
||||||
it('should handle simple pie', function () {
|
|
||||||
const res = pie.parser.parse(`pie
|
|
||||||
"ash" : 60
|
|
||||||
"bat" : 40
|
|
||||||
`);
|
|
||||||
const sections = pieDb.getSections();
|
|
||||||
const section1 = sections['ash'];
|
|
||||||
expect(section1).toBe(60);
|
|
||||||
});
|
|
||||||
it('should handle simple pie with comments', function () {
|
|
||||||
const res = pie.parser.parse(`pie
|
|
||||||
%% comments
|
|
||||||
"ash" : 60
|
|
||||||
"bat" : 40
|
|
||||||
`);
|
|
||||||
const sections = pieDb.getSections();
|
|
||||||
const section1 = sections['ash'];
|
|
||||||
expect(section1).toBe(60);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle simple pie with a directive', function () {
|
|
||||||
const res = pie.parser.parse(`%%{init: {'logLevel':0}}%%
|
|
||||||
pie
|
|
||||||
"ash" : 60
|
|
||||||
"bat" : 40
|
|
||||||
`);
|
|
||||||
const sections = pieDb.getSections();
|
|
||||||
const section1 = sections['ash'];
|
|
||||||
expect(section1).toBe(60);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle simple pie with a title', function () {
|
|
||||||
const res = pie.parser.parse(`pie title a 60/40 pie
|
|
||||||
"ash" : 60
|
|
||||||
"bat" : 40
|
|
||||||
`);
|
|
||||||
const sections = pieDb.getSections();
|
|
||||||
const title = pieDb.getDiagramTitle();
|
|
||||||
const section1 = sections['ash'];
|
|
||||||
expect(section1).toBe(60);
|
|
||||||
expect(title).toBe('a 60/40 pie');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle simple pie without an acc description (accDescr)', function () {
|
|
||||||
const res = pie.parser.parse(`pie title a neat chart
|
|
||||||
"ash" : 60
|
|
||||||
"bat" : 40
|
|
||||||
`);
|
|
||||||
|
|
||||||
const sections = pieDb.getSections();
|
|
||||||
const title = pieDb.getDiagramTitle();
|
|
||||||
const description = pieDb.getAccDescription();
|
|
||||||
const section1 = sections['ash'];
|
|
||||||
expect(section1).toBe(60);
|
|
||||||
expect(title).toBe('a neat chart');
|
|
||||||
expect(description).toBe('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle simple pie with an acc description (accDescr)', function () {
|
|
||||||
const res = pie.parser.parse(`pie title a neat chart
|
|
||||||
accDescr: a neat description
|
|
||||||
"ash" : 60
|
|
||||||
"bat" : 40
|
|
||||||
`);
|
|
||||||
|
|
||||||
const sections = pieDb.getSections();
|
|
||||||
const title = pieDb.getDiagramTitle();
|
|
||||||
const description = pieDb.getAccDescription();
|
|
||||||
const section1 = sections['ash'];
|
|
||||||
expect(section1).toBe(60);
|
|
||||||
expect(title).toBe('a neat chart');
|
|
||||||
expect(description).toBe('a neat description');
|
|
||||||
});
|
|
||||||
it('should handle simple pie with a multiline acc description (accDescr)', function () {
|
|
||||||
const res = pie.parser.parse(`pie title a neat chart
|
|
||||||
accDescr {
|
|
||||||
a neat description
|
|
||||||
on multiple lines
|
|
||||||
}
|
|
||||||
"ash" : 60
|
|
||||||
"bat" : 40
|
|
||||||
`);
|
|
||||||
|
|
||||||
const sections = pieDb.getSections();
|
|
||||||
const title = pieDb.getDiagramTitle();
|
|
||||||
const description = pieDb.getAccDescription();
|
|
||||||
const section1 = sections['ash'];
|
|
||||||
expect(section1).toBe(60);
|
|
||||||
expect(title).toBe('a neat chart');
|
|
||||||
expect(description).toBe('a neat description\non multiple lines');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle simple pie with positive decimal', function () {
|
|
||||||
const res = pie.parser.parse(`pie
|
|
||||||
"ash" : 60.67
|
|
||||||
"bat" : 40
|
|
||||||
`);
|
|
||||||
const sections = pieDb.getSections();
|
|
||||||
const section1 = sections['ash'];
|
|
||||||
expect(section1).toBe(60.67);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle simple pie with negative decimal', function () {
|
|
||||||
expect(() => {
|
|
||||||
pie.parser.parse(`pie
|
|
||||||
"ash" : 60.67
|
|
||||||
"bat" : 40..12
|
|
||||||
`);
|
|
||||||
}).toThrowError();
|
|
||||||
});
|
|
||||||
});
|
|
180
packages/mermaid/src/diagrams/pie/pie.spec.ts
Normal file
180
packages/mermaid/src/diagrams/pie/pie.spec.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
// @ts-ignore: JISON doesn't support types
|
||||||
|
import { parser } from './parser/pie.jison';
|
||||||
|
import { DEFAULT_PIE_DB, db } from './pieDb.js';
|
||||||
|
import { setConfig } from '../../config.js';
|
||||||
|
|
||||||
|
setConfig({
|
||||||
|
securityLevel: 'strict',
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pie', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
parser.yy = db;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
parser.yy.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parse', () => {
|
||||||
|
it('should handle very simple pie', () => {
|
||||||
|
parser.parse(`pie
|
||||||
|
"ash": 100
|
||||||
|
`);
|
||||||
|
|
||||||
|
const sections = db.getSections();
|
||||||
|
expect(sections['ash']).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle simple pie', () => {
|
||||||
|
parser.parse(`pie
|
||||||
|
"ash" : 60
|
||||||
|
"bat" : 40
|
||||||
|
`);
|
||||||
|
|
||||||
|
const sections = db.getSections();
|
||||||
|
expect(sections['ash']).toBe(60);
|
||||||
|
expect(sections['bat']).toBe(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle simple pie with showData', () => {
|
||||||
|
parser.parse(`pie showData
|
||||||
|
"ash" : 60
|
||||||
|
"bat" : 40
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(db.getShowData()).toBeTruthy();
|
||||||
|
|
||||||
|
const sections = db.getSections();
|
||||||
|
expect(sections['ash']).toBe(60);
|
||||||
|
expect(sections['bat']).toBe(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle simple pie with comments', () => {
|
||||||
|
parser.parse(`pie
|
||||||
|
%% comments
|
||||||
|
"ash" : 60
|
||||||
|
"bat" : 40
|
||||||
|
`);
|
||||||
|
|
||||||
|
const sections = db.getSections();
|
||||||
|
expect(sections['ash']).toBe(60);
|
||||||
|
expect(sections['bat']).toBe(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle simple pie with a directive', () => {
|
||||||
|
parser.parse(`%%{init: {'logLevel':0}}%%
|
||||||
|
pie
|
||||||
|
"ash" : 60
|
||||||
|
"bat" : 40
|
||||||
|
`);
|
||||||
|
const sections = db.getSections();
|
||||||
|
expect(sections['ash']).toBe(60);
|
||||||
|
expect(sections['bat']).toBe(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle simple pie with a title', () => {
|
||||||
|
parser.parse(`pie title a 60/40 pie
|
||||||
|
"ash" : 60
|
||||||
|
"bat" : 40
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(db.getDiagramTitle()).toBe('a 60/40 pie');
|
||||||
|
|
||||||
|
const sections = db.getSections();
|
||||||
|
expect(sections['ash']).toBe(60);
|
||||||
|
expect(sections['bat']).toBe(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle simple pie with an acc title (accTitle)', () => {
|
||||||
|
parser.parse(`pie title a neat chart
|
||||||
|
accTitle: a neat acc title
|
||||||
|
"ash" : 60
|
||||||
|
"bat" : 40
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(db.getDiagramTitle()).toBe('a neat chart');
|
||||||
|
|
||||||
|
expect(db.getAccTitle()).toBe('a neat acc title');
|
||||||
|
|
||||||
|
const sections = db.getSections();
|
||||||
|
expect(sections['ash']).toBe(60);
|
||||||
|
expect(sections['bat']).toBe(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle simple pie with an acc description (accDescr)', () => {
|
||||||
|
parser.parse(`pie title a neat chart
|
||||||
|
accDescr: a neat description
|
||||||
|
"ash" : 60
|
||||||
|
"bat" : 40
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(db.getDiagramTitle()).toBe('a neat chart');
|
||||||
|
|
||||||
|
expect(db.getAccDescription()).toBe('a neat description');
|
||||||
|
|
||||||
|
const sections = db.getSections();
|
||||||
|
expect(sections['ash']).toBe(60);
|
||||||
|
expect(sections['bat']).toBe(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle simple pie with a multiline acc description (accDescr)', () => {
|
||||||
|
parser.parse(`pie title a neat chart
|
||||||
|
accDescr {
|
||||||
|
a neat description
|
||||||
|
on multiple lines
|
||||||
|
}
|
||||||
|
"ash" : 60
|
||||||
|
"bat" : 40
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(db.getDiagramTitle()).toBe('a neat chart');
|
||||||
|
|
||||||
|
expect(db.getAccDescription()).toBe('a neat description\non multiple lines');
|
||||||
|
|
||||||
|
const sections = db.getSections();
|
||||||
|
expect(sections['ash']).toBe(60);
|
||||||
|
expect(sections['bat']).toBe(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle simple pie with positive decimal', () => {
|
||||||
|
parser.parse(`pie
|
||||||
|
"ash" : 60.67
|
||||||
|
"bat" : 40
|
||||||
|
`);
|
||||||
|
|
||||||
|
const sections = db.getSections();
|
||||||
|
expect(sections['ash']).toBe(60.67);
|
||||||
|
expect(sections['bat']).toBe(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle simple pie with negative decimal', () => {
|
||||||
|
expect(() => {
|
||||||
|
parser.parse(`pie
|
||||||
|
"ash" : -60.67
|
||||||
|
"bat" : 40.12
|
||||||
|
`);
|
||||||
|
}).toThrowError();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('config', () => {
|
||||||
|
it.todo('setConfig', () => {
|
||||||
|
// db.setConfig({ useWidth: 850, useMaxWidth: undefined });
|
||||||
|
|
||||||
|
const config = db.getConfig();
|
||||||
|
expect(config.useWidth).toBe(850);
|
||||||
|
expect(config.useMaxWidth).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getConfig', () => {
|
||||||
|
expect(db.getConfig()).toStrictEqual(DEFAULT_PIE_DB.config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.todo('resetConfig', () => {
|
||||||
|
// db.setConfig({ textPosition: 0 });
|
||||||
|
// db.resetConfig();
|
||||||
|
expect(db.getConfig().textPosition).toStrictEqual(DEFAULT_PIE_DB.config.textPosition);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -1,69 +0,0 @@
|
|||||||
import { log } from '../../logger.js';
|
|
||||||
import mermaidAPI from '../../mermaidAPI.js';
|
|
||||||
import * as configApi from '../../config.js';
|
|
||||||
import common from '../common/common.js';
|
|
||||||
import {
|
|
||||||
setAccTitle,
|
|
||||||
getAccTitle,
|
|
||||||
setDiagramTitle,
|
|
||||||
getDiagramTitle,
|
|
||||||
getAccDescription,
|
|
||||||
setAccDescription,
|
|
||||||
clear as commonClear,
|
|
||||||
} from '../../commonDb.js';
|
|
||||||
|
|
||||||
let sections = {};
|
|
||||||
let showData = false;
|
|
||||||
|
|
||||||
export const parseDirective = function (statement, context, type) {
|
|
||||||
mermaidAPI.parseDirective(this, statement, context, type);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addSection = function (id, value) {
|
|
||||||
id = common.sanitizeText(id, configApi.getConfig());
|
|
||||||
if (sections[id] === undefined) {
|
|
||||||
sections[id] = value;
|
|
||||||
log.debug('Added new section :', id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const getSections = () => sections;
|
|
||||||
|
|
||||||
const setShowData = function (toggle) {
|
|
||||||
showData = toggle;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getShowData = function () {
|
|
||||||
return showData;
|
|
||||||
};
|
|
||||||
|
|
||||||
const cleanupValue = function (value) {
|
|
||||||
if (value.substring(0, 1) === ':') {
|
|
||||||
value = value.substring(1).trim();
|
|
||||||
return Number(value.trim());
|
|
||||||
} else {
|
|
||||||
return Number(value.trim());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const clear = function () {
|
|
||||||
sections = {};
|
|
||||||
showData = false;
|
|
||||||
commonClear();
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
parseDirective,
|
|
||||||
getConfig: () => configApi.getConfig().pie,
|
|
||||||
addSection,
|
|
||||||
getSections,
|
|
||||||
cleanupValue,
|
|
||||||
clear,
|
|
||||||
setAccTitle,
|
|
||||||
getAccTitle,
|
|
||||||
setDiagramTitle,
|
|
||||||
getDiagramTitle,
|
|
||||||
setShowData,
|
|
||||||
getShowData,
|
|
||||||
getAccDescription,
|
|
||||||
setAccDescription,
|
|
||||||
};
|
|
84
packages/mermaid/src/diagrams/pie/pieDb.ts
Normal file
84
packages/mermaid/src/diagrams/pie/pieDb.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { log } from '../../logger.js';
|
||||||
|
import { parseDirective as _parseDirective } from '../../directiveUtils.js';
|
||||||
|
import { getConfig as commonGetConfig } from '../../config.js';
|
||||||
|
import { sanitizeText } from '../common/common.js';
|
||||||
|
import {
|
||||||
|
setAccTitle,
|
||||||
|
getAccTitle,
|
||||||
|
setDiagramTitle,
|
||||||
|
getDiagramTitle,
|
||||||
|
getAccDescription,
|
||||||
|
setAccDescription,
|
||||||
|
clear as commonClear,
|
||||||
|
} from '../../commonDb.js';
|
||||||
|
import type { ParseDirectiveDefinition } from '../../diagram-api/types.js';
|
||||||
|
import type { PieFields, PieDB, Sections } from './pieTypes.js';
|
||||||
|
import type { RequiredDeep } from 'type-fest';
|
||||||
|
import type { PieDiagramConfig } from '../../config.type.js';
|
||||||
|
import DEFAULT_CONFIG from '../../defaultConfig.js';
|
||||||
|
|
||||||
|
export const DEFAULT_PIE_CONFIG: Required<PieDiagramConfig> = DEFAULT_CONFIG.pie;
|
||||||
|
|
||||||
|
export const DEFAULT_PIE_DB: RequiredDeep<PieFields> = {
|
||||||
|
sections: {},
|
||||||
|
showData: false,
|
||||||
|
config: DEFAULT_PIE_CONFIG,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
let sections: Sections = DEFAULT_PIE_DB.sections;
|
||||||
|
let showData: boolean = DEFAULT_PIE_DB.showData;
|
||||||
|
const config: Required<PieDiagramConfig> = structuredClone(DEFAULT_PIE_CONFIG);
|
||||||
|
|
||||||
|
const getConfig = (): Required<PieDiagramConfig> => structuredClone(config);
|
||||||
|
|
||||||
|
const parseDirective: ParseDirectiveDefinition = (statement, context, type) => {
|
||||||
|
_parseDirective(this, statement, context, type);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clear = (): void => {
|
||||||
|
sections = structuredClone(DEFAULT_PIE_DB.sections);
|
||||||
|
showData = DEFAULT_PIE_DB.showData;
|
||||||
|
commonClear();
|
||||||
|
};
|
||||||
|
|
||||||
|
const addSection = (label: string, value: number): void => {
|
||||||
|
label = sanitizeText(label, commonGetConfig());
|
||||||
|
if (sections[label] === undefined) {
|
||||||
|
sections[label] = value;
|
||||||
|
log.debug(`added new section: ${label}, with value: ${value}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSections = (): Sections => sections;
|
||||||
|
|
||||||
|
const cleanupValue = (value: string): number => {
|
||||||
|
if (value.substring(0, 1) === ':') {
|
||||||
|
value = value.substring(1).trim();
|
||||||
|
}
|
||||||
|
return Number(value.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
const setShowData = (toggle: boolean): void => {
|
||||||
|
showData = toggle;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getShowData = (): boolean => showData;
|
||||||
|
|
||||||
|
export const db: PieDB = {
|
||||||
|
getConfig,
|
||||||
|
|
||||||
|
parseDirective,
|
||||||
|
clear,
|
||||||
|
setDiagramTitle,
|
||||||
|
getDiagramTitle,
|
||||||
|
setAccTitle,
|
||||||
|
getAccTitle,
|
||||||
|
setAccDescription,
|
||||||
|
getAccDescription,
|
||||||
|
|
||||||
|
addSection,
|
||||||
|
getSections,
|
||||||
|
cleanupValue,
|
||||||
|
setShowData,
|
||||||
|
getShowData,
|
||||||
|
};
|
@@ -15,10 +15,8 @@ const loader: DiagramLoader = async () => {
|
|||||||
return { id, diagram };
|
return { id, diagram };
|
||||||
};
|
};
|
||||||
|
|
||||||
const plugin: ExternalDiagramDefinition = {
|
export const pie: ExternalDiagramDefinition = {
|
||||||
id,
|
id,
|
||||||
detector,
|
detector,
|
||||||
loader,
|
loader,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default plugin;
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { DiagramDefinition } from '../../diagram-api/types.js';
|
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||||
// @ts-ignore: JISON doesn't support types
|
// @ts-ignore: JISON doesn't support types
|
||||||
import parser from './parser/pie.jison';
|
import parser from './parser/pie.jison';
|
||||||
import db from './pieDb.js';
|
import { db } from './pieDb.js';
|
||||||
import styles from './styles.js';
|
import styles from './pieStyles.js';
|
||||||
import renderer from './pieRenderer.js';
|
import { renderer } from './pieRenderer.js';
|
||||||
|
|
||||||
export const diagram: DiagramDefinition = {
|
export const diagram: DiagramDefinition = {
|
||||||
parser,
|
parser,
|
||||||
|
@@ -1,204 +0,0 @@
|
|||||||
/** Created by AshishJ on 11-09-2019. */
|
|
||||||
import { select, scaleOrdinal, pie as d3pie, arc } from 'd3';
|
|
||||||
import { log } from '../../logger.js';
|
|
||||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
|
||||||
import * as configApi from '../../config.js';
|
|
||||||
import { parseFontSize } from '../../utils.js';
|
|
||||||
|
|
||||||
let conf = configApi.getConfig();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a Pie Chart with the data given in text.
|
|
||||||
*
|
|
||||||
* @param text
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
let width;
|
|
||||||
const height = 450;
|
|
||||||
export const draw = (txt, id, _version, diagObj) => {
|
|
||||||
try {
|
|
||||||
conf = configApi.getConfig();
|
|
||||||
log.debug('Rendering info diagram\n' + txt);
|
|
||||||
|
|
||||||
const securityLevel = configApi.getConfig().securityLevel;
|
|
||||||
// Handle root and Document for when rendering in sandbox mode
|
|
||||||
let sandboxElement;
|
|
||||||
if (securityLevel === 'sandbox') {
|
|
||||||
sandboxElement = select('#i' + id);
|
|
||||||
}
|
|
||||||
const root =
|
|
||||||
securityLevel === 'sandbox'
|
|
||||||
? select(sandboxElement.nodes()[0].contentDocument.body)
|
|
||||||
: select('body');
|
|
||||||
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
|
||||||
|
|
||||||
// Parse the Pie Chart definition
|
|
||||||
const elem = doc.getElementById(id);
|
|
||||||
width = elem.parentElement.offsetWidth;
|
|
||||||
|
|
||||||
if (width === undefined) {
|
|
||||||
width = 1200;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conf.useWidth !== undefined) {
|
|
||||||
width = conf.useWidth;
|
|
||||||
}
|
|
||||||
if (conf.pie.useWidth !== undefined) {
|
|
||||||
width = conf.pie.useWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
const diagram = root.select('#' + id);
|
|
||||||
configureSvgSize(diagram, height, width, conf.pie.useMaxWidth);
|
|
||||||
|
|
||||||
// Set viewBox
|
|
||||||
elem.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
|
|
||||||
|
|
||||||
// Fetch the default direction, use TD if none was found
|
|
||||||
var margin = 40;
|
|
||||||
var legendRectSize = 18;
|
|
||||||
var legendSpacing = 4;
|
|
||||||
|
|
||||||
var radius = Math.min(width, height) / 2 - margin;
|
|
||||||
|
|
||||||
var svg = diagram
|
|
||||||
.append('g')
|
|
||||||
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
|
|
||||||
|
|
||||||
var data = diagObj.db.getSections();
|
|
||||||
var sum = 0;
|
|
||||||
Object.keys(data).forEach(function (key) {
|
|
||||||
sum += data[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
const themeVariables = conf.themeVariables;
|
|
||||||
var myGeneratedColors = [
|
|
||||||
themeVariables.pie1,
|
|
||||||
themeVariables.pie2,
|
|
||||||
themeVariables.pie3,
|
|
||||||
themeVariables.pie4,
|
|
||||||
themeVariables.pie5,
|
|
||||||
themeVariables.pie6,
|
|
||||||
themeVariables.pie7,
|
|
||||||
themeVariables.pie8,
|
|
||||||
themeVariables.pie9,
|
|
||||||
themeVariables.pie10,
|
|
||||||
themeVariables.pie11,
|
|
||||||
themeVariables.pie12,
|
|
||||||
];
|
|
||||||
|
|
||||||
const textPosition = conf.pie?.textPosition ?? 0.75;
|
|
||||||
let [outerStrokeWidth] = parseFontSize(themeVariables.pieOuterStrokeWidth);
|
|
||||||
outerStrokeWidth ??= 2;
|
|
||||||
|
|
||||||
// Set the color scale
|
|
||||||
var color = scaleOrdinal().range(myGeneratedColors);
|
|
||||||
|
|
||||||
// Compute the position of each group on the pie:
|
|
||||||
var pieData = Object.entries(data).map(function (el, idx) {
|
|
||||||
return {
|
|
||||||
order: idx,
|
|
||||||
name: el[0],
|
|
||||||
value: el[1],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
var pie = d3pie()
|
|
||||||
.value(function (d) {
|
|
||||||
return d.value;
|
|
||||||
})
|
|
||||||
.sort(function (a, b) {
|
|
||||||
// Sort slices in clockwise direction
|
|
||||||
return a.order - b.order;
|
|
||||||
});
|
|
||||||
var dataReady = pie(pieData);
|
|
||||||
|
|
||||||
// Shape helper to build arcs:
|
|
||||||
var arcGenerator = arc().innerRadius(0).outerRadius(radius);
|
|
||||||
var labelArcGenerator = arc()
|
|
||||||
.innerRadius(radius * textPosition)
|
|
||||||
.outerRadius(radius * textPosition);
|
|
||||||
|
|
||||||
svg
|
|
||||||
.append('circle')
|
|
||||||
.attr('cx', 0)
|
|
||||||
.attr('cy', 0)
|
|
||||||
.attr('r', radius + outerStrokeWidth / 2)
|
|
||||||
.attr('class', 'pieOuterCircle');
|
|
||||||
|
|
||||||
// Build the pie chart: each part of the pie is a path that we build using the arc function.
|
|
||||||
svg
|
|
||||||
.selectAll('mySlices')
|
|
||||||
.data(dataReady)
|
|
||||||
.enter()
|
|
||||||
.append('path')
|
|
||||||
.attr('d', arcGenerator)
|
|
||||||
.attr('fill', function (d) {
|
|
||||||
return color(d.data.name);
|
|
||||||
})
|
|
||||||
.attr('class', 'pieCircle');
|
|
||||||
|
|
||||||
// Now add the percentage.
|
|
||||||
// Use the centroid method to get the best coordinates.
|
|
||||||
svg
|
|
||||||
.selectAll('mySlices')
|
|
||||||
.data(dataReady)
|
|
||||||
.enter()
|
|
||||||
.append('text')
|
|
||||||
.text(function (d) {
|
|
||||||
return ((d.data.value / sum) * 100).toFixed(0) + '%';
|
|
||||||
})
|
|
||||||
.attr('transform', function (d) {
|
|
||||||
return 'translate(' + labelArcGenerator.centroid(d) + ')';
|
|
||||||
})
|
|
||||||
.style('text-anchor', 'middle')
|
|
||||||
.attr('class', 'slice');
|
|
||||||
|
|
||||||
svg
|
|
||||||
.append('text')
|
|
||||||
.text(diagObj.db.getDiagramTitle())
|
|
||||||
.attr('x', 0)
|
|
||||||
.attr('y', -(height - 50) / 2)
|
|
||||||
.attr('class', 'pieTitleText');
|
|
||||||
|
|
||||||
// Add the legends/annotations for each section
|
|
||||||
var legend = svg
|
|
||||||
.selectAll('.legend')
|
|
||||||
.data(color.domain())
|
|
||||||
.enter()
|
|
||||||
.append('g')
|
|
||||||
.attr('class', 'legend')
|
|
||||||
.attr('transform', function (d, i) {
|
|
||||||
const height = legendRectSize + legendSpacing;
|
|
||||||
const offset = (height * color.domain().length) / 2;
|
|
||||||
const horizontal = 12 * legendRectSize;
|
|
||||||
const vertical = i * height - offset;
|
|
||||||
return 'translate(' + horizontal + ',' + vertical + ')';
|
|
||||||
});
|
|
||||||
|
|
||||||
legend
|
|
||||||
.append('rect')
|
|
||||||
.attr('width', legendRectSize)
|
|
||||||
.attr('height', legendRectSize)
|
|
||||||
.style('fill', color)
|
|
||||||
.style('stroke', color);
|
|
||||||
|
|
||||||
legend
|
|
||||||
.data(dataReady)
|
|
||||||
.append('text')
|
|
||||||
.attr('x', legendRectSize + legendSpacing)
|
|
||||||
.attr('y', legendRectSize - legendSpacing)
|
|
||||||
.text(function (d) {
|
|
||||||
if (diagObj.db.getShowData() || conf.showData || conf.pie.showData) {
|
|
||||||
return d.data.name + ' [' + d.data.value + ']';
|
|
||||||
} else {
|
|
||||||
return d.data.name;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
log.error('Error while rendering info diagram');
|
|
||||||
log.error(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
draw,
|
|
||||||
};
|
|
179
packages/mermaid/src/diagrams/pie/pieRenderer.ts
Normal file
179
packages/mermaid/src/diagrams/pie/pieRenderer.ts
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
import d3, { scaleOrdinal, pie as d3pie, arc } from 'd3';
|
||||||
|
|
||||||
|
import { log } from '../../logger.js';
|
||||||
|
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||||
|
import { getConfig } from '../../config.js';
|
||||||
|
import { cleanAndMerge, parseFontSize } from '../../utils.js';
|
||||||
|
import type { DrawDefinition, Group, SVG } from '../../diagram-api/types.js';
|
||||||
|
import type { D3Sections, PieDB, Sections } from './pieTypes.js';
|
||||||
|
import type { MermaidConfig, PieDiagramConfig } from '../../config.type.js';
|
||||||
|
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
|
||||||
|
|
||||||
|
const createPieArcs = (sections: Sections): d3.PieArcDatum<D3Sections>[] => {
|
||||||
|
// Compute the position of each group on the pie:
|
||||||
|
const pieData: D3Sections[] = Object.entries(sections).map(
|
||||||
|
(element: [string, number]): D3Sections => {
|
||||||
|
return {
|
||||||
|
label: element[0],
|
||||||
|
value: element[1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const pie: d3.Pie<unknown, D3Sections> = d3pie<D3Sections>().value(
|
||||||
|
(d3Section: D3Sections): 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);
|
||||||
|
|
||||||
|
const db = diagObj.db as PieDB;
|
||||||
|
const globalConfig: MermaidConfig = getConfig();
|
||||||
|
const pieConfig: Required<PieDiagramConfig> = cleanAndMerge(db.getConfig(), globalConfig.pie);
|
||||||
|
|
||||||
|
const height = 450;
|
||||||
|
// TODO: remove document width
|
||||||
|
const width: number =
|
||||||
|
document.getElementById(id)?.parentElement?.offsetWidth ?? pieConfig.useWidth;
|
||||||
|
const svg: SVG = selectSvgElement(id);
|
||||||
|
// Set viewBox
|
||||||
|
svg.attr('viewBox', `0 0 ${width} ${height}`);
|
||||||
|
configureSvgSize(svg, height, width, pieConfig.useMaxWidth);
|
||||||
|
|
||||||
|
const MARGIN = 40;
|
||||||
|
const LEGEND_RECT_SIZE = 18;
|
||||||
|
const LEGEND_SPACING = 4;
|
||||||
|
|
||||||
|
const group: Group = svg.append('g');
|
||||||
|
group.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
|
||||||
|
|
||||||
|
const { themeVariables } = globalConfig;
|
||||||
|
let [outerStrokeWidth] = parseFontSize(themeVariables.pieOuterStrokeWidth);
|
||||||
|
outerStrokeWidth ??= 2;
|
||||||
|
|
||||||
|
const textPosition: number = pieConfig.textPosition;
|
||||||
|
const radius: number = Math.min(width, height) / 2 - MARGIN;
|
||||||
|
// Shape helper to build arcs:
|
||||||
|
const arcGenerator: d3.Arc<unknown, d3.PieArcDatum<D3Sections>> = arc<
|
||||||
|
d3.PieArcDatum<D3Sections>
|
||||||
|
>()
|
||||||
|
.innerRadius(0)
|
||||||
|
.outerRadius(radius);
|
||||||
|
const labelArcGenerator: d3.Arc<unknown, d3.PieArcDatum<D3Sections>> = arc<
|
||||||
|
d3.PieArcDatum<D3Sections>
|
||||||
|
>()
|
||||||
|
.innerRadius(radius * textPosition)
|
||||||
|
.outerRadius(radius * textPosition);
|
||||||
|
|
||||||
|
group
|
||||||
|
.append('circle')
|
||||||
|
.attr('cx', 0)
|
||||||
|
.attr('cy', 0)
|
||||||
|
.attr('r', radius + outerStrokeWidth / 2)
|
||||||
|
.attr('class', 'pieOuterCircle');
|
||||||
|
|
||||||
|
const sections: Sections = db.getSections();
|
||||||
|
const arcs: d3.PieArcDatum<D3Sections>[] = createPieArcs(sections);
|
||||||
|
|
||||||
|
const myGeneratedColors = [
|
||||||
|
themeVariables.pie1,
|
||||||
|
themeVariables.pie2,
|
||||||
|
themeVariables.pie3,
|
||||||
|
themeVariables.pie4,
|
||||||
|
themeVariables.pie5,
|
||||||
|
themeVariables.pie6,
|
||||||
|
themeVariables.pie7,
|
||||||
|
themeVariables.pie8,
|
||||||
|
themeVariables.pie9,
|
||||||
|
themeVariables.pie10,
|
||||||
|
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<D3Sections>) => {
|
||||||
|
return color(datum.data.label);
|
||||||
|
})
|
||||||
|
.attr('class', 'pieCircle');
|
||||||
|
|
||||||
|
let sum = 0;
|
||||||
|
Object.keys(sections).forEach((key: string): void => {
|
||||||
|
sum += sections[key];
|
||||||
|
});
|
||||||
|
// 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<D3Sections>): string => {
|
||||||
|
return ((datum.data.value / sum) * 100).toFixed(0) + '%';
|
||||||
|
})
|
||||||
|
.attr('transform', (datum: d3.PieArcDatum<D3Sections>): string => {
|
||||||
|
return 'translate(' + labelArcGenerator.centroid(datum) + ')';
|
||||||
|
})
|
||||||
|
.style('text-anchor', 'middle')
|
||||||
|
.attr('class', 'slice');
|
||||||
|
|
||||||
|
group
|
||||||
|
.append('text')
|
||||||
|
.text(db.getDiagramTitle())
|
||||||
|
.attr('x', 0)
|
||||||
|
.attr('y', -(height - 50) / 2)
|
||||||
|
.attr('class', 'pieTitleText');
|
||||||
|
|
||||||
|
// Add the legends/annotations for each section
|
||||||
|
const legend = group
|
||||||
|
.selectAll('.legend')
|
||||||
|
.data(color.domain())
|
||||||
|
.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 horizontal = 12 * LEGEND_RECT_SIZE;
|
||||||
|
const vertical = index * height - offset;
|
||||||
|
return 'translate(' + horizontal + ',' + vertical + ')';
|
||||||
|
});
|
||||||
|
|
||||||
|
legend
|
||||||
|
.append('rect')
|
||||||
|
.attr('width', LEGEND_RECT_SIZE)
|
||||||
|
.attr('height', LEGEND_RECT_SIZE)
|
||||||
|
.style('fill', color)
|
||||||
|
.style('stroke', color);
|
||||||
|
|
||||||
|
legend
|
||||||
|
.data(arcs)
|
||||||
|
.append('text')
|
||||||
|
.attr('x', LEGEND_RECT_SIZE + LEGEND_SPACING)
|
||||||
|
.attr('y', LEGEND_RECT_SIZE - LEGEND_SPACING)
|
||||||
|
.text((datum: d3.PieArcDatum<D3Sections>): string => {
|
||||||
|
const { label, value } = datum.data;
|
||||||
|
if (db.getShowData()) {
|
||||||
|
return `${label} [${value}]`;
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renderer = { draw };
|
@@ -1,4 +1,7 @@
|
|||||||
const getStyles = (options) =>
|
import type { DiagramStylesProvider } from '../../diagram-api/types.js';
|
||||||
|
import type { PieStyleOptions } from './pieTypes.js';
|
||||||
|
|
||||||
|
const getStyles: DiagramStylesProvider = (options: PieStyleOptions) =>
|
||||||
`
|
`
|
||||||
.pieCircle{
|
.pieCircle{
|
||||||
stroke: ${options.pieStrokeColor};
|
stroke: ${options.pieStrokeColor};
|
64
packages/mermaid/src/diagrams/pie/pieTypes.ts
Normal file
64
packages/mermaid/src/diagrams/pie/pieTypes.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import type { PieDiagramConfig } from '../../config.type.js';
|
||||||
|
import type { DiagramDB, ParseDirectiveDefinition } from '../../diagram-api/types.js';
|
||||||
|
|
||||||
|
export interface PieFields {
|
||||||
|
sections: Sections;
|
||||||
|
showData: boolean;
|
||||||
|
config: PieDiagramConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PieStyleOptions {
|
||||||
|
fontFamily: string;
|
||||||
|
pie1: string;
|
||||||
|
pie2: string;
|
||||||
|
pie3: string;
|
||||||
|
pie4: string;
|
||||||
|
pie5: string;
|
||||||
|
pie6: string;
|
||||||
|
pie7: string;
|
||||||
|
pie8: string;
|
||||||
|
pie9: string;
|
||||||
|
pie10: string;
|
||||||
|
pie11: string;
|
||||||
|
pie12: string;
|
||||||
|
pieTitleTextSize: string;
|
||||||
|
pieTitleTextColor: string;
|
||||||
|
pieSectionTextSize: string;
|
||||||
|
pieSectionTextColor: string;
|
||||||
|
pieLegendTextSize: string;
|
||||||
|
pieLegendTextColor: string;
|
||||||
|
pieStrokeColor: string;
|
||||||
|
pieStrokeWidth: string;
|
||||||
|
pieOuterStrokeWidth: string;
|
||||||
|
pieOuterStrokeColor: string;
|
||||||
|
pieOpacity: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Sections = Record<string, number>;
|
||||||
|
|
||||||
|
export interface D3Sections {
|
||||||
|
label: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PieDB extends DiagramDB {
|
||||||
|
// config
|
||||||
|
getConfig: () => Required<PieDiagramConfig>;
|
||||||
|
|
||||||
|
// common db
|
||||||
|
parseDirective: ParseDirectiveDefinition;
|
||||||
|
clear: () => void;
|
||||||
|
setDiagramTitle: (title: string) => void;
|
||||||
|
getDiagramTitle: () => string;
|
||||||
|
setAccTitle: (title: string) => void;
|
||||||
|
getAccTitle: () => string;
|
||||||
|
setAccDescription: (describetion: string) => void;
|
||||||
|
getAccDescription: () => string;
|
||||||
|
|
||||||
|
// diagram db
|
||||||
|
addSection: (label: string, value: number) => void;
|
||||||
|
getSections: () => Sections;
|
||||||
|
cleanupValue: (value: string) => number;
|
||||||
|
setShowData: (toggle: boolean) => void;
|
||||||
|
getShowData: () => boolean;
|
||||||
|
}
|
@@ -21,7 +21,7 @@ import flowchartElk from './diagrams/flowchart/elk/styles.js';
|
|||||||
import er from './diagrams/er/styles.js';
|
import er from './diagrams/er/styles.js';
|
||||||
import git from './diagrams/git/styles.js';
|
import git from './diagrams/git/styles.js';
|
||||||
import gantt from './diagrams/gantt/styles.js';
|
import gantt from './diagrams/gantt/styles.js';
|
||||||
import pie from './diagrams/pie/styles.js';
|
import pie from './diagrams/pie/pieStyles.js';
|
||||||
import requirement from './diagrams/requirement/styles.js';
|
import requirement from './diagrams/requirement/styles.js';
|
||||||
import sequence from './diagrams/sequence/styles.js';
|
import sequence from './diagrams/sequence/styles.js';
|
||||||
import state from './diagrams/state/styles.js';
|
import state from './diagrams/state/styles.js';
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { vi } from 'vitest';
|
import { vi } from 'vitest';
|
||||||
import utils from './utils.js';
|
import utils, { cleanAndMerge } from './utils.js';
|
||||||
import assignWithDepth from './assignWithDepth.js';
|
import assignWithDepth from './assignWithDepth.js';
|
||||||
import { detectType } from './diagram-api/detectType.js';
|
import { detectType } from './diagram-api/detectType.js';
|
||||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
||||||
@@ -10,51 +10,51 @@ addDiagrams();
|
|||||||
|
|
||||||
describe('when assignWithDepth: should merge objects within objects', function () {
|
describe('when assignWithDepth: should merge objects within objects', function () {
|
||||||
it('should handle simple, depth:1 types (identity)', function () {
|
it('should handle simple, depth:1 types (identity)', function () {
|
||||||
let config_0 = { foo: 'bar', bar: 0 };
|
const config_0 = { foo: 'bar', bar: 0 };
|
||||||
let config_1 = { foo: 'bar', bar: 0 };
|
const config_1 = { foo: 'bar', bar: 0 };
|
||||||
let result = assignWithDepth(config_0, config_1);
|
const result = assignWithDepth(config_0, config_1);
|
||||||
expect(result).toEqual(config_1);
|
expect(result).toEqual(config_1);
|
||||||
});
|
});
|
||||||
it('should handle simple, depth:1 types (dst: undefined)', function () {
|
it('should handle simple, depth:1 types (dst: undefined)', function () {
|
||||||
let config_0 = undefined;
|
const config_0 = undefined;
|
||||||
let config_1 = { foo: 'bar', bar: 0 };
|
const config_1 = { foo: 'bar', bar: 0 };
|
||||||
let result = assignWithDepth(config_0, config_1);
|
const result = assignWithDepth(config_0, config_1);
|
||||||
expect(result).toEqual(config_1);
|
expect(result).toEqual(config_1);
|
||||||
});
|
});
|
||||||
it('should handle simple, depth:1 types (src: undefined)', function () {
|
it('should handle simple, depth:1 types (src: undefined)', function () {
|
||||||
let config_0 = { foo: 'bar', bar: 0 };
|
const config_0 = { foo: 'bar', bar: 0 };
|
||||||
let config_1 = undefined;
|
const config_1 = undefined;
|
||||||
let result = assignWithDepth(config_0, config_1);
|
const result = assignWithDepth(config_0, config_1);
|
||||||
expect(result).toEqual(config_0);
|
expect(result).toEqual(config_0);
|
||||||
});
|
});
|
||||||
it('should handle simple, depth:1 types (merge)', function () {
|
it('should handle simple, depth:1 types (merge)', function () {
|
||||||
let config_0 = { foo: 'bar', bar: 0 };
|
const config_0 = { foo: 'bar', bar: 0 };
|
||||||
let config_1 = { foo: 'foo' };
|
const config_1 = { foo: 'foo' };
|
||||||
let result = assignWithDepth(config_0, config_1);
|
const result = assignWithDepth(config_0, config_1);
|
||||||
expect(result).toEqual({ foo: 'foo', bar: 0 });
|
expect(result).toEqual({ foo: 'foo', bar: 0 });
|
||||||
});
|
});
|
||||||
it('should handle depth:2 types (dst: orphan)', function () {
|
it('should handle depth:2 types (dst: orphan)', function () {
|
||||||
let config_0 = { foo: 'bar', bar: { foo: 'bar' } };
|
const config_0 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||||
let config_1 = { foo: 'bar' };
|
const config_1 = { foo: 'bar' };
|
||||||
let result = assignWithDepth(config_0, config_1);
|
const result = assignWithDepth(config_0, config_1);
|
||||||
expect(result).toEqual(config_0);
|
expect(result).toEqual(config_0);
|
||||||
});
|
});
|
||||||
it('should handle depth:2 types (dst: object, src: simple type)', function () {
|
it('should handle depth:2 types (dst: object, src: simple type)', function () {
|
||||||
let config_0 = { foo: 'bar', bar: { foo: 'bar' } };
|
const config_0 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||||
let config_1 = { foo: 'foo', bar: 'should NOT clobber' };
|
const config_1 = { foo: 'foo', bar: 'should NOT clobber' };
|
||||||
let result = assignWithDepth(config_0, config_1);
|
const result = assignWithDepth(config_0, config_1);
|
||||||
expect(result).toEqual({ foo: 'foo', bar: { foo: 'bar' } });
|
expect(result).toEqual({ foo: 'foo', bar: { foo: 'bar' } });
|
||||||
});
|
});
|
||||||
it('should handle depth:2 types (src: orphan)', function () {
|
it('should handle depth:2 types (src: orphan)', function () {
|
||||||
let config_0 = { foo: 'bar' };
|
const config_0 = { foo: 'bar' };
|
||||||
let config_1 = { foo: 'bar', bar: { foo: 'bar' } };
|
const config_1 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||||
let result = assignWithDepth(config_0, config_1);
|
const result = assignWithDepth(config_0, config_1);
|
||||||
expect(result).toEqual(config_1);
|
expect(result).toEqual(config_1);
|
||||||
});
|
});
|
||||||
it('should handle depth:2 types (merge)', function () {
|
it('should handle depth:2 types (merge)', function () {
|
||||||
let config_0 = { foo: 'bar', bar: { foo: 'bar' }, boofar: 1 };
|
const config_0 = { foo: 'bar', bar: { foo: 'bar' }, boofar: 1 };
|
||||||
let config_1 = { foo: 'foo', bar: { bar: 0 }, foobar: 'foobar' };
|
const config_1 = { foo: 'foo', bar: { bar: 0 }, foobar: 'foobar' };
|
||||||
let result = assignWithDepth(config_0, config_1);
|
const result = assignWithDepth(config_0, config_1);
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
foo: 'foo',
|
foo: 'foo',
|
||||||
bar: { foo: 'bar', bar: 0 },
|
bar: { foo: 'bar', bar: 0 },
|
||||||
@@ -63,17 +63,17 @@ describe('when assignWithDepth: should merge objects within objects', function (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should handle depth:3 types (merge with clobber because assignWithDepth::depth == 2)', function () {
|
it('should handle depth:3 types (merge with clobber because assignWithDepth::depth == 2)', function () {
|
||||||
let config_0 = {
|
const config_0 = {
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
bar: { foo: 'bar', bar: { foo: { message: 'this', willbe: 'clobbered' } } },
|
bar: { foo: 'bar', bar: { foo: { message: 'this', willbe: 'clobbered' } } },
|
||||||
boofar: 1,
|
boofar: 1,
|
||||||
};
|
};
|
||||||
let config_1 = {
|
const config_1 = {
|
||||||
foo: 'foo',
|
foo: 'foo',
|
||||||
bar: { foo: 'foo', bar: { foo: { message: 'clobbered other foo' } } },
|
bar: { foo: 'foo', bar: { foo: { message: 'clobbered other foo' } } },
|
||||||
foobar: 'foobar',
|
foobar: 'foobar',
|
||||||
};
|
};
|
||||||
let result = assignWithDepth(config_0, config_1);
|
const result = assignWithDepth(config_0, config_1);
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
foo: 'foo',
|
foo: 'foo',
|
||||||
bar: { foo: 'foo', bar: { foo: { message: 'clobbered other foo' } } },
|
bar: { foo: 'foo', bar: { foo: { message: 'clobbered other foo' } } },
|
||||||
@@ -82,7 +82,7 @@ describe('when assignWithDepth: should merge objects within objects', function (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should handle depth:3 types (merge with clobber because assignWithDepth::depth == 1)', function () {
|
it('should handle depth:3 types (merge with clobber because assignWithDepth::depth == 1)', function () {
|
||||||
let config_0 = {
|
const config_0 = {
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
bar: {
|
bar: {
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
@@ -90,12 +90,12 @@ describe('when assignWithDepth: should merge objects within objects', function (
|
|||||||
},
|
},
|
||||||
boofar: 1,
|
boofar: 1,
|
||||||
};
|
};
|
||||||
let config_1 = {
|
const config_1 = {
|
||||||
foo: 'foo',
|
foo: 'foo',
|
||||||
bar: { foo: 'foo', bar: { foo: { message: 'this' } } },
|
bar: { foo: 'foo', bar: { foo: { message: 'this' } } },
|
||||||
foobar: 'foobar',
|
foobar: 'foobar',
|
||||||
};
|
};
|
||||||
let result = assignWithDepth(config_0, config_1, { depth: 1 });
|
const result = assignWithDepth(config_0, config_1, { depth: 1 });
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
foo: 'foo',
|
foo: 'foo',
|
||||||
bar: { foo: 'foo', bar: { foo: { message: 'this' } } },
|
bar: { foo: 'foo', bar: { foo: { message: 'this' } } },
|
||||||
@@ -104,17 +104,17 @@ describe('when assignWithDepth: should merge objects within objects', function (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should handle depth:3 types (merge with no clobber because assignWithDepth::depth == 3)', function () {
|
it('should handle depth:3 types (merge with no clobber because assignWithDepth::depth == 3)', function () {
|
||||||
let config_0 = {
|
const config_0 = {
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
bar: { foo: 'bar', bar: { foo: { message: '', willbe: 'present' } } },
|
bar: { foo: 'bar', bar: { foo: { message: '', willbe: 'present' } } },
|
||||||
boofar: 1,
|
boofar: 1,
|
||||||
};
|
};
|
||||||
let config_1 = {
|
const config_1 = {
|
||||||
foo: 'foo',
|
foo: 'foo',
|
||||||
bar: { foo: 'foo', bar: { foo: { message: 'this' } } },
|
bar: { foo: 'foo', bar: { foo: { message: 'this' } } },
|
||||||
foobar: 'foobar',
|
foobar: 'foobar',
|
||||||
};
|
};
|
||||||
let result = assignWithDepth(config_0, config_1, { depth: 3 });
|
const result = assignWithDepth(config_0, config_1, { depth: 3 });
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
foo: 'foo',
|
foo: 'foo',
|
||||||
bar: { foo: 'foo', bar: { foo: { message: 'this', willbe: 'present' } } },
|
bar: { foo: 'foo', bar: { foo: { message: 'this', willbe: 'present' } } },
|
||||||
@@ -125,8 +125,8 @@ describe('when assignWithDepth: should merge objects within objects', function (
|
|||||||
});
|
});
|
||||||
describe('when memoizing', function () {
|
describe('when memoizing', function () {
|
||||||
it('should return the same value', function () {
|
it('should return the same value', function () {
|
||||||
const fib = memoize(
|
const fib: any = memoize(
|
||||||
function (n, x, canary) {
|
function (n: number, x: string, canary: { flag: boolean }) {
|
||||||
canary.flag = true;
|
canary.flag = true;
|
||||||
if (n < 2) {
|
if (n < 2) {
|
||||||
return 1;
|
return 1;
|
||||||
@@ -260,7 +260,7 @@ describe('when formatting urls', function () {
|
|||||||
it('should handle links', function () {
|
it('should handle links', function () {
|
||||||
const url = 'https://mermaid-js.github.io/mermaid/#/';
|
const url = 'https://mermaid-js.github.io/mermaid/#/';
|
||||||
|
|
||||||
let config = { securityLevel: 'loose' };
|
const config = { securityLevel: 'loose' };
|
||||||
let result = utils.formatUrl(url, config);
|
let result = utils.formatUrl(url, config);
|
||||||
expect(result).toEqual(url);
|
expect(result).toEqual(url);
|
||||||
|
|
||||||
@@ -271,7 +271,7 @@ describe('when formatting urls', function () {
|
|||||||
it('should handle anchors', function () {
|
it('should handle anchors', function () {
|
||||||
const url = '#interaction';
|
const url = '#interaction';
|
||||||
|
|
||||||
let config = { securityLevel: 'loose' };
|
const config = { securityLevel: 'loose' };
|
||||||
let result = utils.formatUrl(url, config);
|
let result = utils.formatUrl(url, config);
|
||||||
expect(result).toEqual(url);
|
expect(result).toEqual(url);
|
||||||
|
|
||||||
@@ -282,7 +282,7 @@ describe('when formatting urls', function () {
|
|||||||
it('should handle mailto', function () {
|
it('should handle mailto', function () {
|
||||||
const url = 'mailto:user@user.user';
|
const url = 'mailto:user@user.user';
|
||||||
|
|
||||||
let config = { securityLevel: 'loose' };
|
const config = { securityLevel: 'loose' };
|
||||||
let result = utils.formatUrl(url, config);
|
let result = utils.formatUrl(url, config);
|
||||||
expect(result).toEqual(url);
|
expect(result).toEqual(url);
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ describe('when formatting urls', function () {
|
|||||||
it('should handle other protocols', function () {
|
it('should handle other protocols', function () {
|
||||||
const url = 'notes://do-your-thing/id';
|
const url = 'notes://do-your-thing/id';
|
||||||
|
|
||||||
let config = { securityLevel: 'loose' };
|
const config = { securityLevel: 'loose' };
|
||||||
let result = utils.formatUrl(url, config);
|
let result = utils.formatUrl(url, config);
|
||||||
expect(result).toEqual(url);
|
expect(result).toEqual(url);
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ describe('when formatting urls', function () {
|
|||||||
it('should handle scripts', function () {
|
it('should handle scripts', function () {
|
||||||
const url = 'javascript:alert("test")';
|
const url = 'javascript:alert("test")';
|
||||||
|
|
||||||
let config = { securityLevel: 'loose' };
|
const config = { securityLevel: 'loose' };
|
||||||
let result = utils.formatUrl(url, config);
|
let result = utils.formatUrl(url, config);
|
||||||
expect(result).toEqual(url);
|
expect(result).toEqual(url);
|
||||||
|
|
||||||
@@ -425,6 +425,42 @@ describe('when parsing font sizes', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles unparseable input', function () {
|
it('handles unparseable input', function () {
|
||||||
|
// @ts-expect-error Explicitly testing unparsable input
|
||||||
expect(utils.parseFontSize({ fontSize: 14 })).toEqual([undefined, undefined]);
|
expect(utils.parseFontSize({ fontSize: 14 })).toEqual([undefined, undefined]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('cleanAndMerge', () => {
|
||||||
|
test('should merge objects', () => {
|
||||||
|
expect(cleanAndMerge({ a: 1, b: 2 }, { b: 3 })).toEqual({ a: 1, b: 3 });
|
||||||
|
expect(cleanAndMerge({ a: 1 }, { a: 2 })).toEqual({ a: 2 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should remove undefined values', () => {
|
||||||
|
expect(cleanAndMerge({ a: 1, b: 2 }, { b: undefined })).toEqual({ a: 1, b: 2 });
|
||||||
|
expect(cleanAndMerge({ a: 1, b: 2 }, { a: 2, b: undefined })).toEqual({ a: 2, b: 2 });
|
||||||
|
expect(cleanAndMerge({ a: 1, b: { c: 2 } }, { a: 2, b: undefined })).toEqual({
|
||||||
|
a: 2,
|
||||||
|
b: { c: 2 },
|
||||||
|
});
|
||||||
|
// @ts-expect-error Explicitly testing different type
|
||||||
|
expect(cleanAndMerge({ a: 1, b: { c: 2 } }, { a: 2, b: { c: undefined } })).toEqual({
|
||||||
|
a: 2,
|
||||||
|
b: { c: 2 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create deep copies of object', () => {
|
||||||
|
const input: { a: number; b?: number } = { a: 1 };
|
||||||
|
const output = cleanAndMerge(input, { b: 2 });
|
||||||
|
expect(output).toEqual({ a: 1, b: 2 });
|
||||||
|
output.b = 3;
|
||||||
|
expect(input).toEqual({ a: 1 });
|
||||||
|
|
||||||
|
const inputDeep = { a: { b: 1 } };
|
||||||
|
const outputDeep = cleanAndMerge(inputDeep, { a: { b: 2 } });
|
||||||
|
expect(outputDeep).toEqual({ a: { b: 2 } });
|
||||||
|
outputDeep.a.b = 3;
|
||||||
|
expect(inputDeep).toEqual({ a: { b: 1 } });
|
||||||
|
});
|
||||||
|
});
|
@@ -31,6 +31,7 @@ import { detectType } from './diagram-api/detectType.js';
|
|||||||
import assignWithDepth from './assignWithDepth.js';
|
import assignWithDepth from './assignWithDepth.js';
|
||||||
import { MermaidConfig } from './config.type.js';
|
import { MermaidConfig } from './config.type.js';
|
||||||
import memoize from 'lodash-es/memoize.js';
|
import memoize from 'lodash-es/memoize.js';
|
||||||
|
import merge from 'lodash-es/merge.js';
|
||||||
|
|
||||||
export const ZERO_WIDTH_SPACE = '\u200b';
|
export const ZERO_WIDTH_SPACE = '\u200b';
|
||||||
|
|
||||||
@@ -802,7 +803,7 @@ export const calculateTextDimensions: (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const initIdGenerator = class iterator {
|
export const initIdGenerator = class iterator {
|
||||||
constructor(deterministic, seed) {
|
constructor(deterministic, seed?: any) {
|
||||||
this.deterministic = deterministic;
|
this.deterministic = deterministic;
|
||||||
// TODO: Seed is only used for length?
|
// TODO: Seed is only used for length?
|
||||||
this.seed = seed;
|
this.seed = seed;
|
||||||
@@ -994,12 +995,17 @@ export const parseFontSize = (fontSize: string | number | undefined): [number?,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function cleanAndMerge<T>(defaultData: T, data?: Partial<T>): T {
|
||||||
|
return merge({}, defaultData, data);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
assignWithDepth,
|
assignWithDepth,
|
||||||
wrapLabel,
|
wrapLabel,
|
||||||
calculateTextHeight,
|
calculateTextHeight,
|
||||||
calculateTextWidth,
|
calculateTextWidth,
|
||||||
calculateTextDimensions,
|
calculateTextDimensions,
|
||||||
|
cleanAndMerge,
|
||||||
detectInit,
|
detectInit,
|
||||||
detectDirective,
|
detectDirective,
|
||||||
isSubstringInArray,
|
isSubstringInArray,
|
||||||
|
32
pnpm-lock.yaml
generated
32
pnpm-lock.yaml
generated
@@ -193,7 +193,7 @@ importers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@braintree/sanitize-url':
|
'@braintree/sanitize-url':
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1
|
version: 6.0.2
|
||||||
'@types/d3-scale':
|
'@types/d3-scale':
|
||||||
specifier: ^4.0.3
|
specifier: ^4.0.3
|
||||||
version: 4.0.3
|
version: 4.0.3
|
||||||
@@ -267,6 +267,9 @@ importers:
|
|||||||
'@types/d3-selection':
|
'@types/d3-selection':
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.5
|
||||||
version: 3.0.5
|
version: 3.0.5
|
||||||
|
'@types/d3-shape':
|
||||||
|
specifier: ^3.1.1
|
||||||
|
version: 3.1.1
|
||||||
'@types/dompurify':
|
'@types/dompurify':
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
@@ -351,6 +354,9 @@ importers:
|
|||||||
start-server-and-test:
|
start-server-and-test:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
|
type-fest:
|
||||||
|
specifier: ^4.1.0
|
||||||
|
version: 4.1.0
|
||||||
typedoc:
|
typedoc:
|
||||||
specifier: ^0.24.5
|
specifier: ^0.24.5
|
||||||
version: 0.24.5(typescript@5.0.4)
|
version: 0.24.5(typescript@5.0.4)
|
||||||
@@ -377,7 +383,7 @@ importers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@braintree/sanitize-url':
|
'@braintree/sanitize-url':
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1
|
version: 6.0.2
|
||||||
cytoscape:
|
cytoscape:
|
||||||
specifier: ^3.23.0
|
specifier: ^3.23.0
|
||||||
version: 3.23.0
|
version: 3.23.0
|
||||||
@@ -2323,8 +2329,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@braintree/sanitize-url@6.0.1:
|
/@braintree/sanitize-url@6.0.2:
|
||||||
resolution: {integrity: sha512-zr9Qs9KFQiEvMWdZesjcmRJlUck5NR+eKGS1uyKk+oYTWwlYrsoPEi6VmG6/TzBD1hKCGEimrhTgGS6hvn/xIQ==}
|
resolution: {integrity: sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@colors/colors@1.5.0:
|
/@colors/colors@1.5.0:
|
||||||
@@ -3692,7 +3698,7 @@ packages:
|
|||||||
'@types/node': 18.16.0
|
'@types/node': 18.16.0
|
||||||
ansi-escapes: 4.3.2
|
ansi-escapes: 4.3.2
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
ci-info: 3.6.2
|
ci-info: 3.8.0
|
||||||
exit: 0.1.2
|
exit: 0.1.2
|
||||||
graceful-fs: 4.2.10
|
graceful-fs: 4.2.10
|
||||||
jest-changed-files: 29.5.0
|
jest-changed-files: 29.5.0
|
||||||
@@ -4357,8 +4363,8 @@ packages:
|
|||||||
'@types/d3-path': 1.0.9
|
'@types/d3-path': 1.0.9
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/d3-shape@3.1.0:
|
/@types/d3-shape@3.1.1:
|
||||||
resolution: {integrity: sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==}
|
resolution: {integrity: sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/d3-path': 3.0.0
|
'@types/d3-path': 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
@@ -4414,7 +4420,7 @@ packages:
|
|||||||
'@types/d3-scale': 4.0.3
|
'@types/d3-scale': 4.0.3
|
||||||
'@types/d3-scale-chromatic': 3.0.0
|
'@types/d3-scale-chromatic': 3.0.0
|
||||||
'@types/d3-selection': 3.0.5
|
'@types/d3-selection': 3.0.5
|
||||||
'@types/d3-shape': 3.1.0
|
'@types/d3-shape': 3.1.1
|
||||||
'@types/d3-time': 3.0.0
|
'@types/d3-time': 3.0.0
|
||||||
'@types/d3-time-format': 4.0.0
|
'@types/d3-time-format': 4.0.0
|
||||||
'@types/d3-timer': 3.0.0
|
'@types/d3-timer': 3.0.0
|
||||||
@@ -6668,11 +6674,6 @@ packages:
|
|||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/ci-info@3.6.2:
|
|
||||||
resolution: {integrity: sha512-lVZdhvbEudris15CLytp2u6Y0p5EKfztae9Fqa189MfNmln9F33XuH69v5fvNfiRN5/0eAUz2yJL3mo+nhaRKg==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/ci-info@3.8.0:
|
/ci-info@3.8.0:
|
||||||
resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==}
|
resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -14792,6 +14793,11 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/type-fest@4.1.0:
|
||||||
|
resolution: {integrity: sha512-VJGJVepayd8OWavP+rgXt4i3bfLk+tSomTV7r4mca2XD/oTCWnkJlNkpXavkxdmtU2aKdAmFGeHvoQutOVHCZg==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/type-is@1.6.18:
|
/type-is@1.6.18:
|
||||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
Reference in New Issue
Block a user