如何在d3js中显示嵌套数据的嵌套节点?

时间:2018-04-30 19:35:04

标签: javascript d3.js

我试图显示带有群集节点的网络图,但我在D3中遇到了嵌套节点的问题。第一个"层"包含集群,第一层的每个节点可以包含多个节点。网络中的链接可能只发生在集群之间(意味着第一层节点之间)。

这是我到目前为止的代码。我能够显示第一级节点。我无法弄清楚如何显示嵌套节点(请参阅每个节点const datachildren中的代码)。



const node_radius = 100;
const width = 800;
const height = 400;

const links = [
    { "source": 1, "target": 6}
] ;

const data = [
    {
        "id":1,
        "level": "cluster",
        "name": "analytics1",
        "children": [
            {
                "id":2,
                "name": "animate1",
                "level": "leaf",
                "size": 15,
                "parent": 1
            },
            {
                "id":3,
                "name": "animate2",
                "level": "leaf",
                "size": 15,
                "parent": 1
            },
            {
                "id":4,
                "name": "animate3",
                "level": "leaf",
                "size": 15,
                "parent": 1
            }
        ]
    },
    {
        "id":6,
        "name": "analytics2",
        "level": "cluster",
        "children": [
            {
                "id":7,
                "name": "animate4",
                "level": "leaf",
                "size": 10,
                "parent": 6
            }
        ]
    }
]

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

var simulation = d3.forceSimulation()
    // pull nodes together based on the links between them
    .force("link", d3.forceLink().id(function(d) { return d.id; }).strength(0.0001))
    // push nodes apart to space them out
    .force("charge", d3.forceManyBody().strength(-10))
    // add some collision detection so they don't overlap
    .force("collide", d3.forceCollide().radius(node_radius))
    // and draw them around the centre of the space
    .force("center", d3.forceCenter(width / 2, height / 2));

var link = svg.append("g")
    .attr("class", "links")
    .selectAll("line")
    .data(links).enter().append("line")
    .attr("stroke-width", 5)
    .attr("stroke","#000");

var node = svg.append("g")
    .attr("class", "nodes")
    .selectAll("circle")
    .data(data)
    .enter().append("circle")
    .attr("id", function(d) {return "circle"+d.id;})
    .attr("class", "node")
    .attr("r", node_radius)
    .style("opacity", 0.2)
    .attr("dx", 12)
    .attr("dy", ".35em");

var text = svg.append("g")
    .attr("class", "label")
    .selectAll("text")
    .data(data)
    .enter().append("text")
    .text(function(d) { return d.name });

// Update and restart the simulation.
simulation.nodes(data).on("tick", ticked);
simulation.force("link").links(links);
simulation.alpha(1).restart();

function ticked() {
    link
        .attr("x1", function(d) { return d.source.x; })
        .attr("y1", function(d) { return d.source.y; })
        .attr("x2", function(d) { return d.target.x; })
        .attr("y2", function(d) { return d.target.y; });

    node.attr("transform", positionNode);

    text
        .attr("dx", function(d) { return d.x - 30; })
        .attr("dy", function(d) { return d.y + 15; });
}

// move the node based on forces calculations
function positionNode(d) {
    // keep the node within the boundaries of the svg
    if (d.x < node_radius) {
        d.x = 2*node_radius
    };
    if (d.y < node_radius) {
        d.y = 2*node_radius
    };
    if (d.x > width-node_radius) {
        d.x = width-(2*node_radius)
    };
    if (d.y > height-node_radius) {
        d.y = height-(2*node_radius)
    };
    return "translate(" + d.x + "," + d.y + ")";
}
&#13;
<script src="https://d3js.org/d3.v4.min.js"></script>
&#13;
&#13;
&#13;

我希望有类似下图的内容。显示两个集群,并且在每个组中,所有子节点(叶节点)由较小节点表示。节点大小应该可以为两个&#34;层定制。从数据输入。 enter image description here

Example I'm trying to follow on Fiddle.

我还尝试使用d3.pack()将圈子打包到其他圈子中。这是一个example。我目前使用这种方法的问题是我没有成功地在第一层&#34;层的节点之间添加空间和链接。 (在集群之间)。高级集群也被打包在一起,不可能在它们之间添加可理解的链接。

1 个答案:

答案 0 :(得分:0)

我终于成功地将d3.pack()示例与聚类示例合并。这是我的解决方案。

&#13;
&#13;
var data =  [
  {
    "id":1,
    "level": "cluster",
    "name": "analytics1",
    "children": [
      {
        "id":2,
        "name": "animate1",
        "level": "leaf",
        "size": 8,
        "parent": 1,
        "icon":"https://image.freepik.com/free-icon/apple-logo_318-40184.jpg"
      },
      {
        "id":3,
        "name": "animate2",
        "level": "leaf",
        "size": 10,
        "parent": 1,
        "icon": "https://www.freelogodesign.org/img/logo-ex-7.png"
      },
      {
        "id":4,
        "name": "animate3",
        "level": "leaf",
        "size": 5,
        "parent": 1,
        "icon": "http://brandmark.io/logo-rank/random/pepsi.png"
      }
    ]
  },
  {
    "id":6,
    "name": "analytics2",
    "level": "cluster",
    "children": [
      {
        "id":7,
        "name": "animate4",
        "level": "leaf",
        "size": 10,
        "parent": 6,
        "icon":"https://www.seoclerk.com/pics/558390-11FO8A1505384509.png"
      }
    ]
  }
]

