@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.
@@ -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 negatively weighted values
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(dest, src, N, stride, b, p, a, q, sum, h) {
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 < q; ++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
- if (cur > 0) {
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;
@@ -1,57 +1,122 @@
1
- import { isArrowTable } from './is-arrow-table.js';
1
+ import { InternSet, ascending } from 'd3';
2
+ import { convertArrowColumn, convertArrowType, isArrowTable } from './arrow.js';
2
3
 
3
- export function grid1d(n, values) {
4
- return valuesToGrid(new Float64Array(n), values);
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 grid2d(m, n, values, groupby = []) {
8
- return groupby.length
9
- ? Object.values(groupedValuesToGrids(m * n, values, groupby))
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').toArray();
19
- const value = values.getChild('value').toArray();
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.value;
30
+ grid[row.index] = row[name];
27
31
  }
28
32
  }
29
33
  return grid;
30
34
  }
31
35
 
32
- function groupedValuesToGrids(size, values, groupby) {
33
- const grids = {};
34
- const getGrid = key => {
35
- const cell = grids[key] || (grids[key] = { key, grid: new Float64Array(size) });
36
- return cell.grid;
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
- if (isArrowTable(values)) {
39
- // optimize access for Arrow tables
40
- const numRows = values.numRows;
41
- if (numRows === 0) return grids;
42
- const index = values.getChild('index').toArray();
43
- const value = values.getChild('value').toArray();
44
- const groups = groupby.map(name => values.getChild(name));
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
- getGrid(key)[index[row]] = value[row];
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
- // fallback to iterable data
51
- for (const row of values) {
52
- const key = groupby.map(col => row[col]);
53
- getGrid(key)[row.index] = row.value;
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
- return grids;
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
+ }
@@ -1,44 +1,131 @@
1
1
  import { rgb } from 'd3';
2
2
 
3
- export function raster(grid, data, w, h, scale, scheme) {
4
- const n = (scheme.length >> 2) - 1;
5
- for (let j = 0, k = 0; j < h; ++j) {
6
- for (let i = 0, row = (h - j - 1) * w; i < w; ++i, k += 4) {
7
- const c = (n * scale(grid[i + row])) << 2;
8
- data[k + 0] = scheme[c + 0];
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 palette(size, interp) {
17
- const p = new Uint8ClampedArray(4 * size);
18
- const n = size - 1;
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 = interp(i / n);
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
- return p;
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 createCanvas(w, h) {
32
- if (typeof document !== 'undefined') {
33
- const c = document.createElement('canvas');
34
- c.setAttribute('width', w);
35
- c.setAttribute('height', h);
36
- return c;
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
- export function opacityMap(color = 'black') {
42
- const { r, g, b } = rgb(color);
43
- return opacity => ({ r, g, b, opacity });
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 './is-arrow-table.js';
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 = convert(type);
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
- }
@@ -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'],