參考的例子:http://bl.ocks.org/robschmuecker/7880033
- 一、爲什麼選擇d3.js
- 二、d3.js概述
- 三:樹狀圖實現
- 1、創建svg
- 2、在svg元素裏面畫一個g標籤,用於存放樹結構
- 3、組織樹結構,獲取樹結構原始數據,d3加工數據
- 4、data方式綁定數據生成數據結構,創建整棵樹
- 5、創建樹節點,設置樣式並綁定事件
- 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 的技術結合起來, 讓你可以藉助於現代瀏覽器的強大功能自由的對數據進行可視化.
API地址: https://github.com/xswei/d3js_doc
三:樹狀圖實現
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: '償付能力',
field: 'root',
circleColor: 'white',
notEdit: true,
children: [
{
name: '經營和投資彙總',
field: 'jyhtzhz',
value: 40,
unit: '億元',
desc: '銷售現金回款+租金收入-土地購買支出-一般成本彙總-稅務開支彙總',
circleColor: '#09BE49',
children: [
{
name: '銷售現金回款',
field: 'xsxjhk',
fieldRelation: 'jyhtzhz,xsxjhk,zjsr,tdgmzc,ybcbhz,swkzhz',
funCalc: 'xsxjhk+zjsr-tdgmzc-ybcbhz-swkzhz',
value: 100,
unit: '億元',
desc: '銷售金額*回款率',
circleColor: '#09BE49',
}
]
}
}
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) * 50
this.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) + 25
return 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)