删除节点后未删除D3.js链接 [英] D3.js link not removed after deleting node

查看:81
本文介绍了删除节点后未删除D3.js链接的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图通过直接修改treeData来删除d3树中的一个节点(按照Mike Bostocks在这里建议的添加/删除节点的建议:-


点击这个橙色矩形方法-> removeSelectedNode()(具有选定的节点),它找到父节点并删除子索引,然后我重新计算根详细信息并再次调用更新树(不确定是否有更好的方法来进行此更新) 。


  var margin = {
顶部:20,
右:120,
底部:20,
左:120,
},
宽度= 960-margin.right-margin .left,
高度= 800-margin.top-margin.bottom;

函数generateEmptyDecisionBox(condition){
return condition ==='False'吗? [{{
name: newDecision,
id: newId,
type: decision,
value: notSure,
condition:`$ {condition}`,
},{}]:[{},{
name: newDecision,
id: newId,
type: decision,
value: notSure,
; condition:`$ {condition}`,
}];
}

函数generateEmptyActionBox(condition){
return condition ===‘False’吗? [{{
name: newAction,
id: newId,
type: action,
value: notSure,
condition:`$ {condition}`,
},{}]:[{},{
name: newAction,
id: newId,
type: action,
value: notSure,
; condition:`$ {condition}`,
}];
}

var selectedNode;
var selectedLink;


var treeData = [{
name: Root,
type: decision,
; id: root,
children:[{
name: analytics,
condition: False,
类型:决定,
值: a + b,
id: child1,
children :: [{{
name: distinction,
type: action,
id: child2,
; condition: True,
value: 5,
},{
name: nonDistinction,
类型: action,
id: child3,
condition: False,
value ;: 4,
儿童:[],
}],
},
{
condition: True; ,
名称:部门,
类型:决策,
值: a-b,
; id: child33,
children:[],
},
],
}];

var i = 0,
持续时间= 1000,
rectW = 120,
rectH = 60;

var treeMap = d3.tree()
.nodeSize([150,180]);

//链接到绘制链接的功能
var linkFunc = function(d){
console.log( linkFunc,d);
var source = {
x:d.parent.x + rectW / 2,
y:d.parent.y +(rectH / 2),
};
var target = {
x:d.x +(rectW / 2),
y:d.y + 3,
};

//这是直线弯曲
的地方var inflection = {
x:target.x,
y:source.y,
};
var radius = 5;

var结果= M; + source.x +’,’+ source.y;

if(source.x< target.x&&& d.data.type){
//子项位于父项
结果的右侧+ = 'H'+(inflection.x-半径);
}否则,如果(d.data.type){
结果+ =‘H’+(inflection.x +半径);
}否则{返回

}

//在弯曲处稍微弯曲直线
结果+ ='Q'+ inflection.x +','+ inflection.y +''+弯曲。 x +','+(inflection.y +半径);

结果+ ='V'+ target.y;
的返回结果;
};

//链接功能结束//

const zoomSvg = d3.select('。tree-diagram')
.append('svg')
.attr('width',width)
.attr('height',height)
.append('g');

const svg = zoomSvg.append(’g’)
.attr(’transform’,‘translate(’+ 300 +’,’+ 20 +’)’);

const attachZoom = d3.select(’svg’);
attachZoom.call(d3.zoom()。on('zoom',()= >> {
zoomSvg.attr('transform',d3.event.transform)
}) )


//将箭头添加到下一个决策点。

svg.append( svg:defs)
.selectAll( marker)
.data([ end])//不同的链接/可以在此处定义路径类型
.enter()
.append( svg:marker))//此部分在箭头中添加
.attr( id,字符串)
.attr( viewBox, 0 -5 10 10)
.attr( refX,15)
.attr( refY,0.5)
.attr( markerWidth,4)
.attr( markerHeight,4)
.attr( orient, auto)
.append ( svg:路径)
.attr( d, M0,-5L10,0L0,5);

//必要,以便zoom知道从
/ * zm.translate([350,20])进行缩放的位置。 * /

root = d3.hierarchy(treeData [0],function(d){
return d.children;
});
root.x0 = 0;
root.y0 = 0;

更新(根);

d3.select(。tree-diagram)
.style( height, 1000px);

//绘制树的结尾//

函数update(source){
const treeData = treeMap(root);
const treeRoot = d3.hierarchy(root);
// d3.tree(treeRoot);
// var treeData = treeMap(root);

//计算新的树布局。
var个节点= treeData.descendants(),
个链接= treeData.descendants()
.slice(1);

console.log(nodes);

//归一化为固定深度。
个node.forEach(function(d){
d.y = d.depth * 90;
});

//更新节点…
var node = svg.selectAll( g.node)
.data(nodes,function(d){
返回d.data.id ||(d.id = ++ i);
});

//在父级的先前位置输入任何新节点。
var nodeEnter = node.enter()
.append( g)
.attr('transform','translate('+ source.x0 +','+ source。 y0 +')')
.attr(类,节点)
.on( click,click);
// .on( blur,onNodeBlur);

nodeEnter.append('path')
/ * .attr('d',function(d){
if(d.data.type ==='决定'){
返回'M 60 0 120 30 60 60 0 30 Z';
}否则if(d.data.type ==='action'){
返回'M 0 0 120 0 120 60 0 60 Z';
}否则{
返回'M -100 -10 -10 -10 -10 -10 -10 -10 -10Z';
}
})* /
.attr( stroke-width,1)
.attr('class','myPaths')
.style( fill,function( d){
返回 lightsteelblue;
});

nodeEnter.append( text)
.attr( x,rectW / 2)
.attr( y,rectH / 2)
.attr( dy, .35em)
.attr( text-anchor,中间)
.text(function(d){
return d.data.name;
});

//更新
var nodeUpdate = nodeEnter.merge(node);

//过渡到节点
的正确位置nodeUpdate.transition()
.duration(duration)
.attr( transform,function( d){
return translate( +(dx)+, +(dy)+);
});


nodeUpdate.select('path.myPaths')
.attr( d,function(d){
if(d.data.type ==='decision'){
return'M 60 0 120 30 60 60 0 30 Z';
} else if(d.data.type ==='action'){
返回'M 0 0 120 0 120 60 0 60 Z';
} else {
返回'M -100 -10 -10 -10 -10 -10 -10 -10 -10Z';
}
});


var nodeExit = node.exit()
.transition()
.duration(duration)
.attr( transform)函数(d){
return translate( + source.x +``, + source.y +``);
})
.remove() ;

//更新链接…
var link = svg.selectAll(。link)
.data(links,function(d){
return d.data.id;
})
.classed('link1',true);


//在父母的上一个位置输入任何新链接。
var linkEnter = link.enter()
.insert( g, g)
.attr( class, link));

linkEnter.append('path')
.on('click',function(d,i){
selectedLink = d;
//使用本地SVG接口以获取到
的边界框//计算路径的中心

var bbox = this.getBBox();
var x;
var y;

if(d.parent.x< dx){
//子代位于父代的右边
x = bbox.x + bbox.width;
y = bbox.y;
plusButton
.attr('transform','translate('+ x +','+ y +')')
.classed('hide ',false);

}否则{
x = bbox.x;
y = bbox.y;
plusButton
.attr('transform', 'translate('+ x +','+ y +')')
.classed('hide',false);
}
})
.on(' blur',function(d,i){
plusButton
.classed('hide',true);
})
.attr( marker-end, ; url(#end)");

//添加链接文本。
linkEnter.append(‘text’);

//合并新链接和现有链接,然后在所有链接上设置`d`和`text`
link = linkEnter.merge(link);

link.select(路径)
.attr( d,linkFunc);

link.select('text')
.text(function(d,i){
if(d.parent.x< dx){
返回'True';
} else {
返回'False';
}
})
.attr('transform',function(d){
console.log(d);
if(d.parent.x< dx&& d.data.type){
console.log("此处是源< ; target);
返回'translate('+(dx + rectW / 2)+','+(d.parent.y + rectH)+')';
}否则,如果(d .data.type){
return'translate('+(d.parent.x + rectW / 2)+','+(dy + rectH)+')';
} else {
return;
}
});


// LinkUpdate
var linkUpdate = linkEnter.merge(link);

//过渡到其新职位的链接。
link.transition()
.duration(duration)
.attr( d,linkFunc);

//过渡到其新职位的链接。

//将退出节点过渡到父级的新位置。
link.exit()
.transition()
.duration(duration)
.attr( d,linkFunc)
.remove();

//存放旧的过渡职位。
个node.forEach(function(d){
d.x0 = d.x;
d.y0 = d.y;
});
}


//点选
函数click(d){

if(d.data.type == ='action'){
return;
}

selectedNode = d;

if(!(d.data.children&& d.data.children [0]&&&Object.keys(d.data.children [0])。length)) {
diamondImageFalse
.attr('transform','translate('+(dx-20)+','+(dy + 35)+')')
.classed('隐藏',假);

rectangleShapeFalse.attr(’transform’,‘translate(’+(d.x-20)+’,’+ d.y +’)’)。classed('hide',false);

}

if(!(d.data.children&&d.data.children [1]&& Object.keys(d.data。 children [1] .length)){
diamondImage
.attr('transform','translate('+(dx + 110)+','+(dy + 35)+')' )
.classed('hide',false);

rectangleShape.attr(’transform’,‘translate(’+(d.x + 110)+’,’+ d.y +’)’)。classed('hide',false);

}
}

// o通话

函数addElement(d,真){
console.log( d);

d.children =空;
d.children = generateEmptyDecisionBox(truthy);
update(root);
}

//绘制元素//

函数drawDiamond(centroid){
//从顶部
console.log开始(形心);
console.log( rectH,rectH,rectW);
//从顶部
开始var result ='M'+ centroid.x +’,'+(centroid.y-rectH / 2);

//向右移动
结果+ =‘L’+(centroid.x + rectW / 2)+’,’+ centroid.y;

//底部
结果+ =‘L’+ centroid.x +’,’+(centroid.y + rectH / 2);

//左
结果+ =‘L’+(centroid.x-rectW / 2)+’,’+ centroid.y;

//关闭形状
结果+ ='Z';

返回结果;
}

function drawRect(centroid){
//从左上方开始
console.log(centroid);
var result ='M'+(centroid.x-rectW / 2)+’,’+(centroid.y-rectH / 2);

//向右移
结果+ =‘h’+ rectW;

//向下
结果+ =‘v’+ rectH;

//左
结果+ =‘h-’+ rectW;

//关闭形状
结果+ ='Z';

console.log(结果);
的返回结果;
}

var plusButton = svg
.append('g')
.classed('button',true)
.classed('hide ',true)
.on('click',function(){
console.log( hello);
/ * addElement(selectedLink.source); * /


console.log( Clicked on Diamond);
console.log( set hide to true);
removeAllButtonElements();
});

plusButton
.append('rect')
.attr('transform','translate(-8,-8)')//将按钮居中于` g`
.attr('width',16)
.attr('height',16)
.attr('rx',2);

plusButton
.append('path')
.attr('d','M-6 0 H6 M0 -6 V6');

varangleShape = svg.append('g')
.classed('conditionImage',true)
.classed('hide',true)
。 on('click',function(){
//现在对其进行修改以删除该节点。
//选择的节点是在点击函数$ b $中单击决策/矩形svg时保存的。 b removeSelectedNode(selectedNode);
/ * addActionOrDecision(selectedNode,'action','True')* /;
removeAllButtonElements();
});

矩形形状
.append('rect')
.attr('width',30)
.attr('height',20)
.style('fill','orange');


var diamondImage = svg.append('g')
.classed('conditionSvg',true)
.classed('hide',true)
.classed('scale',true)
.on('click',function(){
addActionOrDecision(selectedNode,'decision','True');
removeAllButtonElements( );
});

diamondImage
.append('path')
.attr('d','M 15 0 30 15 15 30 0 15 Z')
.style (填充,橙色);


varangleShapeFalse = svg.append('g')
.classed('conditionImage',true)
.classed('hide',true)
.on('click',function(){
addActionOrDecision(selectedNode,'action','False');
console.log( rectangle clicked);
removeAllButtonElements();
});

矩形ShapeFalse
.append('rect')
.attr('width',30)
.attr('height',20)
.style('fill','orange');

var diamondImageFalse = svg.append('g')
.classed('conditionImage',true)
.classed('hide',true)
。 classed('scale',true)
.on('click',function(){
addActionOrDecision(selectedNode,'decision','False');
// addElement(selectedNode, 'False');
removeAllButtonElements();
});

diamondImageFalse
.append('path')
.attr('d','M 15 0 30 15 15 30 0 15 Z')
.style (填充,橙色);


函数removeAllButtonElements(){
plusButton.classed('hide',true);
diamondImage.classed('hide',true);
angleShape.classed('hide',true);
diamondImageFalse.classed('hide',true);
angleShapeFalse.classed('hide',true);
}


函数addActionOrDecision(selectedNode,nodeType,conditionType){

const parentNodeId = selectedNode.parent.data.id;
const selectedNodeId = selectedNode.data.id;
console.log(parentNodeId,selectedNodeId);

//从实际的treeData中找到选定的节点
const foundRule = getNodeFromNodeId(treeData,selectedNodeId);
const newRuleId = Math.random();

const newNodeToAdd = {
condition:conditionType,
name:nodeType === decision? 新决策:新动作,
类型:nodeType,
值:,
id:newRuleId,
‘parent’:parentNodeId,
‘children’:[],
};

const clonedNewNode = {... newNodeToAdd};

if(conditionType ==='False'&& foundRule.children){
//foundRule.children[0] = newNodeToAdd;

foundRule.children.splice(0,1,clonedNewNode);

if(!(foundRule.children [1]&& Object.keys(foundRule.children [1]))){
foundRule.children [1] = {};
}

} else {
// foundRule.children [1] = newNodeToAdd;

foundRule.children.splice(1,1,clonedNewNode);

if(!(foundRule.children [0]&& Object.keys(foundRule.children [0]))){
founRule.children [0] = {};
}
}

//找到节点并为其添加一个子节点。
updateTree();

}

函数updateTree(){
root = d3.hierarchy(treeData [0],function(d){
return d.children ;
});
root.x0 =高度/ 2;
root.y0 = 0;

更新(根);

console.log(treeData);

}

函数getNodeFromNodeId(nodes,nodeId){
for(节点的常量节点){
const currNode = node;
if(currNode){
if(currNode.id === nodeId){
return currNode;
}否则,如果(currNode.children){
const childResult = getNodeFromNodeId(currNode.children,nodeId);
if(childResult){
return childResult;
}
}
}
}
返回null;
}

函数removeSelectedNode(){
const parentNodeId = selectedNode.parent.data.id;
const selectedNodeId = selectedNode.data.id;

const foundParentNode = getNodeFromNodeId(treeData,parentNodeId);
const foundNode = getNodeFromNodeId(treeData,selectedNodeId);

const foundIndex = foundParentNode.children.findIndex(child => child.id === foundNode.id);
//找到另一个节点,然后将其推入子节点数组。


//创建一个新数组并将未找到的索引推入children数组。
让我= 0;
let results = [];
let len = foundParentNode.children.length;
for(let i = 0; i< len; i ++){
if(i!== foundIndex){
results.push(foundParentNode.children [i]);
}
}

foundParentNode.children =结果;

updateTree();

}

css:-

  .node {
光标:指针;
大纲:无!重要;
}

.node文本{
字体:10px sans-serif;
}

.button> path {
stroke:blue;
笔划宽度:1.5;
/ *轮廓:无; * /
}

.button> rect {
fill:#ddd;
中风:灰色;
笔划宽度:1px;
}

.conditionalSvg {
/ *轮廓:无; * /
显示:无;
}

.hide {
/ * display:none; * /
不透明度:0!important;
/ *指针事件:无; * /
}

.link:hover {
outline:none!important;
cursor:指针;
笔划宽度:3px;
}

.link path {
/ *轮廓:无!重要; * /
fill:无;
中风:深灰色;
stroke-width:2px;
}

.link path:hover {
cursor:指针;
stroke-width:4px;
}

.link text {
font:10px sans-serif;
}

.colorBlue {
background-color:blue;
}


解决方案

显然,我必须删除屏幕上没有使用的链接:-

  link.exit()。remove(); 

这是更新的jsfiddle:- http://jsfiddle.net/awymjbon/

  var margin = {
top:20 ,
右:120,
底部:20,
左:120,
},
宽度= 960-margin.right-margin.left,
高度= 800-margin.top-margin.bottom;

函数generateEmptyDecisionBox(condition){
return condition ==='False'吗? [{{
name: newDecision,
id: newId,
type: decision,
value: notSure,
condition:`$ {condition}`,
},{}]:[{},{
name: newDecision,
id: newId,
type: decision,
value: notSure,
; condition:`$ {condition}`,
}];
}

函数generateEmptyActionBox(condition){
return condition ===‘False’吗? [{{
name: newAction,
id: newId,
type: action,
value: notSure,
condition:`$ {condition}`,
},{}]:[{},{
name: newAction,
id: newId,
type: action,
value: notSure,
; condition:`$ {condition}`,
}];
}

var selectedNode;
var selectedLink;


var treeData = [{
name: Root,
type: decision,
; id: root,
children:[{
name: analytics,
condition: False,
类型:决定,
值: a + b,
id: child1,
children :: [{{
name: distinction,
type: action,
id: child2,
; condition: True,
value: 5,
},{
name: nonDistinction,
类型: action,
id: child3,
condition: False,
value ;: 4,
儿童:[],
}],
},
{
condition: True; ,
名称:部门,
类型:决策,
值: a-b,
; id: child33,
children:[],
},
],
}];

var i = 0,
持续时间= 1000,
rectW = 120,
rectH = 60;

var treeMap = d3.tree()
.nodeSize([150,180]);

//链接到绘制链接的功能
var linkFunc = function(d){
console.log( linkFunc,d);
var source = {
x:d.parent.x + rectW / 2,
y:d.parent.y +(rectH / 2),
};
var target = {
x:d.x +(rectW / 2),
y:d.y + 3,
};

//这是直线弯曲
的地方var inflection = {
x:target.x,
y:source.y,
};
var radius = 5;

var结果= M; + source.x +’,’+ source.y;

if(source.x< target.x&&& d.data.type){
//子项位于父项
结果的右侧+ = 'H'+(inflection.x-半径);
}否则,如果(d.data.type){
结果+ =’H’+(inflection.x +半径);
}否则{返回

}

//在弯曲处稍微弯曲直线
结果+ ='Q'+ inflection.x +','+ inflection.y +''+弯曲。 x +','+(inflection.y +半径);

结果+ ='V'+ target.y;
的返回结果;
};

//链接功能结束//

const zoomSvg = d3.select('。tree-diagram')
.append('svg')
.attr('width',width)
.attr('height',height)
.append('g');

const svg = zoomSvg.append(’g’)
.attr(’transform’,‘translate(’+ 300 +’,’+ 20 +’)’);

const attachZoom = d3.select(’svg’);
attachZoom.call(d3.zoom()。on('zoom',()= >> {
zoomSvg.attr('transform',d3.event.transform)
}) )


//将箭头添加到下一个决策点。

svg.append( svg:defs)
.selectAll( marker)
.data([ end])//不同的链接/可以在此处定义路径类型
.enter()
.append( svg:marker))//此部分在箭头中添加
.attr( id,字符串)
.attr( viewBox, 0 -5 10 10)
.attr( refX,15)
.attr( refY,0.5)
.attr( markerWidth,4)
.attr( markerHeight,4)
.attr( orient, auto)
.append ( svg:路径)
.attr( d, M0,-5L10,0L0,5);

//必要,以便缩放知道从
/ * zm.translate([350,20])进行缩放的位置。 * /

root = d3.hierarchy(treeData [0],function(d){
return d.children;
});
root.x0 = 0;
root.y0 = 0;

更新(根);

d3.select(。tree-diagram)
.style( height, 1000px);

//绘制树的结尾//

函数update(source){
const treeData = treeMap(root);
const treeRoot = d3.hierarchy(root);
// d3.tree(treeRoot);
// var treeData = treeMap(root);

//计算新的树布局。
var个节点= treeData.descendants(),
个链接= treeData.descendants()
.slice(1);

console.log(nodes);

//归一化为固定深度。
个node.forEach(function(d){
d.y = d.depth * 90;
});

//更新节点…
var node = svg.selectAll( g.node)
.data(nodes,function(d){
返回d.data.id ||(d.id = ++ i);
});

//在父级的先前位置输入任何新节点。
var nodeEnter = node.enter()
.append( g)
.attr('transform','translate('+ source.x0 +','+ source。 y0 +')')
.attr(类,节点)
.on( click,click);
// .on( blur,onNodeBlur);

nodeEnter.append('path')
/ * .attr('d',function(d){
if(d.data.type ==='决策'){
返回'M 60 0 120 30 60 60 0 30 Z';
}否则if(d.data.type ==='action'){
返回'M 0 0 120 0 120 60 0 60 Z';
}否则{
返回'M -100 -10 -10 -10 -10 -10 -10 -10 -10Z';
}
})* /
.attr( stroke-width,1)
.attr('class','myPaths')
.style( fill,function( d){
返回 lightsteelblue;
});

nodeEnter.append( text)
.attr( x,rectW / 2)
.attr( y,rectH / 2)
.attr( dy, .35em)
.attr( text-anchor,中间)
.text(function(d){
return d.data.name;
});

//更新
var nodeUpdate = nodeEnter.merge(node);

//过渡到节点
的正确位置nodeUpdate.transition()
.duration(duration)
.attr( transform,function( d){
return translate( +(dx)+, +(dy)+);
});


nodeUpdate.select(’path.myPaths’)
.attr("d", function(d) {
if (d.data.type === ’decision’) {
return ’M 60 0 120 30 60 60 0 30 Z’;
} else if (d.data.type === ’action’) {
return ’M 0 0 120 0 120 60 0 60 Z’;
} else {
return ’M -100 -10 -10 -10 -10 -10 -10 -10Z’;
}
});


var nodeExit = node.exit()
.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();

// Update the links…
var link = svg.selectAll(".link")
.data(links, function(d) {
return d.data.id;
})
.classed(’link1’, true);


// remove links that are no longer on screen.
link.exit().remove();

//在父级的上一个位置输入任何新链接。
var linkEnter = link.enter()
.insert("g", "g")
.attr("class", "link");

linkEnter.append(’path’)
.on(’click’, function(d, i) {
selectedLink = d;
// Use the native SVG interface to get the bounding box to
// calculate the center of the path

var bbox = this.getBBox();
var x;
var y;

if (d.parent.x < d.x) {
// Child is to the right of the parent
x = bbox.x + bbox.width;
y = bbox.y;
plusButton
.attr(’transform’, ’translate(’ + x + ’, ’ + y + ’)’)
.classed(’hide’, false);

} else {
x = bbox.x;
y = bbox.y;
plusButton
.attr(’transform’, ’translate(’ + x + ’, ’ + y + ’)’)
.classed(’hide’, false);
}
})
.on(’blur’, function(d, i) {
plusButton
.classed(’hide’, true);
})
.attr("marker-end", "url(#end)");

// Add Link Texts.
linkEnter.append(’text’);

// Merge the new and the existing links before setting `d` and `text` on all of them
link = linkEnter.merge(link);

link.select(’path’)
.attr("d", linkFunc);

link.select(’text’)
.text(function(d, i) {
if (d.parent.x < d.x) {
return ’True’;
} else {
return ’False’;
}
})
.attr(’transform’, function(d) {
console.log(d);
if (d.parent.x < d.x && d.data.type) {
console.log("comes in here for source < target");
return ’translate(’ + (d.x + rectW / 2) + ’,’ + (d.parent.y + rectH) + ’)’;
} else if (d.data.type) {
return ’translate(’ + (d.parent.x + rectW / 2) + ’,’ + (d.y + rectH) + ’)’;
} else {
return;
}
});


//LinkUpdate
var linkUpdate = linkEnter.merge(link);

// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", linkFunc);

// Transition links to their new position.

//将退出节点过渡到父级的新位置。
link.exit()
.transition()
.duration(duration)
.attr("d", linkFunc)
.remove();

// Stash the old positions for transition.
个node.forEach(function(d){
d.x0 = d.x;
d.y0 = d.y;
});
}


// ON CLICK OF NODES
function click(d) {

if (d.data.type === ’action’) {
return;
}

selectedNode = d;

if(!(d.data.children && d.data.children[0] && Object.keys(d.data.children[0]).length)){
diamondImageFalse
.attr(’transform’, ’translate(’ + (d.x - 20) + ’, ’ + (d.y + 35) + ’)’)
.classed(’hide’, false);

rectangleShapeFalse.attr(’transform’, ’translate(’ + (d.x - 20) + ’, ’ + d.y + ’)’).classed(’hide’, false);

}

if(!(d.data.children && d.data.children[1] && Object.keys(d.data.children[1]).length)){
diamondImage
.attr(’transform’, ’translate(’ + (d.x + 110) + ’, ’ + (d.y + 35) + ’)’)
.classed(’hide’, false);

rectangleShape.attr(’transform’, ’translate(’ + (d.x + 110) + ’, ’ + d.y + ’)’).classed(’hide’, false);

}
}

// oN CALL

function addElement(d, truthy) {
console.log(d);

d.children = null;
d.children = generateEmptyDecisionBox(truthy);
update(root);
}

// draw elements //

function drawDiamond(centroid) {
// Start at the top
console.log(centroid);
console.log("rectH", rectH, rectW);
// Start at the top
var result = ’M’ + centroid.x + ’,’ + (centroid.y - rectH / 2);

// Go right
result += ’L’ + (centroid.x + rectW / 2) + ’,’ + centroid.y;

// Bottom
result += ’L’ + centroid.x + ’,’ + (centroid.y + rectH / 2);

// Left
result += ’L’ + (centroid.x - rectW / 2) + ’,’ + centroid.y;

// Close the shape
result += ’Z’;

return result;
}

function drawRect(centroid) {
// Start at the top left
console.log(centroid);
var result = ’M’ + (centroid.x - rectW / 2) + ’,’ + (centroid.y - rectH / 2);

// Go right
result += ’h’ + rectW;

// Go down
result += ’v’ + rectH;

// Left
result += ’h-’ + rectW;

// Close the shape
result += ’Z’;

console.log(result);
return result;
}

var plusButton = svg
.append(’g’)
.classed(’button’, true)
.classed(’hide’, true)
.on(’click’, function() {
console.log("hello");
/* addElement(selectedLink.source); */


console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});

plusButton
.append(’rect’)
.attr(’transform’, ’translate(-8, -8)’) // center the button inside the `g`
.attr(’width’, 16)
.attr(’height’, 16)
.attr(’rx’, 2);

plusButton
.append(’path’)
.attr(’d’, ’M-6 0 H6 M0 -6 V6’);

var rectangleShape = svg.append(’g’)
.classed(’conditionImage’, true)
.classed(’hide’, true)
.on(’click’, function() {
// for now modifying it to remove thi node.
// selected Node is saved on click of the decision/rect svg in the click function
removeSelectedNode(selectedNode);
/* addActionOrDecision(selectedNode,’action’,’True’) */;
removeAllButtonElements();
});

rectangleShape
.append(’rect’)
.attr(’width’, 30)
.attr(’height’, 20)
.style(’fill’, ’orange’);


var diamondImage = svg.append(’g’)
.classed(’conditionSvg’, true)
.classed(’hide’, true)
.classed(’scale’, true)
.on(’click’, function() {
addActionOrDecision(selectedNode,’decision’,’True’);
removeAllButtonElements();
});

diamondImage
.append(’path’)
.attr(’d’, ’M 15 0 30 15 15 30 0 15 Z’)
.style("fill", ’orange’);


var rectangleShapeFalse = svg.append(’g’)
.classed(’conditionImage’, true)
.classed(’hide’, true)
.on(’click’, function() {
addActionOrDecision(selectedNode,’action’,’False’);
console.log("rectangle clicked");
removeAllButtonElements();
});

rectangleShapeFalse
.append(’rect’)
.attr(’width’, 30)
.attr(’height’, 20)
.style(’fill’, ’orange’);

var diamondImageFalse = svg.append(’g’)
.classed(’conditionImage’, true)
.classed(’hide’, true)
.classed(’scale’, true)
.on(’click’, function() {
addActionOrDecision(selectedNode,’decision’,’False’);
// addElement(selectedNode, ’False’);
removeAllButtonElements();
});

diamondImageFalse
.append(’path’)
.attr(’d’, ’M 15 0 30 15 15 30 0 15 Z’)
.style("fill", ’orange’);


function removeAllButtonElements() {
plusButton.classed(’hide’, true);
diamondImage.classed(’hide’, true);
rectangleShape.classed(’hide’, true);
diamondImageFalse.classed(’hide’, true);
rectangleShapeFalse.classed(’hide’, true);
}


function addActionOrDecision(selectedNode,nodeType,conditionType){

const parentNodeId = selectedNode.parent.data.id;
const selectedNodeId = selectedNode.data.id;
console.log(parentNodeId,selectedNodeId);

// find the selected node from the actual treeData
const foundRule = getNodeFromNodeId(treeData,selectedNodeId);
const newRuleId = Math.random();

const newNodeToAdd = {
"condition": conditionType,
"name": nodeType === ’decision’? ’New Decision’ : ’New Action’,
"type": nodeType,
"value": "",
"id": newRuleId,
"parent": parentNodeId,
"children": [],
};

const clonedNewNode = {...newNodeToAdd};

if(conditionType === ’False’ && foundRule.children){
// foundRule.children[0] = newNodeToAdd;

foundRule.children.splice(0,1,clonedNewNode);

if(!(foundRule.children[1] && Object.keys(foundRule.children[1]))){
foundRule.children[1] = {};
}

} else {
// foundRule.children[1] = newNodeToAdd;

foundRule.children.splice(1,1,clonedNewNode);

if(!(foundRule.children[0] && Object.keys(foundRule.children[0]))){
founRule.children[0] = {};
}
}

// find the node and add a child to it.
updateTree();

}

function updateTree(){
root = d3.hierarchy(treeData[0], function(d) {
return d.children;
});
root.x0 = height/2;
root.y0 = 0;

update(root);

console.log(treeData);

}

function getNodeFromNodeId(nodes, nodeId){
for (const node of nodes) {
const currNode = node;
if (currNode) {
if (currNode.id === nodeId) {
return currNode;
} else if (currNode.children) {
const childResult = getNodeFromNodeId(currNode.children, nodeId);
if (childResult) {
return childResult;
}
}
}
}
return null;
}

function removeSelectedNode() {
const parentNodeId = selectedNode.parent.data.id;
const selectedNodeId = selectedNode.data.id;

const foundParentNode = getNodeFromNodeId(treeData,parentNodeId);
const foundNode = getNodeFromNodeId(treeData,selectedNodeId);

const foundIndex = foundParentNode.children.findIndex(child => child.id === foundNode.id);
//find the other node and just push that into the children array.


// create a new array and push the index not found into the children array.
let i =0;
let results = [];
let len = foundParentNode.children.length;
for(let i=0; i < len; i++){
if(i !== foundIndex){
results.push(foundParentNode.children[i]);
}
}

foundParentNode.children = results;

updateTree();

}


I am trying to delete a node in a d3 tree by directly modifying the treeData ( Following Mike Bostocks suggestion here for adding/removing nodes :- https://github.com/d3/d3-hierarchy/issues/139 ).

I am able to remove the node, but somehow the link does not go away.

jsFiddle:- http://jsfiddle.net/1sr5tbLx/1/

Steps to reproduce issue in Fiddle:- To remove a node( for now) you would have to click on the node, you will find orange svgs -- > select the right top "rectange" svg. That should delete the node.

On click of this orange rectangle I am calling the method --> removeSelectedNode() ( with the selected node) and it finds the parent node and removes the child index and then I am then recalculating the root details and calling the update tree once again ( Not sure if there is a better way to do this update ).

  var margin = {
    top: 20,
    right: 120,
    bottom: 20,
    left: 120,
  },
  width = 960 - margin.right - margin.left,
  height = 800 - margin.top - margin.bottom;

function generateEmptyDecisionBox(condition) {
  return condition === 'False' ? [{
    "name": "newDecision",
    "id": "newId",
    "type": "decision",
    "value": "notSure",
    "condition": `${condition}`,
  }, {}] : [{}, {
    "name": "newDecision",
    "id": "newId",
    "type": "decision",
    "value": "notSure",
    "condition": `${condition}`,
  }];
}

function generateEmptyActionBox(condition) {
  return condition === 'False' ? [{
    "name": "newAction",
    "id": "newId",
    "type": "action",
    "value": "notSure",
    "condition": `${condition}`,
  }, {}] : [{}, {
    "name": "newAction",
    "id": "newId",
    "type": "action",
    "value": "notSure",
    "condition": `${condition}`,
  }];
}

var selectedNode;
var selectedLink;


var treeData = [{
  "name": "Root",
  "type": "decision",
  "id": "root",
  "children": [{
      "name": "analytics",
      "condition": "False",
      "type": "decision",
      "value": "a+b",
      "id": "child1",
      "children": [{
        "name": "distinction",
        "type": "action",
        "id": "child2",
        "condition": "True",
        "value": "5",
      }, {
        "name": "nonDistinction",
        "type": "action",
        "id": "child3",
        "condition": "False",
        "value": "4",
        "children": [],
      }],
    },
    {
      "condition": "True",
      "name": "division",
      "type": "decision",
      "value": "a-b",
      "id": "child33",
      "children":[],
     },
  ],
}];

var i = 0,
  duration = 1000,
  rectW = 120,
  rectH = 60;

var treeMap = d3.tree()
  .nodeSize([150, 180]);

//LINK FUNCTION TO DRAW LINKS
var linkFunc = function(d) {
  console.log("linkFunc", d);
  var source = {
    x: d.parent.x + rectW / 2,
    y: d.parent.y + (rectH / 2),
  };
  var target = {
    x: d.x + (rectW / 2),
    y: d.y + 3,
  };

  // This is where the line bends
  var inflection = {
    x: target.x,
    y: source.y,
  };
  var radius = 5;

  var result = "M" + source.x + ',' + source.y;

  if (source.x < target.x && d.data.type) {
    // Child is to the right of the parent
    result += ' H' + (inflection.x - radius);
  } else if (d.data.type) {
    result += ' H' + (inflection.x + radius);
  } else {
    return;
  }

  // Curve the line at the bend slightly
  result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);

  result += 'V' + target.y;
  return result;
};

// END OF LINK FUNC //
  
 const zoomSvg = d3.select('.tree-diagram')
      .append('svg')
      .attr('width', width)
      .attr('height', height)
      .append('g');

const svg = zoomSvg.append('g')  
      .attr('transform', 'translate(' + 300 + ',' + 20 + ')');

const attachZoom = d3.select('svg');
    attachZoom.call(d3.zoom().on('zoom',() => {
        zoomSvg.attr('transform',d3.event.transform)
    }))


// ADD ARROW TO THE BOTTOM POINTING TO THE NEXT DECISION.

svg.append("svg:defs")
  .selectAll("marker")
  .data(["end"]) // Different link/path types can be defined here
  .enter()
  .append("svg:marker") // This section adds in the arrows
  .attr("id", String)
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 15)
  .attr("refY", 0.5)
  .attr("markerWidth", 4)
  .attr("markerHeight", 4)
  .attr("orient", "auto")
  .append("svg:path")
  .attr("d", "M0,-5L10,0L0,5");

//necessary so that zoom knows where to zoom and unzoom from
/* zm.translate([350, 20]); */

root = d3.hierarchy(treeData[0], function(d) {
  return d.children;
});
root.x0 = 0;
root.y0 = 0;

update(root);

d3.select(".tree-diagram")
  .style("height", "1000px");

// END OF DRAW TREEE //

function update(source) {
  const treeData = treeMap(root);
  const treeRoot = d3.hierarchy(root);
//  d3.tree(treeRoot);
  // var treeData = treeMap(root);

  // Compute the new tree layout.
  var nodes = treeData.descendants(),
    links = treeData.descendants()
    .slice(1);

  console.log(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) {
    d.y = d.depth * 90;
  });

  // Update the nodes…
  var node = svg.selectAll("g.node")
    .data(nodes, function(d) {
      return d.data.id || (d.id = ++i);
    });

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter()
    .append("g")
    .attr('transform', 'translate(' + source.x0 + ', ' + source.y0 + ')')
    .attr("class", "node")
    .on("click", click);
  //  .on("blur", onNodeBlur);

  nodeEnter.append('path')
/*        .attr('d', function(d) {
        if (d.data.type === 'decision') {
          return 'M 60 0 120 30 60 60 0 30 Z';
        } else if (d.data.type === 'action') {
          return 'M 0 0 120 0 120 60 0 60 Z';
        } else {
          return 'M -100 -10 -10 -10 -10 -10 -10 -10Z';
        }
             }) */
    .attr("stroke-width", 1)
    .attr('class', 'myPaths')
    .style("fill", function(d) {
      return "lightsteelblue";
    });

  nodeEnter.append("text")
    .attr("x", rectW / 2)
    .attr("y", rectH / 2)
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .text(function(d) {
      return d.data.name;
    });

  // UPDATE
  var nodeUpdate = nodeEnter.merge(node);

  // Transition to the proper position for the node
  nodeUpdate.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + (d.x) + "," + (d.y) + ")";
    });


  nodeUpdate.select('path.myPaths')
    .attr("d", function(d) {
      if (d.data.type === 'decision') {
        return 'M 60 0 120 30 60 60 0 30 Z';
      } else if (d.data.type === 'action') {
        return 'M 0 0 120 0 120 60 0 60 Z';
      } else {
        return 'M -100 -10 -10 -10 -10 -10 -10 -10Z';
      }
    });


  var nodeExit = node.exit()
    .transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + source.x + "," + source.y + ")";
    })
    .remove();

  // Update the links…
  var link = svg.selectAll(".link")
    .data(links, function(d) {
      return d.data.id;
    })
    .classed('link1', true);


  // Enter any new links at the parent's previous position.
  var linkEnter = link.enter()
    .insert("g", "g")
    .attr("class", "link");

  linkEnter.append('path')
    .on('click', function(d, i) {
      selectedLink = d;
      // Use the native SVG interface to get the bounding box to
      // calculate the center of the path

      var bbox = this.getBBox();
      var x;
      var y;

      if (d.parent.x < d.x) {
        // Child is to the right of the parent
        x = bbox.x + bbox.width;
        y = bbox.y;
        plusButton
          .attr('transform', 'translate(' + x + ', ' + y + ')')
          .classed('hide', false);

      } else {
        x = bbox.x;
        y = bbox.y;
        plusButton
          .attr('transform', 'translate(' + x + ', ' + y + ')')
          .classed('hide', false);
      }
    })
    .on('blur', function(d, i) {
      plusButton
        .classed('hide', true);
    })
    .attr("marker-end", "url(#end)");

  // Add Link Texts.
  linkEnter.append('text');

  // Merge the new and the existing links before setting `d` and `text` on all of them
  link = linkEnter.merge(link);

  link.select('path')
    .attr("d", linkFunc);

  link.select('text')
    .text(function(d, i) {
      if (d.parent.x < d.x) {
        return 'True';
      } else {
        return 'False';
      }
    })
    .attr('transform', function(d) {
      console.log(d);
      if (d.parent.x < d.x && d.data.type) {
        console.log("comes in here for source < target");
        return 'translate(' + (d.x + rectW / 2) + ',' + (d.parent.y + rectH) + ')';
      } else if (d.data.type) {
        return 'translate(' + (d.parent.x + rectW / 2) + ',' + (d.y + rectH) + ')';
      } else {
        return;
      }
    });


  //LinkUpdate
  var linkUpdate = linkEnter.merge(link);

  // Transition links to their new position.
  link.transition()
    .duration(duration)
    .attr("d", linkFunc);

  // Transition links to their new position.

  // Transition exiting nodes to the parent's new position.
  link.exit()
    .transition()
    .duration(duration)
    .attr("d", linkFunc)
    .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}


// ON CLICK OF NODES
function click(d) {

  if (d.data.type === 'action') {
    return;
  }

  selectedNode = d;
    
        if(!(d.data.children && d.data.children[0] && Object.keys(d.data.children[0]).length)){
     diamondImageFalse
      .attr('transform', 'translate(' + (d.x - 20) + ', ' + (d.y + 35) + ')')
      .classed('hide', false);

    rectangleShapeFalse.attr('transform', 'translate(' + (d.x - 20) + ', ' + d.y + ')').classed('hide', false);

    }

    if(!(d.data.children && d.data.children[1] && Object.keys(d.data.children[1]).length)){
    diamondImage
      .attr('transform', 'translate(' + (d.x + 110) + ', ' + (d.y + 35) + ')')
      .classed('hide', false);

    rectangleShape.attr('transform', 'translate(' + (d.x + 110) + ', ' + d.y + ')').classed('hide', false);

    }
}

// oN CALL

function addElement(d, truthy) {
  console.log(d);

  d.children = null;
  d.children = generateEmptyDecisionBox(truthy);
  update(root);
}

// draw elements //

function drawDiamond(centroid) {
  // Start at the top
  console.log(centroid);
  console.log("rectH", rectH, rectW);
  // Start at the top
  var result = 'M' + centroid.x + ',' + (centroid.y - rectH / 2);

  // Go right
  result += 'L' + (centroid.x + rectW / 2) + ',' + centroid.y;

  // Bottom
  result += 'L' + centroid.x + ',' + (centroid.y + rectH / 2);

  // Left
  result += 'L' + (centroid.x - rectW / 2) + ',' + centroid.y;

  // Close the shape
  result += 'Z';

  return result;
}

function drawRect(centroid) {
  // Start at the top left
  console.log(centroid);
  var result = 'M' + (centroid.x - rectW / 2) + ',' + (centroid.y - rectH / 2);

  // Go right
  result += 'h' + rectW;

  // Go down
  result += 'v' + rectH;

  // Left
  result += 'h-' + rectW;

  // Close the shape
  result += 'Z';

  console.log(result);
  return result;
}

var plusButton = svg
  .append('g')
  .classed('button', true)
  .classed('hide', true)
  .on('click', function() {
    console.log("hello");
    /*        addElement(selectedLink.source); */
    
    
    console.log("Clicked on Diamond");
    console.log("set hide to true");
    removeAllButtonElements();
  });

plusButton
  .append('rect')
  .attr('transform', 'translate(-8, -8)') // center the button inside the `g`
  .attr('width', 16)
  .attr('height', 16)
  .attr('rx', 2);

plusButton
  .append('path')
  .attr('d', 'M-6 0 H6 M0 -6 V6');

var rectangleShape = svg.append('g')
  .classed('conditionImage', true)
  .classed('hide', true)
  .on('click', function() {
  // for now modifying it to remove thi node.
  // selected Node is saved on click of the decision/rect svg in the click function
     removeSelectedNode(selectedNode);
     /* addActionOrDecision(selectedNode,'action','True') */;
    removeAllButtonElements();
  });

rectangleShape
  .append('rect')
  .attr('width', 30)
  .attr('height', 20)
  .style('fill', 'orange');


var diamondImage = svg.append('g')
  .classed('conditionSvg', true)
  .classed('hide', true)
  .classed('scale', true)
  .on('click', function() {
    addActionOrDecision(selectedNode,'decision','True');
    removeAllButtonElements();
  });

diamondImage
  .append('path')
  .attr('d', 'M 15 0 30 15 15 30 0 15 Z')
  .style("fill", 'orange');


var rectangleShapeFalse = svg.append('g')
  .classed('conditionImage', true)
  .classed('hide', true)
  .on('click', function() {
     addActionOrDecision(selectedNode,'action','False');
    console.log("rectangle clicked");
    removeAllButtonElements();
  });

rectangleShapeFalse
  .append('rect')
  .attr('width', 30)
  .attr('height', 20)
  .style('fill', 'orange');

var diamondImageFalse = svg.append('g')
  .classed('conditionImage', true)
  .classed('hide', true)
  .classed('scale', true)
  .on('click', function() {
   addActionOrDecision(selectedNode,'decision','False');
   //  addElement(selectedNode, 'False');
    removeAllButtonElements();
  });

diamondImageFalse
  .append('path')
  .attr('d', 'M 15 0 30 15 15 30 0 15 Z')
  .style("fill", 'orange');


function removeAllButtonElements() {
  plusButton.classed('hide', true);
  diamondImage.classed('hide', true);
  rectangleShape.classed('hide', true);
  diamondImageFalse.classed('hide', true);
  rectangleShapeFalse.classed('hide', true);
}


function addActionOrDecision(selectedNode,nodeType,conditionType){  

    const parentNodeId = selectedNode.parent.data.id;
    const selectedNodeId = selectedNode.data.id;
    console.log(parentNodeId,selectedNodeId);

// find the selected node from the actual treeData
    const foundRule = getNodeFromNodeId(treeData,selectedNodeId);
    const newRuleId = Math.random();

    const newNodeToAdd = {
      "condition": conditionType,
      "name": nodeType === 'decision'? 'New Decision' : 'New Action',
      "type": nodeType,
      "value": "",
      "id": newRuleId,
      "parent": parentNodeId,
      "children": [],
    };
    
    const clonedNewNode = {...newNodeToAdd};

    if(conditionType === 'False' && foundRule.children){
  //    foundRule.children[0] = newNodeToAdd;
      
      foundRule.children.splice(0,1,clonedNewNode);
      
     if(!(foundRule.children[1] && Object.keys(foundRule.children[1]))){
                foundRule.children[1] = {};
     }
            
      } else {
   //   foundRule.children[1] = newNodeToAdd;
   
         foundRule.children.splice(1,1,clonedNewNode);
   
      if(!(foundRule.children[0] && Object.keys(foundRule.children[0]))){
                founRule.children[0] = {};
     }
    }

    // find the node and add a child to it. 
        updateTree();
 
  }
  
 function updateTree(){
    root = d3.hierarchy(treeData[0], function(d) {
      return d.children;
    });
    root.x0 = height/2;
    root.y0 = 0;

    update(root);
    
    console.log(treeData);

  }
  
function getNodeFromNodeId(nodes, nodeId){
    for (const node of nodes) {
      const currNode = node;
      if (currNode) {
        if (currNode.id === nodeId) {
          return currNode;
        } else if (currNode.children) {
          const childResult = getNodeFromNodeId(currNode.children, nodeId);
          if (childResult) {
            return childResult;
          }
        }
      }
    }
    return null;
  }
  
function removeSelectedNode() {
   const parentNodeId = selectedNode.parent.data.id;
   const selectedNodeId = selectedNode.data.id;
   
   const foundParentNode = getNodeFromNodeId(treeData,parentNodeId);
   const foundNode = getNodeFromNodeId(treeData,selectedNodeId);

 const foundIndex = foundParentNode.children.findIndex(child => child.id === foundNode.id);
   //find the other node and just push that into the children array. 


 // create a new array and push the index not found into the children array.
  let i =0;
  let results = [];
  let len = foundParentNode.children.length;
   for(let i=0; i < len; i++){
     if(i !== foundIndex){
       results.push(foundParentNode.children[i]);
     }
   }

  foundParentNode.children = results;

    updateTree();

}

css:-

.node {
  cursor: pointer;
   outline: none !important;  
}

.node text {
  font: 10px sans-serif;
} 

 .button>path {
  stroke: blue;
  stroke-width: 1.5;
 /*   outline: none;  */
} 

 .button>rect {
  fill: #ddd;
  stroke: grey;
  stroke-width: 1px;
}  

 .conditionalSvg{
 /*   outline: none; */
   display: none; 
} 

.hide {
/*    display: none;  */
   opacity: 0 !important; 
   /*  pointer-events: none;  */ 
}

.link:hover {
  outline: none !important;
   cursor: pointer;
   stroke-width: 3px; 
}

 .link path{
/*   outline: none !important;  */
  fill: none; 
  stroke: darkgray;
  stroke-width: 2px;
} 

.link path:hover{
   cursor: pointer;
   stroke-width: 4px; 
}

.link text{
    font: 10px sans-serif;
}

.colorBlue{
  background-color: blue;
}

解决方案

Apparently , I had to remove links that are not on the screen using :-

link.exit().remove();

Here is the updated jsfiddle :- http://jsfiddle.net/awymjbon/

var margin = {
    top: 20,
    right: 120,
    bottom: 20,
    left: 120,
  },
  width = 960 - margin.right - margin.left,
  height = 800 - margin.top - margin.bottom;

function generateEmptyDecisionBox(condition) {
  return condition === 'False' ? [{
    "name": "newDecision",
    "id": "newId",
    "type": "decision",
    "value": "notSure",
    "condition": `${condition}`,
  }, {}] : [{}, {
    "name": "newDecision",
    "id": "newId",
    "type": "decision",
    "value": "notSure",
    "condition": `${condition}`,
  }];
}

function generateEmptyActionBox(condition) {
  return condition === 'False' ? [{
    "name": "newAction",
    "id": "newId",
    "type": "action",
    "value": "notSure",
    "condition": `${condition}`,
  }, {}] : [{}, {
    "name": "newAction",
    "id": "newId",
    "type": "action",
    "value": "notSure",
    "condition": `${condition}`,
  }];
}

var selectedNode;
var selectedLink;


var treeData = [{
  "name": "Root",
  "type": "decision",
  "id": "root",
  "children": [{
      "name": "analytics",
      "condition": "False",
      "type": "decision",
      "value": "a+b",
      "id": "child1",
      "children": [{
        "name": "distinction",
        "type": "action",
        "id": "child2",
        "condition": "True",
        "value": "5",
      }, {
        "name": "nonDistinction",
        "type": "action",
        "id": "child3",
        "condition": "False",
        "value": "4",
        "children": [],
      }],
    },
    {
      "condition": "True",
      "name": "division",
      "type": "decision",
      "value": "a-b",
      "id": "child33",
      "children":[],
     },
  ],
}];

var i = 0,
  duration = 1000,
  rectW = 120,
  rectH = 60;

var treeMap = d3.tree()
  .nodeSize([150, 180]);

//LINK FUNCTION TO DRAW LINKS
var linkFunc = function(d) {
  console.log("linkFunc", d);
  var source = {
    x: d.parent.x + rectW / 2,
    y: d.parent.y + (rectH / 2),
  };
  var target = {
    x: d.x + (rectW / 2),
    y: d.y + 3,
  };

  // This is where the line bends
  var inflection = {
    x: target.x,
    y: source.y,
  };
  var radius = 5;

  var result = "M" + source.x + ',' + source.y;

  if (source.x < target.x && d.data.type) {
    // Child is to the right of the parent
    result += ' H' + (inflection.x - radius);
  } else if (d.data.type) {
    result += ' H' + (inflection.x + radius);
  } else {
    return;
  }

  // Curve the line at the bend slightly
  result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);

  result += 'V' + target.y;
  return result;
};

