【问题标题】:How to transition linear gradient for a path如何过渡路径的线性渐变
【发布时间】:2018-07-09 18:08:04
【问题描述】:

我正在制作一个 sankey 图,每当我使用 d3.js 和 sankey-plugin 更改过滤器(单选按钮)时,该图就会更新。现在我正在尝试添加一个功能,每当我将鼠标悬停在其中一个路径上时,我都会将线性渐变附加到从源节点颜色到目标节点颜色的路径。如果我不使用过滤器,一切正常,但是如果我应用过滤器(颜色设置错误),渐变着色不起作用,因为链接已转换。我认为我必须以某种方式转换线性渐变,但我不明白我必须如何做到这一点。

我写了一个小脚本来显示问题,在单击按钮颜色正确之前,以及在它搞砸之后。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="https://unpkg.com/d3-sankey@0.6"></script>
</head>
<body>
    <svg id="diagram" height="150" width="600"></svg>
    <button onclick="updateSankey()">Click Me!</button>

    <style>
        #diagram{
            border: 1px solid black;
        }
    </style>

    <script>
        var target = 0;
        var sankeyLinks;
        var sankeyData = {nodes:[], links:[]};

        calculateLinks();
        initSankey();
        updateSankey();

        function initSankey() {
            /*simple initialisation of the sankey, should explain itself*/

            svg = d3.select("svg"),
                width = +svg.attr("width"),
                height = +svg.attr("height");

            formatNumber = d3.format(",.0f"),
                format = function (d) { return formatNumber(d) + " %"; },
                color = d3.scaleOrdinal(d3.schemeCategory10);

            sankey = d3.sankey()
                .nodeWidth(15)
                .nodePadding(10)
                .extent([[1, 1], [width - 1, height - 6]])
                .iterations(0);

            t = d3.transition()
                .duration(1500)
                .ease(d3.easeLinear);

            //set attributes for all links
            titleGroup = svg.append("g")
                .attr("class", "titles")
                .attr("font-family", "sans-serif")
                .attr("font-size", "150%");

            diagram= svg.append("g")
                .attr("class", "sankey")
               // .attr("transform", "translate(" + marginleft + "," + margintop + ")");

            linkGroup = diagram.append("g")
                .attr("class", "links")
                .attr("fill", "none");
            //.attr("stroke", "#000")
            //.attr("stroke-opacity", 0.2);

            //set attributes for all nodes
            nodeGroup = diagram.append("g")
                .attr("class", "nodes")
                .attr("font-family", "sans-serif")
                .attr("font-size", 10);
        }

        function calculateLinks() {
            if(target == 0)
            {
                target = 1;
                sankeyLinks = [{source:0, target:1, value:5},{source:0, target:2, value:10},{source:0, target:3, value:15}];
            }
            else
            {
                target = 0;
                sankeyLinks = [{source:0, target:2, value:15},{source:0, target:1, value:20},{source:0, target:3, value:10}];
            }
        }

        function updateSankey() {
            calculateLinks();
            sankeyData.links = sankeyLinks;
            sankeyData.nodes =  [{name: "first"}, {name:"second"}, {name:"third"}, {name: "fourth"}];
            sankey(sankeyData);

            var links = linkGroup.selectAll('path')
                .data(sankeyData.links);

            //Set attributes for each link separately
            links.enter().append("g")
                .attr("id",function (d,i) {return "path"+i;})
                .append("path")
                .attr("stroke", "#000")
                .attr("stroke-opacity", 0.15)
                .attr("d", d3.sankeyLinkHorizontal())
                .attr("stroke-width", function (d) {return Math.max(1, d.width); })
                .on("mouseover",function (d,id) {
                    var pathGroup = svg.select('#path' + id);
                    var path = pathGroup.select("path");

                    path.attr("stroke","url(#grad"+id+")")
                        .attr("stroke-opacity","0.95");
                })
                .on("mouseout",function (d, id) {
                    pathGroup = svg.select('#path' + id);
                    var path = pathGroup.select("path");

                    path.attr("stroke","#000")
                        .attr("stroke-opacity","0.15");
                })
                .append("title")
                .text(function (d) {
                    //tooltip info for the links
                    return d.source.name + " → " + d.target.name + "\n" + format(d.value); });

            var pathGradient = svg.select(".links")
                .selectAll("g")
                .append("defs")
                .append("linearGradient")
                .attr("id",function (d, id) {
                    return "grad" + id;
                })
                //.attr("from", function () {return this.parentElement.parentElement.childNodes[0].getAttribute("from");})
                //.attr("to", function () {return this.parentElement.parentElement.childNodes[0].getAttribute("to");})
                .attr("gradientUnit","userSpaceOnUse")
                .attr("style","mix-blend-mode: multiply;")
                .attr("x1","0%")
                .attr("x2","100%")
                .attr("y1","0%")
                .attr("y2","0%");

            pathGradient.append("stop")
                .attr("class","from")
                .attr("offset","0%")
                .attr("style", function (d) {
                    var color = setColor(d.source);
                    return "stop-color:" + color + ";stop-opacity:1";
                });

            pathGradient.append("stop")
                .attr("class","to")
                .attr("offset","100%")
                .attr("style",function (d) {
                    var color = setColor(d.target);
                    return "stop-color:" + color + ";stop-opacity:1";
                });

            links.transition(t)
                .attr("d", d3.sankeyLinkHorizontal())
                .attr("stroke-width", function (d) { return Math.max(1, d.width); })
                .select('title')
                .text(function (d) {
                    //same argumentation as above, we need the method again for the transition
                    return d.source.name + " → " + d.target.name + "\n" + format(d.value); });

            links.exit().remove();

            var nodes = nodeGroup.selectAll('.node')
                .data(sankeyData.nodes);

            var nodesEnter = nodes.enter()
                .append("g")
                .attr('class', 'node');

            //set attributes for each node separately
            nodesEnter.append("rect")
                .attr("x", function (d) { return d.x0; })
                .attr("y", function (d) { return d.y0; })
                .attr("height", function (d) { return d.y1 - d.y0; })
                .attr("width", function (d) {
                    var width = d.x1 - d.x0;
                    return width;
                })
                .attr("fill", setColor)
                .attr("stroke", "#000")
                .attr("fill-opacity", 0.5)

            //specify Pop-Up when hovering over node
            nodesEnter.append("title")
                .text(function (d) { return d.name + "\n" + format(d.value); });

            //Update selection
            var nodesUpdate = nodes.transition(t);

            //same as the links we have to state the methods again in the update
            nodesUpdate.select("rect")
                .attr("y", function (d) { return d.y0; })
                .attr("x", function (d) { return d.x0; })
                .attr("height", function (d) { return d.y1 - d.y0; });

            nodesUpdate.select("title")
                .text(function (d) { return d.name + "\n" + format(d.value); });

            //Exit selection
            nodes.exit().remove();
        }

        function setColor(d) {
            switch (d.name) {
                case "first":
                    return "#f00";
                case "second":
                    return "#ff0";
                case "third":
                    return "#f0f";
                case "fourth":
                    return "#0ff";
                default:
                    return "#0f0";
            }
        }

    </script>
