d3.js v3版本實現-樹狀圖
阿新 • • 發佈:2019-01-29
- 一、為什麼選擇d3.js
- 二、d3.js概述
- 三:樹狀圖實現
- 1、建立svg
- 2、在svg元素裡面畫一個g標籤,用於存放樹結構
- 3、組織樹結構,獲取樹結構原始資料,d3加工資料
- 1.建立tree
- 2.加工資料
- 4、data方式繫結資料生成資料結構,建立整棵樹
- 5、建立樹節點,設定樣式並繫結事件
- 問題:節點的text定位問題?連線線的定位問題?
- 6、連線線生成器
- 7、過渡效果 transition
- 8、tooltip及編輯框
- 9、獲取節點,更新節點方式
一、為什麼選擇d3.js
echarts繪製樹狀圖的侷限性:
1、echarts實現的樹節點樣式比較單一,如下圖所示的樹節點的樣式無法實現,尤其是編輯圖示。
2、echarts點選事件只能繫結在樹節點的那個節點(下圖的圈圈)上,而專案需要繫結樹節點的懸浮事件,編輯圖示的點選事件。
3、樹的連線線與圓點分離,專案需要,用echarts基本無法實現。
根據以上業務需求,促使我們選擇d3.js技術實現樹狀圖。
二、d3.js概述
D3 (或者叫 D3.js )是一個基於 web 標準的 JavaScript 視覺化庫. D3 可以藉助 SVG, Canvas 以及 HTML 將你的資料生動的展現出來. D3 結合了強大的視覺化互動技術以及資料驅動 DOM 的技術結合起來, 讓你可以藉助於現代瀏覽器的強大功能自由的對資料進行視覺化.
三:樹狀圖實現
1、建立svg
<svg ref={(r) => this.chartRef = r}></svg>
d3選擇器選擇svg元素賦值寬高
this.svg = d3.select(this.chartRef).attr('width', this.width).attr('height', this.height)2、在svg元素裡面畫一個g標籤,用於存放樹結構
this.svgGroup = this.svg.append('g')3、組織樹結構,獲取樹結構原始資料,d3加工資料
樹結構原始資料格式:{name: '償付能力'1.建立tree
// 對角線生成器,x和y對調則生成從左到右展開的樹this.diagonal = d3.svg.diagonal().projection((d) => { return [this.svgGWidth - d.y, d.x] }) // 建立treethis.tree = d3.layout.tree().size([this.height, this.width])
2.加工資料
// 取最大節點數 乘以 固定高度 得到樹的高度let newHeight = d3.max(levelWidth) * 50this.tree = this.tree.size([newHeight, this.width])// Compute the new tree layout.let nodes = this.tree.nodes(this.root).reverse()// Normalize for fixed-depth.nodes.forEach((d) => { d.y = d.depth * 220 })nodes就是加工後的資料4、data方式繫結資料生成資料結構,建立整棵樹
// 根據id繫結資料,返回update的節點,需要更新的節點let node = this.svgGroup.selectAll('g.node').data(nodes, (d) => { return d.id || (d.id = ++this.i) })// Enter any new nodes at the parent's previous position.// 操作enter的節點,新增新的節點,這裡的節點位置採用老的位置let nodeEnter = node.enter().append('svg:g').attr('class', 'node').attr('transform', (d) => { return `translate(${(this.svgGWidth-source.y0)},${source.x0})` })// Transition exiting nodes to the parent's new position.// 退出節點過渡效果後刪除let nodeExit = node.exit().transition().duration(duration).attr('transform', (d) => { return `translate(${(this.svgGWidth-source.y)},${source.x})` }).remove()// update link 需要更新的連線線let link = this.svgGroup.selectAll('path.link').data(this.tree.links(nodes), (d) => { return d.target.id })
// Enter any new links at the parent's previous position.// 生成新的連線線,連線線位置是老的link.enter().insert('svg:path', 'g').attr('class', 'link').style('fill', 'none').style('stroke', 'rgba(255,255,255,0.2)').style('stroke-width', '1px').attr('d', (d) => {let o = { x: source.x0, y: source.y0 }return this.diagonal({ source: o, target: o })})// Transition exiting nodes to the parent's new position.// exit 過渡效果到新的位置然後移除link.exit().transition().duration(duration).attr('d', (d) => {let o = { x: source.x, y: source.y }return this.diagonal({ source: o, target: o })}).remove()
5、建立樹節點,設定樣式並繫結事件
這個樹節點其實是由 一個大的<g>元素,包了一個text元素,一個tspan元素,一個image元素,一個circle元素組成。
let text = nodeEnter.append('svg:text').on('mouseover', function (d) {d3.select(this).style('fill', '#a5d5e4')// 描述資訊}).on('mouseout', function (d) {d3.select(this).style('fill', '#FFFFFF')})
text.append('svg:tspan').attr('dx', (d) => { return 10 }).attr('text-anchor', (d) => { return 'start' }).style('fill', 'rgba(255,255,255,0.5)').text((d) => {return d.value != null ? d.value : ''})/*** 編輯按鈕,綁定了編輯事件*/nodeEnter.append('svg:image').on('click', function (d) {_// 編輯事件})// 樹節點上的圈圈
nodeEnter.append('svg:circle').attr('r', 1e-6).style('fill', (d) => { return d._children ? d.circleColor : '#0e1821' }).style('stroke', (d) => {return d.circleColor ? d.circleColor : 'steelblue'}).style('stroke-width', '1.5').style('cursor', 'pointer').on('click', (d) => { this.toggle(d); this.update(d) }) // 繫結的樹節點展開和摺疊的方法
問題:節點的text定位問題?連線線的定位問題?
需要計算節點上面文字的長度計算出具體的寬度畫素值,這樣就可以動態確定編輯圖示的位置以及連線線的位置。
下圖所示,編輯圖示和連線線緊跟在文字後面就是這種方式實現的
6、連線線生成器
// 對角線生成器,x和y對調則生成從左到右展開的樹this.diagonal = d3.svg.diagonal().projection((d) => { return [this.svgGWidth - d.y, d.x] })// 生成新的連線線,連線線位置是老的link.enter().insert('svg:path', 'g').attr('class', 'link').style('fill', 'none').style('stroke', 'rgba(255,255,255,0.2)').style('stroke-width', '1px').attr('d', (d) => {let o = { x: source.x0, y: source.y0 }return this.diagonal({ source: o, target: o })})// Transition links to their new position.// 過渡到新的位置link.transition().duration(duration)// .attr('d', this.diagonal).attr('d', (d) => {d.target.y -= _this.calcWidth(d.target) + 25return this.diagonal(d)})7、過渡效果 transition
// Transition nodes to their new position.// 節點過渡效果,移動到新的位置let nodeUpdate = node.transition().duration(duration).style('cursor', 'pointer').attr('transform', (d) => { return `translate(${(this.svgGWidth-d.y)},${d.x})` })8、tooltip及編輯框
實際是懸浮的div有個外掛 d3-tip 做tooltip的,v3支援的不好,升級到v4時可以用。9、獲取節點,更新節點方式
/*** 拿到指定元素* @param{*}element 元素名稱* @param{*}field 葉子節點唯一欄位*/getSvgElement (elementName, field) {if (!elementName) {elementName = 'tspan'}// 預設當前節點if (!field) {field = this.state.field}let arr = d3.selectAll(elementName)[0] //獲取指定元素的所有節點for (let obj of arr) {if (obj.__data__ && obj.__data__.field && obj.__data__.field === field) {return obj}}return null}
更新節點的值
getSvgElement (elementName, field).text(this.state.leftValue + this.state.unit).style('fill', color)