Skip to content

Commit

Permalink
Use (and re-use) the same clip-path when two renders are clipped with…
Browse files Browse the repository at this point in the history
… the same shape
  • Loading branch information
Fil committed Jul 12, 2024
1 parent 7e09c31 commit cce9064
Showing 1 changed file with 47 additions and 27 deletions.
74 changes: 47 additions & 27 deletions src/style.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {geoPath, group, namespaces} from "d3";
import {geoPath, group, namespaces, select} from "d3";
import {create} from "./context.js";
import {defined, nonempty} from "./defined.js";
import {formatDefault} from "./format.js";
import {isNone, isNoneish, isRound, maybeColorChannel, maybeNumberChannel} from "./options.js";
import {keyof, number, string} from "./options.js";
import {keyof, keyword, number, string} from "./options.js";
import {warn} from "./warnings.js";

export const offset = (typeof window !== "undefined" ? window.devicePixelRatio > 1 : typeof it === "undefined") ? 0 : 0.5; // prettier-ignore
Expand Down Expand Up @@ -297,42 +297,62 @@ export function* groupIndex(I, position, mark, channels) {
}
}

// TODO Accept other types of clips (paths, urls, x, y, other marks…)?
// https://github.com/observablehq/plot/issues/181
export function maybeClip(clip) {
if (clip === true) clip = "frame";
else if (clip === false) clip = null;
else if (clip != null) clip = keyword(clip, "clip", ["frame", "sphere"]);
return clip;
}

function clipDefs({ownerSVGElement}) {
const svg = select(ownerSVGElement);
const defs = svg.select("defs.clip");
return defs.size() ? defs : svg.insert("defs", ":first-child").attr("class", "clip");
}

// Note: may mutate selection.node!
function applyClip(selection, mark, dimensions, context) {
let clipUrl;
const {clip = context.clip} = mark;
switch (clip) {
case "frame": {
const {width, height, marginLeft, marginRight, marginTop, marginBottom} = dimensions;
const id = getClipId();
clipUrl = `url(#${id})`;
selection = create("svg:g", context)
.call((g) =>
g
.append("svg:clipPath")
.attr("id", id)
.append("rect")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - marginRight - marginLeft)
.attr("height", height - marginTop - marginBottom)
)
.each(function () {
this.appendChild(selection.node());
selection.node = () => this; // Note: mutation!
});
const clips = context.clips ?? (context.clips = new Map());
if (!clips.has("frame")) {
const {width, height, marginLeft, marginRight, marginTop, marginBottom} = dimensions;
const id = getClipId();
clips.set("frame", id);
clipDefs(context)
.append("clipPath")
.attr("id", id)
.append("rect")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - marginRight - marginLeft)
.attr("height", height - marginTop - marginBottom);
}
selection = create("svg:g", context).each(function () {
this.appendChild(selection.node());
selection.node = () => this; // Note: mutation!
});
clipUrl = `url(#${clips.get("frame")})`;
break;
}
case "sphere": {
const clips = context.clips ?? (context.clips = new Map());
const {projection} = context;
if (!projection) throw new Error(`the "sphere" clip option requires a projection`);
const id = getClipId();
clipUrl = `url(#${id})`;
selection
.append("clipPath")
.attr("id", id)
.append("path")
.attr("d", geoPath(projection)({type: "Sphere"}));
if (!clips.has("projection")) {
const id = getClipId();
clips.set("projection", id);
clipDefs(context)
.append("clipPath")
.attr("id", id)
.append("path")
.attr("d", geoPath(projection)({type: "Sphere"}));
}
clipUrl = `url(#${clips.get("projection")})`;
break;
}
}
Expand Down

0 comments on commit cce9064

Please sign in to comment.