Zorla yönlendirilmiş düzene yeni düğümler ekleme


89

Stack Overflow ile ilgili ilk soru, bu yüzden bana katlanın! D3.js'de yeniyim, ancak sürekli olarak başkalarının onunla neler başardığına hayran kaldım ... ve kendimle ne kadar az ilerleme kaydedebildiğime neredeyse hayret ediyorum! Açıkçası ben bir şey söylemiyorum, bu yüzden umarım buradaki nazik ruhlar bana ışığı gösterebilir.

Niyetim, aşağıdakileri basitçe yapan yeniden kullanılabilir bir javascript işlevi yapmaktır:

  • Belirtilen bir DOM öğesinde boş bir kuvvet yönelimli grafik oluşturur
  • Aralarındaki bağlantıları belirleyerek bu grafiğe etiketli, görüntü taşıyan düğümleri eklemenizi ve silmenizi sağlar

Http://bl.ocks.org/950642'yi başlangıç ​​noktası olarak aldım , çünkü bu esasen oluşturmak istediğim türden bir düzen:

görüntü açıklamasını buraya girin

Kodum şöyle görünüyor:

<!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>

Her yeni düğüm eklediğimde, mevcut tüm düğümleri yeniden etiketler; bunlar üst üste yığılır ve işler çirkinleşmeye başlar. Bunun nedenini anlıyorum: çünkü update()yeni bir düğüm ekledikten sonra işlev işlevini çağırdığımda node.append(...), tüm veri kümesine a yapar. Bunu yalnızca eklediğim düğüm için nasıl yapacağımı çözemiyorum ... ve yalnızca görünüşe göre node.enter()tek bir yeni öğe oluşturmak için kullanabiliyorum , bu nedenle bu, düğüme bağlanmam gereken ek öğeler için çalışmıyor . Bunu nasıl düzeltebilirim?

Bu konuda verebileceğiniz her türlü rehberlik için teşekkür ederiz!

Daha önce bahsedilen diğer birkaç hatanın kaynağını hızla düzelttiğim için düzenlendi

Yanıtlar:


152

Uzun saatler boyunca bu çalışmayı yapamadığım için, sonunda herhangi bir belgeyle bağlantılı olduğunu düşünmediğim bir demo ile karşılaştım: http://bl.ocks.org/1095795 :

görüntü açıklamasını buraya girin

Bu demo, nihayet sorunu çözmeme yardımcı olan anahtarları içeriyordu.

Bir değişkene birden çok nesne eklemek , bir değişkene enter()atayarak enter()ve ardından buna eklenerek yapılabilir. Bu mantıklı. İkinci kritik bölüm, düğüm ve bağlantı dizilerinin,force() - aksi takdirde, düğümler silindiğinde ve eklendiğinde grafik ve model senkronize olmayacaktır.

Bunun nedeni, eğer onun yerine yeni bir dizi inşa edilirse, aşağıdaki özniteliklerden yoksun olacaktır :

  • dizin - düğüm dizisindeki düğümün sıfır tabanlı dizini.
  • x - geçerli düğüm konumunun x koordinatı.
  • y - geçerli düğüm konumunun y koordinatı.
  • px - önceki düğüm konumunun x koordinatı.
  • py - önceki düğüm konumunun y koordinatı.
  • sabit - düğüm konumunun kilitli olup olmadığını gösteren bir boole.
  • ağırlık - düğüm ağırlığı; ilişkili bağlantıların sayısı.

Bu nitelikler, çağrı için kesinlikle gerekli değildir force.nodes(), ancak bunlar mevcut değilse, o zaman ilk çağrıda rastgele başlatılacaklardır force.start().

Merak eden varsa, çalışma kodu şuna benzer:

<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
Yeni veri eklendiğinde force.start()bunun yerine kullanmak force.resume()kilit noktaydı. Çok teşekkürler!
Mouagip

Bu harika. Yakınlaştırma düzeyini otomatik olarak ölçeklendirdiyse (belki de her şey uyana kadar şarjı düşürürse?), Böylece her şey çizildiği kutu boyutuna
Rob Grant

1
Temiz kod örneği için +1. Bunu Bay Bostock'un örneğinden daha çok seviyorum çünkü bir nesnedeki davranışın nasıl kapsülleneceğini gösteriyor. Aferin. (D3 örnek kitaplığına eklemeyi düşünün?)
fearless_fool

Bu çok güzel! Birkaç gündür forceGraph'ın d3 ile nasıl kullanılacağını öğreniyorum ve bunu yapmanın en güzel yolu gördüğüm. Çok teşekkür ederim!
Lucas Azevedo
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.