@uwdata/mosaic-plot 0.7.1 → 0.9.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.
Files changed (37) hide show
  1. package/dist/mosaic-plot.js +4702 -5648
  2. package/dist/mosaic-plot.min.js +14 -14
  3. package/package.json +5 -5
  4. package/src/index.js +1 -0
  5. package/src/interactors/Highlight.js +6 -3
  6. package/src/interactors/Interval1D.js +14 -12
  7. package/src/interactors/Interval2D.js +13 -16
  8. package/src/interactors/Nearest.js +80 -36
  9. package/src/interactors/PanZoom.js +7 -9
  10. package/src/interactors/Toggle.js +29 -37
  11. package/src/interactors/util/patchScreenCTM.js +2 -0
  12. package/src/legend.js +150 -29
  13. package/src/marks/ConnectedMark.js +6 -0
  14. package/src/marks/ContourMark.js +36 -16
  15. package/src/marks/DenseLineMark.js +9 -5
  16. package/src/marks/Density1DMark.js +22 -13
  17. package/src/marks/Density2DMark.js +33 -18
  18. package/src/marks/ErrorBarMark.js +50 -0
  19. package/src/marks/GeoMark.js +7 -8
  20. package/src/marks/Grid2DMark.js +58 -28
  21. package/src/marks/HexbinMark.js +10 -2
  22. package/src/marks/Mark.js +56 -16
  23. package/src/marks/RasterMark.js +61 -23
  24. package/src/marks/RasterTileMark.js +39 -20
  25. package/src/marks/RegressionMark.js +69 -34
  26. package/src/marks/util/grid.js +94 -86
  27. package/src/marks/util/handle-param.js +10 -11
  28. package/src/marks/util/is-constant-option.js +2 -1
  29. package/src/marks/util/permute.js +10 -0
  30. package/src/marks/util/stats.js +121 -1
  31. package/src/marks/util/to-data-columns.js +71 -0
  32. package/src/plot-attributes.js +11 -3
  33. package/src/plot-renderer.js +28 -9
  34. package/src/plot.js +20 -0
  35. package/src/transforms/bin.js +3 -1
  36. package/src/marks/util/interpolate.js +0 -205
  37. package/src/marks/util/to-data-array.js +0 -50
