d3.js学习笔记vue2(5)drag拖拽操作数据

在网上有很多教程讲如何用drag拖拽操作图形元素的,但是他们都没说如何操作数据。

因为我要写的东西是一个通过在图形界面通过拖拽调整几何形状的程序,所以特别注意了这一点。

首先在zoom里已经有了拖拽功能了,可以让整个场景平移,但是原始数据不变。

而我需要在特定单元上添加拖拽功能,具体来说就是拖拽线条上的标记点,改变线条的数据。

所以必须要让特定的拖拽覆盖zoom的拖拽,也就是说在程序执行时必须先执行zoom,后执行特定的drag

drag的定义。要特别说明,在以前的版本中,需要用d3.behavior.drag(),但是现在的新版把d3打碎成很多的小模块,引用时可以按需引用,可以减少生成的文件的体积,减少网络传输的负担,现在v5可以直接用d3.drag()

this.dragHandler = d3.drag()
  .on('start', (d, i, a) => this.dragStart(d, i, a))
  .on('drag', (d, i, a) => this.dragDrag(d, i, a))
  .on('end', (d, i, a) => this.dragEnd(d, i, a))
this.dragHandler(this.symbols.node.selectAll('path')) // this.symbols.node.selectAll('path').call(this.dragHandler)

先定义一个d3.drag(),然后给start/drag/end分别赋予不同的响应函数

比较常见的start是改变拖拽元素的颜色,然后end再改回来,这样用户可以清楚地看到拖拽了哪个元素。如果在拖拽完成后有存储或者其他需求,可以添加。很少见到start需要做特殊处理的情况。

dragStart (d, i, a) {
      d3.select(a[i])
        .attr('stroke', '#00FFFF')
},
dragEnd (d, i, a) {
      d3.select(a[i])
        .attr('stroke', '#FF0000')
}

在这个函数中需要找到拖拽的元素,本来d3提供了下面的方法来找

d3.select(this)

vue对this有特殊定义,所以在vue组件中这种方法不能用了,只能另辟蹊径

这个匿名函数有3个形参,其中

d=q[i] // 每个元素对应的数据
i // 数据的索引
q // 数据的父节点

 直接用匿名函数的第2个和第3个元素来选取

let target = d3.select(a[i])

 就可以直接找到拖拽的元素。

拖动的函数是最复杂的

dragDrag (d, i, a) {
      let target = d3.select(a[i])
      let [linenum, pointnum] = [target.attr('linenum'), target.attr('pointnum')]
      this.$store.state.lineControlPoints[linenum][pointnum][0] = this.axises.xScale.invert(d3.event.x)
      this.$store.state.lineControlPoints[linenum][pointnum][1] = this.axises.yScale.invert(d3.event.y)
      this.$store.commit('lineUpdate')
      this.polylines.node.selectAll('path')
        .data(this.$store.state.lineControlPoints)
        .attr('d', (d) => this.polylines.lineGenerator(d))
      this.splines.node.selectAll('path')
        .data(this.$store.state.linePoints)
        .attr('d', (d) => this.splines.lineGenerator(d))
      target.attr('transform', `translate(${d3.event.x},${d3.event.y})`)
    },

想在drag同时对图形进行实时更新来进行数据操作就要在dragDrag函数里添加数据操作 。

因为我为了操作方便,所以在当初生成这个元素时添加了linenum和pointnum属性,这里直接用attr取出来就可以了。

let [linenum, pointnum] = [target.attr('linenum'), target.attr('pointnum')]

这里的lineControlpoints和linePoints可以不是一致的,比如lineControlPoints是控制点,linePoints是样条线或者Bezier曲线,需要一些运算,而且这些数据还和程序中其他部分关联,所以使用vuex。因为是拖拽过程中实时更新,所以采用了同步的commit,而不是异步的dispatch

this.$store.state.lineControlPoints[linenum][pointnum][0] = this.axises.xScale.invert(d3.event.x)
this.$store.state.lineControlPoints[linenum][pointnum][1] = this.axises.yScale.invert(d3.event.y)
this.$store.commit('lineUpdate')

数据处理完成以后,从lineControlPoints得到了linePoints

要实时呈现出来,就要设定线条的data,还要处理d属性,因为svg中的元素归根结底是通过d属性来呈现的,仅仅处理到data是不会刷新图形的

