@zombie-mermaid/svg-renderer 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +51 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +1071 -948
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/pie-layout.test.ts +215 -0
- package/src/__tests__/pie-svg.test.ts +194 -0
- package/src/index.ts +2 -0
- package/src/pie/layout.ts +234 -0
- package/src/pie/renderer.ts +161 -0
- package/src/registry.ts +30 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Pie chart SVG renderer
|
|
3
|
+
//
|
|
4
|
+
// Draws a PositionedPieChart (see ./layout.ts) in Mermaid's own structure and
|
|
5
|
+
// paint order: outline circle, slices, percentage labels, title, legend.
|
|
6
|
+
//
|
|
7
|
+
// Styling follows Mermaid's pieStyles.ts and default theme, mapped onto this
|
|
8
|
+
// library's CSS custom properties so every theme (and live theme switching
|
|
9
|
+
// through `--bg`/`--fg`/`--accent`) applies:
|
|
10
|
+
//
|
|
11
|
+
// Mermaid here
|
|
12
|
+
// pie1..pie12 --pie-color-0..11: the theme accent, then
|
|
13
|
+
// getSeriesColor() shades of it (the same
|
|
14
|
+
// palette xychart series use)
|
|
15
|
+
// slice opacity 0.7 opacity: 0.7 (on fill and stroke, as there)
|
|
16
|
+
// pieStrokeColor black, 2px var(--fg), 2px
|
|
17
|
+
// pieOuterStrokeColor black, 2px var(--fg), 2px
|
|
18
|
+
// title 25px, legend/labels 17px same sizes, regular weight
|
|
19
|
+
// text colours (black / #333) var(--_text)
|
|
20
|
+
//
|
|
21
|
+
// The legend swatch is filled and stroked with the slice colour at full
|
|
22
|
+
// opacity, as in Mermaid. There are no tooltips or hover effects: Mermaid
|
|
23
|
+
// draws none by default (`highlightSlice` is off), so `interactivity` does
|
|
24
|
+
// not change the output.
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
import type { PositionedPieChart } from '@zombie-mermaid/mermaid-parser'
|
|
28
|
+
import type { DiagramColors, SvgEmitOptions } from '@zombie-mermaid/core'
|
|
29
|
+
import {
|
|
30
|
+
svgOpenTag,
|
|
31
|
+
buildStyleBlock,
|
|
32
|
+
styleOpenTag,
|
|
33
|
+
escapeXml,
|
|
34
|
+
escapeAttr,
|
|
35
|
+
} from '@zombie-mermaid/core'
|
|
36
|
+
import {
|
|
37
|
+
getSeriesColor,
|
|
38
|
+
CHART_ACCENT_FALLBACK,
|
|
39
|
+
} from '@zombie-mermaid/mermaid-parser'
|
|
40
|
+
import { withDataSrc } from '../renderer.ts'
|
|
41
|
+
import { PIE } from './layout.ts'
|
|
42
|
+
|
|
43
|
+
function r(n: number): string {
|
|
44
|
+
return String(Math.round(n * 100) / 100)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** CSS value for palette slot `index`: slot 0 follows `--accent` live. */
|
|
48
|
+
export function pieColorValue(
|
|
49
|
+
index: number,
|
|
50
|
+
accent: string | undefined,
|
|
51
|
+
bg: string,
|
|
52
|
+
): string {
|
|
53
|
+
if (index === 0) return `var(--accent, ${CHART_ACCENT_FALLBACK})`
|
|
54
|
+
return getSeriesColor(index, accent ?? CHART_ACCENT_FALLBACK, bg)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function pieStyles(
|
|
58
|
+
chart: PositionedPieChart,
|
|
59
|
+
colors: DiagramColors,
|
|
60
|
+
nonce?: string,
|
|
61
|
+
): string {
|
|
62
|
+
const used = new Set<number>()
|
|
63
|
+
for (const s of chart.slices) used.add(s.colorIndex)
|
|
64
|
+
for (const l of chart.legend) used.add(l.colorIndex)
|
|
65
|
+
const indices = [...used].sort((a, b) => a - b)
|
|
66
|
+
|
|
67
|
+
const vars = indices.map(
|
|
68
|
+
(i) =>
|
|
69
|
+
` --pie-color-${i}: ${pieColorValue(i, colors.accent, colors.bg)};`,
|
|
70
|
+
)
|
|
71
|
+
const rules = indices.map(
|
|
72
|
+
(i) =>
|
|
73
|
+
` .pie-color-${i} { fill: var(--pie-color-${i}); }\n` +
|
|
74
|
+
` rect.pie-color-${i} { stroke: var(--pie-color-${i}); }`,
|
|
75
|
+
)
|
|
76
|
+
const varsBlock = vars.length > 0 ? `\n svg {\n${vars.join('\n')}\n }` : ''
|
|
77
|
+
|
|
78
|
+
return `${styleOpenTag(nonce)}
|
|
79
|
+
.pie-outer { fill: none; stroke: var(--fg); stroke-width: ${PIE.outerStrokeWidth}px; }
|
|
80
|
+
.pie-slice { stroke: var(--fg); stroke-width: 2px; opacity: 0.7; }
|
|
81
|
+
.pie-slice-label { fill: var(--_text); font-size: ${PIE.sectionFontSize}px; font-weight: ${PIE.fontWeight}; }
|
|
82
|
+
.pie-title { fill: var(--_text); font-size: ${PIE.titleFontSize}px; font-weight: ${PIE.fontWeight}; }
|
|
83
|
+
.pie-legend-text { fill: var(--_text); font-size: ${PIE.legendFontSize}px; font-weight: ${PIE.fontWeight}; }${varsBlock}
|
|
84
|
+
${rules.join('\n')}
|
|
85
|
+
</style>`
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Render a positioned pie chart as an SVG string.
|
|
90
|
+
*
|
|
91
|
+
* The accessible name is `title` (from `options.title`) when given,
|
|
92
|
+
* otherwise the chart's own `accTitle`; `accDescr` becomes the root
|
|
93
|
+
* `<desc>`. Both are dropped when `decorative` is set — see svgOpenTag() in
|
|
94
|
+
* packages/core/src/theme.ts.
|
|
95
|
+
*/
|
|
96
|
+
export function renderPieSvg(
|
|
97
|
+
chart: PositionedPieChart,
|
|
98
|
+
colors: DiagramColors,
|
|
99
|
+
font: string = 'Inter',
|
|
100
|
+
transparent: boolean = false,
|
|
101
|
+
embedSource?: string,
|
|
102
|
+
title?: string,
|
|
103
|
+
decorative?: boolean,
|
|
104
|
+
emit: SvgEmitOptions = {},
|
|
105
|
+
): string {
|
|
106
|
+
const parts: string[] = []
|
|
107
|
+
|
|
108
|
+
parts.push(
|
|
109
|
+
withDataSrc(
|
|
110
|
+
svgOpenTag(
|
|
111
|
+
chart.width,
|
|
112
|
+
chart.height,
|
|
113
|
+
colors,
|
|
114
|
+
transparent,
|
|
115
|
+
title ?? chart.accTitle,
|
|
116
|
+
decorative,
|
|
117
|
+
undefined,
|
|
118
|
+
emit.styleAttribute,
|
|
119
|
+
chart.accDescr,
|
|
120
|
+
),
|
|
121
|
+
embedSource,
|
|
122
|
+
),
|
|
123
|
+
)
|
|
124
|
+
parts.push(buildStyleBlock(font, false, emit.nonce))
|
|
125
|
+
parts.push(pieStyles(chart, colors, emit.nonce))
|
|
126
|
+
|
|
127
|
+
parts.push(
|
|
128
|
+
`<circle cx="${r(chart.cx)}" cy="${r(chart.cy)}" r="${r(chart.outerRadius)}" class="pie-outer"/>`,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
for (const s of chart.slices) {
|
|
132
|
+
parts.push(
|
|
133
|
+
`<path d="${s.path}" class="pie-slice pie-color-${s.colorIndex}"` +
|
|
134
|
+
` data-label="${escapeAttr(s.label)}" data-value="${s.value}"/>`,
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const s of chart.slices) {
|
|
139
|
+
parts.push(
|
|
140
|
+
`<text x="${r(s.labelX)}" y="${r(s.labelY)}" text-anchor="middle" class="pie-slice-label">${escapeXml(s.percentText)}</text>`,
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (chart.title) {
|
|
145
|
+
parts.push(
|
|
146
|
+
`<text x="${r(chart.title.x)}" y="${r(chart.title.y)}" text-anchor="middle" class="pie-title">${escapeXml(chart.title.text)}</text>`,
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (const item of chart.legend) {
|
|
151
|
+
parts.push(
|
|
152
|
+
`<g class="pie-legend">` +
|
|
153
|
+
`<rect x="${r(item.x)}" y="${r(item.y)}" width="${item.size}" height="${item.size}" class="pie-legend-swatch pie-color-${item.colorIndex}"/>` +
|
|
154
|
+
`<text x="${r(item.textX)}" y="${r(item.textY)}" class="pie-legend-text">${escapeXml(item.text)}</text>` +
|
|
155
|
+
`</g>`,
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
parts.push('</svg>')
|
|
160
|
+
return parts.join('\n')
|
|
161
|
+
}
|
package/src/registry.ts
CHANGED
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
parseArchitecture,
|
|
58
58
|
architectureToGraph,
|
|
59
59
|
parseC4Diagram,
|
|
60
|
+
parsePieChart,
|
|
60
61
|
} from '@zombie-mermaid/mermaid-parser'
|
|
61
62
|
import type {
|
|
62
63
|
XYChart,
|
|
@@ -70,6 +71,8 @@ import type {
|
|
|
70
71
|
ArchitectureDiagram,
|
|
71
72
|
C4Diagram,
|
|
72
73
|
PositionedC4Diagram,
|
|
74
|
+
PieChart,
|
|
75
|
+
PositionedPieChart,
|
|
73
76
|
} from '@zombie-mermaid/mermaid-parser'
|
|
74
77
|
import { layoutXYChart } from './xychart/layout.ts'
|
|
75
78
|
import { renderXYChartSvg } from './xychart/renderer.ts'
|
|
@@ -81,6 +84,8 @@ import { layoutClassDiagramSync } from './class/layout.ts'
|
|
|
81
84
|
import { renderClassSvg } from './class/renderer.ts'
|
|
82
85
|
import { layoutC4DiagramSync } from './c4/layout.ts'
|
|
83
86
|
import { renderC4Svg } from './c4/renderer.ts'
|
|
87
|
+
import { layoutPieChart } from './pie/layout.ts'
|
|
88
|
+
import { renderPieSvg } from './pie/renderer.ts'
|
|
84
89
|
import { layoutFlowchartSync, layoutGraphSync } from './layout-engine.ts'
|
|
85
90
|
import { renderSvg as renderFlowchartSvg } from './renderer.ts'
|
|
86
91
|
import { parseMermaid } from '@zombie-mermaid/mermaid-parser'
|
|
@@ -380,6 +385,30 @@ const architectureModule: DiagramModule<
|
|
|
380
385
|
renderSvg: flowchartModule.renderSvg,
|
|
381
386
|
}
|
|
382
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Pie charts parse from the raw `text` rather than `lines` (see
|
|
390
|
+
* `parsePieChart`'s doc: `splitStatements` would split `title A; B` and
|
|
391
|
+
* treat `'` as a quote), then lay out and render to Mermaid's geometry —
|
|
392
|
+
* see ./pie/layout.ts. No direction or interactivity applies.
|
|
393
|
+
*/
|
|
394
|
+
const pieModule: DiagramModule<PieChart, PositionedPieChart> = {
|
|
395
|
+
type: 'pie',
|
|
396
|
+
parse: (_lines, text) => parsePieChart(text),
|
|
397
|
+
layoutForSvg: layoutPieChart,
|
|
398
|
+
renderSvg(positioned, ctx) {
|
|
399
|
+
return renderPieSvg(
|
|
400
|
+
positioned,
|
|
401
|
+
ctx.colors,
|
|
402
|
+
ctx.font,
|
|
403
|
+
ctx.transparent,
|
|
404
|
+
ctx.embedSource,
|
|
405
|
+
ctx.title,
|
|
406
|
+
ctx.decorative,
|
|
407
|
+
ctx.emit,
|
|
408
|
+
)
|
|
409
|
+
},
|
|
410
|
+
}
|
|
411
|
+
|
|
383
412
|
/**
|
|
384
413
|
* The registry proper — every `DiagramType` is looked up here by the SVG
|
|
385
414
|
* front door (`renderMermaidSVG` in ./index.ts), which has no fallback
|
|
@@ -408,4 +437,5 @@ export const diagramRegistry: Record<DiagramType, AnyDiagramModule> = {
|
|
|
408
437
|
architecture: architectureModule,
|
|
409
438
|
|
|
410
439
|
c4: c4Module,
|
|
440
|
+
pie: pieModule,
|
|
411
441
|
}
|