</body>
</html>

单击一次按钮后,从红色到紫色节点的路径具有从红色到黄色的线性渐变,即使我希望它从红色变为紫色。

我已经意识到,我可以写例如.iterations(15) 而不是 .iterations(0) 中的 initSankey() 来解决这个问题。在实际项目中我不能这样做,因为我必须强制节点的顺序。

我希望我的解释足够清楚,如果没有,请随时询问。

如果有人能告诉我如何解决这个问题,我会非常高兴。

附言。在这个 sn-p 中,顶部的链接在悬停时消失,我已经在实际项目中修复了这个问题,这没什么大不了的。

【问题讨论】:

    标签: javascript css d3.js svg dom-events


    【解决方案1】:

    您的问题是渐变网址基于 i,对于每次更新的特定链接可能会有所不同(即,链接的顺序可能不同,因此具有不同的 i 值);并且数据更新不是基于链接的恒定唯一 ID。

    在 sn-p 中,我在 calculateLinks 函数中为链接添加了一个唯一的名称值,然后将其用于数据连接和创建 def 梯度,这意味着它们在每次更新时保持不变。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="https://d3js.org/d3.v4.min.js"></script>
        <script src="https://unpkg.com/d3-sankey"></script>
    </head>
    <body>
        <svg id="diagram" height="150" width="600"></svg>
        <button onclick="updateSankey()">Click Me!</button>
    
      
        <style>
            #diagram{
                border: 1px solid black;
            }
        </style>
    
        <script>
            var target = 0;
            var sankeyLinks;
            var sankeyData = {nodes:[], links:[]};
    
            calculateLinks();
            initSankey();
            updateSankey();
    
            function initSankey() {
    
                svg = d3.select("svg"),
                    width = +svg.attr("width"),
                    height = +svg.attr("height");
    
                formatNumber = d3.format(",.0f"),
                    format = function (d) { return formatNumber(d) + " %"; },
    
                sankey = d3.sankey()
                    .nodeWidth(15)
                    .nodePadding(10)
                    .size([width - 1, height - 6])
    
                t = d3.transition()
                    .duration(1500)
                    .ease(d3.easeLinear);
    
                //set attributes for all links
                titleGroup = svg.append("g")
                    .attr("class", "titles")
                    .attr("font-family", "sans-serif")
                    .attr("font-size", "150%");
    
                diagram= svg.append("g")
                    .attr("class", "sankey")
                
                svg.append("defs")
                   
                linkGroup = diagram.append("g")
                    .attr("class", "links")
                    .attr("fill", "none");
    
                //set attributes for all nodes
                nodeGroup = diagram.append("g")
                    .attr("class", "nodes")
                    .attr("font-family", "sans-serif")
                    .attr("font-size", 10);
            }
    
            function calculateLinks() {
                if(target == 0)
                {
                    target = 1;
                    sankeyLinks = [
                      {name: "firstsecond",source:0, target:1, value:5},
                      {name: "firstthird",source:0, target:2, value:10},
                      {name: "firstfourth",source:0, target:3, value:15}];
                }
                else
                {
                    target = 0;
                    sankeyLinks = [
                      {name: "firstthird", source:0, target:2, value:15},
                      {name: "firstsecond", source:0, target:1, value:20},
                      {name: "firstfourth", source:0, target:3, value:10}
                    ];
                }
            }
    
            function updateSankey() {
                calculateLinks();
                sankeyData.links = sankeyLinks;
                sankeyData.nodes =  [{name: "first"}, {name:"second"}, {name:"third"}, {name: "fourth"}];
                sankey(sankeyData);
             
              
              var pathGradient = svg.select("defs").selectAll("linearGradient")
              			.data(sankeyData.links, function(d){ return d.name })
              			.enter()
                    .append("linearGradient")
                    .attr("id",function (d) {
                        return "grad" + d.name;
                    })
                    .attr("gradientUnit","userSpaceOnUse")
                    .attr("x1","0%")
                    .attr("x2","100%")
                    .attr("y1","0%")
                    .attr("y2","0%");
    
                pathGradient.append("stop")
                    .attr("class","from")
                    .attr("offset","0%")
                    .attr("style", function (d) {
                        var color = setColor(d.source);
                        return "stop-color:" + color;
                    });
    
                pathGradient.append("stop")
                    .attr("class","to")
                    .attr("offset","100%")
                    .attr("style",function (d) {
                        var color = setColor(d.target);
                        return "stop-color:" + color;
                    });
    
                var links = linkGroup.selectAll('path')
                    .data(sankeyData.links, function(d){ return d.name });
    
                //Set attributes for each link separately
                var linksenter = links.enter()
                		.append("g")
                    .attr("id",function (d) {return "path" + d.name;})
                    .append("path")
                    .style("stroke", "#000")
                    .style("stroke-opacity", 0.15)
                    .attr("stroke-width", function (d) {return Math.max(1, d.width); })
                    .on("mouseover",function (d) {
                        var pathGroup = svg.select('#path' + d.name);
                        var path = pathGroup.select("path");
    
                        path.style("stroke","url(#grad" + d.name + ")")
                            .style("stroke-opacity","0.95");
                    })
                    .on("mouseout",function (d, id) {
                        pathGroup = svg.select('#path' + d.source.name + d.target.name);
                        var path = pathGroup.select("path");
    
                        path.style("stroke","#000")
                            .style("stroke-opacity","0.15");
                    })
          
    						linksenter.merge(links).attr("d", d3.sankeyLinkHorizontal())
                
                links.transition(t)
                    .attr("d", d3.sankeyLinkHorizontal())
                    .attr("stroke-width", function (d) { return Math.max(1, d.width); })
                    .select('title')
                    .text(function (d) {
                        return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
    
                var nodes = nodeGroup.selectAll('.node')
                    .data(sankeyData.nodes, function(d){ return d.name });
    
                var nodesEnter = nodes.enter()
                    .append("g")
                    .attr('class', 'node');
    						
              	nodesEnter.append("rect")
                    .attr("x", function (d) { return d.x0; })
                    .attr("y", function (d) { return d.y0; })
                    .attr("height", function (d) { return d.y1 - d.y0; })
                    .attr("width", function (d) {
                        var width = d.x1 - d.x0;
                        return width;
                    })
                    .attr("fill", setColor)
                    .attr("stroke", "#000")
                    .attr("fill-opacity", 0.5)
    
                //specify Pop-Up when hovering over node
                nodesEnter.append("title")
                    .text(function (d) { return d.name + "\n" + format(d.value); });
    
                //Update selection
                var nodesUpdate = nodes.transition(t);
    
                //same as the links we have to state the methods again in the update
                nodesUpdate.select("rect")
                    .attr("y", function (d) { return d.y0; })
                    .attr("x", function (d) { return d.x0; })
                    .attr("height", function (d) { return d.y1 - d.y0; });
    
                nodesUpdate.select("title")
                    .text(function (d) { return d.name + "\n" + format(d.value); });
    
            }
    
            function setColor(d) {
                switch (d.name) {
                    case "first":
                        return "#f00";
                    case "second":
                        return "#ff0";
                    case "third":
                        return "#f0f";
                    case "fourth":
                        return "#0ff";
                    default:
                        return "#0f0";
                }
            }
    
        </script>
    </body>
    </html>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-06
      • 2017-09-03
      • 2020-12-07
      • 1970-01-01
      • 2021-12-13
      • 2011-11-13
      • 2011-02-19
      相关资源
      最近更新 更多