D3.js + Canvas 绘制组织结构图

D3.js + Canvas 绘制组织结构图

大家好,又见面了,我是你们的朋友全栈君。

D3.js + Canvas 绘制组织结构图

使用 D3.js 默认的 svg 渲染

D3默认的树状图画图使用的是svg, 比如这个来自D3作者的例子:

https://bl.ocks.org/mbostock/…

使用svg有好有坏:

  • 好处是方便操作dom元素, 添加用户交互
  • 坏处是渲染效率不高, 在数据量较大时页面易掉帧, 卡顿

在大多数数据量不是特别大情况下, 使用svg的好处是远远盖过坏处的,但如果我们真的需要渲染大量的数据呢?

使用 D3.js + Canvas 渲染

source code

https://github.com/ssthouse/o…

demo page

https://ssthouse.github.io/or…

demo gif

上面的demo就是使用 D3.js + Canvas 的方式实现的, 在组织的层数超过300时才会出现明显的卡顿, 能满足大部分的组织结构图的数据.

思路

  1. 使用 D3.js的 Three 在 虚拟Dom 中画好图像
  2. 使用Canvas绘图 API将 虚拟Dom 中的数据 (坐标 & 线的path) 等绘制到Canvas上
  3. 使用 Unique-color 的方式实现Canvas 的用户交互

    1. 通过绘制一张和之前 Canvas数据相同的隐藏Canvas, 并给每一个 想要接受用户交互的节点赋予唯一的颜色
    2. 通过监听Canvas点击事件, 获取点击像素的颜色值来判断点击的节点
    3. 该文章中有对该思路的详细介绍: https://medium.com/@lverspohl…

1.使用 D3.js的 Three 在 虚拟Dom 中画好图像

首先调使用D3创建 Tree的虚拟Dom:

this.data = this.d3.hierarchy(data)
this.treeGenerator = this.d3.tree()
  .nodeSize([this.nodeWidth, this.nodeHeight])
let nodes = this.treeData.descendants()
let links = this.treeData.links()

上面的变量 nodeslinks 现在就包含了结构图中每个 组织节点连接线 的坐标信息.

2. 使用Canvas绘图 API将 虚拟Dom 中的数据 (坐标 & 线的path) 等绘制到Canvas上

在 drawShowCanvas中, 通过 d3.select拿到虚拟的dom节点, 再使用 Canvas的绘图函数进行绘制, 这里用到了一些 Util的工具方法, 具体实现请参考源码.

  drawShowCanvas () {
    this.context.clearRect(-50000, -10000, 100000, 100000)

    let self = this
    // draw links
    this.virtualContainerNode.selectAll('.link')
      .each(function () {
        let node = self.d3.select(this)
        let linkPath = self.d3.linkVertical()
          .x(function (d) {
            return d.x
          })
          .y(function (d) {
            return d.y
          })
          .source(function () {
            return {x: node.attr('sourceX'), y: node.attr('sourceY')}
          })
          .target(function () {
            return {x: node.attr('targetX'), y: node.attr('targetY')}
          })
        let path = new Path2D(linkPath())
        self.context.stroke(path)
      })

    this.virtualContainerNode.selectAll('.orgUnit')
      .each(function () {
        let node = self.d3.select(this)
        let treeNode = node.data()[0]
        let data = treeNode.data
        self.context.fillStyle = '#3ca0ff'
        let indexX = Number(node.attr('x')) - self.unitWidth / 2
        let indexY = Number(node.attr('y')) - self.unitHeight / 2

        // draw unit outline rect (if you want to modify this line ===>   please modify the same line in `drawHiddenCanvas`)
        Util.roundRect(self.context, indexX, indexY, self.unitWidth, self.unitHeight, 4, true, false)

        Util.text(self.context, data.name, indexX + self.unitPadding, indexY + self.unitPadding, '20px', '#ffffff')
        // Util.text(self.context, data.title, indexX + self.unitPadding, indexY + self.unitPadding + 30, '20px', '#000000')
        let maxWidth = self.unitWidth - 2 * self.unitPadding
        Util.wrapText(self.context, data.title, indexX + self.unitPadding, indexY + self.unitPadding + 24, maxWidth, 20)
      })
  }

3. 使用 Unique-color 的方式实现Canvas 的用户交互

下图中可以看到, 实际上是有两张Canvas的, 其中下面的Canvas除了的节点颜色不同外, 和上面的Cavans绘制的数据完全相同.

  drawCanvas () {
    this.drawShowCanvas()
    this.drawHiddenCanvas()
  }

unique color.png

在上面一张Canvas上监听用户点击事件, 通过象素的坐标, 在下面一张图中拿到用户点击的节点 (注意: 颜色和节点的键值对 是在下面一张Canvas绘制的时候就已经创建好的.)

  setClickListener () {
    let self = this
    this.canvasNode.node().addEventListener('click', function (e) {
      let colorStr = Util.getColorStrFromCanvas(self.hiddenContext, e.layerX, e.layerY)
      let node = self.colorNodeMap[colorStr]
      if (node) {
        // let treeNodeData = node.data()[0]
        // self.hideChildren(treeNodeData, true)
        self.toggleTreeNode(node.data()[0])
        self.update(node.data()[0])
      }
    })
  }

下面是创建 unique-color和节点的 键值对 的参考代码:

  addColorKey () {
    // give each node a unique color
    let self = this
    this.virtualContainerNode.selectAll('.orgUnit')
      .each(function () {
        let node = self.d3.select(this)
        let newColor = Util.randomColor()
        while (self.colorNodeMap[newColor]) {
          newColor = Util.randomColor()
        }
        node.attr('colorKey', newColor)
        node.data()[0]['colorKey'] = newColor
        self.colorNodeMap[newColor] = node
      })
  }

其他

To draw your own nested data

please replace the data in /src/base/data-generator with your own nested data.

please add your data drawing logic in /src/components/org-chart.js #drawShowCanvas

Want to develop locally ?

source code

if you like it , welcome to star and fork

https://github.com/ssthouse/o…

# install dependencies
npm install

# serve with hot reload at localhost
npm run dev

# build for production with minification (build to ./docs folder, which can be auto servered by github page ?)
npm run build

想继续了解 D3.js ?

这里是我的 D3.js数据可视化 的github 地址, 欢迎 start & fork

D3-blog

如果觉得不错的话, 不妨关注一下 : )

github主页

知乎专栏

掘金

想直接联系我 ?

邮箱: ssthouse@163.com

微信:

wechat

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/107487.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 如何搭建ntp时间服务器(搭建时间同步服务器)

    NTP(NetworkTimeProtocol,网络时间协议)是用来使网络中的各个计算机时间同步的一种协议。它的用途是把计算机的时钟同步到世界协调时UTC,其精度在局域网内可达0.1ms,在互联网上绝大多数的地方其精度可以达到1-50ms。NTP服务器就是利用NTP协议提供时间同步服务的。

    2022年4月9日
    434
  • forkjoin用法_fork join_none

    forkjoin用法_fork join_noneFork/Join是一个分而治之的任务框架,如一个任务需要多线程执行,分割成很多块计算的时候,可以采用这种方法。动态规范:和分而治之不同的是,每个小任务之间互相联系。工作密取:分而治之分割了每个任务之后,某个线程提前完成了任务,就会去其他线程偷取任务来完成,加快执行效率。同时,第一个分配的线程是从队列中的头部拿任务,当完成任务的线程去其他队列拿任务的时候是从尾部拿任务,所以这样就避免了竞争。在Java的Fork/Join框架中,使用两个类完成上述操作:  1.ForkJoinTask:我们要使用F

    2025年8月15日
    2
  • 小旋风虚拟服务器怎么用,超级小旋风asp服务器软件使用图文教程

    小旋风虚拟服务器怎么用,超级小旋风asp服务器软件使用图文教程超级小旋风asp服务器软件是由残剑无敌[1]在NETBOX核心下开发的一套强大简洁的ASPWEB服务器软件,使用这个软件的您完全可以抛弃体积庞大的WINNT,WIN2000服务器系统及漏洞百出的IIS了。可以在任何一个系统上调试和发布ASP程序。目前测试通过的操作系统为:Windows98;Windows98SE;WindowsME;WindowsNT+IE4;Windows2000;W…

    2025年7月25日
    3
  • SecureCRTPortable(CRT)快捷键方法

    SecureCRTPortable(CRT)快捷键方法1.添加按钮,以进入beeline为例1)选中按钮栏2)3)beeline\r\p\p!connectjdbc:hive2://jh01:10000\rroot\r123456\r2.查找历史命令Ctrl+r

    2022年6月10日
    61
  • 个人电脑怎么架设FTP服务器全方案

    个人电脑怎么架设FTP服务器全方案1、架设FTP服务器1.1使用IIS架设FTP服务器如果只是想建立一个小型的同时在线用户数不超过10个的FTP服务器,且不会同时进行大流量的数据传输,则可以使用IIS5作为服务器软件来架设.这里我们对一下案例予以说明,大家借鉴:一台1.1.1安装IIS的FTP服务WindowsXP默认状态是不安装FTP服务的,需要手动添加安装,安装过程如下:(…

    2022年7月21日
    12
  • iPhone屏幕尺寸(包含7p)

    iPhone屏幕尺寸(包含7p)转自:http://blog.csdn.net/jeikerxiao/article/details/52768269px与pt区别字体大小的设置单位,常用的有2种:px、pt。这两个有什么区别呢?先搞清基本概念:px就是表示pixel,像素,是屏幕上显示数据的最基本的点;pt就是point,是印刷行业常用单位,等于1/72英寸。px全称为p

    2022年5月15日
    44

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号