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