@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.js
CHANGED
|
@@ -129,9 +129,22 @@ function normalizeMargin(value, fallback = DEFAULT_MARGIN) {
|
|
|
129
129
|
left: Number(value?.left ?? fallback.left)
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
|
+
function dataTableContribution(target) {
|
|
133
|
+
if (!isElement(target) || typeof target.querySelectorAll !== "function") return 0;
|
|
134
|
+
let total = 0;
|
|
135
|
+
target.querySelectorAll(".vd-chart-data-table:not(.vd-chart-sr-only)").forEach((table) => {
|
|
136
|
+
const style = typeof getComputedStyle === "function" ? getComputedStyle(table) : null;
|
|
137
|
+
const marginTop = style ? parseFloat(style.marginTop) || 0 : 0;
|
|
138
|
+
const marginBottom = style ? parseFloat(style.marginBottom) || 0 : 0;
|
|
139
|
+
total += (table.offsetHeight || 0) + marginTop + marginBottom;
|
|
140
|
+
});
|
|
141
|
+
return total;
|
|
142
|
+
}
|
|
132
143
|
function measureTarget(target, options) {
|
|
133
144
|
const width = Number(options.width) || Math.round(target.clientWidth) || DEFAULT_WIDTH;
|
|
134
|
-
const
|
|
145
|
+
const targetHeight = Math.round(Number(target.clientHeight));
|
|
146
|
+
const availableHeight = Math.max(0, targetHeight - dataTableContribution(target));
|
|
147
|
+
const height = Number(options.height) || (targetHeight > 0 ? availableHeight : DEFAULT_HEIGHT);
|
|
135
148
|
return {
|
|
136
149
|
width: Math.max(160, width),
|
|
137
150
|
height: Math.max(140, height)
|
|
@@ -444,6 +457,74 @@ function makeInteractive(mark, hasClick, hasTooltip) {
|
|
|
444
457
|
mark.setAttribute("focusable", "true");
|
|
445
458
|
}
|
|
446
459
|
}
|
|
460
|
+
function setupKeyboardNavigation(svg, markGroup) {
|
|
461
|
+
svg.addEventListener("keydown", (event) => {
|
|
462
|
+
const active = document.activeElement;
|
|
463
|
+
if (!active || !markGroup.contains(active)) return;
|
|
464
|
+
const marks = Array.from(markGroup.querySelectorAll('[tabindex="0"]'));
|
|
465
|
+
if (!marks.length) return;
|
|
466
|
+
const currentIndex = marks.indexOf(active);
|
|
467
|
+
if (currentIndex === -1) return;
|
|
468
|
+
let targetIndex = -1;
|
|
469
|
+
if (event.key === "ArrowRight" || event.key === "ArrowDown") {
|
|
470
|
+
event.preventDefault();
|
|
471
|
+
targetIndex = (currentIndex + 1) % marks.length;
|
|
472
|
+
} else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
|
|
473
|
+
event.preventDefault();
|
|
474
|
+
targetIndex = (currentIndex - 1 + marks.length) % marks.length;
|
|
475
|
+
} else if (event.key === "Home") {
|
|
476
|
+
event.preventDefault();
|
|
477
|
+
targetIndex = 0;
|
|
478
|
+
} else if (event.key === "End") {
|
|
479
|
+
event.preventDefault();
|
|
480
|
+
targetIndex = marks.length - 1;
|
|
481
|
+
} else if (event.key === "Escape") {
|
|
482
|
+
event.preventDefault();
|
|
483
|
+
active.blur();
|
|
484
|
+
}
|
|
485
|
+
if (targetIndex !== -1 && marks[targetIndex]) {
|
|
486
|
+
marks[targetIndex].focus();
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
function renderDataTable(instance, tableData) {
|
|
491
|
+
const { target, options } = instance;
|
|
492
|
+
if (options.dataTable === false) return;
|
|
493
|
+
if (!tableData || !tableData.rows || !tableData.rows.length) return;
|
|
494
|
+
const table = document.createElement("table");
|
|
495
|
+
table.className = "vd-chart-data-table";
|
|
496
|
+
if (options.dataTable !== "visible") {
|
|
497
|
+
table.classList.add("vd-chart-sr-only");
|
|
498
|
+
}
|
|
499
|
+
const caption = document.createElement("caption");
|
|
500
|
+
caption.textContent = tableData.caption || options.title || options.ariaLabel || `${instance.kind} chart data`;
|
|
501
|
+
table.appendChild(caption);
|
|
502
|
+
if (tableData.headers && tableData.headers.length) {
|
|
503
|
+
const thead = document.createElement("thead");
|
|
504
|
+
const headerRow = document.createElement("tr");
|
|
505
|
+
tableData.headers.forEach((h2) => {
|
|
506
|
+
const th = document.createElement("th");
|
|
507
|
+
th.setAttribute("scope", "col");
|
|
508
|
+
th.textContent = String(h2);
|
|
509
|
+
headerRow.appendChild(th);
|
|
510
|
+
});
|
|
511
|
+
thead.appendChild(headerRow);
|
|
512
|
+
table.appendChild(thead);
|
|
513
|
+
}
|
|
514
|
+
const tbody = document.createElement("tbody");
|
|
515
|
+
tableData.rows.forEach((row) => {
|
|
516
|
+
const tr = document.createElement("tr");
|
|
517
|
+
row.forEach((cell, idx) => {
|
|
518
|
+
const el = idx === 0 ? document.createElement("th") : document.createElement("td");
|
|
519
|
+
if (idx === 0) el.setAttribute("scope", "row");
|
|
520
|
+
el.textContent = cell == null ? "" : String(cell);
|
|
521
|
+
tr.appendChild(el);
|
|
522
|
+
});
|
|
523
|
+
tbody.appendChild(tr);
|
|
524
|
+
});
|
|
525
|
+
table.appendChild(tbody);
|
|
526
|
+
target.appendChild(table);
|
|
527
|
+
}
|
|
447
528
|
function createSvgShell(instance) {
|
|
448
529
|
const { target, options } = instance;
|
|
449
530
|
const size = measureTarget(target, options);
|
|
@@ -460,7 +541,8 @@ function createSvgShell(instance) {
|
|
|
460
541
|
width: size.width,
|
|
461
542
|
height: size.height,
|
|
462
543
|
viewBox: `0 0 ${size.width} ${size.height}`,
|
|
463
|
-
role: "
|
|
544
|
+
role: options.role || "graphics-document document",
|
|
545
|
+
"aria-roledescription": options.ariaRoleDescription || `${instance.kind} chart`
|
|
464
546
|
});
|
|
465
547
|
const titleId = nextId("vd-chart-title");
|
|
466
548
|
const descId = nextId("vd-chart-desc");
|
|
@@ -531,7 +613,7 @@ function drawAxisLine(svg, x1, y1, x2, y2, theme) {
|
|
|
531
613
|
}
|
|
532
614
|
function drawCartesianAxes(svg, config) {
|
|
533
615
|
const { plot, xScale, yScale, xTicks, yTicks, theme, options, categoricalX } = config;
|
|
534
|
-
const axisGroup = append(svg, svgEl("g", { class: "vd-chart-axes" }));
|
|
616
|
+
const axisGroup = append(svg, svgEl("g", { class: "vd-chart-axes", "aria-hidden": "true" }));
|
|
535
617
|
yTicks.forEach((tick) => {
|
|
536
618
|
const y = yScale(tick);
|
|
537
619
|
if (!isFiniteNumber(y)) return;
|
|
@@ -774,7 +856,14 @@ function drawDataLabel(svg, x, y, value, cfg, theme, anchor = "middle") {
|
|
|
774
856
|
function drawAnnotations(svg, options, plot, xScale, yScale, theme) {
|
|
775
857
|
const annotations = toArray(options.annotations);
|
|
776
858
|
if (!annotations.length) return;
|
|
777
|
-
const group = append(
|
|
859
|
+
const group = append(
|
|
860
|
+
svg,
|
|
861
|
+
svgEl("g", {
|
|
862
|
+
class: "vd-chart-annotations",
|
|
863
|
+
role: "graphics-object",
|
|
864
|
+
"aria-roledescription": "annotations"
|
|
865
|
+
})
|
|
866
|
+
);
|
|
778
867
|
annotations.forEach((ann) => {
|
|
779
868
|
const color = ann.color || theme.mutedTextColor;
|
|
780
869
|
const dash = ann.dash === false ? null : "4 3";
|
|
@@ -894,7 +983,14 @@ function renderBarChart(instance) {
|
|
|
894
983
|
});
|
|
895
984
|
drawAnnotations(svg, options, plot, xScale, yScale, theme);
|
|
896
985
|
const labels = dataLabelConfig(options);
|
|
897
|
-
const markGroup = append(
|
|
986
|
+
const markGroup = append(
|
|
987
|
+
svg,
|
|
988
|
+
svgEl("g", {
|
|
989
|
+
class: "vd-chart-marks vd-chart-bars",
|
|
990
|
+
role: "graphics-object",
|
|
991
|
+
"aria-roledescription": "data points"
|
|
992
|
+
})
|
|
993
|
+
);
|
|
898
994
|
const baseline = yScale(Math.min(Math.max(0, yDomain[0]), yDomain[1]));
|
|
899
995
|
rows.forEach((row) => {
|
|
900
996
|
const x = xScale(row.x);
|
|
@@ -912,6 +1008,7 @@ function renderBarChart(instance) {
|
|
|
912
1008
|
rx: 3,
|
|
913
1009
|
fill,
|
|
914
1010
|
role: "graphics-symbol",
|
|
1011
|
+
"aria-roledescription": "bar",
|
|
915
1012
|
"aria-label": `${formatCategory(row.x)}: ${formatNumber(row.y)}`
|
|
916
1013
|
});
|
|
917
1014
|
makeInteractive(rect, typeof options.onBarClick === "function", options.tooltip !== false);
|
|
@@ -933,6 +1030,12 @@ function renderBarChart(instance) {
|
|
|
933
1030
|
if (labels) drawDataLabel(svg, x + xScale.bandwidth() / 2, rectY - 4, row.y, labels, theme);
|
|
934
1031
|
append(markGroup, rect);
|
|
935
1032
|
});
|
|
1033
|
+
setupKeyboardNavigation(svg, markGroup);
|
|
1034
|
+
renderDataTable(instance, {
|
|
1035
|
+
caption: options.title || options.ariaLabel || `${instance.kind} chart data`,
|
|
1036
|
+
headers: [options.xAxis?.label || "Category", options.yAxis?.label || "Value"],
|
|
1037
|
+
rows: rows.map((r) => [formatCategory(r.x), formatNumber(r.y)])
|
|
1038
|
+
});
|
|
936
1039
|
if (options.legend && color && color.scale) {
|
|
937
1040
|
renderTopLegend(
|
|
938
1041
|
svg,
|
|
@@ -986,7 +1089,14 @@ function renderMultiBarChart(instance) {
|
|
|
986
1089
|
});
|
|
987
1090
|
drawAnnotations(svg, options, plot, xScale, yScale, theme);
|
|
988
1091
|
const labels = dataLabelConfig(options);
|
|
989
|
-
const markGroup = append(
|
|
1092
|
+
const markGroup = append(
|
|
1093
|
+
svg,
|
|
1094
|
+
svgEl("g", {
|
|
1095
|
+
class: "vd-chart-marks vd-chart-bars",
|
|
1096
|
+
role: "graphics-object",
|
|
1097
|
+
"aria-roledescription": "grouped bars"
|
|
1098
|
+
})
|
|
1099
|
+
);
|
|
990
1100
|
const baseline = yScale(Math.min(Math.max(0, yDomain[0]), yDomain[1]));
|
|
991
1101
|
seriesList.forEach((series) => {
|
|
992
1102
|
const fill = seriesColor(series, theme);
|
|
@@ -1006,6 +1116,7 @@ function renderMultiBarChart(instance) {
|
|
|
1006
1116
|
rx: 3,
|
|
1007
1117
|
fill,
|
|
1008
1118
|
role: "graphics-symbol",
|
|
1119
|
+
"aria-roledescription": "bar",
|
|
1009
1120
|
"aria-label": `${series.name} \u2014 ${formatCategory(row.x)}: ${formatNumber(row.y)}`
|
|
1010
1121
|
});
|
|
1011
1122
|
makeInteractive(rect, typeof options.onBarClick === "function", options.tooltip !== false);
|
|
@@ -1038,6 +1149,20 @@ function renderMultiBarChart(instance) {
|
|
|
1038
1149
|
append(markGroup, rect);
|
|
1039
1150
|
});
|
|
1040
1151
|
});
|
|
1152
|
+
setupKeyboardNavigation(svg, markGroup);
|
|
1153
|
+
const tableRows = categories.map((cat) => {
|
|
1154
|
+
const rowCells = [cat];
|
|
1155
|
+
seriesList.forEach((s) => {
|
|
1156
|
+
const match = s.rows.find((r) => String(r.x) === cat);
|
|
1157
|
+
rowCells.push(match && isFiniteNumber(match.y) ? formatNumber(match.y) : "");
|
|
1158
|
+
});
|
|
1159
|
+
return rowCells;
|
|
1160
|
+
});
|
|
1161
|
+
renderDataTable(instance, {
|
|
1162
|
+
caption: options.title || options.ariaLabel || `${instance.kind} chart data`,
|
|
1163
|
+
headers: [options.xAxis?.label || "Category", ...seriesList.map((s) => s.name)],
|
|
1164
|
+
rows: tableRows
|
|
1165
|
+
});
|
|
1041
1166
|
if (options.legend !== false) {
|
|
1042
1167
|
renderTopLegend(
|
|
1043
1168
|
svg,
|
|
@@ -1098,7 +1223,14 @@ function renderLineLikeChart(instance, mode) {
|
|
|
1098
1223
|
});
|
|
1099
1224
|
drawAnnotations(svg, options, plot, xInfo.scale, yScale, theme);
|
|
1100
1225
|
const labels = dataLabelConfig(options);
|
|
1101
|
-
const markGroup = append(
|
|
1226
|
+
const markGroup = append(
|
|
1227
|
+
svg,
|
|
1228
|
+
svgEl("g", {
|
|
1229
|
+
class: `vd-chart-marks vd-chart-${mode}`,
|
|
1230
|
+
role: "graphics-object",
|
|
1231
|
+
"aria-roledescription": `${mode} series`
|
|
1232
|
+
})
|
|
1233
|
+
);
|
|
1102
1234
|
if (mode === "area") {
|
|
1103
1235
|
const baseline = yScale(Math.max(0, yDomain[0]));
|
|
1104
1236
|
append(
|
|
@@ -1135,6 +1267,7 @@ function renderLineLikeChart(instance, mode) {
|
|
|
1135
1267
|
stroke: color,
|
|
1136
1268
|
"stroke-width": 2,
|
|
1137
1269
|
role: "graphics-symbol",
|
|
1270
|
+
"aria-roledescription": "data point",
|
|
1138
1271
|
"aria-label": `${formatCategory(point.xValue)}: ${formatNumber(point.yValue)}`
|
|
1139
1272
|
});
|
|
1140
1273
|
makeInteractive(
|
|
@@ -1164,6 +1297,12 @@ function renderLineLikeChart(instance, mode) {
|
|
|
1164
1297
|
(point) => drawDataLabel(svg, point.x, point.y - 8, point.yValue, labels, theme)
|
|
1165
1298
|
);
|
|
1166
1299
|
}
|
|
1300
|
+
setupKeyboardNavigation(svg, markGroup);
|
|
1301
|
+
renderDataTable(instance, {
|
|
1302
|
+
caption: options.title || options.ariaLabel || `${instance.kind} chart data`,
|
|
1303
|
+
headers: [options.xAxis?.label || "X", options.yAxis?.label || "Y"],
|
|
1304
|
+
rows: points.map((p) => [formatCategory(p.xValue), formatNumber(p.yValue)])
|
|
1305
|
+
});
|
|
1167
1306
|
}
|
|
1168
1307
|
function renderMultiLineChart(instance, mode) {
|
|
1169
1308
|
const shell = createSvgShell(instance);
|
|
@@ -1199,7 +1338,14 @@ function renderMultiLineChart(instance, mode) {
|
|
|
1199
1338
|
});
|
|
1200
1339
|
drawAnnotations(svg, options, plot, xInfo.scale, yScale, theme);
|
|
1201
1340
|
const labels = dataLabelConfig(options);
|
|
1202
|
-
const markGroup = append(
|
|
1341
|
+
const markGroup = append(
|
|
1342
|
+
svg,
|
|
1343
|
+
svgEl("g", {
|
|
1344
|
+
class: `vd-chart-marks vd-chart-${mode}`,
|
|
1345
|
+
role: "graphics-object",
|
|
1346
|
+
"aria-roledescription": `multi-series ${mode}`
|
|
1347
|
+
})
|
|
1348
|
+
);
|
|
1203
1349
|
const baseline = yScale(Math.max(0, yDomain[0]));
|
|
1204
1350
|
seriesList.forEach((series) => {
|
|
1205
1351
|
const stroke = seriesColor(series, theme);
|
|
@@ -1246,6 +1392,7 @@ function renderMultiLineChart(instance, mode) {
|
|
|
1246
1392
|
stroke,
|
|
1247
1393
|
"stroke-width": 2,
|
|
1248
1394
|
role: "graphics-symbol",
|
|
1395
|
+
"aria-roledescription": "data point",
|
|
1249
1396
|
"aria-label": `${series.name} ${formatCategory(point.xValue)}: ${formatNumber(point.yValue)}`
|
|
1250
1397
|
});
|
|
1251
1398
|
makeInteractive(
|
|
@@ -1278,6 +1425,31 @@ function renderMultiLineChart(instance, mode) {
|
|
|
1278
1425
|
);
|
|
1279
1426
|
}
|
|
1280
1427
|
});
|
|
1428
|
+
setupKeyboardNavigation(svg, markGroup);
|
|
1429
|
+
const xKey = (value) => String(xInfo.mapValue(value));
|
|
1430
|
+
const uniqueX = /* @__PURE__ */ new Map();
|
|
1431
|
+
for (const row of allRows) {
|
|
1432
|
+
const key = xKey(row.x);
|
|
1433
|
+
if (!uniqueX.has(key)) uniqueX.set(key, row.x);
|
|
1434
|
+
}
|
|
1435
|
+
const seriesValues = seriesList.map((s) => {
|
|
1436
|
+
const byX = /* @__PURE__ */ new Map();
|
|
1437
|
+
for (const row of s.rows) byX.set(xKey(row.x), row.y);
|
|
1438
|
+
return byX;
|
|
1439
|
+
});
|
|
1440
|
+
const tableRows = [...uniqueX.entries()].map(([key, xVal]) => {
|
|
1441
|
+
const rowCells = [formatCategory(xVal)];
|
|
1442
|
+
seriesValues.forEach((byX) => {
|
|
1443
|
+
const y = byX.get(key);
|
|
1444
|
+
rowCells.push(isFiniteNumber(y) ? formatNumber(y) : "");
|
|
1445
|
+
});
|
|
1446
|
+
return rowCells;
|
|
1447
|
+
});
|
|
1448
|
+
renderDataTable(instance, {
|
|
1449
|
+
caption: options.title || options.ariaLabel || `${instance.kind} chart data`,
|
|
1450
|
+
headers: [options.xAxis?.label || "X", ...seriesList.map((s) => s.name)],
|
|
1451
|
+
rows: tableRows
|
|
1452
|
+
});
|
|
1281
1453
|
if (options.legend !== false) {
|
|
1282
1454
|
renderTopLegend(
|
|
1283
1455
|
svg,
|
|
@@ -1329,7 +1501,14 @@ function renderScatterChart(instance) {
|
|
|
1329
1501
|
});
|
|
1330
1502
|
drawAnnotations(svg, options, plot, xInfo.scale, yScale, theme);
|
|
1331
1503
|
const labels = dataLabelConfig(options);
|
|
1332
|
-
const markGroup = append(
|
|
1504
|
+
const markGroup = append(
|
|
1505
|
+
svg,
|
|
1506
|
+
svgEl("g", {
|
|
1507
|
+
class: "vd-chart-marks vd-chart-scatter",
|
|
1508
|
+
role: "graphics-object",
|
|
1509
|
+
"aria-roledescription": "data points"
|
|
1510
|
+
})
|
|
1511
|
+
);
|
|
1333
1512
|
rows.forEach((row) => {
|
|
1334
1513
|
const cx = xInfo.scale(xInfo.mapValue(row.x));
|
|
1335
1514
|
const cy = yScale(row.y);
|
|
@@ -1343,6 +1522,7 @@ function renderScatterChart(instance) {
|
|
|
1343
1522
|
fill,
|
|
1344
1523
|
opacity: options.pointOpacity ?? 0.88,
|
|
1345
1524
|
role: "graphics-symbol",
|
|
1525
|
+
"aria-roledescription": "data point",
|
|
1346
1526
|
"aria-label": `${formatCategory(row.x)}: ${formatNumber(row.y)}`
|
|
1347
1527
|
});
|
|
1348
1528
|
makeInteractive(circle, typeof options.onPointClick === "function", options.tooltip !== false);
|
|
@@ -1363,6 +1543,12 @@ function renderScatterChart(instance) {
|
|
|
1363
1543
|
if (labels) drawDataLabel(svg, cx, cy - 8, row.y, labels, theme);
|
|
1364
1544
|
append(markGroup, circle);
|
|
1365
1545
|
});
|
|
1546
|
+
setupKeyboardNavigation(svg, markGroup);
|
|
1547
|
+
renderDataTable(instance, {
|
|
1548
|
+
caption: options.title || options.ariaLabel || `${instance.kind} chart data`,
|
|
1549
|
+
headers: [options.xAxis?.label || "X", options.yAxis?.label || "Y"],
|
|
1550
|
+
rows: rows.map((r) => [formatCategory(r.x), formatNumber(r.y)])
|
|
1551
|
+
});
|
|
1366
1552
|
if (options.legend && color && color.scale) {
|
|
1367
1553
|
renderTopLegend(
|
|
1368
1554
|
svg,
|
|
@@ -1431,7 +1617,14 @@ function renderDonutChart(instance) {
|
|
|
1431
1617
|
const innerRadius = outerRadius * ratio;
|
|
1432
1618
|
const colorScale = scaleOrdinal({ domain: rows.map((row) => row.label), range: theme.colors });
|
|
1433
1619
|
const labels = dataLabelConfig(options);
|
|
1434
|
-
const markGroup = append(
|
|
1620
|
+
const markGroup = append(
|
|
1621
|
+
svg,
|
|
1622
|
+
svgEl("g", {
|
|
1623
|
+
class: "vd-chart-marks vd-chart-slices",
|
|
1624
|
+
role: "graphics-object",
|
|
1625
|
+
"aria-roledescription": "pie slices"
|
|
1626
|
+
})
|
|
1627
|
+
);
|
|
1435
1628
|
let cursor = -Math.PI / 2;
|
|
1436
1629
|
rows.forEach((row) => {
|
|
1437
1630
|
const angle = row.value / total * TAU;
|
|
@@ -1445,6 +1638,7 @@ function renderDonutChart(instance) {
|
|
|
1445
1638
|
stroke: theme.backgroundColor,
|
|
1446
1639
|
"stroke-width": 2,
|
|
1447
1640
|
role: "graphics-symbol",
|
|
1641
|
+
"aria-roledescription": "slice",
|
|
1448
1642
|
"aria-label": `${formatCategory(row.label)}: ${formatNumber(row.value)}`
|
|
1449
1643
|
});
|
|
1450
1644
|
makeInteractive(path, typeof options.onSliceClick === "function", options.tooltip !== false);
|
|
@@ -1469,6 +1663,16 @@ function renderDonutChart(instance) {
|
|
|
1469
1663
|
}
|
|
1470
1664
|
append(markGroup, path);
|
|
1471
1665
|
});
|
|
1666
|
+
setupKeyboardNavigation(svg, markGroup);
|
|
1667
|
+
renderDataTable(instance, {
|
|
1668
|
+
caption: options.title || options.ariaLabel || `${instance.kind} chart data`,
|
|
1669
|
+
headers: ["Category", "Value", "Percentage"],
|
|
1670
|
+
rows: rows.map((r) => [
|
|
1671
|
+
formatCategory(r.label),
|
|
1672
|
+
formatNumber(r.value),
|
|
1673
|
+
total > 0 ? `${Math.round(r.value / total * 100)}%` : "0%"
|
|
1674
|
+
])
|
|
1675
|
+
});
|
|
1472
1676
|
if (innerRadius > 16 && options.centerLabel !== false) {
|
|
1473
1677
|
append(
|
|
1474
1678
|
svg,
|
|
@@ -1519,7 +1723,11 @@ var ChartInstance = class {
|
|
|
1519
1723
|
}
|
|
1520
1724
|
render() {
|
|
1521
1725
|
if (this.destroyed) return this;
|
|
1726
|
+
const tableHeight = dataTableContribution(this.target);
|
|
1522
1727
|
this.renderer(this);
|
|
1728
|
+
if (!Number(this.options.height) && dataTableContribution(this.target) !== tableHeight) {
|
|
1729
|
+
this.renderer(this);
|
|
1730
|
+
}
|
|
1523
1731
|
return this;
|
|
1524
1732
|
}
|
|
1525
1733
|
update(nextOptions = {}) {
|
|
@@ -1533,11 +1741,12 @@ var ChartInstance = class {
|
|
|
1533
1741
|
setupResizeObserver() {
|
|
1534
1742
|
if (this.options.responsive === false || !hasWindow() || typeof ResizeObserver === "undefined")
|
|
1535
1743
|
return;
|
|
1744
|
+
const chartHeight = () => Math.max(0, this.target.clientHeight - dataTableContribution(this.target));
|
|
1536
1745
|
let lastWidth = this.target.clientWidth;
|
|
1537
|
-
let lastHeight =
|
|
1746
|
+
let lastHeight = chartHeight();
|
|
1538
1747
|
this.resizeObserver = new ResizeObserver(() => {
|
|
1539
1748
|
const width = this.target.clientWidth;
|
|
1540
|
-
const height =
|
|
1749
|
+
const height = chartHeight();
|
|
1541
1750
|
if (width === lastWidth && height === lastHeight) return;
|
|
1542
1751
|
lastWidth = width;
|
|
1543
1752
|
lastHeight = height;
|
|
@@ -1651,7 +1860,13 @@ var CHART_PROPS = {
|
|
|
1651
1860
|
xFormat: { type: Function, default: void 0 },
|
|
1652
1861
|
yFormat: { type: Function, default: void 0 },
|
|
1653
1862
|
xAxis: { type: Object, default: void 0 },
|
|
1654
|
-
yAxis: { type: Object, default: void 0 }
|
|
1863
|
+
yAxis: { type: Object, default: void 0 },
|
|
1864
|
+
// Accessible data table: `true` (sr-only, default) / `false` / `'visible'`.
|
|
1865
|
+
dataTable: { type: [Boolean, String], default: void 0 },
|
|
1866
|
+
// Override the SVG `aria-roledescription` (defaults to `${kind} chart`).
|
|
1867
|
+
ariaRoleDescription: { type: String, default: void 0 },
|
|
1868
|
+
// Avoid colliding with the standard root `role` fallthrough attribute.
|
|
1869
|
+
svgRole: { type: String, default: void 0 }
|
|
1655
1870
|
};
|
|
1656
1871
|
function optionsFrom(target, props, emit) {
|
|
1657
1872
|
return {
|
|
@@ -1685,6 +1900,9 @@ function optionsFrom(target, props, emit) {
|
|
|
1685
1900
|
yFormat: props.yFormat,
|
|
1686
1901
|
xAxis: props.xAxis,
|
|
1687
1902
|
yAxis: props.yAxis,
|
|
1903
|
+
dataTable: props.dataTable,
|
|
1904
|
+
ariaRoleDescription: props.ariaRoleDescription,
|
|
1905
|
+
role: props.svgRole,
|
|
1688
1906
|
// Forward the core's mark-click callbacks as the Vue emit family. Passing
|
|
1689
1907
|
// all three unconditionally is safe: the core only fires the callback
|
|
1690
1908
|
// relevant to the rendered chart type (bars → onBarClick, points →
|
|
@@ -1743,7 +1961,10 @@ var VdChart = defineComponent({
|
|
|
1743
1961
|
props.xFormat,
|
|
1744
1962
|
props.yFormat,
|
|
1745
1963
|
props.xAxis,
|
|
1746
|
-
props.yAxis
|
|
1964
|
+
props.yAxis,
|
|
1965
|
+
props.dataTable,
|
|
1966
|
+
props.ariaRoleDescription,
|
|
1967
|
+
props.svgRole
|
|
1747
1968
|
],
|
|
1748
1969
|
() => {
|
|
1749
1970
|
if (!instance) return;
|