Open mbostock opened 1 year ago
Maybe duplicate of #181.
I have tried to add clip-path
to Plot.image()
but it seems not handled by applyIndirectStyles()
(that is what my browsing of the code brings me to...)
I would like to clip an image to a geo path:
Plot.image(italy, {
x: (d) => d.properties.lon,
y: (d) => d.properties.lat,
width: (d) => d.properties.width,
height: (d) => d.properties.height,
preserveAspectRatio: "none",
src: (d) => d.properties.flag,
clipPath: (d) => `url(#iso-${d.id})`,
title: (d) => d.id
})
I have some tinkering going on here: https://observablehq.com/d/a8fe9e54cf07cb7a
Any thoughts?
Here's a snippet that uses the new render transform:
marks: [
Plot.geo(perimetro_mexico, {
render: (i, s, v, d, c, next) =>
svg`<clipPath id="x">${next(i, s, v, d, c).children[0]}` // create the clipPath "x"
}),
Plot.raster(data, {
x: "longitude",
y: "latitude",
fill: "banda_interes",
interpolate: Plot.interpolatorRandomWalk(),
render: (i, s, v, d, c, next) =>
svg`<g clip-path="url(#x)">${next(i, s, v, d, c)}` // reference "x" as clip-path
}),
see https://observablehq.com/d/d5d3052622043025 & https://observablehq.com/@fil/diy-live-map-of-air-quality-in-the-us
That’s really nice @Fil. I bet we could package that up into something reusable. Perhaps a clip transform where you supply a geometry channel, and it uses the geo mark under the hood?
We should consider using CSS clip-path instead of SVG, since it is now widely supported and much more convenient since you don’t need a globally unique identifier.
clip-path + path is still very poorly supported: https://observablehq.com/d/bf434fc5675c8f13
Chrome doesn't seem to support view-box + polygon(coords)—which works under Safari and Firefox.
Chromium bug reference: https://bugs.chromium.org/p/chromium/issues/detail?id=694218
Until this bug is resolved, we probably have to follow the classic route of adding a clipPath with a unique id and url(). An alternative possibility is to wrap the element we want to clip in a SVG element; but it seems more trouble than necessary, and only works for rectangles:
function applyClip(selection, channels) {
if (!channels) return;
const {x1: X1, y1: Y1, x2: X2, y2: Y2} = channels;
return selection
.each(function (i) {
const g = this.ownerDocument.createElementNS(namespaces.svg, "svg");
const x = Math.min(X1[i], X2[i]);
const y = Math.min(Y1[i], Y2[i]);
const w = Math.abs(X1[i] - X2[i]);
const h = Math.abs(Y1[i] - Y2[i]);
g.setAttribute("viewBox", `${x} ${y} ${w} ${h}`);
g.setAttribute("x", `${x}`);
g.setAttribute("y", `${y}`);
g.setAttribute("width", `${w}`);
g.setAttribute("height", `${h}`);
g.setAttribute("overflow", "hidden");
this.replaceWith(g);
g.appendChild(this);
});
}
The Chrome bug has been fixed in 119 https://chromestatus.com/feature/5068167415595008 https://developer.chrome.com/blog/new-in-chrome-119
The tests in https://observablehq.com/@fil/clip-path-and-basic-shapes-1109 seem to work in all major browsers now, so we could use style="clip-path: view-box path('${path}')"
.
@Fil just helped me with an issue applying clip paths to a faceted Ridgeline plot: https://observablehq.com/@chrispahm/ridgeline-plot-with-average-values
Here we used a style to cancel the transform
property from the clipPath elements (which is added by the facet system), in order to get the position right:
return svg`<clipPath id=${encodeURI(i.fy)} style="transform: none">${
next(i, s, v, d, c).children[0]
}`;
It’d be nice to support arbitrary clip paths. I can implement one by wrapping a mark like so:
That should probably extend Mark, though? And it should generate a unique identifier rather than using “clip”.
Then I could have a function that creates a clipPath element like so:
Ref. https://observablehq.com/@mjbo/oklab-named-colors-wheel