Search code examples
javascriptcsssvgd3.js

SVG arg fadeout/gradient effect


I'm trying to create doughnut visualization using d3 with fadeout/gradient effect for each arc like this: enter image description here

I've tried to add 'filter: drop-shadow(0 0 10px color)' but it doesn't give me this effect, also, it spread on both sides.

So I moved to use two pies

  1. Outer solid color
  2. Inner with (hopefully) the fade out effect

I know in gradient I can make solid, then fade out but since I don't have a clear path to success, I split to two pies

I've tried to use both linearGradient & radialGradient on the second pie which seems like a step in a good direction, but the gradient effect changes as the arcs angles are different:

enter image description here

For the simplicity, currently I created a gradient filter for just one color as I'm not sure this is the solution for my problem...

I've added a snippet to help the helpers :)

const colors = ['red','green','purple','blue','orange','teal']
const data = [10,12,33,112,22,4];
const pie = d3.pie().padAngle(0.03);
const arcs = pie(data);

const svg = d3.select('body')
  .append('svg');
 
const svgDefs = svg.append('defs');

const mainGradient = svgDefs.append('radialGradient')
                .attr('id', 'mainGradient');

            mainGradient.append('stop')
                .attr('stop-color', 'red')
                .attr('offset', '10%');

            mainGradient.append('stop')
                .attr('stop-color', 'pink')
                .attr('offset', '90%');



d3.select('body')
  .style('position', 'relative')
  .append('svg')
  .style('position', 'absolute')
  .style('z-index', 10)
  .attr('width', 600)
  .attr('height', 600)
  .append("g")
  .attr("transform", "translate(100,100)")
  .selectAll('path')
  .data(arcs)
  .enter()
  .append('path')
  .attr('d', (a,b,c) => d3.arc()({...a, innerRadius: 96, outerRadius: 100}))
  .style('fill', (d, index) => colors[index])

 svg.style('position', 'absolute')
    .attr('width', 600)
    .attr('height', 600)
  .append("g")
    .attr("transform", "translate(100,100)")
    .selectAll('path')
    .data(arcs)
    .enter()
    .append('path')
      .transition()
      .attr('d', (a,b,c) => d3.arc()({...a, innerRadius: 0, outerRadius: 96}))
      .style('fill', 'url(#mainGradient)')
    
<html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.14.2/d3.min.js"></script>
<body>
</body>
</html>


Solution

  • I’m not familiar with the D3 library. However, I can answer your question from a native SVG perpective, and then hopefully you can use those principles to create what you need in D3.

    A radial gradient is the correct solution, but you will probably need to define a different gradient for each slice of your doughnut. You also need to specify the position of each gradient as being the centre of your doughnut. (If you don’t specify a position for a radial gradient, it will position itself in the approximate centre of the fill area of the path to which it is applied, which in this case is not what you want.)

    Also, you will need to use two paths to create each slice:

    1. One arc-shaped path with a stroke to create the bright edge of the slice. We can’t fill this path, though, because the fill would only include the area bounded by a direct line between the two endpoints of the path.
    2. A second path with a gradient fill, which follows the same arc as the first path, but then extends to the centre of the doughnut and back to the start of the path (making a sector or “pizza slice”).

    This is similar to the idea stated in your question for having one pie for the outer edge and a second pie for the gradient fill.

    svg {height: 180px;}
    <svg viewBox="-6 -6 12 12">
      <defs>
        <radialGradient gradientUnits="userSpaceOnUse" cx="0" cy="0" r="5" id="gradient-0">
          <stop offset="0.6" style="stop-color: #ff634700;"/>
          <stop offset="0.9" style="stop-color: #ff63472f;"/>
        </radialGradient>
        <radialGradient gradientUnits="userSpaceOnUse" cx="0" cy="0" r="5" id="gradient-1">
          <stop offset="0.6" style="stop-color: #1c9cd900;"/>
          <stop offset="0.9" style="stop-color: #1c9cd92f;"/>
        </radialGradient>
      </defs>
      <path stroke="#ff6347" fill="none" d="M 0 -5 A 5 5 0 0 1 5 0"/>
      <path stroke-width="0" fill="url('#gradient-0')" d="M 0 -5 A 5 5 0 0 1 5 0 L 0 0 Z"/>
      <path stroke="#1c9cd9" fill="none" d="M 0 5 A 5 5 0 0 1 -5 0"/>
      <path stroke-width="0" fill="url('#gradient-1')" d="M 0 5 A 5 5 0 0 1 -5 0 L 0 0 Z"/>
    </svg>