Graph Slider

May 2023

8:35 AM

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.