D3.js 交互事件 #
交互是数据可视化的灵魂,它让用户能够探索数据、发现洞察。D3.js 提供了强大的事件处理系统,支持各种交互方式。
事件基础 #
核心概念 #
text
┌─────────────────────────────────────────────────────────────┐
│ 交互事件流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户操作 ──► 事件触发 ──► 事件处理 ──► 视图更新 │
│ │
│ 点击/移动 event handler transition │
│ │
└─────────────────────────────────────────────────────────────┘
事件监听 #
selection.on() #
javascript
d3.select('circle')
.on('click', function(event, d) {
console.log('Clicked:', d);
d3.select(this).attr('fill', 'red');
});
d3.selectAll('circle')
.on('mouseover', function() {
d3.select(this).attr('r', 10);
})
.on('mouseout', function() {
d3.select(this).attr('r', 5);
});
常用事件类型 #
| 事件类型 | 描述 | 触发时机 |
|---|---|---|
| click | 点击 | 鼠标单击 |
| dblclick | 双击 | 鼠标双击 |
| mouseover | 鼠标悬停 | 鼠标移入 |
| mouseout | 鼠标离开 | 鼠标移出 |
| mouseenter | 鼠标进入 | 鼠标进入(不冒泡) |
| mouseleave | 鼠标离开 | 鼠标离开(不冒泡) |
| mousedown | 鼠标按下 | 鼠标按键按下 |
| mouseup | 鼠标释放 | 鼠标按键释放 |
| mousemove | 鼠标移动 | 鼠标移动 |
| wheel | 滚轮 | 滚轮滚动 |
| keydown | 键盘按下 | 键盘按键按下 |
| keyup | 键盘释放 | 键盘按键释放 |
| touchstart | 触摸开始 | 触摸屏触摸 |
| touchmove | 触摸移动 | 触摸移动 |
| touchend | 触摸结束 | 触摸结束 |
事件对象 #
javascript
d3.select('circle')
.on('click', function(event, d) {
console.log(event.target);
console.log(event.currentTarget);
console.log(event.type);
console.log(event.timeStamp);
console.log(event.clientX, event.clientY);
console.log(event.pageX, event.pageY);
console.log(event.screenX, event.screenY);
console.log(d);
console.log(this);
});
移除事件监听 #
javascript
d3.select('circle')
.on('click', null);
d3.select('circle')
.on('click.myNamespace', null);
命名空间 #
javascript
d3.select('circle')
.on('click.main', function() { })
.on('click.tooltip', function() { });
d3.select('circle')
.on('click.main', null);
拖拽交互 d3.drag #
基本用法 #
javascript
const drag = d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended);
d3.selectAll('circle')
.call(drag);
function dragstarted(event, d) {
d3.select(this).raise().attr('stroke', 'black');
}
function dragged(event, d) {
d3.select(this)
.attr('cx', d.x = event.x)
.attr('cy', d.y = event.y);
}
function dragended(event, d) {
d3.select(this).attr('stroke', null);
}
拖拽事件对象 #
javascript
function dragged(event, d) {
console.log(event.x);
console.log(event.y);
console.log(event.dx);
console.log(event.dy);
console.log(event.subject);
console.log(event.identifier);
console.log(event.active);
console.log(event.sourceEvent);
}
拖拽约束 #
javascript
const drag = d3.drag()
.subject(function(event, d) {
return { x: d.x, y: d.y };
})
.on('drag', function(event, d) {
d.x = Math.max(0, Math.min(width, event.x));
d.y = Math.max(0, Math.min(height, event.y));
d3.select(this)
.attr('cx', d.x)
.attr('cy', d.y);
});
拖拽容器 #
javascript
const drag = d3.drag()
.container(function() {
return this.parentNode;
});
拖拽过滤器 #
javascript
const drag = d3.drag()
.filter(function(event) {
return event.button === 0;
});
缩放交互 d3.zoom #
基本用法 #
javascript
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', zoomed);
svg.call(zoom);
function zoomed(event) {
g.attr('transform', event.transform);
}
缩放事件 #
javascript
const zoom = d3.zoom()
.on('start', zoomstarted)
.on('zoom', zoomed)
.on('end', zoomended);
function zoomstarted(event) {
console.log('Zoom started');
}
function zoomed(event) {
g.attr('transform', event.transform);
}
function zoomended(event) {
console.log('Zoom ended');
}
缩放变换对象 #
javascript
function zoomed(event) {
console.log(event.transform.x);
console.log(event.transform.y);
console.log(event.transform.k);
console.log(event.transform.toString());
g.attr('transform', event.transform);
const point = event.transform.invert([100, 100]);
}
缩放控制 #
javascript
const zoom = d3.zoom()
.scaleExtent([0.5, 10])
.translateExtent([[0, 0], [width, height]]);
svg.transition().call(zoom.scaleBy, 2);
svg.transition().call(zoom.scaleTo, 2);
svg.transition().call(zoom.translateTo, 100, 100);
svg.call(zoom.transform, d3.zoomIdentity);
svg.call(zoom.transform, d3.zoomIdentity.translate(100, 50).scale(2));
禁用缩放行为 #
javascript
const zoom = d3.zoom()
.filter(function(event) {
return event.type !== 'wheel';
});
const zoom = d3.zoom()
.on('zoom', null);
刷选交互 d3.brush #
基本用法 #
javascript
const brush = d3.brush()
.extent([[0, 0], [width, height]])
.on('brush', brushed)
.on('end', brushended);
svg.append('g')
.attr('class', 'brush')
.call(brush);
function brushed(event) {
const selection = event.selection;
if (selection) {
const [[x0, y0], [x1, y1]] = selection;
console.log(x0, y0, x1, y1);
}
}
function brushended(event) {
if (!event.selection) {
console.log('Brush cleared');
}
}
刷选方向 #
javascript
const brushX = d3.brushX()
.extent([[0, 0], [width, height]])
.on('end', brushed);
const brushY = d3.brushY()
.extent([[0, 0], [width, height]])
.on('end', brushed);
刷选控制 #
javascript
brush.move(svg.select('.brush'), [[100, 100], [200, 200]]);
brush.move(svg.select('.brush'), null);
brush.clear(svg.select('.brush'));
刷选过滤 #
javascript
function brushed(event) {
const selection = event.selection;
if (!selection) return;
const [[x0, y0], [x1, y1]] = selection;
circles.classed('selected', d => {
const cx = xScale(d.x);
const cy = yScale(d.y);
return x0 <= cx && cx <= x1 && y0 <= cy && cy <= y1;
});
}
提示框 Tooltip #
基本提示框 #
javascript
const tooltip = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
d3.selectAll('circle')
.on('mouseover', function(event, d) {
tooltip.transition()
.duration(200)
.style('opacity', 0.9);
tooltip.html(`Value: ${d.value}`)
.style('left', (event.pageX + 10) + 'px')
.style('top', (event.pageY - 28) + 'px');
})
.on('mouseout', function() {
tooltip.transition()
.duration(500)
.style('opacity', 0);
});
CSS 样式 #
css
.tooltip {
position: absolute;
background-color: white;
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px;
font-size: 12px;
pointer-events: none;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
鼠标跟踪 #
跟踪鼠标位置 #
javascript
svg.on('mousemove', function(event) {
const [x, y] = d3.pointer(event);
console.log(x, y);
const [px, py] = d3.pointer(event, svg.node());
});
svg.on('mousemove', function(event) {
const [x, y] = d3.mouse(this);
});
十字准线 #
javascript
const focus = svg.append('g')
.style('display', 'none');
focus.append('circle')
.attr('r', 4.5);
focus.append('line')
.attr('class', 'x-hair')
.attr('y1', 0)
.attr('y2', height);
focus.append('line')
.attr('class', 'y-hair')
.attr('x1', 0)
.attr('x2', width);
svg.on('mouseover', () => focus.style('display', null))
.on('mouseout', () => focus.style('display', 'none'))
.on('mousemove', function(event) {
const [x, y] = d3.pointer(event);
focus.select('circle')
.attr('cx', x)
.attr('cy', y);
focus.select('.x-hair')
.attr('x1', x)
.attr('x2', x);
focus.select('.y-hair')
.attr('y1', y)
.attr('y2', y);
});
键盘交互 #
键盘事件 #
javascript
d3.select('body')
.on('keydown', function(event) {
console.log(event.key);
console.log(event.code);
console.log(event.keyCode);
switch(event.key) {
case 'ArrowUp':
break;
case 'ArrowDown':
break;
case 'Enter':
break;
case 'Escape':
break;
}
});
快捷键 #
javascript
d3.select('body')
.on('keydown', function(event) {
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
console.log('Save triggered');
}
if (event.shiftKey && event.key === 'A') {
console.log('Select all');
}
});
触摸交互 #
触摸事件 #
javascript
d3.select('svg')
.on('touchstart', function(event) {
const touches = event.touches;
console.log('Touches:', touches.length);
})
.on('touchmove', function(event) {
event.preventDefault();
const touch = event.touches[0];
console.log(touch.clientX, touch.clientY);
})
.on('touchend', function(event) {
console.log('Touch ended');
});
交互实战示例 #
可拖拽散点图 #
javascript
const data = [
{ x: 100, y: 100, value: 10 },
{ x: 200, y: 150, value: 20 },
{ x: 150, y: 200, value: 15 }
];
const drag = d3.drag()
.on('drag', function(event, d) {
d.x = event.x;
d.y = event.y;
d3.select(this)
.attr('cx', d.x)
.attr('cy', d.y);
});
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', d => d.value)
.attr('fill', 'steelblue')
.call(drag);
可缩放图表 #
javascript
const zoom = d3.zoom()
.scaleExtent([0.5, 4])
.on('zoom', function(event) {
g.attr('transform', event.transform);
});
svg.call(zoom);
svg.append('button')
.text('Reset')
.on('click', function() {
svg.transition().call(zoom.transform, d3.zoomIdentity);
});
可刷选散点图 #
javascript
const brush = d3.brush()
.extent([[0, 0], [width, height]])
.on('brush', function(event) {
const selection = event.selection;
if (!selection) return;
const [[x0, y0], [x1, y1]] = selection;
circles.classed('selected', d => {
const cx = xScale(d.x);
const cy = yScale(d.y);
return x0 <= cx && cx <= x1 && y0 <= cy && cy <= y1;
});
});
svg.append('g')
.attr('class', 'brush')
.call(brush);
交互方法速查表 #
| 方法 | 描述 | 示例 |
|---|---|---|
| on | 添加事件监听 | .on('click', handler) |
| drag | 创建拖拽行为 | d3.drag() |
| zoom | 创建缩放行为 | d3.zoom() |
| brush | 创建刷选行为 | d3.brush() |
| pointer | 获取指针位置 | d3.pointer(event) |
| preventDefault | 阻止默认行为 | event.preventDefault() |
| stopPropagation | 阻止冒泡 | event.stopPropagation() |
下一步 #
现在你已经掌握了交互事件,接下来学习 布局算法,了解如何创建复杂的可视化布局!
最后更新:2026-03-28