@uwdata/mosaic-plot 0.12.2 → 0.14.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.
@@ -489,169 +489,9 @@ var Fixed = Symbol("Fixed");
489
489
  var Transient = Symbol("Transient");
490
490
  var Transform = Symbol("Transform");
491
491
 
492
- // ../core/src/util/throttle.js
493
- var NIL = {};
494
- function throttle(callback, debounce = false) {
495
- let curr;
496
- let next;
497
- let pending = NIL;
498
- function invoke(event) {
499
- curr = callback(event).catch(() => {
500
- }).finally(() => {
501
- if (next) {
502
- const { value } = next;
503
- next = null;
504
- invoke(value);
505
- } else {
506
- curr = null;
507
- }
508
- });
509
- }
510
- function enqueue(event) {
511
- next = { event };
512
- }
513
- function process(event) {
514
- curr ? enqueue(event) : invoke(event);
515
- }
516
- function delay(event) {
517
- if (pending !== event) {
518
- requestAnimationFrame(() => {
519
- const e = pending;
520
- pending = NIL;
521
- process(e);
522
- });
523
- }
524
- pending = event;
525
- }
526
- return debounce ? delay : process;
527
- }
528
-
529
- // ../core/src/MosaicClient.js
530
- var MosaicClient = class {
531
- /**
532
- * Constructor.
533
- * @param {*} filterSelection An optional selection to interactively filter
534
- * this client's data. If provided, a coordinator will re-query and update
535
- * the client when the selection updates.
536
- */
537
- constructor(filterSelection) {
538
- this._filterBy = filterSelection;
539
- this._requestUpdate = throttle(() => this.requestQuery(), true);
540
- this._coordinator = null;
541
- }
542
- /**
543
- * Return this client's connected coordinator.
544
- */
545
- get coordinator() {
546
- return this._coordinator;
547
- }
548
- /**
549
- * Set this client's connected coordinator.
550
- */
551
- set coordinator(coordinator2) {
552
- this._coordinator = coordinator2;
553
- }
554
- /**
555
- * Return this client's filter selection.
556
- */
557
- get filterBy() {
558
- return this._filterBy;
559
- }
560
- /**
561
- * Return a boolean indicating if the client query can be sped up with
562
- * materialized views of pre-aggregated data. Should return true if changes to
563
- * the filterBy selection does not change the groupby domain of the client
564
- * query.
565
- */
566
- get filterStable() {
567
- return true;
568
- }
569
- /**
570
- * Return an array of fields queried by this client.
571
- * @returns {import('./types.js').FieldInfoRequest[] | null}
572
- * The fields to retrieve info for.
573
- */
574
- fields() {
575
- return null;
576
- }
577
- /**
578
- * Called by the coordinator to set the field info for this client.
579
- * @param {import('./types.js').FieldInfo[]} info The field info result.
580
- * @returns {this}
581
- */
582
- fieldInfo(info) {
583
- return this;
584
- }
585
- /**
586
- * Return a query specifying the data needed by this client.
587
- * @param {*} [filter] The filtering criteria to apply in the query.
588
- * @returns {*} The client query
589
- */
590
- query(filter3) {
591
- return null;
592
- }
593
- /**
594
- * Called by the coordinator to inform the client that a query is pending.
595
- * @returns {this}
596
- */
597
- queryPending() {
598
- return this;
599
- }
600
- /**
601
- * Called by the coordinator to return a query result.
602
- * @param {*} data The query result.
603
- * @returns {this}
604
- */
605
- queryResult(data) {
606
- return this;
607
- }
608
- /**
609
- * Called by the coordinator to report a query execution error.
610
- * @param {*} error
611
- * @returns {this}
612
- */
613
- queryError(error) {
614
- return this;
615
- }
616
- /**
617
- * Request the coordinator to execute a query for this client.
618
- * If an explicit query is not provided, the client query method will
619
- * be called, filtered by the current filterBy selection.
620
- * @returns {Promise}
621
- */
622
- requestQuery(query) {
623
- const q = query || this.query(this.filterBy?.predicate(this));
624
- return this._coordinator.requestQuery(this, q);
625
- }
626
- /**
627
- * Request that the coordinator perform a throttled update of this client
628
- * using the default query. Unlike requestQuery, for which every call will
629
- * result in an executed query, multiple calls to requestUpdate may be
630
- * consolidated into a single update.
631
- */
632
- requestUpdate() {
633
- this._requestUpdate();
634
- }
635
- /**
636
- * Reset this client, initiating new field info and query requests.
637
- * @returns {Promise}
638
- */
639
- initialize() {
640
- return this._coordinator.initializeClient(this);
641
- }
642
- /**
643
- * Requests a client update.
644
- * For example to (re-)render an interface component.
645
- *
646
- * @returns {this | Promise<any>}
647
- */
648
- update() {
649
- return this;
650
- }
651
- };
652
-
653
492
  // ../../node_modules/@uwdata/flechette/src/constants.js