// END OF LINK FUNC //
  
 const zoomSvg = d3.select('.tree-diagram')
      .append('svg')
      .attr('width', width)
      .attr('height', height)
      .append('g');

const svg = zoomSvg.append('g')  
      .attr('transform', 'translate(' + 300 + ',' + 20 + ')');

const attachZoom = d3.select('svg');
    attachZoom.call(d3.zoom().on('zoom',() => {
        zoomSvg.attr('transform',d3.event.transform)
    }))


// ADD ARROW TO THE BOTTOM POINTING TO THE NEXT DECISION.

svg.append("svg:defs")
  .selectAll("marker")
  .data(["end"]) // Different link/path types can be defined here
  .enter()
  .append("svg:marker") // This section adds in the arrows
  .attr("id", String)
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 15)
  .attr("refY", 0.5)
  .attr("markerWidth", 4)
  .attr("markerHeight", 4)
  .attr("orient", "auto")
  .append("svg:path")
  .attr("d", "M0,-5L10,0L0,5");

//necessary so that zoom knows where to zoom and unzoom from
/* zm.translate([350, 20]); */

root = d3.hierarchy(treeData[0], function(d) {
  return d.children;
});
root.x0 = 0;
root.y0 = 0;

update(root);

d3.select(".tree-diagram")
  .style("height", "1000px");

