2

I've managed to get highlight working on my force directed graph, with help of this tutorial from Mike Bostock. Now for further procedure in my idea and needs of my graph, I'm little bit stuck, firstly because I'm still nooby about d3js and second, I did non find anything similar yet.

To clear out what my intentions, you can see current graph with highlighting on following link: http://jsfiddle.net/2FwSY/

It works nicely, but my idea is little bit advanced. I was wondering if it is possible to tweak the highlighting in such way that on mouseover to node, it would highlight the links of that node to neighbour nodes, neighbour nodes and also links from that neighbouring nodes to their neighbour nodes.

Precisely, on my jsfiddle example. If you hover BNG , BNG, YHO, CEO and all links between them gets highlighted. The problem are that little one blue nodes which I'm using as "connection" between bigger nodes, my highlight for now gets "cutted" on them because they are nodes in fact . Also it seems to me that there would be needed a IF statement which would tell if the hovered node is that little one or bigger, because I wanna that higllight on that little one connection nodes is working as if it works now..

I don't know from where to start from, so any suggestion or advice is welcome...

full script looks like this:

var data = {"nodes":[
                        {"name":"YHO", "full_name":"Yahoo", "type":1, "slug": "www.yahoo.com", "entity":"company", "img_hrefD":"", "img_hrefL":""},
                        {"name":"GGL", "full_name":"Google", "type":2, "slug": "www.google.com", "entity":"company", "img_hrefD":"", "img_hrefL":""},
                        {"name":"BNG", "full_name":"Bing", "type":2, "slug": "www.bing.com", "entity":"company", "img_hrefD":"", "img_hrefL":""},
                        {"name":"YDX", "full_name":"Yandex", "type":2, "slug": "www.yandex.com", "entity":"company", "img_hrefD":"", "img_hrefL":""},

                        {"name":"Desc1", "type":4, "slug": "", "entity":"description"},
                        {"name":"Desc2", "type":4, "slug": "", "entity":"description"},
                        {"name":"Desc4", "type":4, "slug": "", "entity":"description"},

                        {"name":"CEO", "prefix":"Mr.", "fst_name":"Jim", "snd_name":"Bean", "type":3, "slug": "", "entity":"employee"},
                        {"name":"ATT", "prefix":"Ms.", "fst_name":"Jenna", "snd_name":"Jameson", "type":3, "slug": "", "entity":"employee"},
                        {"name":"CTO", "prefix":"Mr.", "fst_name":"Lucky", "snd_name":"Luke", "type":3, "slug": "", "entity":"employee"},
                        {"name":"CDO", "prefix":"Ms.", "fst_name":"Pamela", "snd_name":"Anderson", "type":3, "slug": "", "entity":"employee"},
                        {"name":"CEO", "prefix":"Mr.", "fst_name":"Nacho", "snd_name":"Vidal", "type":3, "slug": "", "entity":"employee"},

                        {"name":"Desc5", "type":4, "slug": "", "entity":"description"},
                    ], 
            "links":[
                        {"source":0,"target":4,"value":1,"distance":5},
                        {"source":0,"target":5,"value":1,"distance":5},
                        {"source":0,"target":6,"value":1,"distance":5},

                        {"source":1,"target":4,"value":1,"distance":5},
                        {"source":2,"target":5,"value":1,"distance":5},
                        {"source":3,"target":6,"value":1,"distance":5},

                        {"source":7,"target":3,"value":10,"distance":6},
                        {"source":8,"target":3,"value":10,"distance":6},
                        {"source":9,"target":1,"value":10,"distance":6},
                        {"source":10,"target":1,"value":10,"distance":6},

                        {"source":11,"target":12,"value":10,"distance":6},
                        {"source":12,"target":2,"value":10,"distance":6},
                        ]
               }    



    var w = 560,
        h = 500,
        radius = d3.scale.log().domain([0, 312000]).range(["10", "50"]);

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

        //vis.append("defs").append("marker")
        //.attr("id", "arrowhead")
        //.attr("refX", 22 + 3) /*must be smarter way to calculate shift*/
        //.attr("refY", 2)
        //.attr("markerWidth", 6)
        //.attr("markerHeight", 4)
        //.attr("orient", "auto")
        //.append("path")
            //.attr("d", "M 0,0 V 4 L6,2 Z"); //this is actual shape for arrowhead

    //d3.json(data, function(json) {
        var force = self.force = d3.layout.force()
            .nodes(data.nodes)
            .links(data.links)
            .linkDistance(function(d) { return (d.distance*10); })
            //.friction(0.5)
            .charge(-250)
            .size([w, h])
            .start();



        var link = vis.selectAll("line.link")
            .data(data.links)
            .enter().append("svg:line")
            .attr("class", function (d) { return "link" + d.value +""; })
            .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; })
            .attr("marker-end", function(d) {
                                                if (d.value == 1) {return "url(#arrowhead)"}
                                                else    { return " " }
                                            ;});




        function openLink() {
            return function(d) {
                var url = "";
                if(d.slug != "") {
                    url = d.slug
                } //else if(d.type == 2) {
                    //url = "clients/" + d.slug
                //} else if(d.type == 3) {
                    //url = "agencies/" + d.slug
                //}
                window.open("//"+url)
            }
        }




        var node = vis.selectAll("g.node")
            .data(data.nodes)
          .enter().append("svg:g")
            .attr("class", "node")
            .call(force.drag);


        node.append("circle")
            .attr("class", function(d){ return "node type"+d.type})
            .attr("r",function(d){if(d.entity == "description"){ return 6 } else { return 18 }})
            //.on("mouseover", expandNode);
            //.style("fill", function(d) { return fill(d.type); })



        node.append("text")
            .attr("class", function(d){ return "nodetext title_"+d.name })
            .attr("dx", 0)
            .attr("dy", ".35em")
            .style("font-size","10px")
            .attr("text-anchor", "middle")
            .style("fill", "white")
            .text(function(d) { if (d.entity != "description"){return d.name} });



        node.on("mouseover", function (d) {
            if (d.entity == "company"){   
                d3.select(this).select('text')
                    .transition()
                    .duration(300)
                    .text(function(d){
                            return d.full_name;
                        })
                    .style("font-size","15px")

            }
            else if(d.entity == "employee"){
                var asdf = d3.select(this);
                asdf.select('text').remove();

                asdf.append("text")
                            .text(function(d){return d.prefix + ' ' + d.fst_name })
                            .attr("class","nodetext")
                            .attr("dx", 0)
                            .attr("dy", ".35em")
                            .style("font-size","5px")
                            .attr("text-anchor", "middle")
                            .style("fill", "white")
                            .transition()
                            .duration(300)
                            .style("font-size","12px");

                asdf.append("text").text(function(d){return d.snd_name })
                            .attr("class","nodetext")
                            .attr("transform","translate(0, 12)")
                            .attr("dx", 0)
                            .attr("dy", ".35em")
                            .style("font-size","5px")
                            .attr("text-anchor", "middle")
                            .style("fill", "white")
                            .transition()
                            .duration(300)
                            .style("font-size","12px");                                         
            }
            else {
                d3.select(this).select('text')
                    .transition()
                    .duration(300)
                    .style("font-size","15px")
            }

            if (d.entity == "company") {
                d3.select(this).select('image')
                    .attr("width", "90px")
                    .attr("x", "-46px")
                    .attr("y", "-36.5px")
                    .attr("xlink:href", function (d) {
                        return d.img_hrefL
                        });               
            }

            if (d.entity == "company") {

                d3.select(this).select('circle')
                                .transition()
                                .duration(300)
                                .attr("r",28)

            }
            else if (d.entity == "employee"){
                d3.select(this).select('circle')
                                .transition()
                                .duration(300)
                                .attr("r",32)
            }
        })


         node.on("mouseout", function (d) {
            if (d.entity == "company") {
                d3.select(this).select('text')
                    .transition()
                    .duration(300)
                    .text(function(d){return d.name;})
                    .style("font-size","10px")
                }
            else if(d.entity == "employee"){
                ///////////////////////////
                // CHANGE
                ///////////////////////////

                d3.select(this).selectAll('text').remove();

                //d3.select(this).select('text')
                d3.select(this).append('text')
                    .text(function(d){return d.name;})
                    .style("font-size","14px")  
                    .attr("dx", 0)
                    .attr("dy", ".35em")
                    .attr("text-anchor", "middle")
                    .style("fill", "white")
                    .attr("class","nodetext")
                    .transition()
                    .duration(300)
                    .style("font-size","10px")

            }
            else {
                d3.select(this).select('text')
                    .transition()
                    .duration(300)
                    .style("font-size","10px")
            }


             if (d.entity == "company") {
                d3.select(this).select('image')
                    .attr("width", "70px")
                    .attr("x", "-36px")
                    .attr("y", "-36px")
                    .attr("xlink:href", function (d) {
                    return d.img_hrefD
                });
            }

            if (d.entity == "company" || d.entity == "employee") {

                d3.select(this).select('circle')
                                .transition()
                                .duration(300)
                                .attr("r",18)
            }

        });

        node.on("mouseover", fade(.4,"red"))
            .on("mouseout", fade(1));

