Home

Awesome

WebGl 2D Scatterplot with Regl

npm version build status file size DOI regl-scatterplot demo

A highly-scalable pan-and-zoomable scatter plot library that uses WebGL through Regl. This library sacrifices feature richness for speed to allow rendering up to 20 million points (depending on your hardware of course) including fast lasso selection. Further, the footprint of regl-scatterplot is kept small. NEW: Python lovers please see jscatter: a Jupyter Notebook/Lab widget that uses regl-scatterplot.

<p> <img src="https://user-images.githubusercontent.com/932103/62905669-7679f380-bd39-11e9-9528-86ee56d6dfba.gif" /> </p>

Demo: https://flekschas.github.io/regl-scatterplot/

Live Playground: https://observablehq.com/@flekschas/regl-scatterplot

Default Interactions:

Note, you can remap rotate and lasso to other modifier keys via the keyMap option!

Supported Visual Encodings:

Install

npm i regl-scatterplot

FYI, if you're using npm version prior to 7, you have to install regl-scatterplot's peer dependencies (regl and pub-sub-es) manually.

Getting started

Basic Example

import createScatterplot from 'regl-scatterplot';

const canvas = document.querySelector('#canvas');

const { width, height } = canvas.getBoundingClientRect();

const scatterplot = createScatterplot({
  canvas,
  width,
  height,
  pointSize: 5,
});

const points = new Array(10000)
  .fill()
  .map(() => [-1 + Math.random() * 2, -1 + Math.random() * 2, color]);

scatterplot.draw(points);

IMPORTANT: Your points positions need to be normalized to [-1, 1] (normalized device coordinates). Why? Regl-scatterplot is designed to be a lower-level library, whose primary purpose is speed. As such it expects you to normalize the data upfront.

Color, Opacity, and Size Encoding

In regl-scatterplot, points can be associated with two data values. These two values are defined as the third and forth component of the point quadruples ([x, y, value, value]). For instance:

scatterplot.draw([
  [0.2, -0.1, 0, 0.1337],
  [0.3, 0.1, 1, 0.3371],
  [-0.9, 0.8, 2, 0.3713],
]);

These two values can be visually encoded as the color, opacity, or the size. Integers are treated as categorical data and floats that range between [0, 1] are treated as continuous values. In the example above, the first point value would be treated as categorical data and the second would be treated as continuous data.

In the edge case that you have continuous data but all data points are either 0 or 1 you can manually set the data type via the zDataType and wDatatype draw options.

To encode the two point values use the colorBy, opacityBy, and sizeBy property as follows:

scatterplot.set({
  opacityBy: 'valueA',
  sizeBy: 'valueA',
  colorBy: 'valueB',
});

In this example we would encode the first categorical point values ([0, 1, 2]) as the point opacity and size. The second continuous point values ([0.1337, 0.3317, 0.3713]) would be encoded as the point color.

The last thing we need to tell regl-scatterplot is what those point values should be translated to. We do this by specifying a color, opacity, and size map as an array of colors, opacities, and sizes as follows:

scatterplot.set({
  pointColor: ['#000000', '#111111', ..., '#eeeeee', '#ffffff'],
  pointSize: [2, 4, 8],
  opacity: [0.5, 0.75, 1],
});

You can encode a point data value in multiple ways. For instance, as you can see in the example above, the categorical fist data value is encoded via the point size and opacity.

What if I have more than two values associated to a point? Unfortunately, this isn't supported currently. In case you're wondering, this limitation is due to how we store the point data. The whole point state is encoded as an RGBA texture where the x and y coordinate are stored as the red and green color components and the first and second data value are stored in the blue and alpha component of the color. However, this limitation might be addressed in future versions so make sure to check back or, even better, start a pull request!

Why can't I specify a range function instead of a map? Until we have implemented enough scale functions in the shader it's easier to let you pre-compute the map. For instance, if you wanted to encode a continuous values on a log scale of point size, you can simply do pointSize: Array(100).fill().map((v, i) => Math.log(i + 1) + 1).

Code Example | Demo

Connecting points

You can connect points visually using spline curves by adding a 5th component to your point data and setting showPointConnections: true.

The 5th component is needed to identify which points should be connected. By default, the order of how the points are connected is defined by the order in which the points appear in your data.

const points = [
  [1, 1, 0, 0, 0],
  [2, 2, 0, 0, 0],
  [3, 3, 0, 0, 1],
  [4, 4, 0, 0, 1],
  [5, 5, 0, 0, 0],
];

