|
1 | | -import {geoPath, group, namespaces, select} from "d3"; |
| 1 | +import {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,24 +306,20 @@ export function* groupIndex(I, position, mark, channels) { |
306 | 306 | function applyClip(selection, mark, dimensions, context) { |
307 | 307 | let clipUrl; |
308 | 308 | const {clip = context.clip} = mark; |
309 | | - switch (clip) { |
310 | | - case "frame": { |
311 | | - // Wrap the G element with another (untransformed) G element, applying the |
312 | | - // clip to the parent G element so that the clip path is not affected by |
313 | | - // the mark’s transform. To simplify the adoption of this fix, mutate the |
314 | | - // passed-in selection.node to return the parent G element. |
315 | | - selection = create("svg:g", context).each(function () { |
316 | | - this.appendChild(selection.node()); |
317 | | - selection.node = () => this; // Note: mutation! |
318 | | - }); |
319 | | - clipUrl = getFrameClip(context, dimensions); |
320 | | - break; |
321 | | - } |
322 | | - case "sphere": { |
323 | | - clipUrl = getProjectionClip(context); |
324 | | - break; |
325 | | - } |
| 309 | + if (clip === "frame") { |
| 310 | + // Wrap the G element with another (untransformed) G element, applying the |
| 311 | + // clip to the parent G element so that the clip path is not affected by |
| 312 | + // the mark’s transform. To simplify the adoption of this fix, mutate the |
| 313 | + // passed-in selection.node to return the parent G element. |
| 314 | + selection = create("svg:g", context).each(function () { |
| 315 | + this.appendChild(selection.node()); |
| 316 | + selection.node = () => this; // Note: mutation! |
| 317 | + }); |
| 318 | + clipUrl = getFrameClip(context, dimensions); |
| 319 | + } else if (clip) { |
| 320 | + clipUrl = getGeoClip(clip, context); |
326 | 321 | } |
| 322 | + |
327 | 323 | // Here we’re careful to apply the ARIA attributes to the outer G element when |
328 | 324 | // clipping is applied, and to apply the ARIA attributes before any other |
329 | 325 | // attributes (for readability). |
@@ -356,11 +352,21 @@ const getFrameClip = memoizeClip((clipPath, context, dimensions) => { |
356 | 352 | .attr("height", height - marginTop - marginBottom); |
357 | 353 | }); |
358 | 354 |
|
359 | | -const getProjectionClip = memoizeClip((clipPath, context) => { |
360 | | - const {projection} = context; |
361 | | - if (!projection) throw new Error(`the "sphere" clip option requires a projection`); |
362 | | - clipPath.append("path").attr("d", geoPath(projection)({type: "Sphere"})); |
363 | | -}); |
| 355 | +const getGeoClip = (function () { |
| 356 | + const cache = new WeakMap(); |
| 357 | + const sphere = {type: "Sphere"}; |
| 358 | + return (geo, context) => { |
| 359 | + let c, url; |
| 360 | + if (!(c = cache.get(context))) cache.set(context, (c = new WeakMap())); |
| 361 | + if (geo.type === "Sphere") geo = sphere; // coalesce all spheres. |
| 362 | + if (!(url = c.get(geo))) { |
| 363 | + const id = getClipId(); |
| 364 | + select(context.ownerSVGElement).append("clipPath").attr("id", id).append("path").attr("d", context.path()(geo)); |
| 365 | + c.set(geo, (url = `url(#${id})`)); |
| 366 | + } |
| 367 | + return url; |
| 368 | + }; |
| 369 | +})(); |
364 | 370 |
|
365 | 371 | // Note: may mutate selection.node! |
366 | 372 | export function applyIndirectStyles(selection, mark, dimensions, context) { |
|
0 commit comments