@diagrammo/dgmo 0.3.2 → 0.4.1
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/.claude/skills/dgmo-sequence/SKILL.md +7 -9
- package/.cursorrules +4 -4
- package/.github/copilot-instructions.md +4 -4
- package/.windsurfrules +4 -4
- package/README.md +11 -14
- package/dist/cli.cjs +150 -150
- package/dist/index.cjs +336 -891
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -7
- package/dist/index.d.ts +3 -7
- package/dist/index.js +335 -891
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +16 -19
- package/package.json +1 -1
- package/src/chart.ts +8 -39
- package/src/cli.ts +6 -6
- package/src/d3.ts +198 -674
- package/src/dgmo-router.ts +21 -42
- package/src/echarts.ts +80 -220
- package/src/index.ts +1 -0
- package/src/sequence/parser.ts +53 -133
- package/src/sequence/renderer.ts +4 -82
- package/src/utils/arrows.ts +37 -17
- package/src/utils/parsing.ts +43 -0
package/src/d3.ts
CHANGED
|
@@ -182,6 +182,64 @@ import type { DgmoError } from './diagnostics';
|
|
|
182
182
|
import { makeDgmoError, formatDgmoError, suggest } from './diagnostics';
|
|
183
183
|
import { collectIndentedValues } from './utils/parsing';
|
|
184
184
|
|
|
185
|
+
// ============================================================
|
|
186
|
+
// Shared Rendering Helpers
|
|
187
|
+
// ============================================================
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Renders a chart title on the SVG with optional click interaction.
|
|
191
|
+
*/
|
|
192
|
+
function renderChartTitle(
|
|
193
|
+
svg: d3Selection.Selection<SVGSVGElement, unknown, null, undefined>,
|
|
194
|
+
title: string | undefined | null,
|
|
195
|
+
titleLineNumber: number | undefined | null,
|
|
196
|
+
width: number,
|
|
197
|
+
textColor: string,
|
|
198
|
+
onClickItem?: (lineNumber: number) => void
|
|
199
|
+
): void {
|
|
200
|
+
if (!title) return;
|
|
201
|
+
const titleEl = svg.append('text')
|
|
202
|
+
.attr('class', 'chart-title')
|
|
203
|
+
.attr('x', width / 2)
|
|
204
|
+
.attr('y', 30)
|
|
205
|
+
.attr('text-anchor', 'middle')
|
|
206
|
+
.attr('fill', textColor)
|
|
207
|
+
.attr('font-size', '20px')
|
|
208
|
+
.attr('font-weight', '700')
|
|
209
|
+
.style('cursor', onClickItem && titleLineNumber ? 'pointer' : 'default')
|
|
210
|
+
.text(title);
|
|
211
|
+
if (titleLineNumber) {
|
|
212
|
+
titleEl.attr('data-line-number', titleLineNumber);
|
|
213
|
+
if (onClickItem) {
|
|
214
|
+
titleEl
|
|
215
|
+
.on('click', () => onClickItem(titleLineNumber))
|
|
216
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
217
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Initializes a D3 chart: clears existing content, creates SVG, resolves palette colors.
|
|
224
|
+
* Returns null if the container has zero dimensions.
|
|
225
|
+
*/
|
|
226
|
+
function initD3Chart(
|
|
227
|
+
container: HTMLDivElement,
|
|
228
|
+
palette: PaletteColors,
|
|
229
|
+
exportDims?: D3ExportDimensions
|
|
230
|
+
): { svg: d3Selection.Selection<SVGSVGElement, unknown, null, undefined>; width: number; height: number; textColor: string; mutedColor: string; bgColor: string; colors: string[] } | null {
|
|
231
|
+
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
232
|
+
const width = exportDims?.width ?? container.clientWidth;
|
|
233
|
+
const height = exportDims?.height ?? container.clientHeight;
|
|
234
|
+
if (width <= 0 || height <= 0) return null;
|
|
235
|
+
const textColor = palette.text;
|
|
236
|
+
const mutedColor = palette.border;
|
|
237
|
+
const bgColor = palette.bg;
|
|
238
|
+
const colors = getSeriesColors(palette);
|
|
239
|
+
const svg = d3Selection.select(container).append('svg').attr('width', width).attr('height', height).style('background', bgColor);
|
|
240
|
+
return { svg, width, height, textColor, mutedColor, bgColor, colors };
|
|
241
|
+
}
|
|
242
|
+
|
|
185
243
|
// ============================================================
|
|
186
244
|
// Timeline Date Helper
|
|
187
245
|
// ============================================================
|
|
@@ -1077,10 +1135,12 @@ function tokenizeFreeformText(text: string): WordCloudWord[] {
|
|
|
1077
1135
|
/**
|
|
1078
1136
|
* Resolves vertical label collisions by nudging overlapping items apart.
|
|
1079
1137
|
* Takes items with a naturalY (center) and height, returns adjusted center Y positions.
|
|
1138
|
+
* Optional maxY clamps the bottom edge so labels don't overflow the chart area.
|
|
1080
1139
|
*/
|
|
1081
|
-
function resolveVerticalCollisions(
|
|
1140
|
+
export function resolveVerticalCollisions(
|
|
1082
1141
|
items: { naturalY: number; height: number }[],
|
|
1083
|
-
minGap: number
|
|
1142
|
+
minGap: number,
|
|
1143
|
+
maxY?: number
|
|
1084
1144
|
): number[] {
|
|
1085
1145
|
if (items.length === 0) return [];
|
|
1086
1146
|
const sorted = items
|
|
@@ -1090,7 +1150,11 @@ function resolveVerticalCollisions(
|
|
|
1090
1150
|
let prevBottom = -Infinity;
|
|
1091
1151
|
for (const item of sorted) {
|
|
1092
1152
|
const halfH = item.height / 2;
|
|
1093
|
-
|
|
1153
|
+
let top = Math.max(item.naturalY - halfH, prevBottom + minGap);
|
|
1154
|
+
// Clamp so the label bottom doesn't exceed maxY
|
|
1155
|
+
if (maxY !== undefined) {
|
|
1156
|
+
top = Math.min(top, maxY - item.height);
|
|
1157
|
+
}
|
|
1094
1158
|
adjustedY[item.idx] = top + halfH;
|
|
1095
1159
|
prevBottom = top + item.height;
|
|
1096
1160
|
}
|
|
@@ -1112,15 +1176,12 @@ export function renderSlopeChart(
|
|
|
1112
1176
|
onClickItem?: (lineNumber: number) => void,
|
|
1113
1177
|
exportDims?: D3ExportDimensions
|
|
1114
1178
|
): void {
|
|
1115
|
-
// Clear existing content
|
|
1116
|
-
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
1117
|
-
|
|
1118
1179
|
const { periods, data, title } = parsed;
|
|
1119
1180
|
if (data.length === 0 || periods.length < 2) return;
|
|
1120
1181
|
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
|
|
1182
|
+
const init = initD3Chart(container, palette, exportDims);
|
|
1183
|
+
if (!init) return;
|
|
1184
|
+
const { svg, width, height, textColor, mutedColor, bgColor, colors } = init;
|
|
1124
1185
|
|
|
1125
1186
|
// Compute right margin from the longest end-of-line label
|
|
1126
1187
|
const maxLabelText = data.reduce((longest, item) => {
|
|
@@ -1137,12 +1198,6 @@ export function renderSlopeChart(
|
|
|
1137
1198
|
const innerWidth = width - SLOPE_MARGIN.left - rightMargin;
|
|
1138
1199
|
const innerHeight = height - SLOPE_MARGIN.top - SLOPE_MARGIN.bottom;
|
|
1139
1200
|
|
|
1140
|
-
// Theme colors
|
|
1141
|
-
const textColor = palette.text;
|
|
1142
|
-
const mutedColor = palette.border;
|
|
1143
|
-
const bgColor = palette.bg;
|
|
1144
|
-
const colors = getSeriesColors(palette);
|
|
1145
|
-
|
|
1146
1201
|
// Scales
|
|
1147
1202
|
const allValues = data.flatMap((d) => d.values);
|
|
1148
1203
|
const [minVal, maxVal] = d3Array.extent(allValues) as [number, number];
|
|
@@ -1159,14 +1214,6 @@ export function renderSlopeChart(
|
|
|
1159
1214
|
.range([0, innerWidth])
|
|
1160
1215
|
.padding(0);
|
|
1161
1216
|
|
|
1162
|
-
// SVG
|
|
1163
|
-
const svg = d3Selection
|
|
1164
|
-
.select(container)
|
|
1165
|
-
.append('svg')
|
|
1166
|
-
.attr('width', width)
|
|
1167
|
-
.attr('height', height)
|
|
1168
|
-
.style('background', bgColor);
|
|
1169
|
-
|
|
1170
1217
|
const g = svg
|
|
1171
1218
|
.append('g')
|
|
1172
1219
|
.attr('transform', `translate(${SLOPE_MARGIN.left},${SLOPE_MARGIN.top})`);
|
|
@@ -1175,29 +1222,7 @@ export function renderSlopeChart(
|
|
|
1175
1222
|
const tooltip = createTooltip(container, palette, isDark);
|
|
1176
1223
|
|
|
1177
1224
|
// Title
|
|
1178
|
-
|
|
1179
|
-
const titleEl = svg
|
|
1180
|
-
.append('text')
|
|
1181
|
-
.attr('class', 'chart-title')
|
|
1182
|
-
.attr('x', width / 2)
|
|
1183
|
-
.attr('y', 30)
|
|
1184
|
-
.attr('text-anchor', 'middle')
|
|
1185
|
-
.attr('fill', textColor)
|
|
1186
|
-
.attr('font-size', '20px')
|
|
1187
|
-
.attr('font-weight', '700')
|
|
1188
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
1189
|
-
.text(title);
|
|
1190
|
-
|
|
1191
|
-
if (parsed.titleLineNumber) {
|
|
1192
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
1193
|
-
if (onClickItem) {
|
|
1194
|
-
titleEl
|
|
1195
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
1196
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
1197
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1225
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
1201
1226
|
|
|
1202
1227
|
// Period column headers
|
|
1203
1228
|
for (const period of periods) {
|
|
@@ -1284,7 +1309,7 @@ export function renderSlopeChart(
|
|
|
1284
1309
|
naturalY: yScale(item.values[pi]),
|
|
1285
1310
|
height: leftLabelHeight,
|
|
1286
1311
|
}));
|
|
1287
|
-
leftLabelCollisions.set(pi, resolveVerticalCollisions(entries, 4));
|
|
1312
|
+
leftLabelCollisions.set(pi, resolveVerticalCollisions(entries, 4, innerHeight));
|
|
1288
1313
|
}
|
|
1289
1314
|
|
|
1290
1315
|
// --- Resolve right-side label collisions ---
|
|
@@ -1292,7 +1317,7 @@ export function renderSlopeChart(
|
|
|
1292
1317
|
naturalY: yScale(si.lastVal),
|
|
1293
1318
|
height: Math.max(si.labelHeight, SLOPE_LABEL_FONT_SIZE * 1.4),
|
|
1294
1319
|
}));
|
|
1295
|
-
const rightAdjustedY = resolveVerticalCollisions(rightEntries, 4);
|
|
1320
|
+
const rightAdjustedY = resolveVerticalCollisions(rightEntries, 4, innerHeight);
|
|
1296
1321
|
|
|
1297
1322
|
// Render each data series
|
|
1298
1323
|
data.forEach((item, idx) => {
|
|
@@ -1540,14 +1565,12 @@ export function renderArcDiagram(
|
|
|
1540
1565
|
onClickItem?: (lineNumber: number) => void,
|
|
1541
1566
|
exportDims?: D3ExportDimensions
|
|
1542
1567
|
): void {
|
|
1543
|
-
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
1544
|
-
|
|
1545
1568
|
const { links, title, orientation, arcOrder, arcNodeGroups } = parsed;
|
|
1546
1569
|
if (links.length === 0) return;
|
|
1547
1570
|
|
|
1548
|
-
const
|
|
1549
|
-
|
|
1550
|
-
|
|
1571
|
+
const init = initD3Chart(container, palette, exportDims);
|
|
1572
|
+
if (!init) return;
|
|
1573
|
+
const { svg, width, height, textColor, mutedColor, bgColor, colors } = init;
|
|
1551
1574
|
|
|
1552
1575
|
const isVertical = orientation === 'vertical';
|
|
1553
1576
|
const margin = isVertical
|
|
@@ -1562,12 +1585,6 @@ export function renderArcDiagram(
|
|
|
1562
1585
|
const innerWidth = width - margin.left - margin.right;
|
|
1563
1586
|
const innerHeight = height - margin.top - margin.bottom;
|
|
1564
1587
|
|
|
1565
|
-
// Theme colors
|
|
1566
|
-
const textColor = palette.text;
|
|
1567
|
-
const mutedColor = palette.border;
|
|
1568
|
-
const bgColor = palette.bg;
|
|
1569
|
-
const colors = getSeriesColors(palette);
|
|
1570
|
-
|
|
1571
1588
|
// Order nodes by selected strategy
|
|
1572
1589
|
const nodes = orderArcNodes(links, arcOrder, arcNodeGroups);
|
|
1573
1590
|
|
|
@@ -1597,42 +1614,12 @@ export function renderArcDiagram(
|
|
|
1597
1614
|
.domain([minVal, maxVal])
|
|
1598
1615
|
.range([1.5, 6]);
|
|
1599
1616
|
|
|
1600
|
-
// SVG
|
|
1601
|
-
const svg = d3Selection
|
|
1602
|
-
.select(container)
|
|
1603
|
-
.append('svg')
|
|
1604
|
-
.attr('width', width)
|
|
1605
|
-
.attr('height', height)
|
|
1606
|
-
.style('background', bgColor);
|
|
1607
|
-
|
|
1608
1617
|
const g = svg
|
|
1609
1618
|
.append('g')
|
|
1610
1619
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
1611
1620
|
|
|
1612
1621
|
// Title
|
|
1613
|
-
|
|
1614
|
-
const titleEl = svg
|
|
1615
|
-
.append('text')
|
|
1616
|
-
.attr('class', 'chart-title')
|
|
1617
|
-
.attr('x', width / 2)
|
|
1618
|
-
.attr('y', 30)
|
|
1619
|
-
.attr('text-anchor', 'middle')
|
|
1620
|
-
.attr('fill', textColor)
|
|
1621
|
-
.attr('font-size', '20px')
|
|
1622
|
-
.attr('font-weight', '700')
|
|
1623
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
1624
|
-
.text(title);
|
|
1625
|
-
|
|
1626
|
-
if (parsed.titleLineNumber) {
|
|
1627
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
1628
|
-
if (onClickItem) {
|
|
1629
|
-
titleEl
|
|
1630
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
1631
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
1632
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1622
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
1636
1623
|
|
|
1637
1624
|
// Build adjacency map for hover interactions
|
|
1638
1625
|
const neighbors = new Map<string, Set<string>>();
|
|
@@ -2871,29 +2858,7 @@ export function renderTimeline(
|
|
|
2871
2858
|
.append('g')
|
|
2872
2859
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
2873
2860
|
|
|
2874
|
-
|
|
2875
|
-
const titleEl = svg
|
|
2876
|
-
.append('text')
|
|
2877
|
-
.attr('class', 'chart-title')
|
|
2878
|
-
.attr('x', width / 2)
|
|
2879
|
-
.attr('y', 30)
|
|
2880
|
-
.attr('text-anchor', 'middle')
|
|
2881
|
-
.attr('fill', textColor)
|
|
2882
|
-
.attr('font-size', '20px')
|
|
2883
|
-
.attr('font-weight', '700')
|
|
2884
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
2885
|
-
.text(title);
|
|
2886
|
-
|
|
2887
|
-
if (parsed.titleLineNumber) {
|
|
2888
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
2889
|
-
if (onClickItem) {
|
|
2890
|
-
titleEl
|
|
2891
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
2892
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
2893
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
2894
|
-
}
|
|
2895
|
-
}
|
|
2896
|
-
}
|
|
2861
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
2897
2862
|
|
|
2898
2863
|
renderEras(
|
|
2899
2864
|
g,
|
|
@@ -3103,29 +3068,7 @@ export function renderTimeline(
|
|
|
3103
3068
|
.append('g')
|
|
3104
3069
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
3105
3070
|
|
|
3106
|
-
|
|
3107
|
-
const titleEl = svg
|
|
3108
|
-
.append('text')
|
|
3109
|
-
.attr('class', 'chart-title')
|
|
3110
|
-
.attr('x', width / 2)
|
|
3111
|
-
.attr('y', 30)
|
|
3112
|
-
.attr('text-anchor', 'middle')
|
|
3113
|
-
.attr('fill', textColor)
|
|
3114
|
-
.attr('font-size', '20px')
|
|
3115
|
-
.attr('font-weight', '700')
|
|
3116
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3117
|
-
.text(title);
|
|
3118
|
-
|
|
3119
|
-
if (parsed.titleLineNumber) {
|
|
3120
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3121
|
-
if (onClickItem) {
|
|
3122
|
-
titleEl
|
|
3123
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3124
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3125
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3126
|
-
}
|
|
3127
|
-
}
|
|
3128
|
-
}
|
|
3071
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
3129
3072
|
|
|
3130
3073
|
renderEras(
|
|
3131
3074
|
g,
|
|
@@ -3396,29 +3339,7 @@ export function renderTimeline(
|
|
|
3396
3339
|
.append('g')
|
|
3397
3340
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
3398
3341
|
|
|
3399
|
-
|
|
3400
|
-
const titleEl = svg
|
|
3401
|
-
.append('text')
|
|
3402
|
-
.attr('class', 'chart-title')
|
|
3403
|
-
.attr('x', width / 2)
|
|
3404
|
-
.attr('y', 30)
|
|
3405
|
-
.attr('text-anchor', 'middle')
|
|
3406
|
-
.attr('fill', textColor)
|
|
3407
|
-
.attr('font-size', '20px')
|
|
3408
|
-
.attr('font-weight', '700')
|
|
3409
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3410
|
-
.text(title);
|
|
3411
|
-
|
|
3412
|
-
if (parsed.titleLineNumber) {
|
|
3413
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3414
|
-
if (onClickItem) {
|
|
3415
|
-
titleEl
|
|
3416
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3417
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3418
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3419
|
-
}
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3342
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
3422
3343
|
|
|
3423
3344
|
renderEras(
|
|
3424
3345
|
g,
|
|
@@ -3693,29 +3614,7 @@ export function renderTimeline(
|
|
|
3693
3614
|
.append('g')
|
|
3694
3615
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
3695
3616
|
|
|
3696
|
-
|
|
3697
|
-
const titleEl = svg
|
|
3698
|
-
.append('text')
|
|
3699
|
-
.attr('class', 'chart-title')
|
|
3700
|
-
.attr('x', width / 2)
|
|
3701
|
-
.attr('y', 30)
|
|
3702
|
-
.attr('text-anchor', 'middle')
|
|
3703
|
-
.attr('fill', textColor)
|
|
3704
|
-
.attr('font-size', '20px')
|
|
3705
|
-
.attr('font-weight', '700')
|
|
3706
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3707
|
-
.text(title);
|
|
3708
|
-
|
|
3709
|
-
if (parsed.titleLineNumber) {
|
|
3710
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3711
|
-
if (onClickItem) {
|
|
3712
|
-
titleEl
|
|
3713
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3714
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3715
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3716
|
-
}
|
|
3717
|
-
}
|
|
3718
|
-
}
|
|
3617
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
3719
3618
|
|
|
3720
3619
|
renderEras(
|
|
3721
3620
|
g,
|
|
@@ -3965,22 +3864,16 @@ export function renderWordCloud(
|
|
|
3965
3864
|
onClickItem?: (lineNumber: number) => void,
|
|
3966
3865
|
exportDims?: D3ExportDimensions
|
|
3967
3866
|
): void {
|
|
3968
|
-
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
3969
|
-
|
|
3970
3867
|
const { words, title, cloudOptions } = parsed;
|
|
3971
3868
|
if (words.length === 0) return;
|
|
3972
3869
|
|
|
3973
|
-
const
|
|
3974
|
-
|
|
3975
|
-
|
|
3870
|
+
const init = initD3Chart(container, palette, exportDims);
|
|
3871
|
+
if (!init) return;
|
|
3872
|
+
const { svg, width, height, textColor, colors } = init;
|
|
3976
3873
|
|
|
3977
3874
|
const titleHeight = title ? 40 : 0;
|
|
3978
3875
|
const cloudHeight = height - titleHeight;
|
|
3979
3876
|
|
|
3980
|
-
const textColor = palette.text;
|
|
3981
|
-
const bgColor = palette.bg;
|
|
3982
|
-
const colors = getSeriesColors(palette);
|
|
3983
|
-
|
|
3984
3877
|
const { minSize, maxSize } = cloudOptions;
|
|
3985
3878
|
const weights = words.map((w) => w.weight);
|
|
3986
3879
|
const minWeight = Math.min(...weights);
|
|
@@ -3994,36 +3887,7 @@ export function renderWordCloud(
|
|
|
3994
3887
|
|
|
3995
3888
|
const rotateFn = getRotateFn(cloudOptions.rotate);
|
|
3996
3889
|
|
|
3997
|
-
|
|
3998
|
-
.select(container)
|
|
3999
|
-
.append('svg')
|
|
4000
|
-
.attr('width', width)
|
|
4001
|
-
.attr('height', height)
|
|
4002
|
-
.style('background', bgColor);
|
|
4003
|
-
|
|
4004
|
-
if (title) {
|
|
4005
|
-
const titleEl = svg
|
|
4006
|
-
.append('text')
|
|
4007
|
-
.attr('class', 'chart-title')
|
|
4008
|
-
.attr('x', width / 2)
|
|
4009
|
-
.attr('y', 30)
|
|
4010
|
-
.attr('text-anchor', 'middle')
|
|
4011
|
-
.attr('fill', textColor)
|
|
4012
|
-
.attr('font-size', '20px')
|
|
4013
|
-
.attr('font-weight', '700')
|
|
4014
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
4015
|
-
.text(title);
|
|
4016
|
-
|
|
4017
|
-
if (parsed.titleLineNumber) {
|
|
4018
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
4019
|
-
if (onClickItem) {
|
|
4020
|
-
titleEl
|
|
4021
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
4022
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
4023
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
4024
|
-
}
|
|
4025
|
-
}
|
|
4026
|
-
}
|
|
3890
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
4027
3891
|
|
|
4028
3892
|
const g = svg
|
|
4029
3893
|
.append('g')
|
|
@@ -4122,22 +3986,7 @@ function renderWordCloudAsync(
|
|
|
4122
3986
|
.attr('height', height)
|
|
4123
3987
|
.style('background', bgColor);
|
|
4124
3988
|
|
|
4125
|
-
|
|
4126
|
-
const titleEl = svg
|
|
4127
|
-
.append('text')
|
|
4128
|
-
.attr('class', 'chart-title')
|
|
4129
|
-
.attr('x', width / 2)
|
|
4130
|
-
.attr('y', 30)
|
|
4131
|
-
.attr('text-anchor', 'middle')
|
|
4132
|
-
.attr('fill', textColor)
|
|
4133
|
-
.attr('font-size', '20px')
|
|
4134
|
-
.attr('font-weight', '700')
|
|
4135
|
-
.text(title);
|
|
4136
|
-
|
|
4137
|
-
if (parsed.titleLineNumber) {
|
|
4138
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
4139
|
-
}
|
|
4140
|
-
}
|
|
3989
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor);
|
|
4141
3990
|
|
|
4142
3991
|
const g = svg
|
|
4143
3992
|
.append('g')
|
|
@@ -4359,18 +4208,12 @@ export function renderVenn(
|
|
|
4359
4208
|
onClickItem?: (lineNumber: number) => void,
|
|
4360
4209
|
exportDims?: D3ExportDimensions
|
|
4361
4210
|
): void {
|
|
4362
|
-
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
4363
|
-
|
|
4364
4211
|
const { vennSets, vennOverlaps, vennShowValues, title } = parsed;
|
|
4365
4212
|
if (vennSets.length < 2) return;
|
|
4366
4213
|
|
|
4367
|
-
const
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
const textColor = palette.text;
|
|
4372
|
-
const bgColor = palette.bg;
|
|
4373
|
-
const colors = getSeriesColors(palette);
|
|
4214
|
+
const init = initD3Chart(container, palette, exportDims);
|
|
4215
|
+
if (!init) return;
|
|
4216
|
+
const { svg, width, height, textColor, colors } = init;
|
|
4374
4217
|
const titleHeight = title ? 40 : 0;
|
|
4375
4218
|
|
|
4376
4219
|
// Compute radii
|
|
@@ -4477,41 +4320,11 @@ export function renderVenn(
|
|
|
4477
4320
|
marginBottom
|
|
4478
4321
|
).map((c) => ({ ...c, y: c.y + titleHeight }));
|
|
4479
4322
|
|
|
4480
|
-
// SVG
|
|
4481
|
-
const svg = d3Selection
|
|
4482
|
-
.select(container)
|
|
4483
|
-
.append('svg')
|
|
4484
|
-
.attr('width', width)
|
|
4485
|
-
.attr('height', height)
|
|
4486
|
-
.style('background', bgColor);
|
|
4487
|
-
|
|
4488
4323
|
// Tooltip
|
|
4489
4324
|
const tooltip = createTooltip(container, palette, isDark);
|
|
4490
4325
|
|
|
4491
4326
|
// Title
|
|
4492
|
-
|
|
4493
|
-
const titleEl = svg
|
|
4494
|
-
.append('text')
|
|
4495
|
-
.attr('class', 'chart-title')
|
|
4496
|
-
.attr('x', width / 2)
|
|
4497
|
-
.attr('y', 30)
|
|
4498
|
-
.attr('text-anchor', 'middle')
|
|
4499
|
-
.attr('fill', textColor)
|
|
4500
|
-
.attr('font-size', '20px')
|
|
4501
|
-
.attr('font-weight', '700')
|
|
4502
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
4503
|
-
.text(title);
|
|
4504
|
-
|
|
4505
|
-
if (parsed.titleLineNumber) {
|
|
4506
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
4507
|
-
if (onClickItem) {
|
|
4508
|
-
titleEl
|
|
4509
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
4510
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
4511
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
4512
|
-
}
|
|
4513
|
-
}
|
|
4514
|
-
}
|
|
4327
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
4515
4328
|
|
|
4516
4329
|
// ── Semi-transparent filled circles ──
|
|
4517
4330
|
const circleEls: d3Selection.Selection<SVGCircleElement, unknown, null, undefined>[] = [];
|
|
@@ -4784,8 +4597,6 @@ export function renderQuadrant(
|
|
|
4784
4597
|
onClickItem?: (lineNumber: number) => void,
|
|
4785
4598
|
exportDims?: D3ExportDimensions
|
|
4786
4599
|
): void {
|
|
4787
|
-
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
4788
|
-
|
|
4789
4600
|
const {
|
|
4790
4601
|
title,
|
|
4791
4602
|
quadrantLabels,
|
|
@@ -4799,13 +4610,10 @@ export function renderQuadrant(
|
|
|
4799
4610
|
|
|
4800
4611
|
if (quadrantPoints.length === 0) return;
|
|
4801
4612
|
|
|
4802
|
-
const
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
const textColor = palette.text;
|
|
4613
|
+
const init = initD3Chart(container, palette, exportDims);
|
|
4614
|
+
if (!init) return;
|
|
4615
|
+
const { svg, width, height, textColor } = init;
|
|
4807
4616
|
const mutedColor = palette.textMuted;
|
|
4808
|
-
const bgColor = palette.bg;
|
|
4809
4617
|
const borderColor = palette.border;
|
|
4810
4618
|
|
|
4811
4619
|
// Default quadrant colors with alpha
|
|
@@ -4827,49 +4635,11 @@ export function renderQuadrant(
|
|
|
4827
4635
|
const xScale = d3Scale.scaleLinear().domain([0, 1]).range([0, chartWidth]);
|
|
4828
4636
|
const yScale = d3Scale.scaleLinear().domain([0, 1]).range([chartHeight, 0]);
|
|
4829
4637
|
|
|
4830
|
-
// Create SVG
|
|
4831
|
-
const svg = d3Selection
|
|
4832
|
-
.select(container)
|
|
4833
|
-
.append('svg')
|
|
4834
|
-
.attr('width', width)
|
|
4835
|
-
.attr('height', height)
|
|
4836
|
-
.style('background', bgColor);
|
|
4837
|
-
|
|
4838
4638
|
// Tooltip
|
|
4839
4639
|
const tooltip = createTooltip(container, palette, isDark);
|
|
4840
4640
|
|
|
4841
4641
|
// Title
|
|
4842
|
-
|
|
4843
|
-
const titleText = svg
|
|
4844
|
-
.append('text')
|
|
4845
|
-
.attr('class', 'chart-title')
|
|
4846
|
-
.attr('x', width / 2)
|
|
4847
|
-
.attr('y', 30)
|
|
4848
|
-
.attr('text-anchor', 'middle')
|
|
4849
|
-
.attr('fill', textColor)
|
|
4850
|
-
.attr('font-size', '20px')
|
|
4851
|
-
.attr('font-weight', '700')
|
|
4852
|
-
.style(
|
|
4853
|
-
'cursor',
|
|
4854
|
-
onClickItem && quadrantTitleLineNumber ? 'pointer' : 'default'
|
|
4855
|
-
)
|
|
4856
|
-
.text(title);
|
|
4857
|
-
|
|
4858
|
-
if (quadrantTitleLineNumber) {
|
|
4859
|
-
titleText.attr('data-line-number', quadrantTitleLineNumber);
|
|
4860
|
-
}
|
|
4861
|
-
|
|
4862
|
-
if (onClickItem && quadrantTitleLineNumber) {
|
|
4863
|
-
titleText
|
|
4864
|
-
.on('click', () => onClickItem(quadrantTitleLineNumber))
|
|
4865
|
-
.on('mouseenter', function () {
|
|
4866
|
-
d3Selection.select(this).attr('opacity', 0.7);
|
|
4867
|
-
})
|
|
4868
|
-
.on('mouseleave', function () {
|
|
4869
|
-
d3Selection.select(this).attr('opacity', 1);
|
|
4870
|
-
});
|
|
4871
|
-
}
|
|
4872
|
-
}
|
|
4642
|
+
renderChartTitle(svg, title, quadrantTitleLineNumber, width, textColor, onClickItem);
|
|
4873
4643
|
|
|
4874
4644
|
// Chart group (translated by margins)
|
|
4875
4645
|
const chartG = svg
|
|
@@ -5327,6 +5097,55 @@ export function renderQuadrant(
|
|
|
5327
5097
|
const EXPORT_WIDTH = 1200;
|
|
5328
5098
|
const EXPORT_HEIGHT = 800;
|
|
5329
5099
|
|
|
5100
|
+
/**
|
|
5101
|
+
* Resolves the palette for export, falling back to Nord light/dark.
|
|
5102
|
+
*/
|
|
5103
|
+
async function resolveExportPalette(theme: string, palette?: PaletteColors): Promise<PaletteColors> {
|
|
5104
|
+
if (palette) return palette;
|
|
5105
|
+
const { getPalette } = await import('./palettes');
|
|
5106
|
+
return theme === 'dark' ? getPalette('nord').dark : getPalette('nord').light;
|
|
5107
|
+
}
|
|
5108
|
+
|
|
5109
|
+
/**
|
|
5110
|
+
* Creates an offscreen container for export rendering.
|
|
5111
|
+
*/
|
|
5112
|
+
function createExportContainer(width: number, height: number): HTMLDivElement {
|
|
5113
|
+
const container = document.createElement('div');
|
|
5114
|
+
container.style.width = `${width}px`;
|
|
5115
|
+
container.style.height = `${height}px`;
|
|
5116
|
+
container.style.position = 'absolute';
|
|
5117
|
+
container.style.left = '-9999px';
|
|
5118
|
+
document.body.appendChild(container);
|
|
5119
|
+
return container;
|
|
5120
|
+
}
|
|
5121
|
+
|
|
5122
|
+
/**
|
|
5123
|
+
* Extracts the SVG from a container, applies common export styling, and cleans up.
|
|
5124
|
+
*/
|
|
5125
|
+
function finalizeSvgExport(
|
|
5126
|
+
container: HTMLDivElement,
|
|
5127
|
+
theme: string,
|
|
5128
|
+
palette: PaletteColors,
|
|
5129
|
+
options?: { branding?: boolean }
|
|
5130
|
+
): string {
|
|
5131
|
+
const svgEl = container.querySelector('svg');
|
|
5132
|
+
if (!svgEl) return '';
|
|
5133
|
+
if (theme === 'transparent') {
|
|
5134
|
+
svgEl.style.background = 'none';
|
|
5135
|
+
} else if (!svgEl.style.background) {
|
|
5136
|
+
svgEl.style.background = palette.bg;
|
|
5137
|
+
}
|
|
5138
|
+
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5139
|
+
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5140
|
+
const svgHtml = svgEl.outerHTML;
|
|
5141
|
+
document.body.removeChild(container);
|
|
5142
|
+
if (options?.branding !== false) {
|
|
5143
|
+
const brandColor = theme === 'transparent' ? '#888' : palette.textMuted;
|
|
5144
|
+
return injectBranding(svgHtml, brandColor);
|
|
5145
|
+
}
|
|
5146
|
+
return svgHtml;
|
|
5147
|
+
}
|
|
5148
|
+
|
|
5330
5149
|
/**
|
|
5331
5150
|
* Renders a D3 chart to an SVG string for export.
|
|
5332
5151
|
* Creates a detached DOM element, renders into it, extracts the SVG, then cleans up.
|
|
@@ -5353,9 +5172,7 @@ export async function renderD3ForExport(
|
|
|
5353
5172
|
const { renderOrg } = await import('./org/renderer');
|
|
5354
5173
|
|
|
5355
5174
|
const isDark = theme === 'dark';
|
|
5356
|
-
const
|
|
5357
|
-
const effectivePalette =
|
|
5358
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5175
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5359
5176
|
|
|
5360
5177
|
const orgParsed = parseOrg(content, effectivePalette);
|
|
5361
5178
|
if (orgParsed.error) return '';
|
|
@@ -5377,96 +5194,32 @@ export async function renderD3ForExport(
|
|
|
5377
5194
|
hiddenAttributes
|
|
5378
5195
|
);
|
|
5379
5196
|
|
|
5380
|
-
// Size container to fit the diagram content
|
|
5381
5197
|
const PADDING = 20;
|
|
5382
5198
|
const titleOffset = effectiveParsed.title ? 30 : 0;
|
|
5383
5199
|
const exportWidth = orgLayout.width + PADDING * 2;
|
|
5384
5200
|
const exportHeight = orgLayout.height + PADDING * 2 + titleOffset;
|
|
5201
|
+
const container = createExportContainer(exportWidth, exportHeight);
|
|
5385
5202
|
|
|
5386
|
-
|
|
5387
|
-
container
|
|
5388
|
-
container.style.height = `${exportHeight}px`;
|
|
5389
|
-
container.style.position = 'absolute';
|
|
5390
|
-
container.style.left = '-9999px';
|
|
5391
|
-
document.body.appendChild(container);
|
|
5392
|
-
|
|
5393
|
-
try {
|
|
5394
|
-
renderOrg(
|
|
5395
|
-
container,
|
|
5396
|
-
effectiveParsed,
|
|
5397
|
-
orgLayout,
|
|
5398
|
-
effectivePalette,
|
|
5399
|
-
isDark,
|
|
5400
|
-
undefined,
|
|
5401
|
-
{ width: exportWidth, height: exportHeight },
|
|
5402
|
-
activeTagGroup,
|
|
5403
|
-
hiddenAttributes
|
|
5404
|
-
);
|
|
5405
|
-
|
|
5406
|
-
const svgEl = container.querySelector('svg');
|
|
5407
|
-
if (!svgEl) return '';
|
|
5408
|
-
|
|
5409
|
-
if (theme === 'transparent') {
|
|
5410
|
-
svgEl.style.background = 'none';
|
|
5411
|
-
} else if (!svgEl.style.background) {
|
|
5412
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5413
|
-
}
|
|
5414
|
-
|
|
5415
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5416
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5417
|
-
|
|
5418
|
-
const svgHtml = svgEl.outerHTML;
|
|
5419
|
-
if (options?.branding !== false) {
|
|
5420
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5421
|
-
return injectBranding(svgHtml, brandColor);
|
|
5422
|
-
}
|
|
5423
|
-
return svgHtml;
|
|
5424
|
-
} finally {
|
|
5425
|
-
document.body.removeChild(container);
|
|
5426
|
-
}
|
|
5203
|
+
renderOrg(container, effectiveParsed, orgLayout, effectivePalette, isDark, undefined, { width: exportWidth, height: exportHeight }, activeTagGroup, hiddenAttributes);
|
|
5204
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5427
5205
|
}
|
|
5428
5206
|
|
|
5429
5207
|
if (detectedType === 'kanban') {
|
|
5430
5208
|
const { parseKanban } = await import('./kanban/parser');
|
|
5431
5209
|
const { renderKanban } = await import('./kanban/renderer');
|
|
5432
5210
|
|
|
5433
|
-
const
|
|
5434
|
-
const { getPalette } = await import('./palettes');
|
|
5435
|
-
const effectivePalette =
|
|
5436
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5437
|
-
|
|
5211
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5438
5212
|
const kanbanParsed = parseKanban(content, effectivePalette);
|
|
5439
5213
|
if (kanbanParsed.error || kanbanParsed.columns.length === 0) return '';
|
|
5440
5214
|
|
|
5215
|
+
// Kanban renderer self-sizes — no explicit width/height needed
|
|
5441
5216
|
const container = document.createElement('div');
|
|
5442
5217
|
container.style.position = 'absolute';
|
|
5443
5218
|
container.style.left = '-9999px';
|
|
5444
5219
|
document.body.appendChild(container);
|
|
5445
5220
|
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
const svgEl = container.querySelector('svg');
|
|
5450
|
-
if (!svgEl) return '';
|
|
5451
|
-
|
|
5452
|
-
if (theme === 'transparent') {
|
|
5453
|
-
svgEl.style.background = 'none';
|
|
5454
|
-
} else if (!svgEl.style.background) {
|
|
5455
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5456
|
-
}
|
|
5457
|
-
|
|
5458
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5459
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5460
|
-
|
|
5461
|
-
const svgHtml = svgEl.outerHTML;
|
|
5462
|
-
if (options?.branding !== false) {
|
|
5463
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5464
|
-
return injectBranding(svgHtml, brandColor);
|
|
5465
|
-
}
|
|
5466
|
-
return svgHtml;
|
|
5467
|
-
} finally {
|
|
5468
|
-
document.body.removeChild(container);
|
|
5469
|
-
}
|
|
5221
|
+
renderKanban(container, kanbanParsed, effectivePalette, theme === 'dark');
|
|
5222
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5470
5223
|
}
|
|
5471
5224
|
|
|
5472
5225
|
if (detectedType === 'class') {
|
|
@@ -5474,11 +5227,7 @@ export async function renderD3ForExport(
|
|
|
5474
5227
|
const { layoutClassDiagram } = await import('./class/layout');
|
|
5475
5228
|
const { renderClassDiagram } = await import('./class/renderer');
|
|
5476
5229
|
|
|
5477
|
-
const
|
|
5478
|
-
const { getPalette } = await import('./palettes');
|
|
5479
|
-
const effectivePalette =
|
|
5480
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5481
|
-
|
|
5230
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5482
5231
|
const classParsed = parseClassDiagram(content, effectivePalette);
|
|
5483
5232
|
if (classParsed.error || classParsed.classes.length === 0) return '';
|
|
5484
5233
|
|
|
@@ -5487,46 +5236,10 @@ export async function renderD3ForExport(
|
|
|
5487
5236
|
const titleOffset = classParsed.title ? 40 : 0;
|
|
5488
5237
|
const exportWidth = classLayout.width + PADDING * 2;
|
|
5489
5238
|
const exportHeight = classLayout.height + PADDING * 2 + titleOffset;
|
|
5239
|
+
const container = createExportContainer(exportWidth, exportHeight);
|
|
5490
5240
|
|
|
5491
|
-
|
|
5492
|
-
container
|
|
5493
|
-
container.style.height = `${exportHeight}px`;
|
|
5494
|
-
container.style.position = 'absolute';
|
|
5495
|
-
container.style.left = '-9999px';
|
|
5496
|
-
document.body.appendChild(container);
|
|
5497
|
-
|
|
5498
|
-
try {
|
|
5499
|
-
renderClassDiagram(
|
|
5500
|
-
container,
|
|
5501
|
-
classParsed,
|
|
5502
|
-
classLayout,
|
|
5503
|
-
effectivePalette,
|
|
5504
|
-
isDark,
|
|
5505
|
-
undefined,
|
|
5506
|
-
{ width: exportWidth, height: exportHeight }
|
|
5507
|
-
);
|
|
5508
|
-
|
|
5509
|
-
const svgEl = container.querySelector('svg');
|
|
5510
|
-
if (!svgEl) return '';
|
|
5511
|
-
|
|
5512
|
-
if (theme === 'transparent') {
|
|
5513
|
-
svgEl.style.background = 'none';
|
|
5514
|
-
} else if (!svgEl.style.background) {
|
|
5515
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5516
|
-
}
|
|
5517
|
-
|
|
5518
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5519
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5520
|
-
|
|
5521
|
-
const svgHtml = svgEl.outerHTML;
|
|
5522
|
-
if (options?.branding !== false) {
|
|
5523
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5524
|
-
return injectBranding(svgHtml, brandColor);
|
|
5525
|
-
}
|
|
5526
|
-
return svgHtml;
|
|
5527
|
-
} finally {
|
|
5528
|
-
document.body.removeChild(container);
|
|
5529
|
-
}
|
|
5241
|
+
renderClassDiagram(container, classParsed, classLayout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight });
|
|
5242
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5530
5243
|
}
|
|
5531
5244
|
|
|
5532
5245
|
if (detectedType === 'er') {
|
|
@@ -5534,11 +5247,7 @@ export async function renderD3ForExport(
|
|
|
5534
5247
|
const { layoutERDiagram } = await import('./er/layout');
|
|
5535
5248
|
const { renderERDiagram } = await import('./er/renderer');
|
|
5536
5249
|
|
|
5537
|
-
const
|
|
5538
|
-
const { getPalette } = await import('./palettes');
|
|
5539
|
-
const effectivePalette =
|
|
5540
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5541
|
-
|
|
5250
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5542
5251
|
const erParsed = parseERDiagram(content, effectivePalette);
|
|
5543
5252
|
if (erParsed.error || erParsed.tables.length === 0) return '';
|
|
5544
5253
|
|
|
@@ -5547,46 +5256,10 @@ export async function renderD3ForExport(
|
|
|
5547
5256
|
const titleOffset = erParsed.title ? 40 : 0;
|
|
5548
5257
|
const exportWidth = erLayout.width + PADDING * 2;
|
|
5549
5258
|
const exportHeight = erLayout.height + PADDING * 2 + titleOffset;
|
|
5259
|
+
const container = createExportContainer(exportWidth, exportHeight);
|
|
5550
5260
|
|
|
5551
|
-
|
|
5552
|
-
container
|
|
5553
|
-
container.style.height = `${exportHeight}px`;
|
|
5554
|
-
container.style.position = 'absolute';
|
|
5555
|
-
container.style.left = '-9999px';
|
|
5556
|
-
document.body.appendChild(container);
|
|
5557
|
-
|
|
5558
|
-
try {
|
|
5559
|
-
renderERDiagram(
|
|
5560
|
-
container,
|
|
5561
|
-
erParsed,
|
|
5562
|
-
erLayout,
|
|
5563
|
-
effectivePalette,
|
|
5564
|
-
isDark,
|
|
5565
|
-
undefined,
|
|
5566
|
-
{ width: exportWidth, height: exportHeight }
|
|
5567
|
-
);
|
|
5568
|
-
|
|
5569
|
-
const svgEl = container.querySelector('svg');
|
|
5570
|
-
if (!svgEl) return '';
|
|
5571
|
-
|
|
5572
|
-
if (theme === 'transparent') {
|
|
5573
|
-
svgEl.style.background = 'none';
|
|
5574
|
-
} else if (!svgEl.style.background) {
|
|
5575
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5576
|
-
}
|
|
5577
|
-
|
|
5578
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5579
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5580
|
-
|
|
5581
|
-
const svgHtml = svgEl.outerHTML;
|
|
5582
|
-
if (options?.branding !== false) {
|
|
5583
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5584
|
-
return injectBranding(svgHtml, brandColor);
|
|
5585
|
-
}
|
|
5586
|
-
return svgHtml;
|
|
5587
|
-
} finally {
|
|
5588
|
-
document.body.removeChild(container);
|
|
5589
|
-
}
|
|
5261
|
+
renderERDiagram(container, erParsed, erLayout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight });
|
|
5262
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5590
5263
|
}
|
|
5591
5264
|
|
|
5592
5265
|
if (detectedType === 'initiative-status') {
|
|
@@ -5594,11 +5267,7 @@ export async function renderD3ForExport(
|
|
|
5594
5267
|
const { layoutInitiativeStatus } = await import('./initiative-status/layout');
|
|
5595
5268
|
const { renderInitiativeStatus } = await import('./initiative-status/renderer');
|
|
5596
5269
|
|
|
5597
|
-
const
|
|
5598
|
-
const { getPalette } = await import('./palettes');
|
|
5599
|
-
const effectivePalette =
|
|
5600
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5601
|
-
|
|
5270
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5602
5271
|
const isParsed = parseInitiativeStatus(content);
|
|
5603
5272
|
if (isParsed.error || isParsed.nodes.length === 0) return '';
|
|
5604
5273
|
|
|
@@ -5607,46 +5276,10 @@ export async function renderD3ForExport(
|
|
|
5607
5276
|
const titleOffset = isParsed.title ? 40 : 0;
|
|
5608
5277
|
const exportWidth = isLayout.width + PADDING * 2;
|
|
5609
5278
|
const exportHeight = isLayout.height + PADDING * 2 + titleOffset;
|
|
5279
|
+
const container = createExportContainer(exportWidth, exportHeight);
|
|
5610
5280
|
|
|
5611
|
-
|
|
5612
|
-
container
|
|
5613
|
-
container.style.height = `${exportHeight}px`;
|
|
5614
|
-
container.style.position = 'absolute';
|
|
5615
|
-
container.style.left = '-9999px';
|
|
5616
|
-
document.body.appendChild(container);
|
|
5617
|
-
|
|
5618
|
-
try {
|
|
5619
|
-
renderInitiativeStatus(
|
|
5620
|
-
container,
|
|
5621
|
-
isParsed,
|
|
5622
|
-
isLayout,
|
|
5623
|
-
effectivePalette,
|
|
5624
|
-
isDark,
|
|
5625
|
-
undefined,
|
|
5626
|
-
{ width: exportWidth, height: exportHeight }
|
|
5627
|
-
);
|
|
5628
|
-
|
|
5629
|
-
const svgEl = container.querySelector('svg');
|
|
5630
|
-
if (!svgEl) return '';
|
|
5631
|
-
|
|
5632
|
-
if (theme === 'transparent') {
|
|
5633
|
-
svgEl.style.background = 'none';
|
|
5634
|
-
} else if (!svgEl.style.background) {
|
|
5635
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5636
|
-
}
|
|
5637
|
-
|
|
5638
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5639
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5640
|
-
|
|
5641
|
-
const svgHtml = svgEl.outerHTML;
|
|
5642
|
-
if (options?.branding !== false) {
|
|
5643
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5644
|
-
return injectBranding(svgHtml, brandColor);
|
|
5645
|
-
}
|
|
5646
|
-
return svgHtml;
|
|
5647
|
-
} finally {
|
|
5648
|
-
document.body.removeChild(container);
|
|
5649
|
-
}
|
|
5281
|
+
renderInitiativeStatus(container, isParsed, isLayout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight });
|
|
5282
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5650
5283
|
}
|
|
5651
5284
|
|
|
5652
5285
|
if (detectedType === 'c4') {
|
|
@@ -5654,11 +5287,7 @@ export async function renderD3ForExport(
|
|
|
5654
5287
|
const { layoutC4Context, layoutC4Containers, layoutC4Components, layoutC4Deployment } = await import('./c4/layout');
|
|
5655
5288
|
const { renderC4Context, renderC4Containers } = await import('./c4/renderer');
|
|
5656
5289
|
|
|
5657
|
-
const
|
|
5658
|
-
const { getPalette } = await import('./palettes');
|
|
5659
|
-
const effectivePalette =
|
|
5660
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5661
|
-
|
|
5290
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5662
5291
|
const c4Parsed = parseC4(content, effectivePalette);
|
|
5663
5292
|
if (c4Parsed.error || c4Parsed.elements.length === 0) return '';
|
|
5664
5293
|
|
|
@@ -5681,50 +5310,14 @@ export async function renderD3ForExport(
|
|
|
5681
5310
|
const titleOffset = c4Parsed.title ? 40 : 0;
|
|
5682
5311
|
const exportWidth = c4Layout.width + PADDING * 2;
|
|
5683
5312
|
const exportHeight = c4Layout.height + PADDING * 2 + titleOffset;
|
|
5313
|
+
const container = createExportContainer(exportWidth, exportHeight);
|
|
5684
5314
|
|
|
5685
|
-
const
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
container.style.position = 'absolute';
|
|
5689
|
-
container.style.left = '-9999px';
|
|
5690
|
-
document.body.appendChild(container);
|
|
5691
|
-
|
|
5692
|
-
try {
|
|
5693
|
-
const renderFn = c4Level === 'deployment' || (c4Level === 'components' && c4System && c4Container) || (c4Level === 'containers' && c4System)
|
|
5694
|
-
? renderC4Containers
|
|
5695
|
-
: renderC4Context;
|
|
5696
|
-
|
|
5697
|
-
renderFn(
|
|
5698
|
-
container,
|
|
5699
|
-
c4Parsed,
|
|
5700
|
-
c4Layout,
|
|
5701
|
-
effectivePalette,
|
|
5702
|
-
isDark,
|
|
5703
|
-
undefined,
|
|
5704
|
-
{ width: exportWidth, height: exportHeight }
|
|
5705
|
-
);
|
|
5706
|
-
|
|
5707
|
-
const svgEl = container.querySelector('svg');
|
|
5708
|
-
if (!svgEl) return '';
|
|
5709
|
-
|
|
5710
|
-
if (theme === 'transparent') {
|
|
5711
|
-
svgEl.style.background = 'none';
|
|
5712
|
-
} else if (!svgEl.style.background) {
|
|
5713
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5714
|
-
}
|
|
5715
|
-
|
|
5716
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5717
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5315
|
+
const renderFn = c4Level === 'deployment' || (c4Level === 'components' && c4System && c4Container) || (c4Level === 'containers' && c4System)
|
|
5316
|
+
? renderC4Containers
|
|
5317
|
+
: renderC4Context;
|
|
5718
5318
|
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5722
|
-
return injectBranding(svgHtml, brandColor);
|
|
5723
|
-
}
|
|
5724
|
-
return svgHtml;
|
|
5725
|
-
} finally {
|
|
5726
|
-
document.body.removeChild(container);
|
|
5727
|
-
}
|
|
5319
|
+
renderFn(container, c4Parsed, c4Layout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight });
|
|
5320
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5728
5321
|
}
|
|
5729
5322
|
|
|
5730
5323
|
if (detectedType === 'flowchart') {
|
|
@@ -5732,49 +5325,15 @@ export async function renderD3ForExport(
|
|
|
5732
5325
|
const { layoutGraph } = await import('./graph/layout');
|
|
5733
5326
|
const { renderFlowchart } = await import('./graph/flowchart-renderer');
|
|
5734
5327
|
|
|
5735
|
-
const
|
|
5736
|
-
const { getPalette } = await import('./palettes');
|
|
5737
|
-
const effectivePalette =
|
|
5738
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5739
|
-
|
|
5328
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5740
5329
|
const fcParsed = parseFlowchart(content, effectivePalette);
|
|
5741
5330
|
if (fcParsed.error || fcParsed.nodes.length === 0) return '';
|
|
5742
5331
|
|
|
5743
5332
|
const layout = layoutGraph(fcParsed);
|
|
5744
|
-
const container =
|
|
5745
|
-
container.style.width = `${EXPORT_WIDTH}px`;
|
|
5746
|
-
container.style.height = `${EXPORT_HEIGHT}px`;
|
|
5747
|
-
container.style.position = 'absolute';
|
|
5748
|
-
container.style.left = '-9999px';
|
|
5749
|
-
document.body.appendChild(container);
|
|
5750
|
-
|
|
5751
|
-
try {
|
|
5752
|
-
renderFlowchart(container, fcParsed, layout, effectivePalette, isDark, undefined, {
|
|
5753
|
-
width: EXPORT_WIDTH,
|
|
5754
|
-
height: EXPORT_HEIGHT,
|
|
5755
|
-
});
|
|
5756
|
-
|
|
5757
|
-
const svgEl = container.querySelector('svg');
|
|
5758
|
-
if (!svgEl) return '';
|
|
5759
|
-
|
|
5760
|
-
if (theme === 'transparent') {
|
|
5761
|
-
svgEl.style.background = 'none';
|
|
5762
|
-
} else if (!svgEl.style.background) {
|
|
5763
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5764
|
-
}
|
|
5765
|
-
|
|
5766
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5767
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5333
|
+
const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
|
|
5768
5334
|
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5772
|
-
return injectBranding(svgHtml, brandColor);
|
|
5773
|
-
}
|
|
5774
|
-
return svgHtml;
|
|
5775
|
-
} finally {
|
|
5776
|
-
document.body.removeChild(container);
|
|
5777
|
-
}
|
|
5335
|
+
renderFlowchart(container, fcParsed, layout, effectivePalette, theme === 'dark', undefined, { width: EXPORT_WIDTH, height: EXPORT_HEIGHT });
|
|
5336
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5778
5337
|
}
|
|
5779
5338
|
|
|
5780
5339
|
const parsed = parseD3(content, palette);
|
|
@@ -5796,67 +5355,32 @@ export async function renderD3ForExport(
|
|
|
5796
5355
|
if (parsed.type === 'quadrant' && parsed.quadrantPoints.length === 0)
|
|
5797
5356
|
return '';
|
|
5798
5357
|
|
|
5358
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5799
5359
|
const isDark = theme === 'dark';
|
|
5800
|
-
|
|
5801
|
-
// Fall back to Nord palette if none provided
|
|
5802
|
-
const { getPalette } = await import('./palettes');
|
|
5803
|
-
const effectivePalette =
|
|
5804
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5805
|
-
|
|
5806
|
-
// Create a temporary offscreen container
|
|
5807
|
-
const container = document.createElement('div');
|
|
5808
|
-
container.style.width = `${EXPORT_WIDTH}px`;
|
|
5809
|
-
container.style.height = `${EXPORT_HEIGHT}px`;
|
|
5810
|
-
container.style.position = 'absolute';
|
|
5811
|
-
container.style.left = '-9999px';
|
|
5812
|
-
document.body.appendChild(container);
|
|
5813
|
-
|
|
5360
|
+
const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
|
|
5814
5361
|
const dims: D3ExportDimensions = { width: EXPORT_WIDTH, height: EXPORT_HEIGHT };
|
|
5815
5362
|
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
renderSlopeChart(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
5837
|
-
}
|
|
5838
|
-
|
|
5839
|
-
const svgEl = container.querySelector('svg');
|
|
5840
|
-
if (!svgEl) return '';
|
|
5841
|
-
|
|
5842
|
-
// Ensure all chart types have a consistent background
|
|
5843
|
-
if (theme === 'transparent') {
|
|
5844
|
-
svgEl.style.background = 'none';
|
|
5845
|
-
} else if (!svgEl.style.background) {
|
|
5846
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5847
|
-
}
|
|
5848
|
-
|
|
5849
|
-
// Add xmlns for standalone SVG
|
|
5850
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5851
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5852
|
-
|
|
5853
|
-
const svgHtml = svgEl.outerHTML;
|
|
5854
|
-
if (options?.branding !== false) {
|
|
5855
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5856
|
-
return injectBranding(svgHtml, brandColor);
|
|
5857
|
-
}
|
|
5858
|
-
return svgHtml;
|
|
5859
|
-
} finally {
|
|
5860
|
-
document.body.removeChild(container);
|
|
5363
|
+
if (parsed.type === 'sequence') {
|
|
5364
|
+
const { parseSequenceDgmo } = await import('./sequence/parser');
|
|
5365
|
+
const { renderSequenceDiagram } = await import('./sequence/renderer');
|
|
5366
|
+
const seqParsed = parseSequenceDgmo(content);
|
|
5367
|
+
if (seqParsed.error || seqParsed.participants.length === 0) return '';
|
|
5368
|
+
renderSequenceDiagram(container, seqParsed, effectivePalette, isDark, undefined, {
|
|
5369
|
+
exportWidth: EXPORT_WIDTH,
|
|
5370
|
+
});
|
|
5371
|
+
} else if (parsed.type === 'wordcloud') {
|
|
5372
|
+
await renderWordCloudAsync(container, parsed, effectivePalette, isDark, dims);
|
|
5373
|
+
} else if (parsed.type === 'arc') {
|
|
5374
|
+
renderArcDiagram(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
5375
|
+
} else if (parsed.type === 'timeline') {
|
|
5376
|
+
renderTimeline(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
5377
|
+
} else if (parsed.type === 'venn') {
|
|
5378
|
+
renderVenn(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
5379
|
+
} else if (parsed.type === 'quadrant') {
|
|
5380
|
+
renderQuadrant(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
5381
|
+
} else {
|
|
5382
|
+
renderSlopeChart(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
5861
5383
|
}
|
|
5384
|
+
|
|
5385
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5862
5386
|
}
|