0

私はノードの素敵な小さなネットワークを持っています。

1 つは中央ノードで固定されており、他のノードはその周りを飛んでおり、リンクで接続され、力場の影響を受けています。

ユーザーが他のノードの 1 つをクリックすると、これが新しいセントラル ノードになり、固定されます。

このクリックされたノードを、最後の中央ノードがあった svg の中心に移動して、ノードをクリックするたびにネットワークが脇に移動しないようにするという考えです。

現在、この 1 つのノードの位置をリセットできますが、目的の位置にゆっくりと移動することはできません。

誰か私にアドバイスはありますか?

2013 年 11 月 29 日編集:

group.select(this).transition().attr("cx", function(d) { return width/2; });ノード ( ) のクリック ハンドラで、ノードを追加した直後に.transition() を試しました。

編集 2: 上記のコード行は、ノードに入った後にテストされました。

また、fixedNode を介してノードを取得しようとしましたが、これには遷移方法がありません。Afaik には circle オブジェクトのみが含まれており、その性質上、.transition() メソッドはありません。

編集 3: コードを数時間いじってみたところ、一般的なトランジションを行う方法が見つかりました。ノードに力を適用した後に、余分なコードのブロックを追加しました (...call(force.drag);)。

半径を操作するとうまくいきます。唯一の問題は、すべてのノードが影響を受けることです。

r-attributes の変更を で置き換えると、機能test.transition().duration(3000).attr("cx", width/2);しますが、リンクが遷移なしで通常の位置のままであり、遷移が終了するとすぐにノードが跳ね返るため、非常に奇妙に見えます!

ですから、必要なノードを 1 つのノードとして取得し、ポジションが機能するように移行するのを手伝っていただければ幸いです。

2013 年 12 月 2 日編集:

ソースコードを更新しました。Lars Kotthoff のおかげで、円を動かすことができるようになりました。しかし、リンクはまだノードの古い位置に移動し、遷移が終了すると円は開始位置に戻ります。

コピーする作業コードを追加しました。バックグラウンドでjsonファイルが必要であることに注意してください(同じフォルダーにある「rawData.json」)。

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
    <script type="text/javascript" src="d3.v3.js"></script>
    <script type="text/javascript" src="d3.v3.min.js"></script>
    <link rel="stylesheet" type="text/css" href="dhtmlxSlider/codebase/dhtmlxslider.css">
    <script src="dhtmlxSlider/codebase/dhtmlxcommon.js"></script>
    <script src="dhtmlxSlider/codebase/dhtmlxslider.js"></script>
    <script>
        window.dhx_globalImgPath = "dhtmlxSlider/codebase/imgs/";
    </script>
    <style type="text/css">
        .node text {
            pointer-events: none;
            font: 10px sans-serif;
            color: #FF7777;
        }
        .text {
            color: #FF7777;
        }
    </style>
    <title>ConceptMaps</title>
