d3.js 如何修复不需要的圆圈/分解SVG路径元素的Sankey链接与d3?

n7taea2i  于 2022-11-12  发布在  其他
关注(0)|答案(3)|浏览(167)

我正在通过SVG制作一个Sankey图,但是突然在一些未知的条件下,其中一个路径显示了一个不应该有任何圆的圆。
我试图从我的SVG中尽可能多地删除,但进一步删除任何内容都会使其不再可复制。
这里你可以看到我的缩小的SVG图像。黑色的路径元素是一个有问题的。我手动标记路径笔划元素应该结束的地方,但不知何故,它显示了一个圆圈,似乎有相同的半径笔划宽度。

<div style="width: 100%; height: 100%;">
  <svg width=100% height=500px>
    <g class="sankey-layer">
      <g class="link-group">
        <path d="M107.61971830985915,30C107.61971830985915,74,107.61971830985915,74,107.61971830985915,118" fill="none" stroke="#000000" stroke-opacity="1" stroke-width="200"></path>
        <path d="M251.11267605633802,30C251.11267605633802,192,328.943661971831,192,328.943661971831,354" fill="none" stroke="#00bbff35" stroke-opacity="0.5" stroke-width="71.74647887323944"></path>
        <path d="M478.38028169014075,30C478.38028169014075,192,384.7464788732394,192,384.7464788732394,354" fill="none" stroke="#00bbff35" stroke-opacity="0.5" stroke-width="39.859154929577464"></path>
        <path d="M542.1549295774647,30C542.1549295774647,192,572.1549295774648,192,572.1549295774648,354" fill="none" stroke="#00bbff35" stroke-opacity="0.5" stroke-width="87.69014084507042"></path>
        <path d="M344.8169014084507,30C344.8169014084507,192,452.50704225352115,192,452.50704225352115,354" fill="none" stroke="#00bbff35" stroke-opacity="0.5" stroke-width="95.66197183098592"></path>
        <path d="M143.49295774647888,148C143.49295774647888,251,153.49295774647888,251,153.49295774647888,354" fill="none" stroke="#00bbff35" stroke-opacity="0.5" stroke-width="79.71830985915493"></path>
        <path d="M409.12773522289996,148C409.12773522289996,251,213.28169014084506,251,213.28169014084506,354" fill="none" stroke="#00bbff35" stroke-opacity="0.5" stroke-width="39.859154929577464"></path>
      </g>
    </g>
  </svg>
</div>

有什么线索吗?这是从哪里来的?我能做什么来避免它?

澄清:

路径是由d3计算的。它不是一个手动创建的图形,我正在寻找一个通用的解决方案的问题,而不是一个解决方案的这个特殊的例子。

cbjzeqam

cbjzeqam1#

我将第一个路径更改为<path d="M107.62,30L107.62,118" fill="none" stroke="#000000" stroke-opacity="1" stroke-width="200"></path>。我通过将其加载到Illustrator中来解决这个问题。
我不能100%确定问题出在哪里,但也许比我聪明的人会发现。

<div style="width: 100%; height: 100%;">
  <svg width=100% height=500px>
    <g class="sankey-layer">
      <g class="link-group">
        <path d="M107.62,30L107.62,118" fill="none" stroke="#000000" stroke-opacity="1" stroke-width="200"></path>
        <path d="M251.11267605633802,30C251.11267605633802,192,328.943661971831,192,328.943661971831,354"  fill="none" stroke="#00bbff35" stroke-opacity="0.5" stroke-width="71.74647887323944"></path>
        <path d="M478.38028169014075,30C478.38028169014075,192,384.7464788732394,192,384.7464788732394,354" fill="none" stroke="#00bbff35" stroke-opacity="0.5" stroke-width="39.859154929577464"></path>
        <path d="M542.1549295774647,30C542.1549295774647,192,572.1549295774648,192,572.1549295774648,354" fill="none" stroke="#00bbff35" stroke-opacity="0.5" stroke-width="87.69014084507042"></path>
        <path d="M344.8169014084507,30C344.8169014084507,192,452.50704225352115,192,452.50704225352115,354" fill="none" stroke="#00bbff35" stroke-opacity="0.5" stroke-width="95.66197183098592"></path>
        <path d="M143.49295774647888,148C143.49295774647888,251,153.49295774647888,251,153.49295774647888,354" fill="none" stroke="#00bbff35" stroke-opacity="0.5" stroke-width="79.71830985915493"></path>
        <path d="M409.12773522289996,148C409.12773522289996,251,213.28169014084506,251,213.28169014084506,354" fill="none" stroke="#00bbff35" stroke-opacity="0.5" stroke-width="39.859154929577464"></path>
      </g>
    </g>
  </svg>
</div>
chy5wohz

chy5wohz2#

