D3股权穿透图

前言:最近做了一个项目,主要就是实现各种类似企查查的各种图谱,欢迎交流。后期将完成的谱图全部链接上,目前已大致实现了:
【企业关系图谱】、【企业构成图谱】、【股权穿透图】、【股权结构图】、【关联方认定图】

准备:

vue版本:安装的3.x但是代码风格用的2.x,不太标准
D3版本:3.2.7

最终效果图展示:

在这里插入图片描述
实现思路:
1、将树结构看做是上下两部分:以【北京麻六记餐饮管理有限公司】为根节点,分为上游股东和下游对外投资
2、绘制两颗树,将根节点重合,及得到效果图

右侧实现的功能也在js里了,同企查查页面功能,全屏用的其他查件

主界面vue文件:

<!-- 股权穿透图 -->
<template>
  <div id="borrow" style="width: 100%;height: 100%;background-color: #fff;">
    <div id="mountNode" style="width: 100%;height: 100%;"></div>
    <ToolBox @maoScale="maoScale" @simpleChange="simpleChange" @editChange="editChange" @refresh="refresh" @exportImg="exportImg" @screenfullChange="toggleFullScreen" />
  </div>
</template>
<script>
import ToolBox from './components/Equity/ToolBox.vue'
import { drawing, simpleChange1, zoomClick, editChange1 } from './components/Equity/index.js'
import { D3Mixin } from '@/mixin/D3Mixin'

export default {
  components: {
    Header,
    ToolBox
  },
  mixins: [D3Mixin],
  data() {
    return {
    };
  },
  mounted() {
    drawing()
  },
  methods: {
    simpleChange(val) {
      simpleChange1(val.value)
    },
    maoScale(val) {
      zoomClick(val)
    },
    editChange(val) {
      editChange1(val.value)
    },
    refresh() {
      drawing()
    },
    exportImg() {
      this.downloadImpByChart('股权穿透图谱', '北京马六级餐饮')
    }
  }
};
</script>

<style lang="scss" scoped>
.downwardNode text,
.upwardNode text {
  font: 10px sans-serif;
}

.downwardLink {
  fill: none;
  stroke-width: 1px;
  /* opacity: 0.5; */
}

.upwardLink {
  fill: none;
  stroke-width: 1px;
  /* opacity: 0.5; */
}
::v-deep .downLine {
  stroke: #128bed;
  stroke-dasharray: 6, 2;
  stroke-dashoffset: 20;
  animation: 1s down-lines infinite linear;
  z-index: 999;
  opacity: 1;
  stroke-width: 2px;
}
::v-deep .upLine {
  stroke: #128bed;
  stroke-dasharray: 6, 2;
  stroke-dashoffset: 20;
  animation: 1s up-lines infinite linear;
  z-index: 999;
  opacity: 1;
  stroke-width: 2px;
}
@keyframes down-lines {
  0% {
    stroke-dashoffset: 10;
  }

  100% {
    stroke-dashoffset: -10;
  }
}
@keyframes up-lines {
  0% {
    stroke-dashoffset: -10;
  }

  100% {
    stroke-dashoffset: 10;
  }
}
::v-deep .isExpand {
  dominant-baseline: middle;
  text-anchor: middle;
}

::v-deep .linkname {
  text-anchor: middle;
}
</style>


数据json文件 EquityJson.json:

{
    "data": {
        "short_name": "北京麻六记餐饮管",
        "p_trees": [
            {
                "short_name": "宋娜",
                "level": "1",
                "isKey": true,
                "amount": "512.0",
                "has_problem": "0",
                "percent": "0.64",
                "pid": "77bd1a4a4459cafe587269a271c2261a",
                "sh_type": "工商股东",
                "children": [],
                "eid": "",
                "identifier": "2",
                "name": "宋娜",
                "type": "P"
            },
            {
                "short_name": "北京食通达科技发",
                "level": "1",
                "amount": "288.0",
                "has_problem": "0",
                "percent": "0.36",
                "sh_type": "工商股东",
                "children": [
                    {
                        "short_name": "菲特兰装饰设计",
                        "level": "2",
                        "amount": "640.0",
                        "has_problem": "0",
                        "percent": "0.64",
                        "sh_type": "工商股东",
                        "children": [
                            {
                                "short_name": "汪玺",
                                "level": "3",
                                "amount": "9.9",
                                "has_problem": "0",
                                "percent": "0.99",
                                "pid": "1421feeaf56724cdf537590b6e4f12e5",
                                "sh_type": "工商股东",
                                "children": [],
                                "eid": "",
                                "identifier": "5",
                                "name": "汪玺",
                                "type": "P"
                            },
                            {
                                "short_name": "安勇",
                                "level": "3",
                                "amount": "0.1",
                                "has_problem": "0",
                                "percent": "0.01",
                                "pid": "0d33371f31e7ce4734f75c575daf1a0b",
                                "sh_type": "工商股东",
                                "children": [],
                                "eid": "",
                                "identifier": "6",
                                "name": "安勇",
                                "type": "P"
                            }
                        ],
                        "eid": "50aeb7cb-8e99-4def-b1d8-b17fbdd0d41b",
                        "identifier": "4",
                        "name": "北京菲特兰装饰设计有限公司",
                        "type": "E"
                    },
                    {
                        "short_name": "MSA CHINA FUND II L.P",
                        "level": "2",
                        "amount": "200.0",
                        "has_problem": "0",
                        "percent": "0.2",
                        "sh_type": "工商股东",
                        "children": [],
                        "eid": "b3baec3812d0a3e83e96dbc492c74802",
                        "identifier": "7",
                        "name": "MSA CHINA FUND II L.P",
                        "type": "UE"
                    },
                    {
                        "short_name": "五鼎通达(上海)",
                        "level": "2",
                        "amount": "160.0",
                        "has_problem": "0",
                        "percent": "0.16",
                        "sh_type": "工商股东",
                        "children": [
                            {
                                "short_name": "安勇",
                                "level": "3",
                                "amount": "6.25",
                                "has_problem": "0",
                                "percent": "0.625",
                                "pid": "0d33371f31e7ce4734f75c575daf1a0b",
                                "sh_type": "工商股东",
                                "children": [],
                                "eid": "",
                                "identifier": "9",
                                "name": "安勇",
                                "type": "P"
                            },
                            {
                                "short_name": "杨洋",
                                "level": "3",
                                "amount": "1.25",
                                "has_problem": "0",
                                "percent": "0.125",
                                "pid": "05c5376b47c1d675119af0eb39fa0ec9",
                                "sh_type": "工商股东",
                                "children": [],
                                "eid": "",
                                "identifier": "10",
                                "name": "杨洋",
                                "type": "P"
                            },
                            {
                                "short_name": "李萍",
                                "level": "3",
                                "amount": "1.25",
                                "has_problem": "0",
                                "percent": "0.125",
                                "pid": "d222321e2f8e59a3d7dbc0e782fec491",
                                "sh_type": "工商股东",
                                "children": [],
                                "eid": "",
                                "identifier": "11",
                                "name": "李萍",
                                "type": "P"
                            },
                            {
                                "short_name": "钱秀琴",
                                "level": "3",
                                "amount": "0.625",
                                "has_problem": "0",
                                "percent": "0.0625",
                                "pid": "ae318e35f6e54b6bfefccf1c913e2564",
                                "sh_type": "工商股东",
                                "children": [],
                                "eid": "",
                                "identifier": "12",
                                "name": "钱秀琴",
                                "type": "P"
                            },
                            {
                                "short_name": "田金鹭",
                                "level": "3",
                                "amount": "0.625",
                                "has_problem": "0",
                                "percent": "0.0625",
                                "pid": "1b37d55ec0623cda2b0f5bf173b060a7",
                                "sh_type": "工商股东",
                                "children": [],
                                "eid": "",
                                "identifier": "13",
                                "name": "田金鹭",
                                "type": "P"
                            }
                        ],
                        "eid": "bb713bd6-6999-4335-8b18-1e4e4cd92276",
                        "identifier": "8",
                        "name": "五鼎通达(上海)食品科技中心(有限合伙)",
                        "type": "E"
                    }
                ],
                "eid": "6b38d70c-e01d-4548-92e6-0c3df677ac80",
                "identifier": "3",
                "name": "北京食通达科技发展有限公司",
                "type": "E"
            }
        ],
        "c_count": 10,
        "c_trees": [
            {
                "short_name": "青岛曜石麻六记酒",
                "level": "1",
                "amount": "100.0",
                "has_problem": "0",
                "percent": "1.0",
                "sh_type": "工商股东",
                "children": [],
                "eid": "dbc5122c-d56f-4fab-aa05-e8dfa8eae46f",
                "identifier": "15",
                "name": "青岛曜石麻六记酒店管理有限公司",
                "type": "E"
            },
            {
                "short_name": "成都麻六记餐饮管",
                "level": "1",
                "amount": "100.0",
                "has_problem": "0",
                "percent": "1.0",
                "sh_type": "工商股东",
                "children": [],
                "eid": "fc60ec15-db71-4252-b31d-1e4d3354669d",
                "identifier": "16",
                "name": "成都麻六记餐饮管理有限公司",
                "type": "E"
            },
            {
                "short_name": "太原麻六记酒店管",
                "level": "1",
                "amount": "100.0",
                "has_problem": "0",
                "percent": "1.0",
                "sh_type": "工商股东",
                "children": [],
                "eid": "63d94664-fd80-40fd-93a0-e695631c9ebd",
                "identifier": "17",
                "name": "太原麻六记酒店管理有限公司",
                "type": "E"
            },
            {
                "short_name": "北京麻六记酒店管",
                "level": "1",
                "amount": "100.0",
                "has_problem": "0",
                "percent": "1.0",
                "sh_type": "工商股东",
                "children": [
                    {
                        "short_name": "北京亦庄麻六记酒",
                        "level": "2",
                        "amount": "100.0",
                        "has_problem": "0",
                        "percent": "1.0",
                        "sh_type": "工商股东",
                        "children": [],
                        "eid": "4e0b5c24-bba4-4bee-a056-53d83d6264a0",
                        "identifier": "19",
                        "name": "北京亦庄麻六记酒店管理有限公司",
                        "type": "E"
                    },
                    {
                        "short_name": "北京乐堤港麻六记",
                        "level": "2",
                        "amount": "100.0",
                        "has_problem": "0",
                        "percent": "1.0",
                        "sh_type": "工商股东",
                        "children": [],
                        "eid": "7f258c98-c68c-4916-9af5-26b74f3c1e92",
                        "identifier": "20",
                        "name": "北京乐堤港麻六记酒店管理有限公司",
                        "type": "E"
                    },
                    {
                        "short_name": "北京银泰麻六记酒",
                        "level": "2",
                        "amount": "55.0",
                        "has_problem": "0",
                        "percent": "0.55",
                        "sh_type": "工商股东",
                        "children": [],
                        "eid": "3fb166e6-b780-4963-b1a4-0ffe94185d6b",
                        "identifier": "21",
                        "name": "北京银泰麻六记酒店管理有限公司",
                        "type": "E"
                    },
                    {
                        "short_name": "北京西直门麻六记",
                        "level": "2",
                        "amount": "55.0",
                        "has_problem": "0",
                        "percent": "0.55",
                        "sh_type": "工商股东",
                        "children": [],
                        "eid": "106dafe9-9c1a-485e-8580-76d87c7a8750",
                        "identifier": "22",
                        "name": "北京西直门麻六记酒店管理有限公司",
                        "type": "E"
                    }
                ],
                "eid": "980ff820-9473-40ba-b105-998ceec35823",
                "identifier": "18",
                "name": "北京麻六记酒店管理有限公司",
                "type": "E"
            },
            {
                "short_name": "上海麻六记餐饮管",
                "level": "1",
                "amount": "100.0",
                "has_problem": "0",
                "percent": "1.0",
                "sh_type": "工商股东",
                "children": [
                    {
                        "short_name": "上海守味麻六记餐",
                        "level": "2",
                        "amount": "100.0",
                        "has_problem": "0",
                        "percent": "1.0",
                        "sh_type": "工商股东",
                        "children": [],
                        "eid": "0fd17482-1465-11ed-8e23-83cf331ae46e",
                        "identifier": "24",
                        "name": "上海守味麻六记餐饮管理有限公司",
                        "type": "E"
                    }
                ],
                "eid": "21dfeb49-c277-4931-9717-a945735ad14b",
                "identifier": "23",
                "name": "上海麻六记餐饮管理有限公司",
                "type": "E"
            }
        ],
        "eid": "f6114a80-0f5b-4059-83f1-65dd0bdc6397",
        "p_count": 11,
        "has_problem": "0",
        "identifier": "1",
        "name": "北京麻六记餐饮管理有限公司",
        "type": "E"
    },
    "sign": "86f52c7430fe4797a01909365f28bb7f",
    "status": "200",
    "message": "操作成功"
}