In the example above, the points would be connected as follows:

0 -> 1 -> 4
2 -> 3

Line Ordering:

To explicitely define or change the order of how points are connected, you can define a 6th component as follows:

const points = [
  [1, 1, 0, 0, 0, 2],
  [2, 2, 0, 0, 0, 0],
  [3, 3, 0, 0, 1, 1],
  [4, 4, 0, 0, 1, 0],
  [5, 5, 0, 0, 0, 1],
];

would lead tp the following line segment ordering:

1 -> 4 -> 0
3 -> 2

Note, to visualize the point connections, make sure scatterplot.set({ showPointConnection: true }) is set!

Code Example | Demo

Synchronize D3 x and y scales with the scatterplot view

Under the hood regl-scatterplot uses a 2D camera, which you can either get via scatterplot.get('camera') or scatterplot.subscribe('view', ({ camera }) => {}). You can use the camera's view matrix to compute the x and y scale domains. However, since this is tedious, regl-scatterplot allows you to specify D3 x and y scales that will automatically be synchronized. For example:

const xScale = scaleLinear().domain([0, 42]);
const yScale = scaleLinear().domain([-5, 5]);
const scatterplot = createScatterplot({
  canvas,
  width,
  height,
  xScale,
  yScale,
});

Now whenever you pan or zoom, the domains of xScale and yScale will be updated according to your current view. Note, the ranges are automatically set to the width and height of your canvas object.

Code Example | Demo

Translating Point Coordinates to Screen Coordinates

Imagine you want to render additional features on top of points points, for which you need to know where on the canvas points are drawn. To determine the screen coordinates of points you can use D3 scales and scatterplot.get('pointsInView') as follows:

const points = Array.from({ length: 100 }, () => [Math.random() * 42, Math.random()]);
const [xScale, yScale] = [scaleLinear().domain([0, 42]), scaleLinear().domain([0, 1])];

const scatterplot = createScatterplot({ ..., xScale, yScale });
scatterplot.draw(points);

scatterplot.subscribe('view', ({ xScale, yScale }) => {
  console.log('pointsInScreenCoords', scatterplot.get('pointsInView').map((pointIndex) => [
    xScale(points[pointIndex][0]),
    yScale(points[pointIndex][1])
  ]));
});

Code Example | Demo

Transition Points

To make sense of two different states of points, it can help to show an animation by transitioning the points from their first to their second location. To do so, simple draw() the new points as follows:

const initialPoints = Array.from({ length: 100 }, () => [Math.random() * 42, Math.random()]);
const finalPoints = Array.from({ length: 100 }, () => [Math.random() * 42, Math.random()]);

const scatterplot = createScatterplot({ ... });
scatterplot.draw(initialPoints).then(() => {
  scatterplot.draw(finalPoints, { transition: true });
})

It's important that the number of points is the same for the two draw() calls. Also note that the point correspondence is determined by their index.

Code Example | Demo

Zoom to Points

Sometimes it can be useful to programmatically zoom to a set of points. In regl-scatterplot you can do this with the zoomToPoints() method as follows:

const points = Array.from({ length: 100 }, () => [Math.random() * 42, Math.random()]);

const scatterplot = createScatterplot({ ... });
scatterplot.draw(initialPoints).then(() => {
  // We'll select the first five points...
  scatterplot.select([0, 1, 2, 3, 4]);
  // ...and zoom into them
  scatterplot.zoomToPoints([0, 1, 2, 3, 4], { transition: true })
})

Note that the zooming can be smoothly transitioned when { transition: true } is passed to the function.

Code Example | Demo

Update only the Z/W point coordinates

If you only want to update the z/w points coordinates that can be used for encoding te point color, opacity, or size, you can improve the redrawing performance by reusing the existing spatial index, which is otherwise recomputed every time you draw new points.

const x = (length) => Array.from({ length }, () => -1 + Math.random() * 2);
const y = (length) => Array.from({ length }, () => -1 + Math.random() * 2);
const z = (length) => Array.from({ length }, () => Math.round(Math.random()));
const w = (length) => Array.from({ length }, () => Math.random());

const numPoints = 1000000;
const points = {
  x: x(numPoints),
  y: y(numPoints),
  z: z(numPoints),
  w: w(numPoints),
};

