import { isEqual, uniqueId, cloneDeep } from 'lodash';
import * as React from 'react';
import loadjs from 'loadjs';
import classnames from 'classnames';
import { RightMenu, RightMenuStore } from './rightMenu';
import { WeaSlideModal, WeaDialog } from 'ecCom';
import './index.less';
const getLength = (str) => {
let realLength = 0, len = str.length, charCode = -1;
for (let i = 0; i < len; i++) {
charCode = str.charCodeAt(i);
if (charCode >= 0 && charCode <= 128) realLength += 1;
else realLength += 2;
}
return realLength;
};
const getSubStr = (str, start, length) => {
let rt = [], currentLength = 0, len = str.length, charCode = -1;
for (let i = 0; i < len; i++) {
charCode = str.charCodeAt(i);
if (charCode >= 0 && charCode <= 128) {
currentLength++;
} else {
currentLength += 2;
}
if (currentLength > start && currentLength <= length) {
rt.push(str.charAt(i));
}
}
return rt.join('');
}
const getShortStr = (ele, data, w = 100) => {
const length = parseInt(w / 6);
const datalength = data ? getLength(data) : 0;
if (length >= datalength) {
return data;
}
return getSubStr(data, 0, length) + '...';
}
export default class D3Tree extends React.Component {
totalNodes = 0;
maxLabelLength = 0;
i = 0;
selectedPath = [];
currentPath = [];
overedPath = [];
static defaultProps = {
widthToggle: true,
showRight: true,
layout: '0',
showMenu: false
}
constructor(props) {
super(props);
this.state = {
duration: 750,
hoverNode: undefined,
id: `d3TreeContainer_${new Date().getTime()}_${uniqueId()}`,
width: 220,
height: 100
}
this.didMountTree = false;
this.rightMenuStore = new RightMenuStore();
}
render() {
const { showRight, style, showMenu } = this.props;
const menu = [
{
key: '1',
icon: ,
content: '按钮 1',
},
{
key: '2',
icon: ,
content: '按钮 2',
}
];
return (
{ this.props.closeRight(false) }}>
{/*
*/}
this.container = ref}
id={this.state.id}
className="d3-tree"
style={style}
onContextMenu={showMenu ? this.onContextMenu : undefined}
/>
{/* */}
{/*
{this.props.showRight && this.renderRight()}
*/}
e.stopPropagation()} className={"Prj-WeaSlideModal"}>
)}
onClose={() => { this.props.closeRight(false) }}
onAnimationEnd={() => console.log('onAnimationEnd')} />
{/*
this.props.closeDialog(false) }
visible={this.props.showDialog}
style={{ width: 1000, height: 600 }}
title={'活动计划'}
hasScroll
url={this.renderRight() !== 'function'&& this.renderRight()}
>
*/}
this.selection = ref}>
)
}
onContextMenu = (e) => {
if (this.isClickNode && this.opreateNode) {
const left = e.clientX;
const top = e.clientY;
this.showRightMenu();
this.rightMenuStore.show(left, top,
this.opreateNode.id === this.root.id,
this.getHasChild(this.opreateNode),
this.getChildVisible(this.opreateNode));
}
this.isClickNode = false;
e.stopPropagation && e.stopPropagation();
e.preventDefault && e.preventDefault();
e.nativeEvent && e.nativeEvent.preventDefault();
}
showRightMenu = () => {
if (!this.menuWrapper) {
this.menuWrapper = document.createElement('div');
document.body.appendChild(this.menuWrapper);
}
ReactDOM.render(, this.menuWrapper);
}
clickAnyWhere = (e) => {
this.rightMenuStore.hide();
if (this.startMove && this.selection) {
this.startMove = false;
this.selection.style.display = 'none';
const top = parseInt(this.selection.style.top);
const left = parseInt(this.selection.style.left);
const width = parseInt(this.selection.style.width);
const height = parseInt(this.selection.style.height);
this.selection.style.height = '0px';
this.selection.style.width = '0px';
}
}
mouseup = (d) => {
const e = window.event;
if (e.button === 2) {
this.opreateNode = d;
this.isClickNode = true;
this.click(d);
}
}
renderRight = () => {
if (typeof this.props.renderRight === 'function') {
if (this.selectedNode && this.props.renderRight) {
return this.props.renderRight(this.selectedNode);
} else {
return null;
}
} else {
return this.props.renderRight;
}
}
diableMouseOut = () => {
this.canTriggerMouseOut = false;
}
enableMouseOut = () => {
this.canTriggerMouseOut = true;
this.outNode();
}
componentWillReceiveProps(nextProps) {
if (!isEqual(this.props.data, nextProps.data) ||
this.props.layout !== nextProps.layout && this.container &&
(this.didMountTree = false, this.container.innerHTML = "", true)) {
if (nextProps.data) {
clearTimeout(this.initTimer);
this.initTimer = setTimeout(() => {
this.initTree(nextProps.data);
}, 100);
}
}
if (this.props.scale !== nextProps.scale) {
if (this.svgGroup) {
const transform = this.svgGroup.attr("transform");
if (transform) {
const scale = nextProps.scale / 10;
this.svgGroup.attr("transform", `${transform.split('scale')[0]}scale(${scale})`);
}
}
}
}
componentWillUnmount() {
window.removeEventListener('resize', this.resize);
document.removeEventListener('mouseup', this.clickAnyWhere);
if (this.container) {
/* this.container.removeEventListener('mousedown', this.onMouseDown);
this.container.removeEventListener('mousemove', this.onMouseMove); */
}
if (this.menuWrapper) {
ReactDOM.unmountComponentAtNode(this.menuWrapper);
document.body.removeChild(this.menuWrapper);
delete this.menuWrapper;
}
}
componentDidMount() {
window.addEventListener('resize', this.resize);
document.addEventListener('mouseup', this.clickAnyWhere);
if (this.container) {
/* this.container.addEventListener('mousedown', this.onMouseDown);
this.container.addEventListener('mousemove', this.onMouseMove); */
}
if (!loadjs.isDefined('weaEdcD3js')) {
loadjs(['/edc/d3/d3.v3.js'], 'weaEdcD3js', {
success: () => {
if (this.container && this.props.data) {
const _this = this;
setTimeout(function(){
_this.initTree(_this.props.data);
_this.resize();
_this.click(_this.root);
},1000);
}
}
});
} else {
if (this.container && this.props.data) {
const _this = this;
setTimeout(function(){
_this.initTree(_this.props.data);
_this.resize();
_this.click(_this.root);
},1000);
}
}
}
onMouseDown = (e) => {
if (e.button === 0 && this.selection && e.target.tagName === 'svg') {
this.selectionLeft = e.clientX;
this.selectionTop = e.clientY;
this.selection.style.display = 'block';
this.selection.style.top = this.selectionTop + 'px';
this.selection.style.left = this.selectionLeft + 'px';
this.startMove = true;
}
}
onMouseMove = (e) => {
if (this.startMove) {
this.selection.style.height = (e.y - this.selectionTop) + 'px';
this.selection.style.width = (e.x - this.selectionLeft) + 'px';
e.stopPropagation && e.stopPropagation();
e.preventDefault && e.preventDefault();
e.nativeEvent && e.nativeEvent.preventDefault();
}
}
resize = () => {
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
const viewerHeight = this.container.clientHeight;
const viewerWidth = this.container.clientWidth;
d3.select(`#${this.state.id}>svg`)
.attr("width", viewerWidth)
.attr("height", viewerHeight);
}, 100);
}
getSize = () => {
const { layout } = this.props;
const { width, height } = this.state;
if (layout === '1') { // 上
return {
nodeSize: [width + 10, height],
width,
height,
deep: width
}
} else if (layout === '3') { // 下
return {
nodeSize: [width + 10, height],
width,
height,
deep: width * -1
}
} else if (layout === '0') { // 左
return {
nodeSize: [height + 10, width],
width,
height,
deep: width * 2,
}
} else if (layout === '2') { // 右
return {
nodeSize: [height + 10, width],
width,
height,
deep: width * -2,
}
}
}
searchNode = (name, nodes = [this.root]) => {
const searchedNode = [];
nodes.forEach(node => {
if (node.name.toLowerCase().indexOf(name) > -1) {
searchedNode.push(node);
}
if (node.children) {
searchedNode.push(...this.searchNode(name, node.children));
}
if (node._children) {
searchedNode.push(...this.searchNode(name, node._children));
}
});
return searchedNode;
}
initTree(treeData) {
const { layout } = this.props;
const { width, height } = this.state;
const { nodeSize } = this.getSize();
if (this.container && window.d3) {
const viewerHeight = this.container.clientHeight;
const viewerWidth = this.container.clientWidth;
this.tree = d3.layout.tree().nodeSize(nodeSize);
// define a d3 diagonal projection for use by the node paths later on.
this.diagonal = d3.svg.diagonal()
.projection((d) => {
return (layout === '1' || layout === '3') ?
[d.x + width / 2, d.y + height / 2] :
[d.y + width / 2, d.x + height / 2];
});
this.sortTree();
if (!this.didMountTree) {
const zoom = (e) => {
this.svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
if (this.props.zoom) {
this.props.zoom(d3.event.scale)
}
}
if (this.props.zoom) {
this.props.zoom(1)
}
this.zoomListener = d3.behavior.zoom().scaleExtent([0.1, 2]).on("zoom", zoom);
// define the baseSvg, attaching a class for styling and the zoomListener
this.baseSvg = d3.select("#" + this.state.id).append("svg")
.attr("width", viewerWidth)
.attr("height", viewerHeight)
.attr("class", "overlay")
.call(this.zoomListener);
this.svgGroup = this.baseSvg.append("g");
}
// Define the drag listeners for drag/drop behaviour of nodes.
// Define the root
const { x0 = viewerHeight / 2, y0 = 0 } = this.root || {};
this.root = cloneDeep(treeData)
this.root.x0 = x0
this.root.y0 = y0;
// Layout the tree initially and center on the root node.
this.update(this.root);
if (!this.didMountTree) {
this.centerNode(this.root);
}
//选中的节点在不在树中时 选中根节点
const curNodeId = this.selectedNode.id;
const treeNodeIds = [treeData.id];
treeData.children.map(c => {
treeNodeIds.push(c.id);
})
if (treeNodeIds.indexOf(curNodeId) < 0) {
this.click(this.root);
}
this.didMountTree = true;
} else {
setTimeout(() => {
this.initTree(treeData);
}, 50);
}
}
centerNode = (source) => {
const { layout } = this.props;
const { width, height } = this.state;
const viewerHeight = this.container.clientHeight;
const viewerWidth = this.container.clientWidth;
const scale = this.zoomListener.scale();
const isRoot = source === this.root;
this.selectedNode = source;
let x = 0;
let y = 0;
if (layout === '1') {
y = -source.y0;
x = -source.x0;
x = x * scale + viewerWidth / 2;
if (isRoot) {
y = y * scale + height;
} else {
y = y * scale + viewerHeight / 2;
}
} else if (layout === '3') {
y = -source.y0;
x = -source.x0;
x = x * scale + viewerWidth / 2;
if (isRoot) {
y = y * scale + viewerHeight - height * 2;
} else {
y = y * scale + viewerHeight / 2;
}
} else if (layout === '2') {
x = -source.y0;
y = -source.x0;
if (isRoot) {
x = x * scale + viewerWidth - width * 2;
} else {
x = x * scale + viewerWidth / 2;
}
y = y * scale + viewerHeight / 2;
} else {
x = -source.y0;
y = -source.x0;
if (isRoot) {
x = x * scale + width;
} else {
x = x * scale + viewerWidth / 2;
}
y = y * scale + viewerHeight / 2;
}
this.svgGroup.transition()
.duration(this.state.duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
this.zoomListener.scale(scale);
this.zoomListener.translate([x, y]);
this.click(source);
}
collapse = (d) => {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
expand = (d) => {
if (d._children) {
d.children = d._children;
d.children.forEach(expand);
d._children = null;
}
}
sortTree = () => {
this.tree.sort((a, b) => {
return parseInt(a.id) - parseInt(b.id);
});
}
getXY = (x, y) => {
const { layout } = this.props;
if (layout === '1' || layout === '3') { // 上下
return `${x},${y}`;
} else {
return `${y},${x}`;
}
}
update = (source) => {
if (!this.tree) {
setTimeout(() => {
this.update(source);
}, 50);
return;
}
const { desc, desc2, desc3, showname, showSubmitCount = true, nameTitle } = this.props;
const { duration } = this.state;
const { deep, width, height, textAnchor } = this.getSize();
// Compute the new tree layout.
window.tree = this.tree;
const nodes = this.tree.nodes(this.root).reverse();
const links = this.tree.links(nodes);
const isShownameFunc = typeof showname === 'function';
const isDescFunc = typeof desc === 'function';
const isDesc2Func = typeof desc2 === 'function';
const isDesc3Func = typeof desc3 === 'function';
// Normalize for fixed-depth.
nodes.forEach((d) => {
d.y = d.depth * deep;
if (d.current) {
this.currentPath = this.getLinksToParents(d);
}
});
// Update the nodes…
const node = this.svgGroup.selectAll("g.node")
.data(nodes, (d) => d.id);
// Enter any new nodes at the parent's previous position.
const nodeEnter = node.enter().append("g")
.attr("class", 'node')
.attr("transform", d => `translate(${this.getXY(source.x0, source.y0)})`)
.on("click", this.click)
.on('mouseup', this.mouseup);
nodeEnter.append("rect")
.attr('class', 'node-rect')
.attr("width", width)
.attr("height", height)
.attr("stroke", "#999")
.attr("stroke-width", 2)
.attr("fill", "#fff")
.attr("rx", 2)
.attr("ry", 2);
nodeEnter.append("rect")
.attr("class", 'flag')
.attr("x", 1)
.attr("y", 1)
.attr("width", 6)
.attr("height", height - 2)
.attr("fill", "rgb(230, 228, 228)")
nodeEnter.append("rect")
.attr("class", 'flag2')
.attr("x", 1)
.attr("y", (d)=>{
if(d.type=="task"){
// console.log((height - 2)*(d.finish/100),222,height,d.finish,d.id);
let finish = d.finish == -1 ? 0 : d.finish;
return height - height*(finish/100) + 1*(finish/100)
}else{
return 1
}
})
.attr("width", 6)
.attr("height", (d)=>{
if(d.type=="task"){
// console.log((height - 2)*(d.finish/100),222,height,d.finish,d.id);
let finish = d.finish == -1 ? 0 : d.finish;
return (height - 2)*(finish/100)
}else{
// console.log((height - 2));
return height - 2
}
})
.attr("fill", "#fff")
.style("fill", (d) => {
if(d.type=="task"){
if(d.overstr!==""){
return "red";
}else{
return "#52c41a";
}
}else{
return d.type=="prj" ? "#f7f7f7" : (d.type=="task" && d.isfinish == 1 ? "#52c41a" : '#f7f7f7');
}
});
const textg = nodeEnter.append('g').attr('class', 'text-g');
textg.append("text")
.attr("x", 20)
.attr("y", 20)
.attr("dy", ".5em")
.attr("text-anchor", textAnchor)
.text(function (d) {
return getShortStr(this, isShownameFunc ? showname(d) : d[showname],120);
})
.on('click', this.clickTitle);
textg.append('title').text(d => nameTitle ? nameTitle : isShownameFunc ? showname(d) : d[showname]);
nodeEnter.append("text")
.attr("display", d => {
let all = 0;
if (d.children) {
all = d.children.length;
} else if (d._children) {
all = d._children.length;
}
return all ? 'block' : 'none';
})
.attr("class", 'count')
.attr("x", width - 30)
.attr("y", 20)
.attr("dy", ".35em")
.attr("text-anchor", textAnchor)
.text(d => {
let submitCount = 0;
let all = 0;
if (d.children) {
submitCount = d.children.filter(d => d.isfinish).length;
all = d.children.length;
} else if (d._children) {
submitCount = d._children.filter(d => d.isfinish).length;
all = d._children.length;
}
if (showSubmitCount) {
return `${submitCount}/${all}`
} else {
return `${all}`
}
});
const descg = nodeEnter.append('g').attr('class', 'desc-g');
descg.append("text")
.attr("class", 'desc')
.attr("x", 20)
.attr("y", 45)
.attr("dy", ".35em")
.attr("text-anchor", textAnchor).text(function (d) {
return getShortStr(this, isDescFunc ? desc(d) : d[desc], 120);
});
descg.append('title').text(d => isDescFunc ? desc(d) : d[desc]);
const descg2 = nodeEnter.append('g').attr('class', 'desc-g2');
descg2.append("text")
.attr("class", 'desc')
.attr("x", 20)
.attr("y", 65)
.attr("dy", ".35em")
.attr("text-anchor", textAnchor).text(function (d) {
return d[desc2];
});
descg2.append('title').text(d => isDesc2Func ? desc(d) : d[desc2]);
const descg3 = nodeEnter.append('g').attr('class', 'desc-g3');
descg3.append("text")
.attr("class", 'desc')
.attr("x", 20)
.attr("y", 85)
.attr("dy", ".35em")
.attr("fill", "rgb(109, 106, 236")
.attr("text-anchor", textAnchor).text(function (d) {
return getShortStr(this, isDesc3Func ? desc(d) : d[desc3], 120);
});
descg3.append('title').text(d => isDesc3Func ? desc(d) : d[desc3]);
// descText.append('svg:title').text(d=>d[desc]);
// Transition nodes to their new position.
const nodeUpdate = node.transition()
.duration(duration)
.attr("transform", d => `translate(${this.getXY(d.x, d.y)})`)
.attr("class", (d) => {
if (this.selectedNode && d.id === this.selectedNode.id) {
return 'node node-selected'
} else {
return 'node';
}
});
nodeEnter.append("rect")
.attr("class", 'flag2')
.attr("x", 1)
.attr("y", (d)=>{
if(d.type=="task"){
// console.log((height - 2)*(d.finish/100),222,height,d.finish,d.id);
let finish = d.finish == -1 ? 0 : d.finish;
return height - height*(finish/100) + 1*(finish/100)
}else{
return 1
}
})
.attr("width", 6)
.attr("height", (d)=>{
if(d.type=="task"){
// console.log((height - 2)*(d.finish/100),222,height,d.finish,d.id);
let finish = d.finish == -1 ? 0 : d.finish;
return (height - 2)*(finish/100)
}else{
// console.log((height - 2));
return height - 2
}
})
.attr("fill", "#fff")
.style("fill", (d) => {
if(d.type=="task"){
if(d.overstr!==""){
return "red";
}else{
return "#52c41a";
}
}else{
return d.type=="prj" ? "#f7f7f7" : (d.type=="task" && d.isfinish == 1 ? "#52c41a" : '#f7f7f7');
}
});
nodeUpdate.select("rect.flag2")
.attr("y", (d)=>{
if(d.type=="task"){
// console.log((height - 2)*(d.finish/100),222,height,d.finish,d.id);
let finish = d.finish == -1 ? 0 : d.finish;
return height - height*(finish/100) + 1*(finish/100)
}else{
return 1
}
})
.attr("height", (d)=>{
if(d.type=="task"){
// console.log((height - 2)*(d.finish/100),222,height,d.finish,d.id);
let finish = d.finish == -1 ? 0 : d.finish;
return (height - 2)*(finish/100)
}else{
// console.log((height - 2));
return height - 2
}
})
.style("fill", (d) => {
if(d.type=="task"){
if(d.overstr!==""){
return "red";
}else{
return "#52c41a";
}
}else{
return d.type=="prj" ? "#f7f7f7" : (d.type=="task" && d.isfinish == 1 ? "#52c41a" : '#f7f7f7');
}
});
nodeUpdate.select("rect.node-rect")
.attr("width", width)
.attr("height", height)
.attr("stroke", "#999")
.attr("stroke-width", 2)
.attr("fill", "#fff");
nodeUpdate.select("text").style("fill-opacity", 1);
nodeUpdate.select("g.text-g>text").text(function (d) {
return getShortStr(this, isShownameFunc ? showname(d) : d[showname]);
});
nodeUpdate.select("g.text-g>title").text(function (d) {
return nameTitle ? nameTitle : isShownameFunc ? showname(d) : d[showname];
})
nodeUpdate.select("g.desc-g>text.desc").text(function (d) {
return getShortStr(this, isDescFunc ? desc(d) : d[desc], 120);
});
nodeUpdate.select("g.desc-g2>text.desc").text(function (d) {
return d[desc2];
});
nodeUpdate.select("g.desc-g3>text.desc").text(function (d) {
return getShortStr(this, isDesc3Func ? desc(d) : d[desc3], 120);
});
nodeUpdate.select("g.desc-g>title").text(function (d) {
return isDescFunc ? desc(d) : d[desc];
});
nodeUpdate.select("g.desc-g>title").text(d => isDescFunc ? desc(d) : d[desc]);
nodeUpdate.select('text.count').text(d => {
let submitCount = 0;
let all = 0;
if (d.children) {
submitCount = d.children.filter(d => d.isfinish).length;
all = d.children.length;
} else if (d._children) {
submitCount = d._children.filter(d => d.isfinish).length;
all = d._children.length;
}
if (showSubmitCount) {
return `${submitCount}/${all}`
} else {
return `${all}`
}
}).attr("display", d => {
let all = 0;
if (d.children) {
all = d.children.length;
} else if (d._children) {
all = d._children.length;
}
return all ? 'block' : 'none';
})
nodeUpdate.select("g.count>text").text(d => {
let submitCount = 0;
let all = 0;
if (d.children) {
submitCount = d.children.filter(d => d.isfinish).length;
all = d.children.length;
} else if (d._children) {
submitCount = d._children.filter(d => d.isfinish).length;
all = d._children.length;
}
if (showSubmitCount) {
return `${submitCount}/${all}`
} else {
return `${all}`
}
}).attr("display", d => {
let all = 0;
if (d.children) {
all = d.children.length;
} else if (d._children) {
all = d._children.length;
}
return all ? 'block' : 'none';
})
// Transition exiting nodes to the parent's new position.
const nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", d => `translate(${this.getXY(source.x, source.y)})`)
.remove();
nodeExit.select("rect")
.attr("width", width)
.attr("height", height)
.attr("stroke", "#ccc")
.attr("stroke-width", 2);
nodeExit.select("text");
// Update the links…
const link = this.svgGroup.selectAll("path.link")
.data(links, d => d.target.id)
.attr('class', "link");
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr('class', "link")
.attr("stroke", d => {
const { source: { id: from }, target: { id: to } } = d;
if (this.selectedPath.indexOf(`${from},${to}`) > -1) {
return "#2db7f5"
} else if (this.currentPath.indexOf(`${from},${to}`) > -1) {
return "#2db7f5";
}
return "#ccc"
})
.attr('fill', "none")
.attr("stroke-width", 1.5)
.attr("x", width / 2)
.attr("y", height / 2)
.attr("d", (d) => {
const o = {
x: source.x0,
y: source.y0
};
return this.diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", this.diagonal)
.attr("stroke", d => {
const { source: { id: from }, target: { id: to } } = d;
if (this.selectedPath.indexOf(`${from},${to}`) > -1) {
return "#2db7f5"
} else if (this.currentPath.indexOf(`${from},${to}`) > -1) {
return "#2db7f5";
}
return "#ccc"
});
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", (d) => {
var o = {
x: source.x,
y: source.y
};
return this.diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach((d) => {
d.x0 = d.x;
d.y0 = d.y;
});
}
clickTitle = (d) => {
if (this.props.onClickTitle) {
this.props.onClickTitle(d);
window.event.stopPropagation();
}
}
click = (d) => {
if (d) {
this.selectedPath = this.getLinksToParents(d);
this.selectedNode = d;
// d = this.toggleChildren(d);
this.update(d);
if (this.props.onClick) {
this.props.onClick(d, this.getHasChild(d));
}
}
}
getLinksToParents = (d) => {
const selectedPath = [];
let current = d;
while (current) {
selectedPath.push(`${current.parentid},${current.id}`);
current = current.parent;
}
return selectedPath;
}
toggleChildren = () => {
const d = this.opreateNode;
if (d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else if (d._children) {
d.children = d._children;
d._children = null;
}
this.update(d);
}
}
getChildVisible = (d) => {
return !!d.children;
}
getHasChild = (d) => {
return !!d.children || !!d._children;
}
}