</head>
<body>
    <div id="chart" class="chart"></div>
    <div align="center" id="slider" onmouseup="paintIt();"></div>
    <div align="center" id="test"></div>
    <script>

        var reqNodes = [],
            reqLinks = [],
            fixedNode,
            testContainer = null,
            width = window.innerWidth*0.9, 
            height = window.innerHeight*0.9,
            mittelpunkt = "Perry Rhodan", 
            checklist = [],
            mitte = [],
            colorArray = ["#D3D3D2",  "darkblue", "#008000", "#F1AD45",  "#F2D667",     "#8BD3EB",     "#B74965",   "#67A175"],
            force = d3.layout.force().gravity(-0.1).distance(250).charge(-500).linkDistance(80).linkStrength(4).friction(0.6).size([width, height]);

            var group = d3.select("#chart").append("svg")
                .attr("width", width)
                .attr("height", height)
                .attr("id", 'networkBox');

        var sld = new dhtmlxSlider("slider", 100, "arrow", false, 1, 5, 2);
            sld.setImagePath("dhtmlxSlider/codebase/imgs/");
            sld.setSteppingMode(true);
            sld.linkTo("test"); 
            sld.init();

        function getIndexInXML(attribute, targetContent) {
            if (xmlDoc == null) {alert("FAIL!");}
            var searchArray = xmlDoc.documentElement.getElementsByTagName(attribute);
            for (var i = 0; i < searchArray.length; i++) {
                if (searchArray[i].textContent == targetContent) {
                    return i;
                }
            }
        }//end getIndexInXML

        function getNodesFinalIndex(nameOfTarget) {
            for (var i = 0; i < reqNodes.length; i++) {
                if(reqNodes[i] != null){
                    if (reqNodes[i].name == nameOfTarget) {
                        return i;
                    }
                }
            }//for
        }//getNodesFinalIndex

        function getNodesIndex(nameOfTarget) {
            for(var i = 0; i < testContainer.nodes.length; i++){
                if(testContainer.nodes[i].name == nameOfTarget){
                    return i;
                }
            }//for
        }//getNodesIndex
        var linkBin = [];
        function getLinksIndex(nodeIndex){
            linkBin = [];
            for(var n = 0; n < reqLinks.length; n++){
                if(reqLinks[n].source.name == reqNodes[nodeIndex].name || reqLinks[n].target.name == reqNodes[nodeIndex].name){ //can not read name of undefined
                    linkBin.push(n);
                }
            }
            return linkBin;
        }//end getLinksIndex

        //getting the data from JSON-File
        d3.json("rawData.json", function(error, graph) {
            testContainer = graph;
            paintIt();              
        });//end d3.json

        function paintIt() {
            reqLinks = null;
            reqLinks = [];
            getNetParts(mittelpunkt, sld.getValue());
            update();
        }//end paintIt

        //------------ sort by used and unused nodes -------------------
        function getNetParts(searchedFor, depth) {      
            var temp = null;
            temp = [];

            //create a checklist with one element for each node in testContainer
            for(var pr = 0; pr < testContainer.nodes.length; pr++){
                checklist[pr] = false;
            }   

            var middle = getNodesIndex(searchedFor);
            temp.push(testContainer.nodes[middle]);     //asign middle node to temp
            checklist[middle] = true;   //note that middle has already been added to temp to prevent double assignments

            sortReqNodes(middle,1);

            checklist = null;
            checklist = [];
            var isThere = false;
            var freeSpaces = null;
            freeSpaces =  [];
            for(var pr = 0; pr < temp.length; pr++){    //new checklist, delete prev notes
                checklist[pr] = false;
            }
            for(var c = 0; c < reqNodes.length; c++){   //circle over the elements in 'reqNodes'
                    for(var cg = 0; cg < temp.length; cg++){    //look up if the element should be held there for the next version, too
                        if(temp[cg] != null && reqNodes[c] != null){
                            if(temp[cg].name == reqNodes[c].name){
                                temp[cg] = null;    //note the index of the node that doesn't need to be transfered
                                isThere = true;     //and make a mark
                                break;              //and break
                            }
                            else {reqNodes[c] = null;}
                            if(!isThere){
                                freeSpaces.push(c);
                            }
                        }
                    }
            }

            for(var x = 0; x < temp.length; x++){   //circle through temp
                if(temp[x] != null){        //if the element hasn't been deleted yet
                    if(freeSpaces.length != 0)  //if there is a free space, use it  
                            reqNodes[freeSpaces.pop()] = temp[x];   //transfer to reqNodes
                    else
                            reqNodes.push(temp[x]);                             
                }
            }


            //clean reqNodes up - deleting every 'null'
            for(var sl = reqNodes.length-1; sl >= 0; sl--){     //reverse loop - start with last element and end with first -> deleting elements won't disturb the loop
                if(reqNodes[sl] == null){   //if it is empty
                    reqNodes.splice(sl, 1);  //...remove it!
                }
            }


            for(var z = 1; z < reqNodes.length; z++){   //circle through stored Nodes
                if(reqNodes[z] != null){
                    for(var c = 0; c < reqNodes[z].connections.length; c++){    //inspect all of their connections 
                        for(var w = 0; w < reqNodes.length; w++){   //check all stored reqNodes for the one linked
                            if(reqNodes[w] != null){
                            if(testContainer.nodes[reqNodes[z].connections[c]].name == reqNodes[w].name){   //the node is available at reqNodes?
                                var source = null;                                  
                                var newLink = {
                                    "source":z,
                                    "target":w,
                                    "value":1,
                                };                          
                                reqLinks.push(newLink);                 
                            }
                            }
                        }   
                    }
                }   
        }

        function sortReqNodes(mitte, count){
                for(var y = 0; y < testContainer.nodes[mitte].connections.length; y++){ //stores every node connected with the main node in reqNode 
                        if(!checklist[testContainer.nodes[mitte].connections[y]]){  //if this one hasn't already been added
                            temp.push(testContainer.nodes[testContainer.nodes[mitte].connections[y]]);  //push a node connected with mitte to reqNodes
                            checklist[testContainer.nodes[mitte].connections[y]] = true;    //check - node has been pushed!
                        }
                        if(count < depth){  //if we haven't reached the desired depth yet
                            sortReqNodes(testContainer.nodes[mitte].connections[y],count+1);    //one more round, get the nodes connected with this one involved, too!
                        }           
                }
        }//end function sortReqNodes
    }//end function getNetParts

        function update() {

            group.selectAll(".link").remove();
            group.selectAll(".node").remove();

            //unfix any fixed nodes
            for(var count = 0; count < reqNodes.length; count++){
                if(reqNodes[count] != null){
                    reqNodes[count].fixed = false;
                }
            }

            var fixedNode = reqNodes[getNodesFinalIndex(mittelpunkt)];
            fixedNode.x = 900;  //Only works the first time...
            fixedNode.y = 300;

            fixedNode.fixed = true; 

            // fixedNode.px = width/2;      /*working, but makes the node jump -> ugly!*/
            // fixedNode.py = height/2;             

            link = group.selectAll(".link")
                .data(reqLinks);

            link.enter().append("line")
                .attr("class", "link")
                .style("stroke", "#000")
                .style("stroke-width", function(d) { return Math.sqrt(d.value)*1.2; });

            node = group.selectAll("circle")
                .data(reqNodes);

            node.enter().append("circle")
                .attr("class", "node")
                .attr("r", 7)
                .style("stroke", "black")
                .style("fill", function(d) { return colorArray[d.group]; })
                .call(force.drag);

            node.append("name").text(function(d) { return d.name; });

            node.append("title")
                .text(function(d) { return d.name; });
            node.attr("name", function(d) { return d.name; });

            for(var oo = 0; oo < node[0].length; oo++){ 
                if(node[0][oo] != null){
                    if(node[0][oo].getAttribute('name') == mittelpunkt){
                        node[0][oo].style.stroke = "red";   
                        node[0][oo].style.strokeWidth = 3;
                    }
                }
            }

            node.filter(function(d) { return d.fixed == true; }).transition().duration(5000)
                    .attr("cy", height/2)
                    .attr("cx", width/2);
            node.filter(function(d) { return d.fixed == true; })
                    .attr("y", height/2)
                    .attr("x", width/2);

            node.on("click", function(d) {
                mittelpunkt = d.name;
                paintIt();
            });

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

            //shows the fit article from the website www.perrypedia.proc.org, if you don't know the character on the node!
            group.selectAll(".node").on("dblclick", function(d) {
                window.open("http://www.perrypedia.proc.org/wiki/" + d.name);
            });

            force
                .nodes(reqNodes)
                .links(reqLinks)
                .start();


            group.selectAll('.not-fixed').call(force.drag);

            var groupDrag = d3.behavior.drag().on("drag", function(d) {
                // mouse pos offset by starting node pos
                var x = window.event.clientX - 430, y = window.event.clientY - 280;
                group.attr("transform", function(d) {
                    return "translate(" + x + "," + y + ")";
                });
                CurrentGTransformX = x;
                CurrentGTransformY = y;
            })

            group.call(groupDrag);

            node.append("title").text(function(d) {
                return d.skill;
            });

            //HOVER for the nodes - makes it easier too determine which links belong to the node
            node.on("mouseover", function(d) {
                var selection = d3.select(this);
                var links2Change = getLinksIndex(getNodesFinalIndex(selection[0][0].getElementsByTagName("title")[0].textContent));
                var initialWidth = Number( selection.style("stroke-width") );
                var linksContainer = group.selectAll(".link");
                if(links2Change != null && links2Change != []){ 
                    for(var c = 0; c < links2Change.length; c++){
                        linksContainer[0][links2Change[c]].style.opacity = 0.2;
                    }
                }
            } )
            node.on("mouseout", function(d) {
                var selection = d3.select(this);
                var links2Change = getLinksIndex(getNodesFinalIndex(selection[0][0].getElementsByTagName("title")[0].textContent));
                var initialWidth = Number( selection.style("stroke-width") );
                var linksContainer = group.selectAll(".link");
                if(links2Change != null && links2Change != []){ 
                    for(var c = 0; c < links2Change.length; c++){
                        linksContainer[0][links2Change[c]].style.opacity = 1;
                    }
                }
            })

            link.append("title").text(function(d) {
                return d.group;
            });

            function getOffset(el) {
                var _x = 0;
                var _y = 0;
                while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
                    _x += el.offsetLeft - el.scrollLeft;
                    _y += el.offsetTop - el.scrollTop;
                    el = el.offsetParent;
                }
                return {
                    top : _y,
                    left : _x
                };
            }

            force.on("tick", function() {
                node.attr("cx", function(d) { return d.x = Math.max(15, Math.min(width - 15, d.x)); })      //restricts the x-coordinates to the inside of the SVG
                    .attr("cy", function(d) { return d.y = Math.max(15, Math.min(height - 15, d.y)); });    //restricts the y-coordinates to the inside of the SVG

                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;
                });
            });
        }//end function update()
    </script>