var linkedByIndex = {};
    data.links.forEach(function(d) {
        linkedByIndex[d.source.index + "," + d.target.index] = 1;
    });

    function isConnected(a, b) {
        return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
    }

        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 + ")"; });
        });

        function fade(opacity,color) {
            return function(d) {

         node.style("stroke-opacity", function(o) {
            thisOpacity = isConnected(d, o) ? 1 : opacity;
            this.setAttribute('fill-opacity', thisOpacity);
            return thisOpacity;
        });

                link.style("stroke-opacity", function(o) {
                    return o.source === d || o.target === d ? 1 : opacity;
                })

                .style("stroke", function(o) {
                    return o.source === d || o.target === d ? color : "#000" ;
                });
            };

            }
    //});
Community
  • 1
  • 1
dzordz
  • 2,277
  • 13
  • 51
  • 74

1 Answers1

1

To identify your "special" nodes, you could add another attribute to the data that identifies them. Then you can check this in your highlighting function and get second-degree neighbours if necessary. The code would look something like this.

function fade(opacity,color) {
    return function(d) {
        var connected = [d];
        if(d.isAuxiliary) {
            node.each(function(o) { if(isConnected(d, o)) { connected.push(o); } });
        }
        node.style("stroke-opacity", function(o) {
            thisOpacity = opacity;
            connected.forEach(function(e) { 
                if(isConnected(e, o)) { thisOpacity = 1; }
            });
            this.setAttribute('fill-opacity', thisOpacity);
            return thisOpacity;
        });
        // similar for links
    }
}

You can adapt this code to do an arbitrary level of neighbours.

Lars Kotthoff
  • 107,425
  • 16
  • 204
  • 204
  • "isAuxiliary" would be some outer function which would tell if node is special or not? – dzordz Oct 03 '13 at 08:34
  • Or an attribute in the data, yes. – Lars Kotthoff Oct 03 '13 at 08:39
  • okay, nodes are now working as I wanted http://jsfiddle.net/3SWtU/ Now I'm gonna try to do same for links – dzordz Oct 03 '13 at 09:52
  • I've lost myself with set up of this on links... could you help me please? I don't know how to pass the e inside to return properly like for nodes... I'm lost in this... please http://jsfiddle.net/3SWtU/1/ – dzordz Oct 03 '13 at 15:05
  • The code for the links follows exactly the same pattern -- http://jsfiddle.net/3SWtU/2/ – Lars Kotthoff Oct 03 '13 at 15:18
  • sorry but it seems that fade does not work, properly... I removed the color option, and on hover all links stay on 100% of opacity http://jsfiddle.net/tpPNf/ – dzordz Oct 03 '13 at 15:28
  • yea and the, `thisOpacity = opacity;` was supposed to be outside for each loop...now is working correctly... thank you man!! :) http://jsfiddle.net/NKzRH/ – dzordz Oct 03 '13 at 17:15