// END OF DRAW TREEE //

function update(source) {
  const treeData = treeMap(root);
  const treeRoot = d3.hierarchy(root);
//  d3.tree(treeRoot);
  // var treeData = treeMap(root);

  // Compute the new tree layout.
  var nodes = treeData.descendants(),
    links = treeData.descendants()
    .slice(1);

  console.log(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) {
    d.y = d.depth * 90;
  });

  // Update the nodes…
  var node = svg.selectAll("g.node")
    .data(nodes, function(d) {
      return d.data.id || (d.id = ++i);
    });

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter()
    .append("g")
    .attr('transform', 'translate(' + source.x0 + ', ' + source.y0 + ')')
    .attr("class", "node")
    .on("click", click);
  //  .on("blur", onNodeBlur);

  nodeEnter.append('path')
/*        .attr('d', function(d) {
        if (d.data.type === 'decision') {
          return 'M 60 0 120 30 60 60 0 30 Z';
        } else if (d.data.type === 'action') {
          return 'M 0 0 120 0 120 60 0 60 Z';
        } else {
          return 'M -100 -10 -10 -10 -10 -10 -10 -10Z';
        }
             }) */
    .attr("stroke-width", 1)
    .attr('class', 'myPaths')
    .style("fill", function(d) {
      return "lightsteelblue";
    });

  nodeEnter.append("text")
    .attr("x", rectW / 2)
    .attr("y", rectH / 2)
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .text(function(d) {
      return d.data.name;
    });

  // UPDATE
  var nodeUpdate = nodeEnter.merge(node);

  // Transition to the proper position for the node
  nodeUpdate.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + (d.x) + "," + (d.y) + ")";
    });


  nodeUpdate.select('path.myPaths')
    .attr("d", function(d) {
      if (d.data.type === 'decision') {
        return 'M 60 0 120 30 60 60 0 30 Z';
      } else if (d.data.type === 'action') {
        return 'M 0 0 120 0 120 60 0 60 Z';
      } else {
        return 'M -100 -10 -10 -10 -10 -10 -10 -10Z';
      }
    });


  var nodeExit = node.exit()
    .transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + source.x + "," + source.y + ")";
    })
    .remove();

  // Update the links…
  var link = svg.selectAll(".link")
    .data(links, function(d) {
      return d.data.id;
    })
    .classed('link1', true);


