【d3.js入门】d3-drag介绍和实战

697 阅读5分钟

效果如图,这次直接上实战。

d3-drag.gif

使用d3-drag实现交互式拖拽功能

在数据可视化中,交互是非常重要的一部分,能够使用户与图表进行互动并进行自定义操作。其中之一就是拖拽功能,可以让用户自由地移动元素,改变元素的位置或顺序。在D3.js中,我们可以使用d3-drag来实现这样的交互式拖拽功能。

1. 引入d3-drag库

首先,我们需要在HTML文件中引入d3.js和d3-drag库。可以通过CDN或本地文件的方式引入,如下所示:

2. 创建可拖拽的元素

接下来,我们需要创建一个可拖拽的元素。在这个示例中,我们将创建一个简单的矩形,并添加拖拽功能。首先,在HTML中创建一个SVG容器:

<svg id="container" width="400" height="300"></svg>

然后,在JavaScript代码中使用D3.js创建一个矩形元素,并添加拖拽功能:

// 创建一个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.xevent.y来更新元素的位置。在拖拽结束时,我们将填充颜色恢复为初始值。


我们已经掌握了drag的基本用法,下面我们来开发一个颜色匹配的小游戏吧! 游戏思路

  1. 预备5个颜色和对应的中文名称
  2. 用黑色绘制5个矩形区域,填充颜色的名称。
  3. 绘制出5个色块和5个名称对应上。
  4. 移动色块靠近背景区域的时候,当颜色匹配上的时候会出现提示(颜色边框发生变色),释放鼠标会自动吸附到对应色块的中心位置。
  5. 当所有色块都移动到正确位置的时候,提示完成所有的颜色匹配。

这是一个有意思的小游戏,适合小朋友玩哦。改造一个可以开发出各种类似的游戏,比如动物认识。人物认识,形状认识。呵呵

代码如下,自己看,懒得解释了。

// 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)
    }
}

在线地址:scqilin.github.io/d3js/intera…