动画d3.js中的元素

时间:2013-11-12 14:08:59

标签: javascript animation d3.js

我有一个很好的小节点网络。

一个是中心节点并且是固定的,其他的是在它周围飞行,与链接相连并受力场的影响。

当用户点击其中一个节点时,这个节点就是新的中心节点,因此是固定的。

这个想法是将这个点击的节点转移到最后一个中心节点所在的svg的中心,这样网络就不会随你点击的每个节点移动到一边。

现在我可以重置这个节点的位置,但不要慢慢移动到所需的位置。

有人对我有建议吗?

2013年11月29日编辑:

我在节点的点击处理程序(group.select(this).transition().attr("cx", function(d) { return width/2; });)中尝试过.transition() 并且在追加节点后立即。

编辑2:输入节点后测试了上面给出的代码行。

我也尝试通过fixedNode获取节点,但是这个节点没有过渡方法。 Afaik它只包含圆形对象,根据它的性质,没有.transition() - 方法。

编辑3:我已经玩了几个小时的代码,现在我找到了一种方法来进行基因转换。在将力施加到节点后,我添加了一块额外的代码(... call(force.drag);)。

如果我操纵半径,它可以正常工作。唯一的问题是所有节点都受到影响。

如果我用test.transition().duration(3000).attr("cx", width/2);替换r属性更改,它可以工作但看起来很奇怪,因为链接sstill具有没有转换的正常位置,并且一旦转换完成,节点就会跳回来!

所以,如果你可以帮助我获得我需要的节点作为单个节点并获得职位转换,我会很高兴!

编辑12/02/2013:

更新了源代码。感谢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>

编辑12/03/2013:

添加了对节点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}
]
}

编辑12/04/2013:

到现在为止我已经完成了转换工作,但网络没有被中心节点推送,我发现网络本身从fixedNode /此节点的reqNodes-data获取有关节点的数据。如果我更改fixedNode.px,它会一起拍摄。

因此,如果我可以在tick()中添加一行来更新每个tick中的px,我认为这可以按计划进行...

如果您有任何想法或看到我正朝着一个非常错误的方向前进,请在下方发表评论,或者,如果您愿意,请创建一个答案!

1 个答案:

答案 0 :(得分: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 / ypx / py属性接。