// remove links that are no longer on screen.
     link.exit().remove();

  // Enter any new links at the parent's previous position.
  var linkEnter = link.enter()
    .insert("g", "g")
    .attr("class", "link");

  linkEnter.append('path')
    .on('click', function(d, i) {
      selectedLink = d;
      // Use the native SVG interface to get the bounding box to
      // calculate the center of the path

      var bbox = this.getBBox();
      var x;
      var y;

      if (d.parent.x < d.x) {
        // Child is to the right of the parent
        x = bbox.x + bbox.width;
        y = bbox.y;
        plusButton
          .attr('transform', 'translate(' + x + ', ' + y + ')')
          .classed('hide', false);

      } else {
        x = bbox.x;
        y = bbox.y;
        plusButton
          .attr('transform', 'translate(' + x + ', ' + y + ')')
          .classed('hide', false);
      }
    })
    .on('blur', function(d, i) {
      plusButton
        .classed('hide', true);
    })
    .attr("marker-end", "url(#end)");

  // Add Link Texts.
  linkEnter.append('text');

  // Merge the new and the existing links before setting `d` and `text` on all of them
  link = linkEnter.merge(link);

  link.select('path')
    .attr("d", linkFunc);

  link.select('text')
    .text(function(d, i) {
      if (d.parent.x < d.x) {
        return 'True';
      } else {
        return 'False';
      }
    })
    .attr('transform', function(d) {
      console.log(d);
      if (d.parent.x < d.x && d.data.type) {
        console.log("comes in here for source < target");
        return 'translate(' + (d.x + rectW / 2) + ',' + (d.parent.y + rectH) + ')';
      } else if (d.data.type) {
        return 'translate(' + (d.parent.x + rectW / 2) + ',' + (d.y + rectH) + ')';
      } else {
        return;
      }
    });


  //LinkUpdate
  var linkUpdate = linkEnter.merge(link);

  // Transition links to their new position.
  link.transition()
    .duration(duration)
    .attr("d", linkFunc);

  // Transition links to their new position.

  // Transition exiting nodes to the parent's new position.
  link.exit()
    .transition()
    .duration(duration)
    .attr("d", linkFunc)
    .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}