</body>

2013 年 12 月 3 日編集:

ノードの x/y の操作を追加しました。

残念ながら、これは、ノードが移行のためにネットワークから離れて設定されることを妨げません。代わりに px/py で同じ操作を行っても、ノードが transition() のターゲット位置から force() に割り当てられた位置に戻るのを修正するのに役立ちません...

rawData.json:

{
"nodes":[
    {"name":"Perry Rhodan","group":1,"skill":"Sofortumschalter, Erbe des Universums","connections":[1,2,3,4,5,7,13,17,19,18,22]},
    {"name":"Reginald Bull","group":1,"skill":"Techniker, Draufgaenger","connections":[12,0]},
    {"name":"Thora da Zoltral","group":2,"skill":"Hochmuetig","connections":[0,3,13,4]},
    {"name":"Crest da Zoltral","group":2,"skill":"Derengar","connections":[0,2,4]},
    {"name":"Atlan da Gonozal","group":2,"skill":"Der Einsame der Zeit","connections":[0,2,3,24]},
    {"name":"Homer Gershwin Adams","group":1,"skill":"fotografisches Gedaechtnis","connections":[0,7]},
    {"name":"Tatjana Michalowna","group":1,"skill":"Telepathin","connections":[7]},
    {"name":"Mutantenkorps","group":3,"skill":"1972 gegruendet von Perry Rhodan","connections":[0,8,9,10,11,12,15,16]},
    {"name":"Son Okura","group":1,"skill":"Telekinet","connections":[7]},
    {"name":"Wuriu Sengu","group":1,"skill":"Spaeher","connections":[7,14]},
    {"name":"Tako Kakuta","group":1,"skill":"Teleporter","connections":[7]},
    {"name":"Ras Tschubai","group":1,"skill":"Teleporter","connections":[7,12]},
    {"name":"Gucky","group":4,"skill":"Telepath, Telekinet, Teleporter","connections":[7,11,1]},
    {"name":"Thomas Cardif","group":1,"skill":"Rhodan-Imitator, Rhodans Sohn","connections":[0,2]},
    {"name":"Ismael ben Rabbat","group":1,"skill":"Raumschiffskommandant","connections":[9]},
    {"name":"Tama Yokida","group":1,"skill":"Telekinet, Materiewandler","connections":[7]},
    {"name":"Ernst Ellert","group":1,"skill":"Teletemporarier","connections":[7]},
    {"name":"Lotho Keraete","group":0,"skill":"Bote von ES","connections":[0,18,22,26]},
    {"name":"Homunk","group":0,"skill":"Bote von ES","connections":[0,17,26]},
    {"name":"Alaska Saedelaere","group":1,"skill":"Maskentraeger","connections":[0,20,21,22]},
    {"name":"Samburi Yura","group":0,"skill":"Kosmokratenbeauftragte","connections":[19,25]},
    {"name":"Sholoubwa","group":0,"skill":"Konstrukteur","connections":[19]},
    {"name":"Ennerhahl","group":0,"skill":"Beauftragter von ES, 'Mittel, Wege und Moeglichkeiten'","connections":[0,19,17,26]},
    {"name":"Delorian Rhodan","group":0,"skill":"Sohn von Perry & Mondra, Ex-Chronist von ES, Schöpfer einer Enklave","connections":[0,22,20,26]},
    {"name":"Theta da Ariga","group":2,"skill":"Geliebte Atlans","connections":[4]},
    {"name":"LICHT VON AHN","group":5,"skill":"Superintelligenz/Kollektivwesen","connections":[20]},
    {"name":"ES","group":5,"skill":"Superintelligenz","connections":[17,18,22,23]}
],
"links":[
    {"source":1,"target":0,"value":2},
    {"source":3,"target":0,"value":2},
    {"source":2,"target":0,"value":3},
    {"source":3,"target":2,"value":3},
    {"source":4,"target":0,"value":2},
    {"source":5,"target":0,"value":1},
    {"source":4,"target":2,"value":1},
    {"source":4,"target":3,"value":1},
    {"source":6,"target":7,"value":4},
    {"source":7,"target":0,"value":1},
    {"source":5,"target":7,"value":4},
    {"source":8,"target":7,"value":4},
    {"source":9,"target":7,"value":4},
    {"source":10,"target":7,"value":4},
    {"source":11,"target":7,"value":4},
    {"source":12,"target":7,"value":4},
    {"source":12,"target":1,"value":2},
    {"source":13,"target":0,"value":5},
    {"source":13,"target":2,"value":5},
    {"source":14,"target":9,"value":2},
    {"source":11,"target":12,"value":2},
    {"source":15,"target":7,"value":4},
    {"source":16,"target":7,"value":4},
    {"source":18,"target":22,"value":1},
    {"source":20,"target":22,"value":1},
    {"source":21,"target":19,"value":1},
    {"source":22,"target":0,"value":1},
    {"source":22,"target":19,"value":1},
    {"source":22,"target":17,"value":1},
    {"source":23,"target":22,"value":1},
    {"source":23,"target":0,"value":1},
    {"source":23,"target":20,"value":1}
]
}