似乎有一个与浏览器相关的错误,您应该报告
作为“后期处理”的解决方案,您可以使用我曾经创建的此辅助对象将平坦曲线转换为L命令。(基于path data polyfill by Jarek Foksa
已修复edge中的问题。
第一个

同时使用curveto命令

d="M107.6 30 C 107.6 74 107.6 118 107.6 118"
[x2,y2] = [x3,y3]
d="M107.6 30 C 107.6 118 107.6 118 107.6 118"
[x1,y1] = [x2,y2] = [x3,y3]

nhn9ugyo

nhn9ugyo3#

解决方法

我解决这个问题的第一个方法是为我的链接路径的预期边界(开始和结束)定义一个剪切路径。这样,工件就被简单地剪切了,没有任何问题可见:

<defs>
    <clipPath id="cut-off-link-{i}">
        <rect
            y={link.source.x1 - nodeHeight / 2}
            width="100%"
            height={link.target.x0 - link.source.x1 + nodeHeight}
        />
    </clipPath>
</defs>
<path
    d={d3shape.linkVertical()(link)}
    fill="none"
    stroke='#333'
    stroke-width={link.width}
    stroke-linecap="square"
    clip-path="url(#cut-off-link-{i})"
/>
  • 这段代码有点断章取义,但我将其包括进来只是为了给予解决方案的概念。它来自一个带有垂直Sankey的Svelte应用程序,为了简洁起见,我简化了代码。*

这个变通办法实际上效果很好,但感觉太错了。我想要一个“合适”的解决方案。
@herrstrietzel(https://stackoverflow.com/a/72588392/2230045)提出的解决方案在技术上似乎是一个更正确的解决方案,但我不能摆脱它仍然感觉像一个黑客的解决方案。

解决方案

在寻找解决方案时,我偶然发现了这篇博客文章:https://observablehq.com/@enjalot/weird-sankey-links
如果节点之间的距离太近且宽度太大,则默认的d3.sankeyLinkHorizontal会出现故障

虽然博客文章关注的问题看起来略有不同,但我确信这是我正在经历的完全相同的问题。
从某种意义上说,我觉得这里的潜在问题是路径元素的误用。当线条比笔画宽度短时,将填充设置为none并增加笔画宽度来绘制线条似乎无法正常工作。
这篇博文建议绘制自定义的贝塞尔曲线。这样就不需要stroke属性了,我们可以只使用normal fill。我觉得这是一个合适的解决方案。让我们来看看代码。
在这个问题之前,我并没有真正研究过Sankey图的图形方面。当我试图理解博客文章中提出的解决方案时(我需要将代码更改为垂直工作而不是水平工作),我努力完整地绘制出什么坐标指的是什么。d3文档在这方面并没有真正的帮助。这里是我为自己制作的信息图:

博客文章中的代码使用了一个稍微不同的坐标命名。我对此进行了修改,添加了一些注解,并试图简化它。

function sankeyLinkPath(link) {
    /**
     * This function is a drop in replacement for d3.sankeyLinkHorizontal().
     * Except any accessors/options.
     */
    // Start and end of the link
    let sx1 = link.source.x1;
    let tx0 = link.target.x0 + 1;

    // All four outer corners of the link
    // where e.g. lsy0 is the upper corner of the link on the source side
    let lsy0 = link.y0 - link.width / 2;
    let lsy1 = link.y0 + link.width / 2;
    let lty0 = link.y1 - link.width / 2;
    let lty1 = link.y1 + link.width / 2;

    // Center (x) of the link
    let lcx = sx1 + (tx0 - sx1) / 2;

    // Define outline of link as path
    let path = d3.path();
    path.moveTo(sx1, lsy0);
    path.bezierCurveTo(lcx, lsy0, lcx, lty0, tx0, lty0);
    path.lineTo(tx0, lty1);
    path.bezierCurveTo(lcx, lty1, lcx, lsy1, sx1, lsy1);
    path.lineTo(sx1, lsy0);
    return path.toString();
}

它所做的是,它画出了链接的轮廓路径。

它将我的代码简化为:

<path
    d={sankeyLinkPath(link)}
    fill='#333'
/>

调整Sankey的垂直绘图

最后,我调整了代码,使其垂直工作,而不是水平工作:

function sankeyLinkPath(link) {
    /**
     * This function is a drop in replacement for d3.sankeyLinkVertical().
     * Except any accessors/options.
     */
    // Start and end of the link
    let sy1 = link.source.x1;
    let ty0 = link.target.x0 + 1;

    // All four outer corners of the link
    // where e.g. lsx0 is the right corner of the link on the source side
    let lsx0 = link.y0 - (link.width / 2) * linkWidth;
    let lsx1 = link.y0 + (link.width / 2) * linkWidth;
    let ltx0 = link.y1 - (link.width / 2) * linkWidth;
    let ltx1 = link.y1 + (link.width / 2) * linkWidth;

    // Center (y) of the link
    let lcy = sy1 + (ty0 - sy1) / 2;

    // Define outline of link as path
    let path = d3.path();
    path.moveTo(lsx0, sy1);
    path.bezierCurveTo(lsx0, lcy, ltx0, lcy, ltx0, ty0);
    path.lineTo(ltx1, ty0);
    path.bezierCurveTo(ltx1, lcy, lsx1, lcy, lsx1, sy1);
    path.lineTo(lsx0, sy1);
    return path.toString();
}

这就产生了这个绝对完美的桑基图:
第一个
我获得了相当多的洞察力与svg和d3的工作与此。我希望该解决方案有助于其他人在未来。

相关问题