Let's say we have the following SVG path:
<path id="heart" d="M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z" />
I'm wondering if there is a way to fill only a portion of the area of this shape proportional to a given ratio. I do need to color exactly the amount of pixels equals to the ratio.
E.g., ratio = 0,6, then just fill the 60% of the pixels of the shape (starting from the bottom).
Edit:
I tried to implement the @RobertLongson's hint in the comment. There you go:
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<linearGradient id="myGradient" gradientTransform="rotate(90)">
<stop offset="50%" stop-color="black" stop-opacity='0' />
<stop offset="50%" stop-color="black" />
</linearGradient>
<path id="heart" d="M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z" fill="url('#myGradient')" stroke="black" stroke-width="1" />
</svg>
However, if the ratio is 50%, this doesn't work. (i.e. we are not coloring the 50% of the pixels)
How to fill a
<path>
with a percentage volume
I went for the brute force method eventually;
Check every SVG pixel being in the shape with isPointInFill
All data is available in the filled
Array and P.y
values
filled
values are from bottom to top of the path shape
Using a native JavaScript Web Component (JSWC)
Needs some more work with those values to draw a solid fill or gradient
50% , 20% and 80% <svg-area>
shapes:
<style>
svg { --size: 180px; height: var(--size); background: pink }
path { fill: lightgreen; stroke: grey }
</style>
<svg-area percent=".5">
<svg viewBox="0 0 100 100">
<path d="M10,30A20,20,0,0,1,50,30A20,20,0,0,1,90,30Q90,60,50,90Q10,60,10,30z"></path>
</svg>
</svg-area>
<svg-area percent=".2">
<svg viewBox="0 0 100 100">
<path d="M60,10L90,90L10,60z"></path>
</svg>
</svg-area>
<svg-area percent=".8">
<svg viewBox="0 0 50 50">
<path d="m18 15q41 18 28-8l-14 30-29-15a1 1 0 0029 15z"></path>
</svg>
</svg-area>
<script>
customElements.define("svg-area", class extends HTMLElement {
connectedCallback() {
setTimeout(() => {// make sure Web Component can read parsed innerHTML
let svg = this.querySelector("svg");
let path = svg.querySelector("path");
let filled = [], percent = this.getAttribute("percent");
Array(svg.height.baseVal.value).fill().map((_, y) => {
Array(svg.width.baseVal.value).fill().map((_, x) => {
let P = svg.createSVGPoint(); P.x = x; P.y = y;
if (path.isPointInFill(P)) filled.push( P );
});
});
svg.append(
...filled.reverse()
.filter( (P,idx) => idx < filled.length * percent)
.map( P => this.circle(P.x, P.y, "green") ));
});
}
circle(cx, cy, fill) {
let circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("cx", cx);
circle.setAttribute("cy", cy);
circle.setAttribute("fill", fill);
circle.setAttribute("r", .3);
return circle;
}
});
</script>