const scatterplot = createScatterplot({ ... });
scatterplot.draw(initialPoints).then(() => {
  // After the initial draw, we retrieve and save the KDBush spatial index.
  const spatialIndex = scatterplot.get('spatialIndex');
  setInterval(() => {
    // Update Z and W values
    points.z = z(numPoints);
    points.w = w(numPoints);

    // We redraw the scatter plot with the updates points. Importantly, since
    // the x/y coordinates remain unchanged we pass in the saved spatial index
    // to avoid having to re-index the points.
    scatterplot.draw(points, { spatialIndex });
  }, 2000);
})

API

Constructors

<a name="createScatterplot" href="#createScatterplot">#</a> <b>createScatterplot</b>(<i>options = {}</i>)

Returns: a new scatterplot instance.

Options: is an object that accepts any of the properties.

<a name="createRenderer" href="#createRenderer">#</a> <b>createRenderer</b>(<i>options = {}</i>)

Returns: a new Renderer instance with appropriate extensions being enabled.

Options: is an object that accepts any of the following optional properties:

<a name="createRegl" href="#createRegl">#</a> <b>createRegl</b>(<i>canvas</i>)

Returns: a new Regl instance with appropriate extensions being enabled.

Canvas: the canvas object on which the scatterplot will be rendered on.

<a name="createTextureFromUrl" href="#createTextureFromUrl">#</a> <b>createTextureFromUrl</b>(<i>regl</i>, <i>url</i>)

DEPRECATED! Use scatterplot.createTextureFromUrl() instead.

Methods

<a name="scatterplot.draw" href="#scatterplot.draw">#</a> scatterplot.<b>draw</b>(<i>points</i>, <i>options</i>)

Sets and draws points. Importantly, the points' x and y coordinates need to have been normalized to [-1, 1] (normalized device coordinates). The two additional values (valueA and valueB) need to be normalized to [0, 1] (if they represent continuous data) or [0, >1] (if they represent categorical data).

Note that repeatedly calling this method without specifying points will not clear previously set points. To clear points use scatterplot.clear().

Arguments:

Returns: a Promise object that resolves once the points have been drawn or transitioned.

Examples:

const points = [
  [
    // The relative X position in [-1,1] (normalized device coordinates)
    0.9,
    // The relative Y position in [-1,1] (normalized device coordinates)
    0.3,
    // The category, which defaults to `0` if `undefined`
    0,
    // A continuous value between [0,1], which defaults to `0` if `undefined`
    0.5,
  ],
];

scatterplot.draw(points);

// You can now do something else like changing the point size etc.

// If we want to animate the transition of our point from above to another
// x,y position, we can also do this by drawing a new point while enableing
// transition via the `options` argument.
scatterplot.draw([[0.6, 0.6, 0, 0.6]], { transition: true });

// Let's unset the points. To do so, pass in an empty array to `draw()`.
// Or alternatively, call `scatterplot.clear()`
scatterplot.draw([]);

// You can also specify the point data in a column-oriented format. The
// following call will draw three points: (1,3), (2,2), and (3,1)
scatterplot.draw({
  x: [1, 2, 3],
  y: [3, 2, 1],
});

// Finally, you can also specify which point will be hovered, which points will
// be selected, and which points will be filtered. These options are useful to
// avoid a flicker which would occur if `hover()`, `select()`, and `filter()`
// are called after `draw()`.
scatterplot.draw(
  { x: [1, 2, 3], y: [3, 2, 1] },
  { hover: 0, selected: [0, 1], filter: [0, 2] }
);

<a name="scatterplot.redraw" href="#scatterplot.redraw">#</a> scatterplot.<b>redraw</b>()

Redraw the scatter plot at the next animation frame.

Note, that regl-scatterlot automatically redraws the scatter plot whenever the view changes in some ways. So theoretically, there should never be a need to call this function!

<a name="scatterplot.clear" href="#scatterplot.clear">#</a> scatterplot.<b>clear</b>()

Clears previously drawn points, point connections, and annotations.

<a name="scatterplot.clearPoints" href="#scatterplot.clearPoints">#</a> scatterplot.<b>clearPoints</b>()

Clears previously drawn points and point connections.

<a name="scatterplot.clearPointConnections" href="#scatterplot.clearPointConnections">#</a> scatterplot.<b>clearPointConnections</b>()

Clears previously point connections.

<a name="scatterplot.drawAnnotations" href="#scatterplot.drawAnnotations">#</a> scatterplot.<b>drawAnnotations</b>(<i>annotations</i>)

Draw line-based annotations of the following kind in normalized device coordinates:

