@uwdata/mosaic-plot 0.12.0 → 0.12.2
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/README.md +2 -0
- package/dist/mosaic-plot.js +124 -50
- package/dist/mosaic-plot.min.js +8 -8
- package/package.json +4 -4
- package/src/marks/Density1DMark.js +68 -21
- package/src/marks/HexbinMark.js +5 -2
- package/src/marks/Mark.js +7 -3
- package/src/marks/util/grid.js +36 -7
- package/src/marks/util/is-constant-option.js +2 -0
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# mosaic-plot
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@uwdata/mosaic-plot)
|
|
4
|
+
|
|
3
5
|
A Mosaic-powered grammar of graphics based on [Observable Plot](https://github.com/observablehq/plot). Mmarks (plot layers) serve as individual Mosaic clients. These marks can push data processing (binning, hex binning, regression) and optimizations (such as M4 for line/area charts) down to the database. This package also provides interactors for linked selection, filtering, and highlighting using Mosaic Params and Selections.
|
|
4
6
|
|
|
5
7
|
The `mosaic-plot` package is included as part of the [vgplot](https://github.com/uwdata/mosaic/tree/main/packages/vgplot) API.
|
package/dist/mosaic-plot.js
CHANGED
|
@@ -103,7 +103,7 @@ var require_search_bounds = __commonJS({
|
|
|
103
103
|
}
|
|
104
104
|
return -1;
|
|
105
105
|
}
|
|
106
|
-
function
|
|
106
|
+
function norm2(a2, y3, c4, l, h, f) {
|
|
107
107
|
if (typeof c4 === "function") {
|
|
108
108
|
return f(a2, y3, c4, l === void 0 ? 0 : l | 0, h === void 0 ? a2.length - 1 : h | 0);
|
|
109
109
|
}
|
|
@@ -111,19 +111,19 @@ var require_search_bounds = __commonJS({
|
|
|
111
111
|
}
|
|
112
112
|
module.exports = {
|
|
113
113
|
ge: function(a2, y3, c4, l, h) {
|
|
114
|
-
return
|
|
114
|
+
return norm2(a2, y3, c4, l, h, ge);
|
|
115
115
|
},
|
|
116
116
|
gt: function(a2, y3, c4, l, h) {
|
|
117
|
-
return
|
|
117
|
+
return norm2(a2, y3, c4, l, h, gt2);
|
|
118
118
|
},
|
|
119
119
|
lt: function(a2, y3, c4, l, h) {
|
|
120
|
-
return
|
|
120
|
+
return norm2(a2, y3, c4, l, h, lt2);
|
|
121
121
|
},
|
|
122
122
|
le: function(a2, y3, c4, l, h) {
|
|
123
|
-
return
|
|
123
|
+
return norm2(a2, y3, c4, l, h, le);
|
|
124
124
|
},
|
|
125
125
|
eq: function(a2, y3, c4, l, h) {
|
|
126
|
-
return
|
|
126
|
+
return norm2(a2, y3, c4, l, h, eq2);
|
|
127
127
|
}
|
|
128
128
|
};
|
|
129
129
|
}
|
|
@@ -568,14 +568,15 @@ var MosaicClient = class {
|
|
|
568
568
|
}
|
|
569
569
|
/**
|
|
570
570
|
* Return an array of fields queried by this client.
|
|
571
|
-
* @returns {
|
|
571
|
+
* @returns {import('./types.js').FieldInfoRequest[] | null}
|
|
572
|
+
* The fields to retrieve info for.
|
|
572
573
|
*/
|
|
573
574
|
fields() {
|
|
574
575
|
return null;
|
|
575
576
|
}
|
|
576
577
|
/**
|
|
577
578
|
* Called by the coordinator to set the field info for this client.
|
|
578
|
-
* @param {
|
|
579
|
+
* @param {import('./types.js').FieldInfo[]} info The field info result.
|
|
579
580
|
* @returns {this}
|
|
580
581
|
*/
|
|
581
582
|
fieldInfo(info) {
|
|
@@ -3456,6 +3457,9 @@ var ColumnNameRefNode = class extends ColumnRefNode {
|
|
|
3456
3457
|
};
|
|
3457
3458
|
|
|
3458
3459
|
// ../sql/src/ast/column-param.js
|
|
3460
|
+
function isColumnParam(value) {
|
|
3461
|
+
return value instanceof ColumnParamNode;
|
|
3462
|
+
}
|
|
3459
3463
|
var ColumnParamNode = class extends ColumnRefNode {
|
|
3460
3464
|
/**
|
|
3461
3465
|
* Instantiate a column param node.
|
|
@@ -4877,7 +4881,7 @@ function collectAggregates(root2) {
|
|
|
4877
4881
|
function collectColumns(root2) {
|
|
4878
4882
|
const cols = {};
|
|
4879
4883
|
walk(root2, (node) => {
|
|
4880
|
-
if (node.type === COLUMN_REF) {
|
|
4884
|
+
if (node.type === COLUMN_REF || node.type === COLUMN_PARAM) {
|
|
4881
4885
|
cols[node] = node;
|
|
4882
4886
|
}
|
|
4883
4887
|
});
|
|
@@ -4921,21 +4925,21 @@ function bin2d(q, xp, yp, aggs, xn, groupby) {
|
|
|
4921
4925
|
}
|
|
4922
4926
|
|
|
4923
4927
|
// ../sql/src/transforms/bin-linear-1d.js
|
|
4924
|
-
function binLinear1d(query, x3, weight) {
|
|
4928
|
+
function binLinear1d(query, x3, weight = void 0, groupby = []) {
|
|
4925
4929
|
const w = weight ? (x4) => mul(x4, weight) : (x4) => x4;
|
|
4926
4930
|
const p0 = floor(x3);
|
|
4927
4931
|
const p1 = add(p0, 1);
|
|
4928
4932
|
return Query.from(Query.unionAll(
|
|
4929
4933
|
query.clone().select({ i: int322(p0), w: w(sub(p1, x3)) }),
|
|
4930
4934
|
query.clone().select({ i: int322(p1), w: w(sub(x3, p0)) })
|
|
4931
|
-
)).select({ index: "i", density: sum("w") }).groupby("index").having(neq("density", 0));
|
|
4935
|
+
)).select({ index: "i", density: sum("w") }, groupby).groupby("index", groupby).having(neq("density", 0));
|
|
4932
4936
|
}
|
|
4933
4937
|
|
|
4934
4938
|
// ../sql/src/transforms/bin-linear-2d.js
|
|
4935
4939
|
function identity(x3) {
|
|
4936
4940
|
return x3;
|
|
4937
4941
|
}
|
|
4938
|
-
function binLinear2d(q, xp, yp, weight, xn, groupby) {
|
|
4942
|
+
function binLinear2d(q, xp, yp, weight, xn, groupby = []) {
|
|
4939
4943
|
const w = weight ? (x3) => mul(x3, weight) : identity;
|
|
4940
4944
|
const subq = (i, w2) => q.clone().select({ xp, yp, i, w: w2 });
|
|
4941
4945
|
const index2 = (x3, y3) => add(x3, mul(y3, xn));
|
|
@@ -5665,7 +5669,7 @@ var statMap = {
|
|
|
5665
5669
|
[Min]: min,
|
|
5666
5670
|
[Nulls]: (column3) => count().where(isNull(column3))
|
|
5667
5671
|
};
|
|
5668
|
-
function summarize(table, column3, stats) {
|
|
5672
|
+
function summarize({ table, column: column3, stats }) {
|
|
5669
5673
|
return Query.from(table).select(Array.from(stats, (s2) => ({ [s2]: statMap[s2](column3) })));
|
|
5670
5674
|
}
|
|
5671
5675
|
async function queryFieldInfo(mc, fields) {
|
|
@@ -5676,7 +5680,7 @@ async function queryFieldInfo(mc, fields) {
|
|
|
5676
5680
|
}
|
|
5677
5681
|
}
|
|
5678
5682
|
async function getFieldInfo(mc, { table, column: column3, stats }) {
|
|
5679
|
-
const q = Query.from({ source: table }).select({ column: column3 }).groupby(column3
|
|
5683
|
+
const q = Query.from({ source: table }).select({ column: column3 }).groupby(isNode(column3) && isAggregateExpression(column3) ? sql`ALL` : []);
|
|
5680
5684
|
const [desc2] = Array.from(await mc.query(Query.describe(q)));
|
|
5681
5685
|
const info = {
|
|
5682
5686
|
table,
|
|
@@ -5685,16 +5689,16 @@ async function getFieldInfo(mc, { table, column: column3, stats }) {
|
|
|
5685
5689
|
type: jsType(desc2.column_type),
|
|
5686
5690
|
nullable: desc2.null === "YES"
|
|
5687
5691
|
};
|
|
5688
|
-
if (!
|
|
5692
|
+
if (!stats?.length) return info;
|
|
5689
5693
|
const [result] = await mc.query(
|
|
5690
|
-
summarize(table, column3, stats),
|
|
5694
|
+
summarize({ table, column: column3, stats }),
|
|
5691
5695
|
{ persist: true }
|
|
5692
5696
|
);
|
|
5693
5697
|
return Object.assign(info, result);
|
|
5694
5698
|
}
|
|
5695
5699
|
async function getTableInfo(mc, table) {
|
|
5696
|
-
const result = await mc.query(`DESCRIBE ${asTableRef(table)}`);
|
|
5697
|
-
return
|
|
5700
|
+
const result = Array.from(await mc.query(`DESCRIBE ${asTableRef(table)}`));
|
|
5701
|
+
return result.map((desc2) => ({
|
|
5698
5702
|
table,
|
|
5699
5703
|
column: desc2.column_name,
|
|
5700
5704
|
sqlType: desc2.column_type,
|
|
@@ -6310,7 +6314,10 @@ var Coordinator = class {
|
|
|
6310
6314
|
* or a SQL string.
|
|
6311
6315
|
* @param {object} [options] An options object.
|
|
6312
6316
|
* @param {'arrow' | 'json'} [options.type] The query result format type.
|
|
6313
|
-
* @param {boolean} [options.cache=true] If true, cache the query result
|
|
6317
|
+
* @param {boolean} [options.cache=true] If true, cache the query result
|
|
6318
|
+
* client-side within the QueryManager.
|
|
6319
|
+
* @param {boolean} [options.persist] If true, request the database
|
|
6320
|
+
* server to persist a cached query server-side.
|
|
6314
6321
|
* @param {number} [options.priority] The query priority, defaults to
|
|
6315
6322
|
* `Priority.Normal`.
|
|
6316
6323
|
* @returns {QueryResult} A query result promise.
|
|
@@ -7030,7 +7037,7 @@ function clausePoint(field3, value, {
|
|
|
7030
7037
|
source,
|
|
7031
7038
|
clients = source ? /* @__PURE__ */ new Set([source]) : void 0
|
|
7032
7039
|
}) {
|
|
7033
|
-
const predicate = value !== void 0 ?
|
|
7040
|
+
const predicate = value !== void 0 ? isIn(field3, [literal(value)]) : null;
|
|
7034
7041
|
return {
|
|
7035
7042
|
meta: { type: "point" },
|
|
7036
7043
|
source,
|
|
@@ -31148,7 +31155,9 @@ function isColor2(value) {
|
|
|
31148
31155
|
|
|
31149
31156
|
// src/marks/util/is-constant-option.js
|
|
31150
31157
|
var constantOptions = /* @__PURE__ */ new Set([
|
|
31158
|
+
"offset",
|
|
31151
31159
|
"order",
|
|
31160
|
+
"reverse",
|
|
31152
31161
|
"sort",
|
|
31153
31162
|
"label",
|
|
31154
31163
|
"anchor",
|
|
@@ -31220,7 +31229,7 @@ var isFieldObject = (channel, field3) => {
|
|
|
31220
31229
|
var fieldEntry = (channel, field3) => ({
|
|
31221
31230
|
channel,
|
|
31222
31231
|
field: field3,
|
|
31223
|
-
as: isColumnRef(field3) ? field3.column : channel
|
|
31232
|
+
as: isColumnRef(field3) && !isColumnParam(field3) ? field3.column : channel
|
|
31224
31233
|
});
|
|
31225
31234
|
var valueEntry = (channel, value) => ({ channel, value });
|
|
31226
31235
|
var isDataArray = (source) => Array.isArray(source);
|
|
@@ -31316,7 +31325,11 @@ var Mark2 = class extends MosaicClient {
|
|
|
31316
31325
|
reqs[channel]?.forEach((s2) => entry.add(s2));
|
|
31317
31326
|
}
|
|
31318
31327
|
const table = this.sourceTable();
|
|
31319
|
-
return Array.from(fields, ([c4, s2]) => ({
|
|
31328
|
+
return Array.from(fields, ([c4, s2]) => ({
|
|
31329
|
+
table,
|
|
31330
|
+
column: c4,
|
|
31331
|
+
stats: Array.from(s2)
|
|
31332
|
+
}));
|
|
31320
31333
|
}
|
|
31321
31334
|
fieldInfo(info) {
|
|
31322
31335
|
const lookup = Object.fromEntries(info.map((x3) => [x3.column, x3]));
|
|
@@ -31514,13 +31527,33 @@ var ConnectedMark = class extends Mark2 {
|
|
|
31514
31527
|
function array3(size, proto = []) {
|
|
31515
31528
|
return new proto.constructor(size);
|
|
31516
31529
|
}
|
|
31517
|
-
function grid1d(size, index2, value) {
|
|
31518
|
-
const
|
|
31519
|
-
const
|
|
31520
|
-
|
|
31521
|
-
|
|
31530
|
+
function grid1d(size, index2, value, columns, groupby) {
|
|
31531
|
+
const numRows = index2.length;
|
|
31532
|
+
const result = {};
|
|
31533
|
+
const cells = [];
|
|
31534
|
+
if (groupby?.length) {
|
|
31535
|
+
const group3 = new Int32Array(numRows);
|
|
31536
|
+
const gvalues = groupby.map((name) => columns[name]);
|
|
31537
|
+
const cellMap = {};
|
|
31538
|
+
for (let row = 0; row < numRows; ++row) {
|
|
31539
|
+
const key = gvalues.map((group4) => group4[row]);
|
|
31540
|
+
group3[row] = cellMap[key] ??= cells.push(key) - 1;
|
|
31541
|
+
}
|
|
31542
|
+
for (let i = 0; i < groupby.length; ++i) {
|
|
31543
|
+
result[groupby[i]] = cells.map((cell2) => cell2[i]);
|
|
31544
|
+
}
|
|
31545
|
+
const G = result._grid = cells.map(() => array3(size, value));
|
|
31546
|
+
for (let row = 0; row < numRows; ++row) {
|
|
31547
|
+
G[group3[row]][index2[row]] = value[row];
|
|
31548
|
+
}
|
|
31549
|
+
} else {
|
|
31550
|
+
cells.push([]);
|
|
31551
|
+
const [G] = result._grid = [array3(size, value)];
|
|
31552
|
+
for (let row = 0; row < numRows; ++row) {
|
|
31553
|
+
G[index2[row]] = value[row];
|
|
31554
|
+
}
|
|
31522
31555
|
}
|
|
31523
|
-
return
|
|
31556
|
+
return { numRows: cells.length, columns: result };
|
|
31524
31557
|
}
|
|
31525
31558
|
function grid2d(w, h, index2, columns, aggregates, groupby, interpolate) {
|
|
31526
31559
|
const numRows = index2.length;
|
|
@@ -32378,9 +32411,16 @@ function stripXY(mark, filter3) {
|
|
|
32378
32411
|
}
|
|
32379
32412
|
|
|
32380
32413
|
// src/marks/Density1DMark.js
|
|
32414
|
+
var GROUPBY = { fill: 1, stroke: 1, z: 1 };
|
|
32381
32415
|
var Density1DMark = class extends Mark2 {
|
|
32382
32416
|
constructor(type2, source, options) {
|
|
32383
|
-
const {
|
|
32417
|
+
const {
|
|
32418
|
+
bins: bins2 = 1024,
|
|
32419
|
+
bandwidth = 20,
|
|
32420
|
+
normalize: normalize4 = false,
|
|
32421
|
+
stack: stack2 = false,
|
|
32422
|
+
...channels
|
|
32423
|
+
} = options;
|
|
32384
32424
|
const dim = type2.endsWith("X") ? "y" : "x";
|
|
32385
32425
|
super(type2, source, channels, dim === "x" ? xext : yext);
|
|
32386
32426
|
this.dim = dim;
|
|
@@ -32389,7 +32429,13 @@ var Density1DMark = class extends Mark2 {
|
|
|
32389
32429
|
});
|
|
32390
32430
|
this.bandwidth = handleParam(bandwidth, (value) => {
|
|
32391
32431
|
this.bandwidth = value;
|
|
32392
|
-
return this.
|
|
32432
|
+
return this.grids ? this.convolve().update() : null;
|
|
32433
|
+
});
|
|
32434
|
+
this.normalize = handleParam(normalize4, (value) => {
|
|
32435
|
+
return this.normalize = value, this.convolve().update();
|
|
32436
|
+
});
|
|
32437
|
+
this.stack = handleParam(stack2, (value) => {
|
|
32438
|
+
return this.stack = value, this.update();
|
|
32393
32439
|
});
|
|
32394
32440
|
}
|
|
32395
32441
|
get filterStable() {
|
|
@@ -32404,42 +32450,67 @@ var Density1DMark = class extends Mark2 {
|
|
|
32404
32450
|
const [x3, bx] = binExpr(this, dim, bins2, extent4);
|
|
32405
32451
|
const q = markQuery(channels, this.sourceTable(), [dim]).where(filter3.concat(isBetween(bx, extent4)));
|
|
32406
32452
|
const v2 = this.channelField("weight") ? "weight" : null;
|
|
32407
|
-
|
|
32453
|
+
const g = this.groupby = channels.flatMap((c4) => {
|
|
32454
|
+
return GROUPBY[c4.channel] && c4.field ? c4.as : [];
|
|
32455
|
+
});
|
|
32456
|
+
return binLinear1d(q, x3, v2, g);
|
|
32408
32457
|
}
|
|
32409
32458
|
queryResult(data) {
|
|
32410
|
-
const
|
|
32411
|
-
this.
|
|
32459
|
+
const c4 = toDataColumns(data).columns;
|
|
32460
|
+
this.grids = grid1d(this.bins, c4.index, c4.density, c4, this.groupby);
|
|
32412
32461
|
return this.convolve();
|
|
32413
32462
|
}
|
|
32414
32463
|
convolve() {
|
|
32415
|
-
const {
|
|
32416
|
-
|
|
32464
|
+
const {
|
|
32465
|
+
bins: bins2,
|
|
32466
|
+
bandwidth,
|
|
32467
|
+
normalize: normalize4,
|
|
32468
|
+
dim,
|
|
32469
|
+
grids,
|
|
32470
|
+
groupby,
|
|
32471
|
+
plot: plot2,
|
|
32472
|
+
extent: [lo, hi]
|
|
32473
|
+
} = this;
|
|
32474
|
+
const cols = grids.columns;
|
|
32475
|
+
const numGrids = grids.numRows;
|
|
32476
|
+
const b = this.channelField(dim).as;
|
|
32477
|
+
const v2 = dim === "x" ? "y" : "x";
|
|
32417
32478
|
const size = dim === "x" ? plot2.innerWidth() : plot2.innerHeight();
|
|
32479
|
+
const neg = cols._grid.some((grid) => grid.some((v3) => v3 < 0));
|
|
32418
32480
|
const config = dericheConfig(bandwidth * (bins2 - 1) / size, neg);
|
|
32419
|
-
const result = dericheConv1d(config, grid, bins2);
|
|
32420
|
-
const v2 = dim === "x" ? "y" : "x";
|
|
32421
|
-
const b = this.channelField(dim).as;
|
|
32422
32481
|
const b0 = +lo;
|
|
32423
32482
|
const delta = (hi - b0) / (bins2 - 1);
|
|
32424
|
-
const
|
|
32425
|
-
const _b = new Float64Array(
|
|
32426
|
-
const _v = new Float64Array(
|
|
32427
|
-
|
|
32428
|
-
|
|
32429
|
-
|
|
32430
|
-
|
|
32431
|
-
|
|
32483
|
+
const numRows = bins2 * numGrids;
|
|
32484
|
+
const _b = new Float64Array(numRows);
|
|
32485
|
+
const _v = new Float64Array(numRows);
|
|
32486
|
+
const _g = groupby.reduce((m, name) => (m[name] = Array(numRows), m), {});
|
|
32487
|
+
for (let k2 = 0, g = 0; g < numGrids; ++g) {
|
|
32488
|
+
groupby.forEach((name) => _g[name].fill(cols[name][g], k2, k2 + bins2));
|
|
32489
|
+
const grid = cols._grid[g];
|
|
32490
|
+
const result = dericheConv1d(config, grid, bins2);
|
|
32491
|
+
const scale3 = 1 / norm(grid, result, delta, normalize4);
|
|
32492
|
+
for (let i = 0; i < bins2; ++i, ++k2) {
|
|
32493
|
+
_b[k2] = b0 + i * delta;
|
|
32494
|
+
_v[k2] = result[i] * scale3;
|
|
32495
|
+
}
|
|
32496
|
+
}
|
|
32497
|
+
this.data = { numRows, columns: { [b]: _b, [v2]: _v, ..._g } };
|
|
32432
32498
|
return this;
|
|
32433
32499
|
}
|
|
32434
32500
|
plotSpecs() {
|
|
32435
|
-
const { type: type2, data: { numRows: length4, columns }, channels, dim } = this;
|
|
32436
|
-
const
|
|
32501
|
+
const { type: type2, data: { numRows: length4, columns }, channels, dim, stack: stack2 } = this;
|
|
32502
|
+
const _ = type2.startsWith("area") && !stack2 ? "2" : "";
|
|
32503
|
+
const options = dim === "x" ? { [`y${_}`]: columns.y } : { [`x${_}`]: columns.x };
|
|
32437
32504
|
for (const c4 of channels) {
|
|
32438
32505
|
options[c4.channel] = channelOption(c4, columns);
|
|
32439
32506
|
}
|
|
32440
32507
|
return [{ type: type2, data: { length: length4 }, options }];
|
|
32441
32508
|
}
|
|
32442
32509
|
};
|
|
32510
|
+
function norm(grid, smoothed, delta, type2) {
|
|
32511
|
+
const value = type2 === true || type2 === "sum" ? sum2(grid) : type2 === "max" ? max2(smoothed) : delta;
|
|
32512
|
+
return value || 1;
|
|
32513
|
+
}
|
|
32443
32514
|
|
|
32444
32515
|
// src/marks/Density2DMark.js
|
|
32445
32516
|
var Density2DMark = class extends Grid2DMark {
|
|
@@ -32811,13 +32882,16 @@ var HexbinMark = class extends Mark2 {
|
|
|
32811
32882
|
float642(x12),
|
|
32812
32883
|
div(add(mul(add(x3, mul(0.5, bitAnd(y3, 1))), dx), ox2), xr)
|
|
32813
32884
|
),
|
|
32814
|
-
[yc.as]: sub(
|
|
32885
|
+
[yc.as]: sub(
|
|
32886
|
+
float642(y22),
|
|
32887
|
+
div(add(mul(y3, dy), oy2), yr)
|
|
32888
|
+
),
|
|
32815
32889
|
...cols
|
|
32816
32890
|
}).groupby(x3, y3, ...dims).from(
|
|
32817
32891
|
// Subquery performs hex binning in screen space and also passes
|
|
32818
32892
|
// original columns through (the DB should optimize this).
|
|
32819
32893
|
Query.select({
|
|
32820
|
-
[py]: div(mul(yr, sub(
|
|
32894
|
+
[py]: div(sub(mul(yr, sub(y22, yc.field)), oy2), dy),
|
|
32821
32895
|
[pj]: int322(round(py)),
|
|
32822
32896
|
[px]: sub(
|
|
32823
32897
|
div(sub(mul(xr, sub(xc.field, x12)), ox2), dx),
|