बल-निर्देशित लेआउट में नए नोड्स जोड़ना


89

स्टैक ओवरफ्लो पर पहला सवाल, तो मेरे साथ सहन करो! मैं d3.js में नया हूं, लेकिन लगातार इस बात से चकित रह गया हूं कि दूसरे लोग इसे पूरा करने में सक्षम हैं ... और लगभग आश्चर्यचकित हैं कि मैं खुद इसके साथ कितना कम बदलाव कर पाया हूं! स्पष्ट रूप से मैं कुछ नहीं कर रहा हूँ, इसलिए मुझे आशा है कि यहाँ की आत्माएँ मुझे प्रकाश दिखा सकती हैं।

मेरा इरादा एक पुन: प्रयोज्य जावास्क्रिप्ट फंक्शन बनाने का है जो केवल निम्नलिखित कार्य करता है:

  • एक निर्दिष्ट 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()एक नया नोड जोड़ने पर फ़ंक्शन फ़ंक्शन को कॉल करता हूं , तो यह node.append(...)संपूर्ण डेटा सेट पर करता है। मैं यह पता नहीं लगा सकता कि मैं केवल जो नोड जोड़ रहा हूं, उसके लिए यह कैसे किया जा सकता है ... और मैं केवल node.enter()एक ही नया तत्व बनाने के लिए उपयोग कर सकता हूं , ताकि मुझे नोड के लिए बाध्य होने वाले अतिरिक्त तत्वों के लिए काम न करना पड़े । मैं इसे कैसे ठीक करूं?

किसी भी मार्गदर्शन के लिए धन्यवाद जो आप इस मुद्दे पर देने में सक्षम हैं!

संपादित क्योंकि मैंने जल्दी से कई अन्य बग्स का एक स्रोत तय किया है जो पहले उल्लेख किए गए थे

जवाबों:


152

इस काम को पाने में असमर्थ होने के कई लंबे घंटों के बाद, मैंने आखिरकार एक डेमो भर में ठोकर खाई कि मुझे नहीं लगता कि किसी भी दस्तावेज से जुड़ा हुआ है: http://bl.ocks.org/1095795 :

यहां छवि विवरण दर्ज करें

इस डेमो में कुंजियाँ थीं जो आखिरकार मुझे समस्या को सुलझाने में मदद करती हैं।

एक चर पर कई वस्तुओं को जोड़कर एक चर को enter()असाइन किया जा सकता है enter(), और फिर उस पर जोड़ दिया जा सकता है। यह समझ में आता है। दूसरा महत्वपूर्ण हिस्सा यह है कि नोड और लिंक सरणियों पर आधारित होना चाहिए force()- अन्यथा ग्राफ और मॉडल सिंक से बाहर निकल जाएंगे क्योंकि नोड्स हटाए और जोड़े गए हैं।

ऐसा इसलिए है क्योंकि यदि इसके बजाय एक नया सरणी बनाया गया है, तो इसमें निम्नलिखित विशेषताओं का अभाव होगा :

  • सूचकांक - नोड सरणी के भीतर नोड का शून्य-आधारित सूचकांक।
  • x - वर्तमान नोड स्थिति का x- समन्वय।
  • y - वर्तमान नोड स्थिति का y- समन्वय।
  • पीएक्स - पिछले नोड स्थिति का एक्स-समन्वय।
  • 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()महत्वपूर्ण था। आपका बहुत बहुत धन्यवाद!
मौआगिप

यह कमाल का है। अगर यह ज़ूम लेवल को ऑटोसाल्ड करता है तो कूल रहें (हो सकता है कि चार्ज टिल सब कुछ फिट कर दे?) तो बॉक्स के आकार में फिट की गई हर चीज इसमें आ रही थी।
रोब ग्रांट

1
स्वच्छ कोड उदाहरण के लिए +1। मुझे मिस्टर बोस्कॉक के उदाहरण से बेहतर लगता है क्योंकि यह दर्शाता है कि किसी वस्तु में व्यवहार को कैसे समझाया जाए। बहुत बढ़िया। (डी 3 उदाहरण लाइब्रेरी में यह जोड़ने पर विचार करें?)
fearless_fool

वह सुंदर है! मैं सीख रहा हूँ कि अब कुछ दिनों के लिए d3 के साथ ForceGraph का उपयोग कैसे करें, और यह ऐसा करने का सबसे सुंदर तरीका है जो मैंने देखा है। आपका बहुत बहुत धन्यवाद!
लुकास अजेवेदो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.