João Freitas

The following article teaches how you can create a beautiful graph slider with only SVG.

https://rauno.me/craft/graph-slider/


The graph is an svg element. And the rounded indicator itself has offset-path defined with the same path definition that renders the graph stroke. Basically, this property enables moving an element along a given path. For brevity, some details are omitted but here is the general idea:

<div>
  <div 
    data-dot-indicator
    style={{
      position: 'absolute',
      offsetPath: `path("${pathDefinition}")`
    }} 
  />
  <svg>
    <path 
      d={pathDefinition} 
      stroke="#C7C7C7" 
      strokeWidth="2.2" 
      strokeLinecap="round"
    />
  </svg>
</div>

Now to move the indicator, on mouse move the mouse distance is mapped to offset-distance as a percentage:

<div 
  data-dot-indicator
  style={{
    position: 'absolute',
    offsetPath: `path("${offsetPath}")`,
    offsetDistance: ((e.clientX - parentLeft) / parentWidth) * 100 + '%',
  }} 
/>

We could also use offset-path for the vertical indicator but I think that the label moving makes it tricky to read the value: ´

So we want to make sure the label position stays fixed, and translate the line with transform. However, we can’t just map the mouse movement one-to-one since the dot moves along a non-linear path. It would be out of sync that way, like so:

Instead, we can calculate the distance between the start and its current position, and center it relative to the indicator dot:

const x = dotX - parentX + dotWidth / 2 - lineWidth / 2

return (
  <div
    data-vertical-indicator
    style={{
      position: 'absolute',
      transform: `translateX(${x}px)`,
    }} 
  />
);

Lastly, in the SVG there are two paths on top of each other: a grayscaled and a colored one. To partially highlight the graph, clip-path is used to reveal the colored elements based on the same variable from above:

const clipPath = `inset(0 ${parentWidth - x - lineWidth * 2}px 0 0)`;

return (
  <svg>
    <path d={gradientDefinition} fill="url(#grayscale)" />
    <path d={gradientDefinition} fill="url(#color)" style={{ clipPath }} />
    <g strokeWidth="2.2" strokeLinecap="round">
      <path 
        d={strokeDefinition} 
        stroke="var(--colors-gray8)"     
        style={{ clipPath }}
      />
      <path
        d={strokeDefinition}
        stroke="var(--colors-blue9)"
      />
    </g>
  </svg>
);

Acknowledgements

Thanks to Family’s iOS app for the idea, Siddhartha for the graph, and Paco and Jonnie for their help and feedback.

#reads #rauno #svg #graph #math