Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more GeoJSON awareness #2092

Merged
merged 3 commits into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions docs/marks/geo.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const walmarts = shallowRef({type: "FeatureCollection", features: []});
const world = shallowRef(null);
const statemesh = computed(() => us.value ? topojson.mesh(us.value, us.value.objects.states, (a, b) => a !== b) : {type: null});
const nation = computed(() => us.value ? topojson.feature(us.value, us.value.objects.nation) : {type: null});
const states = computed(() => us.value ? topojson.feature(us.value, us.value.objects.states).features : []);
const counties = computed(() => us.value ? topojson.feature(us.value, us.value.objects.counties).features : []);
const states = computed(() => us.value ? topojson.feature(us.value, us.value.objects.states) : {type: null});
const counties = computed(() => us.value ? topojson.feature(us.value, us.value.objects.counties) : {type: null});
const land = computed(() => world.value ? topojson.feature(world.value, world.value.objects.land) : {type: null});

onMounted(() => {
Expand Down Expand Up @@ -48,7 +48,7 @@ Plot.plot({
},
marks: [
Plot.geo(counties, {
fill: (d) => d.properties.unemployment,
fill: "unemployment",
title: (d) => `${d.properties.name} ${d.properties.unemployment}%`,
tip: true
})
Expand All @@ -57,7 +57,7 @@ Plot.plot({
```
:::

A geo mark’s data is typically [GeoJSON](https://geojson.org/). You can pass a single GeoJSON object, a feature or geometry collection, or an array or iterable of GeoJSON objects.
A geo mark’s data is typically [GeoJSON](https://geojson.org/). You can pass a single GeoJSON object, a feature or geometry collection, or an array or iterable of GeoJSON objects; Plot automatically normalizes these into an array of features or geometries. When a mark’s data is GeoJSON, Plot will look for the specified field name (such as _unemployment_ above, for **fill**) in the GeoJSON object’s `properties` if the object does not have this property directly. <VersionBadge pr="2092" />

The size of Point and MultiPoint geometries is controlled by the **r** option. For example, below we show earthquakes in the last seven days with a magnitude of 2.5 or higher as reported by the [USGS](https://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson.php). As with the [dot mark](./dot.md), the effective radius is controlled by the *r* scale, which is by default a *sqrt* scale such that the area of a point is proportional to its value. And likewise point geometries are by default sorted by descending radius to reduce occlusion, drawing the smallest circles on top. Set the **sort** option to null to use input order instead.

Expand All @@ -70,12 +70,12 @@ Plot.plot({
Plot.geo(land, {fill: "currentColor", fillOpacity: 0.2}),
Plot.sphere(),
Plot.geo(earthquakes, {
r: (d) => d.properties.mag,
r: "mag",
fill: "red",
fillOpacity: 0.2,
stroke: "red",
title: (d) => d.properties.title,
href: (d) => d.properties.url,
title: "title",
href: "url",
target: "_blank"
})
]
Expand Down Expand Up @@ -137,7 +137,7 @@ By default, the geo mark doesn’t have **x** and **y** channels; when you use t
Plot.plot({
projection: "albers-usa",
marks: [
Plot.geo(states, {strokeOpacity: 0.1, tip: true, title: (d) => d.properties.name}),
Plot.geo(states, {strokeOpacity: 0.1, tip: true, title: "name"}),
Plot.geo(nation),
Plot.dot(states, Plot.centroid({fill: "red", stroke: "var(--vp-c-bg-alt)"}))
]
Expand All @@ -157,7 +157,7 @@ Plot.plot({
marks: [
Plot.geo(statemesh, {strokeOpacity: 0.2}),
Plot.geo(nation),
Plot.geo(walmarts, {fy: (d) => d.properties.date, r: 1.5, fill: "blue", tip: true, title: (d) => d.properties.date}),
Plot.geo(walmarts, {fy: "date", r: 1.5, fill: "blue", tip: true, title: "date"}),
Plot.axisFy({frameAnchor: "top", dy: 30, tickFormat: (d) => `${d.getUTCFullYear()}’s`})
]
})
Expand All @@ -181,7 +181,7 @@ The **x** and **y** position channels may also be specified in conjunction with
## geo(*data*, *options*) {#geo}

```js
Plot.geo(counties, {fill: (d) => d.properties.rate})
Plot.geo(counties, {fill: "rate"})
```

Returns a new geo mark with the given *data* and *options*. If *data* is a GeoJSON feature collection, then the mark’s data is *data*.features; if *data* is a GeoJSON geometry collection, then the mark’s data is *data*.geometries; if *data* is some other GeoJSON object, then the mark’s data is the single-element array [*data*]. If the **geometry** option is not specified, *data* is assumed to be a GeoJSON object or an iterable of GeoJSON objects.
Expand Down
18 changes: 0 additions & 18 deletions src/marks/geo.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,6 @@ function scaleProjection({x: X, y: Y}) {
}

export function geo(data, options = {}) {
switch (data?.type) {
case "FeatureCollection":
data = data.features;
break;
case "GeometryCollection":
data = data.geometries;
break;
case "Feature":
case "LineString":
case "MultiLineString":
case "MultiPoint":
case "MultiPolygon":
case "Point":
case "Polygon":
case "Sphere":
data = [data];
break;
}
if (options.tip && options.x === undefined && options.y === undefined) options = centroid(options);
else if (options.geometry === undefined) options = {...options, geometry: identity};
return new Geo(data, options);
Expand Down
22 changes: 19 additions & 3 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function floater(f) {
}

export const singleton = [null]; // for data-less decoration marks, e.g. frame
export const field = (name) => (d) => d[name];
export const field = (name) => (d) => { const v = d[name]; return v === undefined && d.type === "Feature" ? d.properties?.[name] : v; }; // prettier-ignore
export const indexOf = {transform: range};
export const identity = {transform: (d) => d};
export const zero = () => 0;
Expand Down Expand Up @@ -131,8 +131,24 @@ export function keyword(input, name, allowed) {
}

// Promotes the specified data to an array as needed.
export function arrayify(data) {
return data == null || data instanceof Array || data instanceof TypedArray ? data : Array.from(data);
export function arrayify(values) {
if (values == null || values instanceof Array || values instanceof TypedArray) return values;
switch (values.type) {
case "FeatureCollection":
return values.features;
case "GeometryCollection":
return values.geometries;
case "Feature":
case "LineString":
case "MultiLineString":
case "MultiPoint":
case "MultiPolygon":
case "Point":
case "Polygon":
case "Sphere":
return [values];
}
return Array.from(values);
}

// An optimization of type.from(values, f): if the given values are already an
Expand Down
2 changes: 1 addition & 1 deletion test/plots/country-centroids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {feature} from "topojson-client";
export async function countryCentroids() {
const world = await d3.json<any>("data/countries-110m.json");
const land = feature(world, world.objects.land);
const countries = feature(world, world.objects.countries).features;
const countries = feature(world, world.objects.countries);
return Plot.plot({
projection: "mercator",
marks: [
Expand Down
2 changes: 1 addition & 1 deletion test/plots/us-county-choropleth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function usCountyChoropleth() {
label: "Unemployment (%)"
},
marks: [
Plot.geo(counties, {fill: (d) => unemployment.get(d.id), title: (d) => d.properties.name}),
Plot.geo(counties, {fill: (d) => unemployment.get(d.id), title: "name"}),
Plot.geo(statemesh, {stroke: "white"})
]
});
Expand Down