Skip to content

Commit 2a99d57

Browse files
Filmbostock
authored andcommitted
document raster and contour (observablehq#1386)
* a weird space * document raster * document contour * cleaner * better contour, raster * defaults for x1, x2, y1, y2 (I think) * edits * edits * edits --------- Co-authored-by: Mike Bostock <[email protected]>
1 parent 19f37cb commit 2a99d57

File tree

4 files changed

+286
-10
lines changed

4 files changed

+286
-10
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2936,7 +2936,7 @@ Constructs a Delaunay triangulation of the samples, and then for each pixel in t
29362936
29372937
#### Plot.interpolatorRandomWalk({*random*, *minDistance* = 0.5, *maxSteps* = 2})
29382938
2939-
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.
2939+
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.
29402940
29412941
## Markers
29422942

src/marks/contour.d.ts

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,89 @@ import type {Data, RenderableMark} from "../mark.js";
44
import type {Thresholds} from "../transforms/bin.js";
55
import type {RasterOptions, RasterSampler} from "./raster.js";
66

7-
export interface ContourOptions extends Omit<RasterOptions, "imageRendering"> {
7+
/** Options for the contour mark. */
8+
export interface ContourOptions extends Omit<RasterOptions, "interval" | "imageRendering"> {
9+
/**
10+
* Whether to apply linear interpolation after marching squares when computing
11+
* contour polygons; defaults to true. For smoother contours, see the **blur**
12+
* option.
13+
*/
814
smooth?: boolean;
15+
16+
/**
17+
* The value can be specified as a channel based on the sample *data*, or as a
18+
* function *f*(*x*, *y*) to be evaluated at each pixel if the *data* is not
19+
* provided.
20+
*/
921
value?: ChannelValue | RasterSampler;
22+
23+
/**
24+
* How to subdivide the domain into discrete level sets; defaults to *auto*;
25+
* one of:
26+
*
27+
* - a named threshold implementation such as *auto* (default) or *sturges*
28+
* - a function that returns an array, count, or range interval
29+
* - a range interval
30+
* - an array of *n* threshold values for *n* - 1 bins
31+
* - a count representing the desired number of bins (a hint; not guaranteed)
32+
*
33+
* For example, for about ten contours:
34+
*
35+
* ```js
36+
* Plot.contour({fill: (x, y) => Math.sin(x) * Math.cos(y), thresholds: 10, x1: 0, x2: 1, y1: 0, y2: 1})
37+
* ```
38+
*
39+
* See also the **interval** option.
40+
*/
1041
thresholds?: Thresholds;
42+
43+
/**
44+
* How to subdivide the domain into discrete level sets; a stricter
45+
* alternative to the **thresholds** option allowing the use of shorthand
46+
* numeric intervals; one of:
47+
*
48+
* - an object that implements *floor*, *offset*, and *range* methods
49+
* - a named time interval such as *day* (for date intervals)
50+
* - a number (for number intervals), defining intervals at integer multiples of *n*
51+
*
52+
* For example to create an isoline every 10 meters on a topographic map:
53+
*
54+
* ```js
55+
* Plot.contour(volcano.values, {interval: 10, width: volcano.width, height: volcano.height})
56+
* ```
57+
*/
1158
interval?: RangeInterval;
1259
}
1360

14-
export function contour(options?: ContourOptions): Contour;
15-
61+
/**
62+
* Returns a new contour mark, which creates contour polygons from spatial
63+
* samples. If *data* is provided, it represents discrete samples in abstract
64+
* coordinates **x** and **y**; the **value** channel specifies further abstract
65+
* values (_e.g._, height in a topographic map) to be spatially interpolated to
66+
* produce a raster grid of quantitative values (like in the raster mark), and
67+
* lastly contours via marching squares, which are rendered as vector polygons.
68+
* For example, to generate filled contours from a topographic map, where the
69+
* color corresponds to the contour threshold value:
70+
*
71+
* ```js
72+
* Plot.contour(volcano.values, {width: volcano.width, height: volcano.height, fill: Plot.identity})
73+
* ```
74+
*
75+
* The **fill** and **fillOpacity** channels may alternatively be specified as
76+
* functions *f*(*x*, *y*) to be evaluated at each pixel centroid of the
77+
* underlying raster grid (without interpolation). For example, to draw a
78+
* contour plot of a two-dimensional function:
79+
*
80+
* ```js
81+
* Plot.contour({x1: -1, x2: 1, y1: -1, y2: 1, fill: (x, y) => Math.atan2(y, x)})
82+
* ```
83+
*
84+
* With the exception of the **x**, **y**, and **value** channels, the contour
85+
* mark’s channels are not evaluated on the initial *data* but rather on the
86+
* generated contour multipolygons.
87+
*/
1688
export function contour(data?: Data, options?: ContourOptions): Contour;
89+
export function contour(options?: ContourOptions): Contour;
1790

91+
/** The contour mark. */
1892
export class Contour extends RenderableMark {}

src/marks/raster.d.ts

Lines changed: 206 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,254 @@
11
import type {ChannelValueSpec} from "../channel.js";
22
import type {Data, MarkOptions, RenderableMark} from "../mark.js";
33

4+
/**
5+
* The built-in spatial interpolation methods; one of:
6+
*
7+
* - *nearest* - assign each pixel to the closest sample’s value (Voronoi diagram)
8+
* - *barycentric* - apply barycentric interpolation over the Delaunay triangulation
9+
* - *random-walk* - apply a random walk from each pixel, stopping when near a sample
10+
*/
411
export type RasterInterpolateName = "nearest" | "barycentric" | "random-walk";
512

13+
/**
14+
* A spatial interpolation implementation function that receives samples’
15+
* positions and values and returns a flat array of *width*×*height* values.
16+
* *x*[*index*[0]] represents the *x*-position of the first sample,
17+
* *y*[*index*[0]] its *y*-position, and *value*[*index*[0]] its value (*e.g.*,
18+
* the observed height for a topographic map).
19+
*/
620
export type RasterInterpolateFunction = (
21+
/** An array of numeric indexes into the channels *x*, *y*, *value*. */
722
index: number[],
23+
/** The width of the raster grid in pixels; a positive integer. */
824
width: number,
25+
/** The height of the raster grid in pixels; a positive integer. */
926
height: number,
10-
X: number[],
11-
Y: number[],
12-
V: any[]
27+
/** An array of values representing the *x*-position of samples. */
28+
x: number[],
29+
/** An array of values representing the *y*-position of samples. */
30+
y: number[],
31+
/** An array of values representing the sample’s observed value. */
32+
values: any[]
1333
) => any[];
1434

35+
/**
36+
* A spatial interpolation method; either a named built-in interpolation method,
37+
* or a custom interpolation function.
38+
*/
1539
export type RasterInterpolate = RasterInterpolateName | RasterInterpolateFunction;
1640

41+
/**
42+
* A source of pseudo-random numbers in [0, 1). The default source is seeded to
43+
* ensure reproducibility.
44+
*/
1745
export type RandomSource = () => number;
1846

19-
export type RasterSampler = (x: number, y: number, facet: number[] & {fx: any; fy: any}) => any;
47+
/**
48+
* A sampler function, which returns a value for the given *x* and *y* values in
49+
* the current *facet*.
50+
*/
51+
export type RasterSampler = (
52+
/** The horizontal position. */
53+
x: number,
54+
/** The vertical position. */
55+
y: number,
56+
/** The current facet index, and corresponding *fx* and *fy* value. */
57+
facet: number[] & {fx: any; fy: any}
58+
) => any;
2059

60+
/** Options for the raster mark. */
2161
export interface RasterOptions extends Omit<MarkOptions, "fill" | "fillOpacity"> {
62+
/** The horizontal position channel, typically bound to the *x* scale. */
2263
x?: ChannelValueSpec;
64+
/** The vertical position channel, typically bound to the *y* scale. */
2365
y?: ChannelValueSpec;
66+
67+
/**
68+
* The starting horizontal position (typically the left edge) of the raster
69+
* domain; the lower bound of the *x* scale.
70+
*
71+
* If **width** is specified, defaults to 0; otherwise, if *data* is
72+
* specified, defaults to the frame’s left coordinate in *x*. If *data* is not
73+
* specified (as when **value** is a function of *x* and *y*), you must
74+
* specify **x1** explicitly.
75+
*/
2476
x1?: number;
77+
78+
/**
79+
* The ending horizontal position (typically the right edge) of the raster
80+
* domain; the upper bound of the *x* scale.
81+
*
82+
* If **width** is specified, defaults to **width**; otherwise, if *data* is
83+
* specified, defaults to the frame’s right coordinate in *x*. If *data* is
84+
* not specified (as when **value** is a function of *x* and *y*), you must
85+
* specify **x2** explicitly.
86+
*/
2587
x2?: number;
88+
89+
/**
90+
* The starting vertical position (typically the bottom edge) of the raster
91+
* domain; the lower bound of the *y* scale.
92+
*
93+
* If **height** is specified, defaults to 0; otherwise, if *data* is
94+
* specified, defaults to the frame’s top coordinate in *y*. If *data* is not
95+
* specified (as when **value** is a function of *x* and *y*), you must
96+
* specify **y1** explicitly.
97+
*/
2698
y1?: number;
99+
100+
/**
101+
* The ending vertical position (typically the bottom edge) of the raster
102+
* domain; the lower bound of the *y* scale.
103+
*
104+
* If **height** is specified, defaults to **height**; otherwise, if *data* is
105+
* specified, defaults to the frame’s bottom coordinate in *y*. If *data* is
106+
* not specified (as when **value** is a function of *x* and *y*), you must
107+
* specify **y2** explicitly.
108+
*/
27109
y2?: number;
110+
111+
/** The width (number of columns) of the raster grid, in actual pixels. */
28112
width?: number;
113+
114+
/** The height (number of rows) of the raster grid, in actual pixels. */
29115
height?: number;
116+
117+
/**
118+
* The effective screen size of a raster pixel, used to determine the height
119+
* and width of the raster from the frame’s dimensions; defaults to 1.
120+
*/
30121
pixelSize?: number;
122+
123+
/**
124+
* A non-negative pixel radius for smoothing; defaults to 0. Note that
125+
* blurring is applied on the values (before the color scale is applied) if
126+
* quantitative, and after (on the materialized pixels), if ordinal.
127+
*/
31128
blur?: number;
129+
130+
/**
131+
* The spatial interpolation method, when using *data* samples. One of:
132+
*
133+
* - *none* (or null, default) - assign each sample to the containing pixel
134+
* - a named interpolation method, such as *nearest*, *barycentric*, or *random-walk*
135+
* - a custom interpolation function
136+
*/
32137
interpolate?: RasterInterpolate | "none" | null;
138+
139+
/**
140+
* The [image-rendering attribute][1]; defaults to *auto* (bilinear). The
141+
* option may be set to *pixelated* to disable bilinear interpolation for a
142+
* sharper image; however, note that this is not supported in WebKit.
143+
*
144+
* [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/image-rendering
145+
*/
33146
imageRendering?: string;
147+
148+
/**
149+
* The fill, typically bound to the *color* scale. Can be specified as a
150+
* constant, a channel based on the sample *data*, or as a function *f*(*x*,
151+
* *y*) to be evaluated at each pixel if the *data* is not provided.
152+
*/
34153
fill?: ChannelValueSpec | RasterSampler;
154+
155+
/**
156+
* The opacity, typically bound to the *opacity* scale. Can be specified as a
157+
* constant, a channel based on the sample *data*, or as a function *f*(*x*,
158+
* *y*) to be evaluated at each pixel if the *data* is not provided.
159+
*/
35160
fillOpacity?: ChannelValueSpec | RasterSampler;
36161
}
37162

38-
export function raster(options?: RasterOptions): Raster;
39-
163+
/**
164+
* Returns a raster mark which renders a raster image from spatial samples. If
165+
* *data* is provided, it represents discrete samples in abstract coordinates
166+
* **x** and **y**; the **fill** and **fillOpacity** channels specify further
167+
* abstract values (_e.g._, height in a topographic map) to be spatially
168+
* interpolated to produce an image.
169+
*
170+
* ```js
171+
* Plot.raster(volcano.values, {width: volcano.width, height: volcano.height})
172+
* ```
173+
*
174+
* The **fill** and **fillOpacity** channels may alternatively be specified as
175+
* functions *f*(*x*, *y*) to be evaluated at each pixel centroid of the raster
176+
* grid (without interpolation).
177+
*
178+
* ```js
179+
* Plot.raster({x1: -1, x2: 1, y1: -1, y2: 1, fill: (x, y) => Math.atan2(y, x)})
180+
* ```
181+
*
182+
* If **width** is specified, **x1** defaults to 0 and **x2** defaults to
183+
* **width**; likewise, if **height** is specified, **y1** defaults to 0 and
184+
* **y2** defaults to **height**. Otherwise, if *data* is specified, **x1**,
185+
* **y1**, **x2**, and **y2** respectively default to the frame’s left, top,
186+
* right, and bottom coordinates. Lastly, if *data* is not specified (as when
187+
* **value** is a function of *x* and *y*), you must specify all of **x1**,
188+
* **x2**, **y1**, and **y2** to define the raster domain.
189+
*/
40190
export function raster(data?: Data, options?: RasterOptions): Raster;
191+
export function raster(options?: RasterOptions): Raster;
41192

193+
/**
194+
* Applies a simple forward mapping of samples, binning them into pixels in the
195+
* raster grid without any blending or interpolation. If multiple samples map to
196+
* the same pixel, the last one wins; this can introduce bias if the points are
197+
* not in random order, so use Plot.shuffle to randomize the input if needed.
198+
*/
42199
export const interpolateNone: RasterInterpolateFunction;
43200

201+
/**
202+
* Constructs a Delaunay triangulation of the samples, and then for each pixel
203+
* in the raster grid, determines the triangle that covers the pixel’s centroid
204+
* and interpolates the values associated with the triangle’s vertices using
205+
* [barycentric coordinates][1]. If the interpolated values are ordinal or
206+
* categorical (_i.e._, anything other than numbers or dates), then one of the
207+
* three values will be picked randomly weighted by the barycentric coordinates;
208+
* the given *random* number generator will be used, which defaults to a [linear
209+
* congruential generator][2] with a fixed seed (for deterministic results).
210+
*
211+
* [1]: https://en.wikipedia.org/wiki/Barycentric_coordinate_system
212+
* [2]: https://github.com/d3/d3-random/blob/main/README.md#randomLcg
213+
*/
44214
export function interpolatorBarycentric(options?: {random?: RandomSource}): RasterInterpolateFunction;
45215

216+
/**
217+
* Assigns each pixel in the raster grid the value of the closest sample;
218+
* effectively a Voronoi diagram.
219+
*/
46220
export const interpolateNearest: RasterInterpolateFunction;
47221

222+
/**
223+
* For each pixel in the raster grid, initiates a random walk, stopping when
224+
* either the walk is within a given distance (**minDistance**) of a sample or
225+
* the maximum allowable number of steps (**maxSteps**) have been taken, and
226+
* then assigning the current pixel the closest sample’s value. The random walk
227+
* uses the “walk on spheres” algorithm in two dimensions described by [Sawhney
228+
* and Crane][1], SIGGRAPH 2020.
229+
*
230+
* [1]: https://www.cs.cmu.edu/~kmcrane/Projects/MonteCarloGeometryProcessing/index.html
231+
*/
48232
export function interpolatorRandomWalk(options?: {
233+
/**
234+
* An optional source of pseudo-random numbers in [0, 1). Called at each step
235+
* of the random walk algorithm with arguments *x*, *y*, and *step*. If not
236+
* specified, defaults to a seeded random number generator.
237+
*/
49238
random?: RandomSource;
239+
240+
/**
241+
* The random walk ends by “snapping” to the closest sample if closer than
242+
* this distance (in pixels).
243+
*/
50244
minDistance?: number;
245+
246+
/**
247+
* After this number of steps, which defaults to 3, lift the **minDistance**
248+
* requirement and snap to the closest sample.
249+
*/
51250
maxSteps?: number;
52251
}): RasterInterpolateFunction;
53252

253+
/** The raster mark. */
54254
export class Raster extends RenderableMark {}

src/transforms/bin.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ export interface BinOptions {
8181
* ```js
8282
* Plot.rectY(numbers, Plot.binX({y: "count"}, {thresholds: 10}))
8383
* ```
84+
*
85+
* See also the **interval** option.
8486
*/
8587
thresholds?: Thresholds;
8688

0 commit comments

Comments
 (0)