D3.js + Canvas 繪製組織結構圖

使用 D3.js 默認的 svg 渲染

D3默認的樹狀圖畫圖使用的是svg, 好比這個來自D3做者的例子:javascript

bl.ocks.org/mbostock/43…java

使用svg有好有壞:node

  • 好處是方便操做dom元素, 添加用戶交互
  • 壞處是渲染效率不高, 在數據量較大時頁面易掉幀, 卡頓

在大多數數據量不是特別大狀況下, 使用svg的好處是遠遠蓋過壞處的,但若是咱們真的須要渲染大量的數據呢?git

使用 D3.js + Canvas 渲染

source code

github.com/ssthouse/or…github

demo page

ssthouse.github.io/organizatio…npm

demo gif

上面的demo就是使用 D3.js + Canvas 的方式實現的, 在組織的層數超過300時纔會出現明顯的卡頓, 能知足大部分的組織結構圖的數據.canvas

思路

  1. 使用 D3.js的 Three 在 虛擬Dom 中畫好圖像
  2. 使用Canvas繪圖 API將 虛擬Dom 中的數據 (座標 & 線的path) 等繪製到Canvas上
  3. 使用 Unique-color 的方式實現Canvas 的用戶交互
  4. 經過繪製一張和以前 Canvas數據相同的隱藏Canvas, 並給每個 想要接受用戶交互的節點賦予惟一的顏色
  5. 經過監聽Canvas點擊事件, 獲取點擊像素的顏色值來判斷點擊的節點
  6. 該文章中有對該思路的詳細介紹: medium.com/@lverspohl/…

1.使用 D3.js的 Three 在 虛擬Dom 中畫好圖像

首先調使用D3建立 Tree的虛擬Dom:bash

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 如今就包含告終構圖中每一個 組織節點鏈接線 的座標信息.dom

2. 使用Canvas繪圖 API將 虛擬Dom 中的數據 (座標 & 線的path) 等繪製到Canvas上

在 drawShowCanvas中, 經過 d3.select拿到虛擬的dom節點, 再使用 Canvas的繪圖函數進行繪製, 這裏用到了一些 Util的工具方法, 具體實現請參考源碼.ide

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 :tada:

github.com/ssthouse/or…

# 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 :tada:

D3-blog

若是以爲不錯的話, 不妨點擊下面的連接關注一下 : )

github主頁

知乎專欄

掘金

歡迎關注個人公衆號:

相關文章
相關標籤/搜索