淡入淡出的链接和未立即连接到该节点的节点在d3图形中徘徊

时间:2018-07-12 21:43:58

标签: javascript html css d3.js

我现在通常是d3和Web开发。

我正在使用d3库创建图形。我试图确保每当用户将鼠标悬停在某个节点上时,其直接父级和子级的不透明度应保持不变,而其余节点的不透明度应降低。

我的目标是通过使下面写的文字消失而不是我所停留的文字的一部分来实现我的目标。

这是我的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;
            });
        };
    }
});

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;
}

和我的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"}
  ]
}

我不知道我要去哪里。我需要实现两件事:1.如果我将鼠标悬停在X和X上,则X的直接父代和子代应保持不褪色。2.与X没有直接关系的其他节点应该像其他链接一样淡出。目前,没有节点消失。

我对代码进行了研究,发现它说所有节点都相互连接,所以我的isConnected()是元凶。我仍然对链接一无所知。

请帮助我。

2 个答案:

答案 0 :(得分:2)

两个要解决的问题

  1. 对于您的节点,由于它们是图像文件,因此需要设置其“不透明度”,而不是笔触/填充不透明度。

    node.style("opacity", function(o) {
        thisOpacity = isConnected(d, o) ? 1 : opacity;
        return thisOpacity;
    });
    
  2. 对于链接,假设名称属性是唯一的,则应将链接的源和目标与所选节点的名称匹配。

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

答案 1 :(得分:1)

@TomShanley答案的补充内容

如果您不熟悉d3,为什么要使用d3v3?目前我们在d3v5上,API有了很大的改进。

该程序并没有立即可用,因为在确定linkedByIndex时它会抱怨links不存在。应该是json.links

无需在break中将return放在linkColor之后。

您搜索类svg.selectAll(".nodes")的元素,但是使用.attr("class", "node")创建元素。如果您要正确使用enter-exit-update,则此方法将无效。链接相同:搜索类links,但添加类为lol的元素。

您的标记不是唯一的,不需要使用each添加marker-end。 也许最好根据颜色创建一组标记,然后引用它们。 在原始代码中,您有多个带有相同id的标签。 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 + ")";
};

编辑:唯一标记,链接是一条边到另一边的路径

我将代码修改为:

    放在svg的defs标记中的每种颜色的
  • 唯一标记。如果尚未使用对象跟踪此颜色,请创建一个新标记。
  • 链接现在是应用Gerardo描述的标记技巧的路径
  • 图像现在位于节点位置的中心,这仅适用于圆形图像。

这是完整的代码

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