Animated SVG Water Pictorial Fraction in Svelte

One of my favorite charts in data visualization is Pictorial Fraction. It's essentially a bar chart as a shape that can be partially filled or used as a small multiple to represent the ratio.

What We're Building

Why Svelte.js?

I've been excited to check out svelte.js for a while and this winter break gave me some time to try it out. It seemed especially great as a way to run data vis in a light-weight way. This was a way to learn a little more about it and see I can make multiple components.

The Pieces

The pictorial fraction is a combination of svg pieces that come together to show and mask elements.

Fill Area

Let's create a new file and call it PictorialFraction.svelte. In here, we'll set up our svg and add a rectangle with a color of your choosing. This will be the color that will show up in the droplet.

Now we can add some variables for width and height. In svelte, we export these in the script tag. We can also pass those variables into the elements.

💡 Svelte tip: we can use a shortcut to add those variables since they have the same name as the attributes. Instead of <svg width={width} /> we can use <svg width />.

Let's position the rectangle using transform: translate(0, 20px);. This should allow us to slide the rectangle up using the y position, giving our mask the feeling that the water drop is filling up. We're using the style attribute for transform rather than the transform attribute on rect because Safari doesn't animate the attribute in the same way Chrome and Firefox do, making it choppy.

Now we'll create the ripple shape that will be placed on top of our rectangle to give a "fluid" feel. For this, we'll use a path element and animate it with css.

That creates the shape, and now we'll position it to be on top of the rectangle. To do this we'll need to know where the rectangle will be positioned, and we'll wrap the path in group that's positioned based on rect's y position.

Let's add our styles to get the ripples animating in smooth way. To do this we can add a <style> tag underneath our <script> tag.

We declare a keyframes animation and let .water leverage that in an infinite loop. We're adding a transition to the rectangle and container for the ripple to make sure it slides up and down smoothly.

Mask Area

Now we can work on the mask area which will give us the water drop shape. We're going to use a clipPath with a path shaped as a water droplet. We'll apply the clipPath to a group that wraps the rectangle and ripple to mask out anything outside the water droplet shape.

We fill the droplet path with black to make the mask full opacity. We can then add another droplet of a different color so that we can have a different background color for our empty section.


Cool, now that we have the mask and bar set up we can configure the logic for the filling of the droplet. For this we'll create a variable of ratio and a reactive declaration that updates when props change. We'll call our reactive declaration offsetY and it will be based on ratio and height.

Now we'll pass that offset to the rectangle and ripple container, so they'll be positioned together. If the ratio is zero, the y should be the negative height of the ripple path which is 19px.

Using the component

To use this component we import it and pass it our width, height and ratio. Let's give it a height and width of 264.5px and a ratio of 0.5 (50%). Update the ratio to see the droplet fill or drain smoothly.


Thanks for following along! We now have a droplet component that we can use in our svelte app. We can add ways to control the ratio and make it more interactive. To add buttons to control the increase/decrease, check out the example on codesandbox

Discuss this article on Twitter