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.
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 curvesfeComponentTransfer
filter: we're basically reducing the number of semi-transparent pixels.