Arguments:

Returns: a Promise object that resolves once the annotations have been drawn or transitioned.

Examples:

const scatterplot = createScatterplot({
  ...,
  annotationLineColor: [1, 1, 1, 0.1], // Default line color
  annotationLineWidth: 1, // Default line width
});

scatterplot.draw({
  x: Array.from({ length: 10000 }, () => -1 + Math.random() * 2),
  y: Array.from({ length: 10000 }, () => -1 + Math.random() * 2),
});

scatterplot.drawAnnotations([
  // Horizontal line
  { y: 0 },
  // Vertical line
  { x: 0 },
  // Rectangle
  {
    x1: -0.5, y1: -0.5, x2: 0.5, y2: 0.5,
    lineColor: [1, 0, 0, 0.33],
    lineWidth: 2,
  },
  // Polygon
  {
    vertices: [[-1, 0], [0, 1], [1, 0], [0, -1], [-1, 0]],
    lineColor: [1, 1, 0, 0.33],
    lineWidth: 3,
  },
]);

<a name="scatterplot.clearAnnotations" href="#scatterplot.clearAnnotations">#</a> scatterplot.<b>clearAnnotations</b>()

Clears previously drawn annotations.

<a name="scatterplot.get" href="#scatterplot.set">#</a> scatterplot.<b>get</b>(<i>property</i>)

Arguments:

Returns: the property value.

<a name="scatterplot.set" href="#scatterplot.set">#</a> scatterplot.<b>set</b>(<i>properties = {}</i>)

Arguments:

<a name="scatterplot.select" href="#scatterplot.select">#</a> scatterplot.<b>select</b>(<i>points</i>, <i>options = {}</i>)

Select some points, such that they get visually highlighted. This will trigger a select event unless options.preventEvent === true.

Arguments:

Examples:

// Let's say we have three points
scatterplot.draw([
  [0.1, 0.1],
  [0.2, 0.2],
  [0.3, 0.3],
]);

// To select the first and second point we have to do
scatterplot.select([0, 1]);

<a name="scatterplot.deselect" href="#scatterplot.deselect">#</a> scatterplot.<b>deselect</b>(<i>options = {}</i>)

Deselect all selected points. This will trigger a deselect event unless options.preventEvent === true.

<a name="scatterplot.filter" href="#scatterplot.filter">#</a> scatterplot.<b>filter</b>(<i>points</i>, <i>options = {}</i>)

Filter down the currently drawn points, such that all points that are not included in the filter are visually and interactivelly hidden. This will trigger a filter event unless options.preventEvent === true.

Note: filtering down points can affect previously selected points. Selected points that are filtered out are also deselected.

Arguments:

Examples:

// Let's say we have three points
scatterplot.draw([
  [0.1, 0.1],
  [0.2, 0.2],
  [0.3, 0.3],
]);

// To only show the first and second point we have to do
scatterplot.filter([0, 1]);

<a name="scatterplot.unfilter" href="#scatterplot.unfilter">#</a> scatterplot.<b>unfilter</b>(<i>options = {}</i>)

Reset previously filtered out points. This will trigger an unfilter event unless options.preventEvent === true.

<a name="scatterplot.hover" href="#scatterplot.hover">#</a> scatterplot.<b>hover</b>(<i>point</i>, <i>options = {}</i>)

Programmatically hover a point, such that it gets visually highlighted. This will trigger a pointover or pointout event unless options.preventEvent === true.

Arguments:

Examples:

scatterplot.draw([
  [0.1, 0.1],
  [0.2, 0.2],
  [0.3, 0.3],
]);

scatterplot.hover(1); // To hover the second point

Arguments:

<a name="scatterplot.zoomToPoints" href="#scatterplot.zoomToPoints">#</a> scatterplot.<b>zoomToPoints</b>(<i>points</i>, <i>options = {}</i>)

Zoom to a set of points

Arguments:

Examples:

// Let's say we have three points
scatterplot.draw([
  [0.1, 0.1],
  [0.2, 0.2],
  [0.3, 0.3],
]);

// To zoom to the first and second point we have to do
scatterplot.zoomToPoints([0, 1]);

<a name="scatterplot.zoomToOrigin" href="#scatterplot.zoomToOrigin">#</a> scatterplot.<b>zoomToOrigin</b>(<i>options = {}</i>)

Zoom to the original camera position. This is similar to resetting the view

Arguments:

<a name="scatterplot.zoomToLocation" href="#scatterplot.zoomToLocation">#</a> scatterplot.<b>zoomToLocation</b>(<i>target</i>, <i>distance</i>, <i>options = {}</i>)

Zoom to a specific location, specified in normalized device coordinates. This function is similar to scatterplot.lookAt(), however, it allows to smoothly transition the camera position.

Arguments:

Examples:

scatterplot.zoomToLocation([0.5, 0.5], 0.5, { transition: true });
// => This will make the camera zoom into the top-right corner of the scatter plot

<a name="scatterplot.zoomToArea" href="#scatterplot.zoomToArea">#</a> scatterplot.<b>zoomToArea</b>(<i>rectangle</i>, <i>options = {}</i>)

Zoom to a specific area specified by a recangle in normalized device coordinates.

Arguments:

Examples:

scatterplot.zoomToArea(
  { x: 0, y: 0, width: 1, height: 1 },
  { transition: true }
);
// => This will make the camera zoom into the top-right corner of the scatter plot

<a name="scatterplot.getScreenPosition" href="#scatterplot.getScreenPosition">#</a> scatterplot.<b>getScreenPosition</b>(<i>pointIdx</i>)

Get the screen position of a point

Arguments:

Examples:

// Let's say we have a 100x100 pixel scatter plot with three points
const scatterplot = createScatterplot({ width: 100, height: 100 });
scatterplot.draw([
  [-1, -1],
  [0, 0],
  [1, 1],
]);

// To retrieve the screen position of the second point you can call. If we
// haven't panned and zoomed, the returned position should be `50, 50`
scatterplot.getScreenPosition(1);
// => [50, 50]

<a name="scatterplot.lookAt" href="#scatterplot.lookAt">#</a> scatterplot.<b>lookAt</b>(<i>view</i>, <i>options = {}</i>)

Update the camera's view matrix to change the viewport. This will trigger a view event unless options.preventEvent === true.

Note, this API is a shorthand to scatterplot.set({ 'cameraView': view }) with the additional features of allowing to prevent view events.

<a name="scatterplot.destroy" href="#scatterplot.destroy">#</a> scatterplot.<b>destroy</b>()

Destroys the scatterplot instance by disposing all event listeners, the pubSub instance, regl, and the camera.

<a name="scatterplot.refresh" href="#scatterplot.refresh">#</a> scatterplot.<b>refresh</b>()

Refreshes the viewport of the scatterplot's regl instance.

<a name="scatterplot.reset" href="#scatterplot.reset">#</a> scatterplot.<b>reset</b>(<i>options</i>)

Sets the view back to the initially defined view. This will trigger a view event unless options.preventEvent === true.

<a name="scatterplot.export" href="#scatterplot.export">#</a> scatterplot.<b>export</b>(<i>options</i>)

Arguments:

Returns: an object with three properties: pixels, width, and height. The pixels is a Uint8ClampedArray.

<a name="scatterplot.subscribe" href="#scatterplot.subscribe">#</a> scatterplot.<b>subscribe</b>(<i>eventName</i>, <i>eventHandler</i>)

Subscribe to an event.

Arguments:

Returns: an unsubscriber object that can be passed into unsubscribe().

<a name="scatterplot.unsubscribe" href="#scatterplot.unsubscribe">#</a> scatterplot.<b>unsubscribe</b>(<i>eventName</i>, <i>eventHandler</i>)

Unsubscribe from an event. See scatterplot.subscribe() for a list of all events.

<a name="scatterplot.createTextureFromUrl" href="#scatterplot.createTextureFromUrl">#</a> scatterplot.<b>createTextureFromUrl</b>(<i>url</i>)

Returns: a Promise that resolves to a Regl texture that can be used, for example, as the background image.

url: the URL to an image.

Properties

You can customize the scatter plot according to the following properties that can be read and written via scatterplot.get() and scatterplot.set().

