You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何让D3.js/NVD3图表支持键盘操作:箭头键触发悬停数据标签

好问题!无障碍访问在数据可视化领域真的很容易被忽略,但却是至关重要的一环。针对你用NVD3搭建的交互式图表,想要通过键盘箭头键触发数据标签(也就是工具提示)显示的需求,我整理了几个实用的实现方案,你可以根据自己的场景灵活选择:

方案一:利用NVD3内置事件+键盘监听

NVD3其实自带了focusblur事件来控制工具提示的显示与隐藏,我们可以直接借助这个机制,通过键盘箭头键手动切换数据点的焦点,从而触发标签显示。

具体步骤如下:

  • 先给页面或者图表容器绑定keydown事件,专门监听左右箭头键(keyCode 37是左箭头,39是右箭头),记得阻止默认行为,避免按箭头键时页面滚动。
  • 获取图表里所有可交互的数据点元素——大部分NVD3图表(比如折线图、散点图)的数据点都带有nv-point类,你可以用浏览器开发者工具确认一下实际类名。
  • 维护一个当前选中的数据点索引,按下箭头键时更新这个索引,注意边界判断(不能小于0,也不能超过数据点总数减一)。
  • 找到对应索引的数据点,手动触发它的focus事件(NVD3会自动弹出工具提示),同时给之前选中的点触发blur事件隐藏旧的标签。
  • 可选:让当前数据点获取焦点,方便屏幕阅读器识别。

示例代码如下:

// 初始化当前选中的索引
let currentIndex = 0;
// 获取所有数据点元素
const dataPoints = document.querySelectorAll('.nv-point');

// 监听键盘按键事件
document.addEventListener('keydown', (e) => {
  // 只处理左右箭头键
  if ([37, 39].indexOf(e.keyCode) === -1) return;
  
  // 阻止默认页面滚动行为
  e.preventDefault();
  
  // 更新选中索引
  if (e.keyCode === 37) {
    currentIndex = Math.max(0, currentIndex - 1);
  } else {
    currentIndex = Math.min(dataPoints.length - 1, currentIndex + 1);
  }
  
  // 让上一个选中的点失去焦点,隐藏旧标签
  const prevIndex = e.keyCode === 37 ? currentIndex + 1 : currentIndex - 1;
  if (dataPoints[prevIndex]) {
    dataPoints[prevIndex].dispatchEvent(new Event('blur'));
  }
  
  // 触发当前点的focus事件,显示标签
  dataPoints[currentIndex].dispatchEvent(new Event('focus'));
  // 让当前点获得焦点,支持屏幕阅读器
  dataPoints[currentIndex].focus();
});
方案二:自定义工具提示控制逻辑

如果内置事件不好用(比如某些特殊图表类型),我们可以直接手动控制工具提示的显示、内容和位置。NVD3的工具提示一般是带有nvtooltip类的DOM元素,我们可以直接操作它。

步骤大概是:

  • 同样监听键盘箭头键,维护选中索引。
  • 通过D3获取当前选中数据点的绑定数据(用d3.select(dataPoint).datum()就能拿到)。
  • 获取工具提示元素,根据数据点的位置和数据内容,更新工具提示的HTML内容和定位,然后设置display: block让它显示出来。
  • 切换数据点时,记得隐藏之前的工具提示(或者直接覆盖内容)。

示例代码片段:

let currentIndex = 0;
// 用D3选择所有数据点
const dataPoints = d3.selectAll('.nv-point');
// 获取工具提示元素
const tooltip = d3.select('.nvtooltip');

document.addEventListener('keydown', (e) => {
  if ([37, 39].indexOf(e.keyCode) === -1) return;
  e.preventDefault();
  
  // 更新选中索引
  currentIndex = e.keyCode === 37 
    ? Math.max(0, currentIndex - 1) 
    : Math.min(dataPoints.size() - 1, currentIndex + 1);
  
  const currentPoint = dataPoints.nodes()[currentIndex];
  // 获取数据点绑定的数据
  const pointData = d3.select(currentPoint).datum();
  // 获取数据点在页面中的位置
  const pointRect = currentPoint.getBoundingClientRect();
  
  // 自定义工具提示内容,根据你的数据结构调整
  const tooltipHtml = `
    <div class="nvtooltip-title">${pointData.series}</div>
    <div class="nvtooltip-value">数值:${pointData.y}</div>
  `;
  
  // 更新并显示工具提示
  tooltip.html(tooltipHtml)
    .style('display', 'block')
    .style('left', `${pointRect.left + window.scrollX + 15}px`)
    .style('top', `${pointRect.top + window.scrollY - 60}px`);
  
  // 让当前数据点获得焦点
  currentPoint.focus();
});

这个方案灵活性更高,适合需要定制工具提示样式或内容的场景,但需要你自己处理数据渲染和定位,要适配不同的图表类型。

额外优化:增强无障碍体验

不管你选哪个方案,都建议给数据点添加无障碍属性,让屏幕阅读器用户也能顺畅使用:

  • 给数据点添加tabindex="0",让它们可以通过Tab键被选中。
  • 添加aria-label属性,用自然语言描述数据点的内容,比如“销售数据系列,2023年1月数值为1200”。

示例代码:

// 初始化图表后,给数据点添加无障碍属性
d3.selectAll('.nv-point')
  .attr('tabindex', '0')
  .attr('aria-label', function(d) {
    return `${d.series}系列,数值为${d.y}`;
  });

这样用户不仅能用箭头键切换,还能通过Tab键导航数据点,屏幕阅读器会自动读出描述,进一步提升无障碍访问体验。


内容的提问来源于stack exchange,提问作者Jesse Cambon

火山引擎 最新活动