|
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"; |
@@ -306,42 +306,53 @@ export function maybeClip(clip) { |
306 | 306 | return clip; |
307 | 307 | } |
308 | 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 | + |
309 | 315 | // Note: may mutate selection.node! |
310 | 316 | function applyClip(selection, mark, dimensions, context) { |
311 | 317 | let clipUrl; |
312 | 318 | const {clip = context.clip} = mark; |
313 | 319 | switch (clip) { |
314 | 320 | case "frame": { |
315 | | - const {width, height, marginLeft, marginRight, marginTop, marginBottom} = dimensions; |
316 | | - const id = getClipId(); |
317 | | - clipUrl = `url(#${id})`; |
318 | | - selection = create("svg:g", context) |
319 | | - .call((g) => |
320 | | - g |
321 | | - .append("svg:clipPath") |
322 | | - .attr("id", id) |
323 | | - .append("rect") |
324 | | - .attr("x", marginLeft) |
325 | | - .attr("y", marginTop) |
326 | | - .attr("width", width - marginRight - marginLeft) |
327 | | - .attr("height", height - marginTop - marginBottom) |
328 | | - ) |
329 | | - .each(function () { |
330 | | - this.appendChild(selection.node()); |
331 | | - selection.node = () => this; // Note: mutation! |
332 | | - }); |
| 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")})`; |
333 | 340 | break; |
334 | 341 | } |
335 | 342 | case "sphere": { |
| 343 | + const clips = context.clips ?? (context.clips = new Map()); |
336 | 344 | const {projection} = context; |
337 | 345 | if (!projection) throw new Error(`the "sphere" clip option requires a projection`); |
338 | | - const id = getClipId(); |
339 | | - clipUrl = `url(#${id})`; |
340 | | - selection |
341 | | - .append("clipPath") |
342 | | - .attr("id", id) |
343 | | - .append("path") |
344 | | - .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")})`; |
345 | 356 | break; |
346 | 357 | } |
347 | 358 | } |
|
0 commit comments