@vitessce/scatterplot 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/deflate.65a17097.mjs +13 -0
  2. package/dist/index.8206952d.mjs +132343 -0
  3. package/dist/index.mjs +15 -0
  4. package/dist/jpeg.4221d32f.mjs +840 -0
  5. package/dist/lerc.8d649494.mjs +1943 -0
  6. package/dist/lzw.89350f4e.mjs +128 -0
  7. package/dist/packbits.986f9d9f.mjs +30 -0
  8. package/dist/pako.esm.4b234125.mjs +3940 -0
  9. package/dist/raw.1cc73933.mjs +12 -0
  10. package/dist/webimage.be69a2d5.mjs +32 -0
  11. package/{dist → dist-tsc}/index.js +0 -0
  12. package/package.json +11 -11
  13. package/src/EmptyMessage.js +11 -0
  14. package/src/Scatterplot.js +385 -0
  15. package/src/ScatterplotOptions.js +247 -0
  16. package/src/ScatterplotTooltipSubscriber.js +38 -0
  17. package/src/index.js +11 -0
  18. package/src/shared-spatial-scatterplot/AbstractSpatialOrScatterplot.js +274 -0
  19. package/src/shared-spatial-scatterplot/ToolMenu.js +105 -0
  20. package/src/shared-spatial-scatterplot/ToolMenu.test.jsx +18 -0
  21. package/src/shared-spatial-scatterplot/cursor.js +23 -0
  22. package/src/shared-spatial-scatterplot/dynamic-opacity.js +58 -0
  23. package/src/shared-spatial-scatterplot/dynamic-opacity.test.js +33 -0
  24. package/src/shared-spatial-scatterplot/force-collide-rects.js +189 -0
  25. package/src/shared-spatial-scatterplot/force-collide-rects.test.js +72 -0
  26. package/{dist → src}/shared-spatial-scatterplot/index.js +4 -1
  27. package/src/shared-spatial-scatterplot/quadtree.js +27 -0
  28. package/dist/EmptyMessage.js +0 -6
  29. package/dist/Scatterplot.js +0 -304
  30. package/dist/ScatterplotOptions.js +0 -50
  31. package/dist/ScatterplotTooltipSubscriber.js +0 -14
  32. package/dist/shared-spatial-scatterplot/AbstractSpatialOrScatterplot.js +0 -213
  33. package/dist/shared-spatial-scatterplot/ToolMenu.js +0 -58
  34. package/dist/shared-spatial-scatterplot/ToolMenu.test.js +0 -16
  35. package/dist/shared-spatial-scatterplot/cursor.js +0 -22
  36. package/dist/shared-spatial-scatterplot/dynamic-opacity.js +0 -47
  37. package/dist/shared-spatial-scatterplot/dynamic-opacity.test.js +0 -28
  38. package/dist/shared-spatial-scatterplot/force-collide-rects.js +0 -169
  39. package/dist/shared-spatial-scatterplot/force-collide-rects.test.js +0 -58
  40. package/dist/shared-spatial-scatterplot/quadtree.js +0 -26
