简体   繁体   English

d3.js 力有向树矩阵

[英]d3.js force directed tree matrix

In previous applications, I have adapted standalone trees into a "matrix" format that uses rows and columns to display many small hierarchical relationships in one visual.在以前的应用程序中,我已将独立树改编为“矩阵”格式,该格式使用行和列在一个视觉对象中显示许多小的层次关系。 See: D3.js v5 - Swarm Tree - How to iteratively center swarms around tree nodes请参阅: D3.js v5 - Swarm Tree - 如何以树节点为中心以迭代方式居中

For the next project, I was attempting the same thing but with "force-directed-trees."对于下一个项目,我尝试了同样的事情,但使用了“力导向树”。

Hit a roadblock though after I followed the identical procedure as above and no errors were thrown in the process.在我按照与上述相同的程序进行操作后遇到了障碍,并且在此过程中没有出现任何错误。

Snippet:片段:

 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 => dx) .attr("cy", d => dy); });
 <script src="https://d3js.org/d3.v5.min.js"></script>

As we can see, I'm using the same approach, constructing data to be an array of objects.正如我们所见,我使用相同的方法,将data构造为对象数组。 One object entry is tree -- which contains the hierarchy data.一个对象条目是tree ——它包含层次结构数据。 (I limited the list to simply two for the sake of simplicity. (为了简单起见,我将列表限制为两个。

Question问题

What could be throwing the process off for force-directed trees, given the success we've had thus far with earlier, identical approaches?考虑到我们迄今为止使用早期相同方法取得的成功,什么可能会导致力导向树的过程中断?

Note If there is away to achieve the force tree matrix without the initial force animation that would be acceptable (actually preferable).注意如果在没有初始力动画的情况下实现力树矩阵是可以接受的(实际上更可取)。

There are a few issues in the current code, one is that you only have one circle per force layout because of how you create the circles:当前代码中存在一些问题,一是由于您创建圆圈的方式,每个力布局只有一个圆圈:

.data(d3.hierarchy(function(d) {return d.tree}).descendants())

Instead of:代替:

.data(function(d) { return d3.hierarchy(d.tree).descendants() })

You also don't pass the actual nodes to the simulation.您也不会将实际节点传递给模拟。 Because you have multiple sets of data, it's best to have multiple force simulations (especially as all are occupy the same coordinate space).因为您有多组数据,所以最好有多个力模拟(特别是因为所有数据都占用相同的坐标空间)。 I think it'll be easier here to use selection.each() to create a force simulation for each root.我认为在这里使用 selection.each() 为每个根创建力模拟会更容易。 We can calculate nodes and links once (we don't want to create new objects reprsenting the same hierarchy) and pass these to the simulation and the enter cycle:我们可以计算一次节点和链接(我们不想创建代表相同层次结构的新对象)并将它们传递给模拟和输入循环:

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 => dx) .attr("cy", d => dy); }); }
 circle { fill: black; stroke-width: 1px; stroke:black; }
 <script src="https://d3js.org/d3.v5.min.js"></script>

To avoid animation, we can use simulation.tick(n) to specify that we want the force to manually move forward n ticks.为了避免动画,我们可以使用 simulation.tick(n) 来指定我们希望力手动向前移动 n 个刻度。 We don't need a tick function in this case: we can position the nodes when drawn:在这种情况下我们不需要刻度函数:我们可以在绘制时定位节点:

 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 => dx) .attr("cy", d => dy); }
 circle { fill: black; stroke-width: 1px; stroke:black; }
 <script src="https://d3js.org/d3.v5.min.js"></script>

(Your simulation takes 400 ticks to cool down, so I've advanced it 400 ticks manually of the get go, you can try lower numbers to get the layout with less computation, but the simulation might not have had enough steps to sort out a good compromise between the forces). (您的模拟需要 400 个滴答来冷却,所以我在开始时手动将其提前了 400 个滴答,您可以尝试使用较低的数字来以较少的计算获得布局,但模拟可能没有足够的步骤来整理力之间的良好妥协)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM