简体   繁体   English

淡化未直接连接到d3图形中悬停的节点的链接和节点

[英]Fade links and nodes that are not immediately connected to the node hovered on in a d3 graph

I am now to d3 and web development in general. 我现在正在进行d3和web开发。

I am creating a graph using the d3 library. 我正在使用d3库创建一个图形。 I am trying to ensure that whenever the user hovers upon a node, the opacity of its immediate parents and children should remain the same but the opacity of the rest of the nodes should decrease. 我试图确保每当用户在节点上盘旋时,其直接父节点和子节点的不透明度应该保持不变,但其余节点的不透明度应该降低。

I am partly achieving my goal by letting the text written below fade for all the ones other than the one I hover on. 我部分实现了我的目标,让下面写的文字在我悬停的所有文本之外消失。

Here is my javascript code: 这是我的javascript代码:

// setting up the canvas size :)
var width = 960,
    height = 500;

// initialization
var svg = d3.select("div").append("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("id", "blueLine"); // the graph invisible thing :)

var force = d3.layout.force()
    .gravity(0) // atom's cohesiveness / elasticity of imgs :)
    .distance(150) // how far the lines ---> arrows :)
    .charge(-50) // meta state transition excitement
    .linkDistance(140)
    //.friction(0.55) // similar to charge for quick reset :)
    .size([width, height]); // degree of freedom to the canvas

// exception handling
d3.json("graph.json", function(error, json) {
    if (error) throw error;

    // Restart the force layout
    force
        .nodes(json.nodes)
        .links(json.links)
        .start();

    // Build the link
    var link = svg.selectAll(".links")
        .data(json.links)
        .enter().append("line")
        .attr("class", "lol")
        .style("stroke-width", "2")
        .attr("stroke", function(d){
            return linkColor(d.colorCode);})
        .each(function(d) {
            var color = linkColor(d.colorCode);
            d3.select(this).attr("marker-end", marker(color));
        });

    function marker(color) {
        svg.append("svg:marker")
            .attr("id", color.replace("#", ""))
            .attr("viewBox", "0 -5 10 10")
            .attr("refX", 10)
            .attr("refY", 0)
            .attr("markerWidth", 15)
            .attr("markerHeight", 15)
            .attr("orient", "auto")
            .attr("markerUnits", "userSpaceOnUse")
            .append("svg:path")
            .attr("d", "M0,-5L10,0L0,5")
            .style("fill", color);

        return "url(" + color + ")";
    };

    // this link : https://stackoverflow.com/questions/32964457/match-arrowhead-color-to-line-color-in-d3

    // create a node
    var node = svg.selectAll(".nodes")
        .data(json.nodes)
        .enter().append("g")
        .attr("class", "node")
        .call(force.drag)
        .on("mouseover", fade(.2))
        .on("mouseout", fade(1));;

    // Define the div for the tooltip
    var div = d3.select("body").append("pre")
        .attr("class", "tooltip")
        .style("opacity", 0);

    // Append custom images
    node.append("svg:image")
        .attr("xlink:href",  function(d) { return d.img;}) // update the node with the image
        .attr("x", function(d) { return -5;}) // how far is the image from the link??
        .attr("y", function(d) { return -25;}) // --- same ---
        .attr("height", 55) // size
        .attr("width", 55);

    node.append("text")
        .attr("class", "labelText")
        .attr("x", function(d) { return -5;})
        .attr("y", function(d) { return 48;})
        .text(function(d) { return d.name });

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

        force.stop();
    });

    function linkColor(linkCode) {
        switch (linkCode)
        {
            case 'ctoc':
                return '#0000FF';//blue
                break;
            case 'ctof':
                return '#00afaa';//green
                break;
            case 'ftoc':
                return '#fab800';//yellow
                break;
            case 'ftof':
                return '#7F007F';//purple
                break;
            default:
                return '#0950D0';//generic blue
                break;
        }
    }

    // build a dictionary of nodes that are linked
    var linkedByIndex = {};
    links.forEach(function(d) {
        linkedByIndex[d.source.id + "," + d.target.id] = 1;
    });

    // check the dictionary to see if nodes are linked
    function isConnected(a, b) {
        return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
    }

    // fade nodes on hover
    function fade(opacity) {
        return function(d) {
            // check all other nodes to see if they're connected
            // to this one. if so, keep the opacity at 1, otherwise
            // fade
            node.style("stroke-opacity", function(o) {
                thisOpacity = isConnected(d, o) ? 1 : opacity;
                return thisOpacity;
            });
            node.style("fill-opacity", function(o) {
                thisOpacity = isConnected(d, o) ? 1 : opacity;
                return thisOpacity;
            });
            // also style link accordingly
            link.style("stroke-opacity", function(o) {
                return o[0] === d || o[2] === d ? 1 : opacity;
            });
        };
    }
});

The css: css:

.node text {
    font-size: 1rem;
    text-decoration: underline;
    fill: #aeb4bf;
    font-weight: 700;
    text-anchor: end;
    alignment-baseline: central;
    pointer-events: none;
}
.node:not(:hover) .nodetext {
    display: none;
}

