沿 D3 Sankey 图中链接的渐变 [英] Gradient along links in D3 Sankey diagram

查看:30
本文介绍了沿 D3 Sankey 图中链接的渐变的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

,它绘制了这个渐变填充循环:

但是,我根本无法将该解决方案集成到我的解决方案中,它对于给定的任务来说看起来太复杂了.

另外,请注意原始 Sankey 图中的链接在节点被拖动时移动,并且即使在这些过渡状态下也必须显示梯度.还有一个小问题是链接和节点的透明度以及绘制顺序.我很感激想法和提示.

解决方案

@VividD:刚看到你的评论,但我已经完成了.在您自己弄清楚之前,可以随意忽略这一点,但我想确保也知道如何去做.另外,这是一个非常常见的问题,非常适合参考.

如何获得沿线定位的渐变

<块引用>

对于稍后阅读本文的任何人的警告,它只会起作用,因为路径几乎是直线,因此线性渐变看起来不合适——设置路径笔划到渐变不会使渐变曲线与路径!

  1. 在初始化中,在SVG中创建一个(定义)元素并将选择保存到一个变量中:

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

  2. 定义一个函数,该函数将从链接数据对象为您的渐变创建一个唯一 id.为确定节点颜色的函数命名也是一个好主意:

    function getGradID(d){return "linkGrad-" + d.source.name + d.target.name;}function nodeColor(d) { return d.color = color(d.name.replace(/.*/, ""));}

  3. 创建一个<linearGradient> 中的 code> 对象并将其连接到您的链接数据,然后根据源和目标数据对象设置停止偏移和线坐标.

    对于您的示例,如果您只是将所有渐变设为水平,它可能看起来不错.由于这是方便的默认设置,我认为我们要做的就是告诉渐变适合它正在绘制的路径的大小:

    var grads = defs.selectAll("linearGradient").data(graph.links, getLinkID);grads.enter().append("linearGradient").attr("id", getGradID).attr("gradientUnits", "objectBoundingBox");//拉伸以适应grads.html("")//删除任何现有的<stop>更新元素.append("停止").attr("偏移", "0%").attr(停止颜色",函数(d){返回节点颜色((d.source.x <= d.target.x)?d.source:d.target)});grads.append("停止").attr("偏移", "100%").attr(停止颜色",函数(d){返回节点颜色((d.source.x > d.target.x)?d.source:d.target)});

    不幸的是,当路径是完全直线时,它的边界框不存在(无论笔画宽度有多宽),最终结果是渐变没有被绘制.

    所以我不得不切换到更通用的模式,其中渐变沿着源和目标之间的线定位和倾斜:

    grads.enter().append("linearGradient").attr("id", getGradID).attr("gradientUnits", "userSpaceOnUse");grads.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;});/* 和之前设置的停止 */

  4. 当然,现在梯度是基于坐标系而不是基于路径的长度定义的,每当节点移动时你必须更新这些坐标,所以我不得不将这些定位语句包装在我可以在 dragmove() 函数中调用的函数.

  5. 最后,在创建链接路径时,将它们的填充设置为 CSS url() 函数,引用从数据派生的相应唯一梯度 id(使用预定义的实用程序函数):

    link.style("stroke", function(d){返回 "url(#" + getGradID(d) + ")";})

还有瞧!

Here is jsfiddle of a Sankey diagram:

I am trying to modify colors of the links so that the color of each link is actually gradient from its source node color to its target node color. (it is assumed that opacity will remain 0.2 or 0.5 depending whether a mouse hovers or not over the link; so links will remain a little "paler" than nodes)

I took a look at this nice and instructive example, which draws this gradient filled loop:

However, I simply couldn't integrate that solution to mine, it looks too complex for the given task.

Also, note that links in original Sankey diagram move while node is being dragged, and must display gradient even in those transitory states. A slight problem is also transparency of links and nodes, and order of drawing. I would appreciate ideas, hints.

解决方案

@VividD: Just saw your comment, but I was about done anyway. Feel free to ignore this until you've figured it out on the own, but I wanted to make sure I knew how to do it, too. Plus, it's a really common question, so good to have for reference.

How to get a gradient positioned along a line

With the caveat for anyone reading this later, that it will only work because the paths are almost straight lines, so a linear gradient will look half-decent -- setting a path stroke to a gradient does not make the gradient curve with the path!

  1. In initialization, create a <defs> (definitions) element in the SVG and save the selection to a variable:

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

  2. Define a function that will create a unique id for your gradient from a link data object. It's also a good idea to give a name to the function for determining node colour:

    function getGradID(d){return "linkGrad-" + d.source.name + d.target.name;}
    function nodeColor(d) { return d.color = color(d.name.replace(/ .*/, ""));}
    

  3. Create a selection of <linearGradient> objects within <defs> and join it to your link data, then set the stop offsets and line coordinates according to the source and target data objects.

    For your example, it probably will look fine if you just make all the gradients horizontal. Since that's conveniently the default I thought all we would have to do is tell the gradient to fit to the size of the path it is painting:

    var grads = defs.selectAll("linearGradient")
                     .data(graph.links, getLinkID);
    
    grads.enter().append("linearGradient")
                 .attr("id", getGradID)
                 .attr("gradientUnits", "objectBoundingBox"); //stretch to fit
    
    grads.html("") //erase any existing <stop> elements on update
         .append("stop")
         .attr("offset", "0%")
         .attr("stop-color", function(d){
               return nodeColor( (d.source.x <= d.target.x)? d.source: d.target) 
              });
    
    grads.append("stop")
         .attr("offset", "100%")
         .attr("stop-color", function(d){
               return nodeColor( (d.source.x > d.target.x)? d.source: d.target) 
              });
    

    Unfortunately, when the path is a completely straight line, its bounding box doesn't exist (no matter how wide the stroke width), and the net result is the gradient doesn't get painted.

    So I had to switch to the more general pattern, in which the gradient is positioned and angled along the line between source and target:

    grads.enter().append("linearGradient")
        .attr("id", getGradID)
        .attr("gradientUnits", "userSpaceOnUse");
    
    grads.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;});
    
    /* and the stops set as before */
    

  4. Of course, now that the gradient is defined based on the coordinate system instead of based on the length of the path, you have to update those coordinates whenever a node moves, so I had to wrap those positioning statements in a function that I could call in the dragmove() function.

  5. Finally, when creating your link paths, set their fill to be a CSS url() function referencing the corresponding unique gradient id derived from the data (using the pre-defined utility function):

    link.style("stroke", function(d){
        return "url(#" + getGradID(d) + ")";
    })
    

And Voila!

这篇关于沿 D3 Sankey 图中链接的渐变的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