Skip to content

Commit

Permalink
clip: geojson
Browse files Browse the repository at this point in the history
  • Loading branch information
Fil committed Nov 21, 2024
1 parent 6bea18e commit 9b488b4
Show file tree
Hide file tree
Showing 37 changed files with 386 additions and 102 deletions.
4 changes: 3 additions & 1 deletion src/mark.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {GeoPermissibleObjects} from "d3";
import type {Channel, ChannelDomainSort, ChannelValue, ChannelValues, ChannelValueSpec} from "./channel.js";
import type {Context} from "./context.js";
import type {Dimensions} from "./dimensions.js";
Expand Down Expand Up @@ -295,11 +296,12 @@ export interface MarkOptions {
*
* - *frame* or true - clip to the plot’s frame (inner area)
* - *sphere* - clip to the projected sphere (*e.g.*, front hemisphere)
* - geojson - a GeoJSON object with a (multi-) polygon geometry
* - null or false - do not clip
*
* The *sphere* clip option requires a geographic projection.
*/
clip?: "frame" | "sphere" | boolean | null;
clip?: "frame" | "sphere" | GeoPermissibleObjects | boolean | null;

/**
* The horizontal offset in pixels; a constant option. On low-density screens,
Expand Down
2 changes: 1 addition & 1 deletion src/marks/area.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class Area extends Mark {
render(index, scales, channels, dimensions, context) {
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1} = channels;
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, scales, 0, 0)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/arrow.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class Arrow extends Mark {
const wingScale = headLength / 1.5;

return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class AbstractBar extends Mark {
const w = this._width(scales, channels, dimensions);
const h = this._height(scales, channels, dimensions);
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(this._transform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/contour.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class Contour extends AbstractRaster {
const {geometry: G} = channels;
const path = geoPath();
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, scales)
.call((g) => {
g.selectAll()
Expand Down
6 changes: 3 additions & 3 deletions src/marks/delaunay.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class DelaunayLink extends Mark {
}

return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call(
Z
Expand Down Expand Up @@ -175,7 +175,7 @@ class AbstractDelaunayMark extends Mark {
}

return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call(
Z
Expand Down Expand Up @@ -249,7 +249,7 @@ class Voronoi extends Mark {
const {x, y} = scales;
const {x: X, y: Y, cells: C} = channels;
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) => {
g.selectAll()
Expand Down
2 changes: 1 addition & 1 deletion src/marks/density.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class Density extends Mark {
const {contours} = channels;
const path = geoPath();
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, {})
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/dot.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class Dot extends Mark {
const size = R ? undefined : r * r * Math.PI;
if (negative(r)) index = [];
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class Frame extends Mark {
const y2 = height - marginBottom - insetBottom;
return create(anchor ? "svg:line" : rx1y1 || rx1y2 || rx2y1 || rx2y2 ? "svg:path" : "svg:rect", context)
.datum(0)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyDirectStyles, this)
.call(applyChannelStyles, this, channels)
.call(applyTransform, this, {})
Expand Down
21 changes: 4 additions & 17 deletions src/marks/geo.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {geoGraticule10, geoPath, geoTransform} from "d3";
import {geoGraticule10, geoPath} from "d3";
import {create} from "../context.js";
import {negative, positive} from "../defined.js";
import {Mark} from "../mark.js";
import {identity, maybeNumberChannel} from "../options.js";
import {xyProjection} from "../projection.js";
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform} from "../style.js";
import {centroid} from "../transforms/centroid.js";
import {withDefaultSort} from "./dot.js";
Expand Down Expand Up @@ -35,12 +36,12 @@ export class Geo extends Mark {
}
render(index, scales, channels, dimensions, context) {
const {geometry: G, r: R} = channels;
const path = geoPath(context.projection ?? scaleProjection(scales));
const path = geoPath(context.projection ?? xyProjection(scales));
const {r} = this;
if (negative(r)) index = [];
else if (r !== undefined) path.pointRadius(r);
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, scales)
.call((g) => {
g.selectAll()
Expand All @@ -55,20 +56,6 @@ export class Geo extends Mark {
}
}

// If no projection is specified, default to a projection that passes points
// through the x and y scales, if any.
function scaleProjection({x: X, y: Y}) {
if (X || Y) {
X ??= (x) => x;
Y ??= (y) => y;
return geoTransform({
point(x, y) {
this.stream.point(X(x), Y(y));
}
});
}
}

export function geo(data, options = {}) {
if (options.tip && options.x === undefined && options.y === undefined) options = centroid(options);
else if (options.geometry === undefined) options = {...options, geometry: identity};
Expand Down
2 changes: 1 addition & 1 deletion src/marks/hexgrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class Hexgrid extends Mark {
}
return create("svg:g", context)
.datum(0)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, {}, offset + ox, offset + oy)
.call((g) => g.append("path").call(applyDirectStyles, this).call(applyChannelStyles, this, channels).attr("d", d))
.node();
Expand Down
2 changes: 1 addition & 1 deletion src/marks/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class Image extends Mark {
const {r, width, height, rotate} = this;
const [cx, cy] = applyFrameAnchor(this, dimensions);
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/line.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class Line extends Mark {
const {x: X, y: Y} = channels;
const {curve} = this;
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/linearRegression.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class LinearRegression extends Mark {
const {x: X, y: Y, z: Z} = channels;
const {ci} = this;
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class Link extends Mark {
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1} = channels;
const {curve} = this;
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/raster.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export class Raster extends AbstractRaster {
context2d.putImageData(image, 0, 0);

return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/rect.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class Rect extends Mark {
const bx = x?.bandwidth ? x.bandwidth() : 0;
const by = y?.bandwidth ? y.bandwidth() : 0;
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, {}, 0, 0)
.call((g) =>
g
Expand Down
4 changes: 2 additions & 2 deletions src/marks/rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class RuleX extends Mark {
const {width, height, marginTop, marginRight, marginLeft, marginBottom} = dimensions;
const {insetTop, insetBottom} = this;
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, {x: X && x}, offset, 0)
.call((g) =>
g
Expand Down Expand Up @@ -85,7 +85,7 @@ export class RuleY extends Mark {
const {width, height, marginTop, marginRight, marginLeft, marginBottom} = dimensions;
const {insetLeft, insetRight} = this;
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, {y: Y && y}, 0, offset)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class Text extends Mark {
const {rotate} = this;
const [cx, cy] = applyFrameAnchor(this, dimensions);
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectTextStyles, this, T, dimensions)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
Expand Down
2 changes: 1 addition & 1 deletion src/marks/tick.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class AbstractTick extends Mark {
}
render(index, scales, channels, dimensions, context) {
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(this._transform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/tip.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class Tip extends Mark {
// We don’t call applyChannelStyles because we only use the channels to
// derive the content of the tip, not its aesthetics.
const g = create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectTextStyles, this)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
Expand Down
2 changes: 1 addition & 1 deletion src/marks/vector.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class Vector extends Mark {
const {length, rotate, anchor, shape, r} = this;
const [cx, cy] = applyFrameAnchor(this, dimensions);
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/waffle.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function waffleRender(y) {
if (ry != null) basePatternRect.setAttribute("ry", ry);

return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(this._transform, this, scales)
.call((g) =>
g
Expand Down
8 changes: 5 additions & 3 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -602,12 +602,14 @@ export function maybeNamed(things) {
return isIterable(things) ? named(things) : things;
}

// TODO Accept other types of clips (paths, urls, x, y, other marks…)?
// https://github.com/observablehq/plot/issues/181
export function maybeClip(clip) {
if (clip === true) clip = "frame";
else if (clip === false) clip = null;
else if (clip != null) clip = keyword(clip, "clip", ["frame", "sphere"]);
else if (
clip != null &&
!["FeatureCollection", "Feature", "Polygon", "GeometryCollection", "MultiPolygon"].includes(clip.type)
)
clip = keyword(clip, "clip", ["frame", "sphere"]);
return clip;
}

Expand Down
14 changes: 14 additions & 0 deletions src/projection.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,17 @@ export function getGeometryChannels(channel) {
for (const object of channel.value) geoStream(object, sink);
return [x, y];
}

// If no projection is specified, default to a projection that passes points
// through the x and y scales, if any.
export function xyProjection({x: X, y: Y}) {
if (X || Y) {
X ??= (x) => x;
Y ??= (y) => y;
return geoTransform({
point(x, y) {
this.stream.point(X(x), Y(y));
}
});
}
}
Loading

0 comments on commit 9b488b4

Please sign in to comment.