@uwdata/mosaic-plot 0.7.1 → 0.8.0
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 +2723 -4098
- package/dist/mosaic-plot.min.js +14 -14
- package/package.json +5 -5
- package/src/interactors/Interval1D.js +6 -4
- package/src/interactors/Interval2D.js +3 -1
- package/src/interactors/Nearest.js +11 -10
- package/src/interactors/PanZoom.js +3 -2
- package/src/interactors/Toggle.js +12 -11
- package/src/interactors/util/patchScreenCTM.js +2 -0
- package/src/legend.js +135 -27
- package/src/marks/ConnectedMark.js +6 -0
- package/src/marks/ContourMark.js +36 -16
- package/src/marks/DenseLineMark.js +9 -5
- package/src/marks/Density1DMark.js +22 -13
- package/src/marks/Density2DMark.js +33 -18
- package/src/marks/GeoMark.js +7 -8
- package/src/marks/Grid2DMark.js +58 -28
- package/src/marks/HexbinMark.js +10 -2
- package/src/marks/Mark.js +43 -11
- package/src/marks/RasterMark.js +55 -23
- package/src/marks/RasterTileMark.js +33 -20
- package/src/marks/RegressionMark.js +69 -34
- package/src/marks/util/grid.js +94 -86
- package/src/marks/util/handle-param.js +10 -11
- package/src/marks/util/stats.js +33 -1
- package/src/marks/util/to-data-columns.js +71 -0
- package/src/plot-attributes.js +11 -3
- package/src/plot-renderer.js +24 -4
- package/src/plot.js +20 -0
- package/src/marks/util/interpolate.js +0 -205
- package/src/marks/util/to-data-array.js +0 -50
package/src/plot-attributes.js
CHANGED
|
@@ -14,7 +14,6 @@ export const attributeMap = new Map([
|
|
|
14
14
|
['grid', 'grid'],
|
|
15
15
|
['label', 'label'],
|
|
16
16
|
['padding', 'padding'],
|
|
17
|
-
['round', 'round'],
|
|
18
17
|
['xScale', 'x.type'],
|
|
19
18
|
['xDomain', 'x.domain'],
|
|
20
19
|
['xRange', 'x.range'],
|
|
@@ -39,10 +38,12 @@ export const attributeMap = new Map([
|
|
|
39
38
|
['xLine', 'x.line'],
|
|
40
39
|
['xLabel', 'x.label'],
|
|
41
40
|
['xLabelAnchor', 'x.labelAnchor'],
|
|
41
|
+
['xLabelArrow', 'x.labelArrow'],
|
|
42
42
|
['xLabelOffset', 'x.labelOffset'],
|
|
43
43
|
['xFontVariant', 'x.fontVariant'],
|
|
44
44
|
['xAriaLabel', 'x.ariaLabel'],
|
|
45
45
|
['xAriaDescription', 'x.ariaDescription'],
|
|
46
|
+
['xPercent', 'x.percent'],
|
|
46
47
|
['xReverse', 'x.reverse'],
|
|
47
48
|
['xZero', 'x.zero'],
|
|
48
49
|
['xBase', 'x.base'],
|
|
@@ -72,10 +73,12 @@ export const attributeMap = new Map([
|
|
|
72
73
|
['yLine', 'y.line'],
|
|
73
74
|
['yLabel', 'y.label'],
|
|
74
75
|
['yLabelAnchor', 'y.labelAnchor'],
|
|
76
|
+
['yLabelArrow', 'y.labelArrow'],
|
|
75
77
|
['yLabelOffset', 'y.labelOffset'],
|
|
76
78
|
['yFontVariant', 'y.fontVariant'],
|
|
77
79
|
['yAriaLabel', 'y.ariaLabel'],
|
|
78
80
|
['yAriaDescription', 'y.ariaDescription'],
|
|
81
|
+
['yPercent', 'y.percent'],
|
|
79
82
|
['yReverse', 'y.reverse'],
|
|
80
83
|
['yZero', 'y.zero'],
|
|
81
84
|
['yBase', 'y.base'],
|
|
@@ -90,7 +93,6 @@ export const attributeMap = new Map([
|
|
|
90
93
|
['facetLabel', 'facet.label'],
|
|
91
94
|
['fxDomain', 'fx.domain'],
|
|
92
95
|
['fxRange', 'fx.range'],
|
|
93
|
-
['fxNice', 'fx.nice'],
|
|
94
96
|
['fxInset', 'fx.inset'],
|
|
95
97
|
['fxInsetLeft', 'fx.insetLeft'],
|
|
96
98
|
['fxInsetRight', 'fx.insetRight'],
|
|
@@ -117,7 +119,6 @@ export const attributeMap = new Map([
|
|
|
117
119
|
['fxReverse', 'fx.reverse'],
|
|
118
120
|
['fyDomain', 'fy.domain'],
|
|
119
121
|
['fyRange', 'fy.range'],
|
|
120
|
-
['fyNice', 'fy.mice'],
|
|
121
122
|
['fyInset', 'fy,inset'],
|
|
122
123
|
['fyInsetTop', 'fy.insetTop'],
|
|
123
124
|
['fyInsetBottom', 'fy.insetBottom'],
|
|
@@ -153,6 +154,7 @@ export const attributeMap = new Map([
|
|
|
153
154
|
['colorPivot', 'color.pivot'],
|
|
154
155
|
['colorSymmetric', 'color.symmetric'],
|
|
155
156
|
['colorLabel', 'color.label'],
|
|
157
|
+
['colorPercent', 'color.percent'],
|
|
156
158
|
['colorReverse', 'color.reverse'],
|
|
157
159
|
['colorZero', 'color.zero'],
|
|
158
160
|
['colorTickFormat', 'color.tickFormat'],
|
|
@@ -165,17 +167,22 @@ export const attributeMap = new Map([
|
|
|
165
167
|
['opacityClamp', 'opacity.clamp'],
|
|
166
168
|
['opacityNice', 'opacity.nice'],
|
|
167
169
|
['opacityLabel', 'opacity.label'],
|
|
170
|
+
['opacityPercent', 'opacity.percent'],
|
|
168
171
|
['opacityReverse', 'opacity.reverse'],
|
|
169
172
|
['opacityZero', 'opacity.zero'],
|
|
170
173
|
['opacityTickFormat', 'opacity.tickFormat'],
|
|
171
174
|
['opacityBase', 'opacity.base'],
|
|
172
175
|
['opacityExponent', 'opacity.exponent'],
|
|
173
176
|
['opacityConstant', 'opacity.constant'],
|
|
177
|
+
['symbolScale', 'symbol.type'],
|
|
178
|
+
['symbolDomain', 'symbol.domain'],
|
|
179
|
+
['symbolRange', 'symbol.range'],
|
|
174
180
|
['rScale', 'r.type'],
|
|
175
181
|
['rDomain', 'r.domain'],
|
|
176
182
|
['rRange', 'r.range'],
|
|
177
183
|
['rClamp', 'r.clamp'],
|
|
178
184
|
['rNice', 'r.nice'],
|
|
185
|
+
['rPercent', 'r.percent'],
|
|
179
186
|
['rZero', 'r.zero'],
|
|
180
187
|
['rBase', 'r.base'],
|
|
181
188
|
['rExponent', 'r.exponent'],
|
|
@@ -185,6 +192,7 @@ export const attributeMap = new Map([
|
|
|
185
192
|
['lengthRange', 'length.range'],
|
|
186
193
|
['lengthClamp', 'length.clamp'],
|
|
187
194
|
['lengthNice', 'length.nice'],
|
|
195
|
+
['lengthPercent', 'length.percent'],
|
|
188
196
|
['lengthZero', 'length.zero'],
|
|
189
197
|
['lengthBase', 'length.base'],
|
|
190
198
|
['lengthExponent', 'length.exponent'],
|
package/src/plot-renderer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as Plot from '@observablehq/plot';
|
|
2
2
|
import { setAttributes } from './plot-attributes.js';
|
|
3
3
|
import { Fixed } from './symbols.js';
|
|
4
|
+
import { isArrowTable } from '@uwdata/mosaic-core';
|
|
4
5
|
|
|
5
6
|
const OPTIONS_ONLY_MARKS = new Set([
|
|
6
7
|
'frame',
|
|
@@ -25,6 +26,22 @@ export async function plotRenderer(plot) {
|
|
|
25
26
|
for (const { type, data, options } of mark.plotSpecs()) {
|
|
26
27
|
if (OPTIONS_ONLY_MARKS.has(type)) {
|
|
27
28
|
spec.marks.push(Plot[type](options));
|
|
29
|
+
} else if (isArrowTable(data)) {
|
|
30
|
+
// optimized calls to Plot for Arrow:
|
|
31
|
+
// https://github.com/observablehq/plot/issues/191#issuecomment-2010986851
|
|
32
|
+
const opts = Object.fromEntries(
|
|
33
|
+
Object.entries(options).map(([k, v]) => {
|
|
34
|
+
let val = v;
|
|
35
|
+
if (typeof v === 'string') {
|
|
36
|
+
val = data.getChild(v) ?? v;
|
|
37
|
+
} else if (typeof v === 'object') {
|
|
38
|
+
const value = data.getChild(v.value);
|
|
39
|
+
val = value ? {value} : v;
|
|
40
|
+
}
|
|
41
|
+
return [k, val]
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
spec.marks.push(Plot[type]({length: data.numRows}, opts));
|
|
28
45
|
} else {
|
|
29
46
|
spec.marks.push(Plot[type](data, options));
|
|
30
47
|
}
|
|
@@ -148,10 +165,13 @@ function annotateMarks(svg, indices) {
|
|
|
148
165
|
}
|
|
149
166
|
|
|
150
167
|
function getType(data, channel) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
168
|
+
const { columns } = data;
|
|
169
|
+
const col = columns[channel] ?? columns[channel+'1'] ?? columns[channel+'2'];
|
|
170
|
+
if (col) {
|
|
171
|
+
for (const v of col) {
|
|
172
|
+
if (v != null) {
|
|
173
|
+
return v instanceof Date ? 'date' : typeof v;
|
|
174
|
+
}
|
|
155
175
|
}
|
|
156
176
|
}
|
|
157
177
|
}
|
package/src/plot.js
CHANGED
|
@@ -72,10 +72,20 @@ export class Plot {
|
|
|
72
72
|
this.synch.resolve();
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} name The attribute to return.
|
|
77
|
+
* @returns {*} The value of the attribute.
|
|
78
|
+
*/
|
|
75
79
|
getAttribute(name) {
|
|
76
80
|
return this.attributes[name];
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
/**
|
|
84
|
+
* @param {string} name The name of the attribute to set.
|
|
85
|
+
* @param {*} value The value to set.
|
|
86
|
+
* @param {{silent: boolean}} [options] Options for setting the attribute.
|
|
87
|
+
* @returns {boolean} whether the value changed.
|
|
88
|
+
*/
|
|
79
89
|
setAttribute(name, value, options) {
|
|
80
90
|
if (distinct(this.attributes[name], value)) {
|
|
81
91
|
if (value === undefined) {
|
|
@@ -91,6 +101,11 @@ export class Plot {
|
|
|
91
101
|
return false;
|
|
92
102
|
}
|
|
93
103
|
|
|
104
|
+
/**
|
|
105
|
+
* @param {string} name The attribute name.
|
|
106
|
+
* @param {*} callback The function to call when the attribute changes.
|
|
107
|
+
* @returns {this}
|
|
108
|
+
*/
|
|
94
109
|
addAttributeListener(name, callback) {
|
|
95
110
|
const map = this.listeners || (this.listeners = new Map);
|
|
96
111
|
if (!map.has(name)) map.set(name, new Set);
|
|
@@ -98,6 +113,11 @@ export class Plot {
|
|
|
98
113
|
return this;
|
|
99
114
|
}
|
|
100
115
|
|
|
116
|
+
/**
|
|
117
|
+
* @param {string} name The attribute name.
|
|
118
|
+
* @param {*} callback The function to call when the attribute changes.
|
|
119
|
+
* @returns {void}
|
|
120
|
+
*/
|
|
101
121
|
removeAttributeListener(name, callback) {
|
|
102
122
|
return this.listeners?.get(name)?.delete(callback);
|
|
103
123
|
}
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { Delaunay, randomLcg } from 'd3';
|
|
2
|
-
|
|
3
|
-
// Derived from Observable Plot’s interpolatorBarycentric:
|
|
4
|
-
// https://github.com/observablehq/plot/blob/41a63e372453d2f95e7a046839dfd245d21e7660/src/marks/raster.js#L283-L334
|
|
5
|
-
|
|
6
|
-
export function interpolatorBarycentric({random = randomLcg(42)} = {}) {
|
|
7
|
-
return (index, width, height, X, Y, V, W) => {
|
|
8
|
-
// Interpolate the interior of all triangles with barycentric coordinates
|
|
9
|
-
const {points, triangles, hull} = Delaunay.from(index, (i) => X[i], (i) => Y[i]);
|
|
10
|
-
const S = new Uint8Array(width * height); // 1 if pixel has been seen.
|
|
11
|
-
const mix = mixer(V, random);
|
|
12
|
-
|
|
13
|
-
for (let i = 0; i < triangles.length; i += 3) {
|
|
14
|
-
const ta = triangles[i];
|
|
15
|
-
const tb = triangles[i + 1];
|
|
16
|
-
const tc = triangles[i + 2];
|
|
17
|
-
const Ax = points[2 * ta];
|
|
18
|
-
const Bx = points[2 * tb];
|
|
19
|
-
const Cx = points[2 * tc];
|
|
20
|
-
const Ay = points[2 * ta + 1];
|
|
21
|
-
const By = points[2 * tb + 1];
|
|
22
|
-
const Cy = points[2 * tc + 1];
|
|
23
|
-
const x1 = Math.min(Ax, Bx, Cx);
|
|
24
|
-
const x2 = Math.max(Ax, Bx, Cx);
|
|
25
|
-
const y1 = Math.min(Ay, By, Cy);
|
|
26
|
-
const y2 = Math.max(Ay, By, Cy);
|
|
27
|
-
const z = (By - Cy) * (Ax - Cx) + (Ay - Cy) * (Cx - Bx);
|
|
28
|
-
if (!z) continue;
|
|
29
|
-
const va = V[index[ta]];
|
|
30
|
-
const vb = V[index[tb]];
|
|
31
|
-
const vc = V[index[tc]];
|
|
32
|
-
for (let x = Math.floor(x1); x < x2; ++x) {
|
|
33
|
-
for (let y = Math.floor(y1); y < y2; ++y) {
|
|
34
|
-
if (x < 0 || x >= width || y < 0 || y >= height) continue;
|
|
35
|
-
const xp = x + 0.5; // sample pixel centroids
|
|
36
|
-
const yp = y + 0.5;
|
|
37
|
-
const ga = ((By - Cy) * (xp - Cx) + (yp - Cy) * (Cx - Bx)) / z;
|
|
38
|
-
if (ga < 0) continue;
|
|
39
|
-
const gb = ((Cy - Ay) * (xp - Cx) + (yp - Cy) * (Ax - Cx)) / z;
|
|
40
|
-
if (gb < 0) continue;
|
|
41
|
-
const gc = 1 - ga - gb;
|
|
42
|
-
if (gc < 0) continue;
|
|
43
|
-
const i = x + width * y;
|
|
44
|
-
W[i] = mix(va, ga, vb, gb, vc, gc, x, y);
|
|
45
|
-
S[i] = 1;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
extrapolateBarycentric(W, S, X, Y, V, width, height, hull, index, mix);
|
|
50
|
-
return W;
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Extrapolate by finding the closest point on the hull.
|
|
55
|
-
function extrapolateBarycentric(W, S, X, Y, V, width, height, hull, index, mix) {
|
|
56
|
-
X = Float64Array.from(hull, (i) => X[index[i]]);
|
|
57
|
-
Y = Float64Array.from(hull, (i) => Y[index[i]]);
|
|
58
|
-
V = Array.from(hull, (i) => V[index[i]]);
|
|
59
|
-
const n = X.length;
|
|
60
|
-
const rays = Array.from({length: n}, (_, j) => ray(j, X, Y));
|
|
61
|
-
let k = 0;
|
|
62
|
-
for (let y = 0; y < height; ++y) {
|
|
63
|
-
const yp = y + 0.5;
|
|
64
|
-
for (let x = 0; x < width; ++x) {
|
|
65
|
-
const i = x + width * y;
|
|
66
|
-
if (!S[i]) {
|
|
67
|
-
const xp = x + 0.5;
|
|
68
|
-
for (let l = 0; l < n; ++l) {
|
|
69
|
-
const j = (n + k + (l % 2 ? (l + 1) / 2 : -l / 2)) % n;
|
|
70
|
-
if (rays[j](xp, yp)) {
|
|
71
|
-
const t = segmentProject(X.at(j - 1), Y.at(j - 1), X[j], Y[j], xp, yp);
|
|
72
|
-
W[i] = mix(V.at(j - 1), t, V[j], 1 - t, V[j], 0, x, y);
|
|
73
|
-
k = j;
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Projects a point p = [x, y] onto the line segment [p1, p2], returning the
|
|
83
|
-
// projected coordinates p’ as t in [0, 1] with p’ = t p1 + (1 - t) p2.
|
|
84
|
-
function segmentProject(x1, y1, x2, y2, x, y) {
|
|
85
|
-
const dx = x2 - x1;
|
|
86
|
-
const dy = y2 - y1;
|
|
87
|
-
const a = dx * (x2 - x) + dy * (y2 - y);
|
|
88
|
-
const b = dx * (x - x1) + dy * (y - y1);
|
|
89
|
-
return a > 0 && b > 0 ? a / (a + b) : +(a > b);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function cross(xa, ya, xb, yb) {
|
|
93
|
-
return xa * yb - xb * ya;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function ray(j, X, Y) {
|
|
97
|
-
const n = X.length;
|
|
98
|
-
const xc = X.at(j - 2);
|
|
99
|
-
const yc = Y.at(j - 2);
|
|
100
|
-
const xa = X.at(j - 1);
|
|
101
|
-
const ya = Y.at(j - 1);
|
|
102
|
-
const xb = X[j];
|
|
103
|
-
const yb = Y[j];
|
|
104
|
-
const xd = X.at(j + 1 - n);
|
|
105
|
-
const yd = Y.at(j + 1 - n);
|
|
106
|
-
const dxab = xa - xb;
|
|
107
|
-
const dyab = ya - yb;
|
|
108
|
-
const dxca = xc - xa;
|
|
109
|
-
const dyca = yc - ya;
|
|
110
|
-
const dxbd = xb - xd;
|
|
111
|
-
const dybd = yb - yd;
|
|
112
|
-
const hab = Math.hypot(dxab, dyab);
|
|
113
|
-
const hca = Math.hypot(dxca, dyca);
|
|
114
|
-
const hbd = Math.hypot(dxbd, dybd);
|
|
115
|
-
return (x, y) => {
|
|
116
|
-
const dxa = x - xa;
|
|
117
|
-
const dya = y - ya;
|
|
118
|
-
const dxb = x - xb;
|
|
119
|
-
const dyb = y - yb;
|
|
120
|
-
return (
|
|
121
|
-
cross(dxa, dya, dxb, dyb) > -1e-6 &&
|
|
122
|
-
cross(dxa, dya, dxab, dyab) * hca - cross(dxa, dya, dxca, dyca) * hab > -1e-6 &&
|
|
123
|
-
cross(dxb, dyb, dxbd, dybd) * hab - cross(dxb, dyb, dxab, dyab) * hbd <= 0
|
|
124
|
-
);
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Derived from Observable Plot’s interpolateNearest:
|
|
129
|
-
// https://github.com/observablehq/plot/blob/41a63e372453d2f95e7a046839dfd245d21e7660/src/marks/raster.js#L410-L428
|
|
130
|
-
|
|
131
|
-
export function interpolateNearest(index, width, height, X, Y, V, W) {
|
|
132
|
-
const delaunay = Delaunay.from(index, (i) => X[i], (i) => Y[i]);
|
|
133
|
-
// memoization of delaunay.find for the line start (iy) and pixel (ix)
|
|
134
|
-
let iy, ix;
|
|
135
|
-
for (let y = 0.5, k = 0; y < height; ++y) {
|
|
136
|
-
ix = iy;
|
|
137
|
-
for (let x = 0.5; x < width; ++x, ++k) {
|
|
138
|
-
ix = delaunay.find(x, y, ix);
|
|
139
|
-
if (x === 0.5) iy = ix;
|
|
140
|
-
W[k] = V[index[ix]];
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return W;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Derived from Observable Plot’s interpolatorRandomWalk:
|
|
147
|
-
// https://github.com/observablehq/plot/blob/41a63e372453d2f95e7a046839dfd245d21e7660/src/marks/raster.js#L430-L462
|
|
148
|
-
|
|
149
|
-
// https://observablehq.com/@observablehq/walk-on-spheres-precision
|
|
150
|
-
export function interpolatorRandomWalk({random = randomLcg(42), minDistance = 0.5, maxSteps = 2} = {}) {
|
|
151
|
-
return (index, width, height, X, Y, V, W) => {
|
|
152
|
-
const delaunay = Delaunay.from(index, (i) => X[i], (i) => Y[i]);
|
|
153
|
-
// memoization of delaunay.find for the line start (iy), pixel (ix), and wos step (iw)
|
|
154
|
-
let iy, ix, iw;
|
|
155
|
-
for (let y = 0.5, k = 0; y < height; ++y) {
|
|
156
|
-
ix = iy;
|
|
157
|
-
for (let x = 0.5; x < width; ++x, ++k) {
|
|
158
|
-
let cx = x;
|
|
159
|
-
let cy = y;
|
|
160
|
-
iw = ix = delaunay.find(cx, cy, ix);
|
|
161
|
-
if (x === 0.5) iy = ix;
|
|
162
|
-
let distance; // distance to closest sample
|
|
163
|
-
let step = 0; // count of steps for this walk
|
|
164
|
-
while ((distance = Math.hypot(X[index[iw]] - cx, Y[index[iw]] - cy)) > minDistance && step < maxSteps) {
|
|
165
|
-
const angle = random(x, y, step) * 2 * Math.PI;
|
|
166
|
-
cx += Math.cos(angle) * distance;
|
|
167
|
-
cy += Math.sin(angle) * distance;
|
|
168
|
-
iw = delaunay.find(cx, cy, iw);
|
|
169
|
-
++step;
|
|
170
|
-
}
|
|
171
|
-
W[k] = V[index[iw]];
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return W;
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function blend(a, ca, b, cb, c, cc) {
|
|
179
|
-
return ca * a + cb * b + cc * c;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function pick(random) {
|
|
183
|
-
return (a, ca, b, cb, c, cc, x, y) => {
|
|
184
|
-
const u = random(x, y);
|
|
185
|
-
return u < ca ? a : u < ca + cb ? b : c;
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function mixer(F, random) {
|
|
190
|
-
return isNumeric(F) || isTemporal(F) ? blend : pick(random);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function isNumeric(values) {
|
|
194
|
-
for (const value of values) {
|
|
195
|
-
if (value == null) continue;
|
|
196
|
-
return typeof value === "number";
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function isTemporal(values) {
|
|
201
|
-
for (const value of values) {
|
|
202
|
-
if (value == null) continue;
|
|
203
|
-
return value instanceof Date;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { convertArrowValue, isArrowTable } from '@uwdata/mosaic-core';
|
|
2
|
-
|
|
3
|
-
export function toDataArray(data) {
|
|
4
|
-
return isArrowTable(data) ? arrowToObjects(data) : data;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Convert Apache Arrow tables to an array of vanilla JS objects.
|
|
9
|
-
* The internal conversions performed by the Arrow JS lib may not
|
|
10
|
-
* always produce values properly interpreted by Observable Plot.
|
|
11
|
-
* In addition, the Arrow JS lib uses Proxy objects to create
|
|
12
|
-
* row (tuple) objects, which can introduce some perf overhead.
|
|
13
|
-
* This method provides custom conversions of data that we can hand
|
|
14
|
-
* to Observable Plot. Internally, Plot will copy input data values
|
|
15
|
-
* into an array-based columnar organizations. If in the future Plot
|
|
16
|
-
* provides an efficient path to directly pass in columnar-data, we
|
|
17
|
-
* can revisit this method.
|
|
18
|
-
*/
|
|
19
|
-
export function arrowToObjects(data) {
|
|
20
|
-
const { batches, numRows: length } = data;
|
|
21
|
-
|
|
22
|
-
// return an empty array for empty tables
|
|
23
|
-
if (!length) return [];
|
|
24
|
-
|
|
25
|
-
// pre-allocate output objects
|
|
26
|
-
const objects = Array.from({ length }, () => ({}));
|
|
27
|
-
|
|
28
|
-
// for each row batch...
|
|
29
|
-
for (let k = 0, b = 0; b < batches.length; ++b) {
|
|
30
|
-
const batch = batches[b];
|
|
31
|
-
const { schema, numRows, numCols } = batch;
|
|
32
|
-
|
|
33
|
-
// for each column...
|
|
34
|
-
for (let j = 0; j < numCols; ++j) {
|
|
35
|
-
const child = batch.getChildAt(j);
|
|
36
|
-
const { name, type } = schema.fields[j];
|
|
37
|
-
const valueOf = convertArrowValue(type);
|
|
38
|
-
|
|
39
|
-
// for each row in the current batch...
|
|
40
|
-
for (let o = k, i = 0; i < numRows; ++i, ++o) {
|
|
41
|
-
// extract/convert value from arrow, copy to output object
|
|
42
|
-
objects[o][name] = valueOf(child.get(i));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
k += numRows;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return objects;
|
|
50
|
-
}
|