import { PureComponent } from 'react'
import { D3 } from "@weapp/ui"
let d3: any = null;
//辅助函数: 递归获取tree的节点数量
function getCount(parent: any) {
var count = 0;
if (Array.isArray(parent.children)) {
count += parent.children.length;
parent.children.forEach(function (child: any) {
if (Array.isArray(child.children)) {
count += getCount(child);
}
});
}
return count;
}
//数据源
var root = {
"x0": null,
"y0": null,
'name': '中国',
'children': [{
'name': '浙江',
"_name": 'zhejiang',
'children': []
},
{
'name': '广西',
'children': [],
"_name": 'guangxi',
},
{
'name': '黑龙江',
"_name": 'heilongjiang',
}
]
}
function createTree() {
//布局和尺寸参数
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
height = 800 - margin.top - margin.bottom;
//常规参数
var i = 0,
duration = 750,
rectW = 120,
rectH = 60;
//使用hierarchy层级布局生成树结构
var tree = d3.tree().nodeSize([130, 60]);
var hierarchyData = d3.hierarchy(root)
//path连线
function linkVertical(d: any) {
// return "M" + d.source.x + "," + d.source.y //曲线
// + "C" + (d.source.x + d.target.x) / 2 + "," + d.source.y
// + " " + (d.source.x + d.target.x) / 2 + "," + d.target.y
// + " " + d.target.x + "," + d.target.y;
let sourceX = d.source.x, //折线
sourceY = d.source.y + rectH,
targetX = d.target.x,
targetY = d.target.y;
return "M" + sourceX + "," + sourceY +
"V" + ((targetY - sourceY) / 2 + sourceY) +
"H" + targetX +
"V" + targetY;
}
//在页面中添加svg 支持拖拽和缩放
var svg = d3.select("#body").append("svg").attr("width", 1000).attr("height", 1000)
.call(d3.zoom().scaleExtent([1, 3]).on("zoom",
function redraw(event: any) {
svg.attr("transform", event.transform);
}
)).append("g").attr("transform", "translate(" + 350 + "," + 20 + ")").append("g");
(root as any).x0 = 0;
(root as any).y0 = height / 2;
// root.children.forEach(collapse);
update(root);
d3.select("#body").style("height", "800px");
function update(source: any) {
let treeData = tree(hierarchyData);
let nodes = treeData.descendants().reverse();
let links = treeData.links()
// Normalize for fixed-depth.
nodes.forEach(function (d: any) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function (d: any) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function (d: any) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
nodeEnter.append("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr('y', 0)
.attr('x', -rectW / 2)
.attr("stroke-width", 1)
.style("fill", function (d: any) {
return d._children ? "lightsteelblue" : "rgb(93,156,236)";
}).on("click", function (e: any, node: any) {
click(node)
});
nodeEnter.append("text")
.attr("x", rectW / 2 - rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function (d: any) {
return d.data.name;
});
// Transition nodes to their new position.
let nodeUpdate = nodeEnter.merge(node);
nodeUpdate.transition()
.duration(750)
.attr("transform", function (d: any) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function (d: any) {
return d._children ? "lightsteelblue" : "rgb(93,156,236)";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function (d: any) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function (d: any) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
let linkEnter = link.enter().insert("path", "g")
.attr("class", "link")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("d", function (d: any) {
var o = {
x: source.x0,
y: source.y0
};
return linkVertical({
source: o,
target: o
});
});
let linkUpdate = linkEnter.merge(link);
// Transition links to their new position.
linkUpdate.transition()
.duration(duration)
.attr("d", linkVertical);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function (d: any) {
var o = {
x: source.x,
y: source.y
};
return linkVertical({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function (d: any) {
d.x0 = d.x;
d.y0 = d.y;
});
if (source.id === getCount(root) + 1) {
//折叠区域渲染 使用延迟渲染优化显示效果
setTimeout(function () {
renderCollapseArea();
}, 200)
}
}
function renderCollapseArea() {
function handleClick(e: any) {
let target = e.target.dataset;
let {
mclass,
type,
collapsed,
self
} = target;
if (type !== "btn") {
return
}
if (collapsed === "false") { //展开
d3.select(`.${mclass}`).transition().style("display", "none");
e.target.dataset.collapsed = "true";
document.getElementsByClassName(`${self}`)[0].innerHTML = "+"
} else {
d3.select(`.${mclass}`).transition().style("display", "block")
e.target.dataset.collapsed = "false";
document.getElementsByClassName(`${self}`)[0].innerHTML = "-"
}
}
window.handleClick = handleClick;
}
renderCollapseArea();
// Toggle children on click.
function click(d: any) {
if (d.id === getCount(root) + 1) {
//折叠前隐藏div区域
setTimeout(function () {
d3.selectAll("foreignObject").style("display", "none");
}, 200)
}
if (d.children) {
d._children = d.children;
d.children = null;
update(d);
} else if (d._children) {
d.children = d._children;
d._children = null;
update(d);
} else if (!d.children && !d._children) {
let _y = (d.depth + 1) * 2;
getNode(d, function (children: any) {
children.forEach((node: any) => {
node.depth = d.depth + 1;
node.height = 0;
node.x = d.x;
node.x0 = d.x0;
node.y = _y;
node.y0 = _y;
node.parent = d;
})
d.children = children;
d._children = d.children;
update(d);
})
}
}
//异步请求node
function getNode(d: any, callback: Function) {
//d是被点击的节点 将这个作为参数去获取不同的子节点
//模拟接口
setTimeout(() => {
let res: any = {
zhejiang: [{ data: { name: "杭州" } },
{ data: { name: "宁波" } },
{ data: { name: "温州" } }],
guangxi: [
{ data: { name: "桂林" } },
{ data: { name: "南宁" } },
{ data: { name: "柳州" } },
{ data: { name: "防城港" } }
]
}
let _res = res[`${d.data._name}`];
_res && callback(_res);
}, 100);
}
}
export default class tree extends PureComponent {
render() {
D3().then(Instance => {
const { selectAll, zoom, select, tree, hierarchy, linkHorizontal } = Instance;
d3 = {
selectAll, zoom, select, tree, hierarchy, linkHorizontal
};
d3 && createTree()
});
return (
<div id='svgContainer'>
<div id="body"></div>
</div>
)
}
}