效果如图,这次直接上实战。
使用d3-drag实现交互式拖拽功能
在数据可视化中,交互是非常重要的一部分,能够使用户与图表进行互动并进行自定义操作。其中之一就是拖拽功能,可以让用户自由地移动元素,改变元素的位置或顺序。在D3.js中,我们可以使用d3-drag来实现这样的交互式拖拽功能。
1. 引入d3-drag库
首先,我们需要在HTML文件中引入d3.js和d3-drag库。可以通过CDN或本地文件的方式引入,如下所示:
2. 创建可拖拽的元素
接下来,我们需要创建一个可拖拽的元素。在这个示例中,我们将创建一个简单的矩形,并添加拖拽功能。首先,在HTML中创建一个SVG容器:
html
<svg id="container" width="400" height="300"></svg>
然后,在JavaScript代码中使用D3.js创建一个矩形元素,并添加拖拽功能:
javascript
// 创建一个SVG容器
const svg = d3.select("#container");
// 创建矩形元素
const rect = svg.append("rect")
.attr("x", 100)
.attr("y", 100)
.attr("width", 50)
.attr("height", 50)
.attr("fill", "blue");
// 创建拖拽行为
const drag = d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded);
// 将拖拽行为应用到矩形元素上
rect.call(drag);
// 拖拽开始时的回调函数
function dragStarted(event, d) {
d3.select(this).raise().attr("fill", "red");
}
// 拖拽过程中的回调函数
function dragged(event, d) {
d3.select(this)
.attr("x", event.x)
.attr("y", event.y);
}
// 拖拽结束时的回调函数
function dragEnded(event, d) {
d3.select(this).attr("fill", "blue");
}
在上面的代码中,我们首先使用d3.select()
选择了SVG容器,并使用append()
方法创建了一个矩形元素。然后,我们创建了一个拖拽行为,并将其应用到矩形元素上,通过调用rect.call(drag)
。
在拖拽行为的回调函数中,我们可以定义拖拽开始、拖拽过程和拖拽结束时的操作。在拖拽开始时,我们将选中的元素提到最前面,并改变其填充颜色。在拖拽过程中,我们使用event.x
和event.y
来更新元素的位置。在拖拽结束时,我们将填充颜色恢复为初始值。
我们已经掌握了drag的基本用法,下面我们来开发一个颜色匹配的小游戏吧! 游戏思路
- 预备5个颜色和对应的中文名称
- 用黑色绘制5个矩形区域,填充颜色的名称。
- 绘制出5个色块和5个名称对应上。
- 移动色块靠近背景区域的时候,当颜色匹配上的时候会出现提示(颜色边框发生变色),释放鼠标会自动吸附到对应色块的中心位置。
- 当所有色块都移动到正确位置的时候,提示完成所有的颜色匹配。
这是一个有意思的小游戏,适合小朋友玩哦。改造一个可以开发出各种类似的游戏,比如动物认识。人物认识,形状认识。呵呵
代码如下,自己看,懒得解释了。
javascript
// drag
let dom = document.createElement('div');
document.body.appendChild(dom);
let padding = 30;
let svgWidth = 600;
let svgHeight = 300;
let svg = d3.select(dom)
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.style('border', '1px solid #999999')
// 定义5个颜色
let colors = [
{
id: 0,
name: '橙色',
color: '#ff6633'
},
{
id: 1,
name: '蓝色',
color: '#3399ff'
},
{
id: 2,
name: '绿色',
color: '#33cc33'
},
{
id: 3,
name: '紫色',
color: '#cc33ff'
},
{
id: 4,
name: '黄色',
color: '#ffcc00'
}
]
// 绘制5个矩形
let rectWidth = 100;
let rectHeight = 100;
let rectPadding = 10;
let pos = {}
// 绘制5个矩形背景边框
let gback = svg.append('g').attr('class', 'back');
let rectBacks = gback.selectAll('rect')
.data(colors)
.enter()
.append('rect')
.attr('x', (d, i) => {
let x = padding + (rectWidth + rectPadding) * i;
pos[d.id] = [x, padding]
return x;
})
.attr('cid', d => d.name)
.attr('ok', 'fail')
.attr('y', padding)
.attr('width', rectWidth)
.attr('height', rectHeight)
.attr('fill', 'none')
.attr('stroke', '#000000')
.attr('stroke-width', '2px')
let _colors = JSON.parse(JSON.stringify(colors));
// 打乱顺序
_colors.sort(function () {
return Math.random() - 0.5;
})
let grect = svg.append('g').attr('class', 'rect');
let rects = grect.selectAll('rect')
.data(_colors)
.enter()
.append('rect')
.attr('cid', d => d.name)
.attr('x', (d, i) => {
return padding + (rectWidth + rectPadding) * i + rectWidth * 0.1;
})
.attr('y', (d, i) => {
return padding + rectHeight + 50;
})
.attr('width', rectWidth * 0.8)
.attr('height', rectHeight * 0.8)
.attr('fill', d => d.color)
// 在背景矩形上添加文字 颜色名称
let gtext = svg.append('g').attr('class', 'text');
let texts = gtext.selectAll('text')
.data(colors)
.enter()
.append('text')
.attr('x', (d, i) => {
return padding + (rectWidth + rectPadding) * i + rectWidth / 2;
})
.attr('y', (d, i) => {
return padding + rectHeight / 2;
})
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
.attr('fill', '#000000')
.attr('font-size', '20px')
.text(d => d.name)
.style('pointer-events', 'none');
// 提示文本 干得漂亮,你完成了所有的颜色匹配
let infoText = svg
.append('text')
.attr('x', svgWidth / 2)
.attr('y', svgHeight - 80)
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
.attr('fill', '#000000')
.attr('font-size', '24px')
.text('干得漂亮Tom,你完成了所有的颜色匹配!')
.style('pointer-events', 'none')
.style('opacity', 0);
let drag = d3.drag()
.on('start', function (d, i) {
d3.select(this)
.raise()// 提升层级 使得当前元素在最上层
.transition()
.attr('stroke', '#000000')
.attr('stroke-width', '2px')
// 鼠标样式
.style('cursor', 'move')
})
.on('drag', function (d) {
let dx = d3.event.dx;
let dy = d3.event.dy;
let x = d3.select(this).attr('x');
let y = d3.select(this).attr('y');
d3.select(this)
.attr('x', +x + dx)
.attr('y', +y + dy);
// 矩形中心点坐标
let cx = +x + dx + (rectWidth * 0.8) / 2;
let cy = +y + dy + (rectHeight * 0.8) / 2;
let cid = d3.select(this).attr('cid');
dis(d3.select(this), cx, cy, cid);
})
.on('end', function (d, i) {
d3.select(this)
.transition()
.attr('stroke', 'none')
//鼠标样式
.style('cursor', 'default')
if (d3.select(this).attr('ok') === 'success') {
// 吸附到背景矩形上
let _x = pos[d.id][0] + rectWidth / 2 - rectWidth * 0.8 / 2;
let _y = pos[d.id][1] + rectHeight / 2 - rectHeight * 0.8 / 2;
d3.select(this)
.transition()
.attr('x', _x)
.attr('y', _y)
}
goodJob();
})
rects.call(drag);
function dis(rect, x, y, cid) {
// 计算x y和 rectBacks 中心点的距离 如果小于 30 就 变色
rectBacks.each(function (d, i) {
let cx = pos[i][0] + rectWidth / 2;
let cy = pos[i][1] + rectHeight / 2;
let _cid = d3.select(this).attr('cid');
let distance = Math.sqrt(Math.pow(cx - x, 2) + Math.pow(cy - y, 2));
if (distance < 30 && cid == _cid) {
d3.select(this)
.attr('stroke', d.color)
.attr('ok', 'success')
rect.attr('ok', 'success')
} else {
if (cid != _cid) return;
d3.select(this)
.attr('stroke', '#000000')
.attr('ok', 'fail')
rect.attr('ok', 'fail')
}
if (d3.select(this).attr('ok') != 'success') {
gogogo = false;
}
})
}
function goodJob() {
let gogogo = true;
rectBacks.each(function (d, i) {
if (d3.select(this).attr('ok') === 'fail') {
gogogo = false;
}
})
if (gogogo) {
infoText
.transition()
.style('opacity', 1)
} else {
infoText
.transition()
.style('opacity', 0)
}
}