Skip to content

Commit 0e5c684

Browse files
authored
more GeoJSON awareness (#2092)
* more geojson-aware * use GeoJSON collections
1 parent 437e15f commit 0e5c684

File tree

5 files changed

+31
-33
lines changed

5 files changed

+31
-33
lines changed

docs/marks/geo.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ const walmarts = shallowRef({type: "FeatureCollection", features: []});
1111
const world = shallowRef(null);
1212
const statemesh = computed(() => us.value ? topojson.mesh(us.value, us.value.objects.states, (a, b) => a !== b) : {type: null});
1313
const nation = computed(() => us.value ? topojson.feature(us.value, us.value.objects.nation) : {type: null});
14-
const states = computed(() => us.value ? topojson.feature(us.value, us.value.objects.states).features : []);
15-
const counties = computed(() => us.value ? topojson.feature(us.value, us.value.objects.counties).features : []);
14+
const states = computed(() => us.value ? topojson.feature(us.value, us.value.objects.states) : {type: null});
15+
const counties = computed(() => us.value ? topojson.feature(us.value, us.value.objects.counties) : {type: null});
1616
const land = computed(() => world.value ? topojson.feature(world.value, world.value.objects.land) : {type: null});
1717

1818
onMounted(() => {
@@ -48,7 +48,7 @@ Plot.plot({
4848
},
4949
marks: [
5050
Plot.geo(counties, {
51-
fill: (d) => d.properties.unemployment,
51+
fill: "unemployment",
5252
title: (d) => `${d.properties.name} ${d.properties.unemployment}%`,
5353
tip: true
5454
})
@@ -57,7 +57,7 @@ Plot.plot({
5757
```
5858
:::
5959

60-
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.
60+
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" />
6161

6262
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.
6363

@@ -70,12 +70,12 @@ Plot.plot({
7070
Plot.geo(land, {fill: "currentColor", fillOpacity: 0.2}),
7171
Plot.sphere(),
7272
Plot.geo(earthquakes, {
73-
r: (d) => d.properties.mag,
73+
r: "mag",
7474
fill: "red",
7575
fillOpacity: 0.2,
7676
stroke: "red",
77-
title: (d) => d.properties.title,
78-
href: (d) => d.properties.url,
77+
title: "title",
78+
href: "url",
7979
target: "_blank"
8080
})
8181
]
@@ -137,7 +137,7 @@ By default, the geo mark doesn’t have **x** and **y** channels; when you use t
137137
Plot.plot({
138138
projection: "albers-usa",
139139
marks: [
140-
Plot.geo(states, {strokeOpacity: 0.1, tip: true, title: (d) => d.properties.name}),
140+
Plot.geo(states, {strokeOpacity: 0.1, tip: true, title: "name"}),
141141
Plot.geo(nation),
142142
Plot.dot(states, Plot.centroid({fill: "red", stroke: "var(--vp-c-bg-alt)"}))
143143
]
@@ -157,7 +157,7 @@ Plot.plot({
157157
marks: [
158158
Plot.geo(statemesh, {strokeOpacity: 0.2}),
159159
Plot.geo(nation),
160-
Plot.geo(walmarts, {fy: (d) => d.properties.date, r: 1.5, fill: "blue", tip: true, title: (d) => d.properties.date}),
160+
Plot.geo(walmarts, {fy: "date", r: 1.5, fill: "blue", tip: true, title: "date"}),
161161
Plot.axisFy({frameAnchor: "top", dy: 30, tickFormat: (d) => `${d.getUTCFullYear()}’s`})
162162
]
163163
})
@@ -181,7 +181,7 @@ The **x** and **y** position channels may also be specified in conjunction with
181181
## geo(*data*, *options*) {#geo}
182182

183183
```js
184-
Plot.geo(counties, {fill: (d) => d.properties.rate})
184+
Plot.geo(counties, {fill: "rate"})
185185
```
186186

187187
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.

src/marks/geo.js

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -70,24 +70,6 @@ function scaleProjection({x: X, y: Y}) {
7070
}
7171

7272
export function geo(data, options = {}) {
73-
switch (data?.type) {
74-
case "FeatureCollection":
75-
data = data.features;
76-
break;
77-
case "GeometryCollection":
78-
data = data.geometries;
79-
break;
80-
case "Feature":
81-
case "LineString":
82-
case "MultiLineString":
83-
case "MultiPoint":
84-
case "MultiPolygon":
85-
case "Point":
86-
case "Polygon":
87-
case "Sphere":
88-
data = [data];
89-
break;
90-
}
9173
if (options.tip && options.x === undefined && options.y === undefined) options = centroid(options);
9274
else if (options.geometry === undefined) options = {...options, geometry: identity};
9375
return new Geo(data, options);

src/options.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function floater(f) {
4747
}
4848

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

133133
// Promotes the specified data to an array as needed.
134-
export function arrayify(data) {
135-
return data == null || data instanceof Array || data instanceof TypedArray ? data : Array.from(data);
134+
export function arrayify(values) {
135+
if (values == null || values instanceof Array || values instanceof TypedArray) return values;
136+
switch (values.type) {
137+
case "FeatureCollection":
138+
return values.features;
139+
case "GeometryCollection":
140+
return values.geometries;
141+
case "Feature":
142+
case "LineString":
143+
case "MultiLineString":
144+
case "MultiPoint":
145+
case "MultiPolygon":
146+
case "Point":
147+
case "Polygon":
148+
case "Sphere":
149+
return [values];
150+
}
151+
return Array.from(values);
136152
}
137153

138154
// An optimization of type.from(values, f): if the given values are already an

test/plots/country-centroids.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {feature} from "topojson-client";
55
export async function countryCentroids() {
66
const world = await d3.json<any>("data/countries-110m.json");
77
const land = feature(world, world.objects.land);
8-
const countries = feature(world, world.objects.countries).features;
8+
const countries = feature(world, world.objects.countries);
99
return Plot.plot({
1010
projection: "mercator",
1111
marks: [

test/plots/us-county-choropleth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export async function usCountyChoropleth() {
2323
label: "Unemployment (%)"
2424
},
2525
marks: [
26-
Plot.geo(counties, {fill: (d) => unemployment.get(d.id), title: (d) => d.properties.name}),
26+
Plot.geo(counties, {fill: (d) => unemployment.get(d.id), title: "name"}),
2727
Plot.geo(statemesh, {stroke: "white"})
2828
]
2929
});

0 commit comments

Comments
 (0)