删除节点后未删除D3.js链接 [英] D3.js link not removed after deleting node
问题描述
我试图通过直接修改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屋!