Обновление узлов и ссылок с помощью меток в графе сети с принудительной ориентацией d3 неправильно удаляет узлы - PullRequest
1 голос
/ 14 апреля 2020

Я пытаюсь создать граф сети, ориентированный на силу d3, где на основе заданной сети я могу обновить сеть, изменив ссылки и узлы и повторно обновив их в svg.

Я настроил код чтобы можно было создать элемент ag, заключив в него каждый круг, чтобы я мог добавить текст в этот же элемент g.

Но теперь метки работают отлично, но когда я перехожу с графика 3 на график 1, нажимая сначала нажмите кнопку 3, затем нажмите кнопку 1, ссылки, которые являются избыточными (a-> d, a-> f), удаляются идеально, но узлы, которые являются избыточными (e и f), остаются в svg.

I Не удалось выяснить, был ли это неправильный выбор или мне нужно немного подправить функцию tick ()?

Вот код:

    var height = 200;
    var width = 200;
    
    const graph = {
        "nodes": [
            { "name": "a", "group": 1 },
            { "name": "b", "group": 2 },
            { "name": "c", "group": 3 },
            { "name": "d", "group": 4 }
        ],
        "links": [
            { "source": "a", "target": "b", "value": 1 },
            { "source": "b", "target": "c", "value": 1 },
            { "source": "c", "target": "d", "value": 1 }
        ]
    }
    
    const graph2 = {
        "nodes": [
            { "name": "a", "group": 1 },
            { "name": "b", "group": 2 },
            { "name": "c", "group": 3 },
            { "name": "d", "group": 4 }
        ],
        "links": [
            { "source": "a", "target": "b", "value": 1 },
            { "source": "b", "target": "c", "value": 1 },
            { "source": "c", "target": "d", "value": 1 },
            { "source": "a", "target": "d", "value": 1 }
        ]
    }
    
    const graph3 = {
        "nodes": [
            { "name": "a", "group": 1 },
            { "name": "b", "group": 2 },
            { "name": "c", "group": 3 },
            { "name": "d", "group": 4 },
            { "name": "e", "group": 4 },
            { "name": "f", "group": 4 }
        ],
        "links": [
            { "source": "a", "target": "b", "value": 1 },
            { "source": "b", "target": "c", "value": 1 },
            { "source": "c", "target": "d", "value": 1 },
            { "source": "a", "target": "d", "value": 1 },
            { "source": "f", "target": "a", "value": 1 }
        ]
    }
    
    var simulation = d3.forceSimulation()
        .force("ct", d3.forceCenter(height / 2, width / 2))
        .force("link", d3.forceLink().id(function(d) { return d.name; })
            .distance(50).strength(2))
        .force("charge", d3.forceManyBody().strength(-240))
        // use forceX and forceY instead to change the relative positioning
        // .force("centering", d3.forceCenter(width/2, height/2))
        .force("x", d3.forceX(width / 2))
        .force("y", d3.forceY(height / 2))
        .on("tick", tick);
    
    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);
    
    svg.append("g").attr("class", "links");
    svg.append("g").attr("class", "nodes");
    
    function start(graph) {
    
        var linkElements = svg.select(".links").selectAll(".link").data(graph.links);
    
        linkElements.enter().append("line").attr("class", "link");
        linkElements.exit().remove();
    
        var nodeElements = svg.select(".nodes").selectAll(".node")
            .data(graph.nodes, function(d) { return d.name })
            .enter().append("g")
            .attr("class", "node");
    
        var circles = nodeElements.append("circle")
            .attr("r", 8);
    
        var labels = nodeElements.append("text")
            .text(function(d) { return d.name; })
            .attr("x", 10)
            .attr("y", 10);
    
        nodeElements.exit().remove();
    
        simulation.nodes(graph.nodes);
        simulation.force("link").links(graph.links);
        simulation.alphaTarget(0.1).restart();
    }
    
    function tick() {
        var nodeElements = svg.select(".nodes").selectAll(".node");
        var linkElements = svg.select(".links").selectAll(".link");
    
        nodeElements.attr("transform", function(d) {
                return "translate(" + d.x + "," + d.y + ")";
            })
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));
    
        linkElements.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; });
    }
    
    function dragstarted(d) {
        if (!d3.event.active) simulation.alphaTarget(0.1).restart();
        d.fx = d.x;
        d.fy = d.y;
    }
    
    function dragged(d) {
        d.fx = d3.event.x;
        d.fy = d3.event.y;
    }
    
    function dragended(d) {
        if (!d3.event.active) simulation.alphaTarget(0);
        d.fx = null;
        d.fy = null;
    }
    
    start(graph);
    
    
    document.getElementById('btn1').addEventListener('click', function() {
        start(graph);
    });
    
    document.getElementById('btn2').addEventListener('click', function() {
        start(graph2);
    });
    
    document.getElementById('btn3').addEventListener('click', function() {
        start(graph3);
    });
    .link {
            stroke: #000;
            stroke-width: 1.5px;
        }

    .node {
        stroke-width: 1.5px;
    }

    text {
          font-family: sans-serif;
          font-size: 10px;
          fill: #000000;
        }
    <body>
    <div>
        <button id='btn1'>1</button>
        <button id='btn2'>2</button>
        <button id='btn3'>3</button>
    </div>
    </body>
    <script src="https://d3js.org/d3.v5.min.js"></script>

Вот версия кода jsfiddle: https://jsfiddle.net/syedarehaq/myd0h5w1/

1 Ответ