js文件

import d3 from './d3.min.js'
import EquityJson from '@/api/EquityJson.json'
import deepcopy from 'deepcopy'

// 是否全称
let shortNameShow = false
// 是否显示编辑按钮
let editShow = false

var zoom,treeG
/* 如果需要将页面的整体高度拉伸
 *(折线的高度拉伸,公司长方体的块也的Y距也需要调整及所有标签Y轴都需要调整,
 * 始终保证折线的端点在长方形的中心点)
 */

// json数据结构改变

var rootData = {
	downward: {
		"direction": "downward",
		"name": "origin",
		"children": deepcopy(EquityJson.data.c_trees)
	},
	upward: {
		"direction": "upward",
		"name": "origin",
		"children": deepcopy(EquityJson.data.p_trees)
	}
}
var rootName = EquityJson.data.name;

let width = 0
let height = 0
var _this = this;
var rootRectWidth = 0; //根节点rect的宽度
var forUpward = true

// 数据重置
function resizeData() {
	shortNameShow = false
	editShow = false
	// 重新获取json数据
	rootData = {
		downward: {
			"direction": "downward",
			"name": "origin",
			"children": deepcopy(EquityJson.data.c_trees)
		},
		upward: {
			"direction": "upward",
			"name": "origin",
			"children": deepcopy(EquityJson.data.p_trees)
		}
	}
 	rootName = EquityJson.data.name
}

export function drawing() {
	if (d3.select('#svg')) {
		d3.select('#svg').remove();
		resizeData()
	}
	width = document.getElementById('mountNode').scrollWidth
	height = document.getElementById('mountNode').scrollHeight
	var d3GenerationChart = new treeChart(d3);
	d3GenerationChart.drawChart();
}
// 简称 全称切换
export function simpleChange1(val) {
	shortNameShow = val
	if (shortNameShow) {
		d3.selectAll(".text-g").attr('visibility', 'hidden');
		d3.selectAll(".short-text-g").attr('visibility', 'visible');
	} else {
		d3.selectAll(".text-g").attr('visibility', 'visible');
		d3.selectAll(".short-text-g").attr('visibility', 'hidden');
	}
}
// 编辑
export function editChange1(val) {
	editShow = val
	if (editShow) {
		d3.selectAll(".edit").attr('visibility', 'visible');
	} else {
		d3.selectAll(".edit").attr('visibility', 'hidden');
	}
}

	// 缩放
export function zoomClick(type) {
	// var clicked = d3.event.target,
		var direction = 1,
		factor = 0.3,
		target_zoom = 1,
		center = [width / 2, height / 2],
		extent = zoom.scaleExtent(),
		translate = zoom.translate(),
		translate0 = [],
		l = [],
		view = {
			x: translate[0],
			y: translate[1],
			k: zoom.scale()
		};

	// d3.event.preventDefault();
	direction = type === 1 ? 1 : -1;

	target_zoom = Number(zoom.scale() + factor * direction).toFixed(1)

	if (target_zoom === extent[0] || target_zoom === extent[1]) {
		return false
	}
	if (target_zoom < extent[0]) {
		target_zoom = extent[0]
	}
	if (target_zoom > extent[1]) {
		target_zoom = extent[1]
	}
	translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
	view.k = target_zoom;
	l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];

	view.x += center[0] - l[0];
	view.y += center[1] - l[1];
	// var d3GenerationChart = new treeChart(d3);
	// d3GenerationChart.drawChart();
	interpolateZoom([view.x, view.y], view.k);
}

// 重置刷新
export function refresh() {
	drawing()
}
	// d3.select("#reset").on("click", function(d) {
	// 	interpolateZoom([0, 0], 1);
	// });

	function interpolateZoom(translate, scale) {
		return d3
			.transition()
			.duration(350)
			.tween("zoom", function() {
				var iTranslate = d3.interpolate(zoom.translate(), translate),
					iScale = d3.interpolate(zoom.scale(), scale);
				return function(t) {
					zoom.scale(iScale(t)).translate(iTranslate(t));
					redraw();
				};
			});
}
function redraw() {
	treeG.attr('transform', 'translate(' + zoom.translate() + ')' +
		' scale(' + zoom.scale() + ')');
}

var treeChart = function(d3Object) {
	this.d3 = d3Object;
	this.directions = ['upward', 'downward'];
};

