Обработка клавиатурного ввода в D3.js строится поверх стандартной модели DOM-событий, однако требует дополнительной настройки, поскольку большинство SVG-элементов изначально не фокусируемы и не участвуют в цепочке клавиатурной навигации.
Ключевая особенность заключается в том, что обработчики клавиатуры
могут быть назначены на любые выбранные элементы через механизм
selection.on, однако без обеспечения фокуса события не
будут инициироваться.
d3.select("svg")
.on("keydown", function(event) {
console.log("Key pressed:", event.key);
});
Однако для корректной работы требуется обеспечить возможность получения фокуса:
d3.select("svg")
.attr("tabindex", 0);
tabindex="0" включает SVG-элемент в порядок табуляции
документа и делает возможной генерацию событий keydown,
keyup и keypress.
Современные версии D3 используют передачу события через аргумент функции обработчика:
selection.on("keydown", (event) => {
if (event.key === "ArrowRight") {
console.log("движение вправо");
}
});
Ранее использовалась глобальная переменная d3.event,
которая больше не применяется в актуальных версиях библиотеки. Переход
на явную передачу события упрощает поддержку асинхронного кода и
устраняет проблемы с контекстом выполнения.
Визуализации, построенные на D3.js, часто требуют реализации навигации по элементам графика с помощью клавиатуры. Это особенно важно для диаграмм, где данные представлены в виде отдельных интерактивных элементов.
Типовой подход включает:
Enter и Space для активации
элементовПример обработки стрелок:
d3.selectAll(".bar")
.attr("tabindex", 0)
.on("keydown", function(event, d) {
if (event.key === "ArrowUp") {
console.log("увеличение значения:", d.value);
}
if (event.key === "ArrowDown") {
console.log("уменьшение значения:", d.value);
}
});
Для сложных графиков применяется концепция «активного элемента». Состояние фокуса синхронизируется между визуальным выделением и логическим выбором.
let activeIndex = 0;
const bars = d3.selectAll(".bar")
.attr("tabindex", (d, i) => i === 0 ? 0 : -1);
bars.on("keydown", function(event, d, i) {
if (event.key === "ArrowRight") {
activeIndex = Math.min(activeIndex + 1, bars.size() - 1);
}
if (event.key === "ArrowLeft") {
activeIndex = Math.max(activeIndex - 1, 0);
}
bars.attr("tabindex", (d, i) => i === activeIndex ? 0 : -1);
bars.filter((d, i) => i === activeIndex).node().focus();
});
Такой подход предотвращает потерю фокуса и обеспечивает последовательную навигацию.
SVG по умолчанию не предоставляет семантики для вспомогательных технологий, поэтому требуется явное описание роли и структуры.
Основные атрибуты:
role — определяет тип графического контейнераaria-label — краткое описание визуализацииaria-describedby — ссылка на расширенное описаниеaria-live — динамические обновленияПример:
d3.select("svg")
.attr("role", "img")
.attr("aria-label", "Гистограмма продаж по месяцам")
.attr("aria-describedby", "chart-description");
Описание может быть размещено в скрытом HTML-блоке:
<div id="chart-description" class="visually-hidden">
Данные отображают динамику продаж за 12 месяцев с пиком в июле.
</div>
При интерактивных графиках важно отражать состояние элементов через ARIA-атрибуты:
aria-selectedaria-pressedaria-expandedПример для точек на графике:
d3.selectAll(".dot")
.attr("role", "button")
.attr("tabindex", 0)
.on("click", function(event, d) {
d3.select(this)
.attr("aria-pressed", "true");
});
Состояния должны синхронизироваться между визуальным стилем и DOM-атрибутами, иначе вспомогательные технологии получают некорректную информацию.
В визуализациях применяются повторяющиеся схемы взаимодействия:
Линейные графики
Столбчатые диаграммы
Сеточные визуализации
Реализация:
function handleNavigation(event) {
switch (event.key) {
case "ArrowRight":
moveFocus(1);
break;
case "ArrowLeft":
moveFocus(-1);
break;
case "Home":
moveToStart();
break;
case "End":
moveToEnd();
break;
}
}
В сложных интерфейсах D3 часто комбинируется с HTML-оверлеями. SVG используется для визуализации, HTML — для управления доступностью.
Фокус может передаваться между слоями:
d3.selectAll(".point")
.on("focus", function() {
d3.select(this).classed("focused", true);
})
.on("blur", function() {
d3.select(this).classed("focused", false);
});
HTML-элементы часто выступают как прокси для SVG-объектов, обеспечивая лучшую поддержку экранных дикторов.
При изменении данных важно уведомлять вспомогательные технологии без перегрузки интерфейса.
Используются aria-live области:
<div aria-live="polite" id="announcer"></div>
Обновление:
d3.select("#announcer")
.text("Данные обновлены: добавлено 5 новых точек");
Важно избегать избыточных обновлений, которые могут прерывать чтение текущего контекста.
В больших графиках применяется разделение логики:
Пример архитектурного разделения:
function createKeyboardManager(selection) {
let index = 0;
selection
.attr("tabindex", 0)
.on("keydown", (event) => {
if (event.key === "ArrowRight") index++;
if (event.key === "ArrowLeft") index--;
updateFocus(index);
});
}
Фокус должен быть визуально различим даже при сложной стилизации SVG.
.bar:focus {
outline: none;
stroke: black;
stroke-width: 2px;
}
Для SVG часто используется изменение stroke,
opacity или добавление внешнего контура через фильтры.
Тултипы должны быть связаны с элементами через
aria-describedby или дублировать содержимое в текстовой
форме.
d3.selectAll(".point")
.on("focus", function(event, d) {
d3.select("#tooltip")
.text(`Значение: ${d.value}`)
.style("opacity", 1);
});
Дополнительно создаётся текстовая версия данных для экранных дикторов.
Интерактивные элементы должны одинаково реагировать на разные типы ввода:
function activate(d) {
console.log("активирован элемент:", d);
}
d3.selectAll(".node")
.on("click", (event, d) => activate(d))
.on("keydown", (event, d) => {
if (event.key === "Enter") activate(d);
});
Такой подход устраняет расхождение между мышью и клавиатурой в пользовательской модели взаимодействия.