1 голос
/ 14 апреля 2020

В предоставленном коде переменная nodeElements содержит выбор ввода, а не весь выбор привязки данных.

var nodeElements = svg.select(".nodes").selectAll(".node")
      .data(graph.nodes, function(d) { return d.name })
      .enter().append("g")
        .attr("class", "node");

nodeElements объявление не должно содержать бит .enter - оно должно быть точно так же, как linkSelection объявление переменной:

var nodeElements = svg.select(".nodes").selectAll(".node")
      .data(graph.nodes, function(d) { return d.name })

Затем, чтобы добавить новые круги и тексты только к входящим элементам g, выполните следующие действия:

var enterSelection = nodeElements.enter().append("g")
      .attr("class", "node");

var circles = enterSelection.append("circle")
      .attr("r", 8);

var labels = enterSelection.append("text")
      .text(function(d) { return d.name; })
      .attr("x", 10)
      .attr("y", 10);

Вызов функции exit теперь работает как положено.

Демонстрация в фрагменте ниже.

var height = 200;
    var width = 200;
    
    const graph = {
        "nodes": [
            { "name": "a", "group": 1 },
            { "name": "b", "group": 2 },
            { "name": "c", "group": 3 },
            { "name": "d", "group": 4 }
        ],
        "links": [
            { "source": "a", "target": "b", "value": 1 },
            { "source": "b", "target": "c", "value": 1 },
            { "source": "c", "target": "d", "value": 1 }
        ]
    }
    
    const graph2 = {
        "nodes": [
            { "name": "a", "group": 1 },
            { "name": "b", "group": 2 },
            { "name": "c", "group": 3 },
            { "name": "d", "group": 4 }
        ],
        "links": [
            { "source": "a", "target": "b", "value": 1 },
            { "source": "b", "target": "c", "value": 1 },
            { "source": "c", "target": "d", "value": 1 },
            { "source": "a", "target": "d", "value": 1 }
        ]
    }
    
    const graph3 = {
        "nodes": [
            { "name": "a", "group": 1 },
            { "name": "b", "group": 2 },
            { "name": "c", "group": 3 },
            { "name": "d", "group": 4 },
            { "name": "e", "group": 4 },
            { "name": "f", "group": 4 }
        ],
        "links": [
            { "source": "a", "target": "b", "value": 1 },
            { "source": "b", "target": "c", "value": 1 },
            { "source": "c", "target": "d", "value": 1 },
            { "source": "a", "target": "d", "value": 1 },
            { "source": "f", "target": "a", "value": 1 }
        ]
    }
    
    var simulation = d3.forceSimulation()
        .force("ct", d3.forceCenter(height / 2, width / 2))
        .force("link", d3.forceLink().id(function(d) { return d.name; })
            .distance(50).strength(2))
        .force("charge", d3.forceManyBody().strength(-240))
        // use forceX and forceY instead to change the relative positioning
        // .force("centering", d3.forceCenter(width/2, height/2))
        .force("x", d3.forceX(width / 2))
        .force("y", d3.forceY(height / 2))
        .on("tick", tick);
    
    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);
    
    svg.append("g").attr("class", "links");
    svg.append("g").attr("class", "nodes");
    
    function start(graph) {
    
        var linkElements = svg.select(".links").selectAll(".link").data(graph.links);
    
        linkElements.enter().append("line").attr("class", "link");
        linkElements.exit().remove();
    
        var nodeElements = svg.select(".nodes").selectAll(".node")
            .data(graph.nodes, function(d) { return d.name })
        
        var enterSelection = nodeElements.enter().append("g")
            .attr("class", "node");
    
        var circles = enterSelection.append("circle")
            .attr("r", 8);
    
        var labels = enterSelection.append("text")
            .text(function(d) { return d.name; })
            .attr("x", 10)
            .attr("y", 10);
    
        nodeElements.exit().remove();
    
        simulation.nodes(graph.nodes);
        simulation.force("link").links(graph.links);
        simulation.alphaTarget(0.1).restart();
    }
    
    function tick() {
        var nodeElements = svg.select(".nodes").selectAll(".node");
        var linkElements = svg.select(".links").selectAll(".link");
    
        nodeElements.attr("transform", function(d) {
                return "translate(" + d.x + "," + d.y + ")";
            })
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));
    
        linkElements.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; });
    }
    
    function dragstarted(d) {
        if (!d3.event.active) simulation.alphaTarget(0.1).restart();
        d.fx = d.x;
        d.fy = d.y;
    }
    
    function dragged(d) {
        d.fx = d3.event.x;
        d.fy = d3.event.y;
    }
    
    function dragended(d) {
        if (!d3.event.active) simulation.alphaTarget(0);
        d.fx = null;
        d.fy = null;
    }
    
    start(graph);
    
    
    document.getElementById('btn1').addEventListener('click', function() {
        start(graph);
    });
    
    document.getElementById('btn2').addEventListener('click', function() {
        start(graph2);
    });
    
    document.getElementById('btn3').addEventListener('click', function() {
        start(graph3);
    });
.link {
            stroke: #000;
            stroke-width: 1.5px;
        }

    .node {
        stroke-width: 1.5px;
    }

    text {
          font-family: sans-serif;
          font-size: 10px;
          fill: #000000;
        }
<body>
    <div>
        <button id='btn1'>1</button>
        <button id='btn2'>2</button>
        <button id='btn3'>3</button>
    </div>
    </body>
    <script src="https://d3js.org/d3.v5.min.js"></script>
...