@uwdata/mosaic-plot 0.5.0 → 0.6.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 +843 -547
- 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 +86 -53
- package/src/marks/RasterMark.js +188 -64
- package/src/marks/RasterTileMark.js +54 -100
- package/src/marks/util/arrow.js +25 -0
- package/src/marks/util/bin-expr.js +30 -0
- package/src/marks/util/channel-scale.js +27 -0
- package/src/marks/util/grid.js +90 -19
- 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/transforms/bin.js +18 -20
- package/src/marks/util/bin-field.js +0 -17
- package/src/marks/util/is-arrow-table.js +0 -3
package/src/marks/util/grid.js
CHANGED
|
@@ -1,57 +1,128 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InternSet, ascending } from 'd3';
|
|
2
|
+
import { DECIMAL, FLOAT, INTEGER, isArrowTable } from './arrow.js';
|
|
3
|
+
|
|
4
|
+
function arrayType(values, name = 'density') {
|
|
5
|
+
if (isArrowTable(values)) {
|
|
6
|
+
const type = values.getChild(name).type;
|
|
7
|
+
switch (type.typeId) {
|
|
8
|
+
case INTEGER:
|
|
9
|
+
case FLOAT:
|
|
10
|
+
case DECIMAL:
|
|
11
|
+
return Float64Array;
|
|
12
|
+
default:
|
|
13
|
+
return Array;
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
return typeof values[0][name] === 'number' ? Float64Array : Array;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
2
19
|
|
|
3
20
|
export function grid1d(n, values) {
|
|
4
|
-
|
|
21
|
+
const Type = arrayType(values);
|
|
22
|
+
return valuesToGrid(new Type(n), values);
|
|
5
23
|
}
|
|
6
24
|
|
|
7
|
-
export function grid2d(m, n, values, groupby = []) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
25
|
+
export function grid2d(m, n, values, aggr, groupby = []) {
|
|
26
|
+
if (groupby.length) {
|
|
27
|
+
// generate grids per group
|
|
28
|
+
return groupedValuesToGrids(m * n, values, aggr, groupby);
|
|
29
|
+
} else {
|
|
30
|
+
const cell = {};
|
|
31
|
+
aggr.forEach(name => {
|
|
32
|
+
const Type = arrayType(values, name);
|
|
33
|
+
cell[name] = valuesToGrid(new Type(m * n), values, name);
|
|
34
|
+
});
|
|
35
|
+
return [cell];
|
|
36
|
+
}
|
|
11
37
|
}
|
|
12
38
|
|
|
13
|
-
function valuesToGrid(grid, values) {
|
|
39
|
+
function valuesToGrid(grid, values, name = 'density') {
|
|
14
40
|
if (isArrowTable(values)) {
|
|
15
41
|
// optimize access for Arrow tables
|
|
16
42
|
const numRows = values.numRows;
|
|
17
43
|
if (numRows === 0) return grid;
|
|
18
44
|
const index = values.getChild('index').toArray();
|
|
19
|
-
const value = values.getChild(
|
|
45
|
+
const value = values.getChild(name).toArray();
|
|
20
46
|
for (let row = 0; row < numRows; ++row) {
|
|
21
47
|
grid[index[row]] = value[row];
|
|
22
48
|
}
|
|
23
49
|
} else {
|
|
24
50
|
// fallback to iterable data
|
|
25
51
|
for (const row of values) {
|
|
26
|
-
grid[row.index] = row
|
|
52
|
+
grid[row.index] = row[name];
|
|
27
53
|
}
|
|
28
54
|
}
|
|
29
55
|
return grid;
|
|
30
56
|
}
|
|
31
57
|
|
|
32
|
-
function groupedValuesToGrids(size, values, groupby) {
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
58
|
+
function groupedValuesToGrids(size, values, aggr, groupby) {
|
|
59
|
+
const Types = aggr.map(name => arrayType(values, name));
|
|
60
|
+
const numAggr = aggr.length;
|
|
61
|
+
|
|
62
|
+
const cellMap = {};
|
|
63
|
+
const getCell = key => {
|
|
64
|
+
let cell = cellMap[key];
|
|
65
|
+
if (!cell) {
|
|
66
|
+
cell = cellMap[key] = {};
|
|
67
|
+
groupby.forEach((name, i) => cell[name] = key[i]);
|
|
68
|
+
aggr.forEach((name, i) => cell[name] = new Types[i](size));
|
|
69
|
+
}
|
|
70
|
+
return cell;
|
|
37
71
|
};
|
|
72
|
+
|
|
38
73
|
if (isArrowTable(values)) {
|
|
39
74
|
// optimize access for Arrow tables
|
|
40
75
|
const numRows = values.numRows;
|
|
41
|
-
if (numRows === 0) return
|
|
76
|
+
if (numRows === 0) return [];
|
|
77
|
+
|
|
42
78
|
const index = values.getChild('index').toArray();
|
|
43
|
-
const value = values.getChild(
|
|
79
|
+
const value = aggr.map(name => values.getChild(name).toArray());
|
|
44
80
|
const groups = groupby.map(name => values.getChild(name));
|
|
81
|
+
|
|
45
82
|
for (let row = 0; row < numRows; ++row) {
|
|
46
83
|
const key = groups.map(vec => vec.get(row));
|
|
47
|
-
|
|
84
|
+
const cell = getCell(key);
|
|
85
|
+
for (let i = 0; i < numAggr; ++i) {
|
|
86
|
+
cell[aggr[i]][index[row]] = value[i][row];
|
|
87
|
+
}
|
|
48
88
|
}
|
|
49
89
|
} else {
|
|
50
90
|
// fallback to iterable data
|
|
51
91
|
for (const row of values) {
|
|
52
92
|
const key = groupby.map(col => row[col]);
|
|
53
|
-
|
|
93
|
+
const cell = getCell(key);
|
|
94
|
+
for (let i = 0; i < numAggr; ++i) {
|
|
95
|
+
cell[aggr[i]][row.index] = row[aggr[i]];
|
|
96
|
+
}
|
|
54
97
|
}
|
|
55
98
|
}
|
|
56
|
-
|
|
99
|
+
|
|
100
|
+
return Object.values(cellMap);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function gridDomainContinuous(grids, prop) {
|
|
104
|
+
let lo = 0, hi = 0;
|
|
105
|
+
grids.forEach(cell => {
|
|
106
|
+
const grid = cell[prop];
|
|
107
|
+
const n = grid.length;
|
|
108
|
+
for (let i = 0; i < n; ++i) {
|
|
109
|
+
const v = grid[i];
|
|
110
|
+
if (v < lo) lo = v;
|
|
111
|
+
if (v > hi) hi = v;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return (lo === 0 && hi === 0) ? [0, 1] : [lo, hi];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function gridDomainDiscrete(grids, prop) {
|
|
118
|
+
// TODO: sort options?
|
|
119
|
+
const values = new InternSet();
|
|
120
|
+
grids.forEach(cell => {
|
|
121
|
+
const grid = cell[prop];
|
|
122
|
+
const n = grid.length;
|
|
123
|
+
for (let i = 0; i < n; ++i) {
|
|
124
|
+
values.add(grid[i]);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return Array.from(values).sort(ascending);
|
|
57
128
|
}
|
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'],
|
package/src/transforms/bin.js
CHANGED
|
@@ -1,30 +1,26 @@
|
|
|
1
|
-
import { asColumn } from '@uwdata/mosaic-sql';
|
|
2
1
|
import { Transform } from '../symbols.js';
|
|
2
|
+
import { channelScale } from '../marks/util/channel-scale.js';
|
|
3
3
|
|
|
4
|
-
const EXTENT = [
|
|
5
|
-
'rectY-x', 'rectX-y', 'rect-x', 'rect-y'
|
|
6
|
-
];
|
|
7
|
-
|
|
8
|
-
function hasExtent(channel, type) {
|
|
9
|
-
return EXTENT.includes(`${type}-${channel}`);
|
|
10
|
-
}
|
|
4
|
+
const EXTENT = new Set(['rectY-x', 'rectX-y', 'rect-x', 'rect-y']);
|
|
11
5
|
|
|
12
6
|
export function bin(field, options = { steps: 25 }) {
|
|
13
7
|
const fn = (mark, channel) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
if (EXTENT.has(`${mark.type}-${channel}`)) {
|
|
9
|
+
return {
|
|
10
|
+
[`${channel}1`]: binField(mark, channel, field, options),
|
|
11
|
+
[`${channel}2`]: binField(mark, channel, field, { ...options, offset: 1 })
|
|
12
|
+
};
|
|
13
|
+
} else {
|
|
14
|
+
return {
|
|
15
|
+
[channel]: binField(mark, channel, field, options)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
22
18
|
};
|
|
23
19
|
fn[Transform] = true;
|
|
24
20
|
return fn;
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
function binField(mark, column, options) {
|
|
23
|
+
function binField(mark, channel, column, options) {
|
|
28
24
|
return {
|
|
29
25
|
column,
|
|
30
26
|
label: column,
|
|
@@ -32,13 +28,15 @@ function binField(mark, column, options) {
|
|
|
32
28
|
get columns() { return [column]; },
|
|
33
29
|
get basis() { return column; },
|
|
34
30
|
toString() {
|
|
31
|
+
const { apply, sqlApply, sqlInvert } = channelScale(mark, channel);
|
|
35
32
|
const { min, max } = mark.stats[column];
|
|
36
|
-
const b = bins(min, max, options);
|
|
37
|
-
const col =
|
|
33
|
+
const b = bins(apply(min), apply(max), options);
|
|
34
|
+
const col = sqlApply(column);
|
|
38
35
|
const base = b.min === 0 ? col : `(${col} - ${b.min})`;
|
|
39
36
|
const alpha = `${(b.max - b.min) / b.steps}::DOUBLE`;
|
|
40
37
|
const off = options.offset ? `${options.offset} + ` : '';
|
|
41
|
-
|
|
38
|
+
const expr = `${b.min} + ${alpha} * (${off}FLOOR(${base} / ${alpha}))`;
|
|
39
|
+
return `${sqlInvert(expr)}`;
|
|
42
40
|
}
|
|
43
41
|
};
|
|
44
42
|
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { epoch_ms, sql } from '@uwdata/mosaic-sql';
|
|
2
|
-
|
|
3
|
-
export function binField(mark, channel, expr) {
|
|
4
|
-
if (!mark.stats) return field;
|
|
5
|
-
const { field } = mark.channelField(channel);
|
|
6
|
-
const { type } = mark.stats[field.column];
|
|
7
|
-
expr = expr ?? field;
|
|
8
|
-
return type === 'date' ? epoch_ms(expr) : expr;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function bin1d(x, x0, x1, n, reverse = false, pad = 1) {
|
|
12
|
-
const d = (n - pad) / (x1 - x0);
|
|
13
|
-
const f = d !== 1 ? ` * ${d}::DOUBLE` : '';
|
|
14
|
-
return reverse
|
|
15
|
-
? sql`(${+x1} - ${x}::DOUBLE)${f}`
|
|
16
|
-
: sql`(${x}::DOUBLE - ${+x0})${f}`;
|
|
17
|
-
}
|