@bicharts/chart-host 0.6.113 → 0.6.116

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 (40) hide show
  1. package/README.md +94 -5
  2. package/dist/adapters.mjs +3 -3
  3. package/dist/card.mjs +2 -2
  4. package/dist/category-colours.mjs +275 -0
  5. package/dist/{chunk-7XT5OJ6M.mjs → chunk-63IUMWY2.mjs} +40 -3
  6. package/dist/{chunk-AEO5SFEQ.mjs → chunk-EFKGSDFF.mjs} +1 -1
  7. package/dist/{chunk-TE54AFRN.mjs → chunk-GZSYTDFN.mjs} +531 -393
  8. package/dist/{chunk-PELRAFYZ.mjs → chunk-IW6BKFF5.mjs} +1 -1
  9. package/dist/{chunk-OLCUKXKL.mjs → chunk-JMLSNV5Q.mjs} +1 -1
  10. package/dist/chunk-JTZTTJJK.mjs +244 -0
  11. package/dist/{chunk-FIG5DIW7.mjs → chunk-K2TH7D5V.mjs} +335 -335
  12. package/dist/{chunk-KY4FI2HA.mjs → chunk-QG5TENI3.mjs} +5 -1
  13. package/dist/{chunk-KGVNZR23.mjs → chunk-RLOIA7QK.mjs} +1 -1
  14. package/dist/{chunk-IL5M7K6Z.mjs → chunk-TUZCM7SX.mjs} +2 -2
  15. package/dist/{chunk-GTJG3SMD.mjs → chunk-VGJH63LZ.mjs} +1 -1
  16. package/dist/{chunk-2KLFIGTQ.mjs → chunk-ZZBX5ZJ6.mjs} +145 -18
  17. package/dist/dax.mjs +23 -1
  18. package/dist/index.mjs +97 -16
  19. package/dist/legend-fit.mjs +3 -3
  20. package/dist/page.mjs +7 -5
  21. package/dist/provinces.mjs +4 -4
  22. package/dist/react.mjs +18 -14
  23. package/dist/testing.mjs +1 -1
  24. package/dist/types/categoryColours.d.ts +71 -0
  25. package/dist/types/contract.d.ts +16 -3
  26. package/dist/types/dax.d.ts +26 -0
  27. package/dist/types/filterScope.d.ts +52 -4
  28. package/dist/types/fit.d.ts +6 -1
  29. package/dist/types/fitDom.d.ts +13 -0
  30. package/dist/types/host.d.ts +19 -5
  31. package/dist/types/index.d.ts +7 -4
  32. package/dist/types/keyboardMarks.d.ts +1 -1
  33. package/dist/types/markFollower.d.ts +21 -0
  34. package/dist/types/page.d.ts +1 -1
  35. package/dist/types/qualifyTable.d.ts +52 -0
  36. package/dist/types/react.d.ts +11 -3
  37. package/dist/types/selectionMarker.d.ts +92 -0
  38. package/dist/zoom-memory.mjs +1 -1
  39. package/package.json +6 -1
  40. /package/dist/{chunk-5YSZABCS.mjs → chunk-LXTW6YII.mjs} +0 -0
@@ -1,7 +1,7 @@
1
1
  /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
2
 
3
3
  // src/contract.ts
4
- var HOST_CONTRACT_VERSION = "1.23.0";
4
+ var HOST_CONTRACT_VERSION = "1.24.0";
5
5
  var GEO_POINT_PRECISIONS = ["latlon", "city", "zip3", "state", "country"];
6
6
  var MARK_CLASS = "d3-mark";
7
7
  var LEGEND_MARK_CLASS = "d3-legend-mark";
@@ -22,6 +22,7 @@ var CONTAINER_SLOT_XF_CLEAR = "__llmXfClear";
22
22
  var CONTAINER_SLOT_INITIAL_XF_MARK = "__llmInitialXfMark";
23
23
  var CONTAINER_SLOT_UI_STATE = "__lchUiState";
24
24
  var CONTAINER_SLOT_SCROLL = "__lchScroll";