这个案例用的是用d3.line()处理的,此处的处理方法和设定线条数据的方法一样

this.polylines.node.selectAll('path')
        .data(this.$store.state.impeller.stage.lineControlPoints)
        .attr('d', (d) => this.polylines.lineGenerator(d))
this.splines.node.selectAll('path')
        .data(this.$store.state.impeller.stage.linePoints)
        .attr('d', (d) => this.splines.lineGenerator(d))

如果vuex中存储的数据和这里要呈现的数据之间还有较多运算,也可以引入其他函数

因为目标点是一个symbol,而且symbol的数据也是直接关联的vuex数据,所以不做过多处理,而是直接移动symbol的位置即可

target.attr('transform', `translate(${d3.event.x},${d3.event.y})`)

这样,在拖动的同时,标记点随鼠标移动,vuex中的数据随时更新,svg中呈现的线条图形也跟着更新

这里要特别提示,这里移动的是symbol,而symbol默认的初始位置是原点,也就是svg的左上角。所以这里translate直接到鼠标位置。如果是其他元素,需要根据元素自身的实际情况来设定。

对于大多数元素,可以设定drag的origin,然后

dragHandler.origin(target.x, target.y)
target.attr('transform', `translate(${d3.event.dx},${d3.event.dy})`)

一定要设定origin,不仅仅是因为这样有助于计算鼠标的实际移动距离,而且在鼠标刚刚点中目标元素时,目标元素不会突然跳动使得目标元素的定位点和鼠标位置重合。比如点击饼图的单元时,饼图单元的中心位置在半径均值、角相位均值的扇形中心位置,如果鼠标点击到饼图扇形的其他位置,饼图会突然跳动使得中心位置跳动到鼠标位置,这样的跳动会改变操作者的预期,使得操作不方便。

如果拖拽的目标不仅仅是改变图形呈现的位置,还要改变图形切实的定位,那么

target.attr('x', d3.event.x)
  .attr('y', d3.event.y)

这里使用的是x、y属性,涉及到具体的图形元素,可能有不同的属性,比如圆形就是cx、cy,可以根据实际情况调整

如果绑定是简单的数据其实也可以用

d3.select(this).data()[0]=xxx

来直接操作绑定的数据,但是这样不是很灵活。比如说我这里的点数据是3维数组,第0维是线条编号。这样我在把数据绑定到标记点的时候就要做一次转化。而且处理完节点以后,还要处理Bezier曲线,这需要较复杂的函数。还不如直接处理原始数据,然后重新给图形赋值。

该示例代码是vue单文件组件中的代码,所以this指的是上下文组件,this里的数据是在data里定义的

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
D3.js 是一个强大的 JavaScript 数据可视化库,而 Vue 是一个流行的 JavaScript 前端框架。它们可以很好地结合使用,以创建交互式和动态的数据可视化。 在将 D3.js 结合 Vue 使用时,有几种常见的方法可以实现: 1. 使用 Vue 的生命周期钩子函数:可以在 Vue 组件的 `mounted` 或 `updated` 钩子函数中初始化和更新 D3.js 可视化。这样,在组件渲染完成后或数据更新后,可以调用 D3.js 的相关方法来绘制、更新或销毁可视化。 2. 创建自定义 Vue 指令:可以编写一个自定义指令,用于在 DOM 元素上直接调用 D3.js 的方法。这样,可以在模板中使用指令来绑定数据和配置,将 D3.js 的功能封装到指令中,使代码更具可维护性和复用性。 3. 使用第三方插件或库:有一些第三方插件或库可以帮助更好地结合 D3.jsVue。例如,`vue-d3` 是一个专门为 D3.jsVue 设计的插件,提供了一些方便的组件和指令,简化了 D3.jsVue 的集成过程。 无论选择哪种方法,重要的是要确保良好的数据流和交互机制。使用 Vue 的响应式数据和事件系统,可以实现数据的双向绑定、动态更新和交互操作,以提供更丰富的用户体验。 需要注意的是,D3.jsVue 在处理 DOM 和数据方面有不同的思维方式和方法,因此在结合使用时,需要理解和熟悉两者的工作原理,并根据具体需求进行适当的调整和优化。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值