d3.js 带嵌套圆的D3力有向图节点

8yoxcaq7  于 2022-11-12  发布在  其他
关注(0)|答案(2)|浏览(296)

在我的力有向图中,我希望我的每个节点都有:

  • 两圈。
  • 两个圆的大小必须相同。
  • 这些圆圈需要在彼此的顶部,这样在视觉上我们只能看到一个。
  • 下面的圆是用红色填充的圆。
  • 上面的圆圈是一个有网址作为填充。

我做错了什么?

const node = svg.append("g")
        .attr("stroke", nodeStroke)
        .attr("stroke-opacity", nodeStrokeOpacity)
        .attr("stroke-width", nodeStrokeWidth)

        .selectAll("circle")
        .data(nodes)
        .join("circle")
        .style("fill", "red")
        .attr("r", 30)
        
        node.selectAll("circle")
        .data(nodes)
        .join("circle")
        .style("fill", d => `url(#${d.id})`)
        .attr("r", 30)
        .call(drag(simulation))
  • 编辑2* 在尝试将建议的解决方案应用到我的代码中后,我成功地创建了我想要的圆圈。剩下的问题是,我的圆圈似乎都堆积在SVG的中心,因为我的拖动(模拟)不再对圆圈起作用了。

下面是我的代码的一个截图和更详细的部分:

ForceGraph(
    nodes, // an iterable of node objects (typically [{id}, …])
    links // an iterable of link objects (typically [{src, target}, …])
    ){
    var nodeId = d => d.id // given d in nodes, returns a unique identifier (string)
    const nodeStrength = -450 // -1750
    const linkDistance = 100
    const linkStrokeOpacity = 1 // link stroke opacity
    const linkStrokeWidth = 3 // given d in links, returns a stroke width in pixels
    const linkStrokeLinecap = "round" // link stroke linecap
    const linkStrength =1
    var width = this.$refs.mapFrame.clientWidth // scale to parent container
    var height = this.$refs.mapFrame.clientHeight // scale to parent container
 
    const N = d3.map(nodes, nodeId);
    

    // Replace the input nodes and links with mutable objects for the simulation.
    nodes = nodes.map(n => Object.assign({}, n));
    links = links.map(l => ({
        orig: l,
        //Object.assign({}, l)
        source: l.src,
        target: l.target
    }));
  

    // Construct the forces.
    const forceNode = d3.forceManyBody();
    const forceLink = d3.forceLink(links).id(({index: i}) => N[i]);
    forceNode.strength(nodeStrength);
    forceLink.strength(linkStrength);
    forceLink.distance(linkDistance)


    const simulation = d3.forceSimulation(nodes)
        .force(link, forceLink)
        .force("charge", forceNode)
        .force("x", d3.forceX())
        .force("y", d3.forceY())
        .on("tick", ticked);


    const svg = d3.create("svg")
    .attr("id", "svgId")
        .attr("preserveAspectRatio", "xMidYMid meet")
        .attr("viewBox", [-width/2,-height/2, width,height])
        .classed("svg-content-responsive", true)

    const defs = svg.append('svg:defs');
  

    defs.selectAll("pattern")
    .data(nodes)
    .join(
      enter => {
        // For every new <pattern>, set the constants and append an <image> tag
         const patterns = enter
          .append("pattern")
          .attr("preserveAspectRatio", "none")
          .attr("viewBox", [0,0, 100,100])
          .attr("width", 1)
          .attr("height", 1);
          
        patterns
          .append("image")
          .attr("width", 80)
          .attr("height", 80)
          .attr("x", 10)
          .attr("y", 10);
        return patterns;
      }
    )
    // For every <pattern>, set it to point to the correct
    // URL and have the correct (company) ID
    .attr("id", d => d.id)
    .select("image")
    .datum(d => {
      return d;
    })
    .attr("xlink:href", d => {
      return d.image
    })
    
 
    
     

    const link = svg.append("g")
        .attr("stroke-opacity", linkStrokeOpacity)
        .attr("stroke-width",  linkStrokeWidth)
        .attr("stroke-linecap", linkStrokeLinecap)
        .selectAll("line")
        .data(links)
        .join("line")
        ;
        link.attr("stroke", "white")
 
     
   
       

    

        

    var node  
 var group = svg
        .selectAll(".circle-group")
        .data(nodes)
        .join(enter => {
          node = enter.append("g")        
            .attr("class", "circle-group");
          node.append("circle")
            .attr("class", "background") // classes aren't necessary here, but they can help with selections/styling
            .style("fill", "red")
            .attr("r", 30);
          node.append("circle")
            .attr("class", "foreground") // classes aren't necessary here, but they can help with selections/styling
            .style("fill", d => `url(#${d.id})`)
            .attr("r", 30)          
         
        }).call(drag(simulation))
        
        

    function ticked() {
        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);
        node
        .attr("cx", d => d.x)
        .attr("cy", d => d.y);
    }

    function drag(simulation) {    
        function dragstarted(event) {
        if (!event.active) simulation.alphaTarget(0.3).restart();
        event.subject.fx = event.subject.x;
        event.subject.fy = event.subject.y;
        }
        
        function dragged(event) {
        event.subject.fx = event.x;
        event.subject.fy = event.y;
        }
        
        function dragended(event) {
        if (!event.active) simulation.alphaTarget(0);
        event.subject.fx = null;
        event.subject.fy = null;
        }

        return d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended);
    }


    return Object.assign(svg.node() );
    }//forcegraph
bsxbgnwa

bsxbgnwa1#

正如Andrew Reid所暗示的,既然你想在同一个节点上添加多个圆圈,你可能会想在每个节点上添加一个svg group元素(g元素),这样你就可以通过重复选择同一个组并追加到它上面来添加多个圆圈。

无更新

例如,这里有一个使用预填充图像的简单例子,它是由维基共享资源提供的。它适用于只设置一次数据的情况:
第一个

包含更新

当更新数据时,你只想在新元素中添加圆圈,这里我们可以将圆圈添加到join的第一个函数参数中(enter函数)。
第一个

cmssoen2

cmssoen22#

这是预期的行为:
node是一组圆圈-您的代码块位于:

svg.append("g")
    .attr("stroke", nodeStroke)
    .attr("stroke-opacity", nodeStrokeOpacity)
    .attr("stroke-width", nodeStrokeWidth)
    .selectAll("circle")
    .data(nodes)
    .join("circle")
    .style("fill", "red")
    .attr("r", 30)

最终返回一组圆,这就是nodes所引用的内容。第二个selectAll语句尝试将一个圆附加到此组圆中。circle SVG元素不能包含子circle元素,因为这是无效的语法,因此您将无法呈现这些子circle元素。
如果我们把这个链拆开,我们就可以得到一个父节点g的选择来附加两组圆:

const g = svg.append("g")
    .attr("stroke", nodeStroke)
    .attr("stroke-opacity", nodeStrokeOpacity)
    .attr("stroke-width", nodeStrokeWidth)

然后,我们可以使用g.selectAll()来确保我们将圆添加到法律的的父对象,即g。但是,如果我们不对代码进行一些调整,您将只能得到一组圆,因为第二个selectAll()语句将选择第一个selectAll()语句之后添加的所有圆:第一次执行此操作时,没有圆,所有圆都被输入/追加。第二次执行此操作时,数据数组中的每一项都有一个圆,因此没有新的圆被追加。
您可以在selectAll陈述式中套用类别名称来加以区分:

.selectAll(".circleA")
 .data(...)
 .join("circle")
 .attr("class", "circleA")

但是,如果您从未打算添加圆一次,并且不修改其数据,则可以使用.selectAll("null")

相关问题