25
+ var TABLE_SCROLLER_CLASS = "lch-table-body";
25
26
  var CONTAINER_SLOT_CLICKED_MARKS = "__lchClickedMarks";
26
27
  var VIEW_ONLY_UI_STATE_KEYS = Object.freeze(["camera", "llmZoom"]);
27
28
  function withoutViewOnlyKeys(bag) {
@@ -39,6 +40,7 @@ var ACTIVE_TICK_CLASS = "lch-tick-active";
39
40
  var DIM_OPACITY_VAR = "--lch-dim-opacity";
40
41
  var DIM_OPACITY_DEFAULT = 0.25;
41
42
  var DIM_OPACITY_MIN = 0.01;
43
+ var CATEGORY_DELTA_E_FLOOR = 25;
42
44
  function chartOwnsTimeline(container) {
43
45
  return !!container && (!!container[CONTAINER_SLOT_XF_CLEAR] || !!container[CONTAINER_SLOT_ANIM_STOP]);
44
46
  }
@@ -96,6 +98,7 @@ export {
96
98
  CONTAINER_SLOT_INITIAL_XF_MARK,
97
99
  CONTAINER_SLOT_UI_STATE,
98
100
  CONTAINER_SLOT_SCROLL,
101
+ TABLE_SCROLLER_CLASS,
99
102
  CONTAINER_SLOT_CLICKED_MARKS,
100
103
  VIEW_ONLY_UI_STATE_KEYS,
101
104
  withoutViewOnlyKeys,
@@ -107,6 +110,7 @@ export {
107
110
  DIM_OPACITY_VAR,
108
111
  DIM_OPACITY_DEFAULT,
109
112
  DIM_OPACITY_MIN,
113
+ CATEGORY_DELTA_E_FLOOR,
110
114
  chartOwnsTimeline,
111
115
  periodTickSuppressesFeedback,
112
116
  ANIM_PLAY_SPEED_DEFAULT,
@@ -8,7 +8,7 @@ import {
8
8
  SELECTION_RULE_ATTR,
9
9
  SELECTION_RULE_CLICKED_MARKS,
10
10
  XFILTER_REFRESH_EVENT
11
- } from "./chunk-KY4FI2HA.mjs";
11
+ } from "./chunk-QG5TENI3.mjs";
12
12
 
13
13
  // src/selection.ts
14
14
  function rowIdxsFromMark(el) {
@@ -4,7 +4,7 @@ import {
4
4
  gestureWasDrag,
5
5
  isInsideControl,
6
6
  rowIdxsFromMark
7
- } from "./chunk-KGVNZR23.mjs";
7
+ } from "./chunk-RLOIA7QK.mjs";
8
8
  import {
9
9
  AXIS_FILTER_CLASS,
10
10
  CONTROL_CLASS,
@@ -12,7 +12,7 @@ import {
12
12
  MARK_CLASS,
13
13
  ROW_IDX_ATTR,
14
14
  SNAPSHOT_SVG_ATTR
15
- } from "./chunk-KY4FI2HA.mjs";
15
+ } from "./chunk-QG5TENI3.mjs";
16
16
 
17
17
  // src/lasso.ts
18
18
  var LASSO_ROW_CAP = 5e3;
@@ -1,7 +1,7 @@
1
1
  /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
2
  import {
3
3
  LASSO_KNOB_KEY
4
- } from "./chunk-IL5M7K6Z.mjs";
4
+ } from "./chunk-TUZCM7SX.mjs";
5
5
 
6
6
  // src/controls.ts
7
7
  var isKnob = (key) => key !== LASSO_KNOB_KEY;
@@ -43,6 +43,11 @@ function toKeys(input, width) {
43
43
  return input.length === width ? [input.slice()] : [];
44
44
  }
45
45
  var nextId = 0;
46
+ var peersOf = /* @__PURE__ */ new WeakMap();
47
+ var pickSubsOf = /* @__PURE__ */ new WeakMap();
48
+ var firePick = (f) => {
49
+ for (const m of Array.from(peersOf.get(f) ?? [f])) for (const cb of Array.from(pickSubsOf.get(m) ?? [])) cb();
50
+ };
46
51
  function createFilter(columns, opts = {}) {
47
52
  const cols = Array.isArray(columns) ? columns.slice() : [columns];
48
53
  if (!cols.length || cols.some((c) => typeof c !== "string" || !c)) {
@@ -71,7 +76,11 @@ function createFilter(columns, opts = {}) {
71
76
  rows = nextRows;
72
77
  reason = why;
73
78
  version++;
79
+ if (nextKeys.length) {
80
+ for (const p of Array.from(peersOf.get(filter) ?? [])) if (p !== filter && p.active) p.clear(why);
81
+ }
74
82
  for (const cb of Array.from(subs)) cb(filter, why);
83
+ firePick(filter);
75
84
  };
76
85
  const dedupe = (ks) => {
77
86
  const seen2 = /* @__PURE__ */ new Set();
@@ -94,6 +103,15 @@ function createFilter(columns, opts = {}) {
94
103
  const text = parts.filter(shown).map(String).join(" \u2192 ");
95
104
  return text || k.filter(shown).map(String).join(" \u2192 ");
96
105
  };
106
+ const partsOf = (k, more, names) => {
107
+ const s = keyString(k);
108
+ const pos = cols.map((c, i) => names.includes(c) && !isAnyValue(k[i]) ? i : -1).filter((i) => i >= 0);
109
+ const row = (more ?? []).find((r) => keyString(keyOfRow(r, cols)) === s) ?? seen.get(s);
110
+ const speaksFor = (d, j) => cols.includes(d) ? cols.indexOf(d) : display.length === cols.length ? j : -1;
111
+ const shown = (p) => p != null && p !== "" && !isAnyValue(p);
112
+ const text = row ? display.filter((d, j) => pos.includes(speaksFor(d, j))).map((d) => row[d]).filter(shown).map(String).join(" \u2192 ") : "";
113
+ return text || pos.map((i) => k[i]).filter(shown).map(String).join(" \u2192 ");
114
+ };
97
115
  let warnedKeep = false;
98
116
  const filter = {
99
117
  id: opts.id ?? `f${++nextId}`,
@@ -137,9 +155,9 @@ function createFilter(columns, opts = {}) {
137
155
  get text() {
138
156
  return keys.map((k) => displayOf(k)).join(", ");
139
157
  },
140
- textOf(key, more) {
141
- const k = Array.isArray(key) ? key : [key];
142
- return displayOf(k.slice(0, cols.length), more);
158
+ textOf(key, more, parts) {
159
+ const k = (Array.isArray(key) ? key : [key]).slice(0, cols.length);
160
+ return parts && cols.some((c) => !parts.includes(c)) ? partsOf(k, more, parts) : displayOf(k, more);
143
161
  },
144
162
  get reason() {
145
163
  return reason;
@@ -198,8 +216,23 @@ function createFilter(columns, opts = {}) {
198
216
  return () => {
199
217
  subs.delete(cb);
200
218
  };
219
+ },
220
+ group: opts.group || null,
221
+ get pick() {
222
+ const peers = peersOf.get(filter);
223
+ if (!peers) return keys.length ? filter : null;
224
+ for (const p of peers) if (p.active) return p;
225
+ return null;
226
+ },
227
+ onPick(cb) {
228
+ pickSubs.add(cb);
229
+ return () => {
230
+ pickSubs.delete(cb);
231
+ };
201
232
  }
202
233
  };
234
+ const pickSubs = /* @__PURE__ */ new Set();
235
+ pickSubsOf.set(filter, pickSubs);
203
236
  return filter;
204
237
  }
205
238
  function createFilterScope() {
@@ -211,6 +244,43 @@ function createFilterScope() {
211
244
  const notify = () => {
212
245
  for (const cb of Array.from(subs)) cb(scope);
213
246
  };
247
+ const groups = /* @__PURE__ */ new Map();
248
+ const names = /* @__PURE__ */ new Map();
249
+ const nameFor = (f) => {
250
+ const taken = new Set(names.values());
251
+ let k = f.key, n = 1;
252
+ while (taken.has(k)) k = `${f.key}-${++n}`;
253
+ names.set(f, k);
254
+ };
255
+ const nameOf = (f) => names.get(f) ?? f.key;
256
+ const join = (f) => {
257
+ const name = f.group;
258
+ if (!name) return;
259
+ const members = groups.get(name) ?? /* @__PURE__ */ new Set();
260
+ const first = members.values().next().value;
261
+ if (first && first.columns.join("") !== f.columns.join("")) {
262
+ try {
263
+ console.warn(`[chart-host] filter "${f.label}" is keyed by ${f.columns.join(", ")} but its group "${name}" by ${first.columns.join(", ")}, so it stays out of the group: give one pick's filters the same key columns.`);
264
+ } catch {
265
+ }
266
+ return;
267
+ }
268
+ if (f.active && [...members].some((m) => m.active)) f.clear("page");
269
+ members.add(f);
270
+ groups.set(name, members);
271
+ peersOf.set(f, members);
272
+ firePick(f);
273
+ };
274
+ const leave = (f) => {
275
+ const members = f.group ? groups.get(f.group) : void 0;
276
+ if (!members?.has(f)) return;
277
+ members.delete(f);
278
+ peersOf.delete(f);
279
+ if (!members.size) groups.delete(f.group);
280
+ firePick(f);
281
+ const rest = members.values().next().value;
282
+ if (rest) firePick(rest);
283
+ };
214
284
  const scope = {
215
285
  get filters() {
216
286
  return list.slice();
@@ -226,7 +296,9 @@ function createFilterScope() {
226
296
  add(f) {
227
297
  if (!offs.has(f)) {
228
298
  list.push(f);
299
+ nameFor(f);
229
300
  offs.set(f, f.onChange(() => notify()));
301
+ join(f);
230
302
  notify();
231
303
  }
232
304
  return () => {
@@ -236,6 +308,8 @@ function createFilterScope() {
236
308
  offs.delete(f);
237
309
  const i = list.indexOf(f);
238
310
  if (i >= 0) list.splice(i, 1);
311
+ names.delete(f);
312
+ leave(f);
239
313
  notify();
240
314
  };
241
315
  },
@@ -248,6 +322,9 @@ function createFilterScope() {
248
322
  subs.delete(cb);
249
323
  };
250
324
  },
325
+ keyOf(f) {
326
+ return offs.has(f) ? nameOf(f) : null;
327
+ },
251
328
  get controls() {
252
329
  return ctl;
253
330
  },
@@ -269,7 +346,7 @@ function createFilterScope() {
269
346
  },
270
347
  save() {
271
348
  const view = { v: 1 };
272
- for (const f of list) if (f.active) (view.filters ??= {})[f.key] = f.keys.map((k) => k.slice());
349
+ for (const f of list) if (f.active) (view.filters ??= {})[nameOf(f)] = f.keys.map((k) => k.slice());
273
350
  for (const [k, c] of ctl) {
274
351
  const values = c.values;
275
352
  if (Object.keys(values).length) (view.controls ??= {})[k] = { ...values };
@@ -278,10 +355,15 @@ function createFilterScope() {
278
355
  },
279
356
  restore(view) {
280
357
  if (!view || view.v !== 1) return;
358
+ const restored = /* @__PURE__ */ new Set();
281
359
  for (const f of list) {
282
- const keys = view.filters?.[f.key];
283
- if (keys && keys.length) f.set(keys, "page");
284
- else f.clear("page");
360
+ const keys = view.filters?.[nameOf(f)];
361
+ const g = f.group && peersOf.get(f) ? f.group : null;
362
+ if (g && restored.has(g)) continue;
363
+ if (keys && keys.length) {
364
+ f.set(keys, "page");
365
+ if (g) restored.add(g);
366
+ } else f.clear("page");
285
367
  }
286
368
  for (const [k, c] of ctl) {
287
369
  const values = view.controls?.[k];
@@ -291,19 +373,47 @@ function createFilterScope() {
291
373
  };
292
374
  return scope;
293
375
  }
376
+ function scopeFilterOf(src) {
377
+ if (!src || typeof src !== "object") return null;
378
+ if (Array.isArray(src.keys)) return src;
379
+ const f = src.filter;
380
+ return f && typeof f === "object" ? f : null;
381
+ }
382
+ var warnedTakes = /* @__PURE__ */ new Set();
294
383
  function filterScopeOf(filters) {
295
384
  const list = Array.isArray(filters) ? filters : filters ? [filters] : [];
296
- const active = list.filter((f) => f && f.active);
297
385
  const values = [];
298
- for (const f of active) {
386
+ const fields = [];
387
+ for (const src of list) {
388
+ const f = scopeFilterOf(src);
389
+ if (!f || !f.active) continue;
390
+ const entry = src === f ? null : src;
391
+ const takes = entry?.takes;
392
+ for (const c of takes ?? []) {
393
+ if (f.columns.includes(c) || warnedTakes.has(`${f.id}${c}`)) continue;
394
+ warnedTakes.add(`${f.id}${c}`);
395
+ try {
396
+ console.warn(`[chart-host] scopedBy: filter "${f.label}" isn't keyed by ${c} (its key: ${f.columns.join(", ")}), so its scope line can't take it - name the filter's own key columns in takes.`);
397
+ } catch {
398
+ }
399
+ }
400
+ const part = takes ? f.columns.filter((c) => takes.includes(c)) : f.columns;
401
+ const whole = part.length === f.columns.length;
402
+ if (!whole) {
403
+ const at = part.map((c) => f.columns.indexOf(c));
404
+ if (!at.length || f.keys.some((k) => at.every((i) => isAnyValue(k[i])))) continue;
405
+ }
406
+ let said = false;
299
407
  for (const k of f.keys) {
300
- const t = f.textOf(k).trim();
408
+ const t = (whole ? f.textOf(k) : f.textOf(k, void 0, part)).trim();
409
+ if (t) said = true;
301
410
  if (t && !values.includes(t)) values.push(t);
302
411
  }
412
+ if (!whole && !said) continue;
413
+ const label = entry?.label || (whole || f.label !== f.columns.join(" / ") ? f.label : part.join(" / "));
414
+ if (label && !fields.includes(label)) fields.push(label);
303
415
  }
304
416
  if (!values.length) return void 0;
305
- const fields = [];
306
- for (const f of active) if (f.label && !fields.includes(f.label)) fields.push(f.label);
307
417
  return { field: fields.join(", "), values };
308
418
  }
309
419
  var LINKED_HOVER_CLASS = "bic-linked-hover";
@@ -339,18 +449,21 @@ var sortedEq = (a, b) => {
339
449
  return x.every((v, i) => v === y[i]);
340
450
  };
341
451
  function bindFilter(host, filter, opts) {
452
+ const pickOf = () => filter.pick ?? filter;
342
453
  const wanted = () => {
343
- if (!filter.active) return [];
454
+ const src = pickOf();
455
+ if (!src.active) return [];
344
456
  const out = [];
345
457
  const n = opts.rowCount();
346
458
  for (let i = 0; i < n; i++) {
347
459
  const r = opts.rowAt(i);
348
- if (r && filter.has(r)) out.push(i);
460
+ if (r && src.has(r)) out.push(i);
349
461
  }
350
462
  return out;
351
463
  };
352
464
  const hiddenKeys = () => {
353
- if (!filter.active) return [];
465
+ const src = pickOf();
466
+ if (!src.active) return [];
354
467
  const carried = /* @__PURE__ */ new Set();
355
468
  const n = opts.rowCount();
356
469
  for (let i = 0; i < n; i++) {
@@ -364,7 +477,7 @@ function bindFilter(host, filter, opts) {
364
477
  }
365
478
  return false;
366
479
  };
367
- return filter.keys.filter((k) => k.some(isAnyValue) ? !anyRowMatches(k) : !carried.has(keyString(k)));
480
+ return src.keys.filter((k) => k.some(isAnyValue) ? !anyRowMatches(k) : !carried.has(keyString(k)));
368
481
  };
369
482
  const paint = () => {
370
483
  const want = wanted();
@@ -379,7 +492,8 @@ function bindFilter(host, filter, opts) {
379
492
  if (source === "host") return;
380
493
  const keep = meta?.keepHidden ? hiddenKeys() : [];
381
494
  if (!idxs.length && !keep.length) {
382
- if (filter.active) filter.clear("clear");
495
+ const src = pickOf();
496
+ if (src.active) src.clear("clear");
383
497
  return;
384
498
  }
385
499
  const rows = idxs.map((i) => opts.rowAt(i)).filter((r) => !!r);
@@ -389,8 +503,20 @@ function bindFilter(host, filter, opts) {
389
503
  filter.set([label], "click");
390
504
  return;
391
505
  }
506
+ const group = meta?.fromMark && !keep.length ? sharedKey(rows) : null;
507
+ if (group) {
508
+ filter.set([group], "click");
509
+ return;
510
+ }
392
511
  filter.selectRows(rows, "click", keep);
393
512
  });
513
+ const sharedKey = (rows) => {
514
+ const key = filter.columns.map((c) => {
515
+ const v = rows[0][c];
516
+ return rows.every((r) => keyString([r[c]]) === keyString([v])) ? v : ANY_VALUE;
517
+ });
518
+ return key.some(isAnyValue) && !key.every(isAnyValue) ? key : null;
519
+ };
394
520
  const labelKey = (rows) => {
395
521
  const n = opts.rowCount();
396
522
  const key = filter.columns.map((c) => {
@@ -404,7 +530,7 @@ function bindFilter(host, filter, opts) {
404
530
  });
405
531
  return key.some(isAnyValue) && !key.every(isAnyValue) ? key : null;
406
532
  };
407
- const offFilter = filter.onChange(() => paint());
533
+ const offFilter = filter.onPick ? filter.onPick(() => paint()) : filter.onChange(() => paint());
408
534
  const el = opts.container ?? null;
409
535
  let offHoverDom = () => {
410
536
  };
@@ -486,6 +612,7 @@ export {
486
612
  isAnyValue,
487
613
  createFilter,
488
614
  createFilterScope,
615
+ scopeFilterOf,
489
616
  filterScopeOf,
490
617
  LINKED_HOVER_CLASS,
491
618
  payloadRowReader,
package/dist/dax.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
2
  import {
3
3
  isAnyValue
4
- } from "./chunk-2KLFIGTQ.mjs";
4
+ } from "./chunk-ZZBX5ZJ6.mjs";
5
5
  import "./chunk-GHODWOAL.mjs";
6
6
 
7
7
  // src/dax.ts
@@ -64,9 +64,31 @@ ${tail}`.replace(/\n$/, "");
64
64
  function isFilter(x) {
65
65
  return !!x && typeof x === "object" && Array.isArray(x.keys) && typeof x.has === "function";
66
66
  }
67
+ var HEADER = /^(?:'(?:[^']|'')*'|[^[\]']*)\[((?:[^\]]|\]\])+)\]$/;
68
+ function columnName(header) {
69
+ const m = HEADER.exec(String(header ?? "").trim());
70
+ return m ? m[1].replace(/\]\]/g, "]") : String(header ?? "");
71
+ }
72
+ function queryRows(result) {
73
+ const table = result && "columns" in result ? result : result && "table" in result ? result.table : void 0;
74
+ if (!table || !Array.isArray(table.columns) || !Array.isArray(table.rows)) return [];
75
+ const bare = table.columns.map((c) => columnName(c?.name ?? ""));
76
+ const count = /* @__PURE__ */ new Map();
77
+ for (const b of bare) count.set(b, (count.get(b) ?? 0) + 1);
78
+ const names = bare.map((b, i) => (count.get(b) ?? 0) > 1 ? String(table.columns[i]?.name ?? b) : b);
79
+ return table.rows.map((r) => {
80
+ const o = {};
81
+ names.forEach((n, k) => {
82
+ o[n] = r?.[k];
83
+ });
84
+ return o;
85
+ });
86
+ }
67
87
  export {
68
88
  calculateTable,
89
+ columnName,
69
90
  daxLiteral,
70
91
  filterQuery,
92
+ queryRows,
71
93
  treatAs
72
94
  };