@diagrammo/dgmo 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +69 -69
- package/dist/index.cjs +214 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -9
- package/dist/index.d.ts +15 -9
- package/dist/index.js +214 -141
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/d3.ts +159 -130
- package/src/echarts.ts +103 -51
- package/src/index.ts +1 -1
- package/src/palettes/bold.ts +1 -1
- package/src/sequence/parser.ts +12 -1
- package/src/sequence/renderer.ts +9 -4
package/src/d3.ts
CHANGED
|
@@ -130,6 +130,12 @@ export interface QuadrantLabels {
|
|
|
130
130
|
bottomRight: QuadrantLabel | null;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
/** Optional explicit dimensions for CLI/export rendering (bypasses DOM layout). */
|
|
134
|
+
export interface D3ExportDimensions {
|
|
135
|
+
width?: number;
|
|
136
|
+
height?: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
133
139
|
export interface ParsedD3 {
|
|
134
140
|
type: D3ChartType | null;
|
|
135
141
|
title: string | null;
|
|
@@ -1036,8 +1042,8 @@ function tokenizeFreeformText(text: string): WordCloudWord[] {
|
|
|
1036
1042
|
// ============================================================
|
|
1037
1043
|
|
|
1038
1044
|
const SLOPE_MARGIN = { top: 80, bottom: 40, left: 80 };
|
|
1039
|
-
const SLOPE_LABEL_FONT_SIZE =
|
|
1040
|
-
const SLOPE_CHAR_WIDTH =
|
|
1045
|
+
const SLOPE_LABEL_FONT_SIZE = 14;
|
|
1046
|
+
const SLOPE_CHAR_WIDTH = 8; // approximate px per character at 14px
|
|
1041
1047
|
|
|
1042
1048
|
/**
|
|
1043
1049
|
* Renders a slope chart into the given container using D3.
|
|
@@ -1047,7 +1053,8 @@ export function renderSlopeChart(
|
|
|
1047
1053
|
parsed: ParsedD3,
|
|
1048
1054
|
palette: PaletteColors,
|
|
1049
1055
|
isDark: boolean,
|
|
1050
|
-
onClickItem?: (lineNumber: number) => void
|
|
1056
|
+
onClickItem?: (lineNumber: number) => void,
|
|
1057
|
+
exportDims?: D3ExportDimensions
|
|
1051
1058
|
): void {
|
|
1052
1059
|
// Clear existing content
|
|
1053
1060
|
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
@@ -1055,8 +1062,8 @@ export function renderSlopeChart(
|
|
|
1055
1062
|
const { periods, data, title } = parsed;
|
|
1056
1063
|
if (data.length === 0 || periods.length < 2) return;
|
|
1057
1064
|
|
|
1058
|
-
const width = container.clientWidth;
|
|
1059
|
-
const height = container.clientHeight;
|
|
1065
|
+
const width = exportDims?.width ?? container.clientWidth;
|
|
1066
|
+
const height = exportDims?.height ?? container.clientHeight;
|
|
1060
1067
|
if (width <= 0 || height <= 0) return;
|
|
1061
1068
|
|
|
1062
1069
|
// Compute right margin from the longest end-of-line label
|
|
@@ -1067,7 +1074,7 @@ export function renderSlopeChart(
|
|
|
1067
1074
|
const estimatedLabelWidth = maxLabelText.length * SLOPE_CHAR_WIDTH;
|
|
1068
1075
|
const maxRightMargin = Math.floor(width * 0.35);
|
|
1069
1076
|
const rightMargin = Math.min(
|
|
1070
|
-
Math.max(estimatedLabelWidth +
|
|
1077
|
+
Math.max(estimatedLabelWidth + 30, 120),
|
|
1071
1078
|
maxRightMargin
|
|
1072
1079
|
);
|
|
1073
1080
|
|
|
@@ -1077,7 +1084,7 @@ export function renderSlopeChart(
|
|
|
1077
1084
|
// Theme colors
|
|
1078
1085
|
const textColor = palette.text;
|
|
1079
1086
|
const mutedColor = palette.border;
|
|
1080
|
-
const bgColor = palette.
|
|
1087
|
+
const bgColor = palette.bg;
|
|
1081
1088
|
const colors = getSeriesColors(palette);
|
|
1082
1089
|
|
|
1083
1090
|
// Scales
|
|
@@ -1119,7 +1126,7 @@ export function renderSlopeChart(
|
|
|
1119
1126
|
.attr('y', 30)
|
|
1120
1127
|
.attr('text-anchor', 'middle')
|
|
1121
1128
|
.attr('fill', textColor)
|
|
1122
|
-
.attr('font-size', '
|
|
1129
|
+
.attr('font-size', '20px')
|
|
1123
1130
|
.attr('font-weight', '700')
|
|
1124
1131
|
.text(title);
|
|
1125
1132
|
}
|
|
@@ -1132,7 +1139,7 @@ export function renderSlopeChart(
|
|
|
1132
1139
|
.attr('y', -15)
|
|
1133
1140
|
.attr('text-anchor', 'middle')
|
|
1134
1141
|
.attr('fill', textColor)
|
|
1135
|
-
.attr('font-size', '
|
|
1142
|
+
.attr('font-size', '18px')
|
|
1136
1143
|
.attr('font-weight', '600')
|
|
1137
1144
|
.text(period);
|
|
1138
1145
|
|
|
@@ -1232,7 +1239,7 @@ export function renderSlopeChart(
|
|
|
1232
1239
|
.attr('dy', '0.35em')
|
|
1233
1240
|
.attr('text-anchor', isFirst ? 'end' : 'middle')
|
|
1234
1241
|
.attr('fill', textColor)
|
|
1235
|
-
.attr('font-size', '
|
|
1242
|
+
.attr('font-size', '16px')
|
|
1236
1243
|
.text(val.toString());
|
|
1237
1244
|
}
|
|
1238
1245
|
});
|
|
@@ -1420,15 +1427,16 @@ export function renderArcDiagram(
|
|
|
1420
1427
|
parsed: ParsedD3,
|
|
1421
1428
|
palette: PaletteColors,
|
|
1422
1429
|
_isDark: boolean,
|
|
1423
|
-
onClickItem?: (lineNumber: number) => void
|
|
1430
|
+
onClickItem?: (lineNumber: number) => void,
|
|
1431
|
+
exportDims?: D3ExportDimensions
|
|
1424
1432
|
): void {
|
|
1425
1433
|
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
1426
1434
|
|
|
1427
1435
|
const { links, title, orientation, arcOrder, arcNodeGroups } = parsed;
|
|
1428
1436
|
if (links.length === 0) return;
|
|
1429
1437
|
|
|
1430
|
-
const width = container.clientWidth;
|
|
1431
|
-
const height = container.clientHeight;
|
|
1438
|
+
const width = exportDims?.width ?? container.clientWidth;
|
|
1439
|
+
const height = exportDims?.height ?? container.clientHeight;
|
|
1432
1440
|
if (width <= 0 || height <= 0) return;
|
|
1433
1441
|
|
|
1434
1442
|
const isVertical = orientation === 'vertical';
|
|
@@ -1447,7 +1455,7 @@ export function renderArcDiagram(
|
|
|
1447
1455
|
// Theme colors
|
|
1448
1456
|
const textColor = palette.text;
|
|
1449
1457
|
const mutedColor = palette.border;
|
|
1450
|
-
const bgColor = palette.
|
|
1458
|
+
const bgColor = palette.bg;
|
|
1451
1459
|
const colors = getSeriesColors(palette);
|
|
1452
1460
|
|
|
1453
1461
|
// Order nodes by selected strategy
|
|
@@ -1499,7 +1507,7 @@ export function renderArcDiagram(
|
|
|
1499
1507
|
.attr('y', 30)
|
|
1500
1508
|
.attr('text-anchor', 'middle')
|
|
1501
1509
|
.attr('fill', textColor)
|
|
1502
|
-
.attr('font-size', '
|
|
1510
|
+
.attr('font-size', '20px')
|
|
1503
1511
|
.attr('font-weight', '700')
|
|
1504
1512
|
.text(title);
|
|
1505
1513
|
}
|
|
@@ -1541,11 +1549,11 @@ export function renderArcDiagram(
|
|
|
1541
1549
|
g.selectAll<SVGGElement, unknown>('.arc-node').attr('opacity', 1);
|
|
1542
1550
|
g.selectAll<SVGRectElement, unknown>('.arc-group-band').attr(
|
|
1543
1551
|
'fill-opacity',
|
|
1544
|
-
0.
|
|
1552
|
+
0.06
|
|
1545
1553
|
);
|
|
1546
1554
|
g.selectAll<SVGTextElement, unknown>('.arc-group-label').attr(
|
|
1547
1555
|
'fill-opacity',
|
|
1548
|
-
0.
|
|
1556
|
+
0.5
|
|
1549
1557
|
);
|
|
1550
1558
|
}
|
|
1551
1559
|
|
|
@@ -1610,8 +1618,8 @@ export function renderArcDiagram(
|
|
|
1610
1618
|
.attr('width', bandHalfW * 2)
|
|
1611
1619
|
.attr('height', maxY - minY)
|
|
1612
1620
|
.attr('rx', 4)
|
|
1613
|
-
.attr('fill',
|
|
1614
|
-
.attr('fill-opacity', 0.
|
|
1621
|
+
.attr('fill', textColor)
|
|
1622
|
+
.attr('fill-opacity', 0.06)
|
|
1615
1623
|
.style('cursor', 'pointer')
|
|
1616
1624
|
.on('mouseenter', () => handleGroupEnter(group.name))
|
|
1617
1625
|
.on('mouseleave', handleMouseLeave)
|
|
@@ -1623,11 +1631,11 @@ export function renderArcDiagram(
|
|
|
1623
1631
|
.attr('class', 'arc-group-label')
|
|
1624
1632
|
.attr('data-group', group.name)
|
|
1625
1633
|
.attr('x', baseX - bandHalfW + 6)
|
|
1626
|
-
.attr('y', minY +
|
|
1627
|
-
.attr('fill',
|
|
1628
|
-
.attr('font-size', '
|
|
1634
|
+
.attr('y', minY + 14)
|
|
1635
|
+
.attr('fill', textColor)
|
|
1636
|
+
.attr('font-size', '12px')
|
|
1629
1637
|
.attr('font-weight', '600')
|
|
1630
|
-
.attr('fill-opacity', 0.
|
|
1638
|
+
.attr('fill-opacity', 0.5)
|
|
1631
1639
|
.style('cursor', onClickItem ? 'pointer' : 'default')
|
|
1632
1640
|
.text(group.name)
|
|
1633
1641
|
.on('mouseenter', () => handleGroupEnter(group.name))
|
|
@@ -1743,8 +1751,8 @@ export function renderArcDiagram(
|
|
|
1743
1751
|
.attr('width', maxX - minX)
|
|
1744
1752
|
.attr('height', bandHalfH * 2)
|
|
1745
1753
|
.attr('rx', 4)
|
|
1746
|
-
.attr('fill',
|
|
1747
|
-
.attr('fill-opacity', 0.
|
|
1754
|
+
.attr('fill', textColor)
|
|
1755
|
+
.attr('fill-opacity', 0.06)
|
|
1748
1756
|
.style('cursor', 'pointer')
|
|
1749
1757
|
.on('mouseenter', () => handleGroupEnter(group.name))
|
|
1750
1758
|
.on('mouseleave', handleMouseLeave)
|
|
@@ -1758,10 +1766,10 @@ export function renderArcDiagram(
|
|
|
1758
1766
|
.attr('x', (minX + maxX) / 2)
|
|
1759
1767
|
.attr('y', baseY + bandHalfH - 4)
|
|
1760
1768
|
.attr('text-anchor', 'middle')
|
|
1761
|
-
.attr('fill',
|
|
1762
|
-
.attr('font-size', '
|
|
1769
|
+
.attr('fill', textColor)
|
|
1770
|
+
.attr('font-size', '12px')
|
|
1763
1771
|
.attr('font-weight', '600')
|
|
1764
|
-
.attr('fill-opacity', 0.
|
|
1772
|
+
.attr('fill-opacity', 0.5)
|
|
1765
1773
|
.style('cursor', onClickItem ? 'pointer' : 'default')
|
|
1766
1774
|
.text(group.name)
|
|
1767
1775
|
.on('mouseenter', () => handleGroupEnter(group.name))
|
|
@@ -2549,7 +2557,8 @@ export function renderTimeline(
|
|
|
2549
2557
|
parsed: ParsedD3,
|
|
2550
2558
|
palette: PaletteColors,
|
|
2551
2559
|
isDark: boolean,
|
|
2552
|
-
onClickItem?: (lineNumber: number) => void
|
|
2560
|
+
onClickItem?: (lineNumber: number) => void,
|
|
2561
|
+
exportDims?: D3ExportDimensions
|
|
2553
2562
|
): void {
|
|
2554
2563
|
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
2555
2564
|
|
|
@@ -2568,8 +2577,8 @@ export function renderTimeline(
|
|
|
2568
2577
|
|
|
2569
2578
|
const tooltip = createTooltip(container, palette, isDark);
|
|
2570
2579
|
|
|
2571
|
-
const width = container.clientWidth;
|
|
2572
|
-
const height = container.clientHeight;
|
|
2580
|
+
const width = exportDims?.width ?? container.clientWidth;
|
|
2581
|
+
const height = exportDims?.height ?? container.clientHeight;
|
|
2573
2582
|
if (width <= 0 || height <= 0) return;
|
|
2574
2583
|
|
|
2575
2584
|
const isVertical = orientation === 'vertical';
|
|
@@ -2577,7 +2586,7 @@ export function renderTimeline(
|
|
|
2577
2586
|
// Theme colors
|
|
2578
2587
|
const textColor = palette.text;
|
|
2579
2588
|
const mutedColor = palette.border;
|
|
2580
|
-
const bgColor = palette.
|
|
2589
|
+
const bgColor = palette.bg;
|
|
2581
2590
|
const colors = getSeriesColors(palette);
|
|
2582
2591
|
|
|
2583
2592
|
// Assign colors to groups
|
|
@@ -2732,7 +2741,7 @@ export function renderTimeline(
|
|
|
2732
2741
|
.attr('y', 30)
|
|
2733
2742
|
.attr('text-anchor', 'middle')
|
|
2734
2743
|
.attr('fill', textColor)
|
|
2735
|
-
.attr('font-size', '
|
|
2744
|
+
.attr('font-size', '20px')
|
|
2736
2745
|
.attr('font-weight', '700')
|
|
2737
2746
|
.text(title);
|
|
2738
2747
|
}
|
|
@@ -2923,7 +2932,7 @@ export function renderTimeline(
|
|
|
2923
2932
|
.attr('y', 30)
|
|
2924
2933
|
.attr('text-anchor', 'middle')
|
|
2925
2934
|
.attr('fill', textColor)
|
|
2926
|
-
.attr('font-size', '
|
|
2935
|
+
.attr('font-size', '20px')
|
|
2927
2936
|
.attr('font-weight', '700')
|
|
2928
2937
|
.text(title);
|
|
2929
2938
|
}
|
|
@@ -3175,7 +3184,7 @@ export function renderTimeline(
|
|
|
3175
3184
|
.attr('y', 30)
|
|
3176
3185
|
.attr('text-anchor', 'middle')
|
|
3177
3186
|
.attr('fill', textColor)
|
|
3178
|
-
.attr('font-size', '
|
|
3187
|
+
.attr('font-size', '20px')
|
|
3179
3188
|
.attr('font-weight', '700')
|
|
3180
3189
|
.text(title);
|
|
3181
3190
|
}
|
|
@@ -3371,8 +3380,8 @@ export function renderTimeline(
|
|
|
3371
3380
|
.attr('dy', '0.35em')
|
|
3372
3381
|
.attr('text-anchor', 'start')
|
|
3373
3382
|
.attr('fill', '#ffffff')
|
|
3374
|
-
.attr('font-size', '
|
|
3375
|
-
.attr('font-weight', '
|
|
3383
|
+
.attr('font-size', '14px')
|
|
3384
|
+
.attr('font-weight', '700')
|
|
3376
3385
|
.text(ev.label);
|
|
3377
3386
|
} else {
|
|
3378
3387
|
// Text outside bar - check if it fits on left or must go right
|
|
@@ -3459,7 +3468,7 @@ export function renderTimeline(
|
|
|
3459
3468
|
.attr('y', 30)
|
|
3460
3469
|
.attr('text-anchor', 'middle')
|
|
3461
3470
|
.attr('fill', textColor)
|
|
3462
|
-
.attr('font-size', '
|
|
3471
|
+
.attr('font-size', '20px')
|
|
3463
3472
|
.attr('font-weight', '700')
|
|
3464
3473
|
.text(title);
|
|
3465
3474
|
}
|
|
@@ -3639,8 +3648,8 @@ export function renderTimeline(
|
|
|
3639
3648
|
.attr('dy', '0.35em')
|
|
3640
3649
|
.attr('text-anchor', 'start')
|
|
3641
3650
|
.attr('fill', '#ffffff')
|
|
3642
|
-
.attr('font-size', '
|
|
3643
|
-
.attr('font-weight', '
|
|
3651
|
+
.attr('font-size', '14px')
|
|
3652
|
+
.attr('font-weight', '700')
|
|
3644
3653
|
.text(ev.label);
|
|
3645
3654
|
} else {
|
|
3646
3655
|
// Text outside bar - check if it fits on left or must go right
|
|
@@ -3708,22 +3717,23 @@ export function renderWordCloud(
|
|
|
3708
3717
|
parsed: ParsedD3,
|
|
3709
3718
|
palette: PaletteColors,
|
|
3710
3719
|
_isDark: boolean,
|
|
3711
|
-
onClickItem?: (lineNumber: number) => void
|
|
3720
|
+
onClickItem?: (lineNumber: number) => void,
|
|
3721
|
+
exportDims?: D3ExportDimensions
|
|
3712
3722
|
): void {
|
|
3713
3723
|
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
3714
3724
|
|
|
3715
3725
|
const { words, title, cloudOptions } = parsed;
|
|
3716
3726
|
if (words.length === 0) return;
|
|
3717
3727
|
|
|
3718
|
-
const width = container.clientWidth;
|
|
3719
|
-
const height = container.clientHeight;
|
|
3728
|
+
const width = exportDims?.width ?? container.clientWidth;
|
|
3729
|
+
const height = exportDims?.height ?? container.clientHeight;
|
|
3720
3730
|
if (width <= 0 || height <= 0) return;
|
|
3721
3731
|
|
|
3722
3732
|
const titleHeight = title ? 40 : 0;
|
|
3723
3733
|
const cloudHeight = height - titleHeight;
|
|
3724
3734
|
|
|
3725
3735
|
const textColor = palette.text;
|
|
3726
|
-
const bgColor = palette.
|
|
3736
|
+
const bgColor = palette.bg;
|
|
3727
3737
|
const colors = getSeriesColors(palette);
|
|
3728
3738
|
|
|
3729
3739
|
const { minSize, maxSize } = cloudOptions;
|
|
@@ -3750,10 +3760,10 @@ export function renderWordCloud(
|
|
|
3750
3760
|
svg
|
|
3751
3761
|
.append('text')
|
|
3752
3762
|
.attr('x', width / 2)
|
|
3753
|
-
.attr('y',
|
|
3763
|
+
.attr('y', 30)
|
|
3754
3764
|
.attr('text-anchor', 'middle')
|
|
3755
3765
|
.attr('fill', textColor)
|
|
3756
|
-
.attr('font-size', '
|
|
3766
|
+
.attr('font-size', '20px')
|
|
3757
3767
|
.attr('font-weight', '700')
|
|
3758
3768
|
.text(title);
|
|
3759
3769
|
}
|
|
@@ -3805,7 +3815,8 @@ function renderWordCloudAsync(
|
|
|
3805
3815
|
container: HTMLDivElement,
|
|
3806
3816
|
parsed: ParsedD3,
|
|
3807
3817
|
palette: PaletteColors,
|
|
3808
|
-
_isDark: boolean
|
|
3818
|
+
_isDark: boolean,
|
|
3819
|
+
exportDims?: D3ExportDimensions
|
|
3809
3820
|
): Promise<void> {
|
|
3810
3821
|
return new Promise((resolve) => {
|
|
3811
3822
|
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
@@ -3816,8 +3827,8 @@ function renderWordCloudAsync(
|
|
|
3816
3827
|
return;
|
|
3817
3828
|
}
|
|
3818
3829
|
|
|
3819
|
-
const width = container.clientWidth;
|
|
3820
|
-
const height = container.clientHeight;
|
|
3830
|
+
const width = exportDims?.width ?? container.clientWidth;
|
|
3831
|
+
const height = exportDims?.height ?? container.clientHeight;
|
|
3821
3832
|
if (width <= 0 || height <= 0) {
|
|
3822
3833
|
resolve();
|
|
3823
3834
|
return;
|
|
@@ -3827,7 +3838,7 @@ function renderWordCloudAsync(
|
|
|
3827
3838
|
const cloudHeight = height - titleHeight;
|
|
3828
3839
|
|
|
3829
3840
|
const textColor = palette.text;
|
|
3830
|
-
const bgColor = palette.
|
|
3841
|
+
const bgColor = palette.bg;
|
|
3831
3842
|
const colors = getSeriesColors(palette);
|
|
3832
3843
|
|
|
3833
3844
|
const { minSize, maxSize } = cloudOptions;
|
|
@@ -3854,10 +3865,10 @@ function renderWordCloudAsync(
|
|
|
3854
3865
|
svg
|
|
3855
3866
|
.append('text')
|
|
3856
3867
|
.attr('x', width / 2)
|
|
3857
|
-
.attr('y',
|
|
3868
|
+
.attr('y', 30)
|
|
3858
3869
|
.attr('text-anchor', 'middle')
|
|
3859
3870
|
.attr('fill', textColor)
|
|
3860
|
-
.attr('font-size', '
|
|
3871
|
+
.attr('font-size', '20px')
|
|
3861
3872
|
.attr('font-weight', '700')
|
|
3862
3873
|
.text(title);
|
|
3863
3874
|
}
|
|
@@ -4096,19 +4107,20 @@ export function renderVenn(
|
|
|
4096
4107
|
parsed: ParsedD3,
|
|
4097
4108
|
palette: PaletteColors,
|
|
4098
4109
|
isDark: boolean,
|
|
4099
|
-
onClickItem?: (lineNumber: number) => void
|
|
4110
|
+
onClickItem?: (lineNumber: number) => void,
|
|
4111
|
+
exportDims?: D3ExportDimensions
|
|
4100
4112
|
): void {
|
|
4101
4113
|
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
4102
4114
|
|
|
4103
4115
|
const { vennSets, vennOverlaps, vennShowValues, title } = parsed;
|
|
4104
4116
|
if (vennSets.length < 2) return;
|
|
4105
4117
|
|
|
4106
|
-
const width = container.clientWidth;
|
|
4107
|
-
const height = container.clientHeight;
|
|
4118
|
+
const width = exportDims?.width ?? container.clientWidth;
|
|
4119
|
+
const height = exportDims?.height ?? container.clientHeight;
|
|
4108
4120
|
if (width <= 0 || height <= 0) return;
|
|
4109
4121
|
|
|
4110
4122
|
const textColor = palette.text;
|
|
4111
|
-
const bgColor = palette.
|
|
4123
|
+
const bgColor = palette.bg;
|
|
4112
4124
|
const colors = getSeriesColors(palette);
|
|
4113
4125
|
const titleHeight = title ? 40 : 0;
|
|
4114
4126
|
|
|
@@ -4198,10 +4210,10 @@ export function renderVenn(
|
|
|
4198
4210
|
svg
|
|
4199
4211
|
.append('text')
|
|
4200
4212
|
.attr('x', width / 2)
|
|
4201
|
-
.attr('y',
|
|
4213
|
+
.attr('y', 30)
|
|
4202
4214
|
.attr('text-anchor', 'middle')
|
|
4203
4215
|
.attr('fill', textColor)
|
|
4204
|
-
.attr('font-size', '
|
|
4216
|
+
.attr('font-size', '20px')
|
|
4205
4217
|
.attr('font-weight', '700')
|
|
4206
4218
|
.text(title);
|
|
4207
4219
|
}
|
|
@@ -4510,7 +4522,8 @@ export function renderQuadrant(
|
|
|
4510
4522
|
parsed: ParsedD3,
|
|
4511
4523
|
palette: PaletteColors,
|
|
4512
4524
|
isDark: boolean,
|
|
4513
|
-
onClickItem?: (lineNumber: number) => void
|
|
4525
|
+
onClickItem?: (lineNumber: number) => void,
|
|
4526
|
+
exportDims?: D3ExportDimensions
|
|
4514
4527
|
): void {
|
|
4515
4528
|
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
4516
4529
|
|
|
@@ -4527,13 +4540,13 @@ export function renderQuadrant(
|
|
|
4527
4540
|
|
|
4528
4541
|
if (quadrantPoints.length === 0) return;
|
|
4529
4542
|
|
|
4530
|
-
const width = container.clientWidth;
|
|
4531
|
-
const height = container.clientHeight;
|
|
4543
|
+
const width = exportDims?.width ?? container.clientWidth;
|
|
4544
|
+
const height = exportDims?.height ?? container.clientHeight;
|
|
4532
4545
|
if (width <= 0 || height <= 0) return;
|
|
4533
4546
|
|
|
4534
4547
|
const textColor = palette.text;
|
|
4535
4548
|
const mutedColor = palette.textMuted;
|
|
4536
|
-
const bgColor = palette.
|
|
4549
|
+
const bgColor = palette.bg;
|
|
4537
4550
|
const borderColor = palette.border;
|
|
4538
4551
|
|
|
4539
4552
|
// Default quadrant colors with alpha
|
|
@@ -4545,7 +4558,9 @@ export function renderQuadrant(
|
|
|
4545
4558
|
];
|
|
4546
4559
|
|
|
4547
4560
|
// Margins
|
|
4548
|
-
const
|
|
4561
|
+
const hasXAxis = !!quadrantXAxis;
|
|
4562
|
+
const hasYAxis = !!quadrantYAxis;
|
|
4563
|
+
const margin = { top: title ? 60 : 30, right: 30, bottom: hasXAxis ? 70 : 40, left: hasYAxis ? 80 : 40 };
|
|
4549
4564
|
const chartWidth = width - margin.left - margin.right;
|
|
4550
4565
|
const chartHeight = height - margin.top - margin.bottom;
|
|
4551
4566
|
|
|
@@ -4572,7 +4587,7 @@ export function renderQuadrant(
|
|
|
4572
4587
|
.attr('y', 30)
|
|
4573
4588
|
.attr('text-anchor', 'middle')
|
|
4574
4589
|
.attr('fill', textColor)
|
|
4575
|
-
.attr('font-size', '
|
|
4590
|
+
.attr('font-size', '20px')
|
|
4576
4591
|
.attr('font-weight', '700')
|
|
4577
4592
|
.style(
|
|
4578
4593
|
'cursor',
|
|
@@ -4597,7 +4612,19 @@ export function renderQuadrant(
|
|
|
4597
4612
|
.append('g')
|
|
4598
4613
|
.attr('transform', `translate(${margin.left}, ${margin.top})`);
|
|
4599
4614
|
|
|
4600
|
-
//
|
|
4615
|
+
// Mix two hex colors: pct=100 → all `a`, pct=0 → all `b`
|
|
4616
|
+
const mixHex = (a: string, b: string, pct: number): string => {
|
|
4617
|
+
const parse = (h: string) => {
|
|
4618
|
+
const r = h.replace('#', '');
|
|
4619
|
+
const f = r.length === 3 ? r[0]+r[0]+r[1]+r[1]+r[2]+r[2] : r;
|
|
4620
|
+
return [parseInt(f.substring(0,2),16), parseInt(f.substring(2,4),16), parseInt(f.substring(4,6),16)];
|
|
4621
|
+
};
|
|
4622
|
+
const [ar,ag,ab] = parse(a), [br,bg,bb] = parse(b), t = pct/100;
|
|
4623
|
+
const c = (x: number, y: number) => Math.round(x*t + y*(1-t)).toString(16).padStart(2,'0');
|
|
4624
|
+
return `#${c(ar,br)}${c(ag,bg)}${c(ab,bb)}`;
|
|
4625
|
+
};
|
|
4626
|
+
|
|
4627
|
+
// Opaque quadrant fills using the assigned color directly
|
|
4601
4628
|
const getQuadrantFill = (
|
|
4602
4629
|
label: QuadrantLabel | null,
|
|
4603
4630
|
defaultIdx: number
|
|
@@ -4678,11 +4705,17 @@ export function renderQuadrant(
|
|
|
4678
4705
|
.attr('stroke', borderColor)
|
|
4679
4706
|
.attr('stroke-width', 0.5);
|
|
4680
4707
|
|
|
4681
|
-
//
|
|
4682
|
-
const contrastColor =
|
|
4683
|
-
const shadowColor =
|
|
4708
|
+
// White text for points; quadrant labels use a darkened shade of their fill
|
|
4709
|
+
const contrastColor = '#ffffff';
|
|
4710
|
+
const shadowColor = 'rgba(0,0,0,0.4)';
|
|
4684
4711
|
|
|
4685
|
-
//
|
|
4712
|
+
// Darken the quadrant fill to create a watermark-style label color
|
|
4713
|
+
const getQuadrantLabelColor = (d: (typeof quadrantDefs)[number]): string => {
|
|
4714
|
+
const fill = getQuadrantFill(d.label, d.colorIdx);
|
|
4715
|
+
return mixHex('#000000', fill, 40);
|
|
4716
|
+
};
|
|
4717
|
+
|
|
4718
|
+
// Draw quadrant labels (large, centered, darkened shade of fill — recedes behind points)
|
|
4686
4719
|
const quadrantLabelTexts = chartG
|
|
4687
4720
|
.selectAll('text.quadrant-label')
|
|
4688
4721
|
.data(quadrantDefs.filter((d) => d.label !== null))
|
|
@@ -4693,10 +4726,9 @@ export function renderQuadrant(
|
|
|
4693
4726
|
.attr('y', (d) => d.labelY)
|
|
4694
4727
|
.attr('text-anchor', 'middle')
|
|
4695
4728
|
.attr('dominant-baseline', 'central')
|
|
4696
|
-
.attr('fill',
|
|
4697
|
-
.attr('font-size', '
|
|
4698
|
-
.attr('font-weight', '
|
|
4699
|
-
.style('text-shadow', `0 1px 2px ${shadowColor}`)
|
|
4729
|
+
.attr('fill', (d) => getQuadrantLabelColor(d))
|
|
4730
|
+
.attr('font-size', '48px')
|
|
4731
|
+
.attr('font-weight', '700')
|
|
4700
4732
|
.style('cursor', (d) =>
|
|
4701
4733
|
onClickItem && d.label?.lineNumber ? 'pointer' : 'default'
|
|
4702
4734
|
)
|
|
@@ -4715,46 +4747,36 @@ export function renderQuadrant(
|
|
|
4715
4747
|
});
|
|
4716
4748
|
}
|
|
4717
4749
|
|
|
4718
|
-
// X-axis labels
|
|
4750
|
+
// X-axis labels — centered on left/right halves
|
|
4719
4751
|
if (quadrantXAxis) {
|
|
4720
|
-
// Low label (left)
|
|
4752
|
+
// Low label (centered on left half)
|
|
4721
4753
|
const xLowLabel = svg
|
|
4722
4754
|
.append('text')
|
|
4723
|
-
.attr('x', margin.left)
|
|
4724
|
-
.attr('y', height -
|
|
4725
|
-
.attr('text-anchor', '
|
|
4755
|
+
.attr('x', margin.left + chartWidth / 4)
|
|
4756
|
+
.attr('y', height - 20)
|
|
4757
|
+
.attr('text-anchor', 'middle')
|
|
4726
4758
|
.attr('fill', textColor)
|
|
4727
|
-
.attr('font-size', '
|
|
4759
|
+
.attr('font-size', '18px')
|
|
4728
4760
|
.style(
|
|
4729
4761
|
'cursor',
|
|
4730
4762
|
onClickItem && quadrantXAxisLineNumber ? 'pointer' : 'default'
|
|
4731
4763
|
)
|
|
4732
4764
|
.text(quadrantXAxis[0]);
|
|
4733
4765
|
|
|
4734
|
-
// High label (right)
|
|
4766
|
+
// High label (centered on right half)
|
|
4735
4767
|
const xHighLabel = svg
|
|
4736
4768
|
.append('text')
|
|
4737
|
-
.attr('x',
|
|
4738
|
-
.attr('y', height -
|
|
4739
|
-
.attr('text-anchor', '
|
|
4769
|
+
.attr('x', margin.left + (chartWidth * 3) / 4)
|
|
4770
|
+
.attr('y', height - 20)
|
|
4771
|
+
.attr('text-anchor', 'middle')
|
|
4740
4772
|
.attr('fill', textColor)
|
|
4741
|
-
.attr('font-size', '
|
|
4773
|
+
.attr('font-size', '18px')
|
|
4742
4774
|
.style(
|
|
4743
4775
|
'cursor',
|
|
4744
4776
|
onClickItem && quadrantXAxisLineNumber ? 'pointer' : 'default'
|
|
4745
4777
|
)
|
|
4746
4778
|
.text(quadrantXAxis[1]);
|
|
4747
4779
|
|
|
4748
|
-
// Arrow in the middle
|
|
4749
|
-
svg
|
|
4750
|
-
.append('text')
|
|
4751
|
-
.attr('x', width / 2)
|
|
4752
|
-
.attr('y', height - 15)
|
|
4753
|
-
.attr('text-anchor', 'middle')
|
|
4754
|
-
.attr('fill', mutedColor)
|
|
4755
|
-
.attr('font-size', '12px')
|
|
4756
|
-
.text('→');
|
|
4757
|
-
|
|
4758
4780
|
if (onClickItem && quadrantXAxisLineNumber) {
|
|
4759
4781
|
[xLowLabel, xHighLabel].forEach((label) => {
|
|
4760
4782
|
label
|
|
@@ -4769,49 +4791,41 @@ export function renderQuadrant(
|
|
|
4769
4791
|
}
|
|
4770
4792
|
}
|
|
4771
4793
|
|
|
4772
|
-
// Y-axis labels
|
|
4794
|
+
// Y-axis labels — centered on top/bottom halves
|
|
4773
4795
|
if (quadrantYAxis) {
|
|
4774
|
-
|
|
4796
|
+
const yMidBottom = margin.top + (chartHeight * 3) / 4;
|
|
4797
|
+
const yMidTop = margin.top + chartHeight / 4;
|
|
4798
|
+
|
|
4799
|
+
// Low label (centered on bottom half)
|
|
4775
4800
|
const yLowLabel = svg
|
|
4776
4801
|
.append('text')
|
|
4777
|
-
.attr('x',
|
|
4778
|
-
.attr('y',
|
|
4779
|
-
.attr('text-anchor', '
|
|
4802
|
+
.attr('x', 22)
|
|
4803
|
+
.attr('y', yMidBottom)
|
|
4804
|
+
.attr('text-anchor', 'middle')
|
|
4780
4805
|
.attr('fill', textColor)
|
|
4781
|
-
.attr('font-size', '
|
|
4782
|
-
.attr('transform', `rotate(-90,
|
|
4806
|
+
.attr('font-size', '18px')
|
|
4807
|
+
.attr('transform', `rotate(-90, 22, ${yMidBottom})`)
|
|
4783
4808
|
.style(
|
|
4784
4809
|
'cursor',
|
|
4785
4810
|
onClickItem && quadrantYAxisLineNumber ? 'pointer' : 'default'
|
|
4786
4811
|
)
|
|
4787
4812
|
.text(quadrantYAxis[0]);
|
|
4788
4813
|
|
|
4789
|
-
// High label (top)
|
|
4814
|
+
// High label (centered on top half)
|
|
4790
4815
|
const yHighLabel = svg
|
|
4791
4816
|
.append('text')
|
|
4792
|
-
.attr('x',
|
|
4793
|
-
.attr('y',
|
|
4794
|
-
.attr('text-anchor', '
|
|
4817
|
+
.attr('x', 22)
|
|
4818
|
+
.attr('y', yMidTop)
|
|
4819
|
+
.attr('text-anchor', 'middle')
|
|
4795
4820
|
.attr('fill', textColor)
|
|
4796
|
-
.attr('font-size', '
|
|
4797
|
-
.attr('transform', `rotate(-90,
|
|
4821
|
+
.attr('font-size', '18px')
|
|
4822
|
+
.attr('transform', `rotate(-90, 22, ${yMidTop})`)
|
|
4798
4823
|
.style(
|
|
4799
4824
|
'cursor',
|
|
4800
4825
|
onClickItem && quadrantYAxisLineNumber ? 'pointer' : 'default'
|
|
4801
4826
|
)
|
|
4802
4827
|
.text(quadrantYAxis[1]);
|
|
4803
4828
|
|
|
4804
|
-
// Arrow in the middle
|
|
4805
|
-
svg
|
|
4806
|
-
.append('text')
|
|
4807
|
-
.attr('x', 15)
|
|
4808
|
-
.attr('y', height / 2)
|
|
4809
|
-
.attr('text-anchor', 'middle')
|
|
4810
|
-
.attr('fill', mutedColor)
|
|
4811
|
-
.attr('font-size', '12px')
|
|
4812
|
-
.attr('transform', `rotate(-90, 15, ${height / 2})`)
|
|
4813
|
-
.text('→');
|
|
4814
|
-
|
|
4815
4829
|
if (onClickItem && quadrantYAxisLineNumber) {
|
|
4816
4830
|
[yLowLabel, yHighLabel].forEach((label) => {
|
|
4817
4831
|
label
|
|
@@ -4866,13 +4880,13 @@ export function renderQuadrant(
|
|
|
4866
4880
|
|
|
4867
4881
|
const pointG = pointsG.append('g').attr('class', 'point-group');
|
|
4868
4882
|
|
|
4869
|
-
// Circle
|
|
4883
|
+
// Circle with white fill and colored border for visibility on opaque quadrants
|
|
4870
4884
|
pointG
|
|
4871
4885
|
.append('circle')
|
|
4872
4886
|
.attr('cx', cx)
|
|
4873
4887
|
.attr('cy', cy)
|
|
4874
4888
|
.attr('r', 6)
|
|
4875
|
-
.attr('fill',
|
|
4889
|
+
.attr('fill', '#ffffff')
|
|
4876
4890
|
.attr('stroke', pointColor)
|
|
4877
4891
|
.attr('stroke-width', 2);
|
|
4878
4892
|
|
|
@@ -4883,7 +4897,8 @@ export function renderQuadrant(
|
|
|
4883
4897
|
.attr('y', cy - 10)
|
|
4884
4898
|
.attr('text-anchor', 'middle')
|
|
4885
4899
|
.attr('fill', contrastColor)
|
|
4886
|
-
.attr('font-size', '
|
|
4900
|
+
.attr('font-size', '12px')
|
|
4901
|
+
.attr('font-weight', '700')
|
|
4887
4902
|
.style('text-shadow', `0 1px 2px ${shadowColor}`)
|
|
4888
4903
|
.text(point.label);
|
|
4889
4904
|
|
|
@@ -4958,7 +4973,15 @@ export async function renderD3ForExport(
|
|
|
4958
4973
|
palette?: PaletteColors
|
|
4959
4974
|
): Promise<string> {
|
|
4960
4975
|
const parsed = parseD3(content, palette);
|
|
4961
|
-
if
|
|
4976
|
+
// Allow sequence diagrams through even if parseD3 errors —
|
|
4977
|
+
// sequence is parsed by its own dedicated parser (parseSequenceDgmo)
|
|
4978
|
+
// and may not have a "chart:" line (auto-detected from arrow syntax).
|
|
4979
|
+
if (parsed.error && parsed.type !== 'sequence') {
|
|
4980
|
+
// Check if content looks like a sequence diagram (has arrows but no chart: line)
|
|
4981
|
+
const looksLikeSequence = /->|~>|<-/.test(content);
|
|
4982
|
+
if (!looksLikeSequence) return '';
|
|
4983
|
+
parsed.type = 'sequence';
|
|
4984
|
+
}
|
|
4962
4985
|
if (parsed.type === 'wordcloud' && parsed.words.length === 0) return '';
|
|
4963
4986
|
if (parsed.type === 'slope' && parsed.data.length === 0) return '';
|
|
4964
4987
|
if (parsed.type === 'arc' && parsed.links.length === 0) return '';
|
|
@@ -4983,33 +5006,39 @@ export async function renderD3ForExport(
|
|
|
4983
5006
|
container.style.left = '-9999px';
|
|
4984
5007
|
document.body.appendChild(container);
|
|
4985
5008
|
|
|
5009
|
+
const dims: D3ExportDimensions = { width: EXPORT_WIDTH, height: EXPORT_HEIGHT };
|
|
5010
|
+
|
|
4986
5011
|
try {
|
|
4987
5012
|
if (parsed.type === 'sequence') {
|
|
4988
5013
|
const { parseSequenceDgmo } = await import('./sequence/parser');
|
|
4989
5014
|
const { renderSequenceDiagram } = await import('./sequence/renderer');
|
|
4990
5015
|
const seqParsed = parseSequenceDgmo(content);
|
|
4991
5016
|
if (seqParsed.error || seqParsed.participants.length === 0) return '';
|
|
4992
|
-
renderSequenceDiagram(container, seqParsed, effectivePalette, isDark
|
|
5017
|
+
renderSequenceDiagram(container, seqParsed, effectivePalette, isDark, undefined, {
|
|
5018
|
+
exportWidth: EXPORT_WIDTH,
|
|
5019
|
+
});
|
|
4993
5020
|
} else if (parsed.type === 'wordcloud') {
|
|
4994
|
-
await renderWordCloudAsync(container, parsed, effectivePalette, isDark);
|
|
5021
|
+
await renderWordCloudAsync(container, parsed, effectivePalette, isDark, dims);
|
|
4995
5022
|
} else if (parsed.type === 'arc') {
|
|
4996
|
-
renderArcDiagram(container, parsed, effectivePalette, isDark);
|
|
5023
|
+
renderArcDiagram(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
4997
5024
|
} else if (parsed.type === 'timeline') {
|
|
4998
|
-
renderTimeline(container, parsed, effectivePalette, isDark);
|
|
5025
|
+
renderTimeline(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
4999
5026
|
} else if (parsed.type === 'venn') {
|
|
5000
|
-
renderVenn(container, parsed, effectivePalette, isDark);
|
|
5027
|
+
renderVenn(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
5001
5028
|
} else if (parsed.type === 'quadrant') {
|
|
5002
|
-
renderQuadrant(container, parsed, effectivePalette, isDark);
|
|
5029
|
+
renderQuadrant(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
5003
5030
|
} else {
|
|
5004
|
-
renderSlopeChart(container, parsed, effectivePalette, isDark);
|
|
5031
|
+
renderSlopeChart(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
5005
5032
|
}
|
|
5006
5033
|
|
|
5007
5034
|
const svgEl = container.querySelector('svg');
|
|
5008
5035
|
if (!svgEl) return '';
|
|
5009
5036
|
|
|
5010
|
-
//
|
|
5037
|
+
// Ensure all chart types have a consistent background
|
|
5011
5038
|
if (theme === 'transparent') {
|
|
5012
5039
|
svgEl.style.background = 'none';
|
|
5040
|
+
} else if (!svgEl.style.background) {
|
|
5041
|
+
svgEl.style.background = effectivePalette.bg;
|
|
5013
5042
|
}
|
|
5014
5043
|
|
|
5015
5044
|
// Add xmlns for standalone SVG
|