diff --git a/src/marks/contour.js b/src/marks/contour.js index 6d692d7d91..252c143eca 100644 --- a/src/marks/contour.js +++ b/src/marks/contour.js @@ -1,7 +1,7 @@ -import {blur2, contours, geoPath, max, min, nice, range, ticks, thresholdSturges} from "d3"; +import {blur2, contours, geoPath, max, min, nice, range, ticks, thresholdSturges, scaleUtc} from "d3"; import {createChannels} from "../channel.js"; import {create} from "../context.js"; -import {labelof, identity, arrayify, map} from "../options.js"; +import {labelof, identity, arrayify, map, isTemporal} from "../options.js"; import {applyPosition} from "../projection.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, styles} from "../style.js"; import {initializer} from "../transforms/basic.js"; @@ -115,7 +115,8 @@ function contourGeometry({thresholds, interval, ...options}) { const {pixelSize: k, width: w = Math.round(Math.abs(dx) / k), height: h = Math.round(Math.abs(dy) / k)} = this; const kx = w / dx; const ky = h / dy; - const V = channels.value.value; + const temporal = isTemporal(channels.value.value); + const V = temporal && this.blur > 0 ? Float64Array.from(channels.value.value) : channels.value.value; const VV = []; // V per facet // Interpolate the raster grid, as needed. @@ -149,7 +150,7 @@ function contourGeometry({thresholds, interval, ...options}) { if (this.blur > 0) for (const V of VV) blur2({data: V, width: w, height: h}, this.blur); // Compute the contour thresholds. - const T = maybeTicks(thresholds, V, ...finiteExtent(VV)); + const T = maybeTicks(thresholds, V, ...finiteExtent(VV), temporal); if (T === null) throw new Error(`unsupported thresholds: ${thresholds}`); // Compute the (maybe faceted) contours. @@ -187,10 +188,11 @@ function contourGeometry({thresholds, interval, ...options}) { // thresholds across facets. When an interval is used, note that the lowest // threshold should be below (or equal) to the lowest value, or else some data // will be missing. -function maybeTicks(thresholds, V, min, max) { +function maybeTicks(thresholds, V, min, max, temporal) { if (typeof thresholds?.range === "function") return thresholds.range(thresholds.floor(min), max); if (typeof thresholds === "function") thresholds = thresholds(V, min, max); if (typeof thresholds !== "number") return arrayify(thresholds); + if (temporal) return scaleUtc().domain([min, max]).nice(thresholds).ticks(thresholds); const tz = ticks(...nice(min, max, thresholds), thresholds); while (tz[tz.length - 1] >= max) tz.pop(); while (tz[1] < min) tz.shift(); diff --git a/test/output/walmartsDateContours.html b/test/output/walmartsDateContours.html new file mode 100644 index 0000000000..9116fad929 --- /dev/null +++ b/test/output/walmartsDateContours.html @@ -0,0 +1,72 @@ +
+ + + + + + 1980 + + + + 1990 + + + + 2000 + + + Mean opening date + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/test/plots/walmarts.ts b/test/plots/walmarts.ts index b5b95d97a1..3bfe37fed0 100644 --- a/test/plots/walmarts.ts +++ b/test/plots/walmarts.ts @@ -1,6 +1,6 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -import {mesh} from "topojson-client"; +import {feature, mesh} from "topojson-client"; export async function walmarts() { const [walmarts, statemesh] = await Promise.all([ @@ -36,3 +36,34 @@ export async function walmarts() { ] }); } + +export async function walmartsDateContours() { + const [walmarts, [statemesh, nation]] = await Promise.all([ + d3.tsv("data/walmarts.tsv", d3.autoType), + d3.json("data/us-counties-10m.json").then((us) => [ + mesh(us, { + type: "GeometryCollection", + geometries: us.objects.states.geometries.filter((d) => d.id !== "02" && d.id !== "15") + }), + feature(us, us.objects.nation) + ]) + ]); + return Plot.plot({ + width: 960, + height: 600, + projection: "albers", + color: {legend: true, label: "Mean opening date"}, + clip: nation, + marks: [ + Plot.contour(walmarts, { + x: "longitude", + y: "latitude", + fill: "date", + interpolate: "random-walk", + blur: 5 + }), + Plot.geo(statemesh, {strokeOpacity: 0.25}), + Plot.geo(nation, {strokeWidth: 1.5}) + ] + }); +}