微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

d3.js - 在力定向图组上添加背景矩形

如何解决d3.js - 在力定向图组上添加背景矩形

我想在第2组中添加一个背景矩形,想法是添加一个g元素并将第2组的所有节点附加到g元素,然后使用g元素bBox绘制一个矩形。

但我不知道如何将现有节点移动到 g 元素! (也许不可能?)。

示例代码如下:

var graph = {
  nodes:[
    {id: "A",name:'AAAA',group: 1},{id: "B",name:'BBBB',group: 2},{id: "C",name:'CCCC',{id: "D",name:'dddd',{id: "E",name:'EEEE',{id: "F",name:'FFFF',group: 3},{id: "G",name:'GGGG',{id: "H",name:'HHHH',{id: "I",name:'IIII',group: 3}
  ],links:[
    {source: "A",target: "B",value: 1},{source: "A",target: "C",target: "D",target: "E",target: "F",target: "G",target: "H",target: "I",]
};

var width = 400
var height = 200
var svg = d3.select('body').append('svg')
.attr('width',width)
.attr('height',height)
.style('border','1px solid red')

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

var simulation = d3.forceSimulation()
.force("link",d3.forceLink().id(function(d) { return d.id; }).distance(100))
.force("charge",d3.forceManyBody())
.force("x",d3.forceX(function(d){
  if(d.group === 2){
    return width/3
  } else if (d.group === 3){
    return 2*width/3
  } else {
    return width/2 
  }
}))
.force("y",d3.forceY(height/2))
.force("center",d3.forceCenter(width / 2,height / 2));

var g = svg.append("g")
.attr("class","nodes")
.selectAll("circle")
.data(graph.nodes)
.enter()

var w = 80
var txts = g.append('text')
.attr('class','text')
.attr('text-anchor','middle')
.attr("dominant-baseline","central")
.attr('fill','black')
.text(d => d.name)
.each((d,i,n) => {
  var bBox = d3.select(n[i]).node().getBBox()
  var margin = 4
  bBox.x -= margin
  bBox.y -= margin
  bBox.width += 2*margin
  bBox.height += 2*margin
  if (bBox.width < w) {
    bBox.width = w
  }
  d.bBox = bBox
})

var node = g
.insert('rect','text')
.attr('stroke','black')
.attr('width',d => d.bBox.width)
.attr('height',d => d.bBox.height)
.attr("fill",function(d) { return color(d.group); })
.attr('fill-opacity',0.3)
.call(d3.drag()
      .on("start",dragstarted)
      .on("drag",dragged)
      .on("end",dragended));

var link = svg.append("g")
.attr("class","links")
.attr('stroke','black')
.selectAll("line")
.data(graph.links)
.enter().append("path")
.attr("stroke-width",function(d) { return Math.sqrt(d.value); });

simulation
  .nodes(graph.nodes)
  .on("tick",ticked);

simulation.force("link")
  .links(graph.links);

function ticked() {
  link
    .attr("d",function(d) { 
    var ax = d.source.x
    var ay = d.source.y
    var bx = d.target.x
    var by = d.target.y
    if (bx < ax) {
      ax -= w/2
      bx += w/2
    }else{
      ax += w/2
      bx -= w/2
    }
    var path = ['M',ax,ay,'L',bx,by]
    return path.join(' ')
  })

  txts.attr('x',d => d.x)
    .attr('y',d => d.y)

  node
    .attr("x",function(d) { return d.x - d.bBox.width/2; })
    .attr("y",function(d) { return d.y - d.bBox.height/2; });

}

function dragstarted(event,d) {
  if (!event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(event,d) {
  d.fx = event.x;
  d.fy = event.y;
}

function dragended(event,d) {
  if (!event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

解决方法

力模拟不使用 DOM 做任何事情。它只是计算节点应该在哪里,你如何渲染它们,如果你渲染它们,取决于你。因此,将一些节点放在 g 中而不是其他节点不是问题。例如,我们可以为第 2 组添加一个 g,遍历所有节点,如果它们来自第 2 组,则将它们与 DOM 分离,然后将它们重新附加到新的 g

var parent = d3.select("g").append("g").lower();
node.each(function(d) {
    if (d.group == 2) {
      d3.select(this).remove();
      parent.append((d)=>this);      
    }
  })

然后我们需要做的就是创建一个背景矩形:

var background = d3.select("g")
  .append("rect")
  .lower()  // so it is behind the nodes.
  ....

并在勾选时使用 g 的新边界框更新它,如下所示。

var graph = {
  nodes:[
    {id: "A",name:'AAAA',group: 1},{id: "B",name:'BBBB',group: 2},{id: "C",name:'CCCC',{id: "D",name:'DDDD',{id: "E",name:'EEEE',{id: "F",name:'FFFF',group: 3},{id: "G",name:'GGGG',{id: "H",name:'HHHH',{id: "I",name:'IIII',group: 3}
  ],links:[
    {source: "A",target: "B",value: 1},{source: "A",target: "C",target: "D",target: "E",target: "F",target: "G",target: "H",target: "I",]
};

var width = 400
var height = 200
var svg = d3.select('body').append('svg')
.attr('width',width)
.attr('height',height)
.style('border','1px solid red')

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

var simulation = d3.forceSimulation()
.force("link",d3.forceLink().id(function(d) { return d.id; }).distance(100))
.force("charge",d3.forceManyBody())
.force("x",d3.forceX(function(d){
  if(d.group === 2){
    return width/3
  } else if (d.group === 3){
    return 2*width/3
  } else {
    return width/2 
  }
}))
.force("y",d3.forceY(height/2))
.force("center",d3.forceCenter(width / 2,height / 2));

var g = svg.append("g")
.attr("class","nodes")
.selectAll("circle")
.data(graph.nodes)
.enter()

var w = 80
var txts = g.append('text')
.attr('class','text')
.attr('text-anchor','middle')
.attr("dominant-baseline","central")
.attr('fill','black')
.text(d => d.name)
.each((d,i,n) => {
  var bbox = d3.select(n[i]).node().getBBox()
  var margin = 4
  bbox.x -= margin
  bbox.y -= margin
  bbox.width += 2*margin
  bbox.height += 2*margin
  if (bbox.width < w) {
    bbox.width = w
  }
  d.bbox = bbox
})

var node = g
.insert('rect','text')
.attr('stroke','black')
.attr('width',d => d.bbox.width)
.attr('height',d => d.bbox.height)
.attr("fill",function(d) { return color(d.group); })
.attr('fill-opacity',0.3)
.call(d3.drag()
      .on("start",dragstarted)
      .on("drag",dragged)
      .on("end",dragended));

// Start Changes 1/2
var parent = d3.select("g").append("g").lower();
node.each(function(d) {
    if (d.group == 2) {
      d3.select(this).remove();
      parent.append((d)=>this);      
    }
  })
var background = d3.select("g")
  .append("rect")
  .lower()
  .attr("ry",5)
  .attr("rx",5)
  .attr("fill","#ccc")
  .attr("stroke","#999")
  .attr("stroke-width",1);
// End Changes 1/2
  
      

var link = svg.append("g")
.attr("class","links")
.attr('stroke','black')
.selectAll("line")
.data(graph.links)
.enter().append("path")
.attr("stroke-width",function(d) { return Math.sqrt(d.value); });

simulation
  .nodes(graph.nodes)
  .on("tick",ticked);

simulation.force("link")
  .links(graph.links);

function ticked() {
  link
    .attr("d",function(d) { 
    var ax = d.source.x
    var ay = d.source.y
    var bx = d.target.x
    var by = d.target.y
    if (bx < ax) {
      ax -= w/2
      bx += w/2
    }else{
      ax += w/2
      bx -= w/2
    }
    var path = ['M',ax,ay,'L',bx,by]
    return path.join(' ')
  })

  txts.attr('x',d => d.x)
    .attr('y',d => d.y)

  node
    .attr("x",function(d) { return d.x - d.bbox.width/2; })
    .attr("y",function(d) { return d.y - d.bbox.height/2; });
    
// Start changes 2/2
var box = parent.node().getBBox() 

background.attr("width",box.width+10)
  .attr("height",box.height+10)
  .attr("x",box.x-5)
  .attr("y",box.y-5);
//End Changes 2/2

}

function dragstarted(event,d) {
  if (!event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(event,d) {
  d.fx = event.x;
  d.fy = event.y;
}

function dragended(event,d) {
  if (!event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.js"></script>

如果您想要多个组,或者拥有动态数据,则这种方法并不理想 - 需要稍微修改联接或数据结构以使更规范的方法起作用 - 我可能会重新审视今晚晚些时候用另一种方式。照原样,此解决方案可能对您现有的代码影响最小。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。