D3.js v5 - Swarm Tree -如何迭代地将集群集中在树节点周围



任务是在树的节点周围附加一群小圆圈(我在那里放置了更大的圆圈)。树的深度很小——只有两个,然而,当考虑到我迭代地追加几个树时,就会有轻微的复杂性。

为了简单起见,我甚至没有尝试将蜂群附加到所有的树上。相反,我为自己设定了一个更温和的目标,即将蜂群附加到一棵树的一个节点上。下面的注释和代码片段:

var margins = {top:100, bottom:300, left:100, right:100};
var height = 600;
var width = 900;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");
var data = [
{name:"China", tree: {
"name": "Actors",
"children": [
{
"name": "Jackie Chan",
"movies":d3.range(50).map(function(v) {return v.toString()+'_movie'})
},
{
"name": "Jet Li",
"movies":d3.range(38).map(function(v) {return v.toString()+'_movie'})
},
{
"name": "Chow Yun Fat",
"movies":d3.range(46).map(function(v) {return v.toString()+'_movie'})
}
]
}},
{name:"USA", tree: {
"name": "Actors",
"children": [
{
"name": "Tom Cruise",
"movies":d3.range(7).map(function(v) {return v.toString()+'_movie'})
},
{
"name": "Johnny Depp",
"movies":d3.range(4).map(function(v) {return v.toString()+'_movie'})
},
{
"name": "Harrison Ford",
"movies":d3.range(20).map(function(v) {return v.toString()+'_movie'})
},
{
"name": "Ryan Gosling",
"movies":d3.range(4).map(function(v) {return v.toString()+'_movie'})
}
]
}},
{name:"UK", tree: {
"name": "Actors",
"children": [
{
"name": "Daniel Day-Lewis",
"movies":d3.range(36).map(function(v) {return v.toString()+'_movie'})
},
{
"name": "Christoper Lee",
"movies":d3.range(50).map(function(v) {return v.toString()+'_movie'})
},
]
}}
];
//var formatComma = d3.format(",");
var columns = 3;
var spacing = 200;
var vSpacing = 180;
var regionG = graphGroup.selectAll('.region')
.data(data)
.enter()
.append('g')
.attr('class', 'region')
.attr('id', (d, i) => 'region' + i)
.attr('transform', (d, k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate(" + horSpace + "," + vertSpace + ")";
});
var miniTree = d3.tree()
.size([150, 75]);
regionG.append('rect')
.attr('x',0)
.attr('y',0)
.attr('width',100)
.attr('height',25)
.style('fill',"#003366");
regionG.append('text')
.attr('x',50)
.attr('y',-10)
.attr('text-anchor','middle')
.text(function(d) {return d.name});

regionG.selectAll(null)
.data( function(d) {
return miniTree(d3.hierarchy(d.tree)).descendants().slice(1)
})
.enter().append("path")
.attr("transform", "translate(-25,20)") // extra positioning.
.attr("class", "link")
.attr("d", function(d) {
return "M" + d.x + "," + d.y
+ "C" + d.x + "," + (d.y + d.parent.y) / 2
+ " " + d.parent.x + "," +  (d.y + d.parent.y) / 2
+ " " + d.parent.x + "," + d.parent.y;
});
regionG.selectAll(null)
.data( function(d) {return miniTree(d3.hierarchy(d.tree)).descendants() })
.enter().append("g")
.attr("class", function(d) {
return "node" +
(d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) {
return "translate(" + (d.x - 25) + "," + (d.y+20) + ")"; }) // with extra positioning.
.append("circle")
.attr('r',10)
.attr('cx',0)
.attr('cy',0)
.style('fill',"#003366");

//start simulation for one node
var simulation = d3.forceSimulation(data[0].tree.children[0].movies)
.force("x", d3.forceX(function(d) {
return 0;
}).strength(0.03))
.force("y", d3.forceY(function(d) {
return 95;
}).strength(0.1))
.force("collide", d3.forceCollide(04).iterations(1))
.stop();
simulation.tick(75);
//append circles at (0,95) -- the center coorinates of the bottom left tree node
var circles = graphGroup.selectAll(null)
.data(data[0].tree.children[0].movies)
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d) { return d.x;})
.attr("cy", function(d) { return d.y;})
.style('fill', function(d) {return "#4f81b9"; });
<script src="https://d3js.org/d3.v5.min.js"></script>

