D3.js中点击矩阵像素时更新相邻元素的实现咨询
D3.js中点击矩阵像素时更新相邻元素的实现咨询
嘿,我看你已经用D3搭好了一个基础的像素矩阵,目前点击只能删除当前元素,但想要实现点击后更新相邻像素的功能对吧?我来帮你梳理下怎么调整代码,实现你想要的效果~
首先,咱们先分析下原代码的小问题:
- 原点击事件里只处理了当前元素的删除,但没用到数据里自带的
x/y坐标信息,没法定位相邻元素 - 直接删除DOM元素不是你想要的目标,咱们应该用数据驱动的方式来更新视图,这样既能轻松获取所有像素的状态,也能方便地更新元素样式
接下来是具体的改进步骤和代码:
1. 简化坐标计算,给像素添加初始颜色
原代码里通过索引计算transform的方式有点绕,咱们可以直接用数据里的x和y来定位,同时根据value值给像素设置初始填充色:
.attr("transform", d => `translate(${d.x * entry_side_length}, ${d.y * entry_side_length})`) .attr("fill", d => d.value ? "#000" : "#fff") // 1=黑色,0=白色
2. 编写点击事件逻辑,更新相邻像素
咱们需要在点击事件里:
- 获取当前点击像素的坐标
- 找出上下左右四个邻居(注意边界判断,避免超出矩阵范围)
- 更新邻居的数据状态
- 重新渲染视图,让样式和数据同步
修改后的点击事件函数如下:
function handlePixelClick(d) { // 拿到当前点击像素的坐标 const { x, y } = d; // 定义四个邻居的坐标偏移 const neighborOffsets = [ { dx: -1, dy: 0 }, // 左 { dx: 1, dy: 0 }, // 右 { dx: 0, dy: -1 }, // 上 { dx: 0, dy: 1 } // 下 ]; // 遍历所有邻居,更新它们的value值(这里用取反演示,你可以换成自己的计算逻辑) neighborOffsets.forEach(offset => { const neighborX = x + offset.dx; const neighborY = y + offset.dy; // 检查邻居坐标是否在矩阵范围内 if (neighborX >= 0 && neighborX < cols && neighborY >= 0 && neighborY < rows) { // 找到对应的数据项 const neighborData = data.find(item => item.x === neighborX && item.y === neighborY); if (neighborData) { neighborData.value = 1 - neighborData.value; // 翻转颜色值 } } }); // 重新绑定数据,更新所有像素的颜色 canvas.selectAll("rect") .data(data) .attr("fill", d => d.value ? "#000" : "#fff"); }
然后把原有的on("click", delete_element)替换成on("click", handlePixelClick)就可以了。
3. 获取所有像素的状态
现在所有像素的状态都存在data数组里,你随时可以遍历这个数组来获取每个像素的x/y/value,做你需要的各种计算,比如统计黑色像素数量、判断特定区域的状态等等。
完整修改后的代码
chart = { const cols = 5; const rows = 5; const entry_side_length = 20; const matrix_width = cols * entry_side_length; const matrix_height = rows * entry_side_length; // 生成随机0/1矩阵数据 let data = []; for (let i = 0; i < cols; i++) { for (let j = 0; j < rows; j++) { // 修正原代码内层循环的小bug,cols改为rows data.push({ x: i, y: j, value: Math.round(Math.random()) }); } } let canvas = d3.create("svg") .attr("width", `${matrix_width}px`) .attr("height", `${matrix_height}px`); canvas.append("g").selectAll("rect") .data(data) .join("rect") .attr("width", `${entry_side_length}px`) .attr("height", `${entry_side_length}px`) .attr("transform", d => `translate(${d.x * entry_side_length}, ${d.y * entry_side_length})`) .attr("fill", d => d.value ? "#000" : "#fff") .attr("stroke", "#ccc") // 加个边框区分像素 .on("click", handlePixelClick); function handlePixelClick(d) { const { x, y } = d; const neighborOffsets = [ { dx: -1, dy: 0 }, { dx: 1, dy: 0 }, { dx: 0, dy: -1 }, { dx: 0, dy: 1 } ]; neighborOffsets.forEach(offset => { const nx = x + offset.dx; const ny = y + offset.dy; if (nx >= 0 && nx < cols && ny >= 0 && ny < rows) { const neighbor = data.find(item => item.x === nx && item.y === ny); if (neighbor) { neighbor.value = 1 - neighbor.value; } } }); // 更新视图 canvas.selectAll("rect") .data(data) .attr("fill", d => d.value ? "#000" : "#fff"); } return Object.assign(canvas.node()) }
小提示
我还帮你修正了原代码里的一个小bug:生成数据时内层循环的条件是j < cols,应该改成j < rows,这样才会生成正确的5x5矩阵~
这样修改后,点击任意像素,它的四个相邻像素就会翻转颜色,而且你可以随时通过data数组获取所有像素的状态,方便后续扩展更多功能。
内容来源于stack exchange
相关产品推荐
相关产品推荐