NameTypeDefaultConstraintsSettableNullifiable
canvasobjectdocument.createElement('canvas')falsefalse
reglReglcreateRegl(canvas)falsefalse
rendererRenderercreateRenderer()falsefalse
syncEventsbooleanfalsefalsefalse
versionstringfalsefalse
spatialIndexArrayBufferfalsefalse
spatialIndexUseWorkerundefined or booleanundefinedtruefalse
widthint or str'auto''auto' or > 0truefalse
heightint or str'auto''auto' or > 0truefalse
aspectRatiofloat1.0> 0truefalse
backgroundColorstring or arrayrgba(0, 0, 0, 1)hex, rgb, rgbatruefalse
backgroundImagefunctionnullRegl texturetruetrue
cameraobjectSee dom-2d-camerafalsefalse
cameraTargettuple[0, 0]truefalse
cameraDistancefloat1> 0truefalse
cameraRotationfloat0truefalse
cameraViewFloat32Array[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]truefalse
cameraIsFixedbooleanfalsetruefalse
colorBystringnullSee data encodingtruetrue
sizeBystringnullSee data encodingtruetrue
opacityBystringnullSee data encodingtruetrue
deselectOnDblClickbooleantruetruefalse
deselectOnEscapebooleantruetruefalse
opacityfloat1Must be in ]0, 1]truefalse
opacityInactiveMaxfloat1Must be in [0, 1]truefalse
opacityInactiveScalefloat1Must be in [0, 1]truefalse
pointstuple[][[0.5, 2.3], ...]falsefalse
selectedPointsint[][4, 2]falsefalse
filteredPointsint[][4, 2]falsefalse
pointsInViewint[][1, 2, 12]falsefalse
pointColorquadruple[0.66, 0.66, 0.66, 1]single value or list of hex, rgb, rgbatruefalse
pointColorActivequadruple[0, 0.55, 1, 1]single value or list of hex, rgb, rgbatruefalse
pointColorHoverquadruple[1, 1, 1, 1]single value or list of hex, rgb, rgbatruefalse
pointOutlineWidthint2>= 0truefalse
pointSizeint6> 0truefalse
pointSizeSelectedint2>= 0truefalse
showPointConnectionbooleanfalsetruefalse
pointConnectionColorquadruple[0.66, 0.66, 0.66, 0.2]truefalse
pointConnectionColorActivequadruple[0, 0.55, 1, 1]truefalse
pointConnectionColorHoverquadruple[1, 1, 1, 1]truefalse
pointConnectionColorBystringnullSee data encodingtruefalse
pointConnectionOpacityfloat0.1truefalse
pointConnectionOpacityActivefloat0.66truefalse
pointConnectionOpacityBystringnullSee data encodingtruefalse
pointConnectionSizefloat2truefalse
pointConnectionSizeActivefloat2truefalse
pointConnectionSizeBystringnullSee data encodingtruefalse
pointConnectionMaxIntPointsPerSegmentint100truefalse
pointConnectionTolerancefloat0.002truefalse
pointScaleModestring'asinh''asinh', 'linear', or 'constant'truefalse
lassoColorquadruplergba(0, 0.667, 1, 1)hex, rgb, rgbatruefalse
lassoLineWidthfloat2>= 1truefalse
lassoMinDelayint15>= 0truefalse
lassoMinDistint4>= 0truefalse
lassoClearEventstring'lassoEnd''lassoEnd' or 'deselect'truefalse
lassoInitiatorbooleanfalsetruefalse
lassoInitiatorElementobjectthe lasso dom elementfalsefalse
lassoInitiatorParentElementobjectdocument.bodytruefalse
lassoLongPressIndicatorParentElementobjectdocument.bodytruefalse
lassoOnLongPressbooleanfalsetruefalse
lassoLongPressTimeint750truefalse
lassoLongPressAfterEffectTimeint500truefalse
lassoLongPressEffectDelayint100truefalse
lassoLongPressRevertEffectTimeint250truefalse
showReticlebooleanfalsetrue or falsetruefalse
reticleColorquadruplergba(1, 1, 1, .5)hex, rgb, rgbatruefalse
xScalefunctionnullmust follow the D3 scale APItruetrue
yScalefunctionnullmust follow the D3 scale APItruetrue
keyMapobject{ alt: 'rotate', shift: 'lasso' }See the notes belowtruefalse
mouseModestring'panZoom''panZoom', 'lasso', or 'rotate'truefalse
performanceModebooleanfalsecan only be set during initialization!truefalse
gammafloat1to control the opacity blendingtruefalse
isDestroyedbooleanfalsefalsefalse
isPointsDrawnbooleanfalsefalsefalse
isPointsFilteredbooleanfalsefalsefalse
annotationLineColorstring or quadruple[1, 1, 1, 0.1]hex, rgb, rgbatruefalse
annotationLineWidthnumber1truefalse
annotationHVLineLimitnumber1000the extent of horizontal or vertical linestruefalse

<a name="property-notes" href="#property-notes">#</a> <b>Notes:</b>