2013 年 12 月 4 日編集:

今では移行は機能していますが、ネットワークはセントラルノードでプッシュされていないため、ネットワーク自体がこのノードの fixedNode / reqNodes-data からノードに関するデータを取得することがわかりました。fixedNode.px を変更すると、一緒にスナップします。

したがって、すべてのティックで px を更新する行を tick() に追加できれば、これは計画どおりに機能すると思います...

アイデアがある場合、または私が非常に間違った方向に向かっていることに気付いた場合は、下にコメントを残すか、必要に応じて回答を作成してください!

4

1 に答える 1

1

参考までに、位置が固定された後にノードを新しい位置に移動する正しい方法を次に示します。

node.filter(function(d) { return d.fixed; }).transition().duration(1000)
    .tween("x", function() {
                var i = d3.interpolate(fixedNode.x, 900);
                return function(t) {
                  fixedNode.x = i(t);
                  fixedNode.px = i(t);
                };
     }).tween("y", function() {
                var i = d3.interpolate(fixedNode.y, 300);
                return function(t) {
                  fixedNode.y = i(t);
                  fixedNode.py = i(t);
                };
     });

これは、固定された要素でカスタム トゥイーン関数を使用し、各ステップで/属性とx/属性を設定して強制レイアウトを取得します。ypxpy

于 2013-12-04T19:43:39.753 に答える