treeChart.prototype.drawChart = function() {
	// First get tree data for both directions.
	this.treeData = {};
	var self = this;
	self.directions.forEach(function(direction) {
		self.treeData[direction] = rootData[direction];
	});
	rootRectWidth = rootName.length * 15;
	self.graphTree(self.getTreeConfig());
};
treeChart.prototype.getTreeConfig = function() {
	var treeConfig = {};
	treeConfig.chartWidth = width
	treeConfig.chartHeight = height
	treeConfig.centralHeight = treeConfig.chartHeight / 2;
	treeConfig.centralWidth = treeConfig.chartWidth / 2;
	treeConfig.linkLength = 160; //箭头长度(上下节点距离)
	treeConfig.duration = 500; //动画时间
	return treeConfig;
};
treeChart.prototype.graphTree = function(config) {
	var self = this;
	var d3 = this.d3;
	var linkLength = config.linkLength;
	var duration = config.duration;
	var hasChildNodeArr = [];
	var id = 0;
	//折线
	var diagonal = function(obj) {
		var s = obj.d.source;
		var t = obj.d.target;
		return ("M" + s.x + "," + (s.y) + "L" + s.x + "," + (s.y + (t.y - s.y) / 2) + "L" + t.x + "," + (s.y + (t.y - s.y) / 2) + "L" + t.x + "," + (t.y));
		// return ("M" + s.x + "," + (s.y + 20) + "L" + s.x + "," + (s.y + 20 + (t.y - s.y) / 2) + "L" + t.x + "," + (s.y + 20 + (t.y - s.y) / 2) + "L" + t.x + "," + (t.y + 20));
	};
	zoom = d3.behavior.zoom().scaleExtent([0.5, 2]).on('zoom', redraw);
	var svg = d3.select('#mountNode')
		.append('svg')
		.attr('id', 'svg')
		.attr('width', '100%')
		.attr('height', '100%')
		.attr('viewBox', '0 0 ' + config.chartWidth + ' ' + config.chartHeight)
		.attr('xmlns', 'http://www.w3.org/2000/svg')
		// .on('mousedown', disableRightClick)
		.call(zoom)
		.on('dblclick.zoom', null);
	treeG = svg.append('g')
		.attr('class', 'gbox')
		.attr('transform', 'translate(0,0)');


	for (var d in this.directions) {
		var direction = this.directions[d];
		var data = self.treeData[direction];
		data.x0 = config.centralWidth;
		data.y0 = config.centralHeight;
		data.children.forEach(collapse);
		update(data, data, treeG);
	}

	function update(source, originalData, g) {
		var direction = originalData["direction"];
		forUpward = direction == "upward";
		var node_class = direction + "Node";
		var link_class = direction + "Link";
		var downwardSign = forUpward ? -1 : 1;
		var isExpand = false;
		var nodeSpace = 200;
		var tree = d3.layout.tree().sort(sortByDate).nodeSize([nodeSpace, 0]);
		var nodes = tree.nodes(originalData);
		var links = tree.links(nodes);
		var offsetX = -config.centralWidth;
		nodes.forEach(function(d) {
			d.y = downwardSign * (d.depth * linkLength) + config.centralHeight;
			d.x = d.x - offsetX;
			if (d.name == "origin") {
				d.x = config.centralWidth;
				d.y += downwardSign * 0; // 上下两树图根节点之间的距离
			}
		});

		// Update the node.
		var node = g.selectAll('g.' + node_class)
			.data(nodes, function(d) {
				return d.id || (d.id = ++id);
			})
		var nodeEnter = node.enter().append('g')
			.attr('class', node_class)
			.attr('transform', function(d) {
				return 'translate(' + source.x0 + ',' + source.y0 + ')';
			})
			.style('cursor', function(d) {
				return d.name == "origin" ?
					"" :
					d.children || d._children ?
					"pointer" :
					"";
			})
			.on('click', d => {
				if (d.direction == 'upward' || d.direction == 'downward') {
					// _this.$router.push({
					//   path: '/search/detail',
					//   name: 'search-detail',
					//   query: {
					//     companyId: d.direction == 'upward' ? d.beijingCrid : d.pbeijingCrid
					//     }
					//   })
				}
			})
			.on("mouseenter", nodeHover)
			.on("mouseleave", nodeOut);

		const rectG = nodeEnter.append("g")
		// 公司或股东长方形样式
		const rect = rectG.append("svg:rect")
			.attr("x", function(d) {
				return d.name == 'origin' ? -(rootRectWidth / 2) : -90;
			})
			.attr("y", function(d) {
				return d.name == "origin" ? -20 : forUpward ? -31 : -40;
			})
			.attr("width", function(d) {
				return d.name == 'origin' ? rootRectWidth : 180;
			})
			.attr("height", function(d) {
				return d.name == 'origin' ? 40 : 70;
			})
			.attr("rx", 0)
			.style('cursor', "pointer")
			// 边框线
			.style("stroke", "#128bed")
			// 字体填充颜色
			.style("fill", function(d) {
				if (d.name == 'origin') {
					return "#128bed"
				} else {
					return "#fff"
				}
			})
			.on("mouseenter", nodeHover);

		// 公司名称文本第一行  全称
		const textG = rectG.append("g")
			.attr('class', 'text-g')
			.attr('visibility', 'visible')
		textG.append("text")
			.attr("x", 0)
			.attr('dy', function(d) {
				if (d.name == 'origin') {
					return '.35em'
				} else if (forUpward) {
					return d.name.length > 10 ? '-1' : '10';
				} else {
					return d.name.length > 10 ? '-10' : '0';
				}
				// 后续方框底部有融资轮次,样式需要用这个
				//  else if (d.financingRound) {
				//   if (forUpward) {
				//     return d.name.length > 10 ? '0' : '0';
				//   } else {
				//     return d.name.length > 10 ? '-20' : '-10';
				//   }
				// } else {
				//   if (forUpward) {
				//     return d.name.length > 10 ? '-1' : '10';
				//   } else {
				//     return d.name.length > 10 ? '-10' : '0';
				//   }
				// }
			})
			.attr("text-anchor", "middle")
			.attr("fill", "#333")
			.text(function(d) {
				if (d.name == "origin") {
					return rootName;
				}
				if (d.repeated) {
					return "[Recurring] " + d.name;
				}
				return d.name.length > 10 ? d.name.substr(0, 10) : d.name;
			})
			.style({
				'fill': function(d) {
					if (d.name == 'origin') {
						return '#fff';
					}
				},
				'font-size': 14,
				'cursor': "pointer"
			})
			.on('click', Change_modal)
			.append('svg:title').text(d => d.name)

		// 公司名称文本第二行  全称
		textG.append("text")
			.attr("x", "0")
			.attr("dy", function(d) {
				if (d.name == 'origin') {
					return '.35em'
				} else if (forUpward) {
					return d.name.length > 10 ? '20' : '40';
				} else {
					return d.name.length > 10 ? '10' : '40';
				}
				// 后续方框底部有融资轮次,样式需要用这个
				//  else if (d.financingRound) {
				//   if (forUpward) {
				//     return d.name.length > 10 ? '-40' : '40';
				//   } else {
				//     return d.name.length > 10 ? '0' : '40';
				//   }
				// } else {
				//   if (forUpward) {
				//     return d.name.length > 10 ? '20' : '40';
				//   } else {
				//     return d.name.length > 10 ? '10' : '40';
				//   }
				// }
			})
			.attr("text-anchor", "middle")
			.text(function(d) {
				return d.name.length > 20 ? d.name.substr(10, 10) + '...' : d.name.substr(10, d.name.length);
			})
			.style({
				'fill': "#333", // 第二行字体颜色
				"font-size": 14,
				'cursor': "pointer"
			})
			.append('svg:title').text(d => d.name)

		// 公司名称文本第一行  简称
		const shortTextG = rectG.append("g")
			.attr('class', 'short-text-g')
			.attr('visibility', 'hidden')

		shortTextG.append("text")
			.attr("x", "0")
			.attr("dy", function(d) {
				if (d.name == 'origin') {
					return '.35em'
				} else {
					return forUpward ? 10 : 0
				}
				// 后续方框底部有融资轮次,样式需要用这个
				//  else if (d.financingRound) {
				//   if (forUpward) {
				//     return d.name.length > 10 ? '-40' : '40';
				//   } else {
				//     return d.name.length > 10 ? '0' : '40';
				//   }
				// } else {
				//   if (forUpward) {
				//     return d.name.length > 10 ? '20' : '40';
				//   } else {
				//     return d.name.length > 10 ? '10' : '40';
				//   }
				// }
			})
			.attr("text-anchor", "middle")
			.text(function (d) {
				return d.name == 'origin' ? rootName : d.short_name.length > 10 ? d.short_name.substr(0, 10) + '...' : d.short_name.substr(0, d.short_name.length);
			})
			.style({
				'fill': function (d) {
					return d.name == 'origin' ? '#fff' : "#333"
				},
				"font-size": 14,
				'cursor': "pointer",
			})
			.append('svg:title').text(d => d.name)
		
		// 最终受益人红色背景框 isKey
		const personTopRect = rectG.append("svg:rect")
			.attr("x", -40)
			.attr("y", -56)
			.attr("width", 80)
			.attr("height", function(d) {
				return d.isKey ? 18 : 0;
			})
			.attr("rx", 2)
			.style("stroke", "#FA6B64")
			.style("fill", "#FA6B64")
		// 最终受益人红色小三角形 isKey
		rectG
			.append("svg:path")
			.attr("fill", d => {
				return d.isKey ? "#FA6B64" : ""
			})
			.attr("d", function(d) {
				return d.isKey ? "M0 -33 L-5 -38 L5 -38 Z" : ""
			})
		// 最终受益人 文本 isKey
		rectG.append("text")
			.attr("x", 0)
			.attr("dy", -43)
			.attr("text-anchor", "middle")
			.style("fill", "#fff")
			.style('font-size', 10)
			.text(function(d) {
				return d.isKey ? "最终受益人" : '';
			});
		// 融资轮次背景框 financingRound
		const financingRoundRect = rectG.append('svg:rect')
			.attr("x", "-89")
			.attr("y", function(d) {
				return forUpward ? 18 : 9;
			})
			.attr("rx", 2)
			.attr("width", function(d) {
				return d.financingRound ? 178 : 0;
			})
			.attr("height", function(d) {
				return d.financingRound ? 20 : 0;
			})
			.style("fill", "#e9f3ff")

		// 融资轮次文字  financingRound
		rectG.append("text")
			.attr("x", "0")
			.attr("dy", () => {
				return forUpward ? 29 : 20
			})
			.attr("text-anchor", "middle")
			.attr("dominant-baseline", "middle")
			.style("fill", "#128bed")
			.style('font-size', 12)
			.text(function(d) {
				return d.financingRound ? "融资轮次:天使轮" : "";
			});
		// 注销红色标签背景 state
		const holdingCompanyRect = rectG.append('svg:rect')
			.attr("x", "55")
			.attr("y", function(d) {
				return d.name == 'origin' ? '.35em' : forUpward ? '-40' : '-50';
			})
			.attr("rx", 2)
			.attr("width", function(d) {
				return d.state ? 30 : 0;
			})
			.attr("height", function(d) {
				return d.state ? 18 : 0;
			})
			.style("fill", "#FFEEE5")

		// 注销红色标签文字
		rectG.append("text")
			.attr("x", "70")
			.attr("dy", function(d) {
				return (d.name == 'origin') ? '.35em' : forUpward ? '-27' : '-37';
			})
			.attr("text-anchor", "middle")
			.style("fill", "#FF722D")
			.style('font-size', 11)
			.text(function(d) {
				return d.state ? "注销" : "";
			});

		// 持资占比文字
		rectG
			.append("text")
			.attr("x", "18")
			.attr("dy", function(d) {
				return d.name == "origin" ? ".35em" : forUpward ? "50" : "-48";
			})
			.attr("text-anchor", "start")
			.attr("class", "linkname")
			.style("fill", "#128bed") //比例颜色(55%)
			.style("font-size", 10)
			.text(function(d) {
				if (d.percent != "0" && d.percent != "") {
					return d.name == "origin" ? "" : parseInt(parseFloat(d.percent) * 100) + '%';
				}
			})
			.on("mouseenter", nodeOut)
			.on("mouseleave", nodeOut);

		// 加减号圈
		rectG
			.append("circle")
			.attr('r', function(d) {
				return d.name == 'origin' ? 0 : (d.children && d.children.length || d._children && d._children
					.length) ? 10 : 0
			})
			.attr('cy', function(d) {
				return (d.name == 'origin') ? -20 : (forUpward) ? -42 : 41;
			})
			.style('fill', function(d) {
				return (d.children && d.children.length || d._children && d._children.length) ? "#fff" :
					"" //展开按钮背景颜色
			})
			.style("stroke", function(d) {
				// +号的外圈颜色 展开按钮背景颜色
				return hasChildNodeArr.indexOf(d) != -1 ?
					"#128bed" :
					""
			})
			.style('stroke-width', function(d) {
				if (d.repeated) {
					return 5;
				}
			})
			.on("mouseenter", nodeOut)
			.on("mouseleave", nodeOut);
		// + - 号样式及绑定点击事件
		rectG
			.append("svg:text")
			.attr("class", "isExpand")
			.attr("x", "0")
			.attr("dy", function(d) {
				return forUpward ? -40 : 42.5;
			})
			.attr("text-anchor", "middle")
			.style("fill", function(d) {
				return d.type != "P" ? "#128bed" : "#FF4D4D";
			})
			.style('cursor', 'pointer')
			.style('font-size', 20)
			//+、-字体颜色
			.text(function(d) {
				if (d.name == "origin") {
					return "";
				}
				return d._children ? "+" : ''
			})
			.on("click", click)
			.on("mouseenter", nodeOut)
			.on("mouseleave", nodeOut);
		
		// 编辑按钮圈
		const treeC = rectG.append("g")
			.attr('class', 'edit')
			.attr('visibility', 'hidden')
		
		treeC.append("circle")
			.attr('r', function(d) {
				return d.name == 'origin' ? 0 : 10
			})
			.attr('cx', function (d) {
				return (d.name == 'origin') ? 0 : 90;
			})
			.attr('cy', function(d) {
				return (d.name == 'origin') ? -20 : (forUpward) ? -35 : -40;
			})
			.style('fill', "#FF6060")
			.on("mouseenter", nodeOut)
			.on("mouseleave", nodeOut);
		// 编辑按钮×号样式及绑定点击事件
		treeC
			.append("svg:text")
			.attr("class", "isExpand")
			.attr("x", "0")
			.attr("dx", 90)
			.attr("dy", function(d) {
				return forUpward ? -33.5 : -38;
			})
			.attr("text-anchor", "middle")
			.style("fill", '#fff')
			.style('cursor', 'pointer')
			.style('font-size', 20)
			.text(function(d) {
				return d.name == "origin"? '':'×'
			})
			.on("click", editBtnClick)
			.on("mouseenter", nodeOut)
			.on("mouseleave", nodeOut);

		// Transition nodes to their new position.原有节点更新到新位置
		var nodeUpdate = node.transition()
			.duration(duration)
			.attr('transform', function(d) {
				return 'translate(' + d.x + ',' + d.y + ')';
			});

		nodeUpdate.select('text').style('fill-opacity', 1)

		nodeUpdate.select('text').style('fill-opacity', 1)

		var nodeExit = node.exit().transition()
			.duration(duration)
			.attr('transform', function(d) {
				return 'translate(' + source.x + ',' + source.y + ')';
			})
			.remove();
		nodeExit.select('circle')
			.attr('r', 1e-6)
		nodeExit.select('text')
			.style('fill-opacity', 1e-6);

		var link = g.selectAll('path.' + link_class)
			.data(links, function(d) {
				return d.target.id;
			});

		//箭头(下半部分)
		var markerDown = svg
			.append("marker")
			.attr("id", "resolvedDown")
			.attr("markerUnits", "strokeWidth") //设置为strokeWidth箭头会随着线的粗细发生变化
			.attr("markerUnits", "userSpaceOnUse")
			.attr("viewBox", "0 -5 10 10") //坐标系的区域
			.attr("refX", 51) //箭头坐标
			.attr("refY", 0)
			.attr("markerWidth", 10) //标识的大小
			.attr("markerHeight", 10)
			.attr("orient", "90") //绘制方向,可设定为:auto(自动确认方向)和 角度值
			.attr("stroke-width", 2) //箭头宽度
			.append("path")
			.attr("d", "M0,-5L10,0L0,5") //箭头的路径
			.attr("fill", "#128bed")
			.attr("fill-opacity", 1); //箭头颜色
		//箭头(上半部分)
		var markerUp = svg
			.append("marker")
			.attr("id", "resolvedUp")
			.attr("markerUnits", "strokeWidth") //设置为strokeWidth箭头会随着线的粗细发生变化
			.attr("markerUnits", "userSpaceOnUse")
			.attr("viewBox", "0 -5 10 10") //坐标系的区域
			.attr("refX", -50) //箭头坐标
			.attr("refY", 0)
			.attr("markerWidth", 10) //标识的大小
			.attr("markerHeight", 10)
			.attr("orient", "90") //绘制方向,可设定为:auto(自动确认方向)和 角度值
			.attr("stroke-width", 2) //箭头宽度
			.append("path")
			.attr("d", "M0,-5L10,0L0,5") //箭头的路径
			.attr("fill", "#128bed")
			.attr("fill-opacity", 1); //箭头颜色

		// 折线及三角形样式
		link.enter()
			.insert("path", "g")
			.attr("class", link_class)
			.attr("stroke", function(d) {
				return "#bbb";
			})
			.attr("fill", "none")
			.attr("stroke-width", "1px")
			.attr("opacity", 0.5)
			.attr("d", function(d) {
				var o = {
					x: source.x0,
					y: source.y0,
				};
				return diagonal({
					source: o,
					target: o,
					d,
				});
			})
			.attr("marker-end", function(d) {
				return forUpward ? "url(#resolvedUp)" : "url(#resolvedDown)";
			}) //根据箭头标记的id号标记箭头;
			.attr("id", function(d, i) {
				return "mypath" + i;
			});
		link.transition()
			.duration(duration)
			.attr('d', function(d) {
				return diagonal({
					d
				});
			});
		link.exit().transition()
			.duration(duration)
			.attr('d', function(d) {
				var o = {
					x: source.x,
					y: source.y
				};
				return diagonal({
					source: o,
					target: o,
					d
				});
			})
			.remove();
		nodes.forEach(function(d) {
			d.x0 = d.x;
			d.y0 = d.y;
		});

		//添加动态关系线
		function nodeHover(d, i) {
			if (d.name != "origin") {
				if (d.parent.direction == "downward") {
					var links = d3.selectAll(".downwardLink")[0];
					//当前节点的子级节点
					links.map((item, index) => {
						if (item.__data__.source.id == d.id) {
							if (d.children) {
								d.children.forEach((citem) => {
									if (item.__data__.target.id == citem.id) {
										d3.select(item).attr(
											"class",
											"downwardLink downLine"
										);
									}
								});
							}
						} else if (
							item.__data__.source.id == d.parent.id &&
							item.__data__.target.id == d.id
						) {
							d3.select(item).attr("class", "downwardLink downLine");
						}
					});
					//递归处理当前节点的祖先节点
					function changeUpLink(d) {
						links.map((item, index) => {
							if (d.name != "origin") {
								if (
									item.__data__.source.id == d.parent.id &&
									item.__data__.target.id == d.id
								) {
									d3.select(item).attr("class", "downwardLink downLine");
								}
							}
						});
						if (d.depth > 1) {
							if (!d.parent) {
								return;
							} else {
								changeUpLink(d.parent);
							}
						}
					}
					// changeUpLink(d, true);
				} else {
					var links = d3.selectAll(".upwardLink")[0];
					for (let i = 0; i < links.length; i++) {
						let item = links[i];
						if (item.__data__.source.id == d.id) {
							if (d.children) {
								d.children.forEach((citem) => {
									if (item.__data__.target.id == citem.id) {
										// console.log(item)
										d3.select(item).attr("class", "upwardLink upLine");
									}
								});
							}
						} else if (
							item.__data__.source.id == d.parent.id &&
							item.__data__.target.id == d.id
						) {
							d3.select(item).attr("class", "upwardLink upLine");
						}
					}

					function cancelUpLink(d) {
						for (let i = 0; i < links.length; i++) {
							let item = links[i];
							if (d.name != "origin") {
								if (
									item.__data__.source.id == d.parent.id &&
									item.__data__.target.id == d.id
								) {
									d3.select(item).attr("class", "upwardLink upLine");
								}
							}
						}
						if (d.parent) {
							cancelUpLink(d.parent);
						}
					}
					// cancelUpLink(d);
				}
			}
		}

		function nodeOut(d, i) {
			if (d.name != "origin") {
				if (d.parent.direction == "downward") {
					var links = d3.selectAll(".downwardLink")[0];
					for (let i = 0; i < links.length; i++) {
						let item = links[i];
						if (
							d3.select(item).attr("class").indexOf("downLine") != "-1"
						) {
							d3.select(item).attr("class", "downwardLink");
						}
					}
				} else {
					var links = d3.selectAll(".upwardLink")[0];
					for (let i = 0; i < links.length; i++) {
						let item = links[i];
						if (d3.select(item).attr("class").indexOf("upLine") != "-1") {
							d3.select(item).attr("class", "upwardLink");
						}
					}
				}
			}
		}

		function Change_modal() {
			_this.Modal = true
		}

		function click(d) {
			event.stopPropagation()
			if (forUpward) {} else {
				if (d._children) {
					console.log('对外投资--ok')
				} else {
					console.log('对外投资--no')
				}
			}
			isExpand = !isExpand;
			if (d.name == 'origin') {
				return;
			}
			if (d.children) {
				d._children = d.children;
				d.children = null;
				d3.select(this).text('+')
				update(d, originalData, g);
			} else {
				if (d._children && d._children.length > 0) {
					d.children = d._children;
					d._children = null;
					if (d.name == 'origin') {
						d.children.forEach(expand);
					}
					d3.select(this).text('-')
					update(d, originalData, g);
					simpleChange1(shortNameShow)
					editChange1(editShow)
				} else {
					// gqctt({
					// 	beijingCrid: d.direction == 'upward' ? d.beijingCrid : d.pbeijingCrid,
					// 	direction: d.direction
					// }).then(res => {
					// 	if (res.code === 0) {
					// 		if (d.direction == 'upward') {
					// 			d.children = res.result.investorList
					// 			d.children.map(item => {
					//   		item.amount = Number(item.subConAm).toFixed(2)
					// 				item.isKey = true
					// 				item.percent = item.subComBl.length > 6 ?
					// 					calculation.accMul(item.subComBl, 100).toFixed(2) + '%' :
					// 					calculation.accMul(item.subComBl, 100) + '%'
					//     item.name = item.entName
					// 				item.type = item.bz === '企业' ? 2 : 1
					// 				item.isKey = item.subComBl >= 0.25 && item.type == 1
					// 				item.direction = direction
					// 				item.holderPercent = item.percent
					// 			})
					// 		} else {
					// 			d.children = res.result.holderList
					// 			d.children.map(item => {
					// 				item.amount = Number(item.subConAm).toFixed(2)
					// 				item.isKey = true
					// 				item.percent = item.subComBl.length > 6 ?
					// 					calculation.accMul(item.subComBl, 100).toFixed(2) + '%' :
					// 					calculation.accMul(item.subComBl, 100) + '%'
					// 				item.name = item.pentName
					// 				item.type = item.bz === '企业' ? 2 : 1
					// 				item.isKey = item.subComBl >= 0.25 && item.type == 1
					// 				item.direction = direction
					// 				item.holderPercent = item.percent
					// 			})
					// 		}
					// 		d._children = null;
					// 		if (d.name == 'origin') {
					// 			d.children.forEach(expand);
					// 		}
					// 		d3.select(this).text('-')
					// 		update(d, originalData, g)
					// 	}
					// })
				}
			}
		}
		function editBtnClick(d) {
			if (d.name == 'origin') {
				return;
			} else {
				const filterId = (data, id) => {
					if (!Array.isArray(data)) {
						return data
					}
					return data.filter(item => {
						if ('children' in item) {
							item.children = filterId(item.children, id)
						} else if ('_children' in item) {
							item._children = filterId(item._children, id)
						}
						return (item.id === undefined || item.id !== id)
					})
				}
				const filtredData = filterId([originalData], d.id)
				update(d, filtredData[0], g);
			}
		}
	}

	function expand(d) {
		if (d._children) {
			d.children = d._children;
			d.children.forEach(expand);
			d._children = null;
		}
	}

	function collapse(d) {
		if (d.children && d.children.length != 0) {
			d._children = d.children;
			d._children.forEach(collapse);
			d.children = null;
			hasChildNodeArr.push(d);
		}
	}

	

	function disableRightClick() {
		// stop zoom
		if (d3.event.button == 2) {
			console.log('No right click allowed');
			d3.event.stopImmediatePropagation();
		}
	}

	function sortByDate(a, b) {
		var aNum = a.name.substr(a.name.lastIndexOf('(') + 1, 4);
		var bNum = b.name.substr(b.name.lastIndexOf('(') + 1, 4);
		return d3.ascending(aNum, bNum) ||
			d3.ascending(a.name, b.name) ||
			d3.ascending(a.id, b.id);
	}
};

混入的jsD3Mixin.js

/**
 * 全屏 toggleFullScreen
 * 保存 downloadImpByChart
 */
export const D3Mixin = {
  data() {
    return {
      isFullscreen: true,
    }
  },
  methods: {
    downloadSvgFn(svg, width, height, chartName, rootName) {
      var serializer = new XMLSerializer()
      var source = '<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(svg)
      var image = new Image()
      image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source)
      image.onload = function () {
        var canvas = document.createElement('canvas')
        canvas.width = width + 40;
        canvas.height = height + 40;
        var context = canvas.getContext('2d');
        context.rect(0, 0, width + 40, height + 40);
        context.fillStyle = '#fff';
        context.fill();
        context.drawImage(image, 20, 20);
        var url = canvas.toDataURL("image/png");
        var a = document.createElement("a");
        a.download = chartName + '-' + rootName + ".png";
        a.href = url;
        a.click();
        return
      }
    },
    downloadImpByChart(chartName, rootName, zoomClassName = '') {
      //得到svg的真实大小    
      let box = document.querySelector('svg').getBBox(),
        x = box.x,
        y = box.y,
        width = box.width,
        height = box.height;
      if (zoomClassName) {
        //查找zoomObj
        var zoomObj = svg.getElementsByClassName(zoomClassName.replace(/\./g, ''))[0];
        if (!zoomObj) {
          return false;
        }
        /*------这里是处理svg缩放的--------*/
        var transformMath = zoomObj.getAttribute('transform'),
          scaleMath = zoomObj.getAttribute('transform');
        if (transformMath || scaleMath) {
          var transformObj = transformMath.match(/translate\(([^,]*),([^,)]*)\)/),
            scaleObj = scaleMath.match(/scale\((.*)\)/);
          if (transformObj || scaleObj) { //匹配到缩放
            var translateX = transformObj[1],
              translateY = transformObj[2],
              scale = scaleObj[1];
            x = (x - translateX) / scale;
            y = (y - translateY) / scale;
            width = width / scale;
            height = height / scale;
          }
        }
      }
      //克隆svg
      var node = svg.cloneNode(true);
      //重新设置svg的width,height,viewbox
      node.setAttribute('width', width);
      node.setAttribute('height', height);
      node.setAttribute('viewBox', [x, y, width, height]);
      if (zoomClassName) {
        var zoomObj = node.getElementsByClassName(zoomClassName.replace(/\./g, ''))[0];
        /*-------------清楚缩放元素的缩放-------------*/
        zoomObj.setAttribute('transform', 'translate(0,0) scale(1)');
      }
      this.downloadSvgFn(node, width, height, chartName, rootName);
    },
    checkFull() {
      var isFull =
        document.fullscreenEnabled || window.fullScreen || document.webkitIsFullScreen || document.msFullscreenEnabled
      if (isFull === undefined) {
        isFull = false
      }
      return isFull
    },
    FullScreen(el) {
      if (this.isFullscreen) {
        //退出全屏
        if (document.exitFullscreen) {
          document.exitFullscreen()
        } else if (document.mozCancelFullScreen) {
          document.mozCancelFullScreen()
        } else if (document.webkitExitFullscreen) {
          document.webkitExitFullscreen()
        } else if (!document.msRequestFullscreen) {
          document.msExitFullscreen()
        }
      } else {
        //进入全屏
        if (el.requestFullscreen) {
          el.requestFullscreen()
        } else if (el.mozRequestFullScreen) {
          el.mozRequestFullScreen()
        } else if (el.webkitRequestFullscreen) {
          //改变平面图在google浏览器上面的样式问题
          el.webkitRequestFullscreen()
        } else if (el.msRequestFullscreen) {
          this.isFullscreen = true
          el.msRequestFullscreen()
        }
      }
    },
    toggleFullScreen(e) {
      this.isFullscreen = !this.isFullscreen
      this.FullScreen(document.getElementById('borrow'))
    }
  },
  mounted() {
    window.addEventListener('resize', () => {
      let that = this
      if (!that.checkFull()) {
        //要执行的动作
        that.isFullscreen = true
      }
    })
  }
}
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值