Making progress meter
In the process of re-creating a nifty application to keep track of tasks I needed to visualize progress in something close to a meter, a horizontal stripe divvied up in smaller, colored rectangles. In this app you can add, complete and pause a task so that the meter needs to highlight the three kinds with three colors. Or two if you consider the color in the background and resolve to show the remaining tasks as empty space.
There are several ways to solve this challenge, not in the least drawing rectangles with specific coordinates and dimensions. Something quite simple with SVG.
<svg viewBox="0 0 42 1">
<rect x="0" width="16.8" height="1" fill="lightgreen" />
<rect x="16.8" width="12.6" height="1" fill="khaki" />
</svg> Where the first rectangle ends the second begins, so you update the positions and the dimensions making a decision on the aspect ratio — you may not even need a complex library like D3 to pull off the feat. That being said, I found at least two alternatives with their own reason, other than postponing an inevitable thing to do.
The first one is perhaps too exotic, but goes back to SVG and just one rectangle, the one for the larger meter. The difference is that you color said rectangle with a gradient.
<svg viewBox="0 0 42 1">
<rect fill="url(#meter-gradient)" width="42" height="1" />
</svg> A linear gradient where the colors follow each other with hard stops.
<linearGradient id="meter-gradient">
<stop stop-color="lightgreen" offset="0.4" />
<stop stop-color="khaki" offset="0.4" />
<stop stop-color="khaki" offset="0.7" />
<stop stop-color="transparent" offset="0.7" />
</linearGradient> With colors instead of shapes the logic is similar: where one color ends, the one which follows immediately begins. Applied to the rectangle the result is the same, barring rounding approximations. And this time the math might even come easier as you need to adjust the offset attribute with values in a convenient 0 to 1 range.
- Complete
- 0.4
- Paused
- 0.3
- Left
- 0.3
Unfortunately, not everything glitters. The code works to visualize the progress, but if you change the values the shift is immediate. It is not possible to update the offset over time with something as simple as a CSS transition. In CSS there is an offset property, but reflects the position of an element along a predefined path, not the attribute of the multiple stops. Leaving the realm of SVG, however, there is certainly a way to have CSS fill the gap. The idea is to use regular HTML elements like empty divs, one for the larger meter and three for the different sections.
<div class="meter">
<div data-value="complete"></div>
<div data-value="paused"></div>
<div data-value="left"></div>
</div> With CSS it is possible to resize the block to keep the same dimensions thanks to the aspect-ratio property, and even have the precaution of setting a minimum height.
.meter {
inline-size: 100%;
aspect-ratio: 42/1;
min-block-size: 1ex;
} Always with CSS, you then recreate the horizontal split making the parent container a flex container. The child nodes can occupy a fraction of the space with the flex-grow property, and you can soften the change in this property with the simple declaration of a transition.
.meter {
/* ... */
display: flex;
}
.meter div {
flex-grow: 1;
transition: flex-grow 0.2s;
} If the three nested divs share the same value they each occupy a third of the space, but the moment you tweak the property the effect is complete. The elements grow and shrink to reach the new state in a more pleasing manner. And you have gained something more than a soothing change: the value of flex-grow doesn’t need to fit in any given range. This means you can use the number of tasks, be them complete, paused, and the rest, and have CSS figure out the proportions. All that is left is adding the colors with a solid background.
/* ... */
[data-value="complete"] {
background: lightgreen;
}
[data-value="paused"] {
background: khaki;
} Sit back and focus on developing the full application and ticking off a few more tasks. 4 complete, 3 paused, 3 left.