@bicharts/chart-host 0.6.56 → 0.6.59

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.
@@ -0,0 +1,598 @@
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
+
3
+ // src/filterScope.ts
4
+ function viewToken(view) {
5
+ const bytes = new TextEncoder().encode(JSON.stringify(view));
6
+ let bin = "";
7
+ bytes.forEach((b) => {
8
+ bin += String.fromCharCode(b);
9
+ });
10
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
11
+ }
12
+ function viewFromToken(token) {
13
+ if (!token) return null;
14
+ try {
15
+ const b64 = token.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((token.length + 3) % 4);
16
+ const bin = atob(b64);
17
+ const bytes = Uint8Array.from(bin, (ch) => ch.charCodeAt(0));
18
+ const v = JSON.parse(new TextDecoder().decode(bytes));
19
+ return v && v.v === 1 ? v : null;
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+ function keyString(values) {
25
+ return JSON.stringify(values.map((v) => v instanceof Date ? `__date:${v.getTime()}` : v === void 0 ? null : v));
26
+ }
27
+ function keyOfRow(row, columns) {
28
+ return columns.map((c) => row?.[c]);
29
+ }
30
+ function toKeys(input, width) {
31
+ if (input == null) return [];
32
+ if (!Array.isArray(input)) return width === 1 ? [[input]] : [];
33
+ if (input.length === 0) return [];
34
+ if (width === 1) return input.map((v) => Array.isArray(v) ? [v[0]] : [v]);
35
+ if (input.every((v) => Array.isArray(v))) return input.map((v) => v.slice(0, width));
36
+ return input.length === width ? [input.slice()] : [];
37
+ }
38
+ var nextId = 0;
39
+ function createFilter(columns, opts = {}) {
40
+ const cols = Array.isArray(columns) ? columns.slice() : [columns];
41
+ if (!cols.length || cols.some((c) => typeof c !== "string" || !c)) {
42
+ throw new Error('createFilter: name the key column(s), e.g. createFilter("RegionCode")');
43
+ }
44
+ const display = opts.display == null ? cols : Array.isArray(opts.display) ? opts.display.slice() : [opts.display];
45
+ let keys = [];
46
+ let keySet = /* @__PURE__ */ new Set();
47
+ let rows = [];
48
+ let reason = null;
49
+ let version = 0;
50
+ const subs = /* @__PURE__ */ new Set();
51
+ let hovered = null;
52
+ const hoverSubs = /* @__PURE__ */ new Set();
53
+ const kept = /* @__PURE__ */ new WeakMap();
54
+ const commit = (nextKeys, nextRows, why) => {
55
+ const nextSet = new Set(nextKeys.map(keyString));
56
+ if (nextSet.size === keySet.size && [...nextSet].every((k) => keySet.has(k))) {
57
+ if (nextRows.length) rows = nextRows;
58
+ return;
59
+ }
60
+ keys = nextKeys;
61
+ keySet = nextSet;
62
+ rows = nextRows;
63
+ reason = why;
64
+ version++;
65
+ for (const cb of Array.from(subs)) cb(filter, why);
66
+ };
67
+ const dedupe = (ks) => {
68
+ const seen2 = /* @__PURE__ */ new Set();
69
+ return ks.filter((k) => {
70
+ const s = keyString(k);
71
+ if (seen2.has(s)) return false;
72
+ seen2.add(s);
73
+ return true;
74
+ });
75
+ };
76
+ const seen = /* @__PURE__ */ new Map();
77
+ const remember = (list) => {
78
+ for (const r of list) seen.set(keyString(keyOfRow(r, cols)), r);
79
+ };
80
+ const displayOf = (k, more) => {
81
+ const s = keyString(k);
82
+ const row = (more ?? []).find((r) => keyString(keyOfRow(r, cols)) === s) ?? seen.get(s);
83
+ const parts = row ? display.map((d) => row[d]) : k;
84
+ const text = parts.filter((p) => p != null && p !== "").map(String).join(" \u2192 ");
85
+ return text || k.filter((p) => p != null && p !== "").map(String).join(" \u2192 ");
86
+ };
87
+ let warnedKeep = false;
88
+ const filter = {
89
+ id: opts.id ?? `f${++nextId}`,
90
+ key: opts.key ?? cols.join("+"),
91
+ hoverEnabled: !!opts.hover,
92
+ get hovered() {
93
+ return hovered;
94
+ },
95
+ setHover(k) {
96
+ const next = k && k.length ? k.slice(0, cols.length) : null;
97
+ if ((next ? keyString(next) : null) === (hovered ? keyString(hovered) : null)) return;
98
+ hovered = next;
99
+ for (const cb of Array.from(hoverSubs)) cb(hovered);
100
+ },
101
+ onHover(cb) {
102
+ hoverSubs.add(cb);
103
+ return () => {
104
+ hoverSubs.delete(cb);
105
+ };
106
+ },
107
+ columns: cols,
108
+ label: opts.label ?? cols.join(" / "),
109
+ get active() {
110
+ return keys.length > 0;
111
+ },
112
+ get keys() {
113
+ return keys;
114
+ },
115
+ get rows() {
116
+ return rows;
117
+ },
118
+ get row() {
119
+ return rows[0] ?? null;
120
+ },
121
+ get value() {
122
+ return keys.length ? keys[0][0] : null;
123
+ },
124
+ get values() {
125
+ return keys.map((k) => k[0]);
126
+ },
127
+ get text() {
128
+ return keys.map((k) => displayOf(k)).join(", ");
129
+ },
130
+ textOf(key, more) {
131
+ const k = Array.isArray(key) ? key : [key];
132
+ return displayOf(k.slice(0, cols.length), more);
133
+ },
134
+ get reason() {
135
+ return reason;
136
+ },
137
+ get version() {
138
+ return version;
139
+ },
140
+ selectRows(selected, why = "page") {
141
+ const list = (selected ?? []).filter((r) => r && typeof r === "object");
142
+ remember(list);
143
+ commit(dedupe(list.map((r) => keyOfRow(r, cols))), list, why);
144
+ },
145
+ set(input, why = "page") {
146
+ const ks = dedupe(toKeys(input, cols.length));
147
+ const want = new Set(ks.map(keyString));
148
+ commit(ks, rows.filter((r) => want.has(keyString(keyOfRow(r, cols)))), why);
149
+ },
150
+ clear(why = "page") {
151
+ commit([], [], why);
152
+ },
153
+ has(row, on) {
154
+ if (!keys.length || !row) return false;
155
+ const where = on && on.length ? on : cols;
156
+ if (cols.length === 1 && where.length > 1) {
157
+ return where.some((c) => keySet.has(keyString([row[c]])));
158
+ }
159
+ return keySet.has(keyString(keyOfRow(row, where)));
160
+ },
161
+ keep(input, on) {
162
+ const list = input ?? [];
163
+ const colKey = (on ?? cols).join("");
164
+ const hit = kept.get(list);
165
+ if (hit && hit.version === version && hit.cols === colKey) return hit.out;
166
+ const where = on && on.length ? on : cols;
167
+ if (keys.length && list.length && !warnedKeep && !list.some((r) => r && where.some((c) => c in r))) {
168
+ warnedKeep = true;
169
+ try {
170
+ console.warn(`[chart-host] filter "${filter.label}": these rows have no ${where.join(" / ")} column, so keep() drops them all - filter their query (filterQuery + treatAs) or add the key column.`);
171
+ } catch {
172
+ }
173
+ }
174
+ const out = keys.length ? list.filter((r) => filter.has(r, on)) : list.slice();
175
+ kept.set(list, { version, cols: colKey, out });
176
+ return out;
177
+ },
178
+ onChange(cb) {
179
+ subs.add(cb);
180
+ return () => {
181
+ subs.delete(cb);
182
+ };
183
+ }
184
+ };
185
+ return filter;
186
+ }
187
+ function createFilterScope() {
188
+ const list = [];
189
+ const offs = /* @__PURE__ */ new Map();
190
+ const ctl = /* @__PURE__ */ new Map();
191
+ const ctlOffs = /* @__PURE__ */ new Map();
192
+ const subs = /* @__PURE__ */ new Set();
193
+ const notify = () => {
194
+ for (const cb of Array.from(subs)) cb(scope);
195
+ };
196
+ const scope = {
197
+ get filters() {
198
+ return list.slice();
199
+ },
200
+ get active() {
201
+ return list.filter((f) => f.active);
202
+ },
203
+ filter(columns, opts) {
204
+ const f = createFilter(columns, opts);
205
+ scope.add(f);
206
+ return f;
207
+ },
208
+ add(f) {
209
+ if (!offs.has(f)) {
210
+ list.push(f);
211
+ offs.set(f, f.onChange(() => notify()));
212
+ notify();
213
+ }
214
+ return () => {
215
+ const off = offs.get(f);
216
+ if (!off) return;
217
+ off();
218
+ offs.delete(f);
219
+ const i = list.indexOf(f);
220
+ if (i >= 0) list.splice(i, 1);
221
+ notify();
222
+ };
223
+ },
224
+ clearAll(why = "page") {
225
+ for (const f of list.slice()) f.clear(why);
226
+ },
227
+ onChange(cb) {
228
+ subs.add(cb);
229
+ return () => {
230
+ subs.delete(cb);
231
+ };
232
+ },
233
+ get controls() {
234
+ return ctl;
235
+ },
236
+ addControls(key, c) {
237
+ let k = key, n = 1;
238
+ while (ctl.has(k) && ctl.get(k) !== c) k = `${key}-${++n}`;
239
+ if (!ctl.has(k)) {
240
+ ctl.set(k, c);
241
+ ctlOffs.set(k, c.onChange(() => notify()));
242
+ notify();
243
+ }
244
+ return () => {
245
+ if (ctl.get(k) !== c) return;
246
+ ctlOffs.get(k)?.();
247
+ ctlOffs.delete(k);
248
+ ctl.delete(k);
249
+ notify();
250
+ };
251
+ },
252
+ save() {
253
+ const view = { v: 1 };
254
+ for (const f of list) if (f.active) (view.filters ??= {})[f.key] = f.keys.map((k) => k.slice());
255
+ for (const [k, c] of ctl) {
256
+ const values = c.values;
257
+ if (Object.keys(values).length) (view.controls ??= {})[k] = { ...values };
258
+ }
259
+ return view;
260
+ },
261
+ restore(view) {
262
+ if (!view || view.v !== 1) return;
263
+ for (const f of list) {
264
+ const keys = view.filters?.[f.key];
265
+ if (keys && keys.length) f.set(keys, "page");
266
+ else f.clear("page");
267
+ }
268
+ for (const [k, c] of ctl) {
269
+ const values = view.controls?.[k];
270
+ if (values) c.set(values);
271
+ }
272
+ }
273
+ };
274
+ return scope;
275
+ }
276
+ var LINKED_HOVER_CLASS = "bic-linked-hover";
277
+ var hoverStyled = false;
278
+ function ensureHoverStyle(doc) {
279
+ if (hoverStyled || !doc?.head) return;
280
+ hoverStyled = true;
281
+ const s = doc.createElement("style");
282
+ s.setAttribute("data-bic", "linked-hover");
283
+ s.textContent = `.${LINKED_HOVER_CLASS}{filter:drop-shadow(0 0 2px var(--bic-linked-hover,rgba(0,0,0,.65))) drop-shadow(0 0 1px var(--bic-linked-hover,rgba(0,0,0,.65)))}`;
284
+ doc.head.appendChild(s);
285
+ }
286
+ function payloadRowReader(getPayload) {
287
+ return {
288
+ rowAt(i) {
289
+ const p = getPayload();
290
+ const r = p?.rows?.[i];
291
+ if (!p || !r) return null;
292
+ const o = {};
293
+ p.columns.forEach((c, k) => {
294
+ o[c.name] = r[k];
295
+ });
296
+ return o;
297
+ },
298
+ rowCount() {
299
+ return getPayload()?.rows?.length ?? 0;
300
+ }
301
+ };
302
+ }
303
+ var sortedEq = (a, b) => {
304
+ if (a.length !== b.length) return false;
305
+ const x = a.slice().sort((p, q) => p - q), y = b.slice().sort((p, q) => p - q);
306
+ return x.every((v, i) => v === y[i]);
307
+ };
308
+ function bindFilter(host, filter, opts) {
309
+ const token = {};
310
+ const ownerKeys = () => keysString(filter.keys);
311
+ const isMine = () => {
312
+ const o = owners.get(filter);
313
+ return !!o && o.token === token && o.keys === ownerKeys();
314
+ };
315
+ {
316
+ const o = owners.get(filter);
317
+ if (o && o.orphan && o.keys === ownerKeys()) owners.set(filter, { token, keys: o.keys, orphan: false });
318
+ }
319
+ const wanted = () => {
320
+ if (!filter.active) return [];
321
+ const out = [];
322
+ const n = opts.rowCount();
323
+ for (let i = 0; i < n; i++) {
324
+ const r = opts.rowAt(i);
325
+ if (r && filter.has(r)) out.push(i);
326
+ }
327
+ return out;
328
+ };
329
+ const paint = () => {
330
+ const want = wanted();
331
+ const cur = host.selection.current ?? [];
332
+ if (sortedEq(want, cur)) return;
333
+ if (want.length) host.selection.highlight(want);
334
+ else if (cur.length) host.selection.clear();
335
+ };
336
+ const offHost = host.selection.onChange((idxs, source) => {
337
+ if (source === "host") return;
338
+ if (!idxs.length) {
339
+ if (filter.active) filter.clear("clear");
340
+ return;
341
+ }
342
+ const rows = idxs.map((i) => opts.rowAt(i)).filter((r) => !!r);
343
+ if (!rows.length) return;
344
+ filter.selectRows(rows, "click");
345
+ owners.set(filter, { token, keys: ownerKeys(), orphan: false });
346
+ });
347
+ const offFilter = filter.onChange(() => {
348
+ const o = owners.get(filter);
349
+ if (o && o.keys !== ownerKeys()) owners.delete(filter);
350
+ paint();
351
+ });
352
+ const el = opts.container ?? null;
353
+ let offHoverDom = () => {
354
+ };
355
+ let offHover = () => {
356
+ };
357
+ if (filter.hoverEnabled && el) {
358
+ ensureHoverStyle(el.ownerDocument);
359
+ const markRow = (t) => {
360
+ const m = t?.closest?.(".d3-mark[data-row-idx]");
361
+ const v = m ? parseInt((m.getAttribute("data-row-idx") ?? "").split(",")[0], 10) : NaN;
362
+ return Number.isFinite(v) ? v : null;
363
+ };
364
+ const over = (e) => {
365
+ const i = markRow(e.target);
366
+ const r = i === null ? null : opts.rowAt(i);
367
+ if (r) filter.setHover(keyOfRow(r, filter.columns));
368
+ };
369
+ const leave = () => filter.setHover(null);
370
+ el.addEventListener("mouseover", over);
371
+ el.addEventListener("mouseleave", leave);
372
+ offHoverDom = () => {
373
+ el.removeEventListener("mouseover", over);
374
+ el.removeEventListener("mouseleave", leave);
375
+ };
376
+ const paintHover = (k) => {
377
+ const want = k ? keyString(k) : null;
378
+ el.querySelectorAll(".d3-mark[data-row-idx]").forEach((m) => {
379
+ const idx = parseInt((m.getAttribute("data-row-idx") ?? "").split(",")[0], 10);
380
+ const r = Number.isFinite(idx) ? opts.rowAt(idx) : null;
381
+ const on = !!want && !!r && keyString(keyOfRow(r, filter.columns)) === want;
382
+ m.classList.toggle(LINKED_HOVER_CLASS, on);
383
+ });
384
+ };
385
+ offHover = filter.onHover(paintHover);
386
+ }
387
+ return {
388
+ reconcile() {
389
+ if (filter.active && opts.rowCount() > 0 && isMine()) {
390
+ const drawn = /* @__PURE__ */ new Set();
391
+ const n = opts.rowCount();
392
+ for (let i = 0; i < n; i++) {
393
+ const r = opts.rowAt(i);
394
+ if (r) drawn.add(keyString(keyOfRow(r, filter.columns)));
395
+ }
396
+ const keep = filter.keys.filter((k) => drawn.has(keyString(k)));
397
+ if (keep.length !== filter.keys.length) {
398
+ filter.set(keep, "data");
399
+ return;
400
+ }
401
+ }
402
+ paint();
403
+ },
404
+ paint,
405
+ detach() {
406
+ offHost();
407
+ offFilter();
408
+ offHoverDom();
409
+ offHover();
410
+ const o = owners.get(filter);
411
+ if (o && o.token === token) owners.set(filter, { ...o, orphan: true });
412
+ }
413
+ };
414
+ }
415
+ var owners = /* @__PURE__ */ new WeakMap();
416
+ var keysString = (keys) => keys.map((k) => keyString(k)).sort().join("|");
417
+ function fromVegaInteraction(filter, events) {
418
+ const before = filter.version;
419
+ for (const ev of events ?? []) {
420
+ if (ev?.action === "clear") {
421
+ filter.clear("clear");
422
+ continue;
423
+ }
424
+ if (ev?.action !== "select") continue;
425
+ const keys = [];
426
+ for (const sel of ev.selections ?? []) {
427
+ const preds = sel?.predicates ?? [];
428
+ const key = filter.columns.map((c) => {
429
+ const p = preds.find((q) => q?.name === c && (q.type === void 0 || q.type === "set"));
430
+ return p && p.values && p.values.length ? p.values[0] : void 0;
431
+ });
432
+ if (key.every((v) => v !== void 0)) keys.push(key);
433
+ }
434
+ if (keys.length) filter.set(keys, "click");
435
+ }
436
+ return filter.version !== before;
437
+ }
438
+
439
+ // src/controls.ts
440
+ var same = (a, b) => a.length === b.length && a.every((x, i) => x.key === b[i].key && x.label === b[i].label && x.text === b[i].text && JSON.stringify(x.value) === JSON.stringify(b[i].value));
441
+ function createControls(initial) {
442
+ const ui = initial ? { knobs: { ...initial } } : {};
443
+ let info = [];
444
+ let version = 0;
445
+ const subs = /* @__PURE__ */ new Set();
446
+ const appliers = /* @__PURE__ */ new Set();
447
+ const notify = () => {
448
+ version++;
449
+ for (const cb of Array.from(subs)) cb(c);
450
+ };
451
+ const c = {
452
+ get values() {
453
+ const out = {};
454
+ for (const i of info) out[i.key] = i.value;
455
+ if (!info.length && ui.knobs && typeof ui.knobs === "object") Object.assign(out, ui.knobs);
456
+ return out;
457
+ },
458
+ get info() {
459
+ return info;
460
+ },
461
+ get summary() {
462
+ return info.map((i) => [i.label, i.text].filter(Boolean).join(" ")).filter(Boolean).join(" \xB7 ");
463
+ },
464
+ get ready() {
465
+ return info.length > 0;
466
+ },
467
+ get version() {
468
+ return version;
469
+ },
470
+ set(values) {
471
+ const knobs = { ...ui.knobs ?? {}, ...values };
472
+ ui.knobs = knobs;
473
+ for (const a of Array.from(appliers)) a();
474
+ },
475
+ reset() {
476
+ delete ui.knobs;
477
+ for (const a of Array.from(appliers)) a();
478
+ },
479
+ onChange(cb) {
480
+ subs.add(cb);
481
+ return () => {
482
+ subs.delete(cb);
483
+ };
484
+ },
485
+ _ui: ui,
486
+ _attach(onApply) {
487
+ appliers.add(onApply);
488
+ return () => {
489
+ appliers.delete(onApply);
490
+ };
491
+ },
492
+ _report(next) {
493
+ if (same(info, next)) return;
494
+ info = next;
495
+ notify();
496
+ }
497
+ };
498
+ return c;
499
+ }
500
+ function readControls(container) {
501
+ const bag = container.__lchKnobs;
502
+ if (!bag || typeof bag !== "object") return [];
503
+ const keys = Object.keys(bag);
504
+ if (!keys.length) return [];
505
+ const groups = Array.from(container.querySelectorAll("g.llm-slider"));
506
+ return keys.map((key, i) => {
507
+ const g = groups.find((x) => x.getAttribute("data-knob") === key) ?? groups[i] ?? null;
508
+ const txt = (sel) => (g?.querySelector(sel)?.textContent ?? "").trim();
509
+ return { key, value: bag[key], label: txt(".llm-slider-label"), text: txt(".llm-slider-readout") };
510
+ });
511
+ }
512
+ function attachControls(getHost, container, controls) {
513
+ const c = controls;
514
+ const read = () => c._report(readControls(container));
515
+ const setUiState = (s) => {
516
+ if (!s || typeof s !== "object") return;
517
+ for (const k of Object.keys(c._ui)) delete c._ui[k];
518
+ Object.assign(c._ui, s);
519
+ Promise.resolve().then(read);
520
+ };
521
+ const off = c._attach(() => {
522
+ const host = getHost();
523
+ if (!host) return;
524
+ host.setOptions({ uiState: c._ui });
525
+ host.rendered.then(read, () => {
526
+ });
527
+ });
528
+ return {
529
+ options: { uiState: c._ui, setUiState },
530
+ read,
531
+ detach: off
532
+ };
533
+ }
534
+
535
+ // src/markNotes.ts
536
+ var keyText = (k) => JSON.stringify(k.map((v) => v == null ? "" : String(v).trim()));
537
+ function defaultKey(n, width) {
538
+ return width > 1 ? [n.markKey, n.markKeyTo] : [n.markKey];
539
+ }
540
+ function noteBadges(notes, opts) {
541
+ const cols = typeof opts.columns === "string" ? [opts.columns] : opts.columns.slice();
542
+ const keyOf = opts.keyOf ?? ((n) => defaultKey(n, cols.length));
543
+ const textOf = opts.textOf ?? ((n) => String(n.body ?? ""));
544
+ const byKey = /* @__PURE__ */ new Map();
545
+ for (const n of notes ?? []) {
546
+ if (opts.chart !== void 0 && n.chart !== opts.chart) continue;
547
+ const key = keyOf(n).slice(0, cols.length);
548
+ if (key.length < cols.length || key.some((v) => v == null || v === "")) continue;
549
+ const id = keyText(key);
550
+ const hit = byKey.get(id);
551
+ if (hit) hit.list.push(n);
552
+ else byKey.set(id, { key, list: [n] });
553
+ }
554
+ return [...byKey.entries()].map(([id, { key, list }]) => {
555
+ const title = list.length > 1 ? `${textOf(list[0])} (+${list.length - 1} more)` : textOf(list[0]);
556
+ const base = { id, label: String(list.length), title };
557
+ if (cols.length === 1) return { ...base, column: cols[0], value: key[0] };
558
+ const where = {};
559
+ cols.forEach((c, i) => {
560
+ where[c] = key[i];
561
+ });
562
+ return { ...base, where };
563
+ });
564
+ }
565
+ function badgeKey(a) {
566
+ try {
567
+ const v = JSON.parse(String(a.id ?? ""));
568
+ if (Array.isArray(v)) return v;
569
+ } catch {
570
+ }
571
+ if (a.where) return Object.values(a.where);
572
+ return a.value !== void 0 ? [a.value] : [];
573
+ }
574
+ function notesFor(notes, key, opts) {
575
+ const cols = typeof opts.columns === "string" ? [opts.columns] : opts.columns.slice();
576
+ const want = !key ? null : Array.isArray(key) ? key : key.keys[0] ?? null;
577
+ if (!want) return [];
578
+ const keyOf = opts.keyOf ?? ((n) => defaultKey(n, cols.length));
579
+ const target = keyText(want.slice(0, cols.length));
580
+ return (notes ?? []).filter((n) => (opts.chart === void 0 || n.chart === opts.chart) && keyText(keyOf(n).slice(0, cols.length)) === target);
581
+ }
582
+
583
+ export {
584
+ viewToken,
585
+ viewFromToken,
586
+ createFilter,
587
+ createFilterScope,
588
+ LINKED_HOVER_CLASS,
589
+ payloadRowReader,
590
+ bindFilter,
591
+ fromVegaInteraction,
592
+ createControls,
593
+ readControls,
594
+ attachControls,
595
+ noteBadges,
596
+ badgeKey,
597
+ notesFor
598
+ };
package/dist/dax.mjs ADDED
@@ -0,0 +1,60 @@
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
+ import "./chunk-GHODWOAL.mjs";
3
+
4
+ // src/dax.ts
5
+ var COLUMN_REF = /^(?:'(?:[^']|'')+'|[A-Za-z_][A-Za-z0-9_]*)\[[^\]]+\]$/;
6
+ function daxLiteral(v) {
7
+ if (v == null) return "BLANK()";
8
+ if (typeof v === "boolean") return v ? "TRUE()" : "FALSE()";
9
+ if (typeof v === "number") {
10
+ if (!Number.isFinite(v)) throw new Error(`daxLiteral: ${v} has no DAX literal`);
11
+ return String(v);
12
+ }
13
+ if (typeof v === "bigint") return v.toString();
14
+ if (v instanceof Date) {
15
+ if (Number.isNaN(v.getTime())) throw new Error("daxLiteral: an invalid Date");
16
+ return `DATE(${v.getFullYear()}, ${v.getMonth() + 1}, ${v.getDate()})` + (v.getHours() || v.getMinutes() || v.getSeconds() ? ` + TIME(${v.getHours()}, ${v.getMinutes()}, ${v.getSeconds()})` : "");
17
+ }
18
+ return `"${String(v).replace(/"/g, '""')}"`;
19
+ }
20
+ function treatAs(selection, ...columnRefs) {
21
+ if (!columnRefs.length) throw new Error(`treatAs: name the model column, e.g. treatAs(region, "'Geography'[RegionCode]")`);
22
+ for (const c of columnRefs) {
23
+ if (!COLUMN_REF.test(c.trim())) throw new Error(`treatAs: "${c}" isn't a column reference like 'Table'[Column]`);
24
+ }
25
+ const keys = !selection ? [] : isFilter(selection) ? selection.keys.slice() : selection.map((k) => Array.isArray(k) ? k : [k]);
26
+ if (!keys.length) return null;
27
+ const width = columnRefs.length;
28
+ for (const k of keys) {
29
+ if (k.length < width) throw new Error(`treatAs: a key has ${k.length} value(s) for ${width} column(s)`);
30
+ }
31
+ const rows = keys.map((k) => width === 1 ? daxLiteral(k[0]) : `(${k.slice(0, width).map(daxLiteral).join(", ")})`);
32
+ return `TREATAS({${rows.join(", ")}}, ${columnRefs.map((c) => c.trim()).join(", ")})`;
33
+ }
34
+ function calculateTable(table, ...filters) {
35
+ const live = filters.filter((f) => typeof f === "string" && f.trim().length > 0);
36
+ return live.length ? `CALCULATETABLE(
37
+ ${table},
38
+ ${live.join(",\n")}
39
+ )` : table;
40
+ }
41
+ function filterQuery(query, ...filters) {
42
+ const live = filters.filter((f) => typeof f === "string" && f.trim().length > 0);
43
+ if (!live.length) return query;
44
+ const evals = query.match(/\bEVALUATE\b/gi) ?? [];
45
+ if (evals.length !== 1) throw new Error(`filterQuery: expected one EVALUATE, found ${evals.length}`);
46
+ const m = /^([\s\S]*?\bEVALUATE\b)([\s\S]*?)(\bORDER\s+BY\b[\s\S]*)?$/i.exec(query);
47
+ const head = m[1], table = m[2].trim(), tail = m[3] ?? "";
48
+ return `${head}
49
+ ${calculateTable(table, ...live)}
50
+ ${tail}`.replace(/\n$/, "");
51
+ }
52
+ function isFilter(x) {
53
+ return !!x && typeof x === "object" && Array.isArray(x.keys) && typeof x.has === "function";
54
+ }
55
+ export {
56
+ calculateTable,
57
+ daxLiteral,
58
+ filterQuery,
59
+ treatAs
60
+ };
package/dist/geo.mjs CHANGED
@@ -7,12 +7,12 @@ import {
7
7
  import {
8
8
  WORLD_110M
9
9
  } from "./chunk-ABGDJ3NJ.mjs";
10
- import {
11
- US_ZIP3
12
- } from "./chunk-D3N3JUMK.mjs";
13
10
  import {
14
11
  US_STATES
15
12
  } from "./chunk-75ZJSYIF.mjs";
13
+ import {
14
+ US_ZIP3
15
+ } from "./chunk-D3N3JUMK.mjs";
16
16
  import "./chunk-GHODWOAL.mjs";
17
17
 
18
18
  // src/geo.ts