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

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

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


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

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

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

相关推荐
gnip10 分钟前
链式调用和延迟执行
前端·javascript
SoaringHeart21 分钟前
Flutter组件封装:页面点击事件拦截
前端·flutter
杨天天.23 分钟前
小程序原生实现音频播放器,下一首上一首切换,拖动进度条等功能
前端·javascript·小程序·音视频
Dragon Wu33 分钟前
React state在setInterval里未获取最新值的问题
前端·javascript·react.js·前端框架
Jinuss34 分钟前
Vue3源码reactivity响应式篇之watch实现
前端·vue3
YU大宗师37 分钟前
React面试题
前端·javascript·react.js
木兮xg38 分钟前
react基础篇
前端·react.js·前端框架
ssshooter1 小时前
你知道怎么用 pnpm 临时给某个库打补丁吗?
前端·面试·npm
IT毕设梦工厂1 小时前
大数据毕业设计选题推荐-基于大数据的国家医用消耗选品采集数据可视化分析系统-Hadoop-Spark-数据可视化-BigData
大数据·hadoop·信息可视化·spark·毕业设计·数据可视化·bigdata
IT利刃出鞘2 小时前
HTML--最简的二级菜单页面
前端·html