在之前的应用程序中,我已将独立树改编为“矩阵”格式,该格式使用行和列在一个视觉效果中显示许多小的层次关系。请参阅:D3.js v5 - Swarm Tree - How to iteratively center swarms around tree nodes
对于下一个项目,我尝试做同样的事情,但是使用“强制定向树”。
尽管在我遵循与上述相同的过程并且在此过程中没有抛出任何错误之后遇到了障碍。
片段:
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:"Company1", tree: {
"name": "Product offerings",
"children": [
{"name": "Equity line", "size": 2200,
"children": [
{"name": "Equity fund 1", "size": 800, "type" : "equity"},
{"name": "Equity fund 2", "size": 600, "type" : "equity"},
{"name": "Equity fund 3", "size": 300, "type" : "equity"},
{"name": "Equity fund 4", "size": 250, "type" : "equity"},
{"name": "Equity fund 5", "size": 250, "type" : "equity"},
{"name": "Equity fund 6", "size": 525, "type" : "equity"},
]
},
{"name": "Bond fund line", "size": 1400,
"children": [
{"name": "Bond fund 1", "size": 800, "type" : "bond"},
{"name": "Bond fund 2", "size": 600, "type" : "bond"},
{"name": "Bond fund 3", "size": 300, "type" : "bond"},
{"name": "Bond fund 4", "size": 250, "type" : "bond"},
]
},
{"name": "Balanced line", "size": 1400,
"children": [
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
]
},
]
}},
{name:"Company2", tree: {
"name": "Product offerings",
"children": [
{"name": "Equity line", "size": 2200,
"children": [
{"name": "Equity fund 1", "size": 800, "type" : "equity"},
{"name": "Equity fund 2", "size": 600, "type" : "equity"},
{"name": "Equity fund 3", "size": 300, "type" : "equity"},
{"name": "Equity fund 4", "size": 250, "type" : "equity"},
{"name": "Equity fund 5", "size": 250, "type" : "equity"},
{"name": "Equity fund 6", "size": 525, "type" : "equity"},
]
},
{"name": "Bond fund line", "size": 1400,
"children": [
{"name": "Bond fund 1", "size": 800, "type" : "bond"},
{"name": "Bond fund 2", "size": 600, "type" : "bond"},
{"name": "Bond fund 3", "size": 300, "type" : "bond"},
{"name": "Bond fund 4", "size": 250, "type" : "bond"},
]
},
{"name": "Balanced line", "size": 1400,
"children": [
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
]
},
]
}},
]
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 colorMap = {
'equity':"#003366",
'bond':"#f6d18b",
'balanced':"#95b3d7"
};
//const root = d3.hierarchy(data);
//const links = root.links();
//const nodes = root.descendants();
const simulation = d3.forceSimulation(d3.hierarchy(function(d) {return d.tree}).descendants())
.force("link", d3.forceLink(d3.hierarchy(function(d) {return d.tree}).links()).id(d => d.id).distance(0).strength(1))
.force("charge", d3.forceManyBody().strength(-50))
.force("x", d3.forceX())
.force("y", d3.forceY());
const link = regionG.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(d3.hierarchy(function(d) {return d.tree}).links())
.join("line");
const node = regionG.append("g")
.attr("fill", "#fff")
.attr("stroke", "#000")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(d3.hierarchy(function(d) {return d.tree}).descendants())
.join("circle")
.attr("fill", d => d.children ? null : colorMap[d.data.type])
.attr("stroke", d => d.children ? null : "#fff")
.attr("r", 3.5);
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
});
<script src="https://d3js.org/d3.v5.min.js"></script>
正如我们所看到的,我使用相同的方法,将 data
构造为对象数组。一个对象条目是tree
——它包含层次结构数据。 (我将列表限制为仅两个
为了简单起见。
问题
考虑到我们迄今为止在早期相同方法上所取得的成功,什么可能会导致力导向树的过程失败?
注意如果可以在没有初始力动画的情况下实现力树矩阵,这是可以接受的(实际上更好)。
最佳答案
当前代码中存在一些问题,其中之一是由于创建圆圈的方式,每个强制布局只有一个圆圈:
.data(d3.hierarchy(function(d) {return d.tree}).descendants())
而不是:
.data(function(d) { return d3.hierarchy(d.tree).descendants() })
您也不会将实际节点传递给模拟。因为您有多组数据,所以最好进行多个力模拟(特别是当所有数据都占据相同的坐标空间时)。我认为这里使用 Selection.each() 为每个根创建力模拟会更容易。我们可以计算一次节点和链接(我们不想创建代表相同层次结构的新对象)并将它们传递给模拟和输入周期:
regionG.each(simulate);
function simulate(d) {
let g = d3.select(this);
let tree = d3.hierarchy(d.tree);
let nodes = tree.descendants();
let links = tree.links();
const simulation = d3.forceSimulation(tree.descendants())
.force("link", d3.forceLink(links).id(d => d.id).distance(0).strength(1))
.force("charge", d3.forceManyBody().strength(-50))
.force("x", d3.forceX())
.force("y", d3.forceY());
const link = g.append("g")
.selectAll("line")
.data(links)
.join("line")
...
const node = g.append("g")
.selectAll("circle")
.data(nodes)
.join("circle")
...
simulation.on("tick", ... })
}
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:"Company1", tree: {
"name": "Product offerings",
"children": [
{"name": "Equity line", "size": 2200,
"children": [
{"name": "Equity fund 1", "size": 800, "type" : "equity"},
{"name": "Equity fund 2", "size": 600, "type" : "equity"},
{"name": "Equity fund 3", "size": 300, "type" : "equity"},
{"name": "Equity fund 4", "size": 250, "type" : "equity"},
{"name": "Equity fund 5", "size": 250, "type" : "equity"},
{"name": "Equity fund 6", "size": 525, "type" : "equity"},
]
},
{"name": "Bond fund line", "size": 1400,
"children": [
{"name": "Bond fund 1", "size": 800, "type" : "bond"},
{"name": "Bond fund 2", "size": 600, "type" : "bond"},
{"name": "Bond fund 3", "size": 300, "type" : "bond"},
{"name": "Bond fund 4", "size": 250, "type" : "bond"},
]
},
{"name": "Balanced line", "size": 1400,
"children": [
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
]
},
]
}},
{name:"Company2", tree: {
"name": "Product offerings",
"children": [
{"name": "Equity line", "size": 2200,
"children": [
{"name": "Equity fund 1", "size": 800, "type" : "equity"},
{"name": "Equity fund 2", "size": 600, "type" : "equity"},
{"name": "Equity fund 3", "size": 300, "type" : "equity"},
{"name": "Equity fund 4", "size": 250, "type" : "equity"},
{"name": "Equity fund 5", "size": 250, "type" : "equity"},
{"name": "Equity fund 6", "size": 525, "type" : "equity"},
]
},
{"name": "Bond fund line", "size": 1400,
"children": [
{"name": "Bond fund 1", "size": 800, "type" : "bond"},
{"name": "Bond fund 2", "size": 600, "type" : "bond"},
{"name": "Bond fund 3", "size": 300, "type" : "bond"},
{"name": "Bond fund 4", "size": 250, "type" : "bond"},
]
},
{"name": "Balanced line", "size": 1400,
"children": [
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
]
},
]
}},
]
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 colorMap = {
'equity':"#003366",
'bond':"#f6d18b",
'balanced':"#95b3d7"
};
//const root = d3.hierarchy(data);
//const links = root.links();
//const nodes = root.descendants();
regionG.each(simulate);
function simulate(d) {
let g = d3.select(this);
let tree = d3.hierarchy(d.tree);
let nodes = tree.descendants();
let links = tree.links();
const simulation = d3.forceSimulation(tree.descendants())
.force("link", d3.forceLink(links).id(d => d.id).distance(0).strength(1))
.force("charge", d3.forceManyBody().strength(-50))
.force("x", d3.forceX())
.force("y", d3.forceY());
const link = g.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.join("line");
const node = g.append("g")
.attr("fill", "#fff")
.attr("stroke", "#000")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("fill", d => d.children ? null : colorMap[d.data.type])
.attr("stroke", d => d.children ? null : "#fff")
.attr("r", 3.5);
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
});
}
circle {
fill: black;
stroke-width: 1px;
stroke:black;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
为了避免动画,我们可以使用simulation.tick(n)来指定我们希望力手动向前移动n个刻度。在这种情况下,我们不需要刻度函数:我们可以在绘制时定位节点:
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:"Company1", tree: {
"name": "Product offerings",
"children": [
{"name": "Equity line", "size": 2200,
"children": [
{"name": "Equity fund 1", "size": 800, "type" : "equity"},
{"name": "Equity fund 2", "size": 600, "type" : "equity"},
{"name": "Equity fund 3", "size": 300, "type" : "equity"},
{"name": "Equity fund 4", "size": 250, "type" : "equity"},
{"name": "Equity fund 5", "size": 250, "type" : "equity"},
{"name": "Equity fund 6", "size": 525, "type" : "equity"},
]
},
{"name": "Bond fund line", "size": 1400,
"children": [
{"name": "Bond fund 1", "size": 800, "type" : "bond"},
{"name": "Bond fund 2", "size": 600, "type" : "bond"},
{"name": "Bond fund 3", "size": 300, "type" : "bond"},
{"name": "Bond fund 4", "size": 250, "type" : "bond"},
]
},
{"name": "Balanced line", "size": 1400,
"children": [
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
]
},
]
}},
{name:"Company2", tree: {
"name": "Product offerings",
"children": [
{"name": "Equity line", "size": 2200,
"children": [
{"name": "Equity fund 1", "size": 800, "type" : "equity"},
{"name": "Equity fund 2", "size": 600, "type" : "equity"},
{"name": "Equity fund 3", "size": 300, "type" : "equity"},
{"name": "Equity fund 4", "size": 250, "type" : "equity"},
{"name": "Equity fund 5", "size": 250, "type" : "equity"},
{"name": "Equity fund 6", "size": 525, "type" : "equity"},
]
},
{"name": "Bond fund line", "size": 1400,
"children": [
{"name": "Bond fund 1", "size": 800, "type" : "bond"},
{"name": "Bond fund 2", "size": 600, "type" : "bond"},
{"name": "Bond fund 3", "size": 300, "type" : "bond"},
{"name": "Bond fund 4", "size": 250, "type" : "bond"},
]
},
{"name": "Balanced line", "size": 1400,
"children": [
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
{"name": "Bond fund 1", "size": 800, "type" : "balanced"},
{"name": "Bond fund 2", "size": 600, "type" : "balanced"},
{"name": "Bond fund 3", "size": 300, "type" : "balanced"},
{"name": "Bond fund 4", "size": 250, "type" : "balanced"},
]
},
]
}},
]
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 colorMap = {
'equity':"#003366",
'bond':"#f6d18b",
'balanced':"#95b3d7"
};
//const root = d3.hierarchy(data);
//const links = root.links();
//const nodes = root.descendants();
regionG.each(simulate);
function simulate(d) {
let g = d3.select(this);
let tree = d3.hierarchy(d.tree);
let nodes = tree.descendants();
let links = tree.links();
const simulation = d3.forceSimulation(tree.descendants())
.force("link", d3.forceLink(links).id(d => d.id).distance(0).strength(1))
.force("charge", d3.forceManyBody().strength(-50))
.force("x", d3.forceX())
.force("y", d3.forceY())
.tick(400)
const link = g.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.join("line")
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
const node = g.append("g")
.attr("fill", "#fff")
.attr("stroke", "#000")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("fill", d => d.children ? null : colorMap[d.data.type])
.attr("stroke", d => d.children ? null : "#fff")
.attr("r", 3.5)
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}
circle {
fill: black;
stroke-width: 1px;
stroke:black;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
(您的模拟需要 400 个刻度来冷却,因此我在开始时手动将其提前了 400 个刻度,您可以尝试较低的数字以较少的计算来获得布局,但模拟可能没有足够的步骤来在力量之间找到一个很好的妥协)。
关于javascript - d3.js 力定向树矩阵,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68493391/