@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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # mosaic-plot
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@uwdata/mosaic-plot.svg)](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.
@@ -103,7 +103,7 @@ var require_search_bounds = __commonJS({
103
103
  }
104
104
  return -1;
105
105
  }
106
- function norm(a2, y3, c4, l, h, f) {
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 norm(a2, y3, c4, l, h, ge);
114
+ return norm2(a2, y3, c4, l, h, ge);
115
115
  },
116
116
  gt: function(a2, y3, c4, l, h) {
117
- return norm(a2, y3, c4, l, h, gt2);
117
+ return norm2(a2, y3, c4, l, h, gt2);
118
118
  },
119
119
  lt: function(a2, y3, c4, l, h) {
120
- return norm(a2, y3, c4, l, h, lt2);
120
+ return norm2(a2, y3, c4, l, h, lt2);
121
121
  },
122
122
  le: function(a2, y3, c4, l, h) {
123
- return norm(a2, y3, c4, l, h, le);
123
+ return norm2(a2, y3, c4, l, h, le);
124
124
  },
125
125
  eq: function(a2, y3, c4, l, h) {
126
- return norm(a2, y3, c4, l, h, eq2);
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 {object[]|null} The fields to retrieve info for.
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 {*} info The field info result.
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.aggregate ? sql`ALL` : []);
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 (!(stats?.length || stats?.size)) return info;
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 Array.from(result).map((desc2) => ({
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 ? isNotDistinct(field3, literal(value)) : null;
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]) => ({ table, column: c4, stats: 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 G = array3(size, value);
31519
- const n = value.length;
31520
- for (let i = 0; i < n; ++i) {
31521
- G[index2[i]] = value[i];
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 G;
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 { bins: bins2 = 1024, bandwidth = 20, ...channels } = options;
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.grid ? this.convolve().update() : null;
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
- return binLinear1d(q, x3, v2);
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 { columns: { index: index2, density: density2 } } = toDataColumns(data);
32411
- this.grid = grid1d(this.bins, index2, density2);
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 { bins: bins2, bandwidth, dim, grid, plot: plot2, extent: [lo, hi] } = this;
32416
- const neg = grid.some((v3) => v3 < 0);
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 scale3 = 1 / delta;
32425
- const _b = new Float64Array(bins2);
32426
- const _v = new Float64Array(bins2);
32427
- for (let i = 0; i < bins2; ++i) {
32428
- _b[i] = b0 + i * delta;
32429
- _v[i] = result[i] * scale3;
32430
- }
32431
- this.data = { numRows: bins2, columns: { [b]: _b, [v2]: _v } };
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 options = dim === "x" ? { y: columns.y } : { x: columns.x };
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(float642(y22), div(add(mul(y3, dy), oy2), yr)),
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(sub(y22, yc.field), oy2)), dy),
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),