<a name="property-by" href="#property-by">#</a> <b>colorBy, opacityBy, sizeBy:</b>

To visual encode one of the two point values set colorBy, opacityBy, or sizeBy to one of the following values referencing the third or forth component of your points. To reference the third component you can use category (only for backwards compatibility), value1, valueA, valueZ, or z. To reference the forth component use value (only for backwards compatibility), value2, valueB, valueW, or w.

Density-based opacity encoding: In addition, the opacity can dynamically be set based on the point density and zoom level via opacityBy: 'density'. As an example go to dynamic-opacity.html. The implementation is an extension of Ricky Reusser's awesome notebook. Huuuge kudos Ricky! 🙇‍♂️

<a name="property-point-conntection-by" href="#property-point-conntection-by">#</a> <b>pointConnectionColorBy, pointConnectionOpacityBy, and pointConnectionSizeBy:</b>

In addition to the properties understood by colorBy, etc., pointConnectionColorBy, pointConnectionOpacityBy, and pointConnectionSizeBy also understand "inherit" and "segment". When set to "inherit", the value will be inherited from its point-specific counterpart. When set to "segment", each segment of a point connection will be encoded separately. This allows you to, for instance, color connection by a gradient from the start to the end of each line.

<a name="property-lassoInitiator" href="#property-lassoInitiator">#</a> <b>lassoInitiator:</b>

When setting lassoInitiator to true you can initiate the lasso selection without the need to hold down a modifier key. Simply click somewhere into the background and a circle will appear under your mouse cursor. Now click into the circle and drag you mouse to start lassoing. You can additionally invoke the lasso initiator circle by a long click on a dot.

Lasso Initiator

You don't like the look of the lasso initiator? No problem. Simple get the DOM element via scatterplot.get('lassoInitiatorElement') and adjust the style via JavaScript. E.g.: scatterplot.get('lassoInitiatorElement').style.background = 'green'.

<a name="property-keymap" href="#property-keymap">#</a> <b>KeyMap:</b>

The keyMap property is an object defining which actions are enabled when holding down which modifier key. E.g.: { shift: 'lasso' }. Acceptable modifier keys are alt, cmd, ctrl, meta, shift. Acceptable actions are lasso, rotate, and merge (for selecting multiple items by merging a series of lasso or click selections).

You can also use the keyMap option to disable the lasso selection and rotation by setting keyMap to an empty object.

<a name="property-examples" href="#property-examples">#</a> <b>Examples:</b>

// Set width and height
scatterplot.set({ width: 300, height: 200 });

// get width
const width = scatterplot.get('width');

// Set the aspect ratio of the scatterplot. This aspect ratio is referring to
// your data source and **not** the aspect ratio of the canvas element! By
// default it is assumed that your data us following a 1:1 ratio and this ratio
// is preserved even if your canvas element has some other aspect ratio. But if
// you wanted you could provide data that's going from [0,2] in x and [0,1] in y
// in which case you'd have to set the aspect ratio as follows to `2`.
scatterplot.set({ aspectRatio: 2.0 });

// Set background color to red
scatterplot.set({ backgroundColor: '#00ff00' }); // hex string
scatterplot.set({ backgroundColor: [255, 0, 0] }); // rgb array
scatterplot.set({ backgroundColor: [255, 0, 0, 1.0] }); // rgba array
scatterplot.set({ backgroundColor: [1.0, 0, 0, 1.0] }); // normalized rgba

// Set background image to an image
scatterplot.set({ backgroundImage: 'https://server.com/my-image.png' });
// If you need to know when the image was loaded you have two options. First,
// you can listen to the following event
scatterplot.subscribe(
  'backgroundImageReady',
  () => {
    console.log('Background image is now loaded and rendered!');
  },
  1
);
// or you load the image yourself as follows
const backgroundImage = await scatterplot.createTextureFromUrl(
  'https://server.com/my-image.png'
);
scatterplot.set({ backgroundImage });

// Color by
scatterplot.set({ colorBy: 'category' });

// Set color map
scatterplot.set({
  pointColor: ['#ff0000', '#00ff00', '#0000ff'],
  pointColorActive: ['#ff0000', '#00ff00', '#0000ff'], // optional
  pointColorHover: ['#ff0000', '#00ff00', '#0000ff'], // optional
});

// Set base opacity
scatterplot.set({ opacity: 0.5 });

// If you want to deemphasize unselected points (when some points are selected)
// you can rescale the unselected points' opacity as follows
scatterplot.set({ opacityInactiveScale: 0.5 });

