Skip to content

document raster and contour #1386

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

Merged
merged 9 commits into from
Mar 31, 2023
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2936,7 +2936,7 @@ Constructs a Delaunay triangulation of the samples, and then for each pixel in t

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

Expand Down
80 changes: 77 additions & 3 deletions src/marks/contour.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,89 @@ import type {Data, RenderableMark} from "../mark.js";
import type {Thresholds} from "../transforms/bin.js";
import type {RasterOptions, RasterSampler} from "./raster.js";

export interface ContourOptions extends Omit<RasterOptions, "imageRendering"> {
/** Options for the contour mark. */
export interface ContourOptions extends Omit<RasterOptions, "interval" | "imageRendering"> {
/**
* Whether to apply linear interpolation after marching squares when computing
* contour polygons; defaults to true. For smoother contours, see the **blur**
* option.
*/
smooth?: boolean;

/**
* The value can be specified as a channel based on the sample *data*, or as a
* function *f*(*x*, *y*) to be evaluated at each pixel if the *data* is not
* provided.
*/
value?: ChannelValue | RasterSampler;

/**
* How to subdivide the domain into discrete level sets; defaults to *auto*;
* one of:
*
* - a named threshold implementation such as *auto* (default) or *sturges*
* - a function that returns an array, count, or range interval
* - a range interval
* - an array of *n* threshold values for *n* - 1 bins
* - a count representing the desired number of bins (a hint; not guaranteed)
*
* For example, for about ten contours:
*
* ```js
* Plot.contour({fill: (x, y) => Math.sin(x) * Math.cos(y), thresholds: 10, x1: 0, x2: 1, y1: 0, y2: 1})
* ```
*
* See also the **interval** option.
*/
thresholds?: Thresholds;

/**
* How to subdivide the domain into discrete level sets; a stricter
* alternative to the **thresholds** option allowing the use of shorthand
* numeric intervals; one of:
*
* - an object that implements *floor*, *offset*, and *range* methods
* - a named time interval such as *day* (for date intervals)
* - a number (for number intervals), defining intervals at integer multiples of *n*
*
* For example to create an isoline every 10 meters on a topographic map:
*
* ```js
* Plot.contour(volcano.values, {interval: 10, width: volcano.width, height: volcano.height})
* ```
*/
interval?: RangeInterval;
}

export function contour(options?: ContourOptions): Contour;

/**
* Returns a new contour mark, which creates contour polygons from spatial
* samples. If *data* is provided, it represents discrete samples in abstract
* coordinates **x** and **y**; the **value** channel specifies further abstract
* values (_e.g._, height in a topographic map) to be spatially interpolated to
* produce a raster grid of quantitative values (like in the raster mark), and
* lastly contours via marching squares, which are rendered as vector polygons.
* For example, to generate filled contours from a topographic map, where the
* color corresponds to the contour threshold value:
*
* ```js
* Plot.contour(volcano.values, {width: volcano.width, height: volcano.height, fill: Plot.identity})
* ```
*
* The **fill** and **fillOpacity** channels may alternatively be specified as
* functions *f*(*x*, *y*) to be evaluated at each pixel centroid of the
* underlying raster grid (without interpolation). For example, to draw a
* contour plot of a two-dimensional function:
*
* ```js
* Plot.contour({x1: -1, x2: 1, y1: -1, y2: 1, fill: (x, y) => Math.atan2(y, x)})
* ```
*
* With the exception of the **x**, **y**, and **value** channels, the contour
* mark’s channels are not evaluated on the initial *data* but rather on the
* generated contour multipolygons.
*/
export function contour(data?: Data, options?: ContourOptions): Contour;
export function contour(options?: ContourOptions): Contour;

/** The contour mark. */
export class Contour extends RenderableMark {}
212 changes: 206 additions & 6 deletions src/marks/raster.d.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,254 @@
import type {ChannelValueSpec} from "../channel.js";
import type {Data, MarkOptions, RenderableMark} from "../mark.js";

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

/**
* A spatial interpolation implementation function that receives samples’
* positions and values and returns a flat array of *width*×*height* values.
* *x*[*index*[0]] represents the *x*-position of the first sample,
* *y*[*index*[0]] its *y*-position, and *value*[*index*[0]] its value (*e.g.*,
* the observed height for a topographic map).
*/
export type RasterInterpolateFunction = (
/** An array of numeric indexes into the channels *x*, *y*, *value*. */
index: number[],
/** The width of the raster grid in pixels; a positive integer. */
width: number,
/** The height of the raster grid in pixels; a positive integer. */
height: number,
X: number[],
Y: number[],
V: any[]
/** An array of values representing the *x*-position of samples. */
x: number[],
/** An array of values representing the *y*-position of samples. */
y: number[],
/** An array of values representing the sample’s observed value. */
values: any[]
) => any[];

/**
* A spatial interpolation method; either a named built-in interpolation method,
* or a custom interpolation function.
*/
export type RasterInterpolate = RasterInterpolateName | RasterInterpolateFunction;

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

export type RasterSampler = (x: number, y: number, facet: number[] & {fx: any; fy: any}) => any;
/**
* A sampler function, which returns a value for the given *x* and *y* values in
* the current *facet*.
*/
export type RasterSampler = (
/** The horizontal position. */
x: number,
/** The vertical position. */
y: number,
/** The current facet index, and corresponding *fx* and *fy* value. */
facet: number[] & {fx: any; fy: any}
) => any;

/** Options for the raster mark. */
export interface RasterOptions extends Omit<MarkOptions, "fill" | "fillOpacity"> {
/** The horizontal position channel, typically bound to the *x* scale. */
x?: ChannelValueSpec;
/** The vertical position channel, typically bound to the *y* scale. */
y?: ChannelValueSpec;

/**
* The starting horizontal position (typically the left edge) of the raster
* domain; the lower bound of the *x* scale.
*
* If **width** is specified, defaults to 0; otherwise, if *data* is
* specified, defaults to the frame’s left coordinate in *x*. If *data* is not
* specified (as when **value** is a function of *x* and *y*), you must
* specify **x1** explicitly.
*/
x1?: number;

/**
* The ending horizontal position (typically the right edge) of the raster
* domain; the upper bound of the *x* scale.
*
* If **width** is specified, defaults to **width**; otherwise, if *data* is
* specified, defaults to the frame’s right coordinate in *x*. If *data* is
* not specified (as when **value** is a function of *x* and *y*), you must
* specify **x2** explicitly.
*/
x2?: number;

/**
* The starting vertical position (typically the bottom edge) of the raster
* domain; the lower bound of the *y* scale.
*
* If **height** is specified, defaults to 0; otherwise, if *data* is
* specified, defaults to the frame’s top coordinate in *y*. If *data* is not
* specified (as when **value** is a function of *x* and *y*), you must
* specify **y1** explicitly.
*/
y1?: number;

/**
* The ending vertical position (typically the bottom edge) of the raster
* domain; the lower bound of the *y* scale.
*
* If **height** is specified, defaults to **height**; otherwise, if *data* is
* specified, defaults to the frame’s bottom coordinate in *y*. If *data* is
* not specified (as when **value** is a function of *x* and *y*), you must
* specify **y2** explicitly.
*/
y2?: number;

/** The width (number of columns) of the raster grid, in actual pixels. */
width?: number;

/** The height (number of rows) of the raster grid, in actual pixels. */
height?: number;

/**
* The effective screen size of a raster pixel, used to determine the height
* and width of the raster from the frame’s dimensions; defaults to 1.
*/
pixelSize?: number;

/**
* A non-negative pixel radius for smoothing; defaults to 0. Note that
* blurring is applied on the values (before the color scale is applied) if
* quantitative, and after (on the materialized pixels), if ordinal.
*/
blur?: number;

/**
* The spatial interpolation method, when using *data* samples. One of:
*
* - *none* (or null, default) - assign each sample to the containing pixel
* - a named interpolation method, such as *nearest*, *barycentric*, or *random-walk*
* - a custom interpolation function
*/
interpolate?: RasterInterpolate | "none" | null;

/**
* The [image-rendering attribute][1]; defaults to *auto* (bilinear). The
* option may be set to *pixelated* to disable bilinear interpolation for a
* sharper image; however, note that this is not supported in WebKit.
*
* [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/image-rendering
*/
imageRendering?: string;

/**
* The fill, typically bound to the *color* scale. Can be specified as a
* constant, a channel based on the sample *data*, or as a function *f*(*x*,
* *y*) to be evaluated at each pixel if the *data* is not provided.
*/
fill?: ChannelValueSpec | RasterSampler;

/**
* The opacity, typically bound to the *opacity* scale. Can be specified as a
* constant, a channel based on the sample *data*, or as a function *f*(*x*,
* *y*) to be evaluated at each pixel if the *data* is not provided.
*/
fillOpacity?: ChannelValueSpec | RasterSampler;
}

export function raster(options?: RasterOptions): Raster;

/**
* Returns a raster mark which renders a raster image from spatial samples. If
* *data* is provided, it represents discrete samples in abstract coordinates
* **x** and **y**; the **fill** and **fillOpacity** channels specify further
* abstract values (_e.g._, height in a topographic map) to be spatially
* interpolated to produce an image.
*
* ```js
* Plot.raster(volcano.values, {width: volcano.width, height: volcano.height})
* ```
*
* The **fill** and **fillOpacity** channels may alternatively be specified as
* functions *f*(*x*, *y*) to be evaluated at each pixel centroid of the raster
* grid (without interpolation).
*
* ```js
* Plot.raster({x1: -1, x2: 1, y1: -1, y2: 1, fill: (x, y) => Math.atan2(y, x)})
* ```
*
* If **width** is specified, **x1** defaults to 0 and **x2** defaults to
* **width**; likewise, if **height** is specified, **y1** defaults to 0 and
* **y2** defaults to **height**. Otherwise, if *data* is specified, **x1**,
* **y1**, **x2**, and **y2** respectively default to the frame’s left, top,
* right, and bottom coordinates. Lastly, if *data* is not specified (as when
* **value** is a function of *x* and *y*), you must specify all of **x1**,
* **x2**, **y1**, and **y2** to define the raster domain.
*/
export function raster(data?: Data, options?: RasterOptions): Raster;
export function raster(options?: RasterOptions): Raster;

/**
* 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 to randomize the input if needed.
*/
export const interpolateNone: RasterInterpolateFunction;

/**
* 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][1]. 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][2] with a fixed seed (for deterministic results).
*
* [1]: https://en.wikipedia.org/wiki/Barycentric_coordinate_system
* [2]: https://github.com/d3/d3-random/blob/main/README.md#randomLcg
*/
export function interpolatorBarycentric(options?: {random?: RandomSource}): RasterInterpolateFunction;

/**
* Assigns each pixel in the raster grid the value of the closest sample;
* effectively a Voronoi diagram.
*/
export const interpolateNearest: RasterInterpolateFunction;

/**
* 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][1], SIGGRAPH 2020.
*
* [1]: https://www.cs.cmu.edu/~kmcrane/Projects/MonteCarloGeometryProcessing/index.html
*/
export function interpolatorRandomWalk(options?: {
/**
* An optional source of pseudo-random numbers in [0, 1). Called at each step
* of the random walk algorithm with arguments *x*, *y*, and *step*. If not
* specified, defaults to a seeded random number generator.
*/
random?: RandomSource;

/**
* The random walk ends by “snapping” to the closest sample if closer than
* this distance (in pixels).
*/
minDistance?: number;

/**
* After this number of steps, which defaults to 3, lift the **minDistance**
* requirement and snap to the closest sample.
*/
maxSteps?: number;
}): RasterInterpolateFunction;

/** The raster mark. */
export class Raster extends RenderableMark {}
2 changes: 2 additions & 0 deletions src/transforms/bin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export interface BinOptions {
* ```js
* Plot.rectY(numbers, Plot.binX({y: "count"}, {thresholds: 10}))
* ```
*
* See also the **interval** option.
*/
thresholds?: Thresholds;

Expand Down