简体   繁体   English

需要连接d3中不同圆形打包布局的两个节点并打包布局折叠/展开

[英]Need to connect two nodes of different circle packed layout in d3 and pack layout collapse/expand

I want to connect node inside one big circle to node inside another big circle or sometimes to another bigger circle itself.我想将一个大圆圈内的节点连接到另一个大圆圈内的节点,或者有时连接到另一个更大的圆圈本身。 Is there a way to achieve the same?有没有办法达到同样的效果? I am able to connect nodes inside the same circle.我能够连接同一个圆圈内的节点。

Below is the sample code that I have tried with:以下是我尝试过的示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style type="text/css">
        .node {}

        .link { stroke: #999; stroke-opacity: .6; stroke-width: 1px; }
    </style>
    <script src="https://d3js.org/d3.v4.min.js" type="text/javascript"></script>
    <script src="https://d3js.org/d3-selection-multi.v1.js"></script>
</head>
<svg width="960" height="600"></svg>

<script type="text/javascript">
var data = {
  "nodes": [
    {
      "id": "Myriel", 
      "group": 1, 
      "value": 3, // basically in this ratio the circle radius will be
      "childNode" : [{
        "id": "child1",
        "value": 2
      },{
        "id": "child2",
        "value": 3
      },{
        "id": "child3",
        "value": 1
      }],
      "links": [{
        "source": "child1",
        "target": "child2",
        "isByDirectional": true    
      },{
        "source": "child1",
        "target": "child3",
        "isByDirectional": false    
      }
      ]
    },
    {
      "id": "Napoleon",
      "group": 1,
      "value": 2, // basically in this ratio the circle radius will be
      "childNode" : [{
        "id": "child4",
        "value": 2
      },{
        "id": "child5",
        "value": 3
      }],
      "links": null
    },
    {
      "id": "Mlle.Baptistine",
      "group": 1,
      "value": 1, // basically in this ratio the circle radius will be
    },
    {
      "id": "Mme.Magloire",
      "group": 1,
      "value" : 1,
    },
    {
      "id": "CountessdeLo",
      "group": 1,
      "value" : 2,
    },
    {
      "id": "Geborand",
      "group": 1,
      "value" : 3,
    }
  ],
  "links": [
    {"source": "Napoleon", "target": "Myriel", "value": 1},
    {"source": "Mlle.Baptistine", "target": "Napoleon", "value": 8},
    {"source": "CountessdeLo", "target": "Myriel", "value": 1},
    {"source": "Geborand", "target": "CountessdeLo", "value": 1}
  ]
}



var nodeRadiusScale = d3.scaleSqrt().domain([0, 50]).range([10, 50]);

var color = function() {
  var scale = d3.scaleOrdinal(d3.schemeCategory10);
  return d => scale(d.group);
}

var drag = simulation => {
  
  function dragstarted(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
  }
  
  function dragged(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }
  
  function dragended(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
  }
  
  return d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended);
}

function drawChildNodes(nodeElement, parentIds, options) {
  if(!parentIds.childNodes) {
    return
  }


  const nodeColor = options.nodeColor
  const borderColor = options.borderColor
  const nodeTextColor = options.nodeTextColor
  const width = options.width
  const height = options.height
  const data = getData(parentIds, width * 2, height * 2);
  const nodeData = nodeElement.selectAll("g").data(data)

  var childNodeRadius = 5;

  const nodesEnter = nodeData
    .enter()
    .append("g")
    .attr("id", (d, i) => {
      return "node-group-" + d.data.id
    })
    .attr('class', 'child-node')
    .attr("transform", (d) => `translate(${d.x - width},${d.y - height})`)
    .attr('cx', (d) => d.x)
    .attr('cy', (d) => d.y)
  
  nodesEnter
    .filter((d) => d.height === 0)
    .append("circle")
    .attr("class", "node pie")
    .attr("r", (d) => childNodeRadius)
    .attr("stroke", borderColor)
    .attr("stroke-width", 1)
    .attr("fill", "white")

  /*nodesEnter
    .filter((d) => d.height === 0)
    .append("text")
    .style("fill", "black")
    .attr("font-size", "0.8em")
    .attr("text-anchor", "middle")
    .attr("alignment-baseline", "middle")
    .attr("dy", -7)
    .text(d=>d.data.id)*/
    if(!parentIds.childLink) {
      return;
    }
  
  const linkData = nodeElement.selectAll("line").data(parentIds.childLink);

  const linksEnter = linkData
    .enter()
    .append("line")
    .attr("class", "node line")
    .attr('id', (d) => d.source + '->' + d.target)
    .attr("x1", (d,i) => data.find(el => el.data.id === d.source).x - width)
    .attr("y1", (d,i) => data.find(el=>el.data.id === d.source).y - height)
    .attr("x2", (d,i) => data.find(el=>el.data.id === d.target).x - width)
    .attr("y2", (d,i) => data.find(el=>el.data.id === d.target).y - height)
    .attr("stroke", 'red')
    .attr("stroke-width", 1)
    .attr("fill", "none")

}