// ON CLICK OF NODES
function click(d) {

  if (d.data.type === 'action') {
    return;
  }

  selectedNode = d;
    
        if(!(d.data.children && d.data.children[0] && Object.keys(d.data.children[0]).length)){
     diamondImageFalse
      .attr('transform', 'translate(' + (d.x - 20) + ', ' + (d.y + 35) + ')')
      .classed('hide', false);

    rectangleShapeFalse.attr('transform', 'translate(' + (d.x - 20) + ', ' + d.y + ')').classed('hide', false);

    }

    if(!(d.data.children && d.data.children[1] && Object.keys(d.data.children[1]).length)){
    diamondImage
      .attr('transform', 'translate(' + (d.x + 110) + ', ' + (d.y + 35) + ')')
      .classed('hide', false);

    rectangleShape.attr('transform', 'translate(' + (d.x + 110) + ', ' + d.y + ')').classed('hide', false);

    }
}

// oN CALL

function addElement(d, truthy) {
  console.log(d);

  d.children = null;
  d.children = generateEmptyDecisionBox(truthy);
  update(root);
}

// draw elements //

function drawDiamond(centroid) {
  // Start at the top
  console.log(centroid);
  console.log("rectH", rectH, rectW);
  // Start at the top
  var result = 'M' + centroid.x + ',' + (centroid.y - rectH / 2);

  // Go right
  result += 'L' + (centroid.x + rectW / 2) + ',' + centroid.y;

  // Bottom
  result += 'L' + centroid.x + ',' + (centroid.y + rectH / 2);

  // Left
  result += 'L' + (centroid.x - rectW / 2) + ',' + centroid.y;

  // Close the shape
  result += 'Z';

  return result;
}

