Multiple overlapping svg paths create unwanted effect

40 views Asked by At

I have created a tree-like structure of cards with lines between parents and children. The lines are svg paths like so:

const newpath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        newpath.setAttributeNS(
            null,
            'd',
            `M${parentX} ${parentY} h10 a${arcRadius},${arcRadius} 0 0 1 ${arcRadius},${arcRadius} v ${
                childY - arcRadius * 2
            } a${arcRadius},${arcRadius} 0 0 0 ${arcRadius},${arcRadius} h12`
        );
        newpath.setAttributeNS(null, 'stroke', 'black');
        newpath.setAttributeNS(null, 'stroke-width', '1');
        newpath.setAttributeNS(null, 'opacity', '1');
        newpath.setAttributeNS(null, 'fill', 'none');

Now this means that for 1 parent with 3 children, 3 paths are created. All 3 overlaps to the first child, 2 paths overlaps to the second child and finally there's 1 path to the third child. The whole tree is zoomable by adjusting the transform: scale() property. Now when I zoom out the lines are darker where they overlap. Is there any way to get rid of this behaviour except drawing the lines only from child to child?

A similiar question has been asked before here: Why do SVG lines/ paths on top of each other create a different stroke? but the accepted solution does not work for me.

enter image description here

enter image description here

1

There are 1 answers

1
herrstrietzel On BEST ANSWER

Unfortunately, there is no perfect solution.

A workaround providing a decent balance between crispness and smooth edges might be to apply a svg feComponentTransfer filter to enhance the contrast of the alpha channel.

svg{
  border: 1px solid #ccc;
  width: 70px;
}

path{
  stroke-width:1px;
  fill:none;
}

.crisp path{
  shape-rendering: crispEdges
}

.contrast
path{
   filter:url(#enhanceContrast);
}
<h3>Unedited</h3>

<svg  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" >
<path  stroke="#000"  d="M46.694,63.025c-6.627,0-12-5.373-12-12V16.248"/>
<path  stroke="#000"  d="M46.694,76.536c-6.627,0-12-5.373-12-12V29.758"/>
<path  stroke="#000"  d="M46.694,89.753c-6.627,0-12-5.373-12-12V42.975"/>
</svg>

<h3>shape-rendering: crispedges</h3>

<svg class="crisp" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" >
<path  stroke="#000"  d="M46.694,63.025c-6.627,0-12-5.373-12-12V16.248"/>
<path  stroke="#000"  d="M46.694,76.536c-6.627,0-12-5.373-12-12V29.758"/>
<path  stroke="#000"  d="M46.694,89.753c-6.627,0-12-5.373-12-12V42.975"/>
</svg>

<h3>Integer coordinates</h3>

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path  stroke="#000" d="M 47 63
c -7 0 -12 -5 -12 -12
v -35"/>
<path  stroke="#000" d="M 47 77
c -7 0 -12 -5 -12 -12
v -35"/>
<path  stroke="#000" d="M 47 90
c -7 0 -12 -5 -12 -12
v -35"/>
</svg>

<h3>Duplicate strokes</h3>

<svg  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path  stroke="#000" d="M 47 63
c -7 0 -12 -5 -12 -12
v -35"/>
<path  stroke="#000" d="M 47 77
c -7 0 -12 -5 -12 -12
v -35"/>
<path  stroke="#000" d="M 47 90
c -7 0 -12 -5 -12 -12
v -35"/>
<path  stroke="#000" d="M 47 63
c -7 0 -12 -5 -12 -12
v -35"/>
<path  stroke="#000" d="M 47 77
c -7 0 -12 -5 -12 -12
v -35"/>
<path  stroke="#000" d="M 47 90
c -7 0 -12 -5 -12 -12
v -35"/>
    
</svg>

<h3>Enhance contrast (svg filter)</h3>

<svg class="contrast" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" >
<path  stroke="#000"  d="M46.694,63.025c-6.627,0-12-5.373-12-12V16.248"/>
<path  stroke="#000"  d="M46.694,76.536c-6.627,0-12-5.373-12-12V29.758"/>
<path  stroke="#000"  d="M46.694,89.753c-6.627,0-12-5.373-12-12V42.975"/>
</svg>



<svg xmlns="http://www.w3.org/2000/svg" style="position: fixed; width:0; height:0; overflow:hidden;">
    <filter id="enhanceContrast" x="0" y="0" width="100%" height="100%">
      <feComponentTransfer>
        <feFuncA type="gamma" amplitude="1" exponent="1" offset="0"></feFuncA>        
      </feComponentTransfer>
      </filter>
</svg>

The above example compares different workarounds:

  • shape-rendering: crispEdges – perfect for straight lines but produces jagged edges on curves
  • Integer coordinates: rounding path command values to integers can often mitigate the undesired effect of differing stroke widths
  • Duplicate strokes: another approach is to duplicate paths – this will prevent "lighter" renderings but also results in a visually thicker stroke width
  • enhance alpha contrast via svg feComponentTransfer filter: we're basically reducing the number of semi-transparent pixels.