|
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