function drawRect(centroid) {
  // Start at the top left
  console.log(centroid);
  var result = 'M' + (centroid.x - rectW / 2) + ',' + (centroid.y - rectH / 2);

  // Go right
  result += 'h' + rectW;

  // Go down
  result += 'v' + rectH;

  // Left
  result += 'h-' + rectW;

  // Close the shape
  result += 'Z';

  console.log(result);
  return result;
}

var plusButton = svg
  .append('g')
  .classed('button', true)
  .classed('hide', true)
  .on('click', function() {
    console.log("hello");
    /*        addElement(selectedLink.source); */
    
    
    console.log("Clicked on Diamond");
    console.log("set hide to true");
    removeAllButtonElements();
  });

plusButton
  .append('rect')
  .attr('transform', 'translate(-8, -8)') // center the button inside the `g`
  .attr('width', 16)
  .attr('height', 16)
  .attr('rx', 2);

plusButton
  .append('path')
  .attr('d', 'M-6 0 H6 M0 -6 V6');

var rectangleShape = svg.append('g')
  .classed('conditionImage', true)
  .classed('hide', true)
  .on('click', function() {
  // for now modifying it to remove thi node.
  // selected Node is saved on click of the decision/rect svg in the click function
     removeSelectedNode(selectedNode);
     /* addActionOrDecision(selectedNode,'action','True') */;
    removeAllButtonElements();
  });

rectangleShape
  .append('rect')
  .attr('width', 30)
  .attr('height', 20)
  .style('fill', 'orange');


var diamondImage = svg.append('g')
  .classed('conditionSvg', true)
  .classed('hide', true)
  .classed('scale', true)
  .on('click', function() {
    addActionOrDecision(selectedNode,'decision','True');
    removeAllButtonElements();
  });

diamondImage
  .append('path')
  .attr('d', 'M 15 0 30 15 15 30 0 15 Z')
  .style("fill", 'orange');


var rectangleShapeFalse = svg.append('g')
  .classed('conditionImage', true)
  .classed('hide', true)
  .on('click', function() {
     addActionOrDecision(selectedNode,'action','False');
    console.log("rectangle clicked");
    removeAllButtonElements();
  });

rectangleShapeFalse
  .append('rect')
  .attr('width', 30)
  .attr('height', 20)
  .style('fill', 'orange');

var diamondImageFalse = svg.append('g')
  .classed('conditionImage', true)
  .classed('hide', true)
  .classed('scale', true)
  .on('click', function() {
   addActionOrDecision(selectedNode,'decision','False');
   //  addElement(selectedNode, 'False');
    removeAllButtonElements();
  });

diamondImageFalse
  .append('path')
  .attr('d', 'M 15 0 30 15 15 30 0 15 Z')
  .style("fill", 'orange');


function removeAllButtonElements() {
  plusButton.classed('hide', true);
  diamondImage.classed('hide', true);
  rectangleShape.classed('hide', true);
  diamondImageFalse.classed('hide', true);
  rectangleShapeFalse.classed('hide', true);
}


function addActionOrDecision(selectedNode,nodeType,conditionType){  

    const parentNodeId = selectedNode.parent.data.id;
    const selectedNodeId = selectedNode.data.id;
    console.log(parentNodeId,selectedNodeId);

// find the selected node from the actual treeData
    const foundRule = getNodeFromNodeId(treeData,selectedNodeId);
    const newRuleId = Math.random();

    const newNodeToAdd = {
      "condition": conditionType,
      "name": nodeType === 'decision'? 'New Decision' : 'New Action',
      "type": nodeType,
      "value": "",
      "id": newRuleId,
      "parent": parentNodeId,
      "children": [],
    };
    
    const clonedNewNode = {...newNodeToAdd};

    if(conditionType === 'False' && foundRule.children){
  //    foundRule.children[0] = newNodeToAdd;
      
      foundRule.children.splice(0,1,clonedNewNode);
      
     if(!(foundRule.children[1] && Object.keys(foundRule.children[1]))){
                foundRule.children[1] = {};
     }
            
      } else {
   //   foundRule.children[1] = newNodeToAdd;
   
         foundRule.children.splice(1,1,clonedNewNode);
   
      if(!(foundRule.children[0] && Object.keys(foundRule.children[0]))){
                founRule.children[0] = {};
     }
    }

    // find the node and add a child to it. 
        updateTree();
 
  }
  
 function updateTree(){
    root = d3.hierarchy(treeData[0], function(d) {
      return d.children;
    });
    root.x0 = height/2;
    root.y0 = 0;

    update(root);
    
    console.log(treeData);

  }
  
function getNodeFromNodeId(nodes, nodeId){
    for (const node of nodes) {
      const currNode = node;
      if (currNode) {
        if (currNode.id === nodeId) {
          return currNode;
        } else if (currNode.children) {
          const childResult = getNodeFromNodeId(currNode.children, nodeId);
          if (childResult) {
            return childResult;
          }
        }
      }
    }
    return null;
  }
  
function removeSelectedNode() {
   const parentNodeId = selectedNode.parent.data.id;
   const selectedNodeId = selectedNode.data.id;
   
   const foundParentNode = getNodeFromNodeId(treeData,parentNodeId);
   const foundNode = getNodeFromNodeId(treeData,selectedNodeId);

 const foundIndex = foundParentNode.children.findIndex(child => child.id === foundNode.id);
   //find the other node and just push that into the children array. 


 // create a new array and push the index not found into the children array.
  let i =0;
  let results = [];
  let len = foundParentNode.children.length;
   for(let i=0; i < len; i++){
     if(i !== foundIndex){
       results.push(foundParentNode.children[i]);
     }
   }

  foundParentNode.children = results;

    updateTree();

}

这篇关于删除节点后未删除D3.js链接的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