D3+react实现可放大、缩小、刷新、下载的横向组织架构图

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() {}
}

**注意:**代码有冗余,或不足,请多指教。。。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值