@vanduo-oss/vd3-cbun 1.4.0 → 1.4.2
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/CHANGELOG.md +121 -0
- package/README.md +18 -6
- package/SKILL.md +21 -5
- package/dist/charts/core.d.ts +6 -0
- package/dist/charts/index.cjs +235 -14
- package/dist/charts/index.cjs.map +3 -3
- package/dist/charts/index.js +235 -14
- package/dist/charts/index.js.map +3 -3
- package/dist/charts/vd3-charts.css +82 -0
- package/dist/charts/vue.d.ts +6 -0
- package/dist/code-editor/vd3-code-editor.css +5 -0
- package/dist/draw/core.d.ts +7 -1
- package/dist/draw/index.cjs +484 -52
- package/dist/draw/index.cjs.map +2 -2
- package/dist/draw/index.js +484 -52
- package/dist/draw/index.js.map +2 -2
- package/dist/draw/vd3-draw.css +45 -0
- package/dist/draw/vue.d.ts +4 -0
- package/dist/hex-grid/core.d.ts +41 -0
- package/dist/hex-grid/index.cjs +311 -13
- package/dist/hex-grid/index.cjs.map +3 -3
- package/dist/hex-grid/index.d.ts +1 -0
- package/dist/hex-grid/index.js +311 -13
- package/dist/hex-grid/index.js.map +3 -3
- package/dist/hex-grid/vue.d.ts +4 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/meta.json +33 -33
- package/package.json +10 -10
package/dist/charts/index.cjs
CHANGED
|
@@ -180,9 +180,22 @@ function normalizeMargin(value, fallback = DEFAULT_MARGIN) {
|
|
|
180
180
|
left: Number(value?.left ?? fallback.left)
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
|
+
function dataTableContribution(target) {
|
|
184
|
+
if (!isElement(target) || typeof target.querySelectorAll !== "function") return 0;
|
|
185
|
+
let total = 0;
|
|
186
|
+
target.querySelectorAll(".vd-chart-data-table:not(.vd-chart-sr-only)").forEach((table) => {
|
|
187
|
+
const style = typeof getComputedStyle === "function" ? getComputedStyle(table) : null;
|
|
188
|
+
const marginTop = style ? parseFloat(style.marginTop) || 0 : 0;
|
|
189
|
+
const marginBottom = style ? parseFloat(style.marginBottom) || 0 : 0;
|
|
190
|
+
total += (table.offsetHeight || 0) + marginTop + marginBottom;
|
|
191
|
+
});
|
|
192
|
+
return total;
|
|
193
|
+
}
|
|
183
194
|
function measureTarget(target, options) {
|
|
184
195
|
const width = Number(options.width) || Math.round(target.clientWidth) || DEFAULT_WIDTH;
|
|
185
|
-
const
|
|
196
|
+
const targetHeight = Math.round(Number(target.clientHeight));
|
|
197
|
+
const availableHeight = Math.max(0, targetHeight - dataTableContribution(target));
|
|
198
|
+
const height = Number(options.height) || (targetHeight > 0 ? availableHeight : DEFAULT_HEIGHT);
|
|
186
199
|
return {
|
|
187
200
|
width: Math.max(160, width),
|
|
188
201
|
height: Math.max(140, height)
|
|
@@ -495,6 +508,74 @@ function makeInteractive(mark, hasClick, hasTooltip) {
|
|
|
495
508
|
mark.setAttribute("focusable", "true");
|
|
496
509
|
}
|
|
497
510
|
}
|
|
511
|
+
function setupKeyboardNavigation(svg, markGroup) {
|
|
512
|
+
svg.addEventListener("keydown", (event) => {
|
|
513
|
+
const active = document.activeElement;
|
|
514
|
+
if (!active || !markGroup.contains(active)) return;
|
|
515
|
+
const marks = Array.from(markGroup.querySelectorAll('[tabindex="0"]'));
|
|
516
|
+
if (!marks.length) return;
|
|
517
|
+
const currentIndex = marks.indexOf(active);
|
|
518
|
+
if (currentIndex === -1) return;
|
|
519
|
+
let targetIndex = -1;
|
|
520
|
+
if (event.key === "ArrowRight" || event.key === "ArrowDown") {
|
|
521
|
+
event.preventDefault();
|
|
522
|
+
targetIndex = (currentIndex + 1) % marks.length;
|
|
523
|
+
} else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
|
|
524
|
+
event.preventDefault();
|
|
525
|
+
targetIndex = (currentIndex - 1 + marks.length) % marks.length;
|
|
526
|
+
} else if (event.key === "Home") {
|
|
527
|
+
event.preventDefault();
|
|
528
|
+
targetIndex = 0;
|
|
529
|
+
} else if (event.key === "End") {
|
|
530
|
+
event.preventDefault();
|
|
531
|
+
targetIndex = marks.length - 1;
|
|
532
|
+
} else if (event.key === "Escape") {
|
|
533
|
+
event.preventDefault();
|
|
534
|
+
active.blur();
|
|
535
|
+
}
|
|
536
|
+
if (targetIndex !== -1 && marks[targetIndex]) {
|
|
537
|
+
marks[targetIndex].focus();
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
function renderDataTable(instance, tableData) {
|
|
542
|
+
const { target, options } = instance;
|
|
543
|
+
if (options.dataTable === false) return;
|
|
544
|
+
if (!tableData || !tableData.rows || !tableData.rows.length) return;
|
|
545
|
+
const table = document.createElement("table");
|
|
546
|
+
table.className = "vd-chart-data-table";
|
|
547
|
+
if (options.dataTable !== "visible") {
|
|
548
|
+
table.classList.add("vd-chart-sr-only");
|
|
549
|
+
}
|
|
550
|
+
const caption = document.createElement("caption");
|
|
551
|
+
caption.textContent = tableData.caption || options.title || options.ariaLabel || `${instance.kind} chart data`;
|
|
552
|
+
table.appendChild(caption);
|
|
553
|
+
if (tableData.headers && tableData.headers.length) {
|
|
554
|
+
const thead = document.createElement("thead");
|
|
555
|
+
const headerRow = document.createElement("tr");
|
|
556
|
+
tableData.headers.forEach((h2) => {
|
|
557
|
+
const th = document.createElement("th");
|
|
558
|
+
th.setAttribute("scope", "col");
|
|
559
|
+
th.textContent = String(h2);
|
|
560
|
+
headerRow.appendChild(th);
|
|
561
|
+
});
|
|
562
|
+
thead.appendChild(headerRow);
|
|
563
|
+
table.appendChild(thead);
|
|
564
|
+
}
|
|
565
|
+
const tbody = document.createElement("tbody");
|
|
566
|
+
tableData.rows.forEach((row) => {
|
|
567
|
+
const tr = document.createElement("tr");
|
|
568
|
+
row.forEach((cell, idx) => {
|
|
569
|
+
const el = idx === 0 ? document.createElement("th") : document.createElement("td");
|
|
570
|
+
if (idx === 0) el.setAttribute("scope", "row");
|
|
571
|
+
el.textContent = cell == null ? "" : String(cell);
|
|
572
|
+
tr.appendChild(el);
|
|
573
|
+
});
|
|
574
|
+
tbody.appendChild(tr);
|
|
575
|
+
});
|
|
576
|
+
table.appendChild(tbody);
|
|
577
|
+
target.appendChild(table);
|
|
578
|
+
}
|
|
498
579
|
function createSvgShell(instance) {
|
|
499
580
|
const { target, options } = instance;
|
|
500
581
|
const size = measureTarget(target, options);
|
|
@@ -511,7 +592,8 @@ function createSvgShell(instance) {
|
|
|
511
592
|
width: size.width,
|
|
512
593
|
height: size.height,
|
|
513
594
|
viewBox: `0 0 ${size.width} ${size.height}`,
|
|
514
|
-
role: "
|
|
595
|
+
role: options.role || "graphics-document document",
|
|
596
|
+
"aria-roledescription": options.ariaRoleDescription || `${instance.kind} chart`
|
|
515
597
|
});
|
|
516
598
|
const titleId = nextId("vd-chart-title");
|
|
517
599
|
const descId = nextId("vd-chart-desc");
|
|
@@ -582,7 +664,7 @@ function drawAxisLine(svg, x1, y1, x2, y2, theme) {
|
|
|
582
664
|
}
|
|
583
665
|
function drawCartesianAxes(svg, config) {
|
|
584
666
|
const { plot, xScale, yScale, xTicks, yTicks, theme, options, categoricalX } = config;
|
|
585
|
-
const axisGroup = append(svg, svgEl("g", { class: "vd-chart-axes" }));
|
|
667
|
+
const axisGroup = append(svg, svgEl("g", { class: "vd-chart-axes", "aria-hidden": "true" }));
|
|
586
668
|
yTicks.forEach((tick) => {
|
|
587
669
|
const y = yScale(tick);
|
|
588
670
|
if (!isFiniteNumber(y)) return;
|
|
@@ -825,7 +907,14 @@ function drawDataLabel(svg, x, y, value, cfg, theme, anchor = "middle") {
|
|
|
825
907
|
function drawAnnotations(svg, options, plot, xScale, yScale, theme) {
|
|
826
908
|
const annotations = toArray(options.annotations);
|
|
827
909
|
if (!annotations.length) return;
|
|
828
|
-
const group = append(
|
|
910
|
+
const group = append(
|
|
911
|
+
svg,
|
|
912
|
+
svgEl("g", {
|
|
913
|
+
class: "vd-chart-annotations",
|
|
914
|
+
role: "graphics-object",
|
|
915
|
+
"aria-roledescription": "annotations"
|
|
916
|
+
})
|
|
917
|
+
);
|
|
829
918
|
annotations.forEach((ann) => {
|
|
830
919
|
const color = ann.color || theme.mutedTextColor;
|
|
831
920
|
const dash = ann.dash === false ? null : "4 3";
|
|
@@ -945,7 +1034,14 @@ function renderBarChart(instance) {
|
|
|
945
1034
|
});
|
|
946
1035
|
drawAnnotations(svg, options, plot, xScale, yScale, theme);
|
|
947
1036
|
const labels = dataLabelConfig(options);
|
|
948
|
-
const markGroup = append(
|
|
1037
|
+
const markGroup = append(
|
|
1038
|
+
svg,
|
|
1039
|
+
svgEl("g", {
|
|
1040
|
+
class: "vd-chart-marks vd-chart-bars",
|
|
1041
|
+
role: "graphics-object",
|
|
1042
|
+
"aria-roledescription": "data points"
|
|
1043
|
+
})
|
|
1044
|
+
);
|
|
949
1045
|
const baseline = yScale(Math.min(Math.max(0, yDomain[0]), yDomain[1]));
|
|
950
1046
|
rows.forEach((row) => {
|
|
951
1047
|
const x = xScale(row.x);
|
|
@@ -963,6 +1059,7 @@ function renderBarChart(instance) {
|
|
|
963
1059
|
rx: 3,
|
|
964
1060
|
fill,
|
|
965
1061
|
role: "graphics-symbol",
|
|
1062
|
+
"aria-roledescription": "bar",
|
|
966
1063
|
"aria-label": `${formatCategory(row.x)}: ${formatNumber(row.y)}`
|
|
967
1064
|
});
|
|
968
1065
|
makeInteractive(rect, typeof options.onBarClick === "function", options.tooltip !== false);
|
|
@@ -984,6 +1081,12 @@ function renderBarChart(instance) {
|
|
|
984
1081
|
if (labels) drawDataLabel(svg, x + xScale.bandwidth() / 2, rectY - 4, row.y, labels, theme);
|
|
985
1082
|
append(markGroup, rect);
|
|
986
1083
|
});
|
|
1084
|
+
setupKeyboardNavigation(svg, markGroup);
|
|
1085
|
+
renderDataTable(instance, {
|
|
1086
|
+
caption: options.title || options.ariaLabel || `${instance.kind} chart data`,
|
|
1087
|
+
headers: [options.xAxis?.label || "Category", options.yAxis?.label || "Value"],
|
|
1088
|
+
rows: rows.map((r) => [formatCategory(r.x), formatNumber(r.y)])
|
|
1089
|
+
});
|
|
987
1090
|
if (options.legend && color && color.scale) {
|
|
988
1091
|
renderTopLegend(
|
|
989
1092
|
svg,
|
|
@@ -1037,7 +1140,14 @@ function renderMultiBarChart(instance) {
|
|
|
1037
1140
|
});
|
|
1038
1141
|
drawAnnotations(svg, options, plot, xScale, yScale, theme);
|
|
1039
1142
|
const labels = dataLabelConfig(options);
|
|
1040
|
-
const markGroup = append(
|
|
1143
|
+
const markGroup = append(
|
|
1144
|
+
svg,
|
|
1145
|
+
svgEl("g", {
|
|
1146
|
+
class: "vd-chart-marks vd-chart-bars",
|
|
1147
|
+
role: "graphics-object",
|
|
1148
|
+
"aria-roledescription": "grouped bars"
|
|
1149
|
+
})
|
|
1150
|
+
);
|
|
1041
1151
|
const baseline = yScale(Math.min(Math.max(0, yDomain[0]), yDomain[1]));
|
|
1042
1152
|
seriesList.forEach((series) => {
|
|
1043
1153
|
const fill = seriesColor(series, theme);
|
|
@@ -1057,6 +1167,7 @@ function renderMultiBarChart(instance) {
|
|
|
1057
1167
|
rx: 3,
|
|
1058
1168
|
fill,
|
|
1059
1169
|
role: "graphics-symbol",
|
|
1170
|
+
"aria-roledescription": "bar",
|
|
1060
1171
|
"aria-label": `${series.name} \u2014 ${formatCategory(row.x)}: ${formatNumber(row.y)}`
|
|
1061
1172
|
});
|
|
1062
1173
|
makeInteractive(rect, typeof options.onBarClick === "function", options.tooltip !== false);
|
|
@@ -1089,6 +1200,20 @@ function renderMultiBarChart(instance) {
|
|
|
1089
1200
|
append(markGroup, rect);
|
|
1090
1201
|
});
|
|
1091
1202
|
});
|
|
1203
|
+
setupKeyboardNavigation(svg, markGroup);
|
|
1204
|
+
const tableRows = categories.map((cat) => {
|
|
1205
|
+
const rowCells = [cat];
|
|
1206
|
+
seriesList.forEach((s) => {
|
|
1207
|
+
const match = s.rows.find((r) => String(r.x) === cat);
|
|
1208
|
+
rowCells.push(match && isFiniteNumber(match.y) ? formatNumber(match.y) : "");
|
|
1209
|
+
});
|
|
1210
|
+
return rowCells;
|
|
1211
|
+
});
|
|
1212
|
+
renderDataTable(instance, {
|
|
1213
|
+
caption: options.title || options.ariaLabel || `${instance.kind} chart data`,
|
|
1214
|
+
headers: [options.xAxis?.label || "Category", ...seriesList.map((s) => s.name)],
|
|
1215
|
+
rows: tableRows
|
|
1216
|
+
});
|
|
1092
1217
|
if (options.legend !== false) {
|
|
1093
1218
|
renderTopLegend(
|
|
1094
1219
|
svg,
|
|
@@ -1149,7 +1274,14 @@ function renderLineLikeChart(instance, mode) {
|
|
|
1149
1274
|
});
|
|
1150
1275
|
drawAnnotations(svg, options, plot, xInfo.scale, yScale, theme);
|
|
1151
1276
|
const labels = dataLabelConfig(options);
|
|
1152
|
-
const markGroup = append(
|
|
1277
|
+
const markGroup = append(
|
|
1278
|
+
svg,
|
|
1279
|
+
svgEl("g", {
|
|
1280
|
+
class: `vd-chart-marks vd-chart-${mode}`,
|
|
1281
|
+
role: "graphics-object",
|
|
1282
|
+
"aria-roledescription": `${mode} series`
|
|
1283
|
+
})
|
|
1284
|
+
);
|
|
1153
1285
|
if (mode === "area") {
|
|
1154
1286
|
const baseline = yScale(Math.max(0, yDomain[0]));
|
|
1155
1287
|
append(
|
|
@@ -1186,6 +1318,7 @@ function renderLineLikeChart(instance, mode) {
|
|
|
1186
1318
|
stroke: color,
|
|
1187
1319
|
"stroke-width": 2,
|
|
1188
1320
|
role: "graphics-symbol",
|
|
1321
|
+
"aria-roledescription": "data point",
|
|
1189
1322
|
"aria-label": `${formatCategory(point.xValue)}: ${formatNumber(point.yValue)}`
|
|
1190
1323
|
});
|
|
1191
1324
|
makeInteractive(
|
|
@@ -1215,6 +1348,12 @@ function renderLineLikeChart(instance, mode) {
|
|
|
1215
1348
|
(point) => drawDataLabel(svg, point.x, point.y - 8, point.yValue, labels, theme)
|
|
1216
1349
|
);
|
|
1217
1350
|
}
|
|
1351
|
+
setupKeyboardNavigation(svg, markGroup);
|
|
1352
|
+
renderDataTable(instance, {
|
|
1353
|
+
caption: options.title || options.ariaLabel || `${instance.kind} chart data`,
|
|
1354
|
+
headers: [options.xAxis?.label || "X", options.yAxis?.label || "Y"],
|
|
1355
|
+
rows: points.map((p) => [formatCategory(p.xValue), formatNumber(p.yValue)])
|
|
1356
|
+
});
|
|
1218
1357
|
}
|
|
1219
1358
|
function renderMultiLineChart(instance, mode) {
|
|
1220
1359
|
const shell = createSvgShell(instance);
|
|
@@ -1250,7 +1389,14 @@ function renderMultiLineChart(instance, mode) {
|
|
|
1250
1389
|
});
|
|
1251
1390
|
drawAnnotations(svg, options, plot, xInfo.scale, yScale, theme);
|
|
1252
1391
|
const labels = dataLabelConfig(options);
|
|
1253
|
-
const markGroup = append(
|
|
1392
|
+
const markGroup = append(
|
|
1393
|
+
svg,
|
|
1394
|
+
svgEl("g", {
|
|
1395
|
+
class: `vd-chart-marks vd-chart-${mode}`,
|
|
1396
|
+
role: "graphics-object",
|
|
1397
|
+
"aria-roledescription": `multi-series ${mode}`
|
|
1398
|
+
})
|
|
1399
|
+
);
|
|
1254
1400
|
const baseline = yScale(Math.max(0, yDomain[0]));
|
|
1255
1401
|
seriesList.forEach((series) => {
|
|
1256
1402
|
const stroke = seriesColor(series, theme);
|
|
@@ -1297,6 +1443,7 @@ function renderMultiLineChart(instance, mode) {
|
|
|
1297
1443
|
stroke,
|
|
1298
1444
|
"stroke-width": 2,
|
|
1299
1445
|
role: "graphics-symbol",
|
|
1446
|
+
"aria-roledescription": "data point",
|
|
1300
1447
|
"aria-label": `${series.name} ${formatCategory(point.xValue)}: ${formatNumber(point.yValue)}`
|
|
1301
1448
|
});
|
|
1302
1449
|
makeInteractive(
|
|
@@ -1329,6 +1476,31 @@ function renderMultiLineChart(instance, mode) {
|
|
|
1329
1476
|
);
|
|
1330
1477
|
}
|
|
1331
1478
|
});
|
|
1479
|
+
setupKeyboardNavigation(svg, markGroup);
|
|
1480
|
+
const xKey = (value) => String(xInfo.mapValue(value));
|
|
1481
|
+
const uniqueX = /* @__PURE__ */ new Map();
|
|
1482
|
+
for (const row of allRows) {
|
|
1483
|
+
const key = xKey(row.x);
|
|
1484
|
+
if (!uniqueX.has(key)) uniqueX.set(key, row.x);
|
|
1485
|
+
}
|
|
1486
|
+
const seriesValues = seriesList.map((s) => {
|
|
1487
|
+
const byX = /* @__PURE__ */ new Map();
|
|
1488
|
+
for (const row of s.rows) byX.set(xKey(row.x), row.y);
|
|
1489
|
+
return byX;
|
|
1490
|
+
});
|
|
1491
|
+
const tableRows = [...uniqueX.entries()].map(([key, xVal]) => {
|
|
1492
|
+
const rowCells = [formatCategory(xVal)];
|
|
1493
|
+
seriesValues.forEach((byX) => {
|
|
1494
|
+
const y = byX.get(key);
|
|
1495
|
+
rowCells.push(isFiniteNumber(y) ? formatNumber(y) : "");
|
|
1496
|
+
});
|
|
1497
|
+
return rowCells;
|
|
1498
|
+
});
|
|
1499
|
+
renderDataTable(instance, {
|
|
1500
|
+
caption: options.title || options.ariaLabel || `${instance.kind} chart data`,
|
|
1501
|
+
headers: [options.xAxis?.label || "X", ...seriesList.map((s) => s.name)],
|
|
1502
|
+
rows: tableRows
|
|
1503
|
+
});
|
|
1332
1504
|
if (options.legend !== false) {
|
|
1333
1505
|
renderTopLegend(
|
|
1334
1506
|
svg,
|
|
@@ -1380,7 +1552,14 @@ function renderScatterChart(instance) {
|
|
|
1380
1552
|
});
|
|
1381
1553
|
drawAnnotations(svg, options, plot, xInfo.scale, yScale, theme);
|
|
1382
1554
|
const labels = dataLabelConfig(options);
|
|
1383
|
-
const markGroup = append(
|
|
1555
|
+
const markGroup = append(
|
|
1556
|
+
svg,
|
|
1557
|
+
svgEl("g", {
|
|
1558
|
+
class: "vd-chart-marks vd-chart-scatter",
|
|
1559
|
+
role: "graphics-object",
|
|
1560
|
+
"aria-roledescription": "data points"
|
|
1561
|
+
})
|
|
1562
|
+
);
|
|
1384
1563
|
rows.forEach((row) => {
|
|
1385
1564
|
const cx = xInfo.scale(xInfo.mapValue(row.x));
|
|
1386
1565
|
const cy = yScale(row.y);
|
|
@@ -1394,6 +1573,7 @@ function renderScatterChart(instance) {
|
|
|
1394
1573
|
fill,
|
|
1395
1574
|
opacity: options.pointOpacity ?? 0.88,
|
|
1396
1575
|
role: "graphics-symbol",
|
|
1576
|
+
"aria-roledescription": "data point",
|
|
1397
1577
|
"aria-label": `${formatCategory(row.x)}: ${formatNumber(row.y)}`
|
|
1398
1578
|
});
|
|
1399
1579
|
makeInteractive(circle, typeof options.onPointClick === "function", options.tooltip !== false);
|
|
@@ -1414,6 +1594,12 @@ function renderScatterChart(instance) {
|
|
|
1414
1594
|
if (labels) drawDataLabel(svg, cx, cy - 8, row.y, labels, theme);
|
|
1415
1595
|
append(markGroup, circle);
|
|
1416
1596
|
});
|
|
1597
|
+
setupKeyboardNavigation(svg, markGroup);
|
|
1598
|
+
renderDataTable(instance, {
|
|
1599
|
+
caption: options.title || options.ariaLabel || `${instance.kind} chart data`,
|
|
1600
|
+
headers: [options.xAxis?.label || "X", options.yAxis?.label || "Y"],
|
|
1601
|
+
rows: rows.map((r) => [formatCategory(r.x), formatNumber(r.y)])
|
|
1602
|
+
});
|
|
1417
1603
|
if (options.legend && color && color.scale) {
|
|
1418
1604
|
renderTopLegend(
|
|
1419
1605
|
svg,
|
|
@@ -1482,7 +1668,14 @@ function renderDonutChart(instance) {
|
|
|
1482
1668
|
const innerRadius = outerRadius * ratio;
|
|
1483
1669
|
const colorScale = scaleOrdinal({ domain: rows.map((row) => row.label), range: theme.colors });
|
|
1484
1670
|
const labels = dataLabelConfig(options);
|
|
1485
|
-
const markGroup = append(
|
|
1671
|
+
const markGroup = append(
|
|
1672
|
+
svg,
|
|
1673
|
+
svgEl("g", {
|
|
1674
|
+
class: "vd-chart-marks vd-chart-slices",
|
|
1675
|
+
role: "graphics-object",
|
|
1676
|
+
"aria-roledescription": "pie slices"
|
|
1677
|
+
})
|
|
1678
|
+
);
|
|
1486
1679
|
let cursor = -Math.PI / 2;
|
|
1487
1680
|
rows.forEach((row) => {
|
|
1488
1681
|
const angle = row.value / total * TAU;
|
|
@@ -1496,6 +1689,7 @@ function renderDonutChart(instance) {
|
|
|
1496
1689
|
stroke: theme.backgroundColor,
|
|
1497
1690
|
"stroke-width": 2,
|
|
1498
1691
|
role: "graphics-symbol",
|
|
1692
|
+
"aria-roledescription": "slice",
|
|
1499
1693
|
"aria-label": `${formatCategory(row.label)}: ${formatNumber(row.value)}`
|
|
1500
1694
|
});
|
|
1501
1695
|
makeInteractive(path, typeof options.onSliceClick === "function", options.tooltip !== false);
|
|
@@ -1520,6 +1714,16 @@ function renderDonutChart(instance) {
|
|
|
1520
1714
|
}
|
|
1521
1715
|
append(markGroup, path);
|
|
1522
1716
|
});
|
|
1717
|
+
setupKeyboardNavigation(svg, markGroup);
|
|
1718
|
+
renderDataTable(instance, {
|
|
1719
|
+
caption: options.title || options.ariaLabel || `${instance.kind} chart data`,
|
|
1720
|
+
headers: ["Category", "Value", "Percentage"],
|
|
1721
|
+
rows: rows.map((r) => [
|
|
1722
|
+
formatCategory(r.label),
|
|
1723
|
+
formatNumber(r.value),
|
|
1724
|
+
total > 0 ? `${Math.round(r.value / total * 100)}%` : "0%"
|
|
1725
|
+
])
|
|
1726
|
+
});
|
|
1523
1727
|
if (innerRadius > 16 && options.centerLabel !== false) {
|
|
1524
1728
|
append(
|
|
1525
1729
|
svg,
|
|
@@ -1570,7 +1774,11 @@ var ChartInstance = class {
|
|
|
1570
1774
|
}
|
|
1571
1775
|
render() {
|
|
1572
1776
|
if (this.destroyed) return this;
|
|
1777
|
+
const tableHeight = dataTableContribution(this.target);
|
|
1573
1778
|
this.renderer(this);
|
|
1779
|
+
if (!Number(this.options.height) && dataTableContribution(this.target) !== tableHeight) {
|
|
1780
|
+
this.renderer(this);
|
|
1781
|
+
}
|
|
1574
1782
|
return this;
|
|
1575
1783
|
}
|
|
1576
1784
|
update(nextOptions = {}) {
|
|
@@ -1584,11 +1792,12 @@ var ChartInstance = class {
|
|
|
1584
1792
|
setupResizeObserver() {
|
|
1585
1793
|
if (this.options.responsive === false || !hasWindow() || typeof ResizeObserver === "undefined")
|
|
1586
1794
|
return;
|
|
1795
|
+
const chartHeight = () => Math.max(0, this.target.clientHeight - dataTableContribution(this.target));
|
|
1587
1796
|
let lastWidth = this.target.clientWidth;
|
|
1588
|
-
let lastHeight =
|
|
1797
|
+
let lastHeight = chartHeight();
|
|
1589
1798
|
this.resizeObserver = new ResizeObserver(() => {
|
|
1590
1799
|
const width = this.target.clientWidth;
|
|
1591
|
-
const height =
|
|
1800
|
+
const height = chartHeight();
|
|
1592
1801
|
if (width === lastWidth && height === lastHeight) return;
|
|
1593
1802
|
lastWidth = width;
|
|
1594
1803
|
lastHeight = height;
|
|
@@ -1702,7 +1911,13 @@ var CHART_PROPS = {
|
|
|
1702
1911
|
xFormat: { type: Function, default: void 0 },
|
|
1703
1912
|
yFormat: { type: Function, default: void 0 },
|
|
1704
1913
|
xAxis: { type: Object, default: void 0 },
|
|
1705
|
-
yAxis: { type: Object, default: void 0 }
|
|
1914
|
+
yAxis: { type: Object, default: void 0 },
|
|
1915
|
+
// Accessible data table: `true` (sr-only, default) / `false` / `'visible'`.
|
|
1916
|
+
dataTable: { type: [Boolean, String], default: void 0 },
|
|
1917
|
+
// Override the SVG `aria-roledescription` (defaults to `${kind} chart`).
|
|
1918
|
+
ariaRoleDescription: { type: String, default: void 0 },
|
|
1919
|
+
// Avoid colliding with the standard root `role` fallthrough attribute.
|
|
1920
|
+
svgRole: { type: String, default: void 0 }
|
|
1706
1921
|
};
|
|
1707
1922
|
function optionsFrom(target, props, emit) {
|
|
1708
1923
|
return {
|
|
@@ -1736,6 +1951,9 @@ function optionsFrom(target, props, emit) {
|
|
|
1736
1951
|
yFormat: props.yFormat,
|
|
1737
1952
|
xAxis: props.xAxis,
|
|
1738
1953
|
yAxis: props.yAxis,
|
|
1954
|
+
dataTable: props.dataTable,
|
|
1955
|
+
ariaRoleDescription: props.ariaRoleDescription,
|
|
1956
|
+
role: props.svgRole,
|
|
1739
1957
|
// Forward the core's mark-click callbacks as the Vue emit family. Passing
|
|
1740
1958
|
// all three unconditionally is safe: the core only fires the callback
|
|
1741
1959
|
// relevant to the rendered chart type (bars → onBarClick, points →
|
|
@@ -1794,7 +2012,10 @@ var VdChart = (0, import_vue.defineComponent)({
|
|
|
1794
2012
|
props.xFormat,
|
|
1795
2013
|
props.yFormat,
|
|
1796
2014
|
props.xAxis,
|
|
1797
|
-
props.yAxis
|
|
2015
|
+
props.yAxis,
|
|
2016
|
+
props.dataTable,
|
|
2017
|
+
props.ariaRoleDescription,
|
|
2018
|
+
props.svgRole
|
|
1798
2019
|
],
|
|
1799
2020
|
() => {
|
|
1800
2021
|
if (!instance) return;
|