pre.tooltip {
    position: absolute;
    text-align: left;
    width: auto;
    height: auto;
    padding: 5px;
    font: 14px "Helvetica","Arial",sans-serif bold;
    background: #273142;
    border: 0;
    border-radius: 8px;
    cursor: pointer!important;
    pointer-events: none;
    color: #aeb4bf;
}

and my json file: 和我的json文件:

{
  "nodes": [
    {"x": 100, "y": 100, "name": "A", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 0},
    {"x": 250, "y": 100, "name": "B", "img":"https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 1},
    {"x": 400, "y": 100, "name": "C", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id": 2},
    {"x": 550, "y": 200, "name": "D", "img":"https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 3},
    {"x": 700, "y": 200, "name": "E", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 4},
    {"x": 100, "y": 300, "name": "F", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 5},
    {"x": 250, "y": 300, "name": "G", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 6},
    {"x": 400, "y": 300, "name": "H", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id": 7}
  ],
  "links": [
    {"source":  0, "target":  1, "colorCode" : "ctof"},
    {"source":  1, "target":  2, "colorCode" : "ftoc"},
    {"source":  2, "target":  3, "colorCode" : "ctof"},
    {"source":  3, "target":  4, "colorCode" : "ftoc"},
    {"source":  5, "target":  6, "colorCode" : "ctof"},
    {"source":  6, "target":  7, "colorCode" : "ftoc"},
    {"source":  7, "target":  3, "colorCode" : "ctof"}
  ]
}

I dont know where I am going wrong. 我不知道我哪里错了。 I need to achieve two things: 1. The immediate parents and children of X should stay unfaded if I hover over X and 2. The other nodes which aren't directly related to X should fade just the way the other links do. 我需要实现两件事:1。当我将鼠标悬停在X和2上时,X的直接父母和孩子应该保持不变。与X没有直接关系的其他节点应该像其他链接一样消失。 Currently none of the node fades. 目前,所有节点都没有消失。

I researched over my code and realized that it says that all the nodes are connected to each other so my isConnected() is the culprit. 我研究了我的代码并意识到它说所有节点都相互连接,所以我的isConnected()是罪魁祸首。 I still have no clue about the links though. 我仍然不知道这些链接。

Please help me. 请帮我。

two issues to resolve 要解决的两个问题

  1. For your nodes, as they are image files, you need set their 'opacity', and not the stroke/fill opacity. 对于您的节点,因为它们是图像文件,您需要设置它们的“不透明度”,而不是笔画/填充不透明度。

     node.style("opacity", function(o) { thisOpacity = isConnected(d, o) ? 1 : opacity; return thisOpacity; }); 
  2. For your links, assuming the name attributes are unique, you should match the link's source and target to the chosen node's name. 对于您的链接,假设名称属性是唯一的,您应该将链接的源和目标与所选节点的名称相匹配。

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

Addition to @TomShanley answer 除了@TomShanley的回答

Why are you using d3v3 if you are new to d3? 如果你是d3的新手,为什么还要使用d3v3? Currently we are at d3v5 and the API has much been improved. 目前我们处于d3v5并且API得到了很大改进。

The program does not run out of the box because in determining linkedByIndex it complains that links does not exist. 该程序没有开箱即用,因为在确定linkedByIndex它抱怨links不存在。 It should be json.links . 它应该是json.links

There is no need to put break after a return in linkColor . linkColor return后无需break

You search for elements of class svg.selectAll(".nodes") but you create elements with .attr("class", "node") . 您搜索类svg.selectAll(".nodes")元素,但您使用.attr("class", "node")创建元素。 This will not work if you want to use the enter-exit-update properly. 如果要正确使用enter-exit-update,这将不起作用。 The same with the links: search for class links but add elements with class lol . 与链接相同:搜索类links但添加类lol元素。

Your markers are not unique and no need to use each to add the marker-end . 您的标记不是唯一的,无需使用each marker-end添加marker-end Maybe best to create a set of markers based on color and just reference them. 也许最好根据颜色创建一组标记,然后引用它们。 In the original code you have multiple tags with the same id . 在原始代码中,您有多个具有相同id标记。 And an id in HTML should be unique. HTML中的id应该是唯一的。

// Build the link
var link = svg.selectAll(".lol")
    .data(json.links)
    .enter().append("line")
    .attr("class", "lol")
    .style("stroke-width", "2")
    .attr("stroke", function(d){
        return linkColor(d.colorCode);})
    .attr("marker-end", function(d, i){
        return marker(i, linkColor(d.colorCode));} );

function marker(i, color) {
    var markId = "#marker"+i;
    svg.append("svg:marker")
        .attr("id", markId.replace("#", ""))
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 10)
        .attr("refY", 0)
        .attr("markerWidth", 15)
        .attr("markerHeight", 15)
        .attr("orient", "auto")
        .attr("markerUnits", "userSpaceOnUse")
        .append("svg:path")
        .attr("d", "M0,-5L10,0L0,5")
        .style("fill", color);

    return "url(" + markId + ")";
};

Edit Unique markers, link is path from edge to edge 编辑唯一标记,链接是从边到边的路径

I have modified the code to have: 我已经修改了代码:

  • unique markers for each color put in the defs tag of the svg. 每种颜色的唯一标记放在svg的defs标签中。 Create a new marker if not already done for this color using an object to keep track. 如果尚未对此颜色使用对象进行跟踪,则创建一个新标记。
  • links are now paths to apply a marker trick described by Gerardo 链接现在是应用Gerardo描述的标记技巧的路径
  • images are now centered on the node position, this only works for circular images. 图像现在以节点位置为中心,这仅适用于圆形图像。

Here is the full code 这是完整的代码

var width = 960,
    height = 500;

// initialization
var svg = d3.select("div").append("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("id", "blueLine"); // the graph invisible thing :)

var svgDefs = svg.append("defs");

var force = d3.layout.force()
    .gravity(0) // atom's cohesiveness / elasticity of imgs :)
    .distance(150) // how far the lines ---> arrows :)
    .charge(-50) // meta state transition excitement
    .linkDistance(140)
    //.friction(0.55) // similar to charge for quick reset :)
    .size([width, height]); // degree of freedom to the canvas

// exception handling
d3.json("/fade-links.json", function(error, json) {
    if (error) throw error;

    var imageSize = { width:55, height:55 };

    // Restart the force layout
    force
        .nodes(json.nodes)
        .links(json.links)
        .start();

    var markersDone = {};

    // Build the link
    var link = svg.selectAll(".lol")
        .data(json.links)
        .enter().append("path")
        .attr("class", "lol")
        .style("stroke-width", "2")
        .attr("stroke", function(d){
            return linkColor(d.colorCode);})
        .attr("marker-end", function(d){
            return marker(linkColor(d.colorCode));} );

    function marker(color) {
        var markerId = markersDone[color];
        if (!markerId) {
            markerId = color;
            markersDone[color] = markerId;
            svgDefs.append("svg:marker")
                .attr("id", color.replace("#", ""))
                .attr("viewBox", "0 -5 10 10")
                .attr("refX", 10)
                .attr("refY", 0)
                .attr("markerWidth", 15)
                .attr("markerHeight", 15)
                .attr("orient", "auto")
                .attr("markerUnits", "userSpaceOnUse")
                .append("svg:path")
                .attr("d", "M0,-5L10,0L0,5")
                .style("fill", color);
        }
        return "url(" + markerId + ")";
    };

    // this link : https://stackoverflow.com/questions/32964457/match-arrowhead-color-to-line-color-in-d3

    // create a node
    var node = svg.selectAll(".node")
        .data(json.nodes)
        .enter().append("g")
        .attr("class", "node")
        .call(force.drag)
        .on("mouseover", fade(.2))
        .on("mouseout", fade(1));

    // Define the div for the tooltip
    var div = d3.select("body").append("pre")
        .attr("class", "tooltip")
        .style("opacity", 0);

    // Append custom images
    node.append("svg:image")
        .attr("xlink:href",  function(d) { return d.img;}) // update the node with the image
        .attr("x", function(d) { return -imageSize.width*0.5;}) // how far is the image from the link??
        .attr("y", function(d) { return -imageSize.height*0.5;}) // --- same ---
        .attr("height", imageSize.width)
        .attr("width", imageSize.height);

    node.append("text")
        .attr("class", "labelText")
        .attr("x", function(d) { return 0;})
        .attr("y", function(d) { return imageSize.height*0.75;})
        .text(function(d) { return d.name });

    force.on("tick", function() {
        // use trick described by Gerardo to only draw link from image border to border:  https://stackoverflow.com/q/51399062/9938317
        link.attr("d", function(d) {
            var dx = d.target.x - d.source.x,
                dy = d.target.y - d.source.y;
            var angle = Math.atan2(dy, dx);
            var radius = imageSize.width*0.5;
            var offsetX = radius * Math.cos(angle);
            var offsetY = radius * Math.sin(angle);
            return ( `M${d.source.x + offsetX},${d.source.y + offsetY}L${d.target.x - offsetX},${d.target.y - offsetY}`);
          });

        node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

        force.stop();
    });

    function linkColor(linkCode) {
        switch (linkCode)
        {
            case 'ctoc': return '#0000FF';//blue
            case 'ctof': return '#00afaa';//green
            case 'ftoc': return '#fab800';//yellow
            case 'ftof': return '#7F007F';//purple
        }
        return '#0950D0';//generic blue
    }

    // build a dictionary of nodes that are linked
    var linkedByIndex = {};
    json.links.forEach(function(d) {
        linkedByIndex[d.source.id + "," + d.target.id] = 1;
    });

    // check the dictionary to see if nodes are linked
    function isConnected(a, b) {
        return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
    }

    // fade nodes on hover
    function fade(opacity) {
        return function(d) {
            // check all other nodes to see if they're connected
            // to this one. if so, keep the opacity at 1, otherwise
            // fade
            node.style("opacity", function(o) {
                return isConnected(d, o) ? 1 : opacity;
            });
            // also style link accordingly
            link.style("opacity", function(o) {
                return o.source.name === d.name || o.target.name === d.name ? 1 : opacity;
            });
        };
    }
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM