Change <svg:path/> Opacity on MouseOver

Viewed 75

I'm trying to create an effect where if a user mouses over a line chart the parts of the svg:path elements that are to the right of the mouse are faded out while the parts of the svg:path element to the left remain at full opacity.

I've tried a few options to no avail - see below.

My first try was to use a path with mask which does change the opacity, but the rest of the lines are hidden because they are not under the mask.

<defs>
    <mask
        id='mask-for-line'
        maskUnits="userSpaceOnUse"
        maskContentUnits="userSpaceOnUse"
    >
        <rect style={{opacity: .5, stroke: 'none', fill: 'white'}}
              x={x}
              y={y}
              width={width}
              height={height}
        />
    </mask>
</defs>
<path mask='url(#mask-for-line)' ... />

line with svg mask

My second try was to put an svg:rect over the faded-out section, but that doesn't work either.

<rect x={x} y={0} width={width} height={height} 
    style={{opacity: .1, stroke: 'none', fill: 'lightgray'}}/>

svg:rect overlaid on line chart

2 Answers

Thanks to the inspiration from michael-rovinsky I was able to solve the problem. Within the mask, I have one <rect/> at full opacity covering the left-side of the chart and a second <rect/> at 25% opacity covering the right-side of the chart.

<defs>
    <mask
        id='mask-for-line'
        maskUnits="userSpaceOnUse"
        maskContentUnits="userSpaceOnUse"
    >
        <rect style={{fillOpacity: .25, fill: 'white'}}
              x={x}
              y={y}
              width={width - x}
              height={height}
        />
        <rect style={{fillOpacity: 1, fill: 'white'}}
              width={x}
              height={height}
        />
    </mask>
</defs>

two svg:mask solution

You can try linear gradient with variable stop offsets:

const svg = d3.select('svg');
const width = parseInt(svg.attr('width'));
const height = parseInt(svg.attr('height'));
console.log(width, height);

const colors = ['red', 'green', 'blue', 'orange', 'purple', 'brown'];

const defs = svg.append('defs');
colors.forEach(color => {
    const grad = defs.append('linearGradient').attr('id', `${color}-opacity-mask`);
  grad.append('stop').attr('offset', '0%').attr('stop-color', color).attr('stop-opacity', 1);
  grad.append('stop').attr('stop-color', color).attr('stop-opacity', 1).classed('mid-stop', true);
  grad.append('stop').attr('stop-color', color).attr('stop-opacity', 0.25).classed('mid-stop', true);
  grad.append('stop').attr('offset', '100%').attr('stop-color', color).attr('stop-opacity', 0.25);
})

const step = 100;
const paths = colors.map(color => {
    let path = '';
  for (let index = 0; index <= width / step; index++)
    if (!index)
        path = `M 0,${Math.random() * height}`;
    else
        path += `L ${index * step},${Math.random() * height}`;
  return {color, path};
});

paths.forEach(({path, color}) => svg.append('path').attr('d', path).style('stroke', `url(#${color}-opacity-mask)`).style('fill', 'none'));

const line = svg.append('line')
  .attr('y1', 0)
  .attr('y2', height)
  .style('stroke', 'black')
  .style('stroke-dasharray', '3 3')
  .style('visibility', 'hidden');

svg.on('mousemove', e => {
    const pct = Math.round(100 * e.layerX / width); 
    svg.selectAll('.mid-stop').attr('offset', `${pct}%`);
    line.attr('x1', e.layerX).attr('x2', e.layerX).style('visibility', 'visible');
});

svg.on('mouseleave', e => line.style('visibility', 'hidden'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
<svg width="500" height="200">
</svg>

Related