这给了我一大堆错误,我在调试时遇到了麻烦:

无法在字符串"0_movie"上创建属性"vx";…不能创建财产'vx' "1_movie"…

期望的结果应该是节点周围附加的圆圈。

背景:

  • 顶部为区域:China
  • 树节点上的大圆圈表示演员:成龙
  • 较小的群圈表示他/她在
  • 中出演的电影数量

最终结果将在每个节点周围有群圈,但出于本问题的目的,我们可以只关注左下角的节点。

我如何在树的最底部深度的每个节点上附加集群?

错误相对简单:您的节点不是对象。传递给力模拟的节点是字符串:

d3.range(50).map(function(v) {return v.toString()+'_movie'})

d3力模拟需要一个对象来存储每个节点的x,y,dx和dy属性。解决方案是返回一个对象而不是字符串:

d3.range(50).map(function(v) {return {name: v.toString()+'_movie' } })

这将按预期绘制集群并且没有错误:

var margins = {top:100, bottom:300, left:100, right:100};
var height = 600;
var width = 900;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");
var data = [
{name:"China", tree: {
"name": "Actors",
"children": [
{
"name": "Jackie Chan",
"movies":d3.range(50).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Jet Li",
"movies":d3.range(38).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Chow Yun Fat",
"movies":d3.range(46).map(function(v) {return {name: v.toString()+'_movie'} })
}
]
}},
{name:"USA", tree: {
"name": "Actors",
"children": [
{
"name": "Tom Cruise",
"movies":d3.range(7).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Johnny Depp",
"movies":d3.range(4).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Harrison Ford",
"movies":d3.range(20).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Ryan Gosling",
"movies":d3.range(4).map(function(v) {return {name: v.toString()+'_movie'} })
}
]
}},
{name:"UK", tree: {
"name": "Actors",
"children": [
{
"name": "Daniel Day-Lewis",
"movies":d3.range(36).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Christoper Lee",
"movies":d3.range(50).map(function(v) {return {name: v.toString()+'_movie'} })
},
]
}}
];
//var formatComma = d3.format(",");
var columns = 3;
var spacing = 200;
var vSpacing = 180;
var regionG = graphGroup.selectAll('.region')
.data(data)
.enter()
.append('g')
.attr('class', 'region')
.attr('id', (d, i) => 'region' + i)
.attr('transform', (d, k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate(" + horSpace + "," + vertSpace + ")";
});
var miniTree = d3.tree()
.size([150, 75]);
regionG.append('rect')
.attr('x',0)
.attr('y',0)
.attr('width',100)
.attr('height',25)
.style('fill',"#003366");
regionG.append('text')
.attr('x',50)
.attr('y',-10)
.attr('text-anchor','middle')
.text(function(d) {return d.name});

regionG.selectAll(null)
.data( function(d) {
return miniTree(d3.hierarchy(d.tree)).descendants().slice(1)
})
.enter().append("path")
.attr("transform", "translate(-25,20)") // extra positioning.
.attr("class", "link")
.attr("d", function(d) {
return "M" + d.x + "," + d.y
+ "C" + d.x + "," + (d.y + d.parent.y) / 2
+ " " + d.parent.x + "," +  (d.y + d.parent.y) / 2
+ " " + d.parent.x + "," + d.parent.y;
});
regionG.selectAll(null)
.data( function(d) {return miniTree(d3.hierarchy(d.tree)).descendants() })
.enter().append("g")
.attr("class", function(d) {
return "node" +
(d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) {
return "translate(" + (d.x - 25) + "," + (d.y+20) + ")"; }) // with extra positioning.
.append("circle")
.attr('r',10)
.attr('cx',0)
.attr('cy',0)
.style('fill',"#003366");

//start simulation for one node
var simulation = d3.forceSimulation(data[0].tree.children[0].movies)
.force("x", d3.forceX(function(d) {
return 0;
}).strength(0.03))
.force("y", d3.forceY(function(d) {
return 95;
}).strength(0.1))
.force("collide", d3.forceCollide(04).iterations(1))
.stop();
simulation.tick(75);
//append circles at (0,95) -- the center coorinates of the bottom left tree node
var circles = graphGroup.selectAll(null)
.data(data[0].tree.children[0].movies)
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d) { return d.x;})
.attr("cy", function(d) { return d.y;})
.style('fill', function(d) {return "#4f81b9"; });
<script src="https://d3js.org/d3.v5.min.js"></script>

