Додавання нових вузлів до макета, спрямованого на Force


89

Перше питання про переповнення стека, так що терпіть зі мною! Я новачок у d3.js, але постійно дивуюсь тому, що інші можуть досягти за допомогою нього ... і майже так само вражений тим, як мало я просунувся до цього сам! Очевидно, я щось не маркую, тому сподіваюся, що добрі душі тут можуть показати мені світло.

Я маю намір створити багаторазову функцію javascript, яка просто робить наступне:

  • Створює порожній графік, спрямований на зусилля, у вказаному елементі DOM
  • Дозволяє додавати та видаляти мічені вузли, що містять зображення, до цього графіка, вказуючи зв’язки між ними

Я взяв за відправну точку http://bl.ocks.org/950642 , оскільки, по суті, саме такий макет я хочу мати можливість створити:

введіть тут опис зображення

Ось як виглядає мій код:

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript" src="jquery.min.js"></script>
    <script type="text/javascript" src="underscore-min.js"></script>
    <script type="text/javascript" src="d3.v2.min.js"></script>
    <style type="text/css">
        .link { stroke: #ccc; }
        .nodetext { pointer-events: none; font: 10px sans-serif; }
        body { width:100%; height:100%; margin:none; padding:none; }
        #graph { width:500px;height:500px; border:3px solid black;border-radius:12px; margin:auto; }
    </style>
</head>
<body>
<div id="graph"></div>
</body>
<script type="text/javascript">

function myGraph(el) {

    // Initialise the graph object
    var graph = this.graph = {
        "nodes":[{"name":"Cause"},{"name":"Effect"}],
        "links":[{"source":0,"target":1}]
    };

    // Add and remove elements on the graph object
    this.addNode = function (name) {
        graph["nodes"].push({"name":name});
        update();
    }

    this.removeNode = function (name) {
        graph["nodes"] = _.filter(graph["nodes"], function(node) {return (node["name"] != name)});
        graph["links"] = _.filter(graph["links"], function(link) {return ((link["source"]["name"] != name)&&(link["target"]["name"] != name))});
        update();
    }

    var findNode = function (name) {
        for (var i in graph["nodes"]) if (graph["nodes"][i]["name"] === name) return graph["nodes"][i];
    }

    this.addLink = function (source, target) {
        graph["links"].push({"source":findNode(source),"target":findNode(target)});
        update();
    }

    // set up the D3 visualisation in the specified element
    var w = $(el).innerWidth(),
        h = $(el).innerHeight();

    var vis = d3.select(el).append("svg:svg")
        .attr("width", w)
        .attr("height", h);

    var force = d3.layout.force()
        .nodes(graph.nodes)
        .links(graph.links)
        .gravity(.05)
        .distance(100)
        .charge(-100)
        .size([w, h]);

    var update = function () {

        var link = vis.selectAll("line.link")
            .data(graph.links);

        link.enter().insert("line")
            .attr("class", "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; });

        link.exit().remove();

        var node = vis.selectAll("g.node")
            .data(graph.nodes);

        node.enter().append("g")
            .attr("class", "node")
            .call(force.drag);

        node.append("image")
            .attr("class", "circle")
            .attr("xlink:href", "https://d3nwyuy0nl342s.cloudfront.net/images/icons/public.png")
            .attr("x", "-8px")
            .attr("y", "-8px")
            .attr("width", "16px")
            .attr("height", "16px");

        node.append("text")
            .attr("class", "nodetext")
            .attr("dx", 12)
            .attr("dy", ".35em")
            .text(function(d) { return d.name });

        node.exit().remove();

        force.on("tick", function() {
          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("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
        });

        // Restart the force layout.
        force
          .nodes(graph.nodes)
          .links(graph.links)
          .start();
    }

    // Make it all go
    update();
}

graph = new myGraph("#graph");

// These are the sort of commands I want to be able to give the object.
graph.addNode("A");
graph.addNode("B");
graph.addLink("A", "B");

</script>
</html>

Кожного разу, коли я додаю новий вузол, він повторно позначає всі існуючі вузли; ці купи накладають один на одного, і речі починають ставати некрасивими. Я розумію, чому це так: адже, коли я викликаю update()функцію функції при додаванні нового вузла, вона робить a node.append(...)для всього набору даних. Я не можу зрозуміти, як це зробити лише для того вузла, який я додаю ... і я можу, мабуть, використовувати його node.enter()для створення одного нового елемента, тому це не працює для додаткових елементів, які мені потрібні, прив'язані до вузла . Як я можу це виправити?

Дякуємо за будь-які вказівки, які ви можете дати щодо будь-якого з цього питання!

Відредаговано, оскільки я швидко виправив джерело кількох інших помилок, про які раніше згадувалося

Відповіді:


152

Після багатьох довгих годин неможливості отримати цю роботу, я нарешті наткнувся на демонстраційне відео, яке, на мою думку, не пов’язане з жодною документацією: http://bl.ocks.org/1095795 :

введіть тут опис зображення

Ця демонстрація містила ключі, які нарешті допомогли мені вирішити проблему.

Додавання кількох об'єктів до an enter()можна зробити, присвоївши enter()змінну, а потім додавши до неї. Це має сенс. Друга критична частина полягає в тому, що масиви вузлів і посилань повинні базуватися на force()- інакше графік і модель не синхронізуються, коли вузли видаляються і додаються.

Це пов’язано з тим, що якщо замість цього будується новий масив, у нього не буде таких атрибутів :

  • index - нульовий індекс вузла в масиві вузлів.
  • x - координата x поточної позиції вузла.
  • y - координата y поточної позиції вузла.
  • px - координата х попередньої позиції вузла.
  • py - координата y попередньої позиції вузла.
  • фіксований - логічне значення, що вказує, чи заблоковано положення вузла.
  • вага - вага вузла; кількість пов'язаних посилань.

Ці атрибути не є суворо необхідними для виклику force.nodes(), але якщо їх немає, тоді вони будуть випадково ініціалізовані при force.start()першому виклику.

Якщо комусь цікаво, робочий код виглядає так:

<script type="text/javascript">

function myGraph(el) {

    // Add and remove elements on the graph object
    this.addNode = function (id) {
        nodes.push({"id":id});
        update();
    }

    this.removeNode = function (id) {
        var i = 0;
        var n = findNode(id);
        while (i < links.length) {
            if ((links[i]['source'] === n)||(links[i]['target'] == n)) links.splice(i,1);
            else i++;
        }
        var index = findNodeIndex(id);
        if(index !== undefined) {
            nodes.splice(index, 1);
            update();
        }
    }

    this.addLink = function (sourceId, targetId) {
        var sourceNode = findNode(sourceId);
        var targetNode = findNode(targetId);

        if((sourceNode !== undefined) && (targetNode !== undefined)) {
            links.push({"source": sourceNode, "target": targetNode});
            update();
        }
    }

    var findNode = function (id) {
        for (var i=0; i < nodes.length; i++) {
            if (nodes[i].id === id)
                return nodes[i]
        };
    }

    var findNodeIndex = function (id) {
        for (var i=0; i < nodes.length; i++) {
            if (nodes[i].id === id)
                return i
        };
    }

    // set up the D3 visualisation in the specified element
    var w = $(el).innerWidth(),
        h = $(el).innerHeight();

    var vis = this.vis = d3.select(el).append("svg:svg")
        .attr("width", w)
        .attr("height", h);

    var force = d3.layout.force()
        .gravity(.05)
        .distance(100)
        .charge(-100)
        .size([w, h]);

    var nodes = force.nodes(),
        links = force.links();

    var update = function () {

        var link = vis.selectAll("line.link")
            .data(links, function(d) { return d.source.id + "-" + d.target.id; });

        link.enter().insert("line")
            .attr("class", "link");

        link.exit().remove();

        var node = vis.selectAll("g.node")
            .data(nodes, function(d) { return d.id;});

        var nodeEnter = node.enter().append("g")
            .attr("class", "node")
            .call(force.drag);

        nodeEnter.append("image")
            .attr("class", "circle")
            .attr("xlink:href", "https://d3nwyuy0nl342s.cloudfront.net/images/icons/public.png")
            .attr("x", "-8px")
            .attr("y", "-8px")
            .attr("width", "16px")
            .attr("height", "16px");

        nodeEnter.append("text")
            .attr("class", "nodetext")
            .attr("dx", 12)
            .attr("dy", ".35em")
            .text(function(d) {return d.id});

        node.exit().remove();

        force.on("tick", function() {
          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("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
        });

        // Restart the force layout.
        force.start();
    }

    // Make it all go
    update();
}

graph = new myGraph("#graph");

// You can do this from the console as much as you like...
graph.addNode("Cause");
graph.addNode("Effect");
graph.addLink("Cause", "Effect");
graph.addNode("A");
graph.addNode("B");
graph.addLink("A", "B");

</script>

1
Ключовим було використання force.start()замість того, force.resume()коли додаються нові дані. Дуже дякую!
— Mouagip

Це круто. Будьте круті, якщо він автоматично масштабує рівень масштабування (можливо, зменшує заряд, поки все підходить?), Щоб все відповідало розміру коробки, в яку він втягувався.
— Роб Грант

1
+1 для прикладу чистого коду. Мені це подобається більше, ніж приклад містера Бостока, оскільки він показує, як інкапсулювати поведінку в об’єкті. Молодці. (Подумайте про додавання його до бібліотеки прикладів D3?)
— fearless_fool

Це прекрасно! Я вже пару днів навчаюсь використовувати forceGraph з d3, і це найкрасивіший спосіб зробити це, який я бачив. Дуже дякую!
— Лукас Азеведо,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.