将圆圈和路径分组为 D3 强制布局中的节点

Grouping Circles and Path as a node in D3 Force Layout?

本文关键字:布局 节点 D3 路径      更新时间:2023-09-26

我正在尝试将圆分组到力导向图布局中,以便我可以添加半圆的路径,使圆变成两个半圆可点击的边。我设法用 d3 作品使两侧半圆可点击,就像这个小提琴一样。我似乎无法包装d3概念如何分组并连接链接。

如何将圆圈和路径分组并使其与力布局配合使用?我未能理解哪些概念才能完成这项工作?我做错了什么?

双面可点击半圆码

var vis = d3.select("body").append("svg")
var pi = Math.PI;
var arc = d3.svg.arc()
    .innerRadius(0)
    .outerRadius(70)
    .startAngle(0) //converting from degs to radians
    .endAngle(pi) //just radians
var canvas = vis.attr("width", "400").attr("height", "400").append("g");

 // Added height and width so arc is visible
        canvas.append("circle")
    .attr("r", "70px")
    .attr("fill","blue")
    .attr("cx",400/2)
    .attr("cy",400/2)
    .on("click", function(d){
        console.log("View");
    })
    .on("mouseover", function(){            d3.select(this).style("fill", "blue");
    console.log(d3.select(this.parentNode).select(".view").style("visibility","visible"));
    })
    .on("mouseout", function(){
            d3.select(this).style("fill", "blue");
        console.log(d3.select(this.parentNode).select(".view").style("visibility","hidden"));
     });
    canvas.append("path")
    .attr("d", arc)
    .attr("fill", "blue")
    .attr("transform", "translate(200,200)")
    .on("click",function(d){
         console.log("Expand");
    })
    .on("mouseover", function(){            d3.select(this).style("fill", "blue");
    console.log(d3.select(this.parentNode).select(".expand").style("visibility","visible"));
    })
    .on("mouseout", function(){
            d3.select(this).style("fill", "blue");
        console.log(d3.select(this.parentNode).select(".expand").style("visibility","hidden"));
     });
   canvas.append("text")
    .attr("dx", "190")
    .attr("dy","200")
    .attr("class","view")
      .text("VIEW")
    canvas.append("text")
    .attr("dx", "190")
    .attr("dy","200")
    .attr("class","expand")
      .text("Expand")

使用圆形作为节点代码的强制布局,灵感来自这个堆栈溢出问题

这把小提琴

var words = [{
  "group": "n",
  "word": "main node",
  "children": [{
    "group": "n",
    "name": "sub node 1"
  }, {
    "group": "n",
    "name": "sub node 2"
  }, {
    "group": "n",
    "name": "sub node 3"
  }, {
    "group": "v",
    "name": "sub node 4"
  }, {
    "group": "s",
    "name": "sub node 5"
  }, {
    "group": "s",
    "name": "sub node 6"
  }, {
    "group": "s",
    "name": "sub node 7"
  }, {
    "group": "s",
    "name": "sub node 8"
  }, {
    "group": "s",
    "name": "sub node 9"
  }, {
    "group": "s",
    "name": "sub node 10"
  }, {
    "group": "s",
    "name": "sub node 11"
  }, {
    "group": "r",
    "name": "sub node 12",
    "children": [{
      "group": "r",
      "name": "sub sub node 1"
    }, {
      "group": "r",
      "name": "sub sub node 2"
    }, {
      "group": "r",
      "name": "sub sub node 3"
    }]
  }]
}]

var w = 600,
  h = 600,
  radius = 30,
  node,
  link,
  root;
var pi = Math.PI;  
var arc = d3.svg.arc()
    .innerRadius(0)
    .outerRadius(radius)
    .startAngle(0) //converting from degs to radians
    .endAngle(pi)  
var force = d3.layout.force()
  .on("tick", tick)
  .charge(function(d) {
    return -1000;
  })
  .linkDistance(function(d) {
    return d.target._children ? 200 : 120;
  })
  .size([w, h - 160]);
var svg = d3.select("#viz").append("svg")
  .attr("width", w)
  .attr("height", h);
root = words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();
function update() {
  var nodes = flatten(root),
    links = d3.layout.tree().links(nodes);
  // Restart the force layout.
  force
    .nodes(nodes)
    .links(links)
    .start();
  // Update the links…
  link = svg.selectAll(".link")
    .data(links);
  // Enter any new links.
  link.enter().insert("svg:line", ".node")
    .attr("class", "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;
    });
  // Exit any old links.
  link.exit().remove();
  // Update the nodes…
  node = svg.selectAll("circle.node")
    .data(nodes, function(d) {
      return d.name;
    })
    .style("fill", color);
  node.transition()
    .attr("r", radius);

  // Enter any new nodes.
  node.enter().append("svg:circle")
    .attr("class", "node")
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })
    .attr("r", radius)
    .style("fill", color)
    //.on("click", click)
    .on("click",function(){
          console.log("Click Main Circle")
    })
    //.call(force.drag);

  node.append("title")
    .text(function(d) {
      return d.name;
    });

  // Exit any old nodes.
  node.exit().remove();
}
function tick() {
  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("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    });
}
// Color leaf nodes orange, and packages white or blue.
function color(d) {
  if (d._children) {
    return "#95a5a6";
  } else {
    switch (d.group) {
      case 'r': //adverb
        return "#e74c3c";
        break;
      case 'n': //noun
        return "#3498db";
        break;
      case 'v': //verb
        return "#2ecc71";
        break;
      case 's': //adjective
        return "#e78229";
        break;
      default:
        return "#9b59b6";
    }
  }
}
// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update();
}
// Returns a list of all nodes under the root.
function flatten(root) {
  var nodes = [],
    i = 0;
  function recurse(node) {
    if (node.children) node.size = node.children.reduce(function(p, v) {
      return p + recurse(v);
    }, 0);
    if (!node.id) node.id = ++i;
    nodes.push(node);
    return node.size;
  }
  root.size = recurse(root);
  return nodes;
}

我试图通过对圆圈进行分组并在组中添加半圆路径来对圆圈进行分组,但布局中断。

 var arc = d3.svg.arc()
.innerRadius(0)
.outerRadius(30)
.startAngle(0) //converting from degs to radians
.endAngle(pi)
var g = node.enter().append("g")
  .attr("class", "node");
    g.append("svg:circle")
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })
    .attr("r", radius)
    .style("fill", color)
    //.on("click", click)
    .on("click",function(){
          console.log("Click Main Circle")
    })

   g.append("path")
   .attr("d", arc)
   .attr("fill", "blue")
   .attr("transform", "translate(200,200)")
   .on("click",function(d){
     console.log("path");
    })

以下是解决布局问题的方法。

而不是将 cx 和 cy 设置为在刻度中圆圈。

执行以下操作:

  1. 创建群组
  2. 将圆圈添加到上面的组(不要设置 cx/cy)
  3. 将路径添加到上述组。(不设置变换)

tick执行以下操作以转换其中包含的圆和路径。

function tick() {
  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;
    });
  //transform for nodes.
  node.attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")"
  })
}

此处的工作代码

希望这有帮助!