@@ -7,17 +7,24 @@ import {
7
7
  import { qt } from './util/stats.js';
8
8
  import { Mark, channelOption } from './Mark.js';
9
9
  import { handleParam } from './util/handle-param.js';
10
- import { toDataArray } from './util/to-data-array.js';
10
+ import { toDataColumns } from './util/to-data-columns.js';
11
11
 
12
12
  export class RegressionMark extends Mark {
13
13
  constructor(source, options) {
14
14
  const { ci = 0.95, precision = 4, ...channels } = options;
15
15
  super('line', source, channels);
16
- const update = () => {
17
- return this.modelFit ? this.confidenceBand().update() : null
18
- };
19
- handleParam(this, 'ci', ci, update);
20
- handleParam(this, 'precision', precision, update);
16
+
17
+ const update = () => this.modelFit ? this.confidenceBand().update() : null;
18
+
19
+ /** @type {number} */
20
+ this.ci = handleParam(ci, value => {
21
+ return (this.ci = value, update());
22
+ });
23
+
24
+ /** @type {number} */
25
+ this.precision = handleParam(precision, value => {
26
+ return (this.precision = value, update());
27
+ });
21
28
  }
22
29
 
23
30
  query(filter = []) {
@@ -44,10 +51,10 @@ export class RegressionMark extends Mark {
44
51
  }
45
52
 
46
53
  queryResult(data) {
47
- this.modelFit = toDataArray(data);
54
+ this.modelFit = toDataColumns(data);
48
55
 
49
56
  // regression line
50
- this.lineData = this.modelFit.flatMap(m => linePoints(m));
57
+ this.lineData = linePoints(this.modelFit);
51
58
 
52
59
  // prepare confidence band
53
60
  return this.confidenceBand();
@@ -56,15 +63,17 @@ export class RegressionMark extends Mark {
56
63
  confidenceBand() {
57
64
  // regression ci area
58
65
  const { ci, modelFit, precision, plot } = this;
59
- const w = plot.innerWidth();
60
- this.areaData = ci ? modelFit.flatMap(m => areaPoints(ci, precision, m, w)) : null;
66
+ const width = plot.innerWidth();
67
+ this.areaData = ci ? areaPoints(modelFit, ci, precision, width) : null;
61
68
  return this;
62
69
  }
63
70
 
64
71
  plotSpecs() {
65
72
  const { lineData, areaData, channels, ci } = this;
66
- const lopt = { x: 'x', y: 'y' };
67
- const aopt = { x: 'x', y1: 'y1', y2: 'y2', fillOpacity: 0.1 };
73
+ const lcols = lineData.columns;
74
+ const acols = ci ? areaData.columns : {};
75
+ const lopt = { x: lcols.x, y: lcols.y };
76
+ const aopt = { x: acols.x, y1: acols.y1, y2: acols.y2, fillOpacity: 0.1 };
68
77
 
69
78
  for (const c of channels) {
70
79
  switch (c.channel) {
@@ -72,46 +81,72 @@ export class RegressionMark extends Mark {
72
81
  case 'y':
73
82
  case 'fill':
74
83
  break;
84
+ case 'tip':
85
+ aopt.tip = channelOption(c, acols);
86
+ break;
75
87
  case 'stroke':
76
- lopt.stroke = aopt.fill = channelOption(c);
88
+ lopt.stroke = channelOption(c, lcols);
89
+ aopt.fill = channelOption(c, acols);
77
90
  break;
78
91
  case 'strokeOpacity':
79
- lopt.strokeOpacity = channelOption(c);
92
+ lopt.strokeOpacity = channelOption(c, lcols);
80
93
  break;
81
94
  case 'fillOpacity':
82
- aopt.fillOpacity = channelOption(c);
95
+ aopt.fillOpacity = channelOption(c, acols);
83
96
  break;
84
97
  default:
85
- lopt[c.channel] = aopt[c.channel] = channelOption(c);
98
+ lopt[c.channel] = channelOption(c, lcols);
99
+ aopt[c.channel] = channelOption(c, acols);
86
100
  break;
87
101
  }
88
102
  }
89
103
 
90
104
  return [
91
- ...(ci ? [{ type: 'areaY', data: areaData, options: aopt }] : []),
92
- { type: 'line', data: lineData, options: lopt }
105
+ ...(ci ? [{ type: 'areaY', data: { length: areaData.numRows }, options: aopt }] : []),
106
+ { type: 'line', data: { length: lineData.numRows }, options: lopt }
93
107
  ];
94
108
  }
95
109
  }
96
110
 
97
- function linePoints(model) {
111
+ function concat(a, b) {
112
+ if (a.concat) return a.concat(b);
113
+ const array = new (a.constructor)(a.length + b.length);
114
+ array.set(a, 0);
115
+ array.set(b, a.length);
116
+ return array;
117
+ }
118
+
119
+ function linePoints(fit) {
98
120
  // eslint-disable-next-line no-unused-vars
99
- const { x0, x1, xm, intercept, slope, n, ssx, ssy, ...rest } = model;
100
- return [
101
- { x: x0, y: intercept + x0 * slope, ...rest },
102
- { x: x1, y: intercept + x1 * slope, ...rest }
103
- ];
121
+ const { x0, x1, xm, intercept, slope, n, ssx, ssy, ...rest } = fit.columns;
122
+ const predict = (x, i) => intercept[i] + x * slope[i];
123
+ const x = concat(x0, x1);
124
+ const y = concat(x0.map(predict), x1.map(predict));
125
+ for (const name in rest) {
126
+ rest[name] = concat(rest[name], rest[name]);
127
+ }
128
+ return { numRows: x.length, columns: { x, y, ...rest } };
104
129
  }
105
130
 
106
- function areaPoints(ci, precision, model, width) {
107
- const { x0, x1, xm, intercept, slope, n, ssx, ssy, ...rest } = model;
108
- const pp = precision * (x1 - x0) / width;
109
- const t_sy = qt((1 - ci) / 2, n - 2) * Math.sqrt(ssy / (n - 2));
110
- return range(x0, x1 - pp / 2, pp)
111
- .concat(x1)
112
- .map(x => {
113
- const y = intercept + x * slope;
114
- const ye = t_sy * Math.sqrt(1 / n + (x - xm) ** 2 / ssx);
115
- return { x, y1: y - ye, y2: y + ye, ...rest };
131
+ function areaPoints(fit, ci, precision, width) {
132
+ const len = fit.numRows;
133
+ const { x0, x1, xm, intercept, slope, n, ssx, ssy, ...rest } = fit.columns;
134
+ const other = Object.keys(rest);
135
+ const columns = { x: [], y1: [], y2: [] };
136
+ other.forEach(name => columns[name] = []);
137
+
138
+ for (let i = 0; i < len; ++i) {
139
+ const pp = precision * (x1[i] - x0[i]) / width;
140
+ const t_sy = qt((1 - ci) / 2, n[i] - 2) * Math.sqrt(ssy[i] / (n[i] - 2));
141
+ range(x0[i], x1[i] - pp / 2, pp).concat(x1[i]).forEach(x => {
142
+ const y = intercept[i] + x * slope[i];
143
+ const ye = t_sy * Math.sqrt(1 / n[i] + (x - xm[i]) ** 2 / ssx[i]);
144
+ columns.x.push(x);
145
+ columns.y1.push(y - ye);
146
+ columns.y2.push(y + ye);
147
+ other.forEach(name => columns[name].push(rest[name][i]));
116
148
  });
149
+ }
150
+
151
+ return { numRows: columns.x.length, columns };
117
152
  }
@@ -1,119 +1,127 @@
1
1
  import { InternSet, ascending } from 'd3';
2
- import {
3
- convertArrowArrayType,
4
- convertArrowColumn,
5
- isArrowTable
6
- } from '@uwdata/mosaic-core';
7
2
 
8
- function arrayType(values, name = 'density') {
9
- return isArrowTable(values)
10
- ? convertArrowArrayType(values.getChild(name).type)
11
- : typeof values[0]?.[name] === 'number' ? Float64Array : Array;
3
+ /**
4
+ * @typedef {Array | Int8Array | Uint8Array | Uint8ClampedArray
5
+ * | Int16Array | Uint16Array | Int32Array | Uint32Array
6
+ * | Float32Array | Float64Array
7
+ * } Arrayish - an Array or TypedArray
8
+ */
9
+
10
+ /**
11
+ * Generate a new array with designated size and type.
12
+ * @param {number} size The size of the array
13
+ * @param {Arrayish} [proto] A prototype object of the desired array type.
14
+ * This may be a typed array or standard array (the default).
15
+ * @returns {Arrayish} The generated array.
16
+ */
17
+ export function array(size, proto = []) {
18
+ // @ts-ignore
19
+ return new proto.constructor(size);
12
20
  }
13
21
 
14
- export function grid1d(n, values, name = 'density') {
15
- const grid = new (arrayType(values))(n);
16
- if (isArrowTable(values)) {
17
- // optimize access for Arrow tables
18
- const numRows = values.numRows;
19
- if (numRows === 0) return grid;
20
- const index = convertArrowColumn(values.getChild('index'));
21
- const value = convertArrowColumn(values.getChild(name));
22
- for (let row = 0; row < numRows; ++row) {
23
- grid[index[row]] = value[row];
24
- }
25
- } else {
26
- // fallback to iterable data
27
- for (const row of values) {
28
- grid[row.index] = row[name];
29
- }
22
+ /**
23
+ * Create a 1D grid for the given sample values
24
+ * @param {number} size The grid size.
25
+ * @param {Arrayish} index The grid indices for sample points.
26
+ * @param {Arrayish} value The sample point values.
27
+ * @returns {Arrayish} The generated value grid.
28
+ */
29
+ export function grid1d(size, index, value) {
30
+ const G = array(size, value);
31
+ const n = value.length;
32
+ for (let i = 0; i < n; ++i) {
33
+ G[index[i]] = value[i];
30
34
  }
31
- return grid;
35
+ return G;
32
36
  }
33
37
 
34
- export function grid2d(w, h, values, aggr, groupby = [], interpolate) {
38
+ /**
39
+ * Create a 2D grid for the given sample values.
40
+ * Can handle multiple grids and groupby values per output row.
41
+ * @param {number} w The grid width.
42
+ * @param {number} h The grid height.
43
+ * @param {Arrayish} index The grid indices for sample points.
44
+ * An index value is an integer of the form (y * w + x).
45
+ * @param {Record<string,Arrayish>} columns Named column arrays with sample point values.
46
+ * @param {string[]} aggregates The names of aggregate columns to grid.
47
+ * @param {string[]} groupby The names of additional columns to group by.
48
+ * @param {function} [interpolate] A grid interpolation function.
49
+ * By default sample values are directly copied to output grid arrays.
50
+ * @returns {{
51
+ * numRows: number;
52
+ * columns: { [key:string]: Arrayish }
53
+ * }} Named column arrays of generated grid values.
54
+ */
55
+ export function grid2d(w, h, index, columns, aggregates, groupby, interpolate) {
56
+ const numRows = index.length;
35
57
  const size = w * h;
36
- const Types = aggr.map(name => arrayType(values, name));
37
- const numAggr = aggr.length;
58
+ const values = aggregates.map(name => columns[name]);
59
+ const result = {};
60
+ const cells = [];
61
+ const group = new Int32Array(numRows);
38
62
 
39
- // grid data tuples
40
- const createCell = (key) => {
41
- const cell = {};
42
- groupby.forEach((name, i) => cell[name] = key[i]);
43
- aggr.forEach((name, i) => cell[name] = new Types[i](size));
44
- return cell;
45
- };
46
- const cellMap = {};
47
- const baseCell = groupby.length ? null : (cellMap[[]] = createCell([]));
48
- const getCell = groupby.length
49
- ? key => cellMap[key] ?? (cellMap[key] = createCell(key))
50
- : () => baseCell;
51
-
52
- // early exit if empty query result
53
- const numRows = values.numRows;
54
- if (numRows === 0) return Object.values(cellMap);
55
-
56
- // extract arrays from arrow table
57
- const index = convertArrowColumn(values.getChild('index'));
58
- const value = aggr.map(name => convertArrowColumn(values.getChild(name)));
59
- const groups = groupby.map(name => values.getChild(name));
60
-
61
- if (!interpolate) {
62
- // if no interpolation, copy values over
63
+ // if grouped, generate per-row group indices
64
+ if (groupby?.length) {
65
+ const gvalues = groupby.map(name => columns[name]);
66
+ const cellMap = {};
63
67
  for (let row = 0; row < numRows; ++row) {
64
- const key = groups.map(vec => vec.get(row));
65
- const cell = getCell(key);
66
- for (let i = 0; i < numAggr; ++i) {
67
- cell[aggr[i]][index[row]] = value[i][row];
68
- }
68
+ const key = gvalues.map(group => group[row]);
69
+ group[row] = cellMap[key] ??= cells.push(key) - 1;
70
+ }
71
+ for (let i = 0; i < groupby.length; ++i) {
72
+ result[groupby[i]] = cells.map(cell => cell[i]);
69
73
  }
70
74
  } else {
75
+ cells.push([]); // single group
76
+ }
77
+
78
+ if (interpolate) {
71
79
  // prepare index arrays, then interpolate grid values
72
80
  const X = index.map(k => k % w);
73
81
  const Y = index.map(k => Math.floor(k / w));
74
- if (groupby.length) {
75
- for (let row = 0; row < numRows; ++row) {
76
- const key = groups.map(vec => vec.get(row));
77
- const cell = getCell(key);
78
- if (!cell.index) { cell.index = []; }
79
- cell.index.push(row);
80
- }
81
- } else {
82
- baseCell.index = index.map((_, i) => i);
82
+ const I = cells.map(() => []);
83
+ for (let row = 0; row < numRows; ++row) {
84
+ I[group[row]].push(row);
83
85
  }
84
- Object.values(cellMap).forEach(cell => {
85
- for (let i = 0; i < numAggr; ++i) {
86
- interpolate(cell.index, w, h, X, Y, value[i], cell[aggr[i]]);
86
+ aggregates.forEach((name, i) => {
87
+ const V = values[i];
88
+ result[name] = cells.map((_, j) => interpolate(I[j], w, h, X, Y, V));
89
+ });
90
+ } else {
91
+ // no interpolation, copy values directly to grids
92
+ aggregates.forEach((name, i) => {
93
+ const V = values[i];
94
+ const G = result[name] = cells.map(() => array(size, V));
95
+ for (let row = 0; row < numRows; ++row) {
96
+ G[group[row]][index[row]] = V[row];
87
97
  }
88
- delete cell.index;
89
- })
98
+ });
90
99
  }
91
100
 
92
- return Object.values(cellMap);
101
+ // @ts-ignore
102
+ return { numRows: cells.length, columns: result };
93
103
  }
94
104
 
95
- export function gridDomainContinuous(grids, prop) {
96
- let lo = 0, hi = 0;
97
- grids.forEach(cell => {
98
- const grid = cell[prop];
99
- const n = grid.length;
105
+ export function gridDomainContinuous(grids) {
106
+ let lo = Infinity, hi = -Infinity;
107
+ grids.forEach(G => {
108
+ const n = G.length;
100
109
  for (let i = 0; i < n; ++i) {
101
- const v = grid[i];
110
+ const v = G[i];
102
111
  if (v < lo) lo = v;
103
112
  if (v > hi) hi = v;
104
113
  }
105
114
  });
106
- return (lo === 0 && hi === 0) ? [0, 1] : [lo, hi];
115
+ return (Number.isFinite(lo) && Number.isFinite(hi)) ? [lo, hi] : [0, 1];
107
116
  }
108
117
 
109
- export function gridDomainDiscrete(grids, prop) {
110
- // TODO: sort options?
118
+ export function gridDomainDiscrete(grids) {
119
+ // TODO: sort options?
111
120
  const values = new InternSet();
112
- grids.forEach(cell => {
113
- const grid = cell[prop];
114
- const n = grid.length;
121
+ grids.forEach(G => {
122
+ const n = G.length;
115
123
  for (let i = 0; i < n; ++i) {
116
- values.add(grid[i]);
124
+ values.add(G[i]);
117
125
  }
118
126
  });
119
127
  return Array.from(values).sort(ascending);
@@ -1,14 +1,13 @@
1
1
  import { isParam } from '@uwdata/mosaic-core';
2
2
 
3
- export function handleParam(client, key, param, update) {
4
- if (isParam(param)) {
5
- update = update || (() => client.requestUpdate());
6
- param.addEventListener('value', value => {
7
- client[key] = value;
8
- return update();
9
- });
10
- client[key] = param.value;
11
- } else {
12
- client[key] = param;
13
- }
3
+ /**
4
+ * Utility to check if a value is a Param, and if so, bind a listener.
5
+ * @param {*} value A potentially Param-typed value.
6
+ * @param {(value: *) => Promise|void} update Update callback
7
+ * @returns the input value or (if a Param) the current Param value.
8
+ */
9
+ export function handleParam(value, update) {
10
+ return isParam(value)
11
+ ? (value.addEventListener('value', update), value.value)
12
+ : value;
14
13
  }
@@ -33,7 +33,8 @@ const constantOptions = new Set([
33
33
  'crossOrigin',
34
34
  'paintOrder',
35
35
  'pointerEvents',
36
- 'target'
36
+ 'target',
37
+ 'select'
37
38
  ]);
38
39
 
39
40
  export function isConstantOption(value) {
@@ -0,0 +1,10 @@
1
+ export function indices(length) {
2
+ return Array.from({ length }, (_, i) => i);
3
+ }
4
+
5
+ export function permute(data, order) {
6
+ const ord = order.reduce((acc, val, i) => (acc[val] = i, acc), {});
7
+ const idx = indices(data.length);
8
+ idx.sort((a, b) => ord[data[a]] - ord[data[b]]);
9
+ return idx;
10
+ }
@@ -20,6 +20,13 @@
20
20
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  // SOFTWARE.
22
22
 
23
+ /**
24
+ * ibetainv function
25
+ * @param {number} p
26
+ * @param {number} a
27
+ * @param {number} b
28
+ * @returns {number}
29
+ */
23
30
  export function ibetainv(p, a, b) {
24
31
  var EPS = 1e-8;
25
32
  var a1 = a - 1;
@@ -60,11 +67,18 @@ export function ibetainv(p, a, b) {
60
67
  return x;
61
68
  }
62
69
 
70
+ /**
71
+ * ibeta function
72
+ * @param {number} x
73
+ * @param {number} a
74
+ * @param {number} b
75
+ * @returns {number}
76
+ */
63
77
  export function ibeta(x, a, b) {
64
78
  // Factors in front of the continued fraction.
65
79
  var bt =
66
80
  x === 0 || x === 1 ? 0 : Math.exp(gammaln(a + b) - gammaln(a) - gammaln(b) + a * Math.log(x) + b * Math.log(1 - x));
67
- if (x < 0 || x > 1) return false;
81
+ if (x < 0 || x > 1) return 0;
68
82
  if (x < (a + 1) / (a + b + 2))
69
83
  // Use continued fraction directly.
70
84
  return (bt * betacf(x, a, b)) / a;
@@ -72,6 +86,13 @@ export function ibeta(x, a, b) {
72
86
  return 1 - (bt * betacf(1 - x, b, a)) / b;
73
87
  }
74
88
 
89
+ /**
90
+ * betacf function
91
+ * @param {number} x
92
+ * @param {number} a
93
+ * @param {number} b
94
+ * @returns {number}
95
+ */
75
96
  export function betacf(x, a, b) {
76
97
  var fpmin = 1e-30;
77
98
  var m = 1;
@@ -112,6 +133,11 @@ export function betacf(x, a, b) {
112
133
  return h;
113
134
  }
114
135
 
136
+ /**
137
+ * gammaln function
138
+ * @param {number} x
139
+ * @returns {number}
140
+ */
115
141
  export function gammaln(x) {
116
142
  var j = 0;
117
143
  var cof = [
@@ -126,8 +152,102 @@ export function gammaln(x) {
126
152
  return Math.log((2.506628274631 * ser) / xx) - tmp;
127
153
  }
128
154
 
155
+ /**
156
+ * qt function
157
+ * @param {number} p
158
+ * @param {number} dof
159
+ * @returns {number}
160
+ */
129
161
  export function qt(p, dof) {
130
162
  var x = ibetainv(2 * Math.min(p, 1 - p), 0.5 * dof, 0.5);
131
163
  x = Math.sqrt((dof * (1 - x)) / x);
132
164
  return p > 0.5 ? x : -x;
133
165
  }
166
+
167
+ /**
168
+ * Approximate inverse error function.
169
+ * @param {number} x
170
+ * @returns {number}
171
+ */
172
+ export function erfinv(x) {
173
+ // Implementation from "Approximating the erfinv function" by Mike Giles,
174
+ // GPU Computing Gems, volume 2, 2010.
175
+ // Ported from Apache Commons Math, http://www.apache.org/licenses/LICENSE-2.0
176
+
177
+ // beware that the logarithm argument must be
178
+ // computed as (1.0 - x) * (1.0 + x),
179
+ // it must NOT be simplified as 1.0 - x * x as this
180
+ // would induce rounding errors near the boundaries +/-1
181
+ let w = - Math.log((1 - x) * (1 + x));
182
+ let p;
183
+
184
+ if (w < 6.25) {
185
+ w -= 3.125;
186
+ p = -3.6444120640178196996e-21;
187
+ p = -1.685059138182016589e-19 + p * w;
188
+ p = 1.2858480715256400167e-18 + p * w;
189
+ p = 1.115787767802518096e-17 + p * w;
190
+ p = -1.333171662854620906e-16 + p * w;
191
+ p = 2.0972767875968561637e-17 + p * w;
192
+ p = 6.6376381343583238325e-15 + p * w;
193
+ p = -4.0545662729752068639e-14 + p * w;
194
+ p = -8.1519341976054721522e-14 + p * w;
195
+ p = 2.6335093153082322977e-12 + p * w;
196
+ p = -1.2975133253453532498e-11 + p * w;
197
+ p = -5.4154120542946279317e-11 + p * w;
198
+ p = 1.051212273321532285e-09 + p * w;
199
+ p = -4.1126339803469836976e-09 + p * w;
200
+ p = -2.9070369957882005086e-08 + p * w;
201
+ p = 4.2347877827932403518e-07 + p * w;
202
+ p = -1.3654692000834678645e-06 + p * w;
203
+ p = -1.3882523362786468719e-05 + p * w;
204
+ p = 0.0001867342080340571352 + p * w;
205
+ p = -0.00074070253416626697512 + p * w;
206
+ p = -0.0060336708714301490533 + p * w;
207
+ p = 0.24015818242558961693 + p * w;
208
+ p = 1.6536545626831027356 + p * w;
209
+ } else if (w < 16.0) {
210
+ w = Math.sqrt(w) - 3.25;
211
+ p = 2.2137376921775787049e-09;
212
+ p = 9.0756561938885390979e-08 + p * w;
213
+ p = -2.7517406297064545428e-07 + p * w;
214
+ p = 1.8239629214389227755e-08 + p * w;
215
+ p = 1.5027403968909827627e-06 + p * w;
216
+ p = -4.013867526981545969e-06 + p * w;
217
+ p = 2.9234449089955446044e-06 + p * w;
218
+ p = 1.2475304481671778723e-05 + p * w;
219
+ p = -4.7318229009055733981e-05 + p * w;
220
+ p = 6.8284851459573175448e-05 + p * w;
221
+ p = 2.4031110387097893999e-05 + p * w;
222
+ p = -0.0003550375203628474796 + p * w;
223
+ p = 0.00095328937973738049703 + p * w;
224
+ p = -0.0016882755560235047313 + p * w;
225
+ p = 0.0024914420961078508066 + p * w;
226
+ p = -0.0037512085075692412107 + p * w;
227
+ p = 0.005370914553590063617 + p * w;
228
+ p = 1.0052589676941592334 + p * w;
229
+ p = 3.0838856104922207635 + p * w;
230
+ } else if (Number.isFinite(w)) {
231
+ w = Math.sqrt(w) - 5.0;
232
+ p = -2.7109920616438573243e-11;
233
+ p = -2.5556418169965252055e-10 + p * w;
234
+ p = 1.5076572693500548083e-09 + p * w;
235
+ p = -3.7894654401267369937e-09 + p * w;
236
+ p = 7.6157012080783393804e-09 + p * w;
237
+ p = -1.4960026627149240478e-08 + p * w;
238
+ p = 2.9147953450901080826e-08 + p * w;
239
+ p = -6.7711997758452339498e-08 + p * w;
240
+ p = 2.2900482228026654717e-07 + p * w;
241
+ p = -9.9298272942317002539e-07 + p * w;
242
+ p = 4.5260625972231537039e-06 + p * w;
243
+ p = -1.9681778105531670567e-05 + p * w;
244
+ p = 7.5995277030017761139e-05 + p * w;
245
+ p = -0.00021503011930044477347 + p * w;
246
+ p = -0.00013871931833623122026 + p * w;
247
+ p = 1.0103004648645343977 + p * w;
248
+ p = 4.8499064014085844221 + p * w;
249
+ } else {
250
+ p = Infinity;
251
+ }
252
+ return p * x;
253
+ }
@@ -0,0 +1,71 @@
1
+ import { convertArrowColumn, isArrowTable } from '@uwdata/mosaic-core';
2
+
3
+ /**
4
+ * @typedef {Array | Int8Array | Uint8Array | Uint8ClampedArray
5
+ * | Int16Array | Uint16Array | Int32Array | Uint32Array
6
+ * | Float32Array | Float64Array
7
+ * } Arrayish - an Array or TypedArray
8
+ */
9
+
10
+ /**
11
+ * @typedef {
12
+ * | { numRows: number, columns: Record<string,Arrayish> }
13
+ * | { numRows: number, values: Arrayish; }
14
+ * } DataColumns
15
+ */
16
+
17
+ /**
18
+ * Convert input data to a set of column arrays.
19
+ * @param {any} data The input data.
20
+ * @returns {DataColumns} An object with named column arrays.
21
+ */
22
+ export function toDataColumns(data) {
23
+ return isArrowTable(data)
24
+ ? arrowToColumns(data)
25
+ : arrayToColumns(data);
26
+ }
27
+
28
+ /**
29
+ * Convert an Arrow table to a set of column arrays.
30
+ * @param {import('apache-arrow').Table} data An Apache Arrow Table.
31
+ * @returns {DataColumns} An object with named column arrays.
32
+ */
33
+ function arrowToColumns(data) {
34
+ const { numRows, numCols, schema: { fields } } = data;
35
+ const columns = {};
36
+
37
+ for (let col = 0; col < numCols; ++col) {
38
+ const name = fields[col].name;
39
+ if (columns[name]) {
40
+ console.warn(`Redundant column name "${name}". Skipping...`);
41
+ } else {
42
+ columns[name] = convertArrowColumn(data.getChildAt(col));
43
+ }
44
+ }
45
+
46
+ return { numRows, columns };
47
+ }
48
+
49
+ /**
50
+ * Convert an array of values to a set of column arrays.
51
+ * If the array values are objects, build out named columns.
52
+ * We use the keys of the first object as the column names.
53
+ * Otherwise, use a special "values" array.
54
+ * @param {object[]} data An array of data objects.
55
+ * @returns {DataColumns} An object with named column arrays.
56
+ */
57
+ function arrayToColumns(data) {
58
+ const numRows = data.length;
59
+ if (typeof data[0] === 'object') {
60
+ const names = numRows ? Object.keys(data[0]) : [];
61
+ const columns = {};
62
+ if (names.length > 0) {
63
+ names.forEach(name => {
64
+ columns[name] = data.map(d => d[name]);
65
+ });
66
+ }
67
+ return { numRows, columns };
68
+ } else {
69
+ return { numRows, values: data };
70
+ }
71
+ }