diff --git a/CHANGELOG.md b/CHANGELOG.md index 90c6c248ab..7784d699f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -124,7 +124,7 @@ Plot.plot({ }); ``` -The new [geoCentroid transform](./README.md#plotgeocentroidoptions) and [centroid initializer](./README.md#plotcentroidoptions) compute the spherical and projected planar centroids of geometry, respectively. +The new [geoCentroid transform](./README.md#plotgeocentroidoptions) and [centroid initializer](./README.md#plotcentroidoptions) compute the spherical and projected planar centroids of geometry, respectively. The new [identity](./README.md#plotidentity) channel helper returns a source array as-is, avoiding an extra copy. The **interval** option now supports named time intervals such as “sunday” and “hour”, equivalent to the corresponding d3-time interval (_e.g._, d3.utcSunday and d3.utcHour). The [bin transform](./README.md#bin) is now many times faster, especially when there are many bins and when binning temporal data. diff --git a/README.md b/README.md index fc197e0674..ca5762ac55 100644 --- a/README.md +++ b/README.md @@ -2190,7 +2190,7 @@ Bins on *y*. Also groups on *x* and first channel of *z*, *fill*, or *stroke*, i -The centroid initializer derives **x** and **y** channels representing the planar (projected) centroids for the the given GeoJSON geometry. If the **geometry** option is not specified, the mark’s data is assumed to be GeoJSON objects. +The centroid initializer derives **x** and **y** channels representing the planar (projected) centroids for the given GeoJSON geometry. If the **geometry** option is not specified, the mark’s data is assumed to be GeoJSON objects. ```js Plot.dot(regions.features, Plot.centroid()).plot({projection: "reflect-y"}) @@ -2202,7 +2202,7 @@ Plot.dot(regions.features, Plot.centroid()).plot({projection: "reflect-y"}) -The geoCentroid transform derives **x** and **y** channels representing the spherical centroids for the the given GeoJSON geometry. If the **geometry** option is not specified, the mark’s data is assumed to be GeoJSON objects. +The geoCentroid transform derives **x** and **y** channels representing the spherical centroids for the given GeoJSON geometry. If the **geometry** option is not specified, the mark’s data is assumed to be GeoJSON objects. ```js Plot.dot(counties.features, Plot.geoCentroid()).plot({projection: "albers-usa"}) @@ -3038,20 +3038,36 @@ So, *x*[*index*[0]] represents the *x*-position of the first sample, *y*[*index* #### Plot.interpolateNone(*index*, *width*, *height*, *x*, *y*, *value*) + + Applies a simple forward mapping of samples, binning them into pixels in the raster grid without any blending or interpolation. If multiple samples map to the same pixel, the last one wins; this can introduce bias if the points are not in random order, so use [Plot.shuffle](#plotshuffleoptions) to randomize the input if needed. + + #### Plot.interpolateNearest(*index*, *width*, *height*, *x*, *y*, *value*) + + Assigns each pixel in the raster grid the value of the closest sample; effectively a Voronoi diagram. + + #### Plot.interpolatorBarycentric({*random*}) + + Constructs a Delaunay triangulation of the samples, and then for each pixel in the raster grid, determines the triangle that covers the pixel’s centroid and interpolates the values associated with the triangle’s vertices using [barycentric coordinates](https://en.wikipedia.org/wiki/Barycentric_coordinate_system). If the interpolated values are ordinal or categorical (_i.e._, anything other than numbers or dates), then one of the three values will be picked randomly weighted by the barycentric coordinates; the given *random* number generator will be used, which defaults to a [linear congruential generator](https://github.com/d3/d3-random/blob/main/README.md#randomLcg) with a fixed seed (for deterministic results). + + #### Plot.interpolatorRandomWalk({*random*, *minDistance* = 0.5, *maxSteps* = 2}) + + For each pixel in the raster grid, initiates a random walk, stopping when either the walk is within a given distance (*minDistance*) of a sample or the maximum allowable number of steps (*maxSteps*) have been taken, and then assigning the current pixel the closest sample’s value. The random walk uses the “walk on spheres” algorithm in two dimensions described by [Sawhney and Crane](https://www.cs.cmu.edu/~kmcrane/Projects/MonteCarloGeometryProcessing/index.html), SIGGRAPH 2020. + + ## Markers A [marker](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker) defines a graphic drawn on vertices of a [line](#line) or a [link](#link) mark. The supported marker options are: diff --git a/src/marks/contour.js b/src/marks/contour.js index 4b0e93c653..46df0d62d1 100644 --- a/src/marks/contour.js +++ b/src/marks/contour.js @@ -195,6 +195,7 @@ function maybeTicks(thresholds, V, min, max) { return tz; } +/** @jsdoc contour */ export function contour() { return new Contour(...maybeTuples("value", ...arguments)); } diff --git a/src/marks/raster.js b/src/marks/raster.js index 67034de57f..2b0d0e76c6 100644 --- a/src/marks/raster.js +++ b/src/marks/raster.js @@ -186,6 +186,7 @@ export function maybeTuples(k, data, options) { return [data, {...rest, x, y, [k]: z}]; } +/** @jsdoc raster */ export function raster() { const [data, options] = maybeTuples("fill", ...arguments); return new Raster( @@ -271,6 +272,7 @@ function maybeInterpolate(interpolate) { // any blending or interpolation. Note: if multiple samples map to the same // pixel, the last one wins; this can introduce bias if the points are not in // random order, so use Plot.shuffle to randomize the input if needed. +/** @jsdoc interpolateNone */ export function interpolateNone(index, width, height, X, Y, V) { const W = new Array(width * height); for (const i of index) { @@ -280,6 +282,7 @@ export function interpolateNone(index, width, height, X, Y, V) { return W; } +/** @jsdoc interpolatorBarycentric */ export function interpolatorBarycentric({random = randomLcg(42)} = {}) { return (index, width, height, X, Y, V) => { // Flatten the input coordinates to prepare to insert extrapolated points @@ -345,6 +348,7 @@ export function interpolatorBarycentric({random = randomLcg(42)} = {}) { }; } +/** @jsdoc interpolateNearest */ export function interpolateNearest(index, width, height, X, Y, V) { const W = new V.constructor(width * height); const delaunay = Delaunay.from( @@ -366,6 +370,7 @@ export function interpolateNearest(index, width, height, X, Y, V) { } // https://observablehq.com/@observablehq/walk-on-spheres-precision +/** @jsdoc interpolatorRandomWalk */ export function interpolatorRandomWalk({random = randomLcg(42), minDistance = 0.5, maxSteps = 2} = {}) { return (index, width, height, X, Y, V) => { const W = new V.constructor(width * height); diff --git a/src/options.js b/src/options.js index dad2df2f62..e936e12e88 100644 --- a/src/options.js +++ b/src/options.js @@ -22,6 +22,7 @@ export function valueof(data, value, type) { export const field = (name) => (d) => d[name]; export const indexOf = (d, i) => i; +/** @jsdoc identity */ export const identity = {transform: (d) => d}; export const zero = () => 0; export const one = () => 1;