javascript - d3.js 力定向树矩阵

标签 javascript d3.js

在之前的应用程序中,我已将独立树改编为“矩阵”格式,该格式使用行和列在一个视觉效果中显示许多小的层次关系。请参阅: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/

相关文章:

javascript - 为什么我的坐标仅在圆定义内未定义?

javascript - D3js 与 Internet Explorer 11 一起工作有什么问题

javascript - mailto 正文中的某些字符正在停止打开邮件窗口....javascript

javascript - 使用用户脚本自动选中复选框?

javascript - 使用 JavaScript/D3 在图表上设置间距条

javascript - D3 从字符串中解析数字

d3.js - Focus/Context Brushing + Pan/Zoom graph - 如何限制平移

javascript - 停止多个内联函数JS的事件传播

javascript - 使用 Create React App 的静态 HTML 登录页面

javascript - Accordion 图标不会切换