javascript - d3.js 强制有向树矩阵
问题描述
在以前的应用程序中,我将独立树调整为“矩阵”格式,该格式使用行和列在一个视觉对象中显示许多小的层次关系。请参阅:D3.js v5 - Swarm Tree - 如何迭代地围绕树节点居中集群
对于下一个项目,我尝试了同样的事情,但使用了“force-directed-trees”。
在我遵循与上述相同的程序之后遇到了一个障碍,并且在此过程中没有引发任何错误。
片段:
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 个滴答声,您可以尝试使用较小的数字来获得更少计算的布局,但模拟可能没有足够的步骤来整理力量之间的良好妥协)。
推荐阅读
- android - 以编程方式获取edittext视图的ID?
- json - 我可以在 Spark Scala 中将 Dataframe 保存为漂亮格式的 JSON 吗?
- angular - Angular:ngBootstrap 模态对话框中的模板驱动表单字段验证
- java - 实施 Google 地图时,使用“.getTag()”不起作用
- javascript - 我无法将此 JS 函数转换为 C#。找不到 isNan 和 || 取代
- c# - FirstOrDefault() 为迭代添加天数
- google-analytics - Google 分析仪表板未收到自定义事件
- tomcat - 在客户端连接终止时停止服务器线程
- amazon-web-services - 弹性豆茎不复制单个容器日志
- reporting-services - 在 SSRS 中以 CSV 格式导出时如何在列标题中使用星号 (*) 标记