@@ -1,22 +0,0 @@
1
- export const getCursorWithTool = () => 'crosshair';
2
- export const getCursor = interactionState => (interactionState.isDragging
3
- ? 'grabbing' : 'default');
4
- export function getOnHoverCallback(obsIndex, setObsHighlight, setComponentHover) {
5
- return (info) => {
6
- // Notify the parent component that its child component is
7
- // the "hover source".
8
- if (setComponentHover) {
9
- setComponentHover();
10
- }
11
- if (info.index) {
12
- const obsId = obsIndex[info.index];
13
- if (setObsHighlight) {
14
- setObsHighlight(obsId);
15
- }
16
- }
17
- else if (setObsHighlight) {
18
- // Clear the currently-hovered cell info by passing null.
19
- setObsHighlight(null);
20
- }
21
- };
22
- }
@@ -1,47 +0,0 @@
1
- import { deck } from '@vitessce/gl';
2
- import clamp from 'lodash/clamp';
3
- // Reference: https://observablehq.com/@rreusser/selecting-the-right-opacity-for-2d-point-clouds
4
- // Reference: https://observablehq.com/@bmschmidt/dot-density-election-maps-with-webgl
5
- export function getPointSizeDevicePixels(devicePixelRatio, zoom, xRange, yRange, width, height) {
6
- // Size of a point, in units of the diagonal axis.
7
- const pointSize = 0.0005;
8
- // Point size maximum, in screen pixels.
9
- const pointScreenSizeMax = 10;
10
- // Point size minimum, in screen pixels.
11
- const pointScreenSizeMin = 1 / devicePixelRatio;
12
- const scaleFactor = 2 ** zoom;
13
- const xAxisRange = 2.0 / ((xRange * scaleFactor) / width);
14
- const yAxisRange = 2.0 / ((yRange * scaleFactor) / height);
15
- // The diagonal screen size as a fraction of the current diagonal axis range,
16
- // then converted to device pixels.
17
- const diagonalScreenSize = Math.sqrt((width ** 2) + (height ** 2));
18
- const diagonalAxisRange = Math.sqrt((xAxisRange ** 2) + (yAxisRange ** 2));
19
- const diagonalFraction = pointSize / diagonalAxisRange;
20
- const deviceSize = diagonalFraction * diagonalScreenSize;
21
- const pointSizeDevicePixels = clamp(deviceSize, pointScreenSizeMin, pointScreenSizeMax);
22
- return pointSizeDevicePixels;
23
- }
24
- // Reference: https://observablehq.com/@rreusser/selecting-the-right-opacity-for-2d-point-clouds
25
- export function getPointOpacity(zoom, xRange, yRange, width, height, numCells, avgFillDensity) {
26
- const N = numCells;
27
- const [minX, minY, maxX, maxY] = new deck.OrthographicView({ zoom }).makeViewport({
28
- height,
29
- width,
30
- viewState: { zoom, target: [0, 0, 0] },
31
- }).getBounds();
32
- const X = maxY - minY;
33
- const Y = maxX - minX;
34
- const X0 = xRange;
35
- const Y0 = yRange;
36
- const W = width;
37
- const H = height;
38
- let rho = avgFillDensity;
39
- if (!rho) {
40
- rho = Math.min(1, 1 / (10 ** (Math.log10(N) - 3)));
41
- }
42
- // p in the calculation is the pixel length/width of a given point, which for us is 1
43
- // so it does not factor into our calculation here.
44
- const alpha = ((rho * W * H) / N) * (Y0 / Y) * (X0 / X);
45
- const pointOpacity = clamp(alpha, 1.01 / 255, 1.0);
46
- return pointOpacity;
47
- }
@@ -1,28 +0,0 @@
1
- import { getPointSizeDevicePixels, getPointOpacity } from './dynamic-opacity';
2
- describe('dynamic-opacity.js', () => {
3
- describe('getPointSizeDevicePixels', () => {
4
- it('calculates point size', () => {
5
- const devicePixelRatio = 2.0;
6
- const zoom = null;
7
- const xRange = 20;
8
- const yRange = 18;
9
- const width = 1000;
10
- const height = 650;
11
- const pointSize = getPointSizeDevicePixels(devicePixelRatio, zoom, xRange, yRange, width, height);
12
- expect(pointSize).toBeCloseTo(0.5);
13
- });
14
- });
15
- describe('getPointOpacity', () => {
16
- it('calculates point opacity', () => {
17
- const zoom = null;
18
- const width = 1000;
19
- const height = 650;
20
- const xRange = 20;
21
- const yRange = 18;
22
- const numCells = 500000;
23
- const avgFillDensity = undefined;
24
- const pointOpacity = getPointOpacity(zoom, xRange, yRange, width, height, numCells, avgFillDensity);
25
- expect(pointOpacity).toBeCloseTo(0.005);
26
- });
27
- });
28
- });
@@ -1,169 +0,0 @@
1
- /* eslint-disable no-plusplus */
2
- /* eslint-disable no-param-reassign */
3
- import { quadtree } from 'd3-quadtree';
4
- /**
5
- * Returns a closure that returns a constant value.
6
- */
7
- function constant(v) {
8
- return (() => v);
9
- }
10
- /**
11
- * Adds a tiny bit of randomness to a number.
12
- */
13
- function jiggle(v) {
14
- return v + (Math.random() - 0.5) * 1e-6;
15
- }
16
- /**
17
- * A force function to be used with d3.forceSimulation.
18
- * This has been adapted for use here, with comments explaining each part.
19
- * Reference: https://bl.ocks.org/cmgiven/547658968d365bcc324f3e62e175709b
20
- */
21
- export function forceCollideRects() {
22
- // D3 implements things with function prototypes rather than classes.
23
- // Pretend these variables are the "instance members" of a class.
24
- // Note that this function actually returns the internal force() function,
25
- // but that the force() function is a closure with access to these instance members.
26
- let nodes;
27
- let masses;
28
- let strength = 1;
29
- let iterations = 1;
30
- let sizes;
31
- let size = constant([0, 0]);
32
- // Given a node, return the center point along the x-axis.
33
- function xCenter(d) {
34
- return d.x + d.vx + sizes[d.index][0] / 2;
35
- }
36
- // Given a node, return the center point along the y-axis.
37
- function yCenter(d) {
38
- return d.y + d.vy + sizes[d.index][1] / 2;
39
- }
40
- // Given a quadtree node, initialize its .size property.
41
- function prepare(quad) {
42
- if (quad.data) {
43
- // This is a leaf node, so we set quad.size to the node's size.
44
- // (No need to compute the max of internal nodes,
45
- // since leaf nodes do not have any internal nodes).
46
- quad.size = sizes[quad.data.index];
47
- }
48
- else {
49
- quad.size = [0, 0];
50
- // Internal nodes of the quadtree are represented
51
- // as four-element arrays in left-to-right, top-to-bottom order.
52
- // Here, we are setting quad.size to [maxWidth, maxHeight]
53
- // among the internal nodes of this current `quad` node.
54
- for (let i = 0; i < 4; i++) {
55
- if (quad[i] && quad[i].size) {
56
- quad.size[0] = Math.max(quad.size[0], quad[i].size[0]);
57
- quad.size[1] = Math.max(quad.size[1], quad[i].size[1]);
58
- }
59
- }
60
- }
61
- }
62
- function force() {
63
- let node;
64
- let nodeSize;
65
- let nodeMass;
66
- let xi;
67
- let yi;
68
- // Create a quadtree based on node center points.
69
- // Initialize each quadtree node's .size property by calling
70
- // the prepare() function on each quadtree node.
71
- const tree = quadtree(nodes, xCenter, yCenter).visitAfter(prepare);
72
- // Update the .vx and .vy properties of both `node` and `data`
73
- // (the current node pair).
74
- function apply(quad, x0, y0, x1, y1) {
75
- // `quad` is a quadtree node.
76
- const { data } = quad;
77
- const xSize = (nodeSize[0] + quad.size[0]) / 2;
78
- const ySize = (nodeSize[1] + quad.size[1]) / 2;
79
- if (data && data.index > node.index) {
80
- // This is a leaf node because `data` is defined.
81
- // `x` is the difference in x centers between `node` and `data`.
82
- // `y` is the difference in y centers between `node` and `data`.
83
- let x = jiggle(xi - xCenter(data));
84
- let y = jiggle(yi - yCenter(data));
85
- const xd = Math.abs(x) - xSize;
86
- const yd = Math.abs(y) - ySize;
87
- // If `xd` and `yd` is less than zero,
88
- // then there is an overlap between the two nodes.
89
- if (xd < 0 && yd < 0) {
90
- const l = Math.sqrt(x * x + y * y);
91
- const m = masses[data.index] / (nodeMass + masses[data.index]);
92
- // We move the nodes either in the x or y direction.
93
- // Nodes are moved proportionally to:
94
- // their distance apart (`l`), their amount of overlap (`xd` or `yd`), their masses (`m`),
95
- // and the strength parameter (`strength`).
96
- if (Math.abs(xd) < Math.abs(yd)) {
97
- node.vx -= (x *= xd / l * strength) * m;
98
- data.vx += x * (1 - m);
99
- }
100
- else {
101
- node.vy -= (y *= yd / l * strength) * m;
102
- data.vy += y * (1 - m);
103
- }
104
- }
105
- // When the quadtree.visit callback returns _true_ for a node,
106
- // then the node's children will _not_ be visited.
107
- return x0 > xi + xSize || x1 < xi - xSize || y0 > yi + ySize || y1 < yi - ySize;
108
- }
109
- return false;
110
- }
111
- function iterate() {
112
- // On every iteration, use the `apply` function to visit every node
113
- // which has an index greater than the current node's index,
114
- // (visiting every node pair).
115
- for (let j = 0; j < nodes.length; j++) {
116
- node = nodes[j];
117
- nodeSize = sizes[j];
118
- nodeMass = masses[j];
119
- xi = xCenter(node);
120
- yi = yCenter(node);
121
- tree.visit(apply);
122
- }
123
- }
124
- // Do the specified number of iterations.
125
- for (let i = 0; i < iterations; i++) {
126
- iterate();
127
- }
128
- }
129
- // The "constructor".
130
- // Takes a list of nodes as input.
131
- force.initialize = (v) => {
132
- nodes = v;
133
- // Get the size [w, h] of each node using the size getter function.
134
- sizes = nodes.map(size);
135
- // Get the mass of each node,
136
- // which is the sum of its horizontal and vertical edge lengths.
137
- masses = sizes.map(d => d[0] + d[1]);
138
- };
139
- // Set the number of iterations.
140
- // If no value is provided as a parameter, this acts as a getter function.
141
- force.iterations = (...v) => {
142
- if (v.length) {
143
- iterations = +v[0];
144
- return force;
145
- }
146
- return iterations;
147
- };
148
- // Set the strength value.
149
- // If no value is provided as a parameter, this acts as a getter function.
150
- force.strength = (...v) => {
151
- if (v.length) {
152
- strength = +v[0];
153
- return force;
154
- }
155
- return strength;
156
- };
157
- // Set the size function.
158
- // The size function takes a node as a parameter and returns its size.
159
- // If no size function is provided as a parameter, this acts as a getter function.
160
- force.size = (...v) => {
161
- if (v.length) {
162
- size = (typeof v[0] === 'function' ? v[0] : constant(v[0]));
163
- return force;
164
- }
165
- return size;
166
- };
167
- // Returns the force closure.
168
- return force;
169
- }
@@ -1,58 +0,0 @@
1
- import { forceSimulation } from 'd3-force';
2
- import { forceCollideRects } from './force-collide-rects';
3
- describe('force-collide-rects.js', () => {
4
- describe('forceCollideRects', () => {
5
- it('can be initialized with a size function', () => {
6
- const collisionForce = forceCollideRects()
7
- .size(d => ([d.width, d.height]));
8
- const size = collisionForce.size();
9
- const [w, h] = size({ width: 2, height: 3 });
10
- expect(w).toEqual(2);
11
- expect(h).toEqual(3);
12
- });
13
- it('cannot prevent a collision of rects after few iterations', () => {
14
- const collisionForce = forceCollideRects()
15
- .size(d => ([d.width, d.height]));
16
- const nodes = [
17
- {
18
- label: 'A', width: 100, height: 100, x: 2, y: 2,
19
- },
20
- {
21
- label: 'B', width: 100, height: 100, x: 3, y: 3,
22
- },
23
- {
24
- label: 'C', width: 100, height: 100, x: 3, y: 2,
25
- },
26
- ];
27
- forceSimulation()
28
- .nodes(nodes)
29
- .force('collision', collisionForce)
30
- .tick(2);
31
- const collisionAB = (Math.abs(nodes[0].x - nodes[1].x) < 100
32
- && Math.abs(nodes[0].y - nodes[1].y) < 100);
33
- expect(collisionAB).toEqual(true);
34
- });
35
- it('can prevent a collision of rects after many iterations', () => {
36
- const collisionForce = forceCollideRects()
37
- .size(d => ([d.width, d.height]));
38
- const nodes = [
39
- {
40
- label: 'A', width: 100, height: 100, x: 2, y: 2,
41
- },
42
- {
43
- label: 'B', width: 100, height: 100, x: 3, y: 3,
44
- },
45
- {
46
- label: 'C', width: 100, height: 100, x: 3, y: 2,
47
- },
48
- ];
49
- forceSimulation()
50
- .nodes(nodes)
51
- .force('collision', collisionForce)
52
- .tick(50);
53
- const collisionAB = (Math.abs(nodes[0].x - nodes[1].x) < 100
54
- && Math.abs(nodes[0].y - nodes[1].y) < 100);
55
- expect(collisionAB).toEqual(false);
56
- });
57
- });
58
- });
@@ -1,26 +0,0 @@
1
- import { quadtree } from 'd3-quadtree';
2
- import range from 'lodash/range';
3
- /**
4
- * Create a d3-quadtree object for cells data points.
5
- * @param {array} cellsEntries Array of [cellId, cell] tuples,
6
- * resulting from running Object.entries on the cells object.
7
- * @param {function} getCellCoords Given a cell object, return the
8
- * spatial/scatterplot coordinates [x, y].
9
- * @returns {object} Quadtree instance.
10
- */
11
- export function createQuadTree(obsEmbedding, getCellCoords) {
12
- // Use the cellsEntries variable since it is already
13
- // an array, converted by Object.entries().
14
- // Only use cellsEntries in quadtree calculation if there is
15
- // centroid data in the cells (i.e not just ids).
16
- // eslint-disable-next-line no-unused-vars
17
- if (!obsEmbedding) {
18
- // Abort if the cells data is not yet available.
19
- return null;
20
- }
21
- const tree = quadtree()
22
- .x(i => getCellCoords(i)[0])
23
- .y(i => getCellCoords(i)[1])
24
- .addAll(range(obsEmbedding.shape[1]));
25
- return tree;
26
- }