@diagrammo/dgmo 0.3.1 → 0.4.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/README.md +11 -14
- package/dist/cli.cjs +150 -151
- package/dist/index.cjs +460 -901
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -6
- package/dist/index.d.ts +4 -6
- package/dist/index.js +459 -901
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +18 -19
- package/package.json +1 -1
- package/src/chart.ts +8 -39
- package/src/cli.ts +6 -6
- package/src/d3.ts +299 -715
- package/src/dgmo-router.ts +21 -42
- package/src/echarts.ts +134 -241
- package/src/index.ts +1 -0
- package/src/sequence/parser.ts +55 -106
- package/src/sequence/renderer.ts +4 -60
- package/src/utils/arrows.ts +43 -18
- 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
|
// ============================================================
|
|
@@ -1074,6 +1132,35 @@ function tokenizeFreeformText(text: string): WordCloudWord[] {
|
|
|
1074
1132
|
// Slope Chart Renderer
|
|
1075
1133
|
// ============================================================
|
|
1076
1134
|
|
|
1135
|
+
/**
|
|
1136
|
+
* Resolves vertical label collisions by nudging overlapping items apart.
|
|
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.
|
|
1139
|
+
*/
|
|
1140
|
+
export function resolveVerticalCollisions(
|
|
1141
|
+
items: { naturalY: number; height: number }[],
|
|
1142
|
+
minGap: number,
|
|
1143
|
+
maxY?: number
|
|
1144
|
+
): number[] {
|
|
1145
|
+
if (items.length === 0) return [];
|
|
1146
|
+
const sorted = items
|
|
1147
|
+
.map((it, i) => ({ ...it, idx: i }))
|
|
1148
|
+
.sort((a, b) => a.naturalY - b.naturalY);
|
|
1149
|
+
const adjustedY = new Array<number>(items.length);
|
|
1150
|
+
let prevBottom = -Infinity;
|
|
1151
|
+
for (const item of sorted) {
|
|
1152
|
+
const halfH = item.height / 2;
|
|
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
|
+
}
|
|
1158
|
+
adjustedY[item.idx] = top + halfH;
|
|
1159
|
+
prevBottom = top + item.height;
|
|
1160
|
+
}
|
|
1161
|
+
return adjustedY;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1077
1164
|
const SLOPE_MARGIN = { top: 80, bottom: 40, left: 80 };
|
|
1078
1165
|
const SLOPE_LABEL_FONT_SIZE = 14;
|
|
1079
1166
|
const SLOPE_CHAR_WIDTH = 8; // approximate px per character at 14px
|
|
@@ -1089,15 +1176,12 @@ export function renderSlopeChart(
|
|
|
1089
1176
|
onClickItem?: (lineNumber: number) => void,
|
|
1090
1177
|
exportDims?: D3ExportDimensions
|
|
1091
1178
|
): void {
|
|
1092
|
-
// Clear existing content
|
|
1093
|
-
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
1094
|
-
|
|
1095
1179
|
const { periods, data, title } = parsed;
|
|
1096
1180
|
if (data.length === 0 || periods.length < 2) return;
|
|
1097
1181
|
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
1182
|
+
const init = initD3Chart(container, palette, exportDims);
|
|
1183
|
+
if (!init) return;
|
|
1184
|
+
const { svg, width, height, textColor, mutedColor, bgColor, colors } = init;
|
|
1101
1185
|
|
|
1102
1186
|
// Compute right margin from the longest end-of-line label
|
|
1103
1187
|
const maxLabelText = data.reduce((longest, item) => {
|
|
@@ -1114,12 +1198,6 @@ export function renderSlopeChart(
|
|
|
1114
1198
|
const innerWidth = width - SLOPE_MARGIN.left - rightMargin;
|
|
1115
1199
|
const innerHeight = height - SLOPE_MARGIN.top - SLOPE_MARGIN.bottom;
|
|
1116
1200
|
|
|
1117
|
-
// Theme colors
|
|
1118
|
-
const textColor = palette.text;
|
|
1119
|
-
const mutedColor = palette.border;
|
|
1120
|
-
const bgColor = palette.bg;
|
|
1121
|
-
const colors = getSeriesColors(palette);
|
|
1122
|
-
|
|
1123
1201
|
// Scales
|
|
1124
1202
|
const allValues = data.flatMap((d) => d.values);
|
|
1125
1203
|
const [minVal, maxVal] = d3Array.extent(allValues) as [number, number];
|
|
@@ -1136,14 +1214,6 @@ export function renderSlopeChart(
|
|
|
1136
1214
|
.range([0, innerWidth])
|
|
1137
1215
|
.padding(0);
|
|
1138
1216
|
|
|
1139
|
-
// SVG
|
|
1140
|
-
const svg = d3Selection
|
|
1141
|
-
.select(container)
|
|
1142
|
-
.append('svg')
|
|
1143
|
-
.attr('width', width)
|
|
1144
|
-
.attr('height', height)
|
|
1145
|
-
.style('background', bgColor);
|
|
1146
|
-
|
|
1147
1217
|
const g = svg
|
|
1148
1218
|
.append('g')
|
|
1149
1219
|
.attr('transform', `translate(${SLOPE_MARGIN.left},${SLOPE_MARGIN.top})`);
|
|
@@ -1152,29 +1222,7 @@ export function renderSlopeChart(
|
|
|
1152
1222
|
const tooltip = createTooltip(container, palette, isDark);
|
|
1153
1223
|
|
|
1154
1224
|
// Title
|
|
1155
|
-
|
|
1156
|
-
const titleEl = svg
|
|
1157
|
-
.append('text')
|
|
1158
|
-
.attr('class', 'chart-title')
|
|
1159
|
-
.attr('x', width / 2)
|
|
1160
|
-
.attr('y', 30)
|
|
1161
|
-
.attr('text-anchor', 'middle')
|
|
1162
|
-
.attr('fill', textColor)
|
|
1163
|
-
.attr('font-size', '20px')
|
|
1164
|
-
.attr('font-weight', '700')
|
|
1165
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
1166
|
-
.text(title);
|
|
1167
|
-
|
|
1168
|
-
if (parsed.titleLineNumber) {
|
|
1169
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
1170
|
-
if (onClickItem) {
|
|
1171
|
-
titleEl
|
|
1172
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
1173
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
1174
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1225
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
1178
1226
|
|
|
1179
1227
|
// Period column headers
|
|
1180
1228
|
for (const period of periods) {
|
|
@@ -1205,28 +1253,83 @@ export function renderSlopeChart(
|
|
|
1205
1253
|
.x((_d, i) => xScale(periods[i])!)
|
|
1206
1254
|
.y((d) => yScale(d));
|
|
1207
1255
|
|
|
1208
|
-
//
|
|
1209
|
-
data.
|
|
1256
|
+
// Pre-compute per-series data for label collision resolution
|
|
1257
|
+
const seriesInfo = data.map((item, idx) => {
|
|
1210
1258
|
const color = item.color ?? colors[idx % colors.length];
|
|
1211
|
-
|
|
1212
|
-
// Wrap each series in a group with data-line-number for sync adapter
|
|
1213
|
-
const seriesG = g
|
|
1214
|
-
.append('g')
|
|
1215
|
-
.attr('class', 'slope-series')
|
|
1216
|
-
.attr('data-line-number', String(item.lineNumber));
|
|
1217
|
-
|
|
1218
|
-
// Tooltip content – overall change for this series
|
|
1219
1259
|
const firstVal = item.values[0];
|
|
1220
1260
|
const lastVal = item.values[item.values.length - 1];
|
|
1221
1261
|
const absChange = lastVal - firstVal;
|
|
1222
1262
|
const pctChange = firstVal !== 0 ? (absChange / firstVal) * 100 : null;
|
|
1223
1263
|
const sign = absChange > 0 ? '+' : '';
|
|
1224
|
-
const
|
|
1225
|
-
pctChange !== null ? ` (${sign}${pctChange.toFixed(1)}%)` : '';
|
|
1226
|
-
const tipLines = [`${sign}${absChange}`];
|
|
1264
|
+
const tipLines = [`${sign}${parseFloat(absChange.toFixed(2))}`];
|
|
1227
1265
|
if (pctChange !== null) tipLines.push(`${sign}${pctChange.toFixed(1)}%`);
|
|
1228
1266
|
const tipHtml = tipLines.join('<br>');
|
|
1229
1267
|
|
|
1268
|
+
// Compute right-side label text and wrapping info
|
|
1269
|
+
const lastX = xScale(periods[periods.length - 1])!;
|
|
1270
|
+
const labelText = `${lastVal} — ${item.label}`;
|
|
1271
|
+
const availableWidth = rightMargin - 15;
|
|
1272
|
+
const maxChars = Math.floor(availableWidth / SLOPE_CHAR_WIDTH);
|
|
1273
|
+
|
|
1274
|
+
let labelLineCount = 1;
|
|
1275
|
+
let wrappedLines: string[] | null = null;
|
|
1276
|
+
if (labelText.length > maxChars) {
|
|
1277
|
+
const words = labelText.split(/\s+/);
|
|
1278
|
+
const lines: string[] = [];
|
|
1279
|
+
let current = '';
|
|
1280
|
+
for (const word of words) {
|
|
1281
|
+
const test = current ? `${current} ${word}` : word;
|
|
1282
|
+
if (test.length > maxChars && current) {
|
|
1283
|
+
lines.push(current);
|
|
1284
|
+
current = word;
|
|
1285
|
+
} else {
|
|
1286
|
+
current = test;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
if (current) lines.push(current);
|
|
1290
|
+
labelLineCount = lines.length;
|
|
1291
|
+
wrappedLines = lines;
|
|
1292
|
+
}
|
|
1293
|
+
const lineHeight = SLOPE_LABEL_FONT_SIZE * 1.2;
|
|
1294
|
+
const labelHeight = labelLineCount === 1
|
|
1295
|
+
? SLOPE_LABEL_FONT_SIZE
|
|
1296
|
+
: labelLineCount * lineHeight;
|
|
1297
|
+
|
|
1298
|
+
return {
|
|
1299
|
+
item, idx, color, firstVal, lastVal, tipHtml,
|
|
1300
|
+
lastX, labelText, maxChars, wrappedLines, labelHeight,
|
|
1301
|
+
};
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
// --- Resolve left-side label collisions per non-last period column ---
|
|
1305
|
+
const leftLabelHeight = 20; // 16px font needs ~20px to avoid glyph overlap
|
|
1306
|
+
const leftLabelCollisions: Map<number, number[]> = new Map();
|
|
1307
|
+
for (let pi = 0; pi < periods.length - 1; pi++) {
|
|
1308
|
+
const entries = data.map((item) => ({
|
|
1309
|
+
naturalY: yScale(item.values[pi]),
|
|
1310
|
+
height: leftLabelHeight,
|
|
1311
|
+
}));
|
|
1312
|
+
leftLabelCollisions.set(pi, resolveVerticalCollisions(entries, 4, innerHeight));
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// --- Resolve right-side label collisions ---
|
|
1316
|
+
const rightEntries = seriesInfo.map((si) => ({
|
|
1317
|
+
naturalY: yScale(si.lastVal),
|
|
1318
|
+
height: Math.max(si.labelHeight, SLOPE_LABEL_FONT_SIZE * 1.4),
|
|
1319
|
+
}));
|
|
1320
|
+
const rightAdjustedY = resolveVerticalCollisions(rightEntries, 4, innerHeight);
|
|
1321
|
+
|
|
1322
|
+
// Render each data series
|
|
1323
|
+
data.forEach((item, idx) => {
|
|
1324
|
+
const si = seriesInfo[idx];
|
|
1325
|
+
const color = si.color;
|
|
1326
|
+
|
|
1327
|
+
// Wrap each series in a group with data-line-number for sync adapter
|
|
1328
|
+
const seriesG = g
|
|
1329
|
+
.append('g')
|
|
1330
|
+
.attr('class', 'slope-series')
|
|
1331
|
+
.attr('data-line-number', String(item.lineNumber));
|
|
1332
|
+
|
|
1230
1333
|
// Line
|
|
1231
1334
|
seriesG.append('path')
|
|
1232
1335
|
.datum(item.values)
|
|
@@ -1244,10 +1347,10 @@ export function renderSlopeChart(
|
|
|
1244
1347
|
.attr('d', lineGen)
|
|
1245
1348
|
.style('cursor', onClickItem ? 'pointer' : 'default')
|
|
1246
1349
|
.on('mouseenter', (event: MouseEvent) =>
|
|
1247
|
-
showTooltip(tooltip, tipHtml, event)
|
|
1350
|
+
showTooltip(tooltip, si.tipHtml, event)
|
|
1248
1351
|
)
|
|
1249
1352
|
.on('mousemove', (event: MouseEvent) =>
|
|
1250
|
-
showTooltip(tooltip, tipHtml, event)
|
|
1353
|
+
showTooltip(tooltip, si.tipHtml, event)
|
|
1251
1354
|
)
|
|
1252
1355
|
.on('mouseleave', () => hideTooltip(tooltip))
|
|
1253
1356
|
.on('click', () => {
|
|
@@ -1269,10 +1372,10 @@ export function renderSlopeChart(
|
|
|
1269
1372
|
.attr('stroke-width', 1.5)
|
|
1270
1373
|
.style('cursor', onClickItem ? 'pointer' : 'default')
|
|
1271
1374
|
.on('mouseenter', (event: MouseEvent) =>
|
|
1272
|
-
showTooltip(tooltip, tipHtml, event)
|
|
1375
|
+
showTooltip(tooltip, si.tipHtml, event)
|
|
1273
1376
|
)
|
|
1274
1377
|
.on('mousemove', (event: MouseEvent) =>
|
|
1275
|
-
showTooltip(tooltip, tipHtml, event)
|
|
1378
|
+
showTooltip(tooltip, si.tipHtml, event)
|
|
1276
1379
|
)
|
|
1277
1380
|
.on('mouseleave', () => hideTooltip(tooltip))
|
|
1278
1381
|
.on('click', () => {
|
|
@@ -1283,59 +1386,41 @@ export function renderSlopeChart(
|
|
|
1283
1386
|
const isFirst = i === 0;
|
|
1284
1387
|
const isLast = i === periods.length - 1;
|
|
1285
1388
|
if (!isLast) {
|
|
1389
|
+
const adjustedY = leftLabelCollisions.get(i)![idx];
|
|
1286
1390
|
seriesG.append('text')
|
|
1287
1391
|
.attr('x', isFirst ? x - 10 : x)
|
|
1288
|
-
.attr('y',
|
|
1392
|
+
.attr('y', adjustedY)
|
|
1289
1393
|
.attr('dy', '0.35em')
|
|
1290
1394
|
.attr('text-anchor', isFirst ? 'end' : 'middle')
|
|
1291
|
-
.attr('fill',
|
|
1395
|
+
.attr('fill', color)
|
|
1292
1396
|
.attr('font-size', '16px')
|
|
1293
1397
|
.text(val.toString());
|
|
1294
1398
|
}
|
|
1295
1399
|
});
|
|
1296
1400
|
|
|
1297
1401
|
// Series label with value at end of line — wraps if it exceeds available space
|
|
1298
|
-
const
|
|
1299
|
-
const lastY = yScale(lastVal);
|
|
1300
|
-
const labelText = `${lastVal} — ${item.label}`;
|
|
1301
|
-
const availableWidth = rightMargin - 15;
|
|
1302
|
-
const maxChars = Math.floor(availableWidth / SLOPE_CHAR_WIDTH);
|
|
1402
|
+
const adjustedLastY = rightAdjustedY[idx];
|
|
1303
1403
|
|
|
1304
1404
|
const labelEl = seriesG
|
|
1305
1405
|
.append('text')
|
|
1306
|
-
.attr('x', lastX + 10)
|
|
1307
|
-
.attr('y',
|
|
1406
|
+
.attr('x', si.lastX + 10)
|
|
1407
|
+
.attr('y', adjustedLastY)
|
|
1308
1408
|
.attr('text-anchor', 'start')
|
|
1309
1409
|
.attr('fill', color)
|
|
1310
1410
|
.attr('font-size', `${SLOPE_LABEL_FONT_SIZE}px`)
|
|
1311
1411
|
.attr('font-weight', '500');
|
|
1312
1412
|
|
|
1313
|
-
if (
|
|
1314
|
-
labelEl.attr('dy', '0.35em').text(labelText);
|
|
1413
|
+
if (!si.wrappedLines) {
|
|
1414
|
+
labelEl.attr('dy', '0.35em').text(si.labelText);
|
|
1315
1415
|
} else {
|
|
1316
|
-
// Wrap into lines that fit the available width
|
|
1317
|
-
const words = labelText.split(/\s+/);
|
|
1318
|
-
const lines: string[] = [];
|
|
1319
|
-
let current = '';
|
|
1320
|
-
for (const word of words) {
|
|
1321
|
-
const test = current ? `${current} ${word}` : word;
|
|
1322
|
-
if (test.length > maxChars && current) {
|
|
1323
|
-
lines.push(current);
|
|
1324
|
-
current = word;
|
|
1325
|
-
} else {
|
|
1326
|
-
current = test;
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
if (current) lines.push(current);
|
|
1330
|
-
|
|
1331
1416
|
const lineHeight = SLOPE_LABEL_FONT_SIZE * 1.2;
|
|
1332
|
-
const totalHeight = (
|
|
1417
|
+
const totalHeight = (si.wrappedLines.length - 1) * lineHeight;
|
|
1333
1418
|
const startDy = -totalHeight / 2;
|
|
1334
1419
|
|
|
1335
|
-
|
|
1420
|
+
si.wrappedLines.forEach((line, li) => {
|
|
1336
1421
|
labelEl
|
|
1337
1422
|
.append('tspan')
|
|
1338
|
-
.attr('x', lastX + 10)
|
|
1423
|
+
.attr('x', si.lastX + 10)
|
|
1339
1424
|
.attr(
|
|
1340
1425
|
'dy',
|
|
1341
1426
|
li === 0
|
|
@@ -1480,14 +1565,12 @@ export function renderArcDiagram(
|
|
|
1480
1565
|
onClickItem?: (lineNumber: number) => void,
|
|
1481
1566
|
exportDims?: D3ExportDimensions
|
|
1482
1567
|
): void {
|
|
1483
|
-
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
1484
|
-
|
|
1485
1568
|
const { links, title, orientation, arcOrder, arcNodeGroups } = parsed;
|
|
1486
1569
|
if (links.length === 0) return;
|
|
1487
1570
|
|
|
1488
|
-
const
|
|
1489
|
-
|
|
1490
|
-
|
|
1571
|
+
const init = initD3Chart(container, palette, exportDims);
|
|
1572
|
+
if (!init) return;
|
|
1573
|
+
const { svg, width, height, textColor, mutedColor, bgColor, colors } = init;
|
|
1491
1574
|
|
|
1492
1575
|
const isVertical = orientation === 'vertical';
|
|
1493
1576
|
const margin = isVertical
|
|
@@ -1502,12 +1585,6 @@ export function renderArcDiagram(
|
|
|
1502
1585
|
const innerWidth = width - margin.left - margin.right;
|
|
1503
1586
|
const innerHeight = height - margin.top - margin.bottom;
|
|
1504
1587
|
|
|
1505
|
-
// Theme colors
|
|
1506
|
-
const textColor = palette.text;
|
|
1507
|
-
const mutedColor = palette.border;
|
|
1508
|
-
const bgColor = palette.bg;
|
|
1509
|
-
const colors = getSeriesColors(palette);
|
|
1510
|
-
|
|
1511
1588
|
// Order nodes by selected strategy
|
|
1512
1589
|
const nodes = orderArcNodes(links, arcOrder, arcNodeGroups);
|
|
1513
1590
|
|
|
@@ -1537,42 +1614,12 @@ export function renderArcDiagram(
|
|
|
1537
1614
|
.domain([minVal, maxVal])
|
|
1538
1615
|
.range([1.5, 6]);
|
|
1539
1616
|
|
|
1540
|
-
// SVG
|
|
1541
|
-
const svg = d3Selection
|
|
1542
|
-
.select(container)
|
|
1543
|
-
.append('svg')
|
|
1544
|
-
.attr('width', width)
|
|
1545
|
-
.attr('height', height)
|
|
1546
|
-
.style('background', bgColor);
|
|
1547
|
-
|
|
1548
1617
|
const g = svg
|
|
1549
1618
|
.append('g')
|
|
1550
1619
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
1551
1620
|
|
|
1552
1621
|
// Title
|
|
1553
|
-
|
|
1554
|
-
const titleEl = svg
|
|
1555
|
-
.append('text')
|
|
1556
|
-
.attr('class', 'chart-title')
|
|
1557
|
-
.attr('x', width / 2)
|
|
1558
|
-
.attr('y', 30)
|
|
1559
|
-
.attr('text-anchor', 'middle')
|
|
1560
|
-
.attr('fill', textColor)
|
|
1561
|
-
.attr('font-size', '20px')
|
|
1562
|
-
.attr('font-weight', '700')
|
|
1563
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
1564
|
-
.text(title);
|
|
1565
|
-
|
|
1566
|
-
if (parsed.titleLineNumber) {
|
|
1567
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
1568
|
-
if (onClickItem) {
|
|
1569
|
-
titleEl
|
|
1570
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
1571
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
1572
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1622
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
1576
1623
|
|
|
1577
1624
|
// Build adjacency map for hover interactions
|
|
1578
1625
|
const neighbors = new Map<string, Set<string>>();
|
|
@@ -2811,29 +2858,7 @@ export function renderTimeline(
|
|
|
2811
2858
|
.append('g')
|
|
2812
2859
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
2813
2860
|
|
|
2814
|
-
|
|
2815
|
-
const titleEl = svg
|
|
2816
|
-
.append('text')
|
|
2817
|
-
.attr('class', 'chart-title')
|
|
2818
|
-
.attr('x', width / 2)
|
|
2819
|
-
.attr('y', 30)
|
|
2820
|
-
.attr('text-anchor', 'middle')
|
|
2821
|
-
.attr('fill', textColor)
|
|
2822
|
-
.attr('font-size', '20px')
|
|
2823
|
-
.attr('font-weight', '700')
|
|
2824
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
2825
|
-
.text(title);
|
|
2826
|
-
|
|
2827
|
-
if (parsed.titleLineNumber) {
|
|
2828
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
2829
|
-
if (onClickItem) {
|
|
2830
|
-
titleEl
|
|
2831
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
2832
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
2833
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
2834
|
-
}
|
|
2835
|
-
}
|
|
2836
|
-
}
|
|
2861
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
2837
2862
|
|
|
2838
2863
|
renderEras(
|
|
2839
2864
|
g,
|
|
@@ -3043,29 +3068,7 @@ export function renderTimeline(
|
|
|
3043
3068
|
.append('g')
|
|
3044
3069
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
3045
3070
|
|
|
3046
|
-
|
|
3047
|
-
const titleEl = svg
|
|
3048
|
-
.append('text')
|
|
3049
|
-
.attr('class', 'chart-title')
|
|
3050
|
-
.attr('x', width / 2)
|
|
3051
|
-
.attr('y', 30)
|
|
3052
|
-
.attr('text-anchor', 'middle')
|
|
3053
|
-
.attr('fill', textColor)
|
|
3054
|
-
.attr('font-size', '20px')
|
|
3055
|
-
.attr('font-weight', '700')
|
|
3056
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3057
|
-
.text(title);
|
|
3058
|
-
|
|
3059
|
-
if (parsed.titleLineNumber) {
|
|
3060
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3061
|
-
if (onClickItem) {
|
|
3062
|
-
titleEl
|
|
3063
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3064
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3065
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3066
|
-
}
|
|
3067
|
-
}
|
|
3068
|
-
}
|
|
3071
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
3069
3072
|
|
|
3070
3073
|
renderEras(
|
|
3071
3074
|
g,
|
|
@@ -3336,29 +3339,7 @@ export function renderTimeline(
|
|
|
3336
3339
|
.append('g')
|
|
3337
3340
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
3338
3341
|
|
|
3339
|
-
|
|
3340
|
-
const titleEl = svg
|
|
3341
|
-
.append('text')
|
|
3342
|
-
.attr('class', 'chart-title')
|
|
3343
|
-
.attr('x', width / 2)
|
|
3344
|
-
.attr('y', 30)
|
|
3345
|
-
.attr('text-anchor', 'middle')
|
|
3346
|
-
.attr('fill', textColor)
|
|
3347
|
-
.attr('font-size', '20px')
|
|
3348
|
-
.attr('font-weight', '700')
|
|
3349
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3350
|
-
.text(title);
|
|
3351
|
-
|
|
3352
|
-
if (parsed.titleLineNumber) {
|
|
3353
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3354
|
-
if (onClickItem) {
|
|
3355
|
-
titleEl
|
|
3356
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3357
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3358
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3359
|
-
}
|
|
3360
|
-
}
|
|
3361
|
-
}
|
|
3342
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
3362
3343
|
|
|
3363
3344
|
renderEras(
|
|
3364
3345
|
g,
|
|
@@ -3633,29 +3614,7 @@ export function renderTimeline(
|
|
|
3633
3614
|
.append('g')
|
|
3634
3615
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
3635
3616
|
|
|
3636
|
-
|
|
3637
|
-
const titleEl = svg
|
|
3638
|
-
.append('text')
|
|
3639
|
-
.attr('class', 'chart-title')
|
|
3640
|
-
.attr('x', width / 2)
|
|
3641
|
-
.attr('y', 30)
|
|
3642
|
-
.attr('text-anchor', 'middle')
|
|
3643
|
-
.attr('fill', textColor)
|
|
3644
|
-
.attr('font-size', '20px')
|
|
3645
|
-
.attr('font-weight', '700')
|
|
3646
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3647
|
-
.text(title);
|
|
3648
|
-
|
|
3649
|
-
if (parsed.titleLineNumber) {
|
|
3650
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3651
|
-
if (onClickItem) {
|
|
3652
|
-
titleEl
|
|
3653
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3654
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3655
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3656
|
-
}
|
|
3657
|
-
}
|
|
3658
|
-
}
|
|
3617
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
3659
3618
|
|
|
3660
3619
|
renderEras(
|
|
3661
3620
|
g,
|
|
@@ -3905,22 +3864,16 @@ export function renderWordCloud(
|
|
|
3905
3864
|
onClickItem?: (lineNumber: number) => void,
|
|
3906
3865
|
exportDims?: D3ExportDimensions
|
|
3907
3866
|
): void {
|
|
3908
|
-
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
3909
|
-
|
|
3910
3867
|
const { words, title, cloudOptions } = parsed;
|
|
3911
3868
|
if (words.length === 0) return;
|
|
3912
3869
|
|
|
3913
|
-
const
|
|
3914
|
-
|
|
3915
|
-
|
|
3870
|
+
const init = initD3Chart(container, palette, exportDims);
|
|
3871
|
+
if (!init) return;
|
|
3872
|
+
const { svg, width, height, textColor, colors } = init;
|
|
3916
3873
|
|
|
3917
3874
|
const titleHeight = title ? 40 : 0;
|
|
3918
3875
|
const cloudHeight = height - titleHeight;
|
|
3919
3876
|
|
|
3920
|
-
const textColor = palette.text;
|
|
3921
|
-
const bgColor = palette.bg;
|
|
3922
|
-
const colors = getSeriesColors(palette);
|
|
3923
|
-
|
|
3924
3877
|
const { minSize, maxSize } = cloudOptions;
|
|
3925
3878
|
const weights = words.map((w) => w.weight);
|
|
3926
3879
|
const minWeight = Math.min(...weights);
|
|
@@ -3934,36 +3887,7 @@ export function renderWordCloud(
|
|
|
3934
3887
|
|
|
3935
3888
|
const rotateFn = getRotateFn(cloudOptions.rotate);
|
|
3936
3889
|
|
|
3937
|
-
|
|
3938
|
-
.select(container)
|
|
3939
|
-
.append('svg')
|
|
3940
|
-
.attr('width', width)
|
|
3941
|
-
.attr('height', height)
|
|
3942
|
-
.style('background', bgColor);
|
|
3943
|
-
|
|
3944
|
-
if (title) {
|
|
3945
|
-
const titleEl = svg
|
|
3946
|
-
.append('text')
|
|
3947
|
-
.attr('class', 'chart-title')
|
|
3948
|
-
.attr('x', width / 2)
|
|
3949
|
-
.attr('y', 30)
|
|
3950
|
-
.attr('text-anchor', 'middle')
|
|
3951
|
-
.attr('fill', textColor)
|
|
3952
|
-
.attr('font-size', '20px')
|
|
3953
|
-
.attr('font-weight', '700')
|
|
3954
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3955
|
-
.text(title);
|
|
3956
|
-
|
|
3957
|
-
if (parsed.titleLineNumber) {
|
|
3958
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3959
|
-
if (onClickItem) {
|
|
3960
|
-
titleEl
|
|
3961
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3962
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3963
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3964
|
-
}
|
|
3965
|
-
}
|
|
3966
|
-
}
|
|
3890
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
3967
3891
|
|
|
3968
3892
|
const g = svg
|
|
3969
3893
|
.append('g')
|
|
@@ -4062,22 +3986,7 @@ function renderWordCloudAsync(
|
|
|
4062
3986
|
.attr('height', height)
|
|
4063
3987
|
.style('background', bgColor);
|
|
4064
3988
|
|
|
4065
|
-
|
|
4066
|
-
const titleEl = svg
|
|
4067
|
-
.append('text')
|
|
4068
|
-
.attr('class', 'chart-title')
|
|
4069
|
-
.attr('x', width / 2)
|
|
4070
|
-
.attr('y', 30)
|
|
4071
|
-
.attr('text-anchor', 'middle')
|
|
4072
|
-
.attr('fill', textColor)
|
|
4073
|
-
.attr('font-size', '20px')
|
|
4074
|
-
.attr('font-weight', '700')
|
|
4075
|
-
.text(title);
|
|
4076
|
-
|
|
4077
|
-
if (parsed.titleLineNumber) {
|
|
4078
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
4079
|
-
}
|
|
4080
|
-
}
|
|
3989
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor);
|
|
4081
3990
|
|
|
4082
3991
|
const g = svg
|
|
4083
3992
|
.append('g')
|
|
@@ -4299,18 +4208,12 @@ export function renderVenn(
|
|
|
4299
4208
|
onClickItem?: (lineNumber: number) => void,
|
|
4300
4209
|
exportDims?: D3ExportDimensions
|
|
4301
4210
|
): void {
|
|
4302
|
-
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
4303
|
-
|
|
4304
4211
|
const { vennSets, vennOverlaps, vennShowValues, title } = parsed;
|
|
4305
4212
|
if (vennSets.length < 2) return;
|
|
4306
4213
|
|
|
4307
|
-
const
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
const textColor = palette.text;
|
|
4312
|
-
const bgColor = palette.bg;
|
|
4313
|
-
const colors = getSeriesColors(palette);
|
|
4214
|
+
const init = initD3Chart(container, palette, exportDims);
|
|
4215
|
+
if (!init) return;
|
|
4216
|
+
const { svg, width, height, textColor, colors } = init;
|
|
4314
4217
|
const titleHeight = title ? 40 : 0;
|
|
4315
4218
|
|
|
4316
4219
|
// Compute radii
|
|
@@ -4417,41 +4320,11 @@ export function renderVenn(
|
|
|
4417
4320
|
marginBottom
|
|
4418
4321
|
).map((c) => ({ ...c, y: c.y + titleHeight }));
|
|
4419
4322
|
|
|
4420
|
-
// SVG
|
|
4421
|
-
const svg = d3Selection
|
|
4422
|
-
.select(container)
|
|
4423
|
-
.append('svg')
|
|
4424
|
-
.attr('width', width)
|
|
4425
|
-
.attr('height', height)
|
|
4426
|
-
.style('background', bgColor);
|
|
4427
|
-
|
|
4428
4323
|
// Tooltip
|
|
4429
4324
|
const tooltip = createTooltip(container, palette, isDark);
|
|
4430
4325
|
|
|
4431
4326
|
// Title
|
|
4432
|
-
|
|
4433
|
-
const titleEl = svg
|
|
4434
|
-
.append('text')
|
|
4435
|
-
.attr('class', 'chart-title')
|
|
4436
|
-
.attr('x', width / 2)
|
|
4437
|
-
.attr('y', 30)
|
|
4438
|
-
.attr('text-anchor', 'middle')
|
|
4439
|
-
.attr('fill', textColor)
|
|
4440
|
-
.attr('font-size', '20px')
|
|
4441
|
-
.attr('font-weight', '700')
|
|
4442
|
-
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
4443
|
-
.text(title);
|
|
4444
|
-
|
|
4445
|
-
if (parsed.titleLineNumber) {
|
|
4446
|
-
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
4447
|
-
if (onClickItem) {
|
|
4448
|
-
titleEl
|
|
4449
|
-
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
4450
|
-
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
4451
|
-
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
4452
|
-
}
|
|
4453
|
-
}
|
|
4454
|
-
}
|
|
4327
|
+
renderChartTitle(svg, title, parsed.titleLineNumber, width, textColor, onClickItem);
|
|
4455
4328
|
|
|
4456
4329
|
// ── Semi-transparent filled circles ──
|
|
4457
4330
|
const circleEls: d3Selection.Selection<SVGCircleElement, unknown, null, undefined>[] = [];
|
|
@@ -4724,8 +4597,6 @@ export function renderQuadrant(
|
|
|
4724
4597
|
onClickItem?: (lineNumber: number) => void,
|
|
4725
4598
|
exportDims?: D3ExportDimensions
|
|
4726
4599
|
): void {
|
|
4727
|
-
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
4728
|
-
|
|
4729
4600
|
const {
|
|
4730
4601
|
title,
|
|
4731
4602
|
quadrantLabels,
|
|
@@ -4739,13 +4610,10 @@ export function renderQuadrant(
|
|
|
4739
4610
|
|
|
4740
4611
|
if (quadrantPoints.length === 0) return;
|
|
4741
4612
|
|
|
4742
|
-
const
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
const textColor = palette.text;
|
|
4613
|
+
const init = initD3Chart(container, palette, exportDims);
|
|
4614
|
+
if (!init) return;
|
|
4615
|
+
const { svg, width, height, textColor } = init;
|
|
4747
4616
|
const mutedColor = palette.textMuted;
|
|
4748
|
-
const bgColor = palette.bg;
|
|
4749
4617
|
const borderColor = palette.border;
|
|
4750
4618
|
|
|
4751
4619
|
// Default quadrant colors with alpha
|
|
@@ -4767,49 +4635,11 @@ export function renderQuadrant(
|
|
|
4767
4635
|
const xScale = d3Scale.scaleLinear().domain([0, 1]).range([0, chartWidth]);
|
|
4768
4636
|
const yScale = d3Scale.scaleLinear().domain([0, 1]).range([chartHeight, 0]);
|
|
4769
4637
|
|
|
4770
|
-
// Create SVG
|
|
4771
|
-
const svg = d3Selection
|
|
4772
|
-
.select(container)
|
|
4773
|
-
.append('svg')
|
|
4774
|
-
.attr('width', width)
|
|
4775
|
-
.attr('height', height)
|
|
4776
|
-
.style('background', bgColor);
|
|
4777
|
-
|
|
4778
4638
|
// Tooltip
|
|
4779
4639
|
const tooltip = createTooltip(container, palette, isDark);
|
|
4780
4640
|
|
|
4781
4641
|
// Title
|
|
4782
|
-
|
|
4783
|
-
const titleText = svg
|
|
4784
|
-
.append('text')
|
|
4785
|
-
.attr('class', 'chart-title')
|
|
4786
|
-
.attr('x', width / 2)
|
|
4787
|
-
.attr('y', 30)
|
|
4788
|
-
.attr('text-anchor', 'middle')
|
|
4789
|
-
.attr('fill', textColor)
|
|
4790
|
-
.attr('font-size', '20px')
|
|
4791
|
-
.attr('font-weight', '700')
|
|
4792
|
-
.style(
|
|
4793
|
-
'cursor',
|
|
4794
|
-
onClickItem && quadrantTitleLineNumber ? 'pointer' : 'default'
|
|
4795
|
-
)
|
|
4796
|
-
.text(title);
|
|
4797
|
-
|
|
4798
|
-
if (quadrantTitleLineNumber) {
|
|
4799
|
-
titleText.attr('data-line-number', quadrantTitleLineNumber);
|
|
4800
|
-
}
|
|
4801
|
-
|
|
4802
|
-
if (onClickItem && quadrantTitleLineNumber) {
|
|
4803
|
-
titleText
|
|
4804
|
-
.on('click', () => onClickItem(quadrantTitleLineNumber))
|
|
4805
|
-
.on('mouseenter', function () {
|
|
4806
|
-
d3Selection.select(this).attr('opacity', 0.7);
|
|
4807
|
-
})
|
|
4808
|
-
.on('mouseleave', function () {
|
|
4809
|
-
d3Selection.select(this).attr('opacity', 1);
|
|
4810
|
-
});
|
|
4811
|
-
}
|
|
4812
|
-
}
|
|
4642
|
+
renderChartTitle(svg, title, quadrantTitleLineNumber, width, textColor, onClickItem);
|
|
4813
4643
|
|
|
4814
4644
|
// Chart group (translated by margins)
|
|
4815
4645
|
const chartG = svg
|
|
@@ -5267,6 +5097,55 @@ export function renderQuadrant(
|
|
|
5267
5097
|
const EXPORT_WIDTH = 1200;
|
|
5268
5098
|
const EXPORT_HEIGHT = 800;
|
|
5269
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
|
+
|
|
5270
5149
|
/**
|
|
5271
5150
|
* Renders a D3 chart to an SVG string for export.
|
|
5272
5151
|
* Creates a detached DOM element, renders into it, extracts the SVG, then cleans up.
|
|
@@ -5293,9 +5172,7 @@ export async function renderD3ForExport(
|
|
|
5293
5172
|
const { renderOrg } = await import('./org/renderer');
|
|
5294
5173
|
|
|
5295
5174
|
const isDark = theme === 'dark';
|
|
5296
|
-
const
|
|
5297
|
-
const effectivePalette =
|
|
5298
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5175
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5299
5176
|
|
|
5300
5177
|
const orgParsed = parseOrg(content, effectivePalette);
|
|
5301
5178
|
if (orgParsed.error) return '';
|
|
@@ -5317,96 +5194,32 @@ export async function renderD3ForExport(
|
|
|
5317
5194
|
hiddenAttributes
|
|
5318
5195
|
);
|
|
5319
5196
|
|
|
5320
|
-
// Size container to fit the diagram content
|
|
5321
5197
|
const PADDING = 20;
|
|
5322
5198
|
const titleOffset = effectiveParsed.title ? 30 : 0;
|
|
5323
5199
|
const exportWidth = orgLayout.width + PADDING * 2;
|
|
5324
5200
|
const exportHeight = orgLayout.height + PADDING * 2 + titleOffset;
|
|
5201
|
+
const container = createExportContainer(exportWidth, exportHeight);
|
|
5325
5202
|
|
|
5326
|
-
|
|
5327
|
-
container
|
|
5328
|
-
container.style.height = `${exportHeight}px`;
|
|
5329
|
-
container.style.position = 'absolute';
|
|
5330
|
-
container.style.left = '-9999px';
|
|
5331
|
-
document.body.appendChild(container);
|
|
5332
|
-
|
|
5333
|
-
try {
|
|
5334
|
-
renderOrg(
|
|
5335
|
-
container,
|
|
5336
|
-
effectiveParsed,
|
|
5337
|
-
orgLayout,
|
|
5338
|
-
effectivePalette,
|
|
5339
|
-
isDark,
|
|
5340
|
-
undefined,
|
|
5341
|
-
{ width: exportWidth, height: exportHeight },
|
|
5342
|
-
activeTagGroup,
|
|
5343
|
-
hiddenAttributes
|
|
5344
|
-
);
|
|
5345
|
-
|
|
5346
|
-
const svgEl = container.querySelector('svg');
|
|
5347
|
-
if (!svgEl) return '';
|
|
5348
|
-
|
|
5349
|
-
if (theme === 'transparent') {
|
|
5350
|
-
svgEl.style.background = 'none';
|
|
5351
|
-
} else if (!svgEl.style.background) {
|
|
5352
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5353
|
-
}
|
|
5354
|
-
|
|
5355
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5356
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5357
|
-
|
|
5358
|
-
const svgHtml = svgEl.outerHTML;
|
|
5359
|
-
if (options?.branding !== false) {
|
|
5360
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5361
|
-
return injectBranding(svgHtml, brandColor);
|
|
5362
|
-
}
|
|
5363
|
-
return svgHtml;
|
|
5364
|
-
} finally {
|
|
5365
|
-
document.body.removeChild(container);
|
|
5366
|
-
}
|
|
5203
|
+
renderOrg(container, effectiveParsed, orgLayout, effectivePalette, isDark, undefined, { width: exportWidth, height: exportHeight }, activeTagGroup, hiddenAttributes);
|
|
5204
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5367
5205
|
}
|
|
5368
5206
|
|
|
5369
5207
|
if (detectedType === 'kanban') {
|
|
5370
5208
|
const { parseKanban } = await import('./kanban/parser');
|
|
5371
5209
|
const { renderKanban } = await import('./kanban/renderer');
|
|
5372
5210
|
|
|
5373
|
-
const
|
|
5374
|
-
const { getPalette } = await import('./palettes');
|
|
5375
|
-
const effectivePalette =
|
|
5376
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5377
|
-
|
|
5211
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5378
5212
|
const kanbanParsed = parseKanban(content, effectivePalette);
|
|
5379
5213
|
if (kanbanParsed.error || kanbanParsed.columns.length === 0) return '';
|
|
5380
5214
|
|
|
5215
|
+
// Kanban renderer self-sizes — no explicit width/height needed
|
|
5381
5216
|
const container = document.createElement('div');
|
|
5382
5217
|
container.style.position = 'absolute';
|
|
5383
5218
|
container.style.left = '-9999px';
|
|
5384
5219
|
document.body.appendChild(container);
|
|
5385
5220
|
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
const svgEl = container.querySelector('svg');
|
|
5390
|
-
if (!svgEl) return '';
|
|
5391
|
-
|
|
5392
|
-
if (theme === 'transparent') {
|
|
5393
|
-
svgEl.style.background = 'none';
|
|
5394
|
-
} else if (!svgEl.style.background) {
|
|
5395
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5396
|
-
}
|
|
5397
|
-
|
|
5398
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5399
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5400
|
-
|
|
5401
|
-
const svgHtml = svgEl.outerHTML;
|
|
5402
|
-
if (options?.branding !== false) {
|
|
5403
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5404
|
-
return injectBranding(svgHtml, brandColor);
|
|
5405
|
-
}
|
|
5406
|
-
return svgHtml;
|
|
5407
|
-
} finally {
|
|
5408
|
-
document.body.removeChild(container);
|
|
5409
|
-
}
|
|
5221
|
+
renderKanban(container, kanbanParsed, effectivePalette, theme === 'dark');
|
|
5222
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5410
5223
|
}
|
|
5411
5224
|
|
|
5412
5225
|
if (detectedType === 'class') {
|
|
@@ -5414,11 +5227,7 @@ export async function renderD3ForExport(
|
|
|
5414
5227
|
const { layoutClassDiagram } = await import('./class/layout');
|
|
5415
5228
|
const { renderClassDiagram } = await import('./class/renderer');
|
|
5416
5229
|
|
|
5417
|
-
const
|
|
5418
|
-
const { getPalette } = await import('./palettes');
|
|
5419
|
-
const effectivePalette =
|
|
5420
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5421
|
-
|
|
5230
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5422
5231
|
const classParsed = parseClassDiagram(content, effectivePalette);
|
|
5423
5232
|
if (classParsed.error || classParsed.classes.length === 0) return '';
|
|
5424
5233
|
|
|
@@ -5427,46 +5236,10 @@ export async function renderD3ForExport(
|
|
|
5427
5236
|
const titleOffset = classParsed.title ? 40 : 0;
|
|
5428
5237
|
const exportWidth = classLayout.width + PADDING * 2;
|
|
5429
5238
|
const exportHeight = classLayout.height + PADDING * 2 + titleOffset;
|
|
5239
|
+
const container = createExportContainer(exportWidth, exportHeight);
|
|
5430
5240
|
|
|
5431
|
-
|
|
5432
|
-
container
|
|
5433
|
-
container.style.height = `${exportHeight}px`;
|
|
5434
|
-
container.style.position = 'absolute';
|
|
5435
|
-
container.style.left = '-9999px';
|
|
5436
|
-
document.body.appendChild(container);
|
|
5437
|
-
|
|
5438
|
-
try {
|
|
5439
|
-
renderClassDiagram(
|
|
5440
|
-
container,
|
|
5441
|
-
classParsed,
|
|
5442
|
-
classLayout,
|
|
5443
|
-
effectivePalette,
|
|
5444
|
-
isDark,
|
|
5445
|
-
undefined,
|
|
5446
|
-
{ width: exportWidth, height: exportHeight }
|
|
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
|
-
}
|
|
5241
|
+
renderClassDiagram(container, classParsed, classLayout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight });
|
|
5242
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5470
5243
|
}
|
|
5471
5244
|
|
|
5472
5245
|
if (detectedType === 'er') {
|
|
@@ -5474,11 +5247,7 @@ export async function renderD3ForExport(
|
|
|
5474
5247
|
const { layoutERDiagram } = await import('./er/layout');
|
|
5475
5248
|
const { renderERDiagram } = await import('./er/renderer');
|
|
5476
5249
|
|
|
5477
|
-
const
|
|
5478
|
-
const { getPalette } = await import('./palettes');
|
|
5479
|
-
const effectivePalette =
|
|
5480
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5481
|
-
|
|
5250
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5482
5251
|
const erParsed = parseERDiagram(content, effectivePalette);
|
|
5483
5252
|
if (erParsed.error || erParsed.tables.length === 0) return '';
|
|
5484
5253
|
|
|
@@ -5487,46 +5256,10 @@ export async function renderD3ForExport(
|
|
|
5487
5256
|
const titleOffset = erParsed.title ? 40 : 0;
|
|
5488
5257
|
const exportWidth = erLayout.width + PADDING * 2;
|
|
5489
5258
|
const exportHeight = erLayout.height + PADDING * 2 + titleOffset;
|
|
5259
|
+
const container = createExportContainer(exportWidth, exportHeight);
|
|
5490
5260
|
|
|
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
|
-
renderERDiagram(
|
|
5500
|
-
container,
|
|
5501
|
-
erParsed,
|
|
5502
|
-
erLayout,
|
|
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
|
-
}
|
|
5261
|
+
renderERDiagram(container, erParsed, erLayout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight });
|
|
5262
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5530
5263
|
}
|
|
5531
5264
|
|
|
5532
5265
|
if (detectedType === 'initiative-status') {
|
|
@@ -5534,11 +5267,7 @@ export async function renderD3ForExport(
|
|
|
5534
5267
|
const { layoutInitiativeStatus } = await import('./initiative-status/layout');
|
|
5535
5268
|
const { renderInitiativeStatus } = await import('./initiative-status/renderer');
|
|
5536
5269
|
|
|
5537
|
-
const
|
|
5538
|
-
const { getPalette } = await import('./palettes');
|
|
5539
|
-
const effectivePalette =
|
|
5540
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5541
|
-
|
|
5270
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5542
5271
|
const isParsed = parseInitiativeStatus(content);
|
|
5543
5272
|
if (isParsed.error || isParsed.nodes.length === 0) return '';
|
|
5544
5273
|
|
|
@@ -5547,46 +5276,10 @@ export async function renderD3ForExport(
|
|
|
5547
5276
|
const titleOffset = isParsed.title ? 40 : 0;
|
|
5548
5277
|
const exportWidth = isLayout.width + PADDING * 2;
|
|
5549
5278
|
const exportHeight = isLayout.height + PADDING * 2 + titleOffset;
|
|
5279
|
+
const container = createExportContainer(exportWidth, exportHeight);
|
|
5550
5280
|
|
|
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
|
-
renderInitiativeStatus(
|
|
5560
|
-
container,
|
|
5561
|
-
isParsed,
|
|
5562
|
-
isLayout,
|
|
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
|
-
}
|
|
5281
|
+
renderInitiativeStatus(container, isParsed, isLayout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight });
|
|
5282
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5590
5283
|
}
|
|
5591
5284
|
|
|
5592
5285
|
if (detectedType === 'c4') {
|
|
@@ -5594,11 +5287,7 @@ export async function renderD3ForExport(
|
|
|
5594
5287
|
const { layoutC4Context, layoutC4Containers, layoutC4Components, layoutC4Deployment } = await import('./c4/layout');
|
|
5595
5288
|
const { renderC4Context, renderC4Containers } = await import('./c4/renderer');
|
|
5596
5289
|
|
|
5597
|
-
const
|
|
5598
|
-
const { getPalette } = await import('./palettes');
|
|
5599
|
-
const effectivePalette =
|
|
5600
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5601
|
-
|
|
5290
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5602
5291
|
const c4Parsed = parseC4(content, effectivePalette);
|
|
5603
5292
|
if (c4Parsed.error || c4Parsed.elements.length === 0) return '';
|
|
5604
5293
|
|
|
@@ -5621,50 +5310,14 @@ export async function renderD3ForExport(
|
|
|
5621
5310
|
const titleOffset = c4Parsed.title ? 40 : 0;
|
|
5622
5311
|
const exportWidth = c4Layout.width + PADDING * 2;
|
|
5623
5312
|
const exportHeight = c4Layout.height + PADDING * 2 + titleOffset;
|
|
5313
|
+
const container = createExportContainer(exportWidth, exportHeight);
|
|
5624
5314
|
|
|
5625
|
-
const
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
container.style.position = 'absolute';
|
|
5629
|
-
container.style.left = '-9999px';
|
|
5630
|
-
document.body.appendChild(container);
|
|
5631
|
-
|
|
5632
|
-
try {
|
|
5633
|
-
const renderFn = c4Level === 'deployment' || (c4Level === 'components' && c4System && c4Container) || (c4Level === 'containers' && c4System)
|
|
5634
|
-
? renderC4Containers
|
|
5635
|
-
: renderC4Context;
|
|
5636
|
-
|
|
5637
|
-
renderFn(
|
|
5638
|
-
container,
|
|
5639
|
-
c4Parsed,
|
|
5640
|
-
c4Layout,
|
|
5641
|
-
effectivePalette,
|
|
5642
|
-
isDark,
|
|
5643
|
-
undefined,
|
|
5644
|
-
{ width: exportWidth, height: exportHeight }
|
|
5645
|
-
);
|
|
5646
|
-
|
|
5647
|
-
const svgEl = container.querySelector('svg');
|
|
5648
|
-
if (!svgEl) return '';
|
|
5315
|
+
const renderFn = c4Level === 'deployment' || (c4Level === 'components' && c4System && c4Container) || (c4Level === 'containers' && c4System)
|
|
5316
|
+
? renderC4Containers
|
|
5317
|
+
: renderC4Context;
|
|
5649
5318
|
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
} else if (!svgEl.style.background) {
|
|
5653
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5654
|
-
}
|
|
5655
|
-
|
|
5656
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5657
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5658
|
-
|
|
5659
|
-
const svgHtml = svgEl.outerHTML;
|
|
5660
|
-
if (options?.branding !== false) {
|
|
5661
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5662
|
-
return injectBranding(svgHtml, brandColor);
|
|
5663
|
-
}
|
|
5664
|
-
return svgHtml;
|
|
5665
|
-
} finally {
|
|
5666
|
-
document.body.removeChild(container);
|
|
5667
|
-
}
|
|
5319
|
+
renderFn(container, c4Parsed, c4Layout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight });
|
|
5320
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5668
5321
|
}
|
|
5669
5322
|
|
|
5670
5323
|
if (detectedType === 'flowchart') {
|
|
@@ -5672,49 +5325,15 @@ export async function renderD3ForExport(
|
|
|
5672
5325
|
const { layoutGraph } = await import('./graph/layout');
|
|
5673
5326
|
const { renderFlowchart } = await import('./graph/flowchart-renderer');
|
|
5674
5327
|
|
|
5675
|
-
const
|
|
5676
|
-
const { getPalette } = await import('./palettes');
|
|
5677
|
-
const effectivePalette =
|
|
5678
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5679
|
-
|
|
5328
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5680
5329
|
const fcParsed = parseFlowchart(content, effectivePalette);
|
|
5681
5330
|
if (fcParsed.error || fcParsed.nodes.length === 0) return '';
|
|
5682
5331
|
|
|
5683
5332
|
const layout = layoutGraph(fcParsed);
|
|
5684
|
-
const container =
|
|
5685
|
-
container.style.width = `${EXPORT_WIDTH}px`;
|
|
5686
|
-
container.style.height = `${EXPORT_HEIGHT}px`;
|
|
5687
|
-
container.style.position = 'absolute';
|
|
5688
|
-
container.style.left = '-9999px';
|
|
5689
|
-
document.body.appendChild(container);
|
|
5690
|
-
|
|
5691
|
-
try {
|
|
5692
|
-
renderFlowchart(container, fcParsed, layout, effectivePalette, isDark, undefined, {
|
|
5693
|
-
width: EXPORT_WIDTH,
|
|
5694
|
-
height: EXPORT_HEIGHT,
|
|
5695
|
-
});
|
|
5696
|
-
|
|
5697
|
-
const svgEl = container.querySelector('svg');
|
|
5698
|
-
if (!svgEl) return '';
|
|
5699
|
-
|
|
5700
|
-
if (theme === 'transparent') {
|
|
5701
|
-
svgEl.style.background = 'none';
|
|
5702
|
-
} else if (!svgEl.style.background) {
|
|
5703
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5704
|
-
}
|
|
5705
|
-
|
|
5706
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5707
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5333
|
+
const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
|
|
5708
5334
|
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5712
|
-
return injectBranding(svgHtml, brandColor);
|
|
5713
|
-
}
|
|
5714
|
-
return svgHtml;
|
|
5715
|
-
} finally {
|
|
5716
|
-
document.body.removeChild(container);
|
|
5717
|
-
}
|
|
5335
|
+
renderFlowchart(container, fcParsed, layout, effectivePalette, theme === 'dark', undefined, { width: EXPORT_WIDTH, height: EXPORT_HEIGHT });
|
|
5336
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5718
5337
|
}
|
|
5719
5338
|
|
|
5720
5339
|
const parsed = parseD3(content, palette);
|
|
@@ -5736,67 +5355,32 @@ export async function renderD3ForExport(
|
|
|
5736
5355
|
if (parsed.type === 'quadrant' && parsed.quadrantPoints.length === 0)
|
|
5737
5356
|
return '';
|
|
5738
5357
|
|
|
5358
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
5739
5359
|
const isDark = theme === 'dark';
|
|
5740
|
-
|
|
5741
|
-
// Fall back to Nord palette if none provided
|
|
5742
|
-
const { getPalette } = await import('./palettes');
|
|
5743
|
-
const effectivePalette =
|
|
5744
|
-
palette ?? (isDark ? getPalette('nord').dark : getPalette('nord').light);
|
|
5745
|
-
|
|
5746
|
-
// Create a temporary offscreen container
|
|
5747
|
-
const container = document.createElement('div');
|
|
5748
|
-
container.style.width = `${EXPORT_WIDTH}px`;
|
|
5749
|
-
container.style.height = `${EXPORT_HEIGHT}px`;
|
|
5750
|
-
container.style.position = 'absolute';
|
|
5751
|
-
container.style.left = '-9999px';
|
|
5752
|
-
document.body.appendChild(container);
|
|
5753
|
-
|
|
5360
|
+
const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
|
|
5754
5361
|
const dims: D3ExportDimensions = { width: EXPORT_WIDTH, height: EXPORT_HEIGHT };
|
|
5755
5362
|
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
renderSlopeChart(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
5777
|
-
}
|
|
5778
|
-
|
|
5779
|
-
const svgEl = container.querySelector('svg');
|
|
5780
|
-
if (!svgEl) return '';
|
|
5781
|
-
|
|
5782
|
-
// Ensure all chart types have a consistent background
|
|
5783
|
-
if (theme === 'transparent') {
|
|
5784
|
-
svgEl.style.background = 'none';
|
|
5785
|
-
} else if (!svgEl.style.background) {
|
|
5786
|
-
svgEl.style.background = effectivePalette.bg;
|
|
5787
|
-
}
|
|
5788
|
-
|
|
5789
|
-
// Add xmlns for standalone SVG
|
|
5790
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
5791
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
5792
|
-
|
|
5793
|
-
const svgHtml = svgEl.outerHTML;
|
|
5794
|
-
if (options?.branding !== false) {
|
|
5795
|
-
const brandColor = theme === 'transparent' ? '#888' : effectivePalette.textMuted;
|
|
5796
|
-
return injectBranding(svgHtml, brandColor);
|
|
5797
|
-
}
|
|
5798
|
-
return svgHtml;
|
|
5799
|
-
} finally {
|
|
5800
|
-
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);
|
|
5801
5383
|
}
|
|
5384
|
+
|
|
5385
|
+
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5802
5386
|
}
|