@zombie-mermaid/svg-renderer 3.0.0 → 3.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 +49 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +1611 -1118
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/c4-mermaid-reference.test.ts +85 -0
- package/src/__tests__/c4-svg.test.ts +337 -0
- package/src/__tests__/class-diamond-markers-1232.test.ts +59 -0
- package/src/__tests__/er-cardinality-markers-1233.test.ts +35 -0
- package/src/__tests__/fixtures/c4-mermaid-reference.json +626 -0
- package/src/__tests__/xychart-smooth-curve.test.ts +42 -0
- package/src/c4/layout.ts +490 -0
- package/src/c4/metrics.ts +235 -0
- package/src/c4/renderer.ts +366 -0
- package/src/class/renderer.ts +72 -68
- package/src/edge-curves.ts +17 -16
- package/src/er/renderer.ts +64 -50
- package/src/layout-engine/elk-graph-builder.ts +4 -0
- package/src/registry.ts +60 -0
- package/src/renderer.ts +190 -189
- package/src/sequence/renderer.ts +66 -65
- package/src/xychart/layout.ts +27 -11
- package/src/xychart/renderer.ts +33 -42
package/src/er/renderer.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
escapeXml as escapeXmlUtil,
|
|
14
14
|
escapeAttr,
|
|
15
15
|
measureMultilineText,
|
|
16
|
+
f,
|
|
16
17
|
} from '@zombie-mermaid/core'
|
|
17
18
|
import { withDataSrc } from '../renderer.ts'
|
|
18
19
|
import {
|
|
@@ -144,19 +145,19 @@ function renderEntityBox(
|
|
|
144
145
|
|
|
145
146
|
// Semantic wrapper with entity metadata
|
|
146
147
|
parts.push(
|
|
147
|
-
`<g class="entity" data-id="${escapeAttr(id)}" data-label="${escapeAttr(label)}">`,
|
|
148
|
+
f`<g class="entity" data-id="${escapeAttr(id)}" data-label="${escapeAttr(label)}">`,
|
|
148
149
|
)
|
|
149
150
|
|
|
150
151
|
// Outer rectangle
|
|
151
152
|
parts.push(
|
|
152
|
-
` <rect x="${x}" y="${y}" width="${width}" height="${height}" ` +
|
|
153
|
-
`rx="0" ry="0" fill="var(--_node-fill)" stroke="var(--_node-stroke)" stroke-width="${STROKE_WIDTHS.outerBox}" />`,
|
|
153
|
+
f` <rect x="${x}" y="${y}" width="${width}" height="${height}" ` +
|
|
154
|
+
f`rx="0" ry="0" fill="var(--_node-fill)" stroke="var(--_node-stroke)" stroke-width="${STROKE_WIDTHS.outerBox}" />`,
|
|
154
155
|
)
|
|
155
156
|
|
|
156
157
|
// Header background
|
|
157
158
|
parts.push(
|
|
158
|
-
` <rect x="${x}" y="${y}" width="${width}" height="${headerHeight}" ` +
|
|
159
|
-
`rx="0" ry="0" fill="var(--_group-hdr)" stroke="var(--_node-stroke)" stroke-width="${STROKE_WIDTHS.outerBox}" />`,
|
|
159
|
+
f` <rect x="${x}" y="${y}" width="${width}" height="${headerHeight}" ` +
|
|
160
|
+
f`rx="0" ry="0" fill="var(--_group-hdr)" stroke="var(--_node-stroke)" stroke-width="${STROKE_WIDTHS.outerBox}" />`,
|
|
160
161
|
)
|
|
161
162
|
|
|
162
163
|
// Entity name (supports multi-line via <br> tags)
|
|
@@ -167,15 +168,15 @@ function renderEntityBox(
|
|
|
167
168
|
x + width / 2,
|
|
168
169
|
y + headerHeight / 2,
|
|
169
170
|
fontSizes.nodeLabel,
|
|
170
|
-
`text-anchor="middle" font-size="${fontSizes.nodeLabel}" font-weight="700" fill="var(--_text)"`,
|
|
171
|
+
f`text-anchor="middle" font-size="${fontSizes.nodeLabel}" font-weight="700" fill="var(--_text)"`,
|
|
171
172
|
),
|
|
172
173
|
)
|
|
173
174
|
|
|
174
175
|
// Divider
|
|
175
176
|
const attrTop = y + headerHeight
|
|
176
177
|
parts.push(
|
|
177
|
-
` <line x1="${x}" y1="${attrTop}" x2="${x + width}" y2="${attrTop}" ` +
|
|
178
|
-
`stroke="var(--_node-stroke)" stroke-width="${STROKE_WIDTHS.innerBox}" />`,
|
|
178
|
+
f` <line x1="${x}" y1="${attrTop}" x2="${x + width}" y2="${attrTop}" ` +
|
|
179
|
+
f`stroke="var(--_node-stroke)" stroke-width="${STROKE_WIDTHS.innerBox}" />`,
|
|
179
180
|
)
|
|
180
181
|
|
|
181
182
|
// Attribute rows
|
|
@@ -190,8 +191,8 @@ function renderEntityBox(
|
|
|
190
191
|
// Empty row placeholder when no attributes
|
|
191
192
|
if (attributes.length === 0) {
|
|
192
193
|
parts.push(
|
|
193
|
-
` <text x="${x + width / 2}" y="${attrTop + rowHeight / 2}" text-anchor="middle" dy="${TEXT_BASELINE_SHIFT}" ` +
|
|
194
|
-
`font-size="${ER_FONT.attrSize}" fill="var(--_text-faint)" font-style="italic">(no attributes)</text>`,
|
|
194
|
+
f` <text x="${x + width / 2}" y="${attrTop + rowHeight / 2}" text-anchor="middle" dy="${TEXT_BASELINE_SHIFT}" ` +
|
|
195
|
+
f`font-size="${ER_FONT.attrSize}" fill="var(--_text-faint)" font-style="italic">(no attributes)</text>`,
|
|
195
196
|
)
|
|
196
197
|
}
|
|
197
198
|
|
|
@@ -220,7 +221,7 @@ function renderAttribute(
|
|
|
220
221
|
if (attr.comment && attr.comment.length > 0) {
|
|
221
222
|
// Replace <br> with newlines for tooltip display
|
|
222
223
|
const tooltipText = attr.comment.replace(/<br\s*\/?>/gi, '\n')
|
|
223
|
-
parts.push(`<g><title>${escapeXml(tooltipText)}</title>`)
|
|
224
|
+
parts.push(f`<g><title>${escapeXml(tooltipText)}</title>`)
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
// Key badges on the left (keep proportional font — they're visual tags, not code)
|
|
@@ -230,29 +231,29 @@ function renderAttribute(
|
|
|
230
231
|
keyWidth =
|
|
231
232
|
estimateTextWidth(keyText, ER_FONT.keySize, ER_FONT.keyWeight) + 8
|
|
232
233
|
parts.push(
|
|
233
|
-
`<rect x="${boxX + 6}" y="${y - 7}" width="${keyWidth}" height="14" rx="2" ry="2" ` +
|
|
234
|
+
f`<rect x="${boxX + 6}" y="${y - 7}" width="${keyWidth}" height="14" rx="2" ry="2" ` +
|
|
234
235
|
`fill="var(--_key-badge)" />`,
|
|
235
236
|
)
|
|
236
237
|
parts.push(
|
|
237
|
-
`<text x="${boxX + 6 + keyWidth / 2}" y="${y}" text-anchor="middle" dy="${TEXT_BASELINE_SHIFT}" ` +
|
|
238
|
-
`font-size="${ER_FONT.keySize}" font-weight="${ER_FONT.keyWeight}" fill="var(--_text-sec)">${attr.keys.join(',')}</text>`,
|
|
238
|
+
f`<text x="${boxX + 6 + keyWidth / 2}" y="${y}" text-anchor="middle" dy="${TEXT_BASELINE_SHIFT}" ` +
|
|
239
|
+
f`font-size="${ER_FONT.keySize}" font-weight="${ER_FONT.keyWeight}" fill="var(--_text-sec)">${attr.keys.join(',')}</text>`,
|
|
239
240
|
)
|
|
240
241
|
}
|
|
241
242
|
|
|
242
243
|
// Type (left-aligned after keys, monospace with syntax highlighting)
|
|
243
244
|
const typeX = boxX + 8 + (keyWidth > 0 ? keyWidth + 6 : 0)
|
|
244
245
|
parts.push(
|
|
245
|
-
`<text x="${typeX}" y="${y}" class="mono" dy="${TEXT_BASELINE_SHIFT}" ` +
|
|
246
|
-
`font-size="${ER_FONT.attrSize}" font-weight="${ER_FONT.attrWeight}">` +
|
|
247
|
-
`<tspan fill="var(--_text-muted)">${escapeXml(attr.type)}</tspan></text>`,
|
|
246
|
+
f`<text x="${typeX}" y="${y}" class="mono" dy="${TEXT_BASELINE_SHIFT}" ` +
|
|
247
|
+
f`font-size="${ER_FONT.attrSize}" font-weight="${ER_FONT.attrWeight}">` +
|
|
248
|
+
f`<tspan fill="var(--_text-muted)">${escapeXml(attr.type)}</tspan></text>`,
|
|
248
249
|
)
|
|
249
250
|
|
|
250
251
|
// Name (right-aligned, monospace with syntax highlighting)
|
|
251
252
|
const nameX = boxX + boxWidth - 8
|
|
252
253
|
parts.push(
|
|
253
|
-
`<text x="${nameX}" y="${y}" class="mono" text-anchor="end" dy="${TEXT_BASELINE_SHIFT}" ` +
|
|
254
|
-
`font-size="${ER_FONT.attrSize}" font-weight="${ER_FONT.attrWeight}">` +
|
|
255
|
-
`<tspan fill="var(--_text-sec)">${escapeXml(attr.name)}</tspan></text>`,
|
|
254
|
+
f`<text x="${nameX}" y="${y}" class="mono" text-anchor="end" dy="${TEXT_BASELINE_SHIFT}" ` +
|
|
255
|
+
f`font-size="${ER_FONT.attrSize}" font-weight="${ER_FONT.attrWeight}">` +
|
|
256
|
+
f`<tspan fill="var(--_text-sec)">${escapeXml(attr.name)}</tspan></text>`,
|
|
256
257
|
)
|
|
257
258
|
|
|
258
259
|
// Close the group if we opened one
|
|
@@ -273,23 +274,23 @@ function renderAttribute(
|
|
|
273
274
|
function renderRelationshipLine(rel: PositionedErRelationship): string {
|
|
274
275
|
if (rel.points.length < 2) return ''
|
|
275
276
|
|
|
276
|
-
const pathData = rel.points.map((p) => `${p.x},${p.y}`).join(' ')
|
|
277
|
+
const pathData = rel.points.map((p) => f`${p.x},${p.y}`).join(' ')
|
|
277
278
|
const dashArray = !rel.identifying ? ' stroke-dasharray="6 4"' : ''
|
|
278
279
|
|
|
279
280
|
// Semantic data attributes for relationship inspection
|
|
280
|
-
const labelAttr = rel.label ? ` data-label="${escapeAttr(rel.label)}"` : ''
|
|
281
|
+
const labelAttr = rel.label ? f` data-label="${escapeAttr(rel.label)}"` : ''
|
|
281
282
|
const dataAttrs = [
|
|
282
283
|
'class="er-relationship"',
|
|
283
|
-
`data-entity1="${escapeAttr(rel.entity1)}"`,
|
|
284
|
-
`data-entity2="${escapeAttr(rel.entity2)}"`,
|
|
285
|
-
`data-cardinality1="${rel.cardinality1}"`,
|
|
286
|
-
`data-cardinality2="${rel.cardinality2}"`,
|
|
287
|
-
`data-identifying="${rel.identifying}"`,
|
|
284
|
+
f`data-entity1="${escapeAttr(rel.entity1)}"`,
|
|
285
|
+
f`data-entity2="${escapeAttr(rel.entity2)}"`,
|
|
286
|
+
f`data-cardinality1="${rel.cardinality1}"`,
|
|
287
|
+
f`data-cardinality2="${rel.cardinality2}"`,
|
|
288
|
+
f`data-identifying="${rel.identifying}"`,
|
|
288
289
|
]
|
|
289
290
|
|
|
290
291
|
return (
|
|
291
|
-
`<polyline ${dataAttrs.join(' ')}${labelAttr} points="${pathData}" fill="none" stroke="var(--_line)" ` +
|
|
292
|
-
`stroke-width="${STROKE_WIDTHS.connector}"${dashArray} />`
|
|
292
|
+
f`<polyline ${dataAttrs.join(' ')}${labelAttr} points="${pathData}" fill="none" stroke="var(--_line)" ` +
|
|
293
|
+
f`stroke-width="${STROKE_WIDTHS.connector}"${dashArray} />`
|
|
293
294
|
)
|
|
294
295
|
}
|
|
295
296
|
|
|
@@ -312,14 +313,14 @@ function renderRelationshipLabel(
|
|
|
312
313
|
const bgH = metrics.height + 6
|
|
313
314
|
|
|
314
315
|
return (
|
|
315
|
-
`<rect x="${mid.x - bgW / 2}" y="${mid.y - bgH / 2}" width="${bgW}" height="${bgH}" rx="2" ry="2" ` +
|
|
316
|
+
f`<rect x="${mid.x - bgW / 2}" y="${mid.y - bgH / 2}" width="${bgW}" height="${bgH}" rx="2" ry="2" ` +
|
|
316
317
|
`fill="var(--bg)" stroke="var(--_inner-stroke)" stroke-width="0.5" />` +
|
|
317
|
-
`\n${renderMultilineText(
|
|
318
|
+
f`\n${renderMultilineText(
|
|
318
319
|
rel.label,
|
|
319
320
|
mid.x,
|
|
320
321
|
mid.y,
|
|
321
322
|
fontSizes.edgeLabel,
|
|
322
|
-
`text-anchor="middle" font-size="${fontSizes.edgeLabel}" font-weight="${FONT_WEIGHTS.edgeLabel}" fill="var(--_text-muted)"`,
|
|
323
|
+
f`text-anchor="middle" font-size="${fontSizes.edgeLabel}" font-weight="${FONT_WEIGHTS.edgeLabel}" fill="var(--_text-muted)"`,
|
|
323
324
|
)}`
|
|
324
325
|
)
|
|
325
326
|
}
|
|
@@ -330,7 +331,7 @@ function renderRelationshipLabel(
|
|
|
330
331
|
* Crow's foot notation:
|
|
331
332
|
* 'one': ─║─ (single vertical line)
|
|
332
333
|
* 'zero-one': ─o║─ (circle + single line)
|
|
333
|
-
* 'many': ─╢─ (crow's foot +
|
|
334
|
+
* 'many': ─╢─ (crow's foot + bar)
|
|
334
335
|
* 'zero-many': ─o╣─ (circle + crow's foot)
|
|
335
336
|
*/
|
|
336
337
|
function renderCardinality(rel: PositionedErRelationship): string {
|
|
@@ -389,17 +390,17 @@ function renderCrowsFoot(
|
|
|
389
390
|
if (hasOneLine) {
|
|
390
391
|
const halfW = 6
|
|
391
392
|
parts.push(
|
|
392
|
-
`<line x1="${tipX + px * halfW}" y1="${tipY + py * halfW}" ` +
|
|
393
|
-
`x2="${tipX - px * halfW}" y2="${tipY - py * halfW}" ` +
|
|
394
|
-
`stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
393
|
+
f`<line x1="${tipX + px * halfW}" y1="${tipY + py * halfW}" ` +
|
|
394
|
+
f`x2="${tipX - px * halfW}" y2="${tipY - py * halfW}" ` +
|
|
395
|
+
f`stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
395
396
|
)
|
|
396
397
|
// Second line slightly back for "exactly one" emphasis
|
|
397
398
|
const line2X = tipX - ux * 4
|
|
398
399
|
const line2Y = tipY - uy * 4
|
|
399
400
|
parts.push(
|
|
400
|
-
`<line x1="${line2X + px * halfW}" y1="${line2Y + py * halfW}" ` +
|
|
401
|
-
`x2="${line2X - px * halfW}" y2="${line2Y - py * halfW}" ` +
|
|
402
|
-
`stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
401
|
+
f`<line x1="${line2X + px * halfW}" y1="${line2Y + py * halfW}" ` +
|
|
402
|
+
f`x2="${line2X - px * halfW}" y2="${line2Y - py * halfW}" ` +
|
|
403
|
+
f`stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
403
404
|
)
|
|
404
405
|
}
|
|
405
406
|
|
|
@@ -412,21 +413,34 @@ function renderCrowsFoot(
|
|
|
412
413
|
// Three lines from tip to back, fanning out
|
|
413
414
|
parts.push(
|
|
414
415
|
// Top fan line
|
|
415
|
-
`<line x1="${cfTipX + px * fanW}" y1="${cfTipY + py * fanW}" ` +
|
|
416
|
-
`x2="${backX}" y2="${backY}" ` +
|
|
417
|
-
`stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
416
|
+
f`<line x1="${cfTipX + px * fanW}" y1="${cfTipY + py * fanW}" ` +
|
|
417
|
+
f`x2="${backX}" y2="${backY}" ` +
|
|
418
|
+
f`stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
418
419
|
)
|
|
419
420
|
parts.push(
|
|
420
421
|
// Center line
|
|
421
|
-
`<line x1="${cfTipX}" y1="${cfTipY}" ` +
|
|
422
|
-
`x2="${backX}" y2="${backY}" ` +
|
|
423
|
-
`stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
422
|
+
f`<line x1="${cfTipX}" y1="${cfTipY}" ` +
|
|
423
|
+
f`x2="${backX}" y2="${backY}" ` +
|
|
424
|
+
f`stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
424
425
|
)
|
|
425
426
|
parts.push(
|
|
426
427
|
// Bottom fan line
|
|
427
|
-
`<line x1="${cfTipX - px * fanW}" y1="${cfTipY - py * fanW}" ` +
|
|
428
|
-
`x2="${backX}" y2="${backY}" ` +
|
|
429
|
-
`stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
428
|
+
f`<line x1="${cfTipX - px * fanW}" y1="${cfTipY - py * fanW}" ` +
|
|
429
|
+
f`x2="${backX}" y2="${backY}" ` +
|
|
430
|
+
f`stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Bar across the foot's convergence point: distinguishes one-or-many
|
|
435
|
+
// from a bare crow's foot (#1233)
|
|
436
|
+
if (cardinality === 'many') {
|
|
437
|
+
const barX = point.x - ux * 20
|
|
438
|
+
const barY = point.y - uy * 20
|
|
439
|
+
const halfW = 6
|
|
440
|
+
parts.push(
|
|
441
|
+
f`<line x1="${barX + px * halfW}" y1="${barY + py * halfW}" ` +
|
|
442
|
+
f`x2="${barX - px * halfW}" y2="${barY - py * halfW}" ` +
|
|
443
|
+
f`stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
430
444
|
)
|
|
431
445
|
}
|
|
432
446
|
|
|
@@ -436,8 +450,8 @@ function renderCrowsFoot(
|
|
|
436
450
|
const circleX = point.x - ux * circleOffset
|
|
437
451
|
const circleY = point.y - uy * circleOffset
|
|
438
452
|
parts.push(
|
|
439
|
-
`<circle cx="${circleX}" cy="${circleY}" r="4" ` +
|
|
440
|
-
`fill="var(--bg)" stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
453
|
+
f`<circle cx="${circleX}" cy="${circleY}" r="4" ` +
|
|
454
|
+
f`fill="var(--bg)" stroke="var(--_line)" stroke-width="${sw}" />`,
|
|
441
455
|
)
|
|
442
456
|
}
|
|
443
457
|
|
|
@@ -47,6 +47,9 @@ export type ElkDirection = 'DOWN' | 'UP' | 'LEFT' | 'RIGHT'
|
|
|
47
47
|
* single fixed orientation, matching Mermaid's own TB class rendering —
|
|
48
48
|
* which `../class/layout.ts` relies on to put a `note for X` above its
|
|
49
49
|
* class.
|
|
50
|
+
* - **C4** — `DOWN`. `C4Diagram.direction` is never set by the parser (C4
|
|
51
|
+
* sources have no direction statement), only by `RenderOptions.direction`,
|
|
52
|
+
* so an unset one is the top-down layout every C4 diagram is drawn in.
|
|
50
53
|
* - **ER** — `RIGHT`. An ER diagram's `direction` is optional
|
|
51
54
|
* (`ErDiagram.direction?`), left `undefined` when the source has no
|
|
52
55
|
* `direction` statement, and this renderer has always laid those out
|
|
@@ -65,6 +68,7 @@ export const ELK_DIRECTION_FALLBACK = {
|
|
|
65
68
|
state: 'DOWN',
|
|
66
69
|
class: 'DOWN',
|
|
67
70
|
er: 'RIGHT',
|
|
71
|
+
c4: 'DOWN',
|
|
68
72
|
} as const satisfies Record<string, ElkDirection>
|
|
69
73
|
|
|
70
74
|
/**
|
package/src/registry.ts
CHANGED
|
@@ -54,6 +54,9 @@ import {
|
|
|
54
54
|
parseErDiagram,
|
|
55
55
|
parseSequenceDiagram,
|
|
56
56
|
parseClassDiagram,
|
|
57
|
+
parseArchitecture,
|
|
58
|
+
architectureToGraph,
|
|
59
|
+
parseC4Diagram,
|
|
57
60
|
} from '@zombie-mermaid/mermaid-parser'
|
|
58
61
|
import type {
|
|
59
62
|
XYChart,
|
|
@@ -64,6 +67,9 @@ import type {
|
|
|
64
67
|
PositionedSequenceDiagram,
|
|
65
68
|
ClassDiagram,
|
|
66
69
|
PositionedClassDiagram,
|
|
70
|
+
ArchitectureDiagram,
|
|
71
|
+
C4Diagram,
|
|
72
|
+
PositionedC4Diagram,
|
|
67
73
|
} from '@zombie-mermaid/mermaid-parser'
|
|
68
74
|
import { layoutXYChart } from './xychart/layout.ts'
|
|
69
75
|
import { renderXYChartSvg } from './xychart/renderer.ts'
|
|
@@ -73,6 +79,8 @@ import { layoutSequenceDiagram } from './sequence/layout.ts'
|
|
|
73
79
|
import { renderSequenceSvg } from './sequence/renderer.ts'
|
|
74
80
|
import { layoutClassDiagramSync } from './class/layout.ts'
|
|
75
81
|
import { renderClassSvg } from './class/renderer.ts'
|
|
82
|
+
import { layoutC4DiagramSync } from './c4/layout.ts'
|
|
83
|
+
import { renderC4Svg } from './c4/renderer.ts'
|
|
76
84
|
import { layoutGraphSync } from './layout-engine.ts'
|
|
77
85
|
import { renderSvg as renderFlowchartSvg } from './renderer.ts'
|
|
78
86
|
import { parseMermaid } from '../../../src/parser.ts'
|
|
@@ -323,6 +331,55 @@ const flowchartModule: DiagramModule<MermaidGraph, PositionedFlowchart> = {
|
|
|
323
331
|
},
|
|
324
332
|
}
|
|
325
333
|
|
|
334
|
+
const c4Module: DiagramModule<C4Diagram, PositionedC4Diagram> = {
|
|
335
|
+
type: 'c4',
|
|
336
|
+
parse: parseC4Diagram,
|
|
337
|
+
// Mermaid's C4 renderer has no direction: shapes are placed in rows in
|
|
338
|
+
// declaration order, so `options.direction` does not apply to the SVG.
|
|
339
|
+
layoutForSvg(diagram, options) {
|
|
340
|
+
return layoutC4DiagramSync(diagram, options)
|
|
341
|
+
},
|
|
342
|
+
renderSvg(positioned, ctx) {
|
|
343
|
+
return renderC4Svg(
|
|
344
|
+
positioned,
|
|
345
|
+
ctx.colors,
|
|
346
|
+
ctx.font,
|
|
347
|
+
ctx.transparent,
|
|
348
|
+
ctx.fontSizes,
|
|
349
|
+
ctx.embedSource,
|
|
350
|
+
ctx.title,
|
|
351
|
+
ctx.decorative,
|
|
352
|
+
ctx.emit,
|
|
353
|
+
)
|
|
354
|
+
},
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Architecture diagrams are lowered to the flowchart model
|
|
359
|
+
* (`architectureToGraph` in `@zombie-mermaid/mermaid-parser`) and ride the
|
|
360
|
+
* flowchart layout + renderer, so this module is `flowchartModule` with a
|
|
361
|
+
* different `parse`. The lowered graph carries no `%%{init}%%` config, so
|
|
362
|
+
* `curve` is `options.curve` or 'linear'.
|
|
363
|
+
*/
|
|
364
|
+
const architectureModule: DiagramModule<
|
|
365
|
+
ArchitectureDiagram,
|
|
366
|
+
PositionedFlowchart
|
|
367
|
+
> = {
|
|
368
|
+
type: 'architecture',
|
|
369
|
+
parse: parseArchitecture,
|
|
370
|
+
layoutForSvg(diagram, options) {
|
|
371
|
+
const graph = withDirectionOverride(
|
|
372
|
+
architectureToGraph(diagram),
|
|
373
|
+
options.direction,
|
|
374
|
+
)
|
|
375
|
+
return {
|
|
376
|
+
graph: layoutGraphSync(graph, options),
|
|
377
|
+
curve: options.curve ?? 'linear',
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
renderSvg: flowchartModule.renderSvg,
|
|
381
|
+
}
|
|
382
|
+
|
|
326
383
|
/**
|
|
327
384
|
* The registry proper — every `DiagramType` is looked up here by the SVG
|
|
328
385
|
* front door (`renderMermaidSVG` in ./index.ts), which has no fallback
|
|
@@ -348,4 +405,7 @@ export const diagramRegistry: Record<DiagramType, AnyDiagramModule> = {
|
|
|
348
405
|
sequence: sequenceModule,
|
|
349
406
|
class: classModule,
|
|
350
407
|
flowchart: flowchartModule,
|
|
408
|
+
architecture: architectureModule,
|
|
409
|
+
|
|
410
|
+
c4: c4Module,
|
|
351
411
|
}
|