@uwdata/mosaic-plot 0.5.0 → 0.6.1
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.
- package/dist/mosaic-plot.js +6667 -6136
- package/dist/mosaic-plot.min.js +14 -14
- package/package.json +4 -4
- package/src/index.js +1 -1
- package/src/marks/ConnectedMark.js +21 -22
- package/src/marks/ContourMark.js +16 -10
- package/src/marks/DenseLineMark.js +8 -13
- package/src/marks/Density1DMark.js +11 -13
- package/src/marks/Density2DMark.js +27 -25
- package/src/marks/Grid2DMark.js +105 -52
- package/src/marks/RasterMark.js +188 -64
- package/src/marks/RasterTileMark.js +54 -100
- package/src/marks/util/arrow.js +55 -0
- package/src/marks/util/bin-expr.js +30 -0
- package/src/marks/util/channel-scale.js +27 -0
- package/src/marks/util/density.js +26 -7
- package/src/marks/util/grid.js +94 -29
- package/src/marks/util/interpolate.js +196 -0
- package/src/marks/util/raster.js +113 -26
- package/src/marks/util/to-data-array.js +2 -22
- package/src/plot-attributes.js +18 -0
- package/src/plot.js +7 -2
- package/src/transforms/bin.js +18 -20
- package/src/marks/util/bin-field.js +0 -17
- package/src/marks/util/is-arrow-table.js +0 -3
|
@@ -96,7 +96,7 @@ export function dericheConv2d(cx, cy, grid, [nx, ny]) {
|
|
|
96
96
|
// allocate buffers
|
|
97
97
|
const yc = new Float64Array(Math.max(nx, ny)); // causal
|
|
98
98
|
const ya = new Float64Array(Math.max(nx, ny)); // anticausal
|
|
99
|
-
const h = new Float64Array(5);
|
|
99
|
+
const h = new Float64Array(5); // q + 1
|
|
100
100
|
const d = new Float64Array(grid.length);
|
|
101
101
|
|
|
102
102
|
// convolve rows
|
|
@@ -119,7 +119,7 @@ export function dericheConv1d(
|
|
|
119
119
|
stride = 1,
|
|
120
120
|
y_causal = new Float64Array(N),
|
|
121
121
|
y_anticausal = new Float64Array(N),
|
|
122
|
-
h = new Float64Array(5),
|
|
122
|
+
h = new Float64Array(5), // q + 1
|
|
123
123
|
d = y_causal,
|
|
124
124
|
init = dericheInitZeroPad
|
|
125
125
|
) {
|
|
@@ -153,6 +153,7 @@ export function dericheConv1d(
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
// initialize the anticausal filter on the right boundary
|
|
156
|
+
// dest, src, N, stride, b, p, a, q, sum, h
|
|
156
157
|
init(
|
|
157
158
|
y_anticausal, src, N, -stride,
|
|
158
159
|
c.b_anticausal, 4, c.a, 4, c.sum_anticausal, h, c.sigma
|
|
@@ -176,7 +177,7 @@ export function dericheConv1d(
|
|
|
176
177
|
|
|
177
178
|
// sum the causal and anticausal responses to obtain the final result
|
|
178
179
|
if (c.negative) {
|
|
179
|
-
// do not threshold if the input grid includes
|
|
180
|
+
// do not threshold if the input grid includes negative values
|
|
180
181
|
for (n = 0, i = 0; n < N; ++n, i += stride) {
|
|
181
182
|
d[i] = y_causal[n] + y_anticausal[N - n - 1];
|
|
182
183
|
}
|
|
@@ -190,13 +191,16 @@ export function dericheConv1d(
|
|
|
190
191
|
return d;
|
|
191
192
|
}
|
|
192
193
|
|
|
193
|
-
export function dericheInitZeroPad(
|
|
194
|
+
export function dericheInitZeroPad(
|
|
195
|
+
dest, src, N, stride, b, p, a, q,
|
|
196
|
+
sum, h, sigma, tol = 0.5
|
|
197
|
+
) {
|
|
194
198
|
const stride_N = Math.abs(stride) * N;
|
|
195
199
|
const off = stride < 0 ? stride_N + stride : 0;
|
|
196
200
|
let i, n, m;
|
|
197
201
|
|
|
198
202
|
// compute the first q taps of the impulse response, h_0, ..., h_{q-1}
|
|
199
|
-
for (n = 0; n
|
|
203
|
+
for (n = 0; n <= q; ++n) {
|
|
200
204
|
h[n] = (n <= p) ? b[n] : 0;
|
|
201
205
|
for (m = 1; m <= q && m <= n; ++m) {
|
|
202
206
|
h[n] -= a[m] * h[n - m];
|
|
@@ -214,12 +218,27 @@ export function dericheInitZeroPad(dest, src, N, stride, b, p, a, q, sum, h) {
|
|
|
214
218
|
}
|
|
215
219
|
}
|
|
216
220
|
|
|
217
|
-
// dest_m = dest_m + h_{n+m} src_{-n}
|
|
218
221
|
const cur = src[off];
|
|
219
|
-
|
|
222
|
+
const max_iter = Math.ceil(sigma * 10);
|
|
223
|
+
for (n = 0; n < max_iter; ++n) {
|
|
224
|
+
/* dest_m = dest_m + h_{n+m} src_{-n} */
|
|
220
225
|
for (m = 0; m < q; ++m) {
|
|
221
226
|
dest[m] += h[m] * cur;
|
|
222
227
|
}
|
|
228
|
+
|
|
229
|
+
sum -= Math.abs(h[0]);
|
|
230
|
+
if (sum <= tol) break;
|
|
231
|
+
|
|
232
|
+
/* Compute the next impulse response tap, h_{n+q} */
|
|
233
|
+
h[q] = (n + q <= p) ? b[n + q] : 0;
|
|
234
|
+
for (m = 1; m <= q; ++m) {
|
|
235
|
+
h[q] -= a[m] * h[q - m];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Shift the h array for the next iteration */
|
|
239
|
+
for (m = 0; m < q; ++m) {
|
|
240
|
+
h[m] = h[m + 1];
|
|
241
|
+
}
|
|
223
242
|
}
|
|
224
243
|
|
|
225
244
|
return;
|
package/src/marks/util/grid.js
CHANGED
|
@@ -1,57 +1,122 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InternSet, ascending } from 'd3';
|
|
2
|
+
import { convertArrowColumn, convertArrowType, isArrowTable } from './arrow.js';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
function arrayType(values, name = 'density') {
|
|
5
|
+
if (isArrowTable(values)) {
|
|
6
|
+
return convertArrowType(values.getChild(name).type);
|
|
7
|
+
} else {
|
|
8
|
+
return typeof values[0][name] === 'number' ? Float64Array : Array;
|
|
9
|
+
}
|
|
5
10
|
}
|
|
6
11
|
|
|
7
|
-
export function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
: [{ grid: valuesToGrid(new Float64Array(m * n), values) }];
|
|
12
|
+
export function grid1d(n, values) {
|
|
13
|
+
const Type = arrayType(values);
|
|
14
|
+
return valuesToGrid(new Type(n), values);
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
function valuesToGrid(grid, values) {
|
|
17
|
+
function valuesToGrid(grid, values, name = 'density') {
|
|
14
18
|
if (isArrowTable(values)) {
|
|
15
19
|
// optimize access for Arrow tables
|
|
16
20
|
const numRows = values.numRows;
|
|
17
21
|
if (numRows === 0) return grid;
|
|
18
|
-
const index = values.getChild('index')
|
|
19
|
-
const value = values.getChild(
|
|
22
|
+
const index = convertArrowColumn(values.getChild('index'));
|
|
23
|
+
const value = convertArrowColumn(values.getChild(name));
|
|
20
24
|
for (let row = 0; row < numRows; ++row) {
|
|
21
25
|
grid[index[row]] = value[row];
|
|
22
26
|
}
|
|
23
27
|
} else {
|
|
24
28
|
// fallback to iterable data
|
|
25
29
|
for (const row of values) {
|
|
26
|
-
grid[row.index] = row
|
|
30
|
+
grid[row.index] = row[name];
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
return grid;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
function
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
export function grid2d(w, h, values, aggr, groupby = [], interpolate) {
|
|
37
|
+
const size = w * h;
|
|
38
|
+
const Types = aggr.map(name => arrayType(values, name));
|
|
39
|
+
const numAggr = aggr.length;
|
|
40
|
+
|
|
41
|
+
// grid data tuples
|
|
42
|
+
const createCell = (key) => {
|
|
43
|
+
const cell = {};
|
|
44
|
+
groupby.forEach((name, i) => cell[name] = key[i]);
|
|
45
|
+
aggr.forEach((name, i) => cell[name] = new Types[i](size));
|
|
46
|
+
return cell;
|
|
37
47
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
const cellMap = {};
|
|
49
|
+
const baseCell = groupby.length ? null : (cellMap[[]] = createCell([]));
|
|
50
|
+
const getCell = groupby.length
|
|
51
|
+
? key => cellMap[key] ?? (cellMap[key] = createCell(key))
|
|
52
|
+
: () => baseCell;
|
|
53
|
+
|
|
54
|
+
// early exit if empty query result
|
|
55
|
+
const numRows = values.numRows;
|
|
56
|
+
if (numRows === 0) return Object.values(cellMap);
|
|
57
|
+
|
|
58
|
+
// extract arrays from arrow table
|
|
59
|
+
const index = convertArrowColumn(values.getChild('index'));
|
|
60
|
+
const value = aggr.map(name => convertArrowColumn(values.getChild(name)));
|
|
61
|
+
const groups = groupby.map(name => values.getChild(name));
|
|
62
|
+
|
|
63
|
+
if (!interpolate) {
|
|
64
|
+
// if no interpolation, copy values over
|
|
45
65
|
for (let row = 0; row < numRows; ++row) {
|
|
46
66
|
const key = groups.map(vec => vec.get(row));
|
|
47
|
-
|
|
67
|
+
const cell = getCell(key);
|
|
68
|
+
for (let i = 0; i < numAggr; ++i) {
|
|
69
|
+
cell[aggr[i]][index[row]] = value[i][row];
|
|
70
|
+
}
|
|
48
71
|
}
|
|
49
72
|
} else {
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
73
|
+
// prepare index arrays, then interpolate grid values
|
|
74
|
+
const X = index.map(k => k % w);
|
|
75
|
+
const Y = index.map(k => Math.floor(k / w));
|
|
76
|
+
if (groupby.length) {
|
|
77
|
+
for (let row = 0; row < numRows; ++row) {
|
|
78
|
+
const key = groups.map(vec => vec.get(row));
|
|
79
|
+
const cell = getCell(key);
|
|
80
|
+
if (!cell.index) { cell.index = []; }
|
|
81
|
+
cell.index.push(row);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
baseCell.index = index.map((_, i) => i);
|
|
54
85
|
}
|
|
86
|
+
Object.values(cellMap).forEach(cell => {
|
|
87
|
+
for (let i = 0; i < numAggr; ++i) {
|
|
88
|
+
interpolate(cell.index, w, h, X, Y, value[i], cell[aggr[i]]);
|
|
89
|
+
}
|
|
90
|
+
delete cell.index;
|
|
91
|
+
})
|
|
55
92
|
}
|
|
56
|
-
|
|
93
|
+
|
|
94
|
+
return Object.values(cellMap);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function gridDomainContinuous(grids, prop) {
|
|
98
|
+
let lo = 0, hi = 0;
|
|
99
|
+
grids.forEach(cell => {
|
|
100
|
+
const grid = cell[prop];
|
|
101
|
+
const n = grid.length;
|
|
102
|
+
for (let i = 0; i < n; ++i) {
|
|
103
|
+
const v = grid[i];
|
|
104
|
+
if (v < lo) lo = v;
|
|
105
|
+
if (v > hi) hi = v;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return (lo === 0 && hi === 0) ? [0, 1] : [lo, hi];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function gridDomainDiscrete(grids, prop) {
|
|
112
|
+
// TODO: sort options?
|
|
113
|
+
const values = new InternSet();
|
|
114
|
+
grids.forEach(cell => {
|
|
115
|
+
const grid = cell[prop];
|
|
116
|
+
const n = grid.length;
|
|
117
|
+
for (let i = 0; i < n; ++i) {
|
|
118
|
+
values.add(grid[i]);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return Array.from(values).sort(ascending);
|
|
57
122
|
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { Delaunay, randomLcg } from 'd3';
|
|
2
|
+
|
|
3
|
+
export function interpolatorBarycentric({random = randomLcg(42)} = {}) {
|
|
4
|
+
return (index, width, height, X, Y, V, W) => {
|
|
5
|
+
// Interpolate the interior of all triangles with barycentric coordinates
|
|
6
|
+
const {points, triangles, hull} = Delaunay.from(index, (i) => X[i], (i) => Y[i]);
|
|
7
|
+
const S = new Uint8Array(width * height); // 1 if pixel has been seen.
|
|
8
|
+
const mix = mixer(V, random);
|
|
9
|
+
|
|
10
|
+
for (let i = 0; i < triangles.length; i += 3) {
|
|
11
|
+
const ta = triangles[i];
|
|
12
|
+
const tb = triangles[i + 1];
|
|
13
|
+
const tc = triangles[i + 2];
|
|
14
|
+
const Ax = points[2 * ta];
|
|
15
|
+
const Bx = points[2 * tb];
|
|
16
|
+
const Cx = points[2 * tc];
|
|
17
|
+
const Ay = points[2 * ta + 1];
|
|
18
|
+
const By = points[2 * tb + 1];
|
|
19
|
+
const Cy = points[2 * tc + 1];
|
|
20
|
+
const x1 = Math.min(Ax, Bx, Cx);
|
|
21
|
+
const x2 = Math.max(Ax, Bx, Cx);
|
|
22
|
+
const y1 = Math.min(Ay, By, Cy);
|
|
23
|
+
const y2 = Math.max(Ay, By, Cy);
|
|
24
|
+
const z = (By - Cy) * (Ax - Cx) + (Ay - Cy) * (Cx - Bx);
|
|
25
|
+
if (!z) continue;
|
|
26
|
+
const va = V[index[ta]];
|
|
27
|
+
const vb = V[index[tb]];
|
|
28
|
+
const vc = V[index[tc]];
|
|
29
|
+
for (let x = Math.floor(x1); x < x2; ++x) {
|
|
30
|
+
for (let y = Math.floor(y1); y < y2; ++y) {
|
|
31
|
+
if (x < 0 || x >= width || y < 0 || y >= height) continue;
|
|
32
|
+
const xp = x + 0.5; // sample pixel centroids
|
|
33
|
+
const yp = y + 0.5;
|
|
34
|
+
const ga = ((By - Cy) * (xp - Cx) + (yp - Cy) * (Cx - Bx)) / z;
|
|
35
|
+
if (ga < 0) continue;
|
|
36
|
+
const gb = ((Cy - Ay) * (xp - Cx) + (yp - Cy) * (Ax - Cx)) / z;
|
|
37
|
+
if (gb < 0) continue;
|
|
38
|
+
const gc = 1 - ga - gb;
|
|
39
|
+
if (gc < 0) continue;
|
|
40
|
+
const i = x + width * y;
|
|
41
|
+
W[i] = mix(va, ga, vb, gb, vc, gc, x, y);
|
|
42
|
+
S[i] = 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
extrapolateBarycentric(W, S, X, Y, V, width, height, hull, index, mix);
|
|
47
|
+
return W;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Extrapolate by finding the closest point on the hull.
|
|
52
|
+
function extrapolateBarycentric(W, S, X, Y, V, width, height, hull, index, mix) {
|
|
53
|
+
X = Float64Array.from(hull, (i) => X[index[i]]);
|
|
54
|
+
Y = Float64Array.from(hull, (i) => Y[index[i]]);
|
|
55
|
+
V = Array.from(hull, (i) => V[index[i]]);
|
|
56
|
+
const n = X.length;
|
|
57
|
+
const rays = Array.from({length: n}, (_, j) => ray(j, X, Y));
|
|
58
|
+
let k = 0;
|
|
59
|
+
for (let y = 0; y < height; ++y) {
|
|
60
|
+
const yp = y + 0.5;
|
|
61
|
+
for (let x = 0; x < width; ++x) {
|
|
62
|
+
const i = x + width * y;
|
|
63
|
+
if (!S[i]) {
|
|
64
|
+
const xp = x + 0.5;
|
|
65
|
+
for (let l = 0; l < n; ++l) {
|
|
66
|
+
const j = (n + k + (l % 2 ? (l + 1) / 2 : -l / 2)) % n;
|
|
67
|
+
if (rays[j](xp, yp)) {
|
|
68
|
+
const t = segmentProject(X.at(j - 1), Y.at(j - 1), X[j], Y[j], xp, yp);
|
|
69
|
+
W[i] = mix(V.at(j - 1), t, V[j], 1 - t, V[j], 0, x, y);
|
|
70
|
+
k = j;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Projects a point p = [x, y] onto the line segment [p1, p2], returning the
|
|
80
|
+
// projected coordinates p’ as t in [0, 1] with p’ = t p1 + (1 - t) p2.
|
|
81
|
+
function segmentProject(x1, y1, x2, y2, x, y) {
|
|
82
|
+
const dx = x2 - x1;
|
|
83
|
+
const dy = y2 - y1;
|
|
84
|
+
const a = dx * (x2 - x) + dy * (y2 - y);
|
|
85
|
+
const b = dx * (x - x1) + dy * (y - y1);
|
|
86
|
+
return a > 0 && b > 0 ? a / (a + b) : +(a > b);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function cross(xa, ya, xb, yb) {
|
|
90
|
+
return xa * yb - xb * ya;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function ray(j, X, Y) {
|
|
94
|
+
const n = X.length;
|
|
95
|
+
const xc = X.at(j - 2);
|
|
96
|
+
const yc = Y.at(j - 2);
|
|
97
|
+
const xa = X.at(j - 1);
|
|
98
|
+
const ya = Y.at(j - 1);
|
|
99
|
+
const xb = X[j];
|
|
100
|
+
const yb = Y[j];
|
|
101
|
+
const xd = X.at(j + 1 - n);
|
|
102
|
+
const yd = Y.at(j + 1 - n);
|
|
103
|
+
const dxab = xa - xb;
|
|
104
|
+
const dyab = ya - yb;
|
|
105
|
+
const dxca = xc - xa;
|
|
106
|
+
const dyca = yc - ya;
|
|
107
|
+
const dxbd = xb - xd;
|
|
108
|
+
const dybd = yb - yd;
|
|
109
|
+
const hab = Math.hypot(dxab, dyab);
|
|
110
|
+
const hca = Math.hypot(dxca, dyca);
|
|
111
|
+
const hbd = Math.hypot(dxbd, dybd);
|
|
112
|
+
return (x, y) => {
|
|
113
|
+
const dxa = x - xa;
|
|
114
|
+
const dya = y - ya;
|
|
115
|
+
const dxb = x - xb;
|
|
116
|
+
const dyb = y - yb;
|
|
117
|
+
return (
|
|
118
|
+
cross(dxa, dya, dxb, dyb) > -1e-6 &&
|
|
119
|
+
cross(dxa, dya, dxab, dyab) * hca - cross(dxa, dya, dxca, dyca) * hab > -1e-6 &&
|
|
120
|
+
cross(dxb, dyb, dxbd, dybd) * hab - cross(dxb, dyb, dxab, dyab) * hbd <= 0
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function interpolateNearest(index, width, height, X, Y, V, W) {
|
|
126
|
+
const delaunay = Delaunay.from(index, (i) => X[i], (i) => Y[i]);
|
|
127
|
+
// memoization of delaunay.find for the line start (iy) and pixel (ix)
|
|
128
|
+
let iy, ix;
|
|
129
|
+
for (let y = 0.5, k = 0; y < height; ++y) {
|
|
130
|
+
ix = iy;
|
|
131
|
+
for (let x = 0.5; x < width; ++x, ++k) {
|
|
132
|
+
ix = delaunay.find(x, y, ix);
|
|
133
|
+
if (x === 0.5) iy = ix;
|
|
134
|
+
W[k] = V[index[ix]];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return W;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// https://observablehq.com/@observablehq/walk-on-spheres-precision
|
|
141
|
+
export function interpolatorRandomWalk({random = randomLcg(42), minDistance = 0.5, maxSteps = 2} = {}) {
|
|
142
|
+
return (index, width, height, X, Y, V, W) => {
|
|
143
|
+
const delaunay = Delaunay.from(index, (i) => X[i], (i) => Y[i]);
|
|
144
|
+
// memoization of delaunay.find for the line start (iy), pixel (ix), and wos step (iw)
|
|
145
|
+
let iy, ix, iw;
|
|
146
|
+
for (let y = 0.5, k = 0; y < height; ++y) {
|
|
147
|
+
ix = iy;
|
|
148
|
+
for (let x = 0.5; x < width; ++x, ++k) {
|
|
149
|
+
let cx = x;
|
|
150
|
+
let cy = y;
|
|
151
|
+
iw = ix = delaunay.find(cx, cy, ix);
|
|
152
|
+
if (x === 0.5) iy = ix;
|
|
153
|
+
let distance; // distance to closest sample
|
|
154
|
+
let step = 0; // count of steps for this walk
|
|
155
|
+
while ((distance = Math.hypot(X[index[iw]] - cx, Y[index[iw]] - cy)) > minDistance && step < maxSteps) {
|
|
156
|
+
const angle = random(x, y, step) * 2 * Math.PI;
|
|
157
|
+
cx += Math.cos(angle) * distance;
|
|
158
|
+
cy += Math.sin(angle) * distance;
|
|
159
|
+
iw = delaunay.find(cx, cy, iw);
|
|
160
|
+
++step;
|
|
161
|
+
}
|
|
162
|
+
W[k] = V[index[iw]];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return W;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function blend(a, ca, b, cb, c, cc) {
|
|
170
|
+
return ca * a + cb * b + cc * c;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function pick(random) {
|
|
174
|
+
return (a, ca, b, cb, c, cc, x, y) => {
|
|
175
|
+
const u = random(x, y);
|
|
176
|
+
return u < ca ? a : u < ca + cb ? b : c;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function mixer(F, random) {
|
|
181
|
+
return isNumeric(F) || isTemporal(F) ? blend : pick(random);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isNumeric(values) {
|
|
185
|
+
for (const value of values) {
|
|
186
|
+
if (value == null) continue;
|
|
187
|
+
return typeof value === "number";
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function isTemporal(values) {
|
|
192
|
+
for (const value of values) {
|
|
193
|
+
if (value == null) continue;
|
|
194
|
+
return value instanceof Date;
|
|
195
|
+
}
|
|
196
|
+
}
|
package/src/marks/util/raster.js
CHANGED
|
@@ -1,44 +1,131 @@
|
|
|
1
1
|
import { rgb } from 'd3';
|
|
2
2
|
|
|
3
|
-
export function
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
data[k + 1] = scheme[c + 1];
|
|
10
|
-
data[k + 2] = scheme[c + 2];
|
|
11
|
-
data[k + 3] = scheme[c + 3];
|
|
12
|
-
}
|
|
3
|
+
export function createCanvas(w, h) {
|
|
4
|
+
if (typeof document !== 'undefined') {
|
|
5
|
+
const c = document.createElement('canvas');
|
|
6
|
+
c.setAttribute('width', w);
|
|
7
|
+
c.setAttribute('height', h);
|
|
8
|
+
return c;
|
|
13
9
|
}
|
|
10
|
+
throw new Error('Can not create a canvas instance.');
|
|
14
11
|
}
|
|
15
12
|
|
|
16
|
-
export function
|
|
17
|
-
const
|
|
18
|
-
|
|
13
|
+
export function alphaConstant(v = 1) {
|
|
14
|
+
const a = (255 * v) | 0;
|
|
15
|
+
|
|
16
|
+
// rasterize
|
|
17
|
+
return (data, w, h) => {
|
|
18
|
+
for (let j = 0, k = 0; j < h; ++j) {
|
|
19
|
+
for (let i = 0; i < w; ++i, k += 4) {
|
|
20
|
+
data[k + 3] = a;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function alphaScheme(scale) {
|
|
27
|
+
const { apply } = scale;
|
|
28
|
+
|
|
29
|
+
// rasterize
|
|
30
|
+
return (data, w, h, grid) => {
|
|
31
|
+
for (let j = 0, k = 0; j < h; ++j) {
|
|
32
|
+
for (let i = 0, row = (h - j - 1) * w; i < w; ++i, k += 4) {
|
|
33
|
+
data[k + 3] = (255 * apply(grid[i + row])) | 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function colorConstant(v = {}) {
|
|
40
|
+
const { r = 0, g = 0, b = 0, opacity = 1 } = typeof v === 'string' ? rgb(v) : v;
|
|
41
|
+
const c = new Uint8ClampedArray([r, g, b, (255 * opacity) | 0]);
|
|
42
|
+
|
|
43
|
+
// rasterize
|
|
44
|
+
return (data, w, h) => {
|
|
45
|
+
for (let j = 0, k = 0; j < h; ++j) {
|
|
46
|
+
for (let i = 0; i < w; ++i, k += 4) {
|
|
47
|
+
data[k + 0] = c[0];
|
|
48
|
+
data[k + 1] = c[1];
|
|
49
|
+
data[k + 2] = c[2];
|
|
50
|
+
data[k + 3] = c[3];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function colorCategory(scale) {
|
|
57
|
+
const { domain, range } = scale;
|
|
58
|
+
const idx = Object.create(null);
|
|
59
|
+
const p = new Uint8ClampedArray(4 * domain.length);
|
|
60
|
+
const n = domain.length - 1;
|
|
61
|
+
const m = range.length;
|
|
62
|
+
|
|
63
|
+
// build palette and index lookup map
|
|
19
64
|
for (let i = 0; i <= n; ++i) {
|
|
20
|
-
const v =
|
|
65
|
+
const v = range[i % m];
|
|
21
66
|
const { r, g, b, opacity = 1 } = typeof v === 'string' ? rgb(v) : v;
|
|
22
67
|
const k = i << 2;
|
|
23
68
|
p[k + 0] = r;
|
|
24
69
|
p[k + 1] = g;
|
|
25
70
|
p[k + 2] = b;
|
|
26
71
|
p[k + 3] = (255 * opacity) | 0;
|
|
72
|
+
idx[domain[i]] = k;
|
|
27
73
|
}
|
|
28
|
-
|
|
74
|
+
|
|
75
|
+
// rasterize
|
|
76
|
+
return (data, w, h, grid) => {
|
|
77
|
+
if (grid.map) {
|
|
78
|
+
// categorical values in grid
|
|
79
|
+
for (let j = 0, k = 0; j < h; ++j) {
|
|
80
|
+
for (let i = 0, row = (h - j - 1) * w; i < w; ++i, k += 4) {
|
|
81
|
+
const c = idx[grid[i + row]];
|
|
82
|
+
data[k + 0] = p[c + 0];
|
|
83
|
+
data[k + 1] = p[c + 1];
|
|
84
|
+
data[k + 2] = p[c + 2];
|
|
85
|
+
data[k + 3] = p[c + 3];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
// categorical value for group
|
|
90
|
+
const c = idx[grid];
|
|
91
|
+
for (let j = 0, k = 0; j < h; ++j) {
|
|
92
|
+
for (let i = 0; i < w; ++i, k += 4) {
|
|
93
|
+
data[k + 0] = p[c + 0];
|
|
94
|
+
data[k + 1] = p[c + 1];
|
|
95
|
+
data[k + 2] = p[c + 2];
|
|
96
|
+
data[k + 3] = p[c + 3];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
29
101
|
}
|
|
30
102
|
|
|
31
|
-
export function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
103
|
+
export function colorScheme(size, scale, frac) {
|
|
104
|
+
const { interpolate } = scale;
|
|
105
|
+
const p = new Uint8ClampedArray(4 * size);
|
|
106
|
+
const n = size - 1;
|
|
107
|
+
|
|
108
|
+
// build palette
|
|
109
|
+
for (let i = 0; i <= n; ++i) {
|
|
110
|
+
const v = interpolate(i / n);
|
|
111
|
+
const { r, g, b, opacity = 1 } = typeof v === 'string' ? rgb(v) : v;
|
|
112
|
+
const k = i << 2;
|
|
113
|
+
p[k + 0] = r;
|
|
114
|
+
p[k + 1] = g;
|
|
115
|
+
p[k + 2] = b;
|
|
116
|
+
p[k + 3] = (255 * opacity) | 0;
|
|
37
117
|
}
|
|
38
|
-
throw new Error('Can not create a canvas instance.');
|
|
39
|
-
}
|
|
40
118
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
119
|
+
// rasterize
|
|
120
|
+
return (data, w, h, grid) => {
|
|
121
|
+
for (let j = 0, k = 0; j < h; ++j) {
|
|
122
|
+
for (let i = 0, row = (h - j - 1) * w; i < w; ++i, k += 4) {
|
|
123
|
+
const c = (n * frac(grid[i + row])) << 2;
|
|
124
|
+
data[k + 0] = p[c + 0];
|
|
125
|
+
data[k + 1] = p[c + 1];
|
|
126
|
+
data[k + 2] = p[c + 2];
|
|
127
|
+
data[k + 3] = p[c + 3];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
44
131
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import { isArrowTable } from './
|
|
2
|
-
|
|
3
|
-
const INTEGER = 2;
|
|
4
|
-
const TIMESTAMP = 10;
|
|
1
|
+
import { convertArrow, isArrowTable } from './arrow.js';
|
|
5
2
|
|
|
6
3
|
export function toDataArray(data) {
|
|
7
4
|
return isArrowTable(data) ? arrowToObjects(data) : data;
|
|
@@ -37,7 +34,7 @@ export function arrowToObjects(data) {
|
|
|
37
34
|
for (let j = 0; j < numCols; ++j) {
|
|
38
35
|
const child = batch.getChildAt(j);
|
|
39
36
|
const { name, type } = schema.fields[j];
|
|
40
|
-
const valueOf =
|
|
37
|
+
const valueOf = convertArrow(type);
|
|
41
38
|
|
|
42
39
|
// for each row in the current batch...
|
|
43
40
|
for (let o = k, i = 0; i < numRows; ++i, ++o) {
|
|
@@ -51,20 +48,3 @@ export function arrowToObjects(data) {
|
|
|
51
48
|
|
|
52
49
|
return objects;
|
|
53
50
|
}
|
|
54
|
-
|
|
55
|
-
function convert(type) {
|
|
56
|
-
const { typeId } = type;
|
|
57
|
-
|
|
58
|
-
// map timestamp numbers to date objects
|
|
59
|
-
if (typeId === TIMESTAMP) {
|
|
60
|
-
return v => v == null ? v : new Date(v);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// map bignum to number
|
|
64
|
-
if (typeId === INTEGER && type.bitWidth >= 64) {
|
|
65
|
-
return v => v == null ? v : Number(v);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// otherwise use Arrow JS defaults
|
|
69
|
-
return v => v;
|
|
70
|
-
}
|
package/src/plot-attributes.js
CHANGED
|
@@ -45,6 +45,9 @@ export const attributeMap = new Map([
|
|
|
45
45
|
['xAriaDescription', 'x.ariaDescription'],
|
|
46
46
|
['xReverse', 'x.reverse'],
|
|
47
47
|
['xZero', 'x.zero'],
|
|
48
|
+
['xBase', 'x.base'],
|
|
49
|
+
['xExponent', 'x.exponent'],
|
|
50
|
+
['xConstant', 'x.constant'],
|
|
48
51
|
['yScale', 'y.type'],
|
|
49
52
|
['yDomain', 'y.domain'],
|
|
50
53
|
['yRange', 'y.range'],
|
|
@@ -75,6 +78,9 @@ export const attributeMap = new Map([
|
|
|
75
78
|
['yAriaDescription', 'y.ariaDescription'],
|
|
76
79
|
['yReverse', 'y.reverse'],
|
|
77
80
|
['yZero', 'y.zero'],
|
|
81
|
+
['yBase', 'y.base'],
|
|
82
|
+
['yExponent', 'y.exponent'],
|
|
83
|
+
['yConstant', 'y.constant'],
|
|
78
84
|
['facetMargin', 'facet.margin'],
|
|
79
85
|
['facetMarginTop', 'facet.marginTop'],
|
|
80
86
|
['facetMarginBottom', 'facet.marginBottom'],
|
|
@@ -150,6 +156,9 @@ export const attributeMap = new Map([
|
|
|
150
156
|
['colorReverse', 'color.reverse'],
|
|
151
157
|
['colorZero', 'color.zero'],
|
|
152
158
|
['colorTickFormat', 'color.tickFormat'],
|
|
159
|
+
['colorBase', 'color.base'],
|
|
160
|
+
['colorExponent', 'color.exponent'],
|
|
161
|
+
['colorConstant', 'color.constant'],
|
|
153
162
|
['opacityScale', 'opacity.type'],
|
|
154
163
|
['opacityDomain', 'opacity.domain'],
|
|
155
164
|
['opacityRange', 'opacity.range'],
|
|
@@ -159,18 +168,27 @@ export const attributeMap = new Map([
|
|
|
159
168
|
['opacityReverse', 'opacity.reverse'],
|
|
160
169
|
['opacityZero', 'opacity.zero'],
|
|
161
170
|
['opacityTickFormat', 'opacity.tickFormat'],
|
|
171
|
+
['opacityBase', 'opacity.base'],
|
|
172
|
+
['opacityExponent', 'opacity.exponent'],
|
|
173
|
+
['opacityConstant', 'opacity.constant'],
|
|
162
174
|
['rScale', 'r.type'],
|
|
163
175
|
['rDomain', 'r.domain'],
|
|
164
176
|
['rRange', 'r.range'],
|
|
165
177
|
['rClamp', 'r.clamp'],
|
|
166
178
|
['rNice', 'r.nice'],
|
|
167
179
|
['rZero', 'r.zero'],
|
|
180
|
+
['rBase', 'r.base'],
|
|
181
|
+
['rExponent', 'r.exponent'],
|
|
182
|
+
['rConstant', 'r.constant'],
|
|
168
183
|
['lengthScale', 'length.type'],
|
|
169
184
|
['lengthDomain', 'length.domain'],
|
|
170
185
|
['lengthRange', 'length.range'],
|
|
171
186
|
['lengthClamp', 'length.clamp'],
|
|
172
187
|
['lengthNice', 'length.nice'],
|
|
173
188
|
['lengthZero', 'length.zero'],
|
|
189
|
+
['lengthBase', 'length.base'],
|
|
190
|
+
['lengthExponent', 'length.exponent'],
|
|
191
|
+
['lengthConstant', 'length.constant'],
|
|
174
192
|
['projectionType', 'projection.type'],
|
|
175
193
|
['projectionParallels', 'projection.parallels'],
|
|
176
194
|
['projectionPrecision', 'projection.precision'],
|