|
1 |
| -import {geoPath, group, namespaces} from "d3"; |
| 1 | +import {geoPath, group, namespaces, select} from "d3"; |
2 | 2 | import {create} from "./context.js";
|
3 | 3 | import {defined, nonempty} from "./defined.js";
|
4 | 4 | import {formatDefault} from "./format.js";
|
5 | 5 | import {isNone, isNoneish, isRound, maybeColorChannel, maybeNumberChannel} from "./options.js";
|
6 |
| -import {keyof, number, string} from "./options.js"; |
| 6 | +import {keyof, keyword, number, string} from "./options.js"; |
7 | 7 | import {warn} from "./warnings.js";
|
8 | 8 |
|
9 | 9 | export const offset = (typeof window !== "undefined" ? window.devicePixelRatio > 1 : typeof it === "undefined") ? 0 : 0.5; // prettier-ignore
|
@@ -297,43 +297,62 @@ export function* groupIndex(I, position, mark, channels) {
|
297 | 297 | }
|
298 | 298 | }
|
299 | 299 |
|
300 |
| -// TODO avoid creating a new clip-path each time? |
| 300 | +// TODO Accept other types of clips (paths, urls, x, y, other marks…)? |
| 301 | +// https://github.com/observablehq/plot/issues/181 |
| 302 | +export function maybeClip(clip) { |
| 303 | + if (clip === true) clip = "frame"; |
| 304 | + else if (clip === false) clip = null; |
| 305 | + else if (clip != null) clip = keyword(clip, "clip", ["frame", "sphere"]); |
| 306 | + return clip; |
| 307 | +} |
| 308 | + |
| 309 | +function clipDefs({ownerSVGElement}) { |
| 310 | + const svg = select(ownerSVGElement); |
| 311 | + const defs = svg.select("defs.clip"); |
| 312 | + return defs.size() ? defs : svg.insert("defs", ":first-child").attr("class", "clip"); |
| 313 | +} |
| 314 | + |
301 | 315 | // Note: may mutate selection.node!
|
302 | 316 | function applyClip(selection, mark, dimensions, context) {
|
303 | 317 | let clipUrl;
|
304 | 318 | const {clip = context.clip} = mark;
|
305 | 319 | switch (clip) {
|
306 | 320 | case "frame": {
|
307 |
| - const {width, height, marginLeft, marginRight, marginTop, marginBottom} = dimensions; |
308 |
| - const id = getClipId(); |
309 |
| - clipUrl = `url(#${id})`; |
310 |
| - selection = create("svg:g", context) |
311 |
| - .call((g) => |
312 |
| - g |
313 |
| - .append("svg:clipPath") |
314 |
| - .attr("id", id) |
315 |
| - .append("rect") |
316 |
| - .attr("x", marginLeft) |
317 |
| - .attr("y", marginTop) |
318 |
| - .attr("width", width - marginRight - marginLeft) |
319 |
| - .attr("height", height - marginTop - marginBottom) |
320 |
| - ) |
321 |
| - .each(function () { |
322 |
| - this.appendChild(selection.node()); |
323 |
| - selection.node = () => this; // Note: mutation! |
324 |
| - }); |
| 321 | + const clips = context.clips ?? (context.clips = new Map()); |
| 322 | + if (!clips.has("frame")) { |
| 323 | + const {width, height, marginLeft, marginRight, marginTop, marginBottom} = dimensions; |
| 324 | + const id = getClipId(); |
| 325 | + clips.set("frame", id); |
| 326 | + clipDefs(context) |
| 327 | + .append("clipPath") |
| 328 | + .attr("id", id) |
| 329 | + .append("rect") |
| 330 | + .attr("x", marginLeft) |
| 331 | + .attr("y", marginTop) |
| 332 | + .attr("width", width - marginRight - marginLeft) |
| 333 | + .attr("height", height - marginTop - marginBottom); |
| 334 | + } |
| 335 | + selection = create("svg:g", context).each(function () { |
| 336 | + this.appendChild(selection.node()); |
| 337 | + selection.node = () => this; // Note: mutation! |
| 338 | + }); |
| 339 | + clipUrl = `url(#${clips.get("frame")})`; |
325 | 340 | break;
|
326 | 341 | }
|
327 | 342 | case "sphere": {
|
| 343 | + const clips = context.clips ?? (context.clips = new Map()); |
328 | 344 | const {projection} = context;
|
329 | 345 | if (!projection) throw new Error(`the "sphere" clip option requires a projection`);
|
330 |
| - const id = getClipId(); |
331 |
| - clipUrl = `url(#${id})`; |
332 |
| - selection |
333 |
| - .append("clipPath") |
334 |
| - .attr("id", id) |
335 |
| - .append("path") |
336 |
| - .attr("d", geoPath(projection)({type: "Sphere"})); |
| 346 | + if (!clips.has("projection")) { |
| 347 | + const id = getClipId(); |
| 348 | + clips.set("projection", id); |
| 349 | + clipDefs(context) |
| 350 | + .append("clipPath") |
| 351 | + .attr("id", id) |
| 352 | + .append("path") |
| 353 | + .attr("d", geoPath(projection)({type: "Sphere"})); |
| 354 | + } |
| 355 | + clipUrl = `url(#${clips.get("projection")})`; |
337 | 356 | break;
|
338 | 357 | }
|
339 | 358 | }
|
|
0 commit comments