// Set the width of the outline of selected points
scatterplot.set({ pointOutlineWidth: 2 });

// Set the base point size
scatterplot.set({ pointSize: 10 });

// Set the additional point size of selected points
scatterplot.set({ pointSizeSelected: 2 });

// Change the lasso color and make it very smooth, i.e., do not wait before
// extending the lasso (i.e., `lassoMinDelay = 0`) and extend the lasso when
// the mouse moves at least 1 pixel
scatterplot.set({
  lassoColor: [1, 1, 1, 1],
  lassoMinDelay: 0,
  lassoMinDist: 1,
  // This will keep the drawn lasso until the selected points are deselected
  lassoClearEvent: 'deselect',
});

// Activate reticle and set reticle color to red
scatterplot.set({ showReticle: true, reticleColor: [1, 0, 0, 0.66] });

Renderer

The renderer class is responsible for rendering pixels onto the scatter plot's canvas using WebGL via Regl. It's created automatically internally but you can also create it yourself, which can be useful when you want to instantiate multiple scatter plot instances as they can share one renderer.

Renderer API

<a name="renderer.canvas" href="#renderer.canvas">#</a> renderer.<b>canvas</b>

The renderer's canvas instance. (Read-only)

<a name="renderer.gamma" href="#renderer.gamma">#</a> renderer.<b>gamma</b>

The renderer's gamma value. This value influences the alpha blending.

<a name="renderer.regl" href="#renderer.regl">#</a> renderer.<b>regl</b>

The renderer's regl instance. (Read-only)

<a name="renderer.onFrame" href="#renderer.onFrame">#</a> renderer.<b>onFrame</b>(<i>function</i>)

Add a function to be called on every animation frame.

Arguments:

Returns: A function to remove the added function from the animation frame cycle.

<a name="renderer.refresh" href="#renderer.refresh">#</a> renderer.<b>refresh</b>()

Updates Regl's viewport, drawingBufferWidth, and drawingBufferHeight.

<a name="renderer.render" href="#renderer.render">#</a> renderer.<b>render</b>(<i>drawFunction</i>, <i>targetCanvas</i>)

Render Regl draw instructions into a target canvas using the renderer.

Arguments:

Events

NameTriggerPayload
initwhen the scatter plot is initializedundefined
destroywhen the scatter plot is destroyedundefined
backgroundImageReadywhen the background image was loadedundefined
pointOverwhen the mouse cursor is over a pointpointIndex
pointOutwhen the mouse cursor moves out of a pointpointIndex
selectwhen points are selected{ points }
deselectwhen points are deselectedundefined
filterwhen points are filtered{ points }
unfilterwhen the point filter is resetundefined
viewwhen the view has changes{ camera, view, xScale, yScale }
drawwhen the plot was drawn{ camera, view, xScale, yScale }
drawingwhen the plot is being drawn{ camera, view, xScale, yScale }
lassoStartwhen the lasso selection has startedundefined
lassoExtendwhen the lasso selection has extended{ coordinates }
lassoEndwhen the lasso selection has ended{ coordinates }
transitionStartwhen points started to transitionundefined
transitionEndwhen points ended to transitioncreateRegl(canvas)
pointConnectionsDrawwhen point connections were drawnundefined

Trouble Shooting

Resizing the scatterplot

The chances are high that you use the regl-scatterplot in a dynamically-resizable or interactive web-app. Please note that regl-scatterplot doesn't not automatically resize when the dimensions of its parent container change. It's your job to keep the size of regl-scatterplot and its parent element in sync. Hence, every time the size of the parent or canvas element changed, you have to call:

const { width, height } = canvas.getBoundingClientRect();
scatterplot.set({ width, height });

Using regl-scatterplot with Vue

Related to the resizing, when conditionally displaying regl-scatterplot in Vue you might have to update the width and height when the visibility is changed. See issue #20 for an example.

Citation

If you like regl-scatterplot and are using it in your research, we'd appreciate if you could cite our paper:

@article {lekschas2023reglscatterplot,
  author = {Lekschas, Fritz},
  title = {Regl-Scatterplot: A Scalable Interactive JavaScript-based Scatter Plot Library},
  journal = {Journal of Open Source Software},
  volume = {8},
  number = {84},
  pages = {5275},
  year = {2023},
  month = {4},
  doi = {10.21105/joss.05275},
  url = {https://doi.org/10.21105/joss.05275},
}