d3.js力导向树矩阵

d3.js force directed tree matrix

在以前的应用程序中,我将独立的树改编成“矩阵”格式,使用行和列在一个视觉中显示许多小的层次关系。参见:

对于下一个项目,我尝试了同样的事情,但使用了“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() })

您也没有将实际节点传递给模拟。因为你有多组数据,所以最好有多个力模拟(尤其是所有都占据相同的坐标space)。我认为在这里使用 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 ticks。在这种情况下我们不需要刻度函数:我们可以在绘制时定位节点:

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 ticks 来冷却,所以我在开始时手动将它提前了 400 ticks,你可以尝试更低的数字来获得更少计算的布局,但模拟可能还不够解决部队之间妥协的步骤)。