d3 избегать репликации g при вызове функции

#d3.js

#d3.js

Вопрос:

Я пытаюсь добавить новые узлы в force directed, но когда я вызываю функцию QuickSearch(value) , она дублирует график.

Кто-нибудь может посоветовать мне, как добавить только новый узел.

 // Graph variables

var w = window.innerWidth;
var h = window.innerHeight;

var svg = d3.select("#svgData"),
    scheme = ['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628','#f781bf','#999999'],
    width =  svg.attr(w),
    height =  svg.attr(h),
    color = d3.scaleOrdinal(d3.schemeCategory20);
    //color = d3.scaleOrdinal(scheme);

var info = {
        "nodes": [
            {"id": "1", "name": "1", "group": 1},
            {"id": "2", "name": "2", "group": 1},
            {"id": "3", "name": "3", "group": 1},
            {"id": "4", "name": "4", "group": 1},
            {"id": "5", "name": "5", "group": 1}
                
        ],
        "links": [
            {"source": "1", "target": "2", "value": 1},
            {"source": "1", "target": "3", "value": 1},
            {"source": "1", "target": "4", "value": 1},
            {"source": "1", "target": "5", "value": 1}
        ]
      }

var marker = d3.select("#svgData").append('defs')
    .append('marker')
    .attr("id", "Triangle")
    .attr('viewBox', '-0 -5 10 10')
    .attr("refX", 25)
    .attr("refY", 0)
    .attr("markerUnits", 'userSpaceOnUse')
    .attr("orient", 'auto')
    .attr("markerWidth", 13)
    .attr("markerHeight", 13)
    .attr('xoverflow', 'visible')
    .append('path')
    .attr("d", 'M 0,-5 L 10 ,0 L 0,5');

   


    function QuickSearch(value) {
        var new_node = {};
        //console.log(info.nodes);
        new_node = {"id": value, "name": value, "group": 1};
        info.nodes.findIndex(x => x.id == new_node.id) == -1 ? info.nodes.push(new_node) : console.log("object already exists")
        createGraph(info);
    };
    
  function createGraph(graph) {
    // Zoom
    var zoom = d3.zoom()
        .scaleExtent([0, 10])
        .on("zoom", zoomed);

    d3.select("#svgData").call(zoom);

    function zoomed() {
        const currentTransform = d3.event.transform;
        container.attr("transform", currentTransform);
      }

    // Simulation

    var simulation = d3.forceSimulation()
        .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(200))
        .force("charge", d3.forceManyBody().strength(10).distanceMax(1000))
        .force("center", d3.forceCenter(w / 2, h / 2))
        .force('collision', d3.forceCollide().radius(30))

    var container = svg.append("g");

    var link = container.append("g")
      .attr("class", "links")
      .selectAll("path")
      .data(graph.links)
      .enter().append("path")
      .attr("marker-end", "url(#Triangle)");

    var value = d3.select("g.links")
      .selectAll("text")
      .data(graph.links)
      .enter().append("text")
      .attr("dy", ".35em")
      .attr("text-anchor", "middle")
      .text(function(d) {
        return d.value;
      });

    var node = container.append("g")
      .attr("class", "nodes")
      .selectAll("circle")
      .data(graph.nodes)
      .enter().append("circle")
      .attr('stroke-width', 3)
      .attr('stroke', function(d) { return color(d.group); })
      .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended))
      .on("click", listInfo);

    var lable = d3.select(".nodes")
      .selectAll("text")
      .data(graph.nodes)
      .enter().append("text")
      .text(function(d) { return d.name; });


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

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

    function ticked() {
      link
        .attr("d", function(d) {
            return "M"   d.source.x   ","   d.source.y
                      "C"   d.source.x   ","   (d.source.y   d.target.y) / 2
                      " "   d.target.x   ","   d.target.y
                      " "   d.target.x   ","   d.target.y;
            })
        .attr("stroke-dasharray", function() { 
            return this.getTotalLength() - 25;
            });

      value
        .attr("x", function(d) { return (d.source.x   d.target.x)/2; })
        .attr("y", function(d) { return (d.source.y   d.target.y)/2; });

      node
        .attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; });

      lable
        .attr("x", function(d) { return d.x; })
        .attr("y", function(d) { return d.y - 30; });

    }

    function slided(d) {
      zoom.scaleTo(svg, d3.select(this).property("value"));
    }

    function dragstarted(d) {
      if (!d3.event.active) simulation.alphaTarget(0.1).restart();
      d.fx = d.x;
      d.fy = d.y;
      d3.select(this).classed("dragging", true);
      d.fixed = true;
    }

    function dragged(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
      //fix_nodes(d);
    }
    
    // Preventing other nodes from moving while dragging one node
    function fix_nodes(this_node) {
        node.each(function(d){
            if (this_node != d){
                d.fx = d.x;
                d.fy = d.y;
            }
        });
    }

    function dragended(d) {
      if (!d3.event.active) simulation.alphaTarget(0);
      d3.select(this).classed("dragging", false);
      //d.fixed = true;
    }

    var nodeText;
    function listInfo(d) {
      d3.select(this)
      .select("text")
      .text(function(d) { return d.name;})
      var nodeText = d.name;

    document.getElementById("nodeId").innerHTML = nodeText;
    }
};
 

Я пробовал несколько способов использования .exit().remove() option, но у меня это не работает.

Комментарии:

1. Каждый раз, когда вы запускаете createGraph, вы создаете новый g и запускаете с ним цикл ввода / обновления / выхода. Но поскольку этот новый g пуст, выходить или обновлять нечего. Вы вводите все каждый раз. Вместо этого либо не добавляйте этот g, а просто добавляйте все непосредственно в svg, либо добавляйте этот g один раз вне функции createGraph. Я уверен, что есть хороший существующий ответ на этот вопрос, но у меня возникли проблемы с его поиском, если я его найду, я свяжусь с ним здесь.

2. Спасибо, Эндрю, звучит многообещающе. Я попробую переместить g за пределы функции.

3. @Andrew, еще раз спасибо. Я переместил переменную «контейнер» за пределы функции, и она работает.