@diagrammo/dgmo 0.2.8 → 0.2.10
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 +29 -1
- package/dist/cli.cjs +105 -105
- package/dist/index.cjs +6457 -6137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +6474 -6158
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/chart.ts +11 -0
- package/src/cli.ts +14 -34
- package/src/d3.ts +160 -15
- package/src/echarts.ts +56 -2
- package/src/graph/flowchart-parser.ts +1 -0
- package/src/graph/flowchart-renderer.ts +15 -4
- package/src/graph/types.ts +1 -0
- package/src/index.ts +7 -0
- package/src/render.ts +68 -0
- package/src/sequence/parser.ts +3 -0
- package/src/sequence/renderer.ts +311 -127
package/package.json
CHANGED
package/src/chart.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface ChartDataPoint {
|
|
|
23
23
|
export interface ParsedChart {
|
|
24
24
|
type: ChartType;
|
|
25
25
|
title?: string;
|
|
26
|
+
titleLineNumber?: number;
|
|
26
27
|
series?: string;
|
|
27
28
|
xlabel?: string;
|
|
28
29
|
ylabel?: string;
|
|
@@ -31,6 +32,7 @@ export interface ParsedChart {
|
|
|
31
32
|
orientation?: 'horizontal' | 'vertical';
|
|
32
33
|
color?: string;
|
|
33
34
|
label?: string;
|
|
35
|
+
labels?: 'name' | 'value' | 'percent' | 'full';
|
|
34
36
|
data: ChartDataPoint[];
|
|
35
37
|
error?: string;
|
|
36
38
|
}
|
|
@@ -120,6 +122,7 @@ export function parseChart(
|
|
|
120
122
|
|
|
121
123
|
if (key === 'title') {
|
|
122
124
|
result.title = value;
|
|
125
|
+
result.titleLineNumber = lineNumber;
|
|
123
126
|
continue;
|
|
124
127
|
}
|
|
125
128
|
|
|
@@ -138,6 +141,14 @@ export function parseChart(
|
|
|
138
141
|
continue;
|
|
139
142
|
}
|
|
140
143
|
|
|
144
|
+
if (key === 'labels') {
|
|
145
|
+
const v = value.toLowerCase();
|
|
146
|
+
if (v === 'name' || v === 'value' || v === 'percent' || v === 'full') {
|
|
147
|
+
result.labels = v;
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
141
152
|
if (key === 'orientation') {
|
|
142
153
|
const v = value.toLowerCase();
|
|
143
154
|
if (v === 'horizontal' || v === 'vertical') {
|
package/src/cli.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { resolve, basename, extname } from 'node:path';
|
|
3
|
-
import { JSDOM } from 'jsdom';
|
|
4
3
|
import { Resvg } from '@resvg/resvg-js';
|
|
5
|
-
import {
|
|
6
|
-
import { renderEChartsForExport } from './echarts';
|
|
7
|
-
import { parseDgmoChartType, getDgmoFramework } from './dgmo-router';
|
|
4
|
+
import { render } from './render';
|
|
8
5
|
import { getPalette } from './palettes/registry';
|
|
9
6
|
import { DEFAULT_FONT_NAME } from './fonts';
|
|
10
7
|
|
|
@@ -108,18 +105,6 @@ function parseArgs(argv: string[]): {
|
|
|
108
105
|
return result;
|
|
109
106
|
}
|
|
110
107
|
|
|
111
|
-
function setupDom(): void {
|
|
112
|
-
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
|
|
113
|
-
const win = dom.window;
|
|
114
|
-
|
|
115
|
-
// Expose DOM globals needed by d3-selection and renderers
|
|
116
|
-
Object.defineProperty(globalThis, 'document', { value: win.document, configurable: true });
|
|
117
|
-
Object.defineProperty(globalThis, 'window', { value: win, configurable: true });
|
|
118
|
-
Object.defineProperty(globalThis, 'navigator', { value: win.navigator, configurable: true });
|
|
119
|
-
Object.defineProperty(globalThis, 'HTMLElement', { value: win.HTMLElement, configurable: true });
|
|
120
|
-
Object.defineProperty(globalThis, 'SVGElement', { value: win.SVGElement, configurable: true });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
108
|
function inferFormat(outputPath: string | undefined): 'svg' | 'png' {
|
|
124
109
|
if (outputPath && extname(outputPath).toLowerCase() === '.svg') {
|
|
125
110
|
return 'svg';
|
|
@@ -217,28 +202,23 @@ async function main(): Promise<void> {
|
|
|
217
202
|
noInput();
|
|
218
203
|
}
|
|
219
204
|
|
|
220
|
-
const
|
|
221
|
-
const paletteColors = isDark
|
|
222
|
-
? getPalette(opts.palette).dark
|
|
223
|
-
: getPalette(opts.palette).light;
|
|
224
|
-
|
|
225
|
-
// Determine which rendering framework to use
|
|
226
|
-
const chartType = parseDgmoChartType(content);
|
|
227
|
-
const framework = chartType ? getDgmoFramework(chartType) : null;
|
|
228
|
-
|
|
229
|
-
let svg: string;
|
|
205
|
+
const paletteColors = getPalette(opts.palette)[opts.theme === 'dark' ? 'dark' : 'light'];
|
|
230
206
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
console.error(`Error: Unknown chart framework "${framework}".`);
|
|
207
|
+
// Word clouds require Canvas APIs (HTMLCanvasElement.getContext('2d'))
|
|
208
|
+
// which are unavailable in Node.js — check before attempting render.
|
|
209
|
+
const wordcloudRe = /^\s*chart\s*:\s*wordcloud\b/im;
|
|
210
|
+
if (wordcloudRe.test(content)) {
|
|
211
|
+
console.error(
|
|
212
|
+
'Error: Word clouds are not supported in the CLI (requires Canvas). Use the desktop app or browser instead.'
|
|
213
|
+
);
|
|
239
214
|
process.exit(1);
|
|
240
215
|
}
|
|
241
216
|
|
|
217
|
+
const svg = await render(content, {
|
|
218
|
+
theme: opts.theme,
|
|
219
|
+
palette: opts.palette,
|
|
220
|
+
});
|
|
221
|
+
|
|
242
222
|
if (!svg) {
|
|
243
223
|
console.error(
|
|
244
224
|
'Error: Failed to render diagram. The input may be empty, invalid, or use an unsupported chart type.'
|
package/src/d3.ts
CHANGED
|
@@ -139,6 +139,7 @@ export interface D3ExportDimensions {
|
|
|
139
139
|
export interface ParsedD3 {
|
|
140
140
|
type: D3ChartType | null;
|
|
141
141
|
title: string | null;
|
|
142
|
+
titleLineNumber: number | null;
|
|
142
143
|
orientation: 'horizontal' | 'vertical';
|
|
143
144
|
periods: string[];
|
|
144
145
|
data: D3DataItem[];
|
|
@@ -265,6 +266,7 @@ export function parseD3(content: string, palette?: PaletteColors): ParsedD3 {
|
|
|
265
266
|
const result: ParsedD3 = {
|
|
266
267
|
type: null,
|
|
267
268
|
title: null,
|
|
269
|
+
titleLineNumber: null,
|
|
268
270
|
orientation: 'horizontal',
|
|
269
271
|
periods: [],
|
|
270
272
|
data: [],
|
|
@@ -609,6 +611,7 @@ export function parseD3(content: string, palette?: PaletteColors): ParsedD3 {
|
|
|
609
611
|
|
|
610
612
|
if (key === 'title') {
|
|
611
613
|
result.title = line.substring(colonIndex + 1).trim();
|
|
614
|
+
result.titleLineNumber = lineNumber;
|
|
612
615
|
if (result.type === 'quadrant') {
|
|
613
616
|
result.quadrantTitleLineNumber = lineNumber;
|
|
614
617
|
}
|
|
@@ -1120,15 +1123,27 @@ export function renderSlopeChart(
|
|
|
1120
1123
|
|
|
1121
1124
|
// Title
|
|
1122
1125
|
if (title) {
|
|
1123
|
-
svg
|
|
1126
|
+
const titleEl = svg
|
|
1124
1127
|
.append('text')
|
|
1128
|
+
.attr('class', 'chart-title')
|
|
1125
1129
|
.attr('x', width / 2)
|
|
1126
1130
|
.attr('y', 30)
|
|
1127
1131
|
.attr('text-anchor', 'middle')
|
|
1128
1132
|
.attr('fill', textColor)
|
|
1129
1133
|
.attr('font-size', '20px')
|
|
1130
1134
|
.attr('font-weight', '700')
|
|
1135
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
1131
1136
|
.text(title);
|
|
1137
|
+
|
|
1138
|
+
if (parsed.titleLineNumber) {
|
|
1139
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
1140
|
+
if (onClickItem) {
|
|
1141
|
+
titleEl
|
|
1142
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
1143
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
1144
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1132
1147
|
}
|
|
1133
1148
|
|
|
1134
1149
|
// Period column headers
|
|
@@ -1164,6 +1179,12 @@ export function renderSlopeChart(
|
|
|
1164
1179
|
data.forEach((item, idx) => {
|
|
1165
1180
|
const color = item.color ?? colors[idx % colors.length];
|
|
1166
1181
|
|
|
1182
|
+
// Wrap each series in a group with data-line-number for sync adapter
|
|
1183
|
+
const seriesG = g
|
|
1184
|
+
.append('g')
|
|
1185
|
+
.attr('class', 'slope-series')
|
|
1186
|
+
.attr('data-line-number', String(item.lineNumber));
|
|
1187
|
+
|
|
1167
1188
|
// Tooltip content – overall change for this series
|
|
1168
1189
|
const firstVal = item.values[0];
|
|
1169
1190
|
const lastVal = item.values[item.values.length - 1];
|
|
@@ -1178,7 +1199,7 @@ export function renderSlopeChart(
|
|
|
1178
1199
|
`Change: ${sign}${absChange}${pctPart}`;
|
|
1179
1200
|
|
|
1180
1201
|
// Line
|
|
1181
|
-
|
|
1202
|
+
seriesG.append('path')
|
|
1182
1203
|
.datum(item.values)
|
|
1183
1204
|
.attr('fill', 'none')
|
|
1184
1205
|
.attr('stroke', color)
|
|
@@ -1186,7 +1207,7 @@ export function renderSlopeChart(
|
|
|
1186
1207
|
.attr('d', lineGen);
|
|
1187
1208
|
|
|
1188
1209
|
// Invisible wider path for easier hover targeting
|
|
1189
|
-
|
|
1210
|
+
seriesG.append('path')
|
|
1190
1211
|
.datum(item.values)
|
|
1191
1212
|
.attr('fill', 'none')
|
|
1192
1213
|
.attr('stroke', 'transparent')
|
|
@@ -1210,7 +1231,7 @@ export function renderSlopeChart(
|
|
|
1210
1231
|
const y = yScale(val);
|
|
1211
1232
|
|
|
1212
1233
|
// Point circle
|
|
1213
|
-
|
|
1234
|
+
seriesG.append('circle')
|
|
1214
1235
|
.attr('cx', x)
|
|
1215
1236
|
.attr('cy', y)
|
|
1216
1237
|
.attr('r', 4)
|
|
@@ -1233,7 +1254,7 @@ export function renderSlopeChart(
|
|
|
1233
1254
|
const isFirst = i === 0;
|
|
1234
1255
|
const isLast = i === periods.length - 1;
|
|
1235
1256
|
if (!isLast) {
|
|
1236
|
-
|
|
1257
|
+
seriesG.append('text')
|
|
1237
1258
|
.attr('x', isFirst ? x - 10 : x)
|
|
1238
1259
|
.attr('y', y)
|
|
1239
1260
|
.attr('dy', '0.35em')
|
|
@@ -1251,7 +1272,7 @@ export function renderSlopeChart(
|
|
|
1251
1272
|
const availableWidth = rightMargin - 15;
|
|
1252
1273
|
const maxChars = Math.floor(availableWidth / SLOPE_CHAR_WIDTH);
|
|
1253
1274
|
|
|
1254
|
-
const labelEl =
|
|
1275
|
+
const labelEl = seriesG
|
|
1255
1276
|
.append('text')
|
|
1256
1277
|
.attr('x', lastX + 10)
|
|
1257
1278
|
.attr('y', lastY)
|
|
@@ -1501,15 +1522,27 @@ export function renderArcDiagram(
|
|
|
1501
1522
|
|
|
1502
1523
|
// Title
|
|
1503
1524
|
if (title) {
|
|
1504
|
-
svg
|
|
1525
|
+
const titleEl = svg
|
|
1505
1526
|
.append('text')
|
|
1527
|
+
.attr('class', 'chart-title')
|
|
1506
1528
|
.attr('x', width / 2)
|
|
1507
1529
|
.attr('y', 30)
|
|
1508
1530
|
.attr('text-anchor', 'middle')
|
|
1509
1531
|
.attr('fill', textColor)
|
|
1510
1532
|
.attr('font-size', '20px')
|
|
1511
1533
|
.attr('font-weight', '700')
|
|
1534
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
1512
1535
|
.text(title);
|
|
1536
|
+
|
|
1537
|
+
if (parsed.titleLineNumber) {
|
|
1538
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
1539
|
+
if (onClickItem) {
|
|
1540
|
+
titleEl
|
|
1541
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
1542
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
1543
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1513
1546
|
}
|
|
1514
1547
|
|
|
1515
1548
|
// Build adjacency map for hover interactions
|
|
@@ -1613,6 +1646,7 @@ export function renderArcDiagram(
|
|
|
1613
1646
|
g.append('rect')
|
|
1614
1647
|
.attr('class', 'arc-group-band')
|
|
1615
1648
|
.attr('data-group', group.name)
|
|
1649
|
+
.attr('data-line-number', String(group.lineNumber))
|
|
1616
1650
|
.attr('x', baseX - bandHalfW)
|
|
1617
1651
|
.attr('y', minY)
|
|
1618
1652
|
.attr('width', bandHalfW * 2)
|
|
@@ -1630,6 +1664,7 @@ export function renderArcDiagram(
|
|
|
1630
1664
|
g.append('text')
|
|
1631
1665
|
.attr('class', 'arc-group-label')
|
|
1632
1666
|
.attr('data-group', group.name)
|
|
1667
|
+
.attr('data-line-number', String(group.lineNumber))
|
|
1633
1668
|
.attr('x', baseX - bandHalfW + 6)
|
|
1634
1669
|
.attr('y', minY + 14)
|
|
1635
1670
|
.attr('fill', textColor)
|
|
@@ -1669,6 +1704,7 @@ export function renderArcDiagram(
|
|
|
1669
1704
|
.attr('class', 'arc-link')
|
|
1670
1705
|
.attr('data-source', link.source)
|
|
1671
1706
|
.attr('data-target', link.target)
|
|
1707
|
+
.attr('data-line-number', String(link.lineNumber))
|
|
1672
1708
|
.attr('d', `M ${baseX},${y1} Q ${controlX},${midY} ${baseX},${y2}`)
|
|
1673
1709
|
.attr('fill', 'none')
|
|
1674
1710
|
.attr('stroke', color)
|
|
@@ -1693,6 +1729,7 @@ export function renderArcDiagram(
|
|
|
1693
1729
|
.append('g')
|
|
1694
1730
|
.attr('class', 'arc-node')
|
|
1695
1731
|
.attr('data-node', node)
|
|
1732
|
+
.attr('data-line-number', nodeLink?.lineNumber ? String(nodeLink.lineNumber) : null)
|
|
1696
1733
|
.style('cursor', 'pointer')
|
|
1697
1734
|
.on('mouseenter', () => handleMouseEnter(node))
|
|
1698
1735
|
.on('mouseleave', handleMouseLeave)
|
|
@@ -1746,6 +1783,7 @@ export function renderArcDiagram(
|
|
|
1746
1783
|
g.append('rect')
|
|
1747
1784
|
.attr('class', 'arc-group-band')
|
|
1748
1785
|
.attr('data-group', group.name)
|
|
1786
|
+
.attr('data-line-number', String(group.lineNumber))
|
|
1749
1787
|
.attr('x', minX)
|
|
1750
1788
|
.attr('y', baseY - bandHalfH)
|
|
1751
1789
|
.attr('width', maxX - minX)
|
|
@@ -1763,6 +1801,7 @@ export function renderArcDiagram(
|
|
|
1763
1801
|
g.append('text')
|
|
1764
1802
|
.attr('class', 'arc-group-label')
|
|
1765
1803
|
.attr('data-group', group.name)
|
|
1804
|
+
.attr('data-line-number', String(group.lineNumber))
|
|
1766
1805
|
.attr('x', (minX + maxX) / 2)
|
|
1767
1806
|
.attr('y', baseY + bandHalfH - 4)
|
|
1768
1807
|
.attr('text-anchor', 'middle')
|
|
@@ -1803,6 +1842,7 @@ export function renderArcDiagram(
|
|
|
1803
1842
|
.attr('class', 'arc-link')
|
|
1804
1843
|
.attr('data-source', link.source)
|
|
1805
1844
|
.attr('data-target', link.target)
|
|
1845
|
+
.attr('data-line-number', String(link.lineNumber))
|
|
1806
1846
|
.attr('d', `M ${x1},${baseY} Q ${midX},${controlY} ${x2},${baseY}`)
|
|
1807
1847
|
.attr('fill', 'none')
|
|
1808
1848
|
.attr('stroke', color)
|
|
@@ -1827,6 +1867,7 @@ export function renderArcDiagram(
|
|
|
1827
1867
|
.append('g')
|
|
1828
1868
|
.attr('class', 'arc-node')
|
|
1829
1869
|
.attr('data-node', node)
|
|
1870
|
+
.attr('data-line-number', nodeLink?.lineNumber ? String(nodeLink.lineNumber) : null)
|
|
1830
1871
|
.style('cursor', 'pointer')
|
|
1831
1872
|
.on('mouseenter', () => handleMouseEnter(node))
|
|
1832
1873
|
.on('mouseleave', handleMouseLeave)
|
|
@@ -1992,6 +2033,7 @@ function renderMarkers(
|
|
|
1992
2033
|
.append('g')
|
|
1993
2034
|
.attr('class', 'tl-marker')
|
|
1994
2035
|
.attr('data-marker-date', String(dateVal))
|
|
2036
|
+
.attr('data-line-number', String(marker.lineNumber))
|
|
1995
2037
|
.style('cursor', 'pointer')
|
|
1996
2038
|
.on('mouseenter', function (event: MouseEvent) {
|
|
1997
2039
|
if (tooltip) {
|
|
@@ -2745,15 +2787,27 @@ export function renderTimeline(
|
|
|
2745
2787
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
2746
2788
|
|
|
2747
2789
|
if (title) {
|
|
2748
|
-
svg
|
|
2790
|
+
const titleEl = svg
|
|
2749
2791
|
.append('text')
|
|
2792
|
+
.attr('class', 'chart-title')
|
|
2750
2793
|
.attr('x', width / 2)
|
|
2751
2794
|
.attr('y', 30)
|
|
2752
2795
|
.attr('text-anchor', 'middle')
|
|
2753
2796
|
.attr('fill', textColor)
|
|
2754
2797
|
.attr('font-size', '20px')
|
|
2755
2798
|
.attr('font-weight', '700')
|
|
2799
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
2756
2800
|
.text(title);
|
|
2801
|
+
|
|
2802
|
+
if (parsed.titleLineNumber) {
|
|
2803
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
2804
|
+
if (onClickItem) {
|
|
2805
|
+
titleEl
|
|
2806
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
2807
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
2808
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2757
2811
|
}
|
|
2758
2812
|
|
|
2759
2813
|
renderEras(
|
|
@@ -2841,6 +2895,7 @@ export function renderTimeline(
|
|
|
2841
2895
|
.append('g')
|
|
2842
2896
|
.attr('class', 'tl-event')
|
|
2843
2897
|
.attr('data-group', laneName)
|
|
2898
|
+
.attr('data-line-number', String(ev.lineNumber))
|
|
2844
2899
|
.attr('data-date', String(parseTimelineDate(ev.date)))
|
|
2845
2900
|
.attr(
|
|
2846
2901
|
'data-end-date',
|
|
@@ -2936,15 +2991,27 @@ export function renderTimeline(
|
|
|
2936
2991
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
2937
2992
|
|
|
2938
2993
|
if (title) {
|
|
2939
|
-
svg
|
|
2994
|
+
const titleEl = svg
|
|
2940
2995
|
.append('text')
|
|
2996
|
+
.attr('class', 'chart-title')
|
|
2941
2997
|
.attr('x', width / 2)
|
|
2942
2998
|
.attr('y', 30)
|
|
2943
2999
|
.attr('text-anchor', 'middle')
|
|
2944
3000
|
.attr('fill', textColor)
|
|
2945
3001
|
.attr('font-size', '20px')
|
|
2946
3002
|
.attr('font-weight', '700')
|
|
3003
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
2947
3004
|
.text(title);
|
|
3005
|
+
|
|
3006
|
+
if (parsed.titleLineNumber) {
|
|
3007
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3008
|
+
if (onClickItem) {
|
|
3009
|
+
titleEl
|
|
3010
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3011
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3012
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
2948
3015
|
}
|
|
2949
3016
|
|
|
2950
3017
|
renderEras(
|
|
@@ -3039,6 +3106,7 @@ export function renderTimeline(
|
|
|
3039
3106
|
.append('g')
|
|
3040
3107
|
.attr('class', 'tl-event')
|
|
3041
3108
|
.attr('data-group', ev.group || '')
|
|
3109
|
+
.attr('data-line-number', String(ev.lineNumber))
|
|
3042
3110
|
.attr('data-date', String(parseTimelineDate(ev.date)))
|
|
3043
3111
|
.attr(
|
|
3044
3112
|
'data-end-date',
|
|
@@ -3188,15 +3256,27 @@ export function renderTimeline(
|
|
|
3188
3256
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
3189
3257
|
|
|
3190
3258
|
if (title) {
|
|
3191
|
-
svg
|
|
3259
|
+
const titleEl = svg
|
|
3192
3260
|
.append('text')
|
|
3261
|
+
.attr('class', 'chart-title')
|
|
3193
3262
|
.attr('x', width / 2)
|
|
3194
3263
|
.attr('y', 30)
|
|
3195
3264
|
.attr('text-anchor', 'middle')
|
|
3196
3265
|
.attr('fill', textColor)
|
|
3197
3266
|
.attr('font-size', '20px')
|
|
3198
3267
|
.attr('font-weight', '700')
|
|
3268
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3199
3269
|
.text(title);
|
|
3270
|
+
|
|
3271
|
+
if (parsed.titleLineNumber) {
|
|
3272
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3273
|
+
if (onClickItem) {
|
|
3274
|
+
titleEl
|
|
3275
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3276
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3277
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3200
3280
|
}
|
|
3201
3281
|
|
|
3202
3282
|
renderEras(
|
|
@@ -3300,6 +3380,7 @@ export function renderTimeline(
|
|
|
3300
3380
|
.append('g')
|
|
3301
3381
|
.attr('class', 'tl-event')
|
|
3302
3382
|
.attr('data-group', lane.name)
|
|
3383
|
+
.attr('data-line-number', String(ev.lineNumber))
|
|
3303
3384
|
.attr('data-date', String(parseTimelineDate(ev.date)))
|
|
3304
3385
|
.attr(
|
|
3305
3386
|
'data-end-date',
|
|
@@ -3472,15 +3553,27 @@ export function renderTimeline(
|
|
|
3472
3553
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
3473
3554
|
|
|
3474
3555
|
if (title) {
|
|
3475
|
-
svg
|
|
3556
|
+
const titleEl = svg
|
|
3476
3557
|
.append('text')
|
|
3558
|
+
.attr('class', 'chart-title')
|
|
3477
3559
|
.attr('x', width / 2)
|
|
3478
3560
|
.attr('y', 30)
|
|
3479
3561
|
.attr('text-anchor', 'middle')
|
|
3480
3562
|
.attr('fill', textColor)
|
|
3481
3563
|
.attr('font-size', '20px')
|
|
3482
3564
|
.attr('font-weight', '700')
|
|
3565
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3483
3566
|
.text(title);
|
|
3567
|
+
|
|
3568
|
+
if (parsed.titleLineNumber) {
|
|
3569
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3570
|
+
if (onClickItem) {
|
|
3571
|
+
titleEl
|
|
3572
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3573
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3574
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3484
3577
|
}
|
|
3485
3578
|
|
|
3486
3579
|
renderEras(
|
|
@@ -3568,6 +3661,7 @@ export function renderTimeline(
|
|
|
3568
3661
|
.append('g')
|
|
3569
3662
|
.attr('class', 'tl-event')
|
|
3570
3663
|
.attr('data-group', ev.group || '')
|
|
3664
|
+
.attr('data-line-number', String(ev.lineNumber))
|
|
3571
3665
|
.attr('data-date', String(parseTimelineDate(ev.date)))
|
|
3572
3666
|
.attr(
|
|
3573
3667
|
'data-end-date',
|
|
@@ -3767,15 +3861,27 @@ export function renderWordCloud(
|
|
|
3767
3861
|
.style('background', bgColor);
|
|
3768
3862
|
|
|
3769
3863
|
if (title) {
|
|
3770
|
-
svg
|
|
3864
|
+
const titleEl = svg
|
|
3771
3865
|
.append('text')
|
|
3866
|
+
.attr('class', 'chart-title')
|
|
3772
3867
|
.attr('x', width / 2)
|
|
3773
3868
|
.attr('y', 30)
|
|
3774
3869
|
.attr('text-anchor', 'middle')
|
|
3775
3870
|
.attr('fill', textColor)
|
|
3776
3871
|
.attr('font-size', '20px')
|
|
3777
3872
|
.attr('font-weight', '700')
|
|
3873
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
3778
3874
|
.text(title);
|
|
3875
|
+
|
|
3876
|
+
if (parsed.titleLineNumber) {
|
|
3877
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3878
|
+
if (onClickItem) {
|
|
3879
|
+
titleEl
|
|
3880
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
3881
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
3882
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3779
3885
|
}
|
|
3780
3886
|
|
|
3781
3887
|
const g = svg
|
|
@@ -3808,6 +3914,10 @@ export function renderWordCloud(
|
|
|
3808
3914
|
'transform',
|
|
3809
3915
|
(d) => `translate(${d.x},${d.y}) rotate(${d.rotate})`
|
|
3810
3916
|
)
|
|
3917
|
+
.attr('data-line-number', (d) => {
|
|
3918
|
+
const ln = (d as WordCloudWord).lineNumber;
|
|
3919
|
+
return ln ? String(ln) : null;
|
|
3920
|
+
})
|
|
3811
3921
|
.text((d) => d.text!)
|
|
3812
3922
|
.on('click', (_event, d) => {
|
|
3813
3923
|
const ln = (d as WordCloudWord).lineNumber;
|
|
@@ -3872,8 +3982,9 @@ function renderWordCloudAsync(
|
|
|
3872
3982
|
.style('background', bgColor);
|
|
3873
3983
|
|
|
3874
3984
|
if (title) {
|
|
3875
|
-
svg
|
|
3985
|
+
const titleEl = svg
|
|
3876
3986
|
.append('text')
|
|
3987
|
+
.attr('class', 'chart-title')
|
|
3877
3988
|
.attr('x', width / 2)
|
|
3878
3989
|
.attr('y', 30)
|
|
3879
3990
|
.attr('text-anchor', 'middle')
|
|
@@ -3881,6 +3992,10 @@ function renderWordCloudAsync(
|
|
|
3881
3992
|
.attr('font-size', '20px')
|
|
3882
3993
|
.attr('font-weight', '700')
|
|
3883
3994
|
.text(title);
|
|
3995
|
+
|
|
3996
|
+
if (parsed.titleLineNumber) {
|
|
3997
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
3998
|
+
}
|
|
3884
3999
|
}
|
|
3885
4000
|
|
|
3886
4001
|
const g = svg
|
|
@@ -4217,15 +4332,27 @@ export function renderVenn(
|
|
|
4217
4332
|
|
|
4218
4333
|
// Title
|
|
4219
4334
|
if (title) {
|
|
4220
|
-
svg
|
|
4335
|
+
const titleEl = svg
|
|
4221
4336
|
.append('text')
|
|
4337
|
+
.attr('class', 'chart-title')
|
|
4222
4338
|
.attr('x', width / 2)
|
|
4223
4339
|
.attr('y', 30)
|
|
4224
4340
|
.attr('text-anchor', 'middle')
|
|
4225
4341
|
.attr('fill', textColor)
|
|
4226
4342
|
.attr('font-size', '20px')
|
|
4227
4343
|
.attr('font-weight', '700')
|
|
4344
|
+
.style('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
|
|
4228
4345
|
.text(title);
|
|
4346
|
+
|
|
4347
|
+
if (parsed.titleLineNumber) {
|
|
4348
|
+
titleEl.attr('data-line-number', parsed.titleLineNumber);
|
|
4349
|
+
if (onClickItem) {
|
|
4350
|
+
titleEl
|
|
4351
|
+
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
4352
|
+
.on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
|
|
4353
|
+
.on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4229
4356
|
}
|
|
4230
4357
|
|
|
4231
4358
|
// ── Clip-path definitions ──
|
|
@@ -4490,6 +4617,7 @@ export function renderVenn(
|
|
|
4490
4617
|
.attr('cy', c.y)
|
|
4491
4618
|
.attr('r', c.r)
|
|
4492
4619
|
.attr('fill', 'transparent')
|
|
4620
|
+
.attr('data-line-number', String(vennSets[i].lineNumber))
|
|
4493
4621
|
.style('cursor', onClickItem ? 'pointer' : 'default')
|
|
4494
4622
|
.on('mouseenter', (event: MouseEvent) => {
|
|
4495
4623
|
for (const rg of regionGroups) {
|
|
@@ -4593,6 +4721,7 @@ export function renderQuadrant(
|
|
|
4593
4721
|
if (title) {
|
|
4594
4722
|
const titleText = svg
|
|
4595
4723
|
.append('text')
|
|
4724
|
+
.attr('class', 'chart-title')
|
|
4596
4725
|
.attr('x', width / 2)
|
|
4597
4726
|
.attr('y', 30)
|
|
4598
4727
|
.attr('text-anchor', 'middle')
|
|
@@ -4605,6 +4734,10 @@ export function renderQuadrant(
|
|
|
4605
4734
|
)
|
|
4606
4735
|
.text(title);
|
|
4607
4736
|
|
|
4737
|
+
if (quadrantTitleLineNumber) {
|
|
4738
|
+
titleText.attr('data-line-number', quadrantTitleLineNumber);
|
|
4739
|
+
}
|
|
4740
|
+
|
|
4608
4741
|
if (onClickItem && quadrantTitleLineNumber) {
|
|
4609
4742
|
titleText
|
|
4610
4743
|
.on('click', () => onClickItem(quadrantTitleLineNumber))
|
|
@@ -4739,6 +4872,9 @@ export function renderQuadrant(
|
|
|
4739
4872
|
.attr('fill', (d) => getQuadrantLabelColor(d))
|
|
4740
4873
|
.attr('font-size', '48px')
|
|
4741
4874
|
.attr('font-weight', '700')
|
|
4875
|
+
.attr('data-line-number', (d) =>
|
|
4876
|
+
d.label?.lineNumber ? String(d.label.lineNumber) : null
|
|
4877
|
+
)
|
|
4742
4878
|
.style('cursor', (d) =>
|
|
4743
4879
|
onClickItem && d.label?.lineNumber ? 'pointer' : 'default'
|
|
4744
4880
|
)
|
|
@@ -4762,11 +4898,13 @@ export function renderQuadrant(
|
|
|
4762
4898
|
// Low label (centered on left half)
|
|
4763
4899
|
const xLowLabel = svg
|
|
4764
4900
|
.append('text')
|
|
4901
|
+
.attr('class', 'quadrant-axis-label')
|
|
4765
4902
|
.attr('x', margin.left + chartWidth / 4)
|
|
4766
4903
|
.attr('y', height - 20)
|
|
4767
4904
|
.attr('text-anchor', 'middle')
|
|
4768
4905
|
.attr('fill', textColor)
|
|
4769
4906
|
.attr('font-size', '18px')
|
|
4907
|
+
.attr('data-line-number', quadrantXAxisLineNumber ? String(quadrantXAxisLineNumber) : null)
|
|
4770
4908
|
.style(
|
|
4771
4909
|
'cursor',
|
|
4772
4910
|
onClickItem && quadrantXAxisLineNumber ? 'pointer' : 'default'
|
|
@@ -4776,11 +4914,13 @@ export function renderQuadrant(
|
|
|
4776
4914
|
// High label (centered on right half)
|
|
4777
4915
|
const xHighLabel = svg
|
|
4778
4916
|
.append('text')
|
|
4917
|
+
.attr('class', 'quadrant-axis-label')
|
|
4779
4918
|
.attr('x', margin.left + (chartWidth * 3) / 4)
|
|
4780
4919
|
.attr('y', height - 20)
|
|
4781
4920
|
.attr('text-anchor', 'middle')
|
|
4782
4921
|
.attr('fill', textColor)
|
|
4783
4922
|
.attr('font-size', '18px')
|
|
4923
|
+
.attr('data-line-number', quadrantXAxisLineNumber ? String(quadrantXAxisLineNumber) : null)
|
|
4784
4924
|
.style(
|
|
4785
4925
|
'cursor',
|
|
4786
4926
|
onClickItem && quadrantXAxisLineNumber ? 'pointer' : 'default'
|
|
@@ -4809,12 +4949,14 @@ export function renderQuadrant(
|
|
|
4809
4949
|
// Low label (centered on bottom half)
|
|
4810
4950
|
const yLowLabel = svg
|
|
4811
4951
|
.append('text')
|
|
4952
|
+
.attr('class', 'quadrant-axis-label')
|
|
4812
4953
|
.attr('x', 22)
|
|
4813
4954
|
.attr('y', yMidBottom)
|
|
4814
4955
|
.attr('text-anchor', 'middle')
|
|
4815
4956
|
.attr('fill', textColor)
|
|
4816
4957
|
.attr('font-size', '18px')
|
|
4817
4958
|
.attr('transform', `rotate(-90, 22, ${yMidBottom})`)
|
|
4959
|
+
.attr('data-line-number', quadrantYAxisLineNumber ? String(quadrantYAxisLineNumber) : null)
|
|
4818
4960
|
.style(
|
|
4819
4961
|
'cursor',
|
|
4820
4962
|
onClickItem && quadrantYAxisLineNumber ? 'pointer' : 'default'
|
|
@@ -4824,12 +4966,14 @@ export function renderQuadrant(
|
|
|
4824
4966
|
// High label (centered on top half)
|
|
4825
4967
|
const yHighLabel = svg
|
|
4826
4968
|
.append('text')
|
|
4969
|
+
.attr('class', 'quadrant-axis-label')
|
|
4827
4970
|
.attr('x', 22)
|
|
4828
4971
|
.attr('y', yMidTop)
|
|
4829
4972
|
.attr('text-anchor', 'middle')
|
|
4830
4973
|
.attr('fill', textColor)
|
|
4831
4974
|
.attr('font-size', '18px')
|
|
4832
4975
|
.attr('transform', `rotate(-90, 22, ${yMidTop})`)
|
|
4976
|
+
.attr('data-line-number', quadrantYAxisLineNumber ? String(quadrantYAxisLineNumber) : null)
|
|
4833
4977
|
.style(
|
|
4834
4978
|
'cursor',
|
|
4835
4979
|
onClickItem && quadrantYAxisLineNumber ? 'pointer' : 'default'
|
|
@@ -4888,7 +5032,8 @@ export function renderQuadrant(
|
|
|
4888
5032
|
const pointColor =
|
|
4889
5033
|
quadDef?.label?.color ?? defaultColors[quadDef?.colorIdx ?? 0];
|
|
4890
5034
|
|
|
4891
|
-
const pointG = pointsG.append('g').attr('class', 'point-group')
|
|
5035
|
+
const pointG = pointsG.append('g').attr('class', 'point-group')
|
|
5036
|
+
.attr('data-line-number', String(point.lineNumber));
|
|
4892
5037
|
|
|
4893
5038
|
// Circle with white fill and colored border for visibility on opaque quadrants
|
|
4894
5039
|
pointG
|