D3+react实现可放大、缩小、刷新、下载的横向组织架构图
代码实现如图所示:
条件,需要引入以下模块:
import * as d3 from ‘d3’
import saveSvg from ‘save-svg-as-png’ // svg下载成png图片的格式
实现代码如下:
import React, { Component } from ‘react’
import * as d3 from ‘d3’
import saveSvg from ‘save-svg-as-png’
// 过渡时间
const duration = 0
export default class modal extends Component {
constructor(props){
super(props)
this.constNode = []; // 大节点数据
this.constRelation = []; // 小节点数据
this.resData = {};
this.layoutTree = ‘’; // 生成结构
this.diamonds = ‘’; // 方块形状
this.i = 0; // 展示节点的下标
this.hasChildNodeArr = []; // 树状内元素数组
this.originDiamonds = ‘’; // 源头对象
this.tree = {}; // 组件结构
this.rootUp = ‘’; // 初始化起点
this.rootDown = ‘’; // 初始化起点
this.svg = ‘’; // 主图
this.nodeData = {}; //节点信息
this.isShowloading = true; // 穿透图 false表示内容加载完成
this.isShowDetail = false; // 穿透图 false表示内容加载完成
this.isShowExportPng = true // 保存按钮显示
this.zoom = null;
}
componentDidMount() {
var treeData = [
{
“name”: “中国”,
“children”: [
{
“name”: “河南”,
“children”: [
{
“name”: “郑州”,
},
{
“name”: “鹤壁”,
},
]
},
{
“name”: “山东”,
“children”: [
{
“name”: “济南”,
},
{
“name”: “德州”,
},
]
},
{
“name”: “上海”,
“children”: [
{
“name”: “浦东新区”,
}
]
},
]
}
];
let svgW = document.body.clientWidth
let svgH = document.body.clientHeight
// 方块形状
this.diamonds = {
w: 175,
h: 68,
intervalW: 200,
intervalH: 150
}
// // .size()设置树的可用大小,因此根据表兄节点、表兄节点等之间的间距,它们可能会被压缩在一起并重叠
// // 使用.nodeSize()只是说每个节点应该有这么大的空间,所以它们永远不会重叠!
this.layoutTree = d3.tree().nodeSize([this.diamonds.intervalW, this.diamonds.intervalH])
.separation(() => 0.5)
this.zoom = d3.zoom()
.scaleExtent([0.5, 3])
.on("zoom", ()=>{ // zoom事件
this.svg.attr("transform", `${d3.event.transform.translate(svgW / 4,200)} scale(${d3.zoomTransform(d3.select("svg").node()).k})`);
});
this.svg = d3.select('#lgwTree').append('svg').attr('width', svgW).attr('height', svgH).attr('id', 'treesvg')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.call(this.zoom)
.on('dblclick.zoom', null)
.attr('style', 'position: relative;z-index: 2;')
.append('g').attr('id', 'gAll')
.attr('transform', 'translate(' + (svgW / 4) + ',' + (200) + ')')
this.rootUp = d3.hierarchy(treeData[0])
this.update(this.rootUp);
}
diagonal (d) {
// console.log(d)
// 树图朝右展示
return “M”+d.source.y+" “+d.source.x+
“L”+(d.source.y+120)+” “+(d.source.x)+
" L”+(d.source.y+120)+" “+(d.target.x)+” L"+
d.target.y+" “+(d.target.x);
// 树图朝下展示
// return “M”+(d.source.x+10)+” “+d.source.y+
// “L”+(d.source.x+10)+” “+(d.target.y-50)+
// " L”+(d.target.x+10)+" “+(d.target.y-50)+” L"+
// (d.target.x+10)+" "+(d.target.y);
}
click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
this.update(d);
}
update(source) {
let _this=this;
if (source.parents === null) {
source.isOpen = !source.isOpen
}// 初始化展开
let nodes = this.layoutTree(this.rootUp).descendants();
let links = this.layoutTree(this.rootUp).links();
// Normalize for fixed-depth.设置y坐标点,每层占180px
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…每个node对应一个group
var node = this.svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++this.i); });//data():绑定一个数组到选择集上,数组的各项值分别与选择集的各元素绑定
// Enter any new nodes at the parent's previous position.新增节点数据集,设置位置
var nodeEnter = node.enter().append("g") //在 svg 中添加一个g,g是 svg 中的一个属性,是 group 的意思,它表示一组什么东西,如一组 lines , rects ,circles 其实坐标轴就是由这些东西构成的。
.attr("class", "node") //attr设置html属性,style设置css属性
.attr('transform',d => "translate(" +( d.y) + "," + (d.x) + ")")
.on("click", (d)=>{
// this.click(d)
});
// 创建圆 加减号
nodeEnter.append('circle')
.attr('type', d => d.id || (d.id = 'text' + ++this.i))
.attr('r', d => d._children || d.children ? '9' : '0')
.attr('cy', d => 0)
.attr('cx', d => 55)
.attr('class', 'circle')
.attr('fill', '#fff') // 初始展示的几个圆圈填充色
.attr('stroke', d =>'#128BED') // 所有圆圈的边框色
.on('click', function(d){
_this.click(d)
setTimeout(() => {
if (this.innerHTML === '-') {
d.isOpen = false
this.innerHTML = '+'
} else {
d.isOpen = true
this.innerHTML = '-'
}
}, 0)
})
// 加减号
nodeEnter.append('svg:text')
.attr('type', d => d.id || (d.id = 'text' + ++this.i))
.on('click', function(d){
_this.click(d)
setTimeout(() => {
if (this.innerHTML === '+') {
d.isOpen = false
this.innerHTML = '-'
} else {
d.isOpen = true
this.innerHTML = '+'
}
}, 0)
})
.attr('x', 55)
//eslint-disable-next-line
.attr('dy', d => 5)
.attr('text-anchor', 'middle')
.attr('font-size', '18')
.attr('fill', '#128BED') // 圆圈内加减号颜色
//eslint-disable-next-line
.text(d => d.data.children?(d.isOpen?'+' : '-'): '')
nodeEnter.append("rect")
.attr("x",-23)
.attr("y", -10)
.attr("width",70)
.attr("height",22)
.attr("rx",10)
.style("fill", "#357CAE");//d 代表数据,也就是与某元素绑定的数据。
// 添加箭头
nodeEnter.append('marker')
.attr("id","arrow")
.attr("markerUnits","strokeWidth")
.attr("markerWidth","12")
.attr("markerHeight","12")
.attr("viewBox","0 0 12 12")
.attr("refX",d=>{
return '38'
})
.attr("refY",6)
.attr("orient","auto")
.attr('stroke-width', 2) //箭头宽度
.append('path')
.attr('d', 'M2,0 L14,6 L2,12 L5,6 L2,0') // 箭头的路径
.attr('fill', '#128BED') // 箭头颜色
//添加标签
nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? 13 : 13; })
.attr("dy", "6")
.attr("text-anchor", "middle")
.text(d=>d.data.name)
.attr('font-size', 12)
.style("fill", "white")
.attr('cursor', 'pointer')
.style("fill-opacity", 1);
// Transition nodes to their new position.将节点过渡到一个新的位置-----主要是针对节点过渡过程中的过渡效果
//node就是保留的数据集,为原来数据的图形添加过渡动画。首先是整个组的位置
var nodeUpdate = node.transition() //开始一个动画过渡
.duration(duration) //过渡延迟时间,此处主要设置的是圆圈节点随斜线的过渡延迟
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("rect")
.attr("x",-23)
.attr("y", -10)
.attr("width",70)
.attr("height",22)
.attr("rx",10)
.style("fill", "#357CAE");
nodeUpdate.select("text")
.attr("text-anchor", "middle")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.过渡现有的节点到父母的新位置。
//最后处理消失的数据,添加消失动画
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
// nodeExit.select("circle")
// .attr("r", 1e-6);
nodeExit.select("rect")
.attr("x",-23)
.attr("y", -10)
.attr("width",70)
.attr("height",22)
.attr("rx",10)
.style("fill", "#357CAE");
nodeExit.select("text")
.attr("text-anchor", "middle")
.style("fill-opacity", 1e-6);
// Update the links…线操作相关
//再处理连线集合
var link = this.svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
//添加新的连线
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", (d)=> {
return this.diagonal(d)
})
.attr('stroke', '#777') // 线条颜色
.style('fill-opacity', 0) // 线条绘制时与直线偏移的区域的透明度
.attr('marker-end', 'url(#arrow)');
// Transition links to their new position.将斜线过渡到新的位置
//保留的连线添加过渡动画
link.transition()
.duration(duration)
.attr('d', d => this.diagonal(d))
// Transition exiting nodes to the parent's new position.过渡现有的斜线到父母的新位置。
//消失的连线添加过渡动画
link.exit().transition()
.duration(duration)
.attr("d", (d) =>{
return this.diagonal(d)
})
.remove();
// Stash the old positions for transition.将旧的斜线过渡效果隐藏
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// 保存图片
downloadPng () {
const base64Data = document.getElementById(‘treesvg’)
const result = base64Data.getBBox()
const width = result.width
const height = result.height
const top = result.y
const left=result.x
const backgroundColor = ‘#fff’
const opts = {
backgroundColor, //使用给定的background 颜色创建 PNG。 默认为透明
left: left - 50, // 指定viewbox位置的左边。 默认为 0.
top: top - 100, // 指定viewbox位置的顶部。 默认为 0
width: width + 100, // 指定图像的宽度。 如果给定的是,或者元素宽度的边界或者元素宽度的CSS,或者 0的计算宽度,默认为,。
// scale: 1, // 更改输出PNG的分辨率。 默认为 1,与源SVG相同的维度。
height: height + 200, // 指定图像的高度。 如果给定的是,或者元素高度的边界或者元素高度的CSS,或者 0的计算高度,则默认为,。
encoderType: ‘image/png’,
encoderOptions: 1, // 0和 1之间的数字表示图像质量。 默认值为 0.8
}
saveSvg.saveSvgAsPng(base64Data,${ '树状图' }${ '.png' }
,opts)
}
// 刷新页面
refresh=()=>{
console.log(‘刷新’);
d3.select(“svg”).call(this.zoom.transform, d3.zoomIdentity);
}
scaleBig(){
console.log(‘放大’);
this.zoom.scaleBy(d3.select(“svg”), 1.1); // 执行该方法后 会触发zoom事件
}
scaleSmall(){
console.log(‘缩小’);
this.zoom.scaleBy(d3.select(“svg”), 0.9); // 执行该方法后 会触发zoom事件
}
render() {}
}
**注意:**代码有冗余,或不足,请多指教。。。