var links = [
    { "source": 1, "target": 6}
] ;

var w = 1200, h = 500;
var cluster_padding = 5;
var node_padding = 2;
var size_ratio =100;

var color = d3.scaleOrdinal(d3.schemeCategory20c);

let sumSizes = 0;
data.forEach(function(cluster){
    cluster.children.forEach(function(node){
        sumSizes += node.size;
    });
});

// Compute sum of sizes for cluster size.
data.forEach(function(cluster){
    cluster.size = (
      cluster.children.map(function(d){return d.size;})
        .reduce(function(acc, val){ return acc+val+node_padding; })/sumSizes
    )*size_ratio + cluster_padding;
    
    cluster.children = cluster.children.sort(function(a,b){
        return (a.size < b.size) ? 1 : ((b.size < a.size) ? -1 : 0);
    })
    
    cluster.children.forEach(function(node){
        node.parentSize = cluster.size;
        node.size = node.size*size_ratio/sumSizes;
    });
});

var svg = d3.select("body").append("svg")
  .attr("width", w)
  .attr("height", h);

////////////////////////
// outer force layout
var outerSimulation = d3.forceSimulation()
  // pull nodes together based on the links between them
  .force("link", d3.forceLink().id(function(d) { return d.id; }).strength(0.001))
  // push nodes apart to space them out
  .force("charge", d3.forceManyBody().strength(-5))
  // add some collision detection so they don't overlap
  .force("collide", d3.forceCollide().radius(function(d){return d.size+cluster_padding;}))
  // and draw them around the centre of the space
  .force("center", d3.forceCenter(w / 2, h / 2));

var outerLinks = svg.selectAll("line")
    .data(links)
    .enter().append("line")
    .attr("class", "links")
    .attr("stroke-width", 5);

var outerNodes = svg.selectAll("g.outer")
  .data(data, function (d) {return d.id;})
  .enter()
  .append("g")
  .attr("class", "outer")
  .attr("id", function (d) {return "cluster"+d.id;})
  .attr("x", w/2)
  .attr("y", w/2)
  .call(d3.drag());

outerNodes.append("circle")
  .style("fill", function(d,i){return color(i);})
  .style("stroke", "blue")
  .attr("r", function(d){return d.size});

// Update and restart the simulation.
outerSimulation.nodes(data).on("tick", outerTick);
outerSimulation.force("link").links(links);
outerSimulation.alpha(1).restart();

////////////////////////
// inner force layouts

var innerNodes = [];
var innerTexts = [];
var packs = [];

var margin = 20;

data.forEach(function(n){
  // Pack hierarchy definition
  var pack = d3.pack()
      .size([2*n.size, 2*n.size])
      .padding(cluster_padding);

  var root = d3.hierarchy(n)
      .sum(function(d) { return d.size; })
      .sort(function(a, b) { return b.value - a.value; });

  var nodes = pack(root).descendants();

  // Round images
  var defs = svg.append("defs").attr("id", "imgdefs")

  var pattern = defs
    .selectAll("pattern")
    .data(nodes.filter(function(d) { return d.parent }))
    .enter().append("pattern")
    .attr("id", function(d){return "photo"+d.data.name})
    .attr("height", 1)
    .attr("width", 1)
    .attr("x", "0")
    .attr("y", "0");

  var image = pattern.append('image')
    .attr("class","roundImg")
    .attr("id", function(d){return "photo"+d.data.name;})
    .attr("xlink:href", function(d){return d.data.icon ? d.data.icon : "";})
    .attr("height", function(d){return 3.2*d.r ;})
    ;

  // Nodes
  var circle = svg.select("g.outer#cluster"+n.id).selectAll("g.inner")
    .data(nodes.filter(function(d) { return d.parent }))
    .enter().append("circle")
    .attr("class", "node node--leaf ")
    .attr("id", function(d) {return d.data.name})
    .style("fill", function(d) { return "url(#photo"+d.data.name+")";})
    .attr("r", function(d) { return d.r; })
    .attr("transform", function(d) { return "translate("+(d.x-n.size) +","+ (d.y-n.size)+")"; })
    ;
});

////////////////////////
// functions
function outerTick (e) {
  outerNodes.attr("transform", positionNode);

  outerLinks
    .attr("x1", function(d) { return d.source.x; })
    .attr("y1", function(d) { return d.source.y; })
    .attr("x2", function(d) { return d.target.x; })
    .attr("y2", function(d) { return d.target.y; });
}

function positionNode(d) {
  // keep the node within the boundaries of the svg
  if (d.x - d.size < 0) {
      d.x = d.size + 2
  };
  if (d.y - d.size < 0) {
      d.y = d.size + 2
  };
  if (d.x + d.size > w) {
      d.x = w - d.size - 2
  };
  if (d.y + d.size > h) {
      d.y = h - d.size - 2
  };
  return "translate(" + d.x + "," + d.y + ")";
}
&#13;
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>

<link rel="stylesheet" type="text/css" href="css/pack.css">

<body>
    <div class="packed" id="packed"></div>
</body>
&#13;
&#13;
&#13;

相关问题