import * as d3 from 'd3';
class LegalPerson extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {
id: "abc1005",
name: "山东吠舍科技有限责任公司",
children: [
{
id: "abc1006",
name: "山东第一首陀罗科技服务有限公司",
percent: "100%",
},
{
id: "abc1007",
name: "山东第二首陀罗程技术有限公司",
percent: "100%",
},
{
id: "abc1008",
name: "山东第三首陀罗光伏材料有限公司",
percent: "100%",
},
{
id: "abc1009",
name: "山东第四首陀罗科技发展有限公司",
percent: "100%",
children: [
{
id: "abc1010",
name: "山东第一达利特瑞利分析仪器有限公司",
percent: "100%",
children: [
{
id: "abc1011",
name: "山东瑞利的子公司一",
percent: "80%",
},
{
id: "abc1012",
name: "山东瑞利的子公司二",
percent: "90%",
},
{
id: "abc1013",
name: "山东瑞利的子公司三",
percent: "100%",
},
],
},
],
},
{
id: "abc1014",
name: "山东第五首陀罗电工科技有限公司",
percent: "100%",
children: [
{
id: "abc1015",
name: "山东第二达利特低自动化设备有限公司",
percent: "100%",
children: [
{
id: "abc1016",
name: "山东敬业的子公司一",
percent: "100%",
},
{
id: "abc1017",
name: "山东敬业的子公司二",
percent: "90%",
},
],
},
],
},
{
id: "abc1020",
name: "山东第六首陀罗分析仪器(集团)有限责任公司",
percent: "100%",
children: [
{
id: "abc1021",
name: "山东第三达利特分气体工业有限公司",
},
],
},
],
parents: [
{
id: "abc2001",
name: "山东刹帝利集团有限责任公司",
percent: "60%",
parents: [
{
id: "abc2000",
name: "山东婆罗门集团有限公司",
percent: "100%",
},
],
},
{
id: "abc2002",
name: "吴小远",
percent: "40%",
parents: [
{
id: "abc1010",
name: "山东第一达利特瑞利分析仪器有限公司",
percent: "100%",
parents: [
{
id: "abc1011",
name: "山东瑞利的子公司一",
percent: "80%",
},
{
id: "abc1012",
name: "山东瑞利的子公司二",
percent: "90%",
},
{
id: "abc1013",
name: "山东瑞利的子公司三",
percent: "100%",
},
],
},
],
},
{
id: "abc2003",
name: "测试数据",
percent: "40%",
},
],
},
};
this.config = {
dx: 200,
dy: 170,
width: 0,
height: 500,
rectWidth: 170,
rectHeight: 70,
};
this.svg = null;
this.gAll = null;
this.gLinks = null;
this.gNodes = null;
this.tree = null;
this.rootOfDown = null;
this.rootOfUp = null;
}
componentDidMount() {
this.initializeChart();
}
initializeChart = () => {
this.drawChart({ type: "fold" });
};
drawChart = (options) => {
const host = d3.select(this.container);
const dom = this.container;
const domRect = dom.getBoundingClientRect();
this.config.width = domRect.width;
this.config.height = domRect.height;
d3.select(this.container).select("svg").remove();
const svg = d3.create("svg")
.attr("viewBox", () => {
let parentsLength = this.state.data.parents
? this.state.data.parents.length
: 0;
return [
-this.config.width / 2,
parentsLength > 0
? -this.config.height / 2
: -this.config.height / 3,
this.config.width,
this.config.height,
];
})
.style("user-select", "none")
.style("cursor", "move");
const gAll = svg.append("g").attr("id", "all");
svg
.call(
d3.zoom()
.scaleExtent([0.2, 5])
.on("zoom", (e) => {
gAll.attr("transform", () => {
return `translate(${e.transform.x},${e.transform.y}) scale(${e.transform.k})`;
});
})
)
.on("dblclick.zoom", null);
this.gAll = gAll;
this.gLinks = gAll.append("g").attr("id", "linkGroup");
this.gNodes = gAll.append("g").attr("id", "nodeGroup");
this.tree = d3.tree().nodeSize([this.config.dx, this.config.dy]);
this.rootOfDown = d3.hierarchy(
this.state.data,
(d) => d.children
);
this.rootOfUp = d3.hierarchy(this.state.data, (d) => d.parents);
this.tree(this.rootOfDown);
[this.rootOfDown.descendants(), this.rootOfUp.descendants()].forEach(
(nodes) => {
nodes.forEach((node) => {
node._children = node.children || null;
if (options.type === "all") {
node.children = node._children;
} else if (options.type === "fold") {
if (node.depth) {
node.children = null;
}
}
});
}
);
svg
.append("marker")
.attr("id", "markerOfDown")
.attr("markerUnits", "userSpaceOnUse")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 55)
.attr("refY", 0)
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("orient", "90")
.attr("stroke-width", 2)
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#215af3");
svg
.append("marker")
.attr("id", "markerOfUp")
.attr("markerUnits", "userSpaceOnUse")
.attr("viewBox", "0 -5 10 10")
.attr("refX", -50)
.attr("refY", 0)
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("orient", "90")
.attr("stroke-width", 2)
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#215af3");
this.svg = svg;
this.update();
host.append(() => svg.node());
};
update = (source) => {
if (!source) {
source = {
x0: 0,
y0: 0,
};
this.rootOfDown.x0 = 0;
this.rootOfDown.y0 = 0;
this.rootOfUp.x0 = 0;
this.rootOfUp.y0 = 0;
}
let nodesOfDown = this.rootOfDown.descendants().reverse();
let linksOfDown = this.rootOfDown.links();
let nodesOfUp = this.rootOfUp.descendants().reverse();
let linksOfUp = this.rootOfUp.links();
this.tree(this.rootOfDown);
this.tree(this.rootOfUp);
const myTransition = this.svg.transition().duration(500);
const node1 = this.gNodes
.selectAll("g.nodeOfDownItemGroup")
.data(nodesOfDown, (d) => d.data.id);
const node1Enter = node1
.enter()
.append("g")
.attr("class", "nodeOfDownItemGroup")
.attr("transform", (d) => `translate(${source.x0},${source.y0})`)
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0)
.style("cursor", "pointer");
node1Enter
.append("rect")
.attr("width", (d) => {
if (d.depth === 0) {
return (d.data.name.length + 2) * 16;
}
return this.config.rectWidth;
})
.attr("height", (d) => {
if (d.depth === 0) {
return 30;
}
return this.config.rectHeight;
})
.attr("x", (d) => {
if (d.depth === 0) {
return (-(d.data.name.length + 2) * 16) / 2;
}
return -this.config.rectWidth / 2;
})
.attr("y", (d) => {
if (d.depth === 0) {
return -15;
}
return -this.config.rectHeight / 2;
})
.attr("rx", 5)
.attr("stroke-width", 1)
.attr("stroke", (d) => {
if (d.depth === 0) {
return "#5682ec";
}
return "#7A9EFF";
})
.attr("fill", (d) => {
if (d.depth === 0) {
return "#7A9EFF";
}
return "#FFFFFF";
})
.on("click", (e, d) => this.nodeClickEvent(e, d));
node1Enter
.append("text")
.attr("class", "main-title")
.attr("x", (d) => 0)
.attr("y", (d) => {
if (d.depth === 0) {
return 5;
}
return -14;
})
.attr("text-anchor", (d) => "middle")
.text((d) => {
if (d.depth === 0) {
return d.data.name;
} else {
return d.data.name.length > 11
? d.data.name.substring(0, 11)
: d.data.name;
}
})
.attr("fill", (d) => {
if (d.depth === 0) {
return "#FFFFFF";
}
return "#000000";
})
.style("font-size", (d) => (d.depth === 0? 16 : 14))
.style("font-family", "黑体")
.style("font-weight", "bold");
node1Enter
.append("text")
.attr("class", "sub-title")
.attr("x", (d) => 0)
.attr("y", (d) => 5)
.attr("text-anchor", (d) => "middle")
.text((d) => {
if (d.depth!== 0) {
let subTitle = d.data.name.substring(11);
if (subTitle.length > 10) {
return subTitle.substring(0, 10) + "...";
}
return subTitle;
}
})
.style("font-size", (d) => 14)
.style("font-family", "黑体")
.style("font-weight", "bold");
node1Enter
.append("text")
.attr("class", "percent")
.attr("x", (d) => 12)
.attr("y", (d) => -45)
.text((d) => {
if (d.depth!== 0) {
return d.data.percent;
}
})
.attr("fill", "#000000")
.style("font-family", "黑体")
.style("font-size", (d) => 14);
const expandBtnG = node1Enter
.append("g")
.attr("class", "expandBtn")
.attr("transform", (d) => `translate(${0},${this.config.rectHeight / 2})`)
.style("display", (d) => {
if (d.depth === 0) {
return "none";
}
if (!d._children) {
return "none";
}
})
.on("click", (e, d) => {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
}
this.update(d);
});
expandBtnG
.append("circle")
.attr("r", 8)
.attr("fill", "#7A9EFF")
.attr("cy", 8);
expandBtnG
.append("text")
.attr("text-anchor", "middle")
.attr("fill", "#ffffff")
.attr("y", 13)
.style("font-size", 16)
.style("font-family", "微软雅黑")
.text((d) => d.children? "-" : "+");
const link1 = this.gLinks
.selectAll("path.linkOfDownItem")
.data(linksOfDown, (d) => d.target.data.id);
const link1Enter = link1
.enter()
.append("path")
.attr("class", "linkOfDownItem")
.attr("d", (d) => {
let o = {
source: {
x: source.x0,
y: source.y0,
},
target: {
x: source.x0,
y: source.y0,
},
};
return this.drawLink(o);
})
.attr("fill", "none")
.attr("stroke", "#7A9EFF")
.attr("stroke-width", 1)
.attr("marker-end", "url(#markerOfDown)");
node1
.merge(node1Enter)
.transition(myTransition)
.attr("transform", (d) => `translate(${d.x},${d.y})`)
.attr("fill-opacity", 1)
.attr("stroke-opacity", 1);
node1
.exit()
.transition(myTransition)
.remove()
.attr("transform", (d) => `translate(${source.x0},${source.y0})`)
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0);
link1.merge(link1Enter).transition(myTransition).attr("d", this.drawLink);
link1
.exit()
.transition(myTransition)
.remove()
.attr("d", (d) => {
let o = {
source: {
x: source.x,
y: source.y,
},
target: {
x: source.x,
y: source.y,
},
};
return this.drawLink(o);
});
nodesOfUp.forEach((node) => {
node.y = -node.y;
});
const node2 = this.gNodes
.selectAll("g.nodeOfUpItemGroup")
.data(nodesOfUp, (d) => d.data.id);
const node2Enter = node2
.enter()
.append("g")
.attr("class", "nodeOfUpItemGroup")
.attr("transform", (d) => `translate(${source.x0},${source.y0})`)
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0)
.style("cursor", "pointer");
node2Enter
.append("rect")
.attr("width", (d) => {
if (d.depth === 0) {
return (d.data.name.length + 2) * 16;
}
return this.config.rectWidth;
})
.attr("height", (d) => {
if (d.depth === 0) {
return 30;
}
return this.config.rectHeight;
})
.attr("x", (d) => {
if (d.depth === 0) {
return (-(d.data.name.length + 2) * 16) / 2;
}
return -this.config.rectWidth / 2;
})
.attr("y", (d) => {
if (d.depth === 0) {
return -15;
}
return -this.config.rectHeight / 2;
})
.attr("rx", 5)
.attr("stroke-width", 1)
.attr("stroke", (d) => {
if (d.depth === 0) {
return "#5682ec";
}
return "#7A9EFF";
})
.attr("fill", (d) => {
if (d.depth === 0) {
return "#7A9EFF";
}
return "#FFFFFF";
})
.on("click", (e, d) => this.nodeClickEvent(e, d));
node2Enter
.append("text")
.attr("class", "main-title")
.attr("x", (d) => 0)
.attr("y", (d) => {
if (d.depth === 0) {
return 5;
}
return -14;
})
.attr("text-anchor", (d) => "middle")
.text((d) => {
if (d.depth === 0) {
return d.data.name;
} else {
return d.data.name.length > 11
? d.data.name.substring(0, 11)
: d.data.name;
}
})
.attr("fill", (d) => {
if (d.depth === 0) {
return "#FFFFFF";
}
return "#000000";
})
.style("font-size", (d) => (d.depth === 0? 16 : 14))
.style("font-family", "黑体")
.style("font-weight", "bold");
node2Enter
.append("text")
.attr("class", "sub-title")
.attr("x", (d) => 0)
.attr("y", (d) => 5)
.attr("text-anchor", (d) => "middle")
.text((d) => {
if (d.depth!== 0) {
let subTitle = d.data.name.substring(11);
if (subTitle.length > 10) {
return subTitle.substring(0, 10) + "...";
}
return subTitle;
}
})
.style("font-size", (d) => 14)
.style("font-family", "黑体")
.style("font-weight", "bold");
node2Enter
.append("text")
.attr("class", "percent")
.attr("x", (d) => 12)
.attr("y", (d) => 55)
.text((d) => {
if (d.depth!== 0) {
return d.data.percent;
}
})
.attr("fill", "#000000")
.style("font-family", "黑体")
.style("font-size", (d) => 14);
const expandBtnG2 = node2Enter
.append("g")
.attr("class", "expandBtn")
.attr("transform", (d) => `translate(${0},${-this.config.rectHeight / 2})`)
.style("display", (d) => {
if (d.depth === 0) {
return "none";
}
if (!d._children) {
return "none";
}
})
.on("click", (e, d) => {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
}
this.update(d);
});
expandBtnG2
.append("circle")
.attr("r", 8)
.attr("fill", "#7A9EFF")
.attr("cy", -8);
expandBtnG2
.append("text")
.attr("text-anchor", "middle")
.attr("fill", "#ffffff")
.attr("y", -3)
.style("font-size", 16)
.style("font-family", "微软雅黑")
.text((d) => d.children? "-" : "+");
const link2 = this.gLinks
.selectAll("path.linkOfUpItem")
.data(linksOfUp, (d) => d.target.data.id);
const link2Enter = link2
.enter()
.append("path")
.attr("class", "linkOfUpItem")
.attr("d", (d) => {
let o = {
source: {
x: source.x0,
y: source.y0,
},
target: {
x: source.x0,
y: source.y0,
},
};
return this.drawLink(o);
})
.attr("fill", "none")
.attr("stroke", "#7A9EFF")
.attr("stroke-width", 1)
.attr("marker-end", "url(#markerOfUp)");
node2
.merge(node2Enter)
.transition(myTransition)
.attr("transform", (d) => `translate(${d.x},${d.y})`)
.attr("fill-opacity", 1)
.attr("stroke-opacity", 1);
node2
.exit()
.transition(myTransition)
.remove()
.attr("transform", (d) => `translate(${source.x0},${source.y0})`)
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0);
link2.merge(link2Enter).transition(myTransition).attr("d", this.drawLink);
link2
.exit()
.transition(myTransition)
.remove()
.attr("d", (d) => {
let o = {
source: {
x: source.x,
y: source.y,
},
target: {
x: source.x,
y: source.y,
},
};
return this.drawLink(o);
});
const expandButtonsSelection = d3.selectAll("g.expandBtn");
expandButtonsSelection
.select("text")
.transition()
.text((d) => d.children? "-" : "+");
this.rootOfDown.eachBefore((d) => {
d.x0 = d.x;
d.y0 = d.y;
});
this.rootOfUp.eachBefore((d) => {
d.x0 = d.x;
d.y0 = d.y;
});
};
drawLink = ({ source, target }) => {
const halfDistance = (target.y - source.y) / 2;
const halfY = source.y + halfDistance;
return `M${source.x},${source.y} L${source.x},${halfY} ${target.x},${halfY} ${target.x},${target.y}`;
};
nodeClickEvent = (e, d) => {
console.log("当前节点的数据:", d);
};
render() {
return (
<div
ref={(el) => this.container = el}
style={{ height: '650px' }}
></div>
);
}
}
export default LegalPerson;
参考