Javascript d3.js将可拖动网络与力定向图相结合

Javascript d3.js将可拖动网络与力定向图相结合,javascript,d3.js,Javascript,D3.js,我试图让用户使用d3的力定向图选择和拖动多个节点,但似乎无法让它工作 这里有一个很好的示例,演示了如何选择多个节点并拖动它们以及标准的力定向图 当我尝试将两者结合起来时,拖动多个节点似乎有点不合适,因为节点不会被拖动在一起,而是一个被拖动,而其他选定的节点会在其周围摆动: 有人能告诉我我做错了什么吗 代码如下: var width = 960, height = 500, shiftKey; var svg = d3.select("body") .attr("tab

我试图让用户使用d3的力定向图选择和拖动多个节点,但似乎无法让它工作

这里有一个很好的示例,演示了如何选择多个节点并拖动它们以及标准的力定向图

当我尝试将两者结合起来时,拖动多个节点似乎有点不合适,因为节点不会被拖动在一起,而是一个被拖动,而其他选定的节点会在其周围摆动:

有人能告诉我我做错了什么吗

代码如下:

var width = 960,
    height = 500,
    shiftKey;

var svg = d3.select("body")
    .attr("tabindex", 1)
    .on("keydown.brush", keydown)
    .on("keyup.brush", keyup)
    .each(function() { this.focus(); })
  .append("svg")
    .attr("width", width)
    .attr("height", height);

var link = svg.append("g")
    .attr("class", "link")
  .selectAll("line");

var brush = svg.append("g")
    .datum(function() { return {selected: false, previouslySelected: false}; })
    .attr("class", "brush");

var node = svg.append("g")
    .attr("class", "node")
  .selectAll("circle");

  graph.links.forEach(function(d) {
    d.source = graph.nodes[d.source];
    d.target = graph.nodes[d.target];
  });

  link = link.data(graph.links).enter().append("line")
      .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; });

  brush.call(d3.svg.brush()
        .x(d3.scale.identity().domain([0, width]))
        .y(d3.scale.identity().domain([0, height]))
        .on("brushstart", function(d) {
          node.each(function(d) { d.previouslySelected = shiftKey && d.selected; });
        })
        .on("brush", function() {
          var extent = d3.event.target.extent();
          node.classed("selected", function(d) {
            return d.selected = d.previouslySelected ^
                (extent[0][0] <= d.x && d.x < extent[1][0]
                && extent[0][1] <= d.y && d.y < extent[1][1]);
          });
        })
        .on("brushend", function() {
          d3.event.target.clear();
          d3.select(this).call(d3.event.target);
        }));

var force = d3.layout.force()
        .charge(-120)
        .linkDistance(30)
        .nodes(graph.nodes)
        .links(graph.links)
        .size([width, height])
        .start();

  node = node.data(graph.nodes).enter().append("circle")
      .attr("r", 4)
      .attr("cx", function(d) { return d.x; })
      .attr("cy", function(d) { return d.y; })
      .on("mousedown", function(d) {
        if (!d.selected) { // Don't deselect on shift-drag.
          if (!shiftKey) node.classed("selected", function(p) { return p.selected = d === p; });
          else d3.select(this).classed("selected", d.selected = true);
        }
      })
      .on("mouseup", function(d) {
        if (d.selected && shiftKey) d3.select(this).classed("selected", d.selected = false);
      })
      .call(force.drag()
        .on("drag", function(d) { nudge(d3.event.dx, d3.event.dy); }));

      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', d.x).attr('cy', d.y);

      };

      force.on("tick", tick);

function nudge(dx, dy) {
  node.filter(function(d) { return d.selected; })
      .attr("cx", function(d) { return d.x += dx; })
      .attr("cy", function(d) { return d.y += dy; })

  link.filter(function(d) { return d.source.selected; })
      .attr("x1", function(d) { return d.source.x; })
      .attr("y1", function(d) { return d.source.y; });

  link.filter(function(d) { return d.target.selected; })
      .attr("x2", function(d) { return d.target.x; })
      .attr("y2", function(d) { return d.target.y; });

  d3.event.sourceEvent.preventDefault();
}

