diff --git a/packages/buckaroo-js-core/package.json b/packages/buckaroo-js-core/package.json index 768324686..674b4b5bb 100644 --- a/packages/buckaroo-js-core/package.json +++ b/packages/buckaroo-js-core/package.json @@ -117,6 +117,7 @@ "ts-node": "^10.9.2", "typescript": "~5.6.2", "typescript-eslint": "^8.11.0", + "vega-embed": "^7.1.0", "vite": "^5.4.10", "vite-plugin-dts": "^4.3.0", "vite-tsconfig-paths": "^5.1.4" diff --git a/packages/buckaroo-js-core/src/components/DFViewerParts/DFViewerInfinite.tsx b/packages/buckaroo-js-core/src/components/DFViewerParts/DFViewerInfinite.tsx index 7c8fa079e..40c1388ea 100644 --- a/packages/buckaroo-js-core/src/components/DFViewerParts/DFViewerInfinite.tsx +++ b/packages/buckaroo-js-core/src/components/DFViewerParts/DFViewerInfinite.tsx @@ -25,6 +25,10 @@ import { TooltipModule, TextFilterModule, ScrollApiModule, + EventApiModule, + RowApiModule, + RenderApiModule, + ClientSideRowModelApiModule, SortChangedEvent, CellClassParams, RefreshCellsParams, @@ -49,6 +53,10 @@ ModuleRegistry.registerModules([ TooltipModule, TextFilterModule, ScrollApiModule, + EventApiModule, + RowApiModule, + RenderApiModule, + ClientSideRowModelApiModule, ]); const AccentColor = "#2196F3" @@ -150,12 +158,14 @@ export function DFViewerInfinite({ max_rows_in_configs, view_name, data_key, + onGridApiReady, }: { data_wrapper: DatasourceOrRaw; df_viewer_config: DFViewerConfig; summary_stats_data?: DFData; activeCol?: [string, string]; setActiveCol: SetColumnFunc; + onGridApiReady?: (api: GridApi) => void; // these are the parameters that could affect the table, // dfviewer doesn't need to understand them, but it does need to use // them as keys to get updated data @@ -233,6 +243,7 @@ export function DFViewerInfinite({ effectiveScheme={effectiveScheme} view_name={view_name} data_key={data_key} + onGridApiReady={onGridApiReady} /> ) @@ -250,6 +261,7 @@ export function DFViewerInfiniteInner({ effectiveScheme, view_name, data_key, + onGridApiReady, }: { data_wrapper: DatasourceOrRaw; df_viewer_config: DFViewerConfig; @@ -266,6 +278,7 @@ export function DFViewerInfiniteInner({ effectiveScheme?: 'light' | 'dark'; view_name?: string; data_key?: string; + onGridApiReady?: (api: GridApi) => void; }) { /* const lastProps = useRef(null); @@ -574,6 +587,9 @@ export function DFViewerInfiniteInner({ // Ensure pinned rows are applied once API is ready params.api.setGridOption('pinnedTopRowData', topRowsRef.current || []); } catch (_e) {} + try { + onGridApiReady?.(params.api); + } catch (_e) {} }} context={{ outside_df_params, ...extra_context }} > @@ -627,16 +643,18 @@ const getDsGridOptions = (origGridOptions: GridOptions, maxRowsWithoutScrolling: return dsGridOptions; };export function DFViewer({ df_data, df_viewer_config, summary_stats_data, activeCol, setActiveCol, + onGridApiReady, }: { df_data: DFData; df_viewer_config: DFViewerConfig; summary_stats_data?: DFData; activeCol?: [string, string]; setActiveCol?: SetColumnFunc; + onGridApiReady?: (api: GridApi) => void; }) { const defaultSetColumnFunc = (_newCol:[string, string]):void => {} const sac:SetColumnFunc = setActiveCol || defaultSetColumnFunc; - + return ( + setActiveCol={sac} + onGridApiReady={onGridApiReady} /> ); } diff --git a/packages/buckaroo-js-core/src/selection/SelectionBus.test.ts b/packages/buckaroo-js-core/src/selection/SelectionBus.test.ts new file mode 100644 index 000000000..37ac17411 --- /dev/null +++ b/packages/buckaroo-js-core/src/selection/SelectionBus.test.ts @@ -0,0 +1,36 @@ +import { SelectionBus, getSelectionBus } from "./SelectionBus"; + +describe("SelectionBus", () => { + it("delivers a published selection to a subscriber on the same channel", () => { + const bus = new SelectionBus("test-roundtrip"); + const received: number[][] = []; + bus.subscribe("ch", (msg) => received.push(msg.ids as number[])); + bus.publish("ch", [1, 2, 3], "src-A"); + expect(received).toEqual([[1, 2, 3]]); + }); + + it("filters out a subscriber's own echo via ownSource", () => { + const bus = new SelectionBus("test-echo"); + const seenBySelf: number[][] = []; + const seenByOther: number[][] = []; + bus.subscribe("ch", (m) => seenBySelf.push(m.ids as number[]), "self"); + bus.subscribe("ch", (m) => seenByOther.push(m.ids as number[]), "other"); + bus.publish("ch", [7], "self"); + expect(seenBySelf).toEqual([]); + expect(seenByOther).toEqual([[7]]); + }); + + it("does not deliver across channels", () => { + const bus = new SelectionBus("test-channels"); + const onA: number[][] = []; + bus.subscribe("a", (m) => onA.push(m.ids as number[])); + bus.publish("b", [1], "src"); + expect(onA).toEqual([]); + }); + + it("getSelectionBus returns a stable singleton on window", () => { + const a = getSelectionBus(); + const b = getSelectionBus(); + expect(a).toBe(b); + }); +}); diff --git a/packages/buckaroo-js-core/src/selection/SelectionBus.ts b/packages/buckaroo-js-core/src/selection/SelectionBus.ts new file mode 100644 index 000000000..fb748a381 --- /dev/null +++ b/packages/buckaroo-js-core/src/selection/SelectionBus.ts @@ -0,0 +1,70 @@ +/** + * Frontend-only selection bus for linked brushing. Publishers (e.g. a Vega + * brush, a row-click handler, a "select these ids" button) emit a set of + * ids on a named channel; subscribers (DFViewer instances, charts) react. + * + * Same-page subscribers receive events via an internal EventTarget. When + * `BroadcastChannel` is available, messages also cross iframe / tab + * boundaries — useful inside JupyterLab where each widget output is its + * own iframe. + * + * No Python round trip, no kernel comm. Messages dedupe by `source` so a + * publisher does not receive its own echo. + */ + +export type SelectionId = string | number; + +export interface SelectionMessage { + channel: string; + ids: SelectionId[]; + source: string; +} + +type Listener = (msg: SelectionMessage) => void; + +export class SelectionBus { + private target = new EventTarget(); + private bc: BroadcastChannel | null = null; + + constructor(broadcastName = "buckaroo-selection") { + if (typeof BroadcastChannel !== "undefined") { + this.bc = new BroadcastChannel(broadcastName); + this.bc.onmessage = (ev) => this.dispatchLocal(ev.data as SelectionMessage); + } + } + + publish(channel: string, ids: Iterable, source: string): void { + const msg: SelectionMessage = { channel, ids: Array.from(ids), source }; + this.dispatchLocal(msg); + this.bc?.postMessage(msg); + } + + subscribe(channel: string, fn: Listener, ownSource?: string): () => void { + const handler = (ev: Event) => { + const msg = (ev as CustomEvent).detail; + if (msg.channel !== channel) return; + if (ownSource && msg.source === ownSource) return; + fn(msg); + }; + this.target.addEventListener("selection", handler); + return () => this.target.removeEventListener("selection", handler); + } + + private dispatchLocal(msg: SelectionMessage) { + this.target.dispatchEvent(new CustomEvent("selection", { detail: msg })); + } +} + +declare global { + interface Window { + __buckarooSelectionBus?: SelectionBus; + } +} + +export function getSelectionBus(): SelectionBus { + if (typeof window === "undefined") return new SelectionBus(); + if (!window.__buckarooSelectionBus) { + window.__buckarooSelectionBus = new SelectionBus(); + } + return window.__buckarooSelectionBus; +} diff --git a/packages/buckaroo-js-core/src/stories/LinkedBrushing.stories.tsx b/packages/buckaroo-js-core/src/stories/LinkedBrushing.stories.tsx new file mode 100644 index 000000000..9d43a16d9 --- /dev/null +++ b/packages/buckaroo-js-core/src/stories/LinkedBrushing.stories.tsx @@ -0,0 +1,285 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import type { GridApi } from "ag-grid-community"; + +import "../style/dcf-npm.css"; +import { DFViewer } from "../components/DFViewerParts/DFViewerInfinite"; +import { + ColumnConfig, + DFData, + DFViewerConfig, +} from "../components/DFViewerParts/DFWhole"; +import { + SelectionId, + getSelectionBus, +} from "../selection/SelectionBus"; + +/** + * Linked brushing demo wired through `SelectionBus` — the same bus a real + * Vega-Lite bridge would publish on. Two `DFViewer` instances subscribe to + * channel "demo" and one of them publishes on row click. Buttons simulate + * an external publisher (the role a brushed Vega chart would play). + * + * On message, each grid mutates `__selected` on each row via + * `api.applyTransaction` and calls `api.refreshCells({ force: true })` — + * no remount, scroll position preserved. + */ + +const CHANNEL = "demo"; +const HIGHLIGHT = "#ffe680"; +const SELECTION_KEY = "id"; +const N_ROWS = 100; + +const REGIONS = ["North", "South", "East", "West", "Central"]; + +type Row = { + index: number; + id: number; + region: string; + revenue: number; + units: number; + __selected: 1 | null; +}; + +function seededRandom(seed: number) { + let s = seed >>> 0; + return () => { + s = (s * 1664525 + 1013904223) >>> 0; + return s / 0xffffffff; + }; +} + +const buildRows = (): Row[] => { + const rand = seededRandom(42); + return Array.from({ length: N_ROWS }, (_, i) => ({ + index: i, + id: i, + region: REGIONS[Math.floor(rand() * REGIONS.length)], + revenue: Math.round(rand() * 20000 + 1000), + units: Math.round(rand() * 500 + 10), + __selected: null as 1 | null, + })); +}; + +const BASE_ROWS = buildRows(); + +const PRESET_SELECTIONS: Record = { + "Top 10 by revenue": [...BASE_ROWS] + .sort((a, b) => b.revenue - a.revenue) + .slice(0, 10) + .map((r) => r.id), + "Region: North": BASE_ROWS.filter((r) => r.region === "North").map( + (r) => r.id, + ), + "Every 7th row": BASE_ROWS.filter((r) => r.id % 7 === 0).map((r) => r.id), +}; + +const HIGHLIGHT_RULE = { + color_rule: "color_not_null" as const, + conditional_color: HIGHLIGHT, + exist_column: "__selected", +}; + +const buildConfig = (): DFViewerConfig => { + const column_config: ColumnConfig[] = [ + { + col_name: "id", + header_name: "id", + displayer_args: { displayer: "integer", min_digits: 1, max_digits: 4 }, + color_map_config: HIGHLIGHT_RULE, + }, + { + col_name: "region", + header_name: "Region", + displayer_args: { displayer: "string" }, + color_map_config: HIGHLIGHT_RULE, + }, + { + col_name: "revenue", + header_name: "Revenue ($)", + displayer_args: { + displayer: "float", + min_fraction_digits: 0, + max_fraction_digits: 0, + }, + color_map_config: HIGHLIGHT_RULE, + }, + { + col_name: "units", + header_name: "Units", + displayer_args: { displayer: "integer", min_digits: 1, max_digits: 4 }, + color_map_config: HIGHLIGHT_RULE, + }, + ]; + return { + column_config, + pinned_rows: [], + left_col_configs: [ + { + col_name: "index", + header_name: "#", + displayer_args: { displayer: "string" }, + }, + ], + }; +}; + +/** + * One grid + the wiring that turns it into a bus participant. Subscribes + * on mount, mutates rows + refreshes cells on every incoming message, + * publishes on row click. + */ +const LinkedGrid = ({ + title, + sourceId, + onSelectionCount, +}: { + title: string; + sourceId: string; + onSelectionCount?: (n: number) => void; +}) => { + const bus = useMemo(getSelectionBus, []); + const apiRef = useRef(null); + const df_viewer_config = useMemo(buildConfig, []); + const df_data: DFData = useMemo( + () => BASE_ROWS.map((r) => ({ ...r })), + [], + ); + + const applySelection = useCallback( + (ids: SelectionId[]) => { + const api = apiRef.current; + if (!api) return; + const idSet = new Set(ids); + const updates: Row[] = []; + api.forEachNode((node) => { + if (!node.data) return; + const wanted: 1 | null = idSet.has(node.data[SELECTION_KEY]) + ? 1 + : null; + if (node.data.__selected !== wanted) { + updates.push({ ...node.data, __selected: wanted }); + } + }); + if (updates.length > 0) { + api.applyTransaction({ update: updates }); + } + api.refreshCells({ force: true }); + onSelectionCount?.(idSet.size); + }, + [onSelectionCount], + ); + + useEffect(() => { + return bus.subscribe(CHANNEL, (msg) => applySelection(msg.ids), sourceId); + }, [bus, sourceId, applySelection]); + + const onApiReady = useCallback((api: GridApi) => { + apiRef.current = api; + api.addEventListener("rowClicked", (ev: any) => { + if (!ev.data) return; + bus.publish(CHANNEL, [ev.data[SELECTION_KEY]], sourceId); + }); + }, [bus, sourceId]); + + return ( +
+
{title}
+
+ +
+
+ ); +}; + +const LinkedBrushingDemo = () => { + const bus = useMemo(getSelectionBus, []); + const [count, setCount] = useState(0); + const [lastSource, setLastSource] = useState("(none)"); + + useEffect(() => { + return bus.subscribe(CHANNEL, (msg) => { + setCount(msg.ids.length); + setLastSource(msg.source); + }); + }, [bus]); + + const publish = (name: string) => { + bus.publish(CHANNEL, PRESET_SELECTIONS[name] ?? [], "buttons"); + }; + + return ( +
+
+ Publish to bus: + {Object.keys(PRESET_SELECTIONS).map((name) => ( + + ))} + + + last: {count} ids from {lastSource} + +
+
+ Both grids subscribe to channel "{CHANNEL}". + Click any row in either grid to broadcast a single-id selection; + the other grid highlights that row. Buttons simulate an + external publisher (the role a Vega-Lite brush would play). +
+
+ undefined} + /> + +
+
+ ); +}; + +const meta: Meta = { + title: "Buckaroo/LinkedBrushing", + component: LinkedBrushingDemo, + parameters: { layout: "padded" }, + tags: ["autodocs"], +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = {}; diff --git a/packages/buckaroo-js-core/src/stories/LinkedBrushingVega.stories.tsx b/packages/buckaroo-js-core/src/stories/LinkedBrushingVega.stories.tsx new file mode 100644 index 000000000..5de0d53dd --- /dev/null +++ b/packages/buckaroo-js-core/src/stories/LinkedBrushingVega.stories.tsx @@ -0,0 +1,332 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { useCallback, useEffect, useMemo, useRef } from "react"; +import type { GridApi } from "ag-grid-community"; + +import "../style/dcf-npm.css"; +import { DFViewer } from "../components/DFViewerParts/DFViewerInfinite"; +import { + ColumnConfig, + DFData, + DFViewerConfig, +} from "../components/DFViewerParts/DFWhole"; +import { + SelectionId, + getSelectionBus, +} from "../selection/SelectionBus"; + +/** + * Linked brushing between a Vega-Lite scatterplot and a Buckaroo + * `DFViewer`, communicating through the same `SelectionBus` used by the + * pure-DFViewer demo. Vega-Lite is loaded with a dynamic `import()` so it + * is not part of the buckaroo-js-core bundle — bundlers code-split this + * chunk and only fetch it when this story is rendered. In production, + * `vega-embed` would be a peer / optional dependency: consumers who want + * the bridge install it themselves. + * + * Brush the chart → buckaroo highlights the brushed ids. + * Click a buckaroo row → the chart highlights that point. + */ + +const CHANNEL = "vega-demo"; +const HIGHLIGHT = "#ffe680"; +const SELECTION_KEY = "id"; +const N_ROWS = 100; + +type Row = { + index: number; + id: number; + x: number; + y: number; + cat: string; + __selected: 1 | null; +}; + +function seededRandom(seed: number) { + let s = seed >>> 0; + return () => { + s = (s * 1664525 + 1013904223) >>> 0; + return s / 0xffffffff; + }; +} + +const CATS = ["alpha", "beta", "gamma", "delta"]; + +const BASE_ROWS: Row[] = (() => { + const rand = seededRandom(7); + return Array.from({ length: N_ROWS }, (_, i) => ({ + index: i, + id: i, + x: Math.round(rand() * 1000) / 10, + y: Math.round(rand() * 1000) / 10, + cat: CATS[Math.floor(rand() * CATS.length)], + __selected: null as 1 | null, + })); +})(); + +const HIGHLIGHT_RULE = { + color_rule: "color_not_null" as const, + conditional_color: HIGHLIGHT, + exist_column: "__selected", +}; + +const buildConfig = (): DFViewerConfig => { + const column_config: ColumnConfig[] = [ + { + col_name: "id", + header_name: "id", + displayer_args: { displayer: "integer", min_digits: 1, max_digits: 4 }, + color_map_config: HIGHLIGHT_RULE, + }, + { + col_name: "cat", + header_name: "Category", + displayer_args: { displayer: "string" }, + color_map_config: HIGHLIGHT_RULE, + }, + { + col_name: "x", + header_name: "x", + displayer_args: { + displayer: "float", + min_fraction_digits: 1, + max_fraction_digits: 1, + }, + color_map_config: HIGHLIGHT_RULE, + }, + { + col_name: "y", + header_name: "y", + displayer_args: { + displayer: "float", + min_fraction_digits: 1, + max_fraction_digits: 1, + }, + color_map_config: HIGHLIGHT_RULE, + }, + ]; + return { + column_config, + pinned_rows: [], + left_col_configs: [ + { + col_name: "index", + header_name: "#", + displayer_args: { displayer: "string" }, + }, + ], + }; +}; + +const BUCKAROO_SOURCE = "vega-demo-buckaroo"; +const VEGA_SOURCE = "vega-demo-chart"; +const BRUSH_NAME = "brush"; + +const vegaSpec = { + $schema: "https://vega.github.io/schema/vega-lite/v6.json", + width: 420, + height: 360, + data: { values: BASE_ROWS }, + params: [ + { + name: BRUSH_NAME, + select: { type: "interval", encodings: ["x", "y"] }, + }, + ], + mark: { type: "circle", size: 80 }, + encoding: { + x: { field: "x", type: "quantitative" }, + y: { field: "y", type: "quantitative" }, + color: { + condition: { param: BRUSH_NAME, field: "cat", type: "nominal" }, + value: "lightgray", + }, + tooltip: [ + { field: "id" }, + { field: "cat" }, + { field: "x" }, + { field: "y" }, + ], + }, +}; + +const VegaChart = () => { + const hostRef = useRef(null); + const bus = useMemo(getSelectionBus, []); + + useEffect(() => { + let cancelled = false; + let view: any = null; + let unsub: (() => void) | null = null; + + // Dynamic import keeps vega-embed out of the buckaroo-js-core bundle. + // The chunk only loads when this story is rendered. + import("vega-embed").then(({ default: embed }) => { + if (cancelled || !hostRef.current) return; + embed(hostRef.current, vegaSpec as any, { actions: false }).then( + (result) => { + if (cancelled) return; + view = result.view; + + // Vega → bus: on every brush change, look up the data + // contained in the brush region and publish their ids. + view.addSignalListener(BRUSH_NAME, (_n: string, value: any) => { + // vega-lite interval selection signal materializes + // as { x: [x0, x1], y: [y0, y1] } once the user has + // drawn a rectangle; it is `{}` or undefined while + // the brush is cleared. + const xRange: [number, number] | undefined = value?.x; + const yRange: [number, number] | undefined = value?.y; + if (!xRange && !yRange) { + bus.publish(CHANNEL, [], VEGA_SOURCE); + return; + } + const inBrush = (view.data("source_0") as Row[]).filter( + (d) => { + const xOk = xRange + ? d.x >= xRange[0] && d.x <= xRange[1] + : true; + const yOk = yRange + ? d.y >= yRange[0] && d.y <= yRange[1] + : true; + return xOk && yOk; + }, + ); + bus.publish( + CHANNEL, + inBrush.map((d) => d.id), + VEGA_SOURCE, + ); + }); + + // Bus → Vega: incoming selection becomes a highlight + // overlay on top of the brush layer. We do it by + // tweaking the dataset rather than the brush param so + // remote selections don't fight the user's drag. + unsub = bus.subscribe( + CHANNEL, + (msg) => { + const ids = new Set(msg.ids); + const updated = (view.data("source_0") as Row[]).map( + (d) => ({ + ...d, + __selected: ids.has(d.id) ? 1 : null, + }), + ); + view.change( + "source_0", + (view as any).changeset().remove(() => true).insert(updated), + ).run(); + }, + VEGA_SOURCE, + ); + }, + ); + }); + + return () => { + cancelled = true; + unsub?.(); + view?.finalize?.(); + }; + }, [bus]); + + return ( +
+
Vega-Lite scatter (brushable)
+
+
+ ); +}; + +const BuckarooGrid = () => { + const bus = useMemo(getSelectionBus, []); + const apiRef = useRef(null); + const df_viewer_config = useMemo(buildConfig, []); + const df_data: DFData = useMemo(() => BASE_ROWS.map((r) => ({ ...r })), []); + + const applySelection = useCallback((ids: SelectionId[]) => { + const api = apiRef.current; + if (!api) return; + const idSet = new Set(ids); + const updates: Row[] = []; + api.forEachNode((node) => { + if (!node.data) return; + const wanted: 1 | null = idSet.has(node.data[SELECTION_KEY]) + ? 1 + : null; + if (node.data.__selected !== wanted) { + updates.push({ ...node.data, __selected: wanted }); + } + }); + if (updates.length > 0) api.applyTransaction({ update: updates }); + api.refreshCells({ force: true }); + }, []); + + useEffect(() => { + return bus.subscribe( + CHANNEL, + (msg) => applySelection(msg.ids), + BUCKAROO_SOURCE, + ); + }, [bus, applySelection]); + + const onApiReady = useCallback( + (api: GridApi) => { + apiRef.current = api; + api.addEventListener("rowClicked", (ev: any) => { + if (!ev.data) return; + bus.publish( + CHANNEL, + [ev.data[SELECTION_KEY]], + BUCKAROO_SOURCE, + ); + }); + }, + [bus], + ); + + return ( +
+
Buckaroo DFViewer
+
+ +
+
+ ); +}; + +const LinkedBrushingVegaDemo = () => { + return ( +
+
+ Both views subscribe to channel "{CHANNEL}" on the + shared SelectionBus. Drag a rectangle on the + scatter to brush; the grid highlights the brushed ids. Click a + row in the grid; the chart re-renders with that point flagged. + Vega-Lite is loaded via dynamic import() — it is + a devDependency of this story, not a bundled dependency of + buckaroo-js-core. +
+
+ + +
+
+ ); +}; + +const meta: Meta = { + title: "Buckaroo/LinkedBrushingVega", + component: LinkedBrushingVegaDemo, + parameters: { layout: "padded" }, + tags: ["autodocs"], +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = {}; diff --git a/packages/pnpm-lock.yaml b/packages/pnpm-lock.yaml index a89618eb4..be780b7eb 100644 --- a/packages/pnpm-lock.yaml +++ b/packages/pnpm-lock.yaml @@ -135,6 +135,9 @@ importers: typescript-eslint: specifier: ^8.11.0 version: 8.50.0(eslint@9.39.2)(typescript@5.6.3) + vega-embed: + specifier: ^7.1.0 + version: 7.1.0(vega-lite@6.4.3(vega@6.2.0))(vega@6.2.0) vite: specifier: ^5.4.10 version: 5.4.21(@types/node@22.19.3) @@ -1440,6 +1443,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -1875,6 +1881,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1897,6 +1907,13 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} @@ -1959,14 +1976,44 @@ packages: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + d3-ease@3.0.1: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + d3-format@3.1.0: resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} engines: {node: '>=12'} + d3-geo-projection@4.0.0: + resolution: {integrity: sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==} + engines: {node: '>=12'} + hasBin: true + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} @@ -1975,6 +2022,14 @@ packages: resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} engines: {node: '>=12'} + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + d3-scale@4.0.2: resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} engines: {node: '>=12'} @@ -2048,6 +2103,9 @@ packages: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -2104,6 +2162,9 @@ packages: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2258,6 +2319,9 @@ packages: resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==} engines: {node: '>=6.0.0'} + fast-json-patch@3.1.1: + resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -2343,6 +2407,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2365,11 +2433,12 @@ packages: glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} @@ -2782,6 +2851,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stringify-pretty-compact@4.0.0: + resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -3229,6 +3301,9 @@ packages: engines: {node: '>= 0.4'} hasBin: true + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rollup@4.54.0: resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3237,6 +3312,9 @@ packages: rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + safe-regex-test@1.1.0: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} @@ -3337,6 +3415,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3427,6 +3509,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + topojson-client@3.1.0: + resolution: {integrity: sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==} + hasBin: true + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -3601,6 +3687,124 @@ packages: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} + vega-canvas@2.0.0: + resolution: {integrity: sha512-9x+4TTw/USYST5nx4yN272sy9WcqSRjAR0tkQYZJ4cQIeon7uVsnohvoPQK1JZu7K1QXGUqzj08z0u/UegBVMA==} + + vega-crossfilter@5.1.0: + resolution: {integrity: sha512-EmVhfP3p6AM7o/lPan/QAoqjblI19BxWUlvl2TSs0xjQd8KbaYYbS4Ixt3cmEvl0QjRdBMF6CdJJ/cy9DTS4Fw==} + + vega-dataflow@6.1.0: + resolution: {integrity: sha512-JxumGlODtFbzoQ4c/jQK8Tb/68ih0lrexlCozcMfTAwQ12XhTqCvlafh7MAKKTMBizjOfaQTHm4Jkyb1H5CfyQ==} + + vega-embed@7.1.0: + resolution: {integrity: sha512-ZmEIn5XJrQt7fSh2lwtSdXG/9uf3yIqZnvXFEwBJRppiBgrEWZcZbj6VK3xn8sNTFQ+sQDXW5sl/6kmbAW3s5A==} + peerDependencies: + vega: '*' + vega-lite: '*' + + vega-encode@5.1.0: + resolution: {integrity: sha512-q26oI7B+MBQYcTQcr5/c1AMsX3FvjZLQOBi7yI0vV+GEn93fElDgvhQiYrgeYSD4Exi/jBPeUXuN6p4bLz16kA==} + + vega-event-selector@4.0.0: + resolution: {integrity: sha512-CcWF4m4KL/al1Oa5qSzZ5R776q8lRxCj3IafCHs5xipoEHrkgu1BWa7F/IH5HrDNXeIDnqOpSV1pFsAWRak4gQ==} + + vega-expression@6.1.0: + resolution: {integrity: sha512-hHgNx/fQ1Vn1u6vHSamH7lRMsOa/yQeHGGcWVmh8fZafLdwdhCM91kZD9p7+AleNpgwiwzfGogtpATFaMmDFYg==} + + vega-force@5.1.0: + resolution: {integrity: sha512-wdnchOSeXpF9Xx8Yp0s6Do9F7YkFeOn/E/nENtsI7NOcyHpICJ5+UkgjUo9QaQ/Yu+dIDU+sP/4NXsUtq6SMaQ==} + + vega-format@2.1.0: + resolution: {integrity: sha512-i9Ht33IgqG36+S1gFDpAiKvXCPz+q+1vDhDGKK8YsgMxGOG4PzinKakI66xd7SdV4q97FgpR7odAXqtDN2wKqw==} + + vega-functions@6.1.1: + resolution: {integrity: sha512-Due6jP0y0FfsGMTrHnzUGnEwXPu7VwE+9relfo+LjL/tRPYnnKqwWvzt7n9JkeBuZqjkgYjMzm/WucNn6Hkw5A==} + + vega-geo@5.1.0: + resolution: {integrity: sha512-H8aBBHfthc3rzDbz/Th18+Nvp00J73q3uXGAPDQqizioDm/CoXCK8cX4pMePydBY9S6ikBiGJrLKFDa80wI20g==} + + vega-hierarchy@5.1.0: + resolution: {integrity: sha512-rZlU8QJNETlB6o73lGCPybZtw2fBBsRIRuFE77aCLFHdGsh6wIifhplVarqE9icBqjUHRRUOmcEYfzwVIPr65g==} + + vega-interpreter@2.2.1: + resolution: {integrity: sha512-o+4ZEme2mdFLewlpF76dwPWW2VkZ3TAF3DMcq75/NzA5KPvnN4wnlCM8At2FVawbaHRyGdVkJSS5ROF5KwpHPQ==} + + vega-label@2.1.0: + resolution: {integrity: sha512-/hgf+zoA3FViDBehrQT42Lta3t8In6YwtMnwjYlh72zNn1p3c7E3YUBwqmAqTM1x+tudgzMRGLYig+bX1ewZxQ==} + + vega-lite@6.4.3: + resolution: {integrity: sha512-d/7hPjfz560UERaQuTmGgIVfXAe3g2hJWeC+igDeaGohUdEoNrHLXgR/yTOBT8vV/lIuuKnw+0/xWWblkDwkMQ==} + engines: {node: '>=20'} + hasBin: true + peerDependencies: + vega: ^6.0.0 + + vega-loader@5.1.0: + resolution: {integrity: sha512-GaY3BdSPbPNdtrBz8SYUBNmNd8mdPc3mtdZfdkFazQ0RD9m+Toz5oR8fKnTamNSk9fRTJX0Lp3uEqxrAlQVreg==} + + vega-parser@7.1.0: + resolution: {integrity: sha512-g0lrYxtmYVW8G6yXpIS4J3Uxt9OUSkc0bLu5afoYDo4rZmoOOdll3x3ebActp5LHPW+usZIE+p5nukRS2vEc7Q==} + + vega-projection@2.1.0: + resolution: {integrity: sha512-EjRjVSoMR5ibrU7q8LaOQKP327NcOAM1+eZ+NO4ANvvAutwmbNVTmfA1VpPH+AD0AlBYc39ND/wnRk7SieDiXA==} + + vega-regression@2.1.0: + resolution: {integrity: sha512-HzC7MuoEwG1rIxRaNTqgcaYF03z/ZxYkQR2D5BN0N45kLnHY1HJXiEcZkcffTsqXdspLjn47yLi44UoCwF5fxQ==} + + vega-runtime@7.1.0: + resolution: {integrity: sha512-mItI+WHimyEcZlZrQ/zYR3LwHVeyHCWwp7MKaBjkU8EwkSxEEGVceyGUY9X2YuJLiOgkLz/6juYDbMv60pfwYA==} + + vega-scale@8.1.0: + resolution: {integrity: sha512-VEgDuEcOec8+C8+FzLcnAmcXrv2gAJKqQifCdQhkgnsLa978vYUgVfCut/mBSMMHbH8wlUV1D0fKZTjRukA1+A==} + + vega-scenegraph@5.1.0: + resolution: {integrity: sha512-4gA89CFIxkZX+4Nvl8SZF2MBOqnlj9J5zgdPh/HPx+JOwtzSlUqIhxFpFj7GWYfwzr/PyZnguBLPihPw1Og/cA==} + + vega-schema-url-parser@3.0.2: + resolution: {integrity: sha512-xAnR7KAvNPYewI3O0l5QGdT8Tv0+GCZQjqfP39cW/hbe/b3aYMAQ39vm8O2wfXUHzm04xTe7nolcsx8WQNVLRQ==} + + vega-selections@6.1.2: + resolution: {integrity: sha512-xJ+V4qdd46nk2RBdwIRrQm2iSTMHdlu/omhLz1pqRL3jZDrkqNBXimrisci2kIKpH2WBpA1YVagwuZEKBmF2Qw==} + + vega-statistics@2.0.0: + resolution: {integrity: sha512-dGPfDXnBlgXbZF3oxtkb8JfeRXd5TYHx25Z/tIoaa9jWua4Vf/AoW2wwh8J1qmMy8J03/29aowkp1yk4DOPazQ==} + + vega-themes@3.0.0: + resolution: {integrity: sha512-1iFiI3BNmW9FrsLnDLx0ZKEddsCitRY3XmUAwp6qmp+p+IXyJYc9pfjlVj9E6KXBPfm4cQyU++s0smKNiWzO4g==} + peerDependencies: + vega: '*' + vega-lite: '*' + + vega-time@3.1.0: + resolution: {integrity: sha512-G93mWzPwNa6UYQRkr8Ujur9uqxbBDjDT/WpXjbDY0yygdSkRT+zXF+Sb4gjhW0nPaqdiwkn0R6kZcSPMj1bMNA==} + + vega-tooltip@1.0.0: + resolution: {integrity: sha512-P1R0JP29v0qnTuwzCQ0SPJlkjAzr6qeyj+H4VgUFSykHmHc1OBxda//XBaFDl/bZgIscEMvjKSjZpXd84x3aZQ==} + + vega-transforms@5.1.0: + resolution: {integrity: sha512-mj/sO2tSuzzpiXX8JSl4DDlhEmVwM/46MTAzTNQUQzJPMI/n4ChCjr/SdEbfEyzlD4DPm1bjohZGjLc010yuMg==} + + vega-typings@2.1.0: + resolution: {integrity: sha512-zdis4Fg4gv37yEvTTSZEVMNhp8hwyEl7GZ4X4HHddRVRKxWFsbyKvZx/YW5Z9Ox4sjxVA2qHzEbod4Fdx+SEJA==} + + vega-util@2.1.1: + resolution: {integrity: sha512-tpNmm8bGtUa8gKfFDSjXPffxqSyPr91vaWIEBnJS/rijhoLZMwM+mgYQG6XfwdcBSN1+jkZ57P0sYSEW/jophw==} + + vega-view-transforms@5.1.0: + resolution: {integrity: sha512-fpigh/xn/32t+An1ShoY3MLeGzNdlbAp2+HvFKzPpmpMTZqJEWkk/J/wHU7Swyc28Ta7W1z3fO+8dZkOYO5TWQ==} + + vega-view@6.1.0: + resolution: {integrity: sha512-hmHDm/zC65lb23mb9Tr9Gx0wkxP0TMS31LpMPYxIZpvInxvUn7TYitkOtz1elr63k2YZrgmF7ztdGyQ4iCQ5fQ==} + + vega-voronoi@5.1.0: + resolution: {integrity: sha512-uKdsoR9x60mz7eYtVG+NhlkdQXeVdMr6jHNAHxs+W+i6kawkUp5S9jp1xf1FmW/uZvtO1eqinHQNwATcDRsiUg==} + + vega-wordcloud@5.1.0: + resolution: {integrity: sha512-sSdNmT8y2D7xXhM2h76dKyaYn3PA4eV49WUUkfYfqHz/vpcu10GSAoFxLhQQTkbZXR+q5ZB63tFUow9W2IFo6g==} + + vega@6.2.0: + resolution: {integrity: sha512-BIwalIcEGysJdQDjeVUmMWB3e50jPDNAMfLJscjEvpunU9bSt7X1OYnQxkg3uBwuRRI4nWfFZO9uIW910nLeGw==} + victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} @@ -3676,10 +3880,12 @@ packages: whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@3.0.0: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} @@ -3721,6 +3927,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -3765,10 +3975,18 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -5073,6 +5291,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/geojson@7946.0.16': {} + '@types/graceful-fs@4.1.9': dependencies: '@types/node': 22.19.3 @@ -5595,6 +5815,12 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 + clsx@2.1.1: {} co@4.6.0: {} @@ -5611,6 +5837,10 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@2.20.3: {} + + commander@7.2.0: {} + compare-versions@6.1.1: {} concat-map@0.0.1: {} @@ -5675,16 +5905,53 @@ snapshots: d3-color@3.1.0: {} + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + + d3-dispatch@3.0.1: {} + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + d3-ease@3.0.1: {} + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + d3-format@3.1.0: {} + d3-geo-projection@4.0.0: + dependencies: + commander: 7.2.0 + d3-array: 3.2.4 + d3-geo: 3.1.1 + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 d3-path@3.1.0: {} + d3-quadtree@3.0.1: {} + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + d3-scale@4.0.2: dependencies: d3-array: 3.2.4 @@ -5744,6 +6011,10 @@ snapshots: define-lazy-prop@2.0.0: {} + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + delayed-stream@1.0.0: {} dequal@2.0.3: {} @@ -5785,6 +6056,8 @@ snapshots: emittery@0.13.1: {} + emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -5999,6 +6272,8 @@ snapshots: fast-equals@5.2.2: {} + fast-json-patch@3.1.1: {} + fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -6075,6 +6350,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.6.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -6746,6 +7023,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-pretty-compact@4.0.0: {} + json5@2.2.3: {} jsonfile@6.2.0: @@ -7175,6 +7454,8 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + robust-predicates@3.0.3: {} + rollup@4.54.0: dependencies: '@types/estree': 1.0.8 @@ -7205,6 +7486,8 @@ snapshots: rrweb-cssom@0.8.0: {} + rw@1.3.3: {} + safe-regex-test@1.1.0: dependencies: call-bound: 1.0.4 @@ -7296,6 +7579,12 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.1.2 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -7375,6 +7664,10 @@ snapshots: dependencies: is-number: 7.0.0 + topojson-client@3.1.0: + dependencies: + commander: 2.20.3 + tough-cookie@4.1.4: dependencies: psl: 1.15.0 @@ -7529,6 +7822,270 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + vega-canvas@2.0.0: {} + + vega-crossfilter@5.1.0: + dependencies: + d3-array: 3.2.4 + vega-dataflow: 6.1.0 + vega-util: 2.1.1 + + vega-dataflow@6.1.0: + dependencies: + vega-format: 2.1.0 + vega-loader: 5.1.0 + vega-util: 2.1.1 + + vega-embed@7.1.0(vega-lite@6.4.3(vega@6.2.0))(vega@6.2.0): + dependencies: + fast-json-patch: 3.1.1 + json-stringify-pretty-compact: 4.0.0 + semver: 7.7.3 + tslib: 2.8.1 + vega: 6.2.0 + vega-interpreter: 2.2.1 + vega-lite: 6.4.3(vega@6.2.0) + vega-schema-url-parser: 3.0.2 + vega-themes: 3.0.0(vega-lite@6.4.3(vega@6.2.0))(vega@6.2.0) + vega-tooltip: 1.0.0 + + vega-encode@5.1.0: + dependencies: + d3-array: 3.2.4 + d3-interpolate: 3.0.1 + vega-dataflow: 6.1.0 + vega-scale: 8.1.0 + vega-util: 2.1.1 + + vega-event-selector@4.0.0: {} + + vega-expression@6.1.0: + dependencies: + '@types/estree': 1.0.8 + vega-util: 2.1.1 + + vega-force@5.1.0: + dependencies: + d3-force: 3.0.0 + vega-dataflow: 6.1.0 + vega-util: 2.1.1 + + vega-format@2.1.0: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-time-format: 4.1.0 + vega-time: 3.1.0 + vega-util: 2.1.1 + + vega-functions@6.1.1: + dependencies: + d3-array: 3.2.4 + d3-color: 3.1.0 + d3-geo: 3.1.1 + vega-dataflow: 6.1.0 + vega-expression: 6.1.0 + vega-scale: 8.1.0 + vega-scenegraph: 5.1.0 + vega-selections: 6.1.2 + vega-statistics: 2.0.0 + vega-time: 3.1.0 + vega-util: 2.1.1 + + vega-geo@5.1.0: + dependencies: + d3-array: 3.2.4 + d3-color: 3.1.0 + d3-geo: 3.1.1 + vega-canvas: 2.0.0 + vega-dataflow: 6.1.0 + vega-projection: 2.1.0 + vega-statistics: 2.0.0 + vega-util: 2.1.1 + + vega-hierarchy@5.1.0: + dependencies: + d3-hierarchy: 3.1.2 + vega-dataflow: 6.1.0 + vega-util: 2.1.1 + + vega-interpreter@2.2.1: + dependencies: + vega-util: 2.1.1 + + vega-label@2.1.0: + dependencies: + vega-canvas: 2.0.0 + vega-dataflow: 6.1.0 + vega-scenegraph: 5.1.0 + vega-util: 2.1.1 + + vega-lite@6.4.3(vega@6.2.0): + dependencies: + json-stringify-pretty-compact: 4.0.0 + tslib: 2.8.1 + vega: 6.2.0 + vega-event-selector: 4.0.0 + vega-expression: 6.1.0 + vega-util: 2.1.1 + yargs: 18.0.0 + + vega-loader@5.1.0: + dependencies: + d3-dsv: 3.0.1 + topojson-client: 3.1.0 + vega-format: 2.1.0 + vega-util: 2.1.1 + + vega-parser@7.1.0: + dependencies: + vega-dataflow: 6.1.0 + vega-event-selector: 4.0.0 + vega-functions: 6.1.1 + vega-scale: 8.1.0 + vega-util: 2.1.1 + + vega-projection@2.1.0: + dependencies: + d3-geo: 3.1.1 + d3-geo-projection: 4.0.0 + vega-scale: 8.1.0 + + vega-regression@2.1.0: + dependencies: + d3-array: 3.2.4 + vega-dataflow: 6.1.0 + vega-statistics: 2.0.0 + vega-util: 2.1.1 + + vega-runtime@7.1.0: + dependencies: + vega-dataflow: 6.1.0 + vega-util: 2.1.1 + + vega-scale@8.1.0: + dependencies: + d3-array: 3.2.4 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + vega-time: 3.1.0 + vega-util: 2.1.1 + + vega-scenegraph@5.1.0: + dependencies: + d3-path: 3.1.0 + d3-shape: 3.2.0 + vega-canvas: 2.0.0 + vega-loader: 5.1.0 + vega-scale: 8.1.0 + vega-util: 2.1.1 + + vega-schema-url-parser@3.0.2: {} + + vega-selections@6.1.2: + dependencies: + d3-array: 3.2.4 + vega-expression: 6.1.0 + vega-util: 2.1.1 + + vega-statistics@2.0.0: + dependencies: + d3-array: 3.2.4 + + vega-themes@3.0.0(vega-lite@6.4.3(vega@6.2.0))(vega@6.2.0): + dependencies: + vega: 6.2.0 + vega-lite: 6.4.3(vega@6.2.0) + + vega-time@3.1.0: + dependencies: + d3-array: 3.2.4 + d3-time: 3.1.0 + vega-util: 2.1.1 + + vega-tooltip@1.0.0: + dependencies: + vega-util: 2.1.1 + + vega-transforms@5.1.0: + dependencies: + d3-array: 3.2.4 + vega-dataflow: 6.1.0 + vega-statistics: 2.0.0 + vega-time: 3.1.0 + vega-util: 2.1.1 + + vega-typings@2.1.0: + dependencies: + '@types/geojson': 7946.0.16 + vega-event-selector: 4.0.0 + vega-expression: 6.1.0 + vega-util: 2.1.1 + + vega-util@2.1.1: {} + + vega-view-transforms@5.1.0: + dependencies: + vega-dataflow: 6.1.0 + vega-scenegraph: 5.1.0 + vega-util: 2.1.1 + + vega-view@6.1.0: + dependencies: + d3-array: 3.2.4 + d3-timer: 3.0.1 + vega-dataflow: 6.1.0 + vega-format: 2.1.0 + vega-functions: 6.1.1 + vega-runtime: 7.1.0 + vega-scenegraph: 5.1.0 + vega-util: 2.1.1 + + vega-voronoi@5.1.0: + dependencies: + d3-delaunay: 6.0.4 + vega-dataflow: 6.1.0 + vega-util: 2.1.1 + + vega-wordcloud@5.1.0: + dependencies: + vega-canvas: 2.0.0 + vega-dataflow: 6.1.0 + vega-scale: 8.1.0 + vega-statistics: 2.0.0 + vega-util: 2.1.1 + + vega@6.2.0: + dependencies: + vega-crossfilter: 5.1.0 + vega-dataflow: 6.1.0 + vega-encode: 5.1.0 + vega-event-selector: 4.0.0 + vega-expression: 6.1.0 + vega-force: 5.1.0 + vega-format: 2.1.0 + vega-functions: 6.1.1 + vega-geo: 5.1.0 + vega-hierarchy: 5.1.0 + vega-label: 2.1.0 + vega-loader: 5.1.0 + vega-parser: 7.1.0 + vega-projection: 2.1.0 + vega-regression: 2.1.0 + vega-runtime: 7.1.0 + vega-scale: 8.1.0 + vega-scenegraph: 5.1.0 + vega-statistics: 2.0.0 + vega-time: 3.1.0 + vega-transforms: 5.1.0 + vega-typings: 2.1.0 + vega-util: 2.1.1 + vega-view: 6.1.0 + vega-view-transforms: 5.1.0 + vega-voronoi: 5.1.0 + vega-wordcloud: 5.1.0 + victory-vendor@36.9.2: dependencies: '@types/d3-array': 3.2.1 @@ -7655,6 +8212,12 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.2 + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + wrappy@1.0.2: {} write-file-atomic@4.0.2: @@ -7678,6 +8241,8 @@ snapshots: yargs-parser@21.1.1: {} + yargs-parser@22.0.0: {} + yargs@17.7.2: dependencies: cliui: 8.0.1 @@ -7688,6 +8253,15 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + yn@3.1.1: {} yocto-queue@0.1.0: {}