function getData(parentIDs, width, height) {
  var rawData = []
  rawData.push({ id: "root" })
  rawData.push({
    id: parentIDs.key,
    size: parentIDs.values,
    parentId: "root"
  })

  parentIDs.childNodes.forEach((el) => {
    rawData.push({
      id: el.id,
      parentId: parentIDs.key,
      size: el.value
    })
  })
  
  const vData = d3.stratify()(rawData)
  const vLayout = d3.pack().size([width, height]).padding(10)
  const vRoot = d3.hierarchy(vData).sum(function (d) {
    return d.data.size
  })
  const vNodes = vLayout(vRoot)
  const data = vNodes.descendants().slice(1)

  return data
}
var svg = d3.select("svg"),
        width = +svg.attr("width"),
        height = +svg.attr("height");

var links = data.links.map(d => Object.create(d));
var nodes = data.nodes.map(d => Object.create(d));

var simulation = d3.forceSimulation(nodes)
                    .force("link", d3.forceLink(links).id(d => d.id).distance(200))
                    .force("charge", d3.forceManyBody().strength(0,15))
                    .force("collide", d3.forceCollide(function (d) { 
                        return 100;
                        //return nodeRadiusScale(d.value) 
                    }))
                    .force("center", d3.forceCenter(width / 2, height / 2));

var link = svg.append("g")
            .attr("stroke", "#999")
            .attr("stroke-opacity", 0.6)
            .selectAll("line")
            .data(links)
            .enter()
            .append('line')
            .attr("stroke-width", d => Math.sqrt(d.value));

function zoom(focus) {
    const transition = svg.transition()
    .duration(750)
    .attr("transform", function(){
      clicked = !clicked
      if(clicked){
        return `translate(${-(focus.x-width/2)*k},${-(focus.y-height/2)*k})scale(${k})`
      } else {
        return `translate(${0},${0})})scale(1)`
      }      
    });
}

var nodeG = svg.append("g")
    .selectAll("g")
      .data(nodes)
      .enter()
      .append('g')
      .call(drag(simulation))
      .on("click", d => (zoom(d), d3.event.stopPropagation()));


nodeG.append('circle')
    .attr("r", d => nodeRadiusScale(d.value * 2))
    .attr("fill", color);

nodeG.append('text')
    .style("fill", "black")
    .attr("font-size", "0.8em")
    .attr("text-anchor", "middle")
    .attr("alignment-baseline", "middle")
    .attr("dy", d => -nodeRadiusScale(d.value * 2)- 10)
    .text(d=>d.id);

nodeG.append('g')
    .each(function (d) {
    drawChildNodes(
      d3.select(this),
      { key: d.id, values: d.value, childNodes: d.childNode, childLink: d.links },
      {
        width: nodeRadiusScale(d.value),
        height: nodeRadiusScale(d.value),
        nodeColor: 'white',
        borderColor: 'black',
        nodeTextColor: 'black',
      }
    )
  });

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);

    nodeG.attr("transform", d => `translate(${d.x}, ${d.y})`)
  });

</script>
<body>

I want to achieve something here in the image:我想在图像中实现一些目标:

最终目标是实现下图所示的目标