654
493
  var MAGIC = Uint8Array.of(65, 82, 82, 79, 87, 49);
494
+ var EOS = Uint8Array.of(255, 255, 255, 255, 0, 0, 0, 0);
655
495
  var Version = (
656
496
  /** @type {const} */
657
497
  {
@@ -3066,7 +2906,7 @@ function literalToSQL(value) {
3066
2906
  case "number":
3067
2907
  return Number.isFinite(value) ? `${value}` : "NULL";
3068
2908
  case "string":
3069
- return `'${value.replace(`'`, `''`)}'`;
2909
+ return `'${value.replaceAll(`'`, `''`)}'`;
3070
2910
  case "boolean":
3071
2911
  return value ? "TRUE" : "FALSE";
3072
2912
  default:
@@ -5140,6 +4980,8 @@ function sufficientStatistics(node, preagg, avg2) {
5140
4980
  return sumExpr(preagg, node);
5141
4981
  case "avg":
5142
4982
  return avgExpr(preagg, node);
4983
+ case "geomean":
4984
+ return geomeanExpr(preagg, node);
5143
4985
  case "arg_max":
5144
4986
  return argmaxExpr(preagg, node);
5145
4987
  case "arg_min":
@@ -5223,6 +5065,12 @@ function avgExpr(preagg, node) {
5223
5065
  const { expr, name } = countExpr(preagg, node);
5224
5066
  return div(sum(mul(as, name)), expr);
5225
5067
  }
5068
+ function geomeanExpr(preagg, node) {
5069
+ const x3 = node.args[0];
5070
+ const expr = addStat(preagg, sum(ln(x3)), node);
5071
+ const { expr: n } = countExpr(preagg, node);
5072
+ return exp(div(sum(expr), n));
5073
+ }
5226
5074
  function argmaxExpr(preagg, node) {
5227
5075
  const expr = addStat(preagg, node);
5228
5076
  const maxy = addStat(preagg, max(node.args[1]), node);
@@ -6295,13 +6143,13 @@ var Coordinator = class {
6295
6143
  }
6296
6144
  /**
6297
6145
  * Issue a query for which no result (return value) is needed.
6298
- * @param {QueryType | QueryType[]} query The query or an array of queries.
6146
+ * @param { import('./types.js').QueryType[] |
6147
+ * import('./types.js').QueryType} query The query or an array of queries.
6299
6148
  * Each query should be either a Query builder object or a SQL string.
6300
6149
  * @param {object} [options] An options object.
6301
6150
  * @param {number} [options.priority] The query priority, defaults to
6302
6151
  * `Priority.Normal`.
6303
- * @returns {QueryResult} A query result
6304
- * promise.
6152
+ * @returns {QueryResult} A query result promise.
6305
6153
  */
6306
6154
  exec(query, { priority = Priority.Normal } = {}) {
6307
6155
  query = Array.isArray(query) ? query.filter((x3) => x3).join(";\n") : query;
@@ -6310,8 +6158,8 @@ var Coordinator = class {
6310
6158
  /**
6311
6159
  * Issue a query to the backing database. The submitted query may be
6312
6160
  * consolidate with other queries and its results may be cached.
6313
- * @param {QueryType} query The query as either a Query builder object
6314
- * or a SQL string.
6161
+ * @param {import('./types.js').QueryType} query The query as either a Query
6162
+ * builder object or a SQL string.
6315
6163
  * @param {object} [options] An options object.
6316
6164
  * @param {'arrow' | 'json'} [options.type] The query result format type.
6317
6165
  * @param {boolean} [options.cache=true] If true, cache the query result
@@ -6333,8 +6181,8 @@ var Coordinator = class {
6333
6181
  /**
6334
6182
  * Issue a query to prefetch data for later use. The query result is cached
6335
6183
  * for efficient future access.
6336
- * @param {QueryType} query The query as either a Query builder object
6337
- * or a SQL string.
6184
+ * @param {import('./types.js').QueryType} query The query as either a Query
6185
+ * builder object or a SQL string.
6338
6186
  * @param {object} [options] An options object.
6339
6187
  * @param {'arrow' | 'json'} [options.type] The query result format type.
6340
6188
  * @returns {QueryResult} A query result promise.
@@ -6369,13 +6217,13 @@ var Coordinator = class {
6369
6217
  * Update client data by submitting the given query and returning the
6370
6218
  * data (or error) to the client.
6371
6219
  * @param {MosaicClient} client A Mosaic client.
6372
- * @param {QueryType} query The data query.
6220
+ * @param {import('./types.js').QueryType} query The data query.
6373
6221
  * @param {number} [priority] The query priority.
6374
6222
  * @returns {Promise} A Promise that resolves upon completion of the update.
6375
6223
  */
6376
6224
  updateClient(client, query, priority = Priority.Normal) {
6377
6225
  client.queryPending();
6378
- return this.query(query, { priority }).then(
6226
+ return client._pending = this.query(query, { priority }).then(
6379
6227
  (data) => client.queryResult(data).update(),
6380
6228
  (err) => {
6381
6229
  this._logger.error(err);
@@ -6388,7 +6236,7 @@ var Coordinator = class {
6388
6236
  * the client is simply updated. Otherwise `updateClient` is called. As a
6389
6237
  * side effect, this method clears the current preaggregator state.
6390
6238
  * @param {MosaicClient} client The client to update.
6391
- * @param {QueryType | null} [query] The query to issue.
6239
+ * @param {import('./types.js').QueryType | null} [query] The query to issue.
6392
6240
  */
6393
6241
  requestQuery(client, query) {
6394
6242
  this.preaggregator.clear();
@@ -6405,7 +6253,7 @@ var Coordinator = class {
6405
6253
  }
6406
6254
  clients.add(client);
6407
6255
  client.coordinator = this;
6408
- this.initializeClient(client);
6256
+ client._pending = this.initializeClient(client);
6409
6257
  connectSelection(this, client.filterBy, client);
6410
6258
  }
6411
6259
  async initializeClient(client) {
@@ -6413,6 +6261,7 @@ var Coordinator = class {
6413
6261
  if (fields?.length) {
6414
6262
  client.fieldInfo(await queryFieldInfo(this, fields));
6415
6263
  }
6264
+ await client.prepare();
6416
6265
  return client.requestQuery();
6417
6266
  }
6418
6267
  /**
@@ -7032,6 +6881,179 @@ var SelectionResolver = class {
7032
6881
  }
7033
6882
  };
7034
6883
 
6884
+ // ../core/src/util/throttle.js
6885
+ var NIL = {};
6886
+ function throttle(callback, debounce = false) {
6887
+ let curr;
6888
+ let next;
6889
+ let pending = NIL;
6890
+ function invoke(event) {
6891
+ curr = callback(event).catch(() => {
6892
+ }).finally(() => {
6893
+ if (next) {
6894
+ const { value } = next;
6895
+ next = null;
6896
+ invoke(value);
6897
+ } else {
6898
+ curr = null;
6899
+ }
6900
+ });
6901
+ }
6902
+ function enqueue(event) {
6903
+ next = { event };
6904
+ }
6905
+ function process(event) {
6906
+ curr ? enqueue(event) : invoke(event);
6907
+ }
6908
+ function delay(event) {
6909
+ if (pending !== event) {
6910
+ requestAnimationFrame(() => {
6911
+ const e = pending;
6912
+ pending = NIL;
6913
+ process(e);
6914
+ });
6915
+ }
6916
+ pending = event;
6917
+ }
6918
+ return debounce ? delay : process;
6919
+ }
6920
+
6921
+ // ../core/src/MosaicClient.js
6922
+ var MosaicClient = class {
6923
+ /**
6924
+ * Constructor.
6925
+ * @param {*} filterSelection An optional selection to interactively filter
6926
+ * this client's data. If provided, a coordinator will re-query and update
6927
+ * the client when the selection updates.
6928
+ */
6929
+ constructor(filterSelection) {
6930
+ this._filterBy = filterSelection;
6931
+ this._requestUpdate = throttle(() => this.requestQuery(), true);
6932
+ this._coordinator = null;
6933
+ this._pending = Promise.resolve();
6934
+ }
6935
+ /**
6936
+ * Return this client's connected coordinator.
6937
+ */
6938
+ get coordinator() {
6939
+ return this._coordinator;
6940
+ }
6941
+ /**
6942
+ * Set this client's connected coordinator.
6943
+ */
6944
+ set coordinator(coordinator2) {
6945
+ this._coordinator = coordinator2;
6946
+ }
6947
+ /**
6948
+ * Return a Promise that resolves once the client has updated.
6949
+ */
6950
+ get pending() {
6951
+ return this._pending;
6952
+ }
6953
+ /**
6954
+ * Return this client's filter selection.
6955
+ */
6956
+ get filterBy() {
6957
+ return this._filterBy;
6958
+ }
6959
+ /**
6960
+ * Return a boolean indicating if the client query can be sped up with
6961
+ * materialized views of pre-aggregated data. Should return true if changes to
6962
+ * the filterBy selection does not change the groupby domain of the client
6963
+ * query.
6964
+ */
6965
+ get filterStable() {
6966
+ return true;
6967
+ }
6968
+ /**
6969
+ * Return an array of fields queried by this client.
6970
+ * @returns {import('./types.js').FieldInfoRequest[] | null}
6971
+ * The fields to retrieve info for.
6972
+ */
6973
+ fields() {
6974
+ return null;
6975
+ }
6976
+ /**
6977
+ * Called by the coordinator to set the field info for this client.
6978
+ * @param {import('./types.js').FieldInfo[]} info The field info result.
6979
+ * @returns {this}
6980
+ */
6981
+ fieldInfo(info) {
6982
+ return this;
6983
+ }
6984
+ /**
6985
+ * Prepare the client before the query() method is called.
6986
+ */
6987
+ async prepare() {
6988
+ }
6989
+ /**
6990
+ * Return a query specifying the data needed by this client.
6991
+ * @param {*} [filter] The filtering criteria to apply in the query.
6992
+ * @returns {*} The client query
6993
+ */
6994
+ query(filter3) {
6995
+ return null;
6996
+ }
6997
+ /**
6998
+ * Called by the coordinator to inform the client that a query is pending.
6999
+ * @returns {this}
7000
+ */
7001
+ queryPending() {
7002
+ return this;
7003
+ }
7004
+ /**
7005
+ * Called by the coordinator to return a query result.
7006
+ * @param {*} data The query result.
7007
+ * @returns {this}
7008
+ */
7009
+ queryResult(data) {
7010
+ return this;
7011
+ }
7012
+ /**
7013
+ * Called by the coordinator to report a query execution error.
7014
+ * @param {*} error
7015
+ * @returns {this}
7016
+ */
7017
+ queryError(error) {
7018
+ return this;
7019
+ }
7020
+ /**
7021
+ * Request the coordinator to execute a query for this client.
7022
+ * If an explicit query is not provided, the client query method will
7023
+ * be called, filtered by the current filterBy selection.
7024
+ * @returns {Promise}
7025
+ */
7026
+ requestQuery(query) {
7027
+ const q = query || this.query(this.filterBy?.predicate(this));
7028
+ return this._coordinator.requestQuery(this, q);
7029
+ }
7030
+ /**
7031
+ * Request that the coordinator perform a throttled update of this client
7032
+ * using the default query. Unlike requestQuery, for which every call will
7033
+ * result in an executed query, multiple calls to requestUpdate may be
7034
+ * consolidated into a single update.
7035
+ */
7036
+ requestUpdate() {
7037
+ this._requestUpdate();
7038
+ }
7039
+ /**
7040
+ * Reset this client, initiating new field info, call the prepare method, and query requests.
7041
+ * @returns {Promise}
7042
+ */
7043
+ initialize() {
7044
+ return this._coordinator.initializeClient(this);
7045
+ }
7046
+ /**
7047
+ * Requests a client update.
7048
+ * For example to (re-)render an interface component.
7049
+ *
7050
+ * @returns {this | Promise<any>}
7051
+ */
7052
+ update() {
7053
+ return this;
7054
+ }
7055
+ };
7056
+
7035
7057
  // ../core/src/SelectionClause.js
7036
7058
  function clausePoint(field3, value, {
7037
7059
  source,
@@ -30999,6 +31021,9 @@ var DEFAULT_ATTRIBUTES = {
30999
31021
  marginBottom: 30
31000
31022
  };
31001
31023
  var Plot = class {
31024
+ /**
31025
+ * @param {HTMLElement} [element]
31026
+ */
31002
31027
  constructor(element) {
31003
31028
  this.attributes = { ...DEFAULT_ATTRIBUTES };
31004
31029
  this.listeners = null;
@@ -31006,12 +31031,12 @@ var Plot = class {
31006
31031
  this.legends = [];
31007
31032
  this.marks = [];
31008
31033
  this.markset = null;
31034
+ this.params = /* @__PURE__ */ new Map();
31035
+ this.synch = synchronizer();
31009
31036
  this.element = element || document.createElement("div");
31010
31037
  this.element.setAttribute("class", "plot");
31011
31038
  this.element.style.display = "flex";
31012
- this.element.value = this;
31013
- this.params = /* @__PURE__ */ new Map();
31014
- this.synch = synchronizer();
31039
+ Object.assign(this.element, { value: this });
31015
31040
  }
31016
31041
  margins() {
31017
31042
  return {
@@ -31386,7 +31411,7 @@ function markQuery(channels, table, skip = []) {
31386
31411
  const { channel, field: field3, as } = c4;
31387
31412
  if (skip.includes(channel)) continue;
31388
31413
  if (channel === "orderby") {
31389
- q.orderby(c4.value);
31414
+ q.orderby(c4.value ?? field3);
31390
31415
  } else if (field3) {
31391
31416
  if (isAggregateExpression(field3)) {
31392
31417
  aggr = true;
@@ -31518,7 +31543,7 @@ var ConnectedMark = class extends Mark2 {
31518
31543
  const cols = q._select.map((c4) => c4.alias).filter((c4) => c4 !== as && c4 !== value);
31519
31544
  return m4(q, expr, as, value, cols);
31520
31545
  } else {
31521
- return q.orderby(field3);
31546
+ return q.orderby(as);
31522
31547
  }
31523
31548
  }
31524
31549
  };
@@ -33678,12 +33703,13 @@ var Nearest = class {
33678
33703
  selection2.update(that.clause(void 0));
33679
33704
  });
33680
33705
  svg.addEventListener("pointerenter", (evt) => {
33681
- if (!evt.buttons) {
33682
- const v2 = this.channels.map(() => 0);
33683
- selection2.activate(this.clause(v2));
33684
- }
33706
+ if (!evt.buttons) this.activate();
33685
33707
  });
33686
33708
  }
33709
+ activate() {
33710
+ const v2 = this.channels.map(() => 0);
33711
+ this.selection.activate(this.clause(v2));
33712
+ }
33687
33713
  };
33688
33714
  function calculateXY(svg, mark) {
33689
33715
  const { data: { columns } } = mark;
@@ -33779,7 +33805,7 @@ var PanZoom = class {
33779
33805
  this.svg = svg;
33780
33806
  if (this.initialized) return;
33781
33807
  else this.initialized = true;
33782
- const { panx, pany, mark: { plot: { element } }, xsel, ysel } = this;
33808
+ const { panx, pany, mark: { plot: { element } } } = this;
33783
33809
  this.xscale = svg.scale("x");
33784
33810
  this.yscale = svg.scale("y");
33785
33811
  const rx = this.xscale.range.slice().sort(asc2);
@@ -33796,19 +33822,21 @@ var PanZoom = class {
33796
33822
  element.addEventListener("pointerenter", (evt) => {
33797
33823
  if (enter) return;
33798
33824
  else enter = true;
33799
- if (evt.buttons) return;
33800
- if (panx) {
33801
- const { xscale, xfield } = this;
33802
- xsel.activate(this.clause(xscale.domain, xfield, xscale));
33803
- }
33804
- if (pany) {
33805
- const { yscale, yfield } = this;
33806
- ysel.activate(this.clause(yscale.domain, yfield, yscale));
33807
- }
33825
+ if (!evt.buttons) this.activate();
33808
33826
  });
33809
33827
  element.addEventListener("pointerleave", () => enter = false);
33810
33828
  }
33811
33829
  }
33830
+ activate() {
33831
+ if (this.panx) {
33832
+ const { xscale, xfield } = this;
33833
+ this.xsel.activate(this.clause(xscale.domain, xfield, xscale));
33834
+ }
33835
+ if (this.pany) {
33836
+ const { yscale, yfield } = this;
33837
+ this.ysel.activate(this.clause(yscale.domain, yfield, yscale));
33838
+ }
33839
+ }
33812
33840
  };
33813
33841
  function extent3(ext, defaultTrue, defaultFalse) {
33814
33842
  return ext ? Array.isArray(ext) ? ext : defaultTrue : defaultFalse;
@@ -34193,8 +34221,8 @@ var Toggle = class {
34193
34221
  channels,
34194
34222
  peers = true
34195
34223
  }) {
34196
- this.value = null;
34197
34224
  this.mark = mark;
34225
+ this.value = null;
34198
34226
  this.selection = selection2;
34199
34227
  this.peers = peers;
34200
34228
  const fields = this.fields = [];
@@ -34246,10 +34274,12 @@ var Toggle = class {
34246
34274
  }
34247
34275
  });
34248
34276
  svg.addEventListener("pointerenter", (evt) => {
34249
- if (evt.buttons) return;
34250
- this.selection.activate(this.clause([this.fields.map(() => 0)]));
34277
+ if (!evt.buttons) this.activate();
34251
34278
  });
34252
34279
  }
34280
+ activate() {
34281
+ this.selection.activate(this.clause([this.fields.map(() => 0)]));
34282
+ }
34253
34283
  };
34254
34284
  function isTargetElement(groups2, node) {
34255
34285
  return groups2.some((g) => g.contains(node));