function keydown() {
  if (!d3.event.metaKey) switch (d3.event.keyCode) {
    case 38: nudge( 0, -1); break; // UP
    case 40: nudge( 0, +1); break; // DOWN
    case 37: nudge(-1,  0); break; // LEFT
    case 39: nudge(+1,  0); break; // RIGHT
  }
  shiftKey = d3.event.shiftKey || d3.event.metaKey;
}

function keyup() {
  shiftKey = d3.event.shiftKey || d3.event.metaKey;
}
var-width=960,
高度=500,
移位键;
var svg=d3.选择(“主体”)
.attr(“tabindex”,1)
.on(“按键向下。笔刷”,按键向下)
.on(“keyup.brush”,keyup)
.each(函数(){this.focus();})
.append(“svg”)
.attr(“宽度”,宽度)
.attr(“高度”,高度);
var link=svg.append(“g”)
.attr(“类”、“链接”)
。选择全部(“行”);
var brush=svg.append(“g”)
.datum(函数(){return{selected:false,previouslySelected:false};})
.attr(“类”、“刷”);
var node=svg.append(“g”)
.attr(“类”、“节点”)
.选择全部(“圆圈”);
图.links.forEach(函数(d){
d、 source=graph.nodes[d.source];
d、 target=graph.nodes[d.target];
});
link=link.data(graph.links).enter().append(“行”)
.attr(“x1”,函数(d){返回d.source.x;})
.attr(“y1”,函数(d){返回d.source.y;})
.attr(“x2”,函数(d){返回d.target.x;})
.attr(“y2”,函数(d){返回d.target.y;});
调用(d3.svg.brush())
.x(d3.scale.identity().domain([0,宽度])
.y(d3.scale.identity().domain([0,高度])
.开启(“刷启动”,功能(d){
node.each(函数(d){d.previouslySelected=shiftKey&&d.selected;});
})
.on(“刷子”,函数(){
var extent=d3.event.target.extent();
节点分类(“选定”,功能(d){
返回d.selected=d.previouslySelected^

(范围[0][0]解决了这个问题。诀窍是覆盖默认的
强制拖动行为。默认情况下,拖动行为将被拖动的节点标记为固定的,并直接更改其位置。这是为了防止其他节点在拖动节点时将其拖动

当选择多个节点时,所有节点都必须固定,我们必须直接更改它们的位置(所有的
px
py
x
,和
y
)。这允许我们一次拖动多个节点。请参见此处的小提琴:

连同相应的代码:

var width = 400,
    height = 500,
    shiftKey;

var svg = d3.select("body")
    .attr("tabindex", 1)
    .on("keydown.brush", keydown)
    .on("keyup.brush", keyup)
    .each(function() { this.focus(); })
  .append("svg")
    .attr("width", width)
    .attr("height", height);

var link = svg.append("g")
    .attr("class", "link")
  .selectAll("line");

var brush = svg.append("g")
    .datum(function() { return {selected: false, previouslySelected: false}; })
    .attr("class", "brush");

var node = svg.append("g")
    .attr("class", "node")
  .selectAll("circle");

var graph = {"nodes":[{"x":444,"y":275},{"x":378,"y":324},{"x":478,"y":278},{"x":471,"y":256},{"x":382,"y":269},{"x":371,"y":247},{"x":359,"y":276},{"x":364,"y":302},{"x":400,"y":330},{"x":388,"y":298},{"x":524,"y":296},{"x":570,"y":243},{"x":552,"y":159},{"x":502,"y":287},{"x":511,"y":313},{"x":513,"y":265},{"x":602,"y":132},{"x":610,"y":90},{"x":592,"y":91},{"x":575,"y":89},{"x":607,"y":73},{"x":591,"y":68},{"x":574,"y":73},{"x":589,"y":149},{"x":620,"y":205},{"x":621,"y":230},{"x":589,"y":234},{"x":602,"y":223},{"x":548,"y":188},{"x":532,"y":196},{"x":548,"y":114},{"x":575,"y":174},{"x":497,"y":250},{"x":576,"y":196},{"x":504,"y":201},{"x":494,"y":186},{"x":482,"y":199},{"x":505,"y":219},{"x":486,"y":216},{"x":590,"y":306},{"x":677,"y":169},{"x":657,"y":258},{"x":667,"y":205},{"x":552,"y":227},{"x":518,"y":173},{"x":473,"y":125},{"x":796,"y":260},{"x":731,"y":272},{"x":642,"y":288},{"x":576,"y":269},{"x":605,"y":187},{"x":559,"y":289},{"x":544,"y":356},{"x":505,"y":365},{"x":579,"y":289},{"x":619,"y":282},{"x":574,"y":329},{"x":664,"y":306},{"x":627,"y":304},{"x":643,"y":327},{"x":664,"y":348},{"x":665,"y":327},{"x":653,"y":317},{"x":650,"y":338},{"x":622,"y":321},{"x":633,"y":338},{"x":647,"y":357},{"x":718,"y":362},{"x":636,"y":240},{"x":640,"y":227},{"x":617,"y":249},{"x":631,"y":254},{"x":566,"y":213},{"x":713,"y":322},{"x":716,"y":298},{"x":666,"y":241},{"x":627,"y":355}],"links":[{"source":1,"target":0},{"source":2,"target":0},{"source":3,"target":0},{"source":3,"target":2},{"source":4,"target":0},{"source":5,"target":0},{"source":6,"target":0},{"source":7,"target":0},{"source":8,"target":0},{"source":9,"target":0},{"source":11,"target":10},{"source":11,"target":3},{"source":11,"target":2},{"source":11,"target":0},{"source":12,"target":11},{"source":13,"target":11},{"source":14,"target":11},{"source":15,"target":11},{"source":17,"target":16},{"source":18,"target":16},{"source":18,"target":17},{"source":19,"target":16},{"source":19,"target":17},{"source":19,"target":18},{"source":20,"target":16},{"source":20,"target":17},{"source":20,"target":18},{"source":20,"target":19},{"source":21,"target":16},{"source":21,"target":17},{"source":21,"target":18},{"source":21,"target":19},{"source":21,"target":20},{"source":22,"target":16},{"source":22,"target":17},{"source":22,"target":18},{"source":22,"target":19},{"source":22,"target":20},{"source":22,"target":21},{"source":23,"target":16},{"source":23,"target":17},{"source":23,"target":18},{"source":23,"target":19},{"source":23,"target":20},{"source":23,"target":21},{"source":23,"target":22},{"source":23,"target":12},{"source":23,"target":11},{"source":24,"target":23},{"source":24,"target":11},{"source":25,"target":24},{"source":25,"target":23},{"source":25,"target":11},{"source":26,"target":24},{"source":26,"target":11},{"source":26,"target":16},{"source":26,"target":25},{"source":27,"target":11},{"source":27,"target":23},{"source":27,"target":25},{"source":27,"target":24},{"source":27,"target":26},{"source":28,"target":11},{"source":28,"target":27},{"source":29,"target":23},{"source":29,"target":27},{"source":29,"target":11},{"source":30,"target":23},{"source":31,"target":30},{"source":31,"target":11},{"source":31,"target":23},{"source":31,"target":27},{"source":32,"target":11},{"source":33,"target":11},{"source":33,"target":27},{"source":34,"target":11},{"source":34,"target":29},{"source":35,"target":11},{"source":35,"target":34},{"source":35,"target":29},{"source":36,"target":34},{"source":36,"target":35},{"source":36,"target":11},{"source":36,"target":29},{"source":37,"target":34},{"source":37,"target":35},{"source":37,"target":36},{"source":37,"target":11},{"source":37,"target":29},{"source":38,"target":34},{"source":38,"target":35},{"source":38,"target":36},{"source":38,"target":37},{"source":38,"target":11},{"source":38,"target":29},{"source":39,"target":25},{"source":40,"target":25},{"source":41,"target":24},{"source":41,"target":25},{"source":42,"target":41},{"source":42,"target":25},{"source":42,"target":24},{"source":43,"target":11},{"source":43,"target":26},{"source":43,"target":27},{"source":44,"target":28},{"source":44,"target":11},{"source":45,"target":28},{"source":47,"target":46},{"source":48,"target":47},{"source":48,"target":25},{"source":48,"target":27},{"source":48,"target":11},{"source":49,"target":26},{"source":49,"target":11},{"source":50,"target":49},{"source":50,"target":24},{"source":51,"target":49},{"source":51,"target":26},{"source":51,"target":11},{"source":52,"target":51},{"source":52,"target":39},{"source":53,"target":51},{"source":54,"target":51},{"source":54,"target":49},{"source":54,"target":26},{"source":55,"target":51},{"source":55,"target":49},{"source":55,"target":39},{"source":55,"target":54},{"source":55,"target":26},{"source":55,"target":11},{"source":55,"target":16},{"source":55,"target":25},{"source":55,"target":41},{"source":55,"target":48},{"source":56,"target":49},{"source":56,"target":55},{"source":57,"target":55},{"source":57,"target":41},{"source":57,"target":48},{"source":58,"target":55},{"source":58,"target":48},{"source":58,"target":27},{"source":58,"target":57},{"source":58,"target":11},{"source":59,"target":58},{"source":59,"target":55},{"source":59,"target":48},{"source":59,"target":57},{"source":60,"target":48},{"source":60,"target":58},{"source":60,"target":59},{"source":61,"target":48},{"source":61,"target":58},{"source":61,"target":60},{"source":61,"target":59},{"source":61,"target":57},{"source":61,"target":55},{"source":62,"target":55},{"source":62,"target":58},{"source":62,"target":59},{"source":62,"target":48},{"source":62,"target":57},{"source":62,"target":41},{"source":62,"target":61},{"source":62,"target":60},{"source":63,"target":59},{"source":63,"target":48},{"source":63,"target":62},{"source":63,"target":57},{"source":63,"target":58},{"source":63,"target":61},{"source":63,"target":60},{"source":63,"target":55},{"source":64,"target":55},{"source":64,"target":62},{"source":64,"target":48},{"source":64,"target":63},{"source":64,"target":58},{"source":64,"target":61},{"source":64,"target":60},{"source":64,"target":59},{"source":64,"target":57},{"source":64,"target":11},{"source":65,"target":63},{"source":65,"target":64},{"source":65,"target":48},{"source":65,"target":62},{"source":65,"target":58},{"source":65,"target":61},{"source":65,"target":60},{"source":65,"target":59},{"source":65,"target":57},{"source":65,"target":55},{"source":66,"target":64},{"source":66,"target":58},{"source":66,"target":59},{"source":66,"target":62},{"source":66,"target":65},{"source":66,"target":48},{"source":66,"target":63},{"source":66,"target":61},{"source":66,"target":60},{"source":67,"target":57},{"source":68,"target":25},{"source":68,"target":11},{"source":68,"target":24},{"source":68,"target":27},{"source":68,"target":48},{"source":68,"target":41},{"source":69,"target":25},{"source":69,"target":68},{"source":69,"target":11},{"source":69,"target":24},{"source":69,"target":27},{"source":69,"target":48},{"source":69,"target":41},{"source":70,"target":25},{"source":70,"target":69},{"source":70,"target":68},{"source":70,"target":11},{"source":70,"target":24},{"source":70,"target":27},{"source":70,"target":41},{"source":70,"target":58},{"source":71,"target":27},{"source":71,"target":69},{"source":71,"target":68},{"source":71,"target":70},{"source":71,"target":11},{"source":71,"target":48},{"source":71,"target":41},{"source":71,"target":25},{"source":72,"target":26},{"source":72,"target":27},{"source":72,"target":11},{"source":73,"target":48},{"source":74,"target":48},{"source":74,"target":73},{"source":75,"target":69},{"source":75,"target":68},{"source":75,"target":25},{"source":75,"target":48},{"source":75,"target":41},{"source":75,"target":70},{"source":75,"target":71},{"source":76,"target":64},{"source":76,"target":65},{"source":76,"target":66},{"source":76,"target":63},{"source":76,"target":62},{"source":76,"target":48},{"source":76,"target":58}]}

  graph.links.forEach(function(d) {
    d.source = graph.nodes[d.source];
    d.target = graph.nodes[d.target];
  });

  link = link.data(graph.links).enter().append("line")
      .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; });

  brush.call(d3.svg.brush()
        .x(d3.scale.identity().domain([0, width]))
        .y(d3.scale.identity().domain([0, height]))
        .on("brushstart", function(d) {
          node.each(function(d) { d.previouslySelected = shiftKey && d.selected; });
        })
        .on("brush", function() {
          var extent = d3.event.target.extent();
          node.classed("selected", function(d) {
            return d.selected = d.previouslySelected ^
                (extent[0][0] <= d.x && d.x < extent[1][0]
                && extent[0][1] <= d.y && d.y < extent[1][1]);
          });
        })
        .on("brushend", function() {
          d3.event.target.clear();
          d3.select(this).call(d3.event.target);
        }));

var force = d3.layout.force()
        .charge(-120)
        .linkDistance(30)
        .nodes(graph.nodes)
        .links(graph.links)
        .size([width, height])
        .start();

node = node.data(graph.nodes).enter().append("circle")
      .attr("r", 4)
      .attr("cx", function(d) { return d.x; })
      .attr("cy", function(d) { return d.y; })
      .on("mousedown", function(d) {
        if (!d.selected) { // Don't deselect on shift-drag.
          if (!shiftKey) node.classed("selected", function(p) { return p.selected = d === p; });
          else d3.select(this).classed("selected", d.selected = true);
        }
      })
      .on("mouseup", function(d) {
        if (d.selected && shiftKey) d3.select(this).classed("selected", d.selected = false);
      })
      .call(d3.behavior.drag()
              .on("dragstart", function(d1) {
                  node.filter(function(d) { return d.selected; })
                  .each(function(d) { d.fixed |= 2; })
                  })
              .on("drag", function(d1) {
                  node.filter(function(d) { return d.selected; })
                  .each(function(d) {
                          d.x += d3.event.dx;
                          d.y += d3.event.dy;

                          d.px += d3.event.dx;
                          d.py += d3.event.dy;
                          })

                  force.resume();
                  })
              .on("dragend", function(d) {
                  node.filter(function(d) { return d.selected; })
                  .each(function(d) { d.fixed &= ~6; })
                  }));

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

      };

      force.on("tick", tick);


function keydown() {
  shiftKey = d3.event.shiftKey || d3.event.metaKey;
}

function keyup() {
  shiftKey = d3.event.shiftKey || d3.event.metaKey;
}
var宽度=400,
高度=500,
移位键;
var svg=d3.选择(“主体”)
.attr(“tabindex”,1)
.on(“按键向下。笔刷”,按键向下)
.on(“keyup.brush”,keyup)
.each(函数(){this.focus();})
.append(“svg”)
.attr(“宽度”,宽度)
.attr(“高度”,高度);
var link=svg.append(“g”)
.attr(“类”、“链接”)
。选择全部(“行”);
var brush=svg.append(“g”)
.datum(函数(){return{selected:false,previouslySelected:false};})
.attr(“类”、“刷”);
var node=svg.append(“g”)
.attr(“类”、“节点”)
.选择全部(“圆圈”);
从“节点”的节点来看,各节点““{“x”:444”,y:2757},,{“x”:378,“y”:324},{“x”:478,“y”:278,y:278},,,{“x”:478,y:278,,{“x”:478,y”节点节点“““,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,{“x”:478,”478,“y:478”,y:277 7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,478,”478,”478,“478,”478,”478,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,502,“y”:287},{“x”:511,“y”:313},{“x”:513,“y”:265},{“x”:602,“y”:132},{“x”:610,“y”:90},{“x”:592,“y”:592,“y”:592,y:91},,{“x”:575,y:89},,{“x”:607,y:73},{“x”:591,y:73},{“x”:592,y:592,y:592,y:91},,{“x”:575,y:575,y:89},,,,{“x”x:607,73,,,,,,,,,{“x”:73},,,,,,,{“x”:73,,,,,,,,,,{“x”:73,,,,,,,,,,,,,,:68,,,,,,,,,,,,,,:68,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,{“x:597 7 7 7 7 7 7 7 7 7,”7 7 7 7 7 7 7 7 7 7 y:250},{x:576,y:196},{x:504,y:201},{x:494,y:186},{x:482,y:199},{x:505,y:219}“x”:677,“y”:1697,,{“x”:657,“y”:258,,,{“x”:587,y”:258,,,{“x”:657,“y”:258,,,{“x”:486,”x:486,“y”:486,“y”:486,“y”:486,“y”:目前,目前,,,,,{“x”x:目前,目前,,,,{“x”x:677 7 7,7,y:677,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,{“x”:487 7 7 7 7 7 7 7 7 7 7 7 7 7万万万万万万万万万万万万万万万万万万万万万万万万万万万万万,或,,,,,,,,,,,,,,,,,,,,,,,,,,,,9},{“x”:544,“y”:356},{“x”:505,“y”:365},{“x”:579,“y”:289},{“x”:619,“y”:282},{“x”:574,“y”:329},{“x”7月7日,y:327},,{“x”:664,“y”:327},,{“x”:664,“y”:327},{“x”:664,y:348},,{“x”:665,y:665,y:327},,{“x”:3067,y:3067,,,,{“x”x:627,y:304,y:304,,,,,,{“x”x:643 3,y:327,,,,,,,,,,,{“x:664 7 7 7 7 7,”3万万万万万万;3067,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,{“x”:631,“y”:254},{“x”:566,“y”:213},{“x”:713,“y”:322},{“x”:716,“y”:298},{“x”:666,“y”:241},{“x”:627,“y”:355}],“链接:[{“源”:1,“目标”:0},{“源”:2,“目标”:0},{“源”:3,“目标”:0},{“源”:3,“目标”:2},{“源”:4,“目标”:0},{“源”:5,“目标”:0},{“源”:6,“目标”:0},{“源”:7,“目标”:0},{“源”:8,“目标”:0},{“源”:9,“目标”:0},{“源”:9,“目标”:0},{“源”:11,“目标”:10},{“源”:11,“目标”:11,{“源”:11,{,“目标”:0},{“源”:12,“目标”:11},{“源”:13,“目标”:11},{“源”:14,“目标”:11},{“源”:15,“目标”:11},{“源”:17,“目标”:16},{“源”:18,“目标”:16},{“源”:18,“目标”:17},{“源”:19,“目标”:16},{“源”:19,“目标”:17},{“源”:19,“目标”:18},{“源”:20,“目标”:16,{“源”:20,“目标”:17,{“源”:18},{“源”:20,“目标”:19},{“源”:21,“目标”:16},{“源”:21,“目标”:17},{“源”:21,“目标”:18},{“源”:21,“目标”:19},{“源”:21,“目标”:20},{“源”:22,“目标”:16},{“源”:22,“目标”:17},{“源”:22,“目标”:18},{“源”:22,“目标”:19},{“源”:22,“目标”:20},{“源”:22,“目标”:21},{“源”:23,{“源”:23,{“:23,“目标”:17},{“源”:23,“目标”:18},{“源”:23,“目标”:19},{“源”:23,“目标”:20},{“源”:23,“目标”:21},{“源”:23,“目标”:22},{“源”:23,“目标”:12},{“源”:23,“目标”:11},{“源”:24,“目标”:23},{“源”:24,“目标”:11},{“源”:25,“目标”:24},{“源”:25,“目标”:23,{“源”:25,“目标”:1}