稍微调整一下,我们可以为每个节点绘制一个集群(这些是独立的集群):

var margins = {top:100, bottom:300, left:100, right:100};
var height = 600;
var width = 900;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");
var data = [
{name:"China", tree: {
"name": "Actors",
"children": [
{
"name": "Jackie Chan",
"movies":d3.range(50).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Jet Li",
"movies":d3.range(38).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Chow Yun Fat",
"movies":d3.range(46).map(function(v) {return {name: v.toString()+'_movie'} })
}
]
}},
{name:"USA", tree: {
"name": "Actors",
"children": [
{
"name": "Tom Cruise",
"movies":d3.range(7).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Johnny Depp",
"movies":d3.range(4).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Harrison Ford",
"movies":d3.range(20).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Ryan Gosling",
"movies":d3.range(4).map(function(v) {return {name: v.toString()+'_movie'} })
}
]
}},
{name:"UK", tree: {
"name": "Actors",
"children": [
{
"name": "Daniel Day-Lewis",
"movies":d3.range(36).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Christoper Lee",
"movies":d3.range(50).map(function(v) {return {name: v.toString()+'_movie'} })
},
]
}}
];
//var formatComma = d3.format(",");
var columns = 3;
var spacing = 200;
var vSpacing = 180;
var regionG = graphGroup.selectAll('.region')
.data(data)
.enter()
.append('g')
.attr('class', 'region')
.attr('id', (d, i) => 'region' + i)
.attr('transform', (d, k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate(" + horSpace + "," + vertSpace + ")";
});
var miniTree = d3.tree()
.size([150, 75]);
regionG.append('rect')
.attr('x',0)
.attr('y',0)
.attr('width',100)
.attr('height',25)
.style('fill',"#003366");
regionG.append('text')
.attr('x',50)
.attr('y',-10)
.attr('text-anchor','middle')
.text(function(d) {return d.name});

regionG.selectAll(null)
.data( function(d) {
return miniTree(d3.hierarchy(d.tree)).descendants().slice(1)
})
.enter().append("path")
.attr("transform", "translate(-25,20)") // extra positioning.
.attr("class", "link")
.attr("d", function(d) {
return "M" + d.x + "," + d.y
+ "C" + d.x + "," + (d.y + d.parent.y) / 2
+ " " + d.parent.x + "," +  (d.y + d.parent.y) / 2
+ " " + d.parent.x + "," + d.parent.y;
});
////////
var g = regionG.selectAll(null)
.data( function(d) {return miniTree(d3.hierarchy(d.tree)).descendants() })
.enter().append("g")
.attr("class", function(d) {
return "node" + (d.children ? " node--internal" : " node--leaf"); 
})
.attr("transform", function(d) {
return "translate(" + (d.x - 25) + "," + (d.y+20) + ")"; 
})  

g.append("circle")
.attr('r',10)
.style('fill',"#003366")
g.filter(function(d) {

return !d.children;
}).each(function(node) {
//start simulation for one node
var simulation = d3.forceSimulation(node.data.movies)
.force("x", d3.forceX(function(d) {
return 0;
}).strength(0.2))
.force("y", d3.forceY(function(d) {
return 0;
}).strength(0.1))
.force("collide", d3.forceCollide(04).iterations(1))
.stop();
simulation.tick(75);
//append circles at (0,95) -- the center coorinates of the bottom left tree node
d3.select(this).selectAll(null)
.data(node.data.movies)
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d) { return d.x;})
.attr("cy", function(d) { return d.y;})
.style('fill', function(d) {return "#4f81b9"; })

})
<script src="https://d3js.org/d3.v5.min.js"></script>