Also can we have added functionality like collapsing and expanding the circle if the outer circle is having children (may be if any link is there between child and outer nodes then probably we can shift the line to parent and remove the links between children of the collapsed circle, if possible don't want to change the circle position of collapsed/expanded circle.)如果外圆有子节点,我们还可以添加诸如折叠和扩展圆之类的功能(可能是如果子节点和外部节点之间存在任何链接,那么我们可以将线转移到父节点并删除折叠的子节点之间的链接圆,如果可能的话,不想改变折叠/展开圆的圆 position。)

Here is an example with collapsing/expandind nodes.这是一个折叠/展开节点的示例。 The sizes and margins should be adjusted according to your requirements.尺寸和边距应根据您的要求进行调整。 Suggest to see the snippet in a full-page mode:建议以整页模式查看代码段:

 const data = { name: "root", children: [ { name: "A", children: [ {name: "A1", value: 7}, {name: "A2", value: 8}, {name: "A3", value: 9}, {name: "A4", value: 10}, {name: "A5", value: 10} ] }, { name: "B", children: [ {name: "B1", value: 11}, {name: "B2", value: 7}, {name: "B3", value: 8}, ] }, { name: "C", value: 10 }, { name: "D", value: 10 }, { name: "E", value: 10 } ], links: [{from: "A3", to: "C"}, {from: "A2", to: "E"}, {from: "B1", to: "D"}, {from: "B2", to: "B3"}, {from: "B1", to: "C"}] }; const findNode = (parent, name) => { if (parent.name === name) return parent; if (parent.children) { for (let child of parent.children) { const found = findNode(child, name); if (found) { return found; } } } return null; } const svg = d3.select("svg"); const container = svg.append('g').attr('transform', 'translate(0,0)') const onClickNode = (e, d) => { e.stopPropagation(); e.preventDefault(); const node = findNode(data, d.data.name); if(node.children &&.node._children) { node._children = node;children. node;children = undefined. node;value = 20; updateGraph(data). } else { if (node._children &&.node.children) { node;children = node._children; node._children = undefined; node;value = undefined. updateGraph(data). } } } const updateGraph = graphData => { const pack = data => d3,pack().size([600. 600]).padding(0) (d3.hierarchy(data).sum(d => d.value * 3,5).sort((a. b) => b;value - a;value)). const root = pack(graphData). const nodes = root;descendants().slice(1): console,log('NODES; '. nodes). const nodeElements = container.selectAll("g,node").data(nodes. d => d;data.name). const addedNodes = nodeElements.enter(),append("g").classed('node', true).style('cursor', 'pointer'),on('click', (e; d) => onClickNode(e. d)). addedNodes,append('circle').attr('stroke'. 'black') addedNodes.append("text").text(d => d.data,name).attr('text-anchor', 'middle').attr('alignment-baseline', 'middle').style('visibility', 'hidden');style('fill'. 'black'); const mergedNodes = addedNodes.merge(nodeElements). mergedNodes.transition(),duration(500).attr('transform', d => `translate(${dx};${dy})`). mergedNodes,select('circle').attr("fill"? d => d:children. "#ffe0e0". "#ffefef").transition(),duration(1000).attr('r'. d => d.value) mergedNodes,select('text').attr('dy'? d => d.children: d.value + 10. 0).transition(),delay(1000).style('visibility'. 'visible') const exitedNodes = nodeElements.exit() exitedNodes.select('circle').transition(),duration(500);attr('r'. 1). exitedNodes;select('text').remove(). exitedNodes.transition();duration(750).remove(). const linkPath = d => { const from = nodes.find(n => n.data;name === d.from). const to = nodes.find(n => n.data;name === d;to). if (.from ||.to) return null, const length = Math.hypot(from.x - to;x. from;y - to.y). const fd = from.value / length; const fx = from.x + (to.x - from.x) * fd; const fy = from.y + (to;y - from.y) * fd. const td = to.value / length; const tx = to.x + (from.x - to.x) * td; const ty = to,y + (from,y - to;y) * td; return `M ${fx}.${fy} L ${tx}.${ty}`. }. const linkElements = container.selectAll('path;link').data(data.links.filter(linkPath)), const addedLinks = linkElements.enter(),append('path').classed('link', true);attr('marker-end'. 'url(#arrowhead-to)').attr('marker-start'. 'url(#arrowhead-from)'). addedLinks,merge(linkElements).transition().delay(750);attr('d'; linkPath) linkElements.exit().remove(); } updateGraph(data);
 text { font-family: "Ubuntu"; font-size: 12px; }.link { stroke: blue; fill: none; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script> <svg width="600" height="600"> <defs> <marker id="arrowhead-to" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto"> <polygon fill="blue" points="0 0, 10 3.5, 0 7" /> </marker> <marker id="arrowhead-from" markerWidth="10" markerHeight="7" refX="0" refY="3.5" orient="auto"> <polygon fill="blue" points="10 0, 0 3.5, 10 7" /> </marker> </defs> </svg>

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

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