通过稍微的调整,我们可以在集群中包含树节点:

var margins = {top:100, bottom:300, left:100, right:100};
var height = 600;
var width = 900;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");
var data = [
{name:"China", tree: {
"name": "Actors",
"children": [
{
"name": "Jackie Chan",
"movies":d3.range(50).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Jet Li",
"movies":d3.range(38).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Chow Yun Fat",
"movies":d3.range(46).map(function(v) {return {name: v.toString()+'_movie'} })
}
]
}},
{name:"USA", tree: {
"name": "Actors",
"children": [
{
"name": "Tom Cruise",
"movies":d3.range(7).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Johnny Depp",
"movies":d3.range(4).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Harrison Ford",
"movies":d3.range(20).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Ryan Gosling",
"movies":d3.range(4).map(function(v) {return {name: v.toString()+'_movie'} })
}
]
}},
{name:"UK", tree: {
"name": "Actors",
"children": [
{
"name": "Daniel Day-Lewis",
"movies":d3.range(36).map(function(v) {return {name: v.toString()+'_movie'} })
},
{
"name": "Christoper Lee",
"movies":d3.range(50).map(function(v) {return {name: v.toString()+'_movie'} })
},
]
}}
];
//var formatComma = d3.format(",");
var columns = 3;
var spacing = 200;
var vSpacing = 180;
var regionG = graphGroup.selectAll('.region')
.data(data)
.enter()
.append('g')
.attr('class', 'region')
.attr('id', (d, i) => 'region' + i)
.attr('transform', (d, k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate(" + horSpace + "," + vertSpace + ")";
});
var miniTree = d3.tree()
.size([150, 75]);
regionG.append('rect')
.attr('x',0)
.attr('y',0)
.attr('width',100)
.attr('height',25)
.style('fill',"#003366");
regionG.append('text')
.attr('x',50)
.attr('y',-10)
.attr('text-anchor','middle')
.text(function(d) {return d.name});

regionG.selectAll(null)
.data( function(d) {
return miniTree(d3.hierarchy(d.tree)).descendants().slice(1)
})
.enter().append("path")
.attr("transform", "translate(-25,20)") // extra positioning.
.attr("class", "link")
.attr("d", function(d) {
return "M" + d.x + "," + d.y
+ "C" + d.x + "," + (d.y + d.parent.y) / 2
+ " " + d.parent.x + "," +  (d.y + d.parent.y) / 2
+ " " + d.parent.x + "," + d.parent.y;
});
////////
var g = regionG.selectAll(null)
.data( function(d) {return miniTree(d3.hierarchy(d.tree)).descendants() })
.enter().append("g")
.attr("class", function(d) {
return "node" + (d.children ? " node--internal" : " node--leaf"); 
})
.attr("transform", function(d) {
return "translate(" + (d.x - 25) + "," + (d.y+20) + ")"; 
})  

g.append("circle")
.attr('r',10)
.style('fill',"#003366")
g.filter(function(d) {

return !d.children;
}).each(function(node) {
var forceNodes = node.data.movies;
// add a placeholder node fixed to the center:
forceNodes.push({fx:0,fy:0,placeholder:true});
//start simulation for one node
var simulation = d3.forceSimulation(forceNodes)
.force("x", d3.forceX(function(d) {
return 0;
}).strength(0.2))
.force("y", d3.forceY(function(d) {
return 0;
}).strength(0.1))
.force("collide", d3.forceCollide().radius(function(d) {
// change collide radius for placeholder node:
return d.placeholder ? 12 : 4;
}).iterations(1))
.stop();
simulation.tick(75);
//append circles at (0,95) -- the center coorinates of the bottom left tree node
d3.select(this).selectAll(null)
// don't append placeholder node:
.data(forceNodes.filter(function(d) { return !d.placeholder }))
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d) { return d.x;})
.attr("cy", function(d) { return d.y;})
.style('fill', function(d) {return "#4f81b9"; })

})
path {
fill: none;
stroke: black;
stroke-width: 1px;
}
<script src="https://d3js.org/d3.v5.min.js"></script>

相关内容

  • 没有找到相关文章

最新更新