diff --git a/packages/plotly/README.md b/packages/plotly/README.md new file mode 100644 index 000000000..ad2515752 --- /dev/null +++ b/packages/plotly/README.md @@ -0,0 +1,112 @@ +# @openuidev/plotly + +Plotly.js component library for OpenUI's generative UI runtime. An LLM speaking openui-lang gets **47 typed chart components** — bar, line, heatmap, violin, sankey, sunburst, candlestick, scatter3d, choropleth, parallel coordinates, the rest of Plotly's catalog — plus a `` wrapper that drops onto any "use client" page. + +## Install + +```bash +pnpm add @openuidev/plotly @openuidev/react-ui @openuidev/react-headless @openuidev/react-lang react react-dom zod +``` + +## Quick start + +```tsx +"use client"; +import "@openuidev/react-ui/components.css"; +import "@openuidev/react-ui/styles/index.css"; +import "@openuidev/plotly/styles.css"; + +import { PlotlyChat } from "@openuidev/plotly"; + +export default function Home() { + return ( +
+ +
+ ); +} +``` + +Pair with an `/api/chat` proxy that streams from your LLM provider (see `examples/` for a working OpenAI implementation). + +## Two-tier API + +Following Plotly's own dual API: + +**Tier 1** — Typed Plotly-Express-style components. Small zod schemas, ergonomic for the LLM: + +``` +Bar(rows, "month", "revenue", "product") # Express style +Bar(null, ["Jan", "Feb", "Mar"], [1.2, 1.5, 1.8]) # Graph-Objects style +``` + +**Tier 2** — `Figure(data, layout)` accepts the full Plotly Graph-Objects schema. Use this for charts Tier 1 doesn't cover (multi-trace overlays, dual axes, animation frames, custom polar/ternary/carpet, advanced 3D scenes). + +**Tier 0** — `PlotlyJSON({ figure })` renders a fully-formed Plotly figure JSON verbatim. Bridge for backend-produced figures (Python `fig.to_json()`). + +## Component catalog + +| Family | Components | +|---|---| +| Layout | `Stack`, `Card`, `CardHeader`, `Heading`, `Text`, `Callout`, `KPI` | +| Cartesian | `Bar`, `Line`, `Scatter`, `Area`, `Histogram` | +| Distributions | `Violin`, `Box` | +| Matrix & 2D-density | `Heatmap`, `Histogram2D`, `Histogram2DContour`, `Contour` | +| Hierarchical | `Sunburst`, `Treemap`, `Icicle` | +| Categorical | `Pie`, `Donut`, `Funnel`, `FunnelArea`, `Waterfall` | +| Flow | `Sankey` | +| Multivariate | `ScatterMatrix`, `ParCoords`, `ParCats` | +| Financial | `Candlestick`, `OHLC` | +| Polar | `ScatterPolar`, `BarPolar` | +| Specialty coords | `ScatterTernary`, `ScatterSmith` | +| Geo | `Choropleth`, `ScatterGeo` | +| Data display | `Indicator`, `Table` | +| Escape hatches | `Figure`, `PlotlyJSON` | + +The LLM sees these via the auto-generated system prompt — no manual Plotly schema authoring required. + +## Bidirectional reactivity + +Plotly events flow into OpenUI's action system: + +``` +Bar(rows, "month", "revenue", + onClick=Action([@Set($selectedMonth, event.x), @Run(monthlyDetail)])) +``` + +Supported events: `onClick`, `onSelected` (lasso/box select), `onRelayout` (zoom/pan). + +## Bundle & loading + +- The Plotly bundle (~3.5 MB minified, ~1 MB gzipped) is loaded **lazily via `React.lazy`** on first chart render. The chat shell first paint is unaffected. +- Chart instances mount only after the assistant message finishes streaming — avoids token-by-token flicker as the LLM populates trace shape. + +## Custom chat shell + +If `` is too restrictive, drop down to the lower-level pieces: + +```tsx +import { Renderer, useTriggerAction } from "@openuidev/react-lang"; +import { FullScreen } from "@openuidev/react-ui"; +import { + plotlyLibrary, + plotlyPromptOptions, + PlotlyAssistantMessage, +} from "@openuidev/plotly"; + +const systemPrompt = plotlyLibrary.prompt(plotlyPromptOptions); +// …compose your own FullScreen / processMessage / assistantMessage… +``` + +## Theming + +`lightTemplate` + `darkTemplate` are exported as plain Plotly `Layout` objects matching OpenUI's design language. Apply them to your own (non-OpenUI) Plotly charts elsewhere if you want consistent visuals. + +## Limitations + +- **`image` trace** is not registered in the default bundle. Plotly's image source does `require('buffer/').Buffer` (with the trailing slash, deliberately), which Turbopack can't resolve. Use `Figure` with custom Plotly registration to enable it in non-Turbopack environments. +- **Carpet family** (`Carpet`, `ScatterCarpet`, `ContourCarpet`) is figure-only — they only render when composed in a single figure, which separate `defineComponent` calls can't express. Use `
`. + +## License + +MIT diff --git a/packages/plotly/package.json b/packages/plotly/package.json new file mode 100644 index 000000000..0a87cd417 --- /dev/null +++ b/packages/plotly/package.json @@ -0,0 +1,96 @@ +{ + "type": "module", + "name": "@openuidev/plotly", + "license": "MIT", + "version": "0.1.0", + "description": "Plotly.js component library for OpenUI generative UI — 47 typed chart components an LLM can compose via openui-lang, plus a wrapper.", + "main": "dist/index.cjs", + "module": "dist/index.mjs", + "types": "dist/index.d.cts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./styles.css": { + "default": "./styles/plotly.css" + } + }, + "files": [ + "dist", + "styles", + "README.md" + ], + "sideEffects": [ + "*.css", + "./dist/index.mjs", + "./dist/index.cjs" + ], + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "rm -rf dist && pnpm build:tsc && pnpm build:cjs", + "build:tsc": "tsc -p . || true", + "build:cjs": "tsdown", + "typecheck": "tsc --noEmit", + "lint:check": "eslint ./src", + "lint:fix": "eslint ./src --fix", + "format:fix": "prettier --write ./src", + "format:check": "prettier --check ./src", + "check:publint": "publint", + "check:attw": "attw --pack . --ignore-rules no-resolution", + "prepare": "pnpm run build", + "prepublishOnly": "pnpm run check:publint && pnpm run check:attw", + "ci": "pnpm run lint:check && pnpm run format:check" + }, + "peerDependencies": { + "@openuidev/react-headless": "workspace:^", + "@openuidev/react-lang": "workspace:^", + "@openuidev/react-ui": "workspace:^", + "react": "^18.3.1 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "dependencies": { + "plotly.js-dist-min": "^3.0.0", + "react-plotly.js": "^2.6.0" + }, + "devDependencies": { + "@types/node": "^22.15.32", + "@types/plotly.js": "^3.0.0", + "@types/plotly.js-dist-min": "^2.3.4", + "@types/react": ">=18.3.1", + "@types/react-dom": ">=18.3.1", + "@types/react-plotly.js": "^2.6.3" + }, + "keywords": [ + "openui", + "plotly", + "plotly.js", + "generative-ui", + "ai", + "llm", + "react", + "charts", + "data-visualization", + "scientific" + ], + "homepage": "https://openui.com", + "author": "engineering@openui.com", + "repository": { + "type": "git", + "url": "https://github.com/thesysdev/openui.git", + "directory": "packages/plotly" + }, + "bugs": { + "url": "https://github.com/thesysdev/openui/issues" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/plotly/src/AssistantMessage.tsx b/packages/plotly/src/AssistantMessage.tsx new file mode 100644 index 000000000..bc774d07a --- /dev/null +++ b/packages/plotly/src/AssistantMessage.tsx @@ -0,0 +1,94 @@ +"use client"; +import type { AssistantMessage as AssistantMessageType } from "@openuidev/react-headless"; +import type { Library } from "@openuidev/react-lang"; +import { Renderer } from "@openuidev/react-lang"; +import React from "react"; + +interface Props { + message: AssistantMessageType; + isStreaming: boolean; + library: Library; +} + +interface ErrorBoundaryState { + err: Error | null; +} + +class MessageErrorBoundary extends React.Component< + { children: React.ReactNode }, + ErrorBoundaryState +> { + override state: ErrorBoundaryState = { err: null }; + static getDerivedStateFromError(err: Error): ErrorBoundaryState { + return { err }; + } + override componentDidCatch(err: Error) { + if (typeof window !== "undefined") { + console.error("[plotly] message render crashed:", err); + } + } + override render() { + if (this.state.err) { + return ( +
+
Render error
+
+ {this.state.err.message} +
+
+ ); + } + return this.props.children; + } +} + +export function PlotlyAssistantMessage({ message, isStreaming, library }: Props) { + const content = typeof message.content === "string" ? message.content : ""; + + React.useEffect(() => { + if (!isStreaming && content) { + // eslint-disable-next-line no-console + console.groupCollapsed( + `%c[plotly] openui-lang (${content.length} chars)`, + "color:#4c78a8;font-weight:600", + ); + // eslint-disable-next-line no-console + console.log(content); + // eslint-disable-next-line no-console + console.groupEnd(); + } + }, [isStreaming, content]); + + return ( +
+ + { + if (errors.length === 0) return; + console.warn( + `%c[plotly] ${errors.length} render error(s)`, + "color:#dc2626;font-weight:600", + errors, + ); + }} + // onParseResult intentionally omitted — it fires on every reactive + // state change which spams the console with N×M log lines on multi- + // chart messages. Re-enable behind a debug flag if needed. + /> + +
+ ); +} diff --git a/packages/plotly/src/PlotlyChat.tsx b/packages/plotly/src/PlotlyChat.tsx new file mode 100644 index 000000000..234704acf --- /dev/null +++ b/packages/plotly/src/PlotlyChat.tsx @@ -0,0 +1,86 @@ +"use client"; +// One-line chat shell: wires up FullScreen, the plotlyLibrary, +// the OpenAI adapter, and PlotlyAssistantMessage with sensible defaults. +// Use the lower-level pieces (plotlyLibrary, PlotlyAssistantMessage, etc.) +// directly if you need custom message processing. + +import { + openAIMessageFormat, + openAIReadableStreamAdapter, + type Message, +} from "@openuidev/react-headless"; +import { FullScreen } from "@openuidev/react-ui"; +import React from "react"; +import { PlotlyAssistantMessage } from "./AssistantMessage"; +import { plotlyLibrary, plotlyPromptOptions } from "./library"; + +export interface PlotlyChatProps { + /** API endpoint that proxies to your LLM. Receives `{ systemPrompt, messages }` + * and returns a streaming response. Defaults to `/api/chat`. */ + apiUrl?: string; + /** Or supply your own message processor (overrides `apiUrl`). */ + processMessage?: (params: { + threadId: string; + messages: Message[]; + abortController: AbortController; + }) => Promise; + /** Override the system prompt fed to the LLM. Defaults to the catalog of + * all 47 components. */ + systemPrompt?: string; + /** Display name shown in the chat shell header. */ + agentName?: string; + /** Inline class on the wrapping div. */ + className?: string; +} + +export function PlotlyChat({ + apiUrl = "/api/chat", + processMessage, + systemPrompt, + agentName = "OpenUI × Plotly", + className, +}: PlotlyChatProps) { + const finalSystemPrompt = React.useMemo( + () => systemPrompt ?? plotlyLibrary.prompt(plotlyPromptOptions), + [systemPrompt], + ); + + const finalProcessMessage = React.useMemo(() => { + if (processMessage) return processMessage; + return async ({ + messages, + abortController, + }: { + threadId: string; + messages: Message[]; + abortController: AbortController; + }) => + fetch(apiUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + systemPrompt: finalSystemPrompt, + messages: openAIMessageFormat.toApi(messages), + }), + signal: abortController.signal, + }); + }, [processMessage, apiUrl, finalSystemPrompt]); + + return ( +
+ ( + + )} + /> +
+ ); +} diff --git a/packages/plotly/src/figure/Figure.ts b/packages/plotly/src/figure/Figure.ts new file mode 100644 index 000000000..f30bd14bf --- /dev/null +++ b/packages/plotly/src/figure/Figure.ts @@ -0,0 +1,27 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Config, Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const FigureSchema = z.object({ + data: z.array(z.record(z.string(), z.unknown())), + layout: z.record(z.string(), z.unknown()).optional(), + config: z.record(z.string(), z.unknown()).optional(), + height: z.number().positive().optional(), +}); + +export const Figure = defineComponent({ + name: "Figure", + props: FigureSchema, + description: + "Generic Plotly Graph-Objects figure. Pass `data` as an array of full Plotly trace objects, e.g. [{ type: 'bar', x: [...], y: [...] }, { type: 'scatter', x: [...], y: [...], mode: 'lines' }]. `layout` accepts the full Plotly layout JSON. Use this when you need a chart not covered by the typed Tier-1 components (Bar, Line, Heatmap, etc.).", + component: ({ props }) => + React.createElement(PlotShell, { + data: props.data as Data[], + layout: props.layout as Partial | undefined, + config: props.config as Partial | undefined, + height: props.height, + }), +}); diff --git a/packages/plotly/src/figure/PlotlyJSON.ts b/packages/plotly/src/figure/PlotlyJSON.ts new file mode 100644 index 000000000..1740b47cc --- /dev/null +++ b/packages/plotly/src/figure/PlotlyJSON.ts @@ -0,0 +1,32 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Config, Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const PlotlyJSONSchema = z.object({ + figure: z.object({ + data: z.array(z.record(z.string(), z.unknown())), + layout: z.record(z.string(), z.unknown()).optional(), + config: z.record(z.string(), z.unknown()).optional(), + }), + height: z.number().positive().optional(), +}); + +export const PlotlyJSON = defineComponent({ + name: "PlotlyJSON", + props: PlotlyJSONSchema, + description: + "Render a fully-formed Plotly figure JSON object verbatim. Use this when a backend tool or query returns a figure produced server-side (e.g. Python `fig.to_json()`). Shape: { figure: { data: [...traces], layout?: {...}, config?: {...} } }.", + component: ({ props }) => { + const fig = props.figure; + if (!fig || !Array.isArray(fig.data)) return null; + return React.createElement(PlotShell, { + data: fig.data as Data[], + layout: fig.layout as Partial | undefined, + config: fig.config as Partial | undefined, + height: props.height, + }); + }, +}); diff --git a/packages/plotly/src/helpers/buildTrace.ts b/packages/plotly/src/helpers/buildTrace.ts new file mode 100644 index 000000000..77343882a --- /dev/null +++ b/packages/plotly/src/helpers/buildTrace.ts @@ -0,0 +1,113 @@ +// Helpers shared by Tier-1 typed components. They accept either Plotly Express +// style (`data` is an array of objects + `x`/`y` are field names) or +// Graph-Objects style (`data` is null/undefined and `x`/`y` are raw arrays). + +export type Row = Record; +export type ArrayLike = readonly (string | number | null | undefined)[]; + +export function isStringField(v: unknown): v is string { + return typeof v === "string"; +} + +export function isArray(v: unknown): v is unknown[] { + return Array.isArray(v); +} + +export function pluck(rows: Row[], field: string): T[] { + return rows.map((r) => r[field] as T); +} + +export interface Resolved { + x: ArrayLike; + y: ArrayLike; + /** Categorical series — present when `color` resolves to a string column. */ + group?: string[]; + /** Continuous color values — present when `color` resolves to a numeric column. */ + color?: number[]; +} + +export interface ResolveInput { + data?: Row[] | null; + x?: ArrayLike | string; + y?: ArrayLike | string; + color?: ArrayLike | string; +} + +/** + * Normalize the (data, x, y, color) input pattern into resolved arrays. + * + * Express style: `data` is row-objects, `x`/`y`/`color` are field names. + * Graph-Objects style: `data` is null, `x`/`y`/`color` are arrays. + * + * `color` is treated as continuous if every value is numeric, categorical otherwise. + */ +export function resolve({ data, x, y, color }: ResolveInput): Resolved | null { + const fromRows = (rows: Row[]): Resolved | null => { + if (!isStringField(x) || !isStringField(y)) return null; + const xs = pluck(rows, x) as ArrayLike; + const ys = pluck(rows, y) as ArrayLike; + if (!color) return { x: xs, y: ys }; + if (!isStringField(color)) return null; + const cs = pluck(rows, color); + if (cs.every((v) => typeof v === "number")) { + return { x: xs, y: ys, color: cs as number[] }; + } + return { x: xs, y: ys, group: cs.map(String) }; + }; + + if (data && Array.isArray(data) && data.length > 0) { + return fromRows(data); + } + + // Graph-Objects style: x / y are raw arrays. + if (isArray(x) && isArray(y)) { + const out: Resolved = { x: x as ArrayLike, y: y as ArrayLike }; + if (isArray(color)) { + const cs = color as unknown[]; + if (cs.every((v) => typeof v === "number")) out.color = cs as number[]; + else out.group = cs.map(String); + } + return out; + } + + return null; +} + +/** + * Split a resolved Resolved into per-group sub-arrays so we can produce + * one Plotly trace per categorical color level. + */ +export function splitByGroup(r: Resolved): Array<{ group: string; x: ArrayLike; y: ArrayLike }> { + if (!r.group) return [{ group: "", x: r.x, y: r.y }]; + const groups = new Map(); + const groupArr = r.group; + for (let i = 0; i < r.x.length; i++) { + const g = groupArr[i]; + if (g === undefined) continue; + if (!groups.has(g)) groups.set(g, { x: [], y: [] }); + const bucket = groups.get(g)!; + bucket.x.push(r.x[i]); + bucket.y.push(r.y[i]); + } + return Array.from(groups.entries()).map(([group, { x, y }]) => ({ + group, + x: x as ArrayLike, + y: y as ArrayLike, + })); +} + +/** + * Build a layout title from the chart's title/xLabel/yLabel props with + * Plotly's expected shape (string is title text). + */ +export function buildAxisLayout(opts: { + title?: string; + xLabel?: string; + yLabel?: string; +}): Record { + const out: Record = {}; + if (opts.title) out["title"] = { text: opts.title }; + if (opts.xLabel) out["xaxis"] = { title: { text: opts.xLabel } }; + if (opts.yLabel) out["yaxis"] = { title: { text: opts.yLabel } }; + return out; +} diff --git a/packages/plotly/src/index.ts b/packages/plotly/src/index.ts new file mode 100644 index 000000000..e15a2ef19 --- /dev/null +++ b/packages/plotly/src/index.ts @@ -0,0 +1,86 @@ +// Public entry point for @openuidev/plotly. +// +// Pairs with @openuidev/react-ui, @openuidev/react-headless, and +// @openuidev/react-lang (peer dependencies) to provide 47 typed Plotly +// components an LLM can address by name in openui-lang, plus a high-level +// wrapper for one-line chat shells. + +// ── The high-level wrapper ────────────────────────────────────────────────── +// +// 99% of users only need this. Drop on a "use client" page, +// add an /api/chat route, you have a working chat with all 47 chart types. +export { PlotlyChat, type PlotlyChatProps } from "./PlotlyChat"; + +// ── The library + prompt + assistantMessage ───────────────────────────────── +// +// Use these directly if you want to compose your own chat shell. +export { PlotlyAssistantMessage } from "./AssistantMessage"; +export { + plotlyAdditionalRules, + plotlyComponentGroups, + plotlyExamples, + plotlyLibrary, + plotlyPromptOptions, + type PlotlyLibrary, +} from "./library"; + +// ── OpenUI re-exports (convenience) ───────────────────────────────────────── +// +// These come from peer-dependency packages — re-exporting just so consumers +// can import everything from a single module if they want to. +export { + BottomTray, + Copilot, + FullScreen, + ThemeProvider, + createTheme, + defaultDarkTheme, + defaultLightTheme, + type Theme, + type ThemeMode, + type ThemeProps, +} from "@openuidev/react-ui"; + +export { + identityMessageFormat, + openAIAdapter, + openAIConversationMessageFormat, + openAIMessageFormat, + openAIReadableStreamAdapter, + openAIResponsesAdapter, + type AssistantMessage, + type Message, + type SystemMessage, + type ToolMessage, + type UserMessage, +} from "@openuidev/react-headless"; + +export { + Renderer, + createLibrary, + defineComponent, + generatePrompt, + useIsStreaming, + useRenderNode, + useTriggerAction, + type ActionEvent, + type ActionPlan, + type ComponentGroup, + type Library, + type LibraryDefinition, + type OpenUIError, + type PromptOptions, +} from "@openuidev/react-lang"; + +// ── Theme / shell / advanced ──────────────────────────────────────────────── +export { isDivergingColormap, resolveColormap } from "./shell/colormap"; +export { PlotShell, type PlotShellProps } from "./shell/PlotShell"; +export { ChartSkeleton, NoDataNotice } from "./shell/skeleton"; +export { darkTemplate, defaultConfig, lightTemplate } from "./shell/template"; + +// NOTE: do not statically re-export Plotly here. plotly.js-dist-min references +// `self` at module load, which crashes Next.js / any SSR environment when the +// package's index is touched on the server. PlotShell loads Plotly lazily via +// React.lazy + dynamic import so the SSR boundary stays clean. Power users +// who want direct access can import "plotly.js-dist-min" themselves inside +// a "use client" boundary. diff --git a/packages/plotly/src/layout/Layout.tsx b/packages/plotly/src/layout/Layout.tsx new file mode 100644 index 000000000..d1a977aed --- /dev/null +++ b/packages/plotly/src/layout/Layout.tsx @@ -0,0 +1,266 @@ +"use client"; +import { defineComponent, useRenderNode } from "@openuidev/react-lang"; +import React from "react"; +import { z } from "zod/v4"; + +const FONT = + 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'; + +// Stack --------------------------------------------------------------------- + +const StackSchema = z.object({ + children: z.array(z.unknown()), + direction: z.enum(["row", "column"]).optional(), + gap: z.number().optional(), + wrap: z.boolean().optional(), +}); + +export const Stack = defineComponent({ + name: "Stack", + props: StackSchema, + description: + "Flex container. `direction='column'` (default) or 'row'. `gap` in px, `wrap=true` to wrap rows. Wrap charts and Cards as children.", + component: ({ props }) => { + const renderNode = useRenderNode(); + const direction = props.direction ?? "column"; + return ( +
+ {(props.children ?? []).map((c, i) => ( +
+ {renderNode(c)} +
+ ))} +
+ ); + }, +}); + +// Card ---------------------------------------------------------------------- + +const CardSchema = z.object({ children: z.array(z.unknown()) }); + +export const Card = defineComponent({ + name: "Card", + props: CardSchema, + description: + "Surface that wraps related content (e.g. CardHeader + a chart). Pass children as an array.", + component: ({ props }) => { + const renderNode = useRenderNode(); + return ( +
+ {(props.children ?? []).map((c, i) => ( + {renderNode(c)} + ))} +
+ ); + }, +}); + +// CardHeader ---------------------------------------------------------------- + +const CardHeaderSchema = z.object({ + title: z.string(), + subtitle: z.string().optional(), +}); + +export const CardHeader = defineComponent({ + name: "CardHeader", + props: CardHeaderSchema, + description: "Header for a Card: title and optional subtitle.", + component: ({ props }) => ( +
+
+ {props.title} +
+ {props.subtitle && ( +
+ {props.subtitle} +
+ )} +
+ ), +}); + +// Heading ------------------------------------------------------------------- + +const HeadingSchema = z.object({ + text: z.string(), + level: z.enum(["h1", "h2", "h3"]).optional(), +}); + +const HEADING_SIZES = { + h1: { fontSize: 22, fontWeight: 700, lineHeight: 1.2 }, + h2: { fontSize: 17, fontWeight: 600, lineHeight: 1.3 }, + h3: { fontSize: 14, fontWeight: 600, lineHeight: 1.35 }, +}; + +export const Heading = defineComponent({ + name: "Heading", + props: HeadingSchema, + description: + "Heading. `level`: 'h1' (default), 'h2', 'h3'. Use 'h1' for dashboard titles, 'h2' for section titles.", + component: ({ props }) => { + const s = HEADING_SIZES[props.level ?? "h1"]; + return ( +
+ {props.text} +
+ ); + }, +}); + +// Text ---------------------------------------------------------------------- + +const TextSchema = z.object({ + text: z.string(), + color: z.string().optional(), +}); + +export const Text = defineComponent({ + name: "Text", + props: TextSchema, + description: "Paragraph of analysis prose. Use newlines for line breaks.", + component: ({ props }) => ( +
+ {props.text} +
+ ), +}); + +// Callout ------------------------------------------------------------------- + +const CalloutSchema = z.object({ + variant: z.enum(["info", "success", "warning", "error"]).optional(), + title: z.string().optional(), + body: z.string(), +}); + +const CALLOUT_COLORS = { + info: { bg: "rgba(59,130,246,0.08)", border: "#3b82f6", icon: "i" }, + success: { bg: "rgba(34,197,94,0.08)", border: "#22c55e", icon: "✓" }, + warning: { bg: "rgba(234,179,8,0.10)", border: "#eab308", icon: "!" }, + error: { bg: "rgba(239,68,68,0.08)", border: "#ef4444", icon: "✕" }, +}; + +export const Callout = defineComponent({ + name: "Callout", + props: CalloutSchema, + description: + "Highlight a finding next to a chart. `variant`: 'info' | 'success' | 'warning' | 'error'. Use to flag effect sizes, p-values, anomalies.", + component: ({ props }) => { + const v = props.variant ?? "info"; + const c = CALLOUT_COLORS[v]; + return ( +
+ {c.icon} +
+ {props.title &&
{props.title}
} +
{props.body}
+
+
+ ); + }, +}); + +// KPI ----------------------------------------------------------------------- + +const KPISchema = z.object({ + label: z.string(), + value: z.string(), + change: z.string().optional(), + changeDirection: z.enum(["up", "down", "flat"]).optional(), +}); + +export const KPI = defineComponent({ + name: "KPI", + props: KPISchema, + description: + "Single big-number metric. `value` should already be formatted (e.g. '$1.26M'). Optional `change` (e.g. '+12.4%') with direction for up/down/flat coloring.", + component: ({ props }) => { + const dir = props.changeDirection ?? "flat"; + const color = dir === "up" ? "#16a34a" : dir === "down" ? "#dc2626" : "rgba(15,23,42,0.55)"; + return ( +
+
+ {props.label} +
+
+ {props.value} +
+ {props.change && ( +
+ {dir === "up" ? "↑" : dir === "down" ? "↓" : "→"} {props.change} +
+ )} +
+ ); + }, +}); diff --git a/packages/plotly/src/library.ts b/packages/plotly/src/library.ts new file mode 100644 index 000000000..06a7aa000 --- /dev/null +++ b/packages/plotly/src/library.ts @@ -0,0 +1,401 @@ +import { createLibrary } from "@openuidev/react-lang"; + +import { Callout, Card, CardHeader, Heading, KPI, Stack, Text } from "./layout/Layout"; + +// Tier 0/2 — escape hatches +import { Figure } from "./figure/Figure"; +import { PlotlyJSON } from "./figure/PlotlyJSON"; + +// Tier 1 — Cartesian +import { Area } from "./traces/Area"; +import { Bar } from "./traces/Bar"; +import { Histogram } from "./traces/Histogram"; +import { Line } from "./traces/Line"; +import { Scatter } from "./traces/Scatter"; + +// Tier 1 — Distributions +import { Box } from "./traces/Box"; +import { Violin } from "./traces/Violin"; + +// Tier 1 — Matrix & 2D-density +import { Contour } from "./traces/Contour"; +import { Heatmap } from "./traces/Heatmap"; +import { Histogram2D } from "./traces/Histogram2D"; +import { Histogram2DContour } from "./traces/Histogram2DContour"; + +// Tier 1 — Hierarchical +import { Icicle } from "./traces/Icicle"; +import { Sunburst } from "./traces/Sunburst"; +import { Treemap } from "./traces/Treemap"; + +// Tier 1 — Categorical / proportions +import { Funnel } from "./traces/Funnel"; +import { FunnelArea } from "./traces/FunnelArea"; +import { Donut, Pie } from "./traces/Pie"; +import { Waterfall } from "./traces/Waterfall"; + +// Tier 1 — Flow +import { Sankey } from "./traces/Sankey"; + +// Tier 1 — Multivariate +import { ParCats } from "./traces/ParCats"; +import { ParCoords } from "./traces/ParCoords"; +import { ScatterMatrix } from "./traces/ScatterMatrix"; + +// Tier 1 — Financial +import { Candlestick } from "./traces/Candlestick"; +import { OHLC } from "./traces/OHLC"; + +// Tier 1 — Polar +import { BarPolar } from "./traces/BarPolar"; +import { ScatterPolar } from "./traces/ScatterPolar"; + +// Tier 1 — Specialty coordinates +import { ScatterSmith } from "./traces/ScatterSmith"; +import { ScatterTernary } from "./traces/ScatterTernary"; + +// Carpet / ScatterCarpet / ContourCarpet are intentionally NOT registered as +// Tier-1 components. They only render when a Carpet trace and a Carpet-overlay +// trace coexist in the SAME figure — a constraint the openui-lang call site +// can't express because each defineComponent invocation produces its own Plot. +// Users who want carpet plots should compose them via Figure(...). + +// Tier 1 — WebGL +import { ScatterGL } from "./traces/ScatterGL"; +import { ScatterPolarGL } from "./traces/ScatterPolarGL"; + +// Tier 1 — Geo (cartesian projection only — tile-based map traces are gated +// behind a future `@openuidev/plotly/geo` sub-entry because maplibre-gl's CSS +// breaks Turbopack HMR.) +import { Choropleth } from "./traces/Choropleth"; +import { ScatterGeo } from "./traces/ScatterGeo"; +// import { ChoroplethMap } from "./traces/ChoroplethMap"; +// import { ScatterMap } from "./traces/ScatterMap"; +// import { DensityMap } from "./traces/DensityMap"; + +// Tier 1 — 3D +import { Cone } from "./traces/Cone"; +import { Isosurface } from "./traces/Isosurface"; +import { Mesh3D } from "./traces/Mesh3D"; +import { Scatter3D } from "./traces/Scatter3D"; +import { StreamTube } from "./traces/StreamTube"; +import { Surface } from "./traces/Surface"; +import { Volume } from "./traces/Volume"; + +// Tier 1 — Data display +import { Indicator } from "./traces/Indicator"; +// Image trace disabled — plotly's image helpers require `buffer/` which doesn't +// resolve under Next.js 16 + Turbopack. Re-enable when Plotly drops the polyfill +// or when a Turbopack alias for `buffer/` becomes possible. +import { Table } from "./traces/Table"; + +export const plotlyComponentGroups = [ + { + name: "Layout", + components: ["Stack", "Card", "CardHeader", "Heading", "Text", "Callout", "KPI"], + notes: [ + "- Wrap each chart in a Card with a CardHeader for a titled section.", + '- Use Stack(direction="row", wrap=true) to lay charts side-by-side; Stack defaults to vertical.', + "- Use KPI for big-number metrics; Heading for dashboard titles; Callout for findings.", + ], + }, + { + name: "Cartesian", + components: ["Bar", "Line", "Scatter", "Area", "Histogram"], + notes: [ + "- Two ways to pass data:", + " • Express style: `data` is row objects, `x`/`y`/`color` are field names. Bar(rows, 'month', 'revenue', 'product').", + " • Graph-Objects style: `data` is null, `x`/`y`/`color` are parallel arrays. Bar(null, ['Jan','Feb'], [120, 150]).", + "- For grouped bars use barmode='group' (default); 'stack' to stack.", + ], + }, + { + name: "Distributions", + components: ["Histogram", "Violin", "Box"], + notes: [ + "- One-variable distribution: Histogram(rows, 'value', nbinsx?, color?).", + "- Compare across groups: Violin (shape) or Box (summary).", + "- Violin overlays a box plot by default — pass showBox=false to hide.", + ], + }, + { + name: "Matrix & 2D-density", + components: ["Heatmap", "Histogram2D", "Histogram2DContour", "Contour"], + notes: [ + "- Heatmap(z, x?, y?, colormap?) for a labeled matrix.", + "- Histogram2D / Histogram2DContour for density of a 2D scatter.", + "- Contour for a continuous scalar field z (rows×cols).", + "- colormap defaults to 'viridis'. Use a diverging map ('RdBu', 'BrBG', 'PiYG', 'spectral') only when zero is meaningful.", + ], + }, + { + name: "Hierarchical", + components: ["Sunburst", "Treemap", "Icicle"], + notes: [ + "- All three take parallel arrays: ids, parents (root parent is empty string ''), values, optional labels.", + "- Sunburst: radial; Treemap: rectangular; Icicle: rectangular like Treemap but stacked along an orientation.", + ], + }, + { + name: "Categorical / proportions", + components: ["Pie", "Donut", "Funnel", "FunnelArea", "Waterfall"], + notes: [ + "- Pie / Donut: parallel `values` and `labels`.", + "- Funnel: stages + values, with auto-computed conversion percentages.", + "- FunnelArea: triangular ribbon-style funnel.", + "- Waterfall: running totals with up/down deltas — `measure` per step ('relative'|'total'|'absolute').", + ], + }, + { + name: "Flow", + components: ["Sankey"], + notes: [ + "- Sankey takes nodes ([{id, label?, color?}]) and links ([{source, target, value}]) with ids referencing nodes.", + ], + }, + { + name: "Multivariate", + components: ["ScatterMatrix", "ParCoords", "ParCats"], + notes: [ + "- ScatterMatrix(rows, ['feature1','feature2','feature3'], color?) — pair plot.", + "- ParCoords for numeric multi-dimension comparison; ParCats for categorical multi-dimension flow.", + "- All three accept a row-objects `data` array + `dimensions` field names.", + ], + }, + { + name: "Financial", + components: ["Candlestick", "OHLC"], + notes: [ + "- Candlestick(x, open, high, low, close) — parallel arrays. OHLC has the same shape with a denser bar style.", + "- Pass showRangeSlider=true on Candlestick for a range slider.", + ], + }, + { + name: "Polar", + components: ["ScatterPolar", "BarPolar"], + notes: [ + "- ScatterPolar(r, theta, mode?) — also makes radar plots when fill='toself'.", + "- BarPolar(r, theta, width?) — wind-rose style.", + "- thetaUnit defaults to 'degrees'; pass 'radians' to switch.", + ], + }, + { + name: "Specialty coordinates", + components: ["ScatterTernary", "ScatterSmith"], + notes: [ + "- ScatterTernary: compositions in a triangle (chemistry, soil, mineralogy).", + "- ScatterSmith: complex impedance (RF/microwave engineering).", + "- For carpet plots (curvilinear grids with ScatterCarpet/ContourCarpet overlays), use Figure with the full Plotly trace JSON — they only render when the carpet and overlay traces coexist in one figure.", + ], + }, + { + name: "WebGL accelerated", + components: ["ScatterGL", "ScatterPolarGL"], + notes: ["- Use these instead of Scatter / ScatterPolar when you have >10k points. Same API."], + }, + { + name: "Geo", + components: ["Choropleth", "ScatterGeo"], + notes: [ + "- Choropleth: country / state region shading using Plotly's natural-earth projection.", + "- ScatterGeo: points / great-circle paths on a globe.", + "- locationmode for Choropleth: 'ISO-3' (default), 'USA-states', 'country names', 'geojson-id'.", + ], + }, + { + name: "3D", + components: ["Scatter3D", "Surface", "Mesh3D", "Cone", "StreamTube", "Isosurface", "Volume"], + notes: [ + "- All 3D charts open with full mouse rotate/pan/zoom.", + "- Scatter3D for points/lines; Surface for z=f(x,y); Mesh3D for arbitrary point clouds.", + "- Cone / StreamTube for vector fields (x,y,z,u,v,w).", + "- Isosurface / Volume for 3D scalar fields (x,y,z,value).", + ], + }, + { + name: "Data display", + components: ["Indicator", "Table"], + notes: [ + "- Indicator: KPI alternative — `mode`: 'number' | 'delta' | 'gauge' | combinations. For deltas pass `reference`; for gauges pass `rangeMin`/`rangeMax`.", + "- Table is COLUMN-oriented — `values` is an array of columns where each column is an array of cells.", + ], + }, + { + name: "Advanced (escape hatches)", + components: ["Figure", "PlotlyJSON"], + notes: [ + "- Figure({ data: [...traces], layout: {...} }) for any composition the typed components don't cover (multi-trace overlays, subplots, dual axes, animation frames, custom polar shapes, …). Use full Plotly Graph-Objects schema.", + "- PlotlyJSON({ figure }) renders a Plotly figure JSON object verbatim — useful when a backend tool returns a precomputed figure (e.g. Python `fig.to_json()`).", + ], + }, +]; + +export const plotlyExamples = [ + `Example — Bar chart (Express style): + +root = Card([CardHeader("Q4 revenue"), b]) +b = Bar(rows, "month", "revenue", "product", null, "group", null, "month", "revenue ($)") +rows = [ + {month: "Oct", revenue: 1200, product: "A"}, + {month: "Oct", revenue: 980, product: "B"}, + {month: "Nov", revenue: 1500, product: "A"}, + {month: "Nov", revenue: 1100, product: "B"}, + {month: "Dec", revenue: 1800, product: "A"}, + {month: "Dec", revenue: 1400, product: "B"} +]`, + + `Example — Histogram and Violin in two cards: + +root = Stack([h1, row]) +h1 = Heading("Latency analysis", "h1") +row = Stack([histCard, violCard], "row", 12, true) +histCard = Card([CardHeader("Distribution"), hist]) +violCard = Card([CardHeader("By region"), viol]) +hist = Histogram(null, latencies, null, 30, null, null, "Latency", "ms", "Count") +viol = Violin(rows, "region", "latency_ms", null, true, "all") +latencies = [120, 135, 142, 158, 175, 195, 220, 245, 280, 310] +rows = [{region:"US", latency_ms: 120}, {region:"EU", latency_ms: 195}, {region:"APAC", latency_ms: 280}]`, + + `Example — Correlation heatmap (diverging colormap): + +root = Card([CardHeader("Feature correlations"), hm]) +hm = Heatmap(matrix, features, features, "RdBu", -1, 1, true, ".2f", "Correlation (Pearson r)") +features = ["sepal_len", "sepal_wid", "petal_len", "petal_wid"] +matrix = [ + [1.00, -0.12, 0.87, 0.82], + [-0.12, 1.00, -0.43, -0.36], + [0.87, -0.43, 1.00, 0.96], + [0.82, -0.36, 0.96, 1.00] +]`, + + `Example — Sankey funnel: + +root = Card([CardHeader("User funnel"), s]) +s = Sankey(nodes, links, "Q4 conversion", 360) +nodes = [{id:"visit", label:"Visit"}, {id:"signup", label:"Sign up"}, {id:"trial", label:"Trial"}, {id:"paid", label:"Paid"}] +links = [{source:"visit", target:"signup", value:1200}, {source:"signup", target:"trial", value:480}, {source:"trial", target:"paid", value:140}]`, + + `Example — Sunburst hierarchy: + +root = Sunburst(ids, parents, values, labels, "remainder", null, "Engineering org") +ids = ["eng", "eng-platform", "eng-product", "eng-platform-data", "eng-platform-infra", "eng-product-web", "eng-product-mobile"] +parents = ["", "eng", "eng", "eng-platform", "eng-platform", "eng-product", "eng-product"] +values = [0, 0, 0, 14, 9, 22, 11] +labels = ["Engineering", "Platform", "Product", "Data", "Infra", "Web", "Mobile"]`, + + `Example — KPI dashboard: + +root = Stack([head, kpiRow, chart]) +head = Heading("Q4 dashboard", "h1") +kpiRow = Stack([k1, k2, k3], "row", 12) +k1 = KPI("Active users", "48,120", "+8.4%", "up") +k2 = KPI("Conversion", "3.84%", "-0.2pp", "down") +k3 = KPI("Net revenue", "$1.26M", "+12.4%", "up") +chart = Card([CardHeader("Revenue by month"), Bar(rows, "month", "revenue")]) +rows = [{month:"Oct", revenue:1200000}, {month:"Nov", revenue:1500000}, {month:"Dec", revenue:1800000}]`, + + `Example — escape hatch via Figure (polar wind-rose with multiple traces): + +root = Card([CardHeader("Polar wind rose"), poly]) +poly = Figure([t1, t2], {polar: {radialaxis: {ticksuffix: " mph"}}}, null, 360) +t1 = {type: "barpolar", r: [77, 32, 11, 5], theta: ["N", "E", "S", "W"], width: [80, 80, 80, 80], marker: {color: "#4c78a8"}, name: "Calm"} +t2 = {type: "barpolar", r: [44, 18, 6, 3], theta: ["N", "E", "S", "W"], width: [80, 80, 80, 80], marker: {color: "#f58518"}, name: "Windy"}`, +]; + +export const plotlyAdditionalRules = [ + "Hard limit: do not emit more than ~25 chart components in a single response. Comprehensive tours quickly exceed the parser's reference budget and produce broken charts. If the user asks for 'all components', give them 5–8 representative ones plus a sentence offering to render any specific type on request.", + "Never emit a chart whose data is purely a placeholder reference (e.g. `Bar(rows, 'x', 'y')` where `rows` is undefined). EVERY chart component in your output MUST be paired with a concrete `rows = [...]` or `arr = [...]` definition that resolves locally — otherwise the chart will render as 'No data'.", + "If the user asks 'what can you do?' or similar capability questions, prefer Markdown / Text describing the catalog over rendering empty cards. Render actual charts only when there's data.", + "Express style is preferred when the data is naturally row-oriented (each object is a row). Pass `data` as the array of objects and use field names for `x`, `y`, `color`. The LLM should NOT pre-flatten data into parallel arrays unless the user already provided data that way.", + "Graph-Objects style (data=null, x/y as raw arrays) is fine for small literal datasets and pasted columns.", + "Always wrap each chart in a Card with a CardHeader carrying a concise title. Use the chart's xLabel/yLabel for axis units (e.g. 'response (mV)').", + "Use diverging colormaps ('RdBu', 'BrBG', 'PiYG', 'spectral') ONLY when zero is a meaningful midpoint (correlations, log fold-change). For ordinal/quantitative data without a special zero, prefer perceptually uniform sequential maps: 'viridis' (default), 'inferno', 'plasma', 'magma', 'cividis', 'turbo'.", + "When the user has raw samples per group, prefer Violin or Box. When they have summary stats already (mean ± sd), prefer Bar with explicit error bars via Figure.", + "For any chart the typed components don't cover (multi-trace overlays, subplots, dual axes, animation frames, custom polar/ternary layouts, advanced 3D scenes, custom hovertemplates), use Figure with full Plotly trace + layout JSON.", + "Pair charts with a short prose interpretation in Text or a Callout when reporting a finding.", + "Sunburst/Treemap/Icicle parents arrays: the root node MUST have an empty string '' as its parent.", + "Sankey link source/target are NODE IDS, not indices. The renderer translates to indices.", + "For >10k points use ScatterGL / ScatterPolarGL instead of Scatter / ScatterPolar.", + "Choropleth with country data: `locationmode='ISO-3'` and 3-letter ISO codes (USA, FRA, JPN). For US states use 'USA-states' and two-letter codes (CA, TX, NY).", +]; + +export const plotlyPromptOptions = { + examples: plotlyExamples, + additionalRules: plotlyAdditionalRules, +}; + +export const plotlyLibrary = createLibrary({ + root: "Stack", + componentGroups: plotlyComponentGroups, + components: [ + // Layout + Stack, + Card, + CardHeader, + Heading, + Text, + Callout, + KPI, + // Cartesian + Bar, + Line, + Scatter, + Area, + Histogram, + // Distributions + Violin, + Box, + // Matrix / 2D density + Heatmap, + Histogram2D, + Histogram2DContour, + Contour, + // Hierarchical + Sunburst, + Treemap, + Icicle, + // Categorical / proportions + Pie, + Donut, + Funnel, + FunnelArea, + Waterfall, + // Flow + Sankey, + // Multivariate + ScatterMatrix, + ParCoords, + ParCats, + // Financial + Candlestick, + OHLC, + // Polar + ScatterPolar, + BarPolar, + // Specialty coords + ScatterTernary, + ScatterSmith, + // WebGL + ScatterGL, + ScatterPolarGL, + // Geo (cartesian projection only — tile-map traces gated behind future sub-entry) + Choropleth, + ScatterGeo, + // 3D + Scatter3D, + Surface, + Mesh3D, + Cone, + StreamTube, + Isosurface, + Volume, + // Data display + Indicator, + Table, + // Advanced + Figure, + PlotlyJSON, + ], +}); + +export type PlotlyLibrary = typeof plotlyLibrary; diff --git a/packages/plotly/src/shell/PlotShell.tsx b/packages/plotly/src/shell/PlotShell.tsx new file mode 100644 index 000000000..284190cc7 --- /dev/null +++ b/packages/plotly/src/shell/PlotShell.tsx @@ -0,0 +1,183 @@ +"use client"; +import type { ActionPlan } from "@openuidev/react-lang"; +import { useIsStreaming, useTriggerAction } from "@openuidev/react-lang"; +import type { + Config, + Data, + Layout, + PlotMouseEvent, + PlotRelayoutEvent, + PlotSelectionEvent, +} from "plotly.js"; +import React from "react"; +import { ChartSkeleton, NoDataNotice } from "./skeleton"; +import { defaultConfig, lightTemplate } from "./template"; + +// Single React.lazy Plot component shared by every chart in the library. +// Dynamic imports of plotly.js-dist-min and react-plotly.js stay dynamic at +// build time — neither module is touched at SSR / module-load time, only when +// React actually mounts the lazy component on the client. This is essential +// because plotly.js-dist-min references `self` at top level and would crash +// any Node SSR pass that statically imports it. +const Plot = React.lazy(async () => { + const [{ default: Plotly }, { default: createPlotlyComponent }] = await Promise.all([ + import("plotly.js-dist-min"), + import("react-plotly.js/factory"), + ]); + return { default: createPlotlyComponent(Plotly) }; +}); + +export interface PlotShellProps { + data: Data[]; + layout?: Partial; + config?: Partial; + height?: number; + /** Optional openui-lang ActionPlan fired on plotly_click. */ + onClick?: ActionPlan; + /** Optional openui-lang ActionPlan fired on plotly_selected (lasso/box select). */ + onSelected?: ActionPlan; + /** Optional openui-lang ActionPlan fired on plotly_relayout (zoom/pan). */ + onRelayout?: ActionPlan; + /** Bumped on each render to force Plotly's diff path; built-in default is fine. */ + revision?: number; +} + +const HASH_SAMPLE_LIMIT = 6; + +// Cheap content hash to bump datarevision so Plotly always diffs even if +// the parent passes the same array reference (defensive for streaming edits). +function quickHash(data: Data[], layout?: Partial): number { + let h = 0; + const str = + JSON.stringify( + data.map((trace) => { + const t = trace as Record; + return { + type: t["type"], + // sample the first few values of x/y/values rather than the full arrays + x: Array.isArray(t["x"]) ? (t["x"] as unknown[]).slice(0, HASH_SAMPLE_LIMIT) : undefined, + y: Array.isArray(t["y"]) ? (t["y"] as unknown[]).slice(0, HASH_SAMPLE_LIMIT) : undefined, + values: Array.isArray(t["values"]) + ? (t["values"] as unknown[]).slice(0, HASH_SAMPLE_LIMIT) + : undefined, + }; + }), + ) + (layout ? JSON.stringify(layout.title ?? "") : ""); + for (let i = 0; i < str.length; i++) { + h = ((h << 5) - h + str.charCodeAt(i)) | 0; + } + return h; +} + +// Extract the small, stable subset of Plotly's event payload that's useful in +// an Action context. Plotly's full payload is huge and unstable across versions. +function extractPoints(e: PlotMouseEvent | PlotSelectionEvent): Record { + const points = (e?.points ?? []).slice(0, 50).map((p) => { + const point = p as unknown as { + x?: unknown; + y?: unknown; + label?: unknown; + value?: unknown; + pointIndex?: number; + pointNumber?: number; + curveNumber?: number; + customdata?: unknown; + }; + return { + x: point.x, + y: point.y, + label: point.label, + value: point.value, + pointIndex: point.pointIndex ?? point.pointNumber, + curveNumber: point.curveNumber, + customdata: point.customdata, + }; + }); + return { points }; +} + +export function PlotShell({ + data, + layout, + config, + height = 320, + onClick, + onSelected, + onRelayout, + revision, +}: PlotShellProps) { + const isStreaming = useIsStreaming(); + const triggerAction = useTriggerAction(); + + const hasData = Array.isArray(data) && data.length > 0; + + // No data + still streaming → loading skeleton (data may arrive any moment). + // No data + stream finished → "No data" notice (data is never coming; usually + // an unresolved variable in the LLM's openui-lang). + if (!hasData) { + return isStreaming ? ( + + ) : ( + + ); + } + + const merged: Partial = { + ...lightTemplate, + ...layout, + // Keep nested overrides composable + xaxis: { ...lightTemplate.xaxis, ...(layout?.xaxis ?? {}) }, + yaxis: { ...lightTemplate.yaxis, ...(layout?.yaxis ?? {}) }, + legend: { ...lightTemplate.legend, ...(layout?.legend ?? {}) }, + hoverlabel: { ...lightTemplate.hoverlabel, ...(layout?.hoverlabel ?? {}) }, + title: { + ...(lightTemplate.title ?? {}), + ...(typeof layout?.title === "string" ? { text: layout.title } : (layout?.title ?? {})), + }, + datarevision: revision ?? quickHash(data, layout), + autosize: true, + }; + + const mergedConfig: Partial = { ...defaultConfig, ...config }; + + return ( +
+ }> + + triggerAction("plotly_click", undefined, { + type: "plotly_click", + params: extractPoints(e), + }) + : undefined + } + onSelected={ + onSelected + ? (e) => + triggerAction("plotly_selected", undefined, { + type: "plotly_selected", + params: e ? extractPoints(e) : { points: [] }, + }) + : undefined + } + onRelayout={ + onRelayout + ? (e: PlotRelayoutEvent) => + triggerAction("plotly_relayout", undefined, { + type: "plotly_relayout", + params: e as Record, + }) + : undefined + } + /> + +
+ ); +} diff --git a/packages/plotly/src/shell/colormap.ts b/packages/plotly/src/shell/colormap.ts new file mode 100644 index 000000000..9a67c4005 --- /dev/null +++ b/packages/plotly/src/shell/colormap.ts @@ -0,0 +1,49 @@ +// Map LLM-friendly colormap names onto Plotly colorscale arrays. +// Plotly accepts a string ("Viridis", "RdBu", …) for built-ins; we lower-case +// and translate so the LLM can write `colormap: "viridis"` and we hand Plotly +// what it actually wants. We also accept arbitrary strings that Plotly knows +// natively, falling back to the value verbatim. + +import type { ColorScale } from "plotly.js"; + +// Sequential and diverging colormaps Plotly ships natively. +const NATIVE_NAME_MAP: Record = { + // sequential — perceptually uniform + viridis: "Viridis", + inferno: "Inferno", + plasma: "Plasma", + magma: "Magma", + cividis: "Cividis", + turbo: "Turbo", + + // sequential — branded + blues: "Blues", + greens: "Greens", + oranges: "Oranges", + purples: "Purples", + greys: "Greys", + reds: "Reds", + ylorrd: "YlOrRd", + ylgnbu: "YlGnBu", + + // diverging + rdbu: "RdBu", + brbg: "BrBG", + piyg: "PiYG", + spectral: "Spectral", + rdylgn: "RdYlGn", + rdylbu: "RdYlBu", +}; + +const DIVERGING = new Set(["rdbu", "brbg", "piyg", "spectral", "rdylgn", "rdylbu"]); + +export function resolveColormap(name?: string): string | ColorScale | undefined { + if (!name) return undefined; + const key = name.toLowerCase(); + return NATIVE_NAME_MAP[key] ?? name; +} + +export function isDivergingColormap(name?: string): boolean { + if (!name) return false; + return DIVERGING.has(name.toLowerCase()); +} diff --git a/packages/plotly/src/shell/skeleton.tsx b/packages/plotly/src/shell/skeleton.tsx new file mode 100644 index 000000000..e529a8d4d --- /dev/null +++ b/packages/plotly/src/shell/skeleton.tsx @@ -0,0 +1,65 @@ +"use client"; + +interface ChartSkeletonProps { + height?: number; +} + +export function ChartSkeleton({ height = 320 }: ChartSkeletonProps) { + return ( +
+ ); +} + +interface NoDataProps { + height?: number; + reason?: string; +} + +// Distinct from ChartSkeleton — used when streaming has finished and the chart +// genuinely received no data (e.g. an unresolved reference in the openui-lang). +export function NoDataNotice({ height = 160, reason }: NoDataProps) { + return ( +
+
No data
+
+ {reason ?? + "This chart did not receive any traces. The model may have referenced an unresolved variable."} +
+
+ ); +} diff --git a/packages/plotly/src/shell/template.ts b/packages/plotly/src/shell/template.ts new file mode 100644 index 000000000..245f73de2 --- /dev/null +++ b/packages/plotly/src/shell/template.ts @@ -0,0 +1,110 @@ +// OpenUI-flavored Plotly templates. Light and dark variants. Merged into every +// chart's `layout` so the LLM never has to author chart styling — only data. + +import type { Layout } from "plotly.js"; + +const SYSTEM_FONT_STACK = + 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'; + +const OPENUI_PALETTE = [ + "#4c78a8", + "#f58518", + "#54a24b", + "#e45756", + "#72b7b2", + "#eeca3b", + "#b279a2", + "#ff9da6", + "#9d755d", + "#bab0ac", + "#5b8ff9", + "#a3a637", +]; + +export const lightTemplate: Partial = { + font: { family: SYSTEM_FONT_STACK, size: 12, color: "#0f172a" }, + paper_bgcolor: "transparent", + plot_bgcolor: "transparent", + margin: { t: 16, r: 16, b: 40, l: 56, pad: 0 }, + colorway: OPENUI_PALETTE, + xaxis: { + gridcolor: "rgba(15,23,42,0.06)", + linecolor: "rgba(15,23,42,0.18)", + tickcolor: "rgba(15,23,42,0.18)", + tickfont: { size: 10.5, color: "rgba(15,23,42,0.6)" }, + title: { font: { size: 12, color: "rgba(15,23,42,0.8)" } }, + zeroline: false, + automargin: true, + }, + yaxis: { + gridcolor: "rgba(15,23,42,0.06)", + linecolor: "rgba(15,23,42,0.18)", + tickcolor: "rgba(15,23,42,0.18)", + tickfont: { size: 10.5, color: "rgba(15,23,42,0.6)" }, + title: { font: { size: 12, color: "rgba(15,23,42,0.8)" } }, + zeroline: false, + automargin: true, + }, + legend: { + font: { size: 11, color: "rgba(15,23,42,0.85)" }, + bgcolor: "transparent", + bordercolor: "rgba(15,23,42,0.10)", + borderwidth: 0, + }, + hoverlabel: { + bgcolor: "white", + bordercolor: "rgba(15,23,42,0.12)", + font: { family: SYSTEM_FONT_STACK, size: 12, color: "#0f172a" }, + align: "left", + }, + title: { + font: { family: SYSTEM_FONT_STACK, size: 14, color: "#0f172a" }, + x: 0, + xanchor: "left", + pad: { l: 0, t: 0, r: 0, b: 8 }, + }, + transition: { duration: 220, easing: "cubic-in-out" }, + modebar: { + bgcolor: "transparent", + color: "rgba(15,23,42,0.45)", + activecolor: "rgba(15,23,42,0.85)", + }, +}; + +// Dark template — same shape, swap surface and ink. +export const darkTemplate: Partial = { + ...lightTemplate, + font: { family: SYSTEM_FONT_STACK, size: 12, color: "#e2e8f0" }, + xaxis: { + ...lightTemplate.xaxis, + gridcolor: "rgba(226,232,240,0.07)", + linecolor: "rgba(226,232,240,0.22)", + tickcolor: "rgba(226,232,240,0.22)", + tickfont: { size: 10.5, color: "rgba(226,232,240,0.6)" }, + title: { font: { size: 12, color: "rgba(226,232,240,0.8)" } }, + }, + yaxis: { + ...lightTemplate.yaxis, + gridcolor: "rgba(226,232,240,0.07)", + linecolor: "rgba(226,232,240,0.22)", + tickcolor: "rgba(226,232,240,0.22)", + tickfont: { size: 10.5, color: "rgba(226,232,240,0.6)" }, + title: { font: { size: 12, color: "rgba(226,232,240,0.8)" } }, + }, + hoverlabel: { + bgcolor: "#0f172a", + bordercolor: "rgba(226,232,240,0.18)", + font: { family: SYSTEM_FONT_STACK, size: 12, color: "#e2e8f0" }, + align: "left", + }, +}; + +import type { Config } from "plotly.js"; + +export const defaultConfig: Partial = { + displayModeBar: false, + displaylogo: false, + responsive: true, + doubleClick: "reset", + scrollZoom: false, +}; diff --git a/packages/plotly/src/traces/Area.ts b/packages/plotly/src/traces/Area.ts new file mode 100644 index 000000000..362e469db --- /dev/null +++ b/packages/plotly/src/traces/Area.ts @@ -0,0 +1,54 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout, resolve, splitByGroup } from "../helpers/buildTrace"; +import { PlotShell } from "../shell/PlotShell"; + +const AreaSchema = z.object({ + data: z.array(z.record(z.string(), z.unknown())).nullable().optional(), + x: z.union([z.array(z.union([z.string(), z.number()])), z.string()]), + y: z.union([z.array(z.number()), z.string()]), + color: z.union([z.array(z.string()), z.string()]).optional(), + stack: z.boolean().optional(), + groupnorm: z.enum(["", "fraction", "percent"]).optional(), + smooth: z.boolean().optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Area = defineComponent({ + name: "Area", + props: AreaSchema, + description: + "Filled area chart. `stack=true` stacks multiple series into a stream. `groupnorm`: 'percent' to normalize stacks to 100%, 'fraction' for 0..1, '' for raw values. `smooth=true` uses spline interpolation.", + component: ({ props }) => { + const r = resolve({ + data: props.data ?? undefined, + x: props.x, + y: props.y, + color: props.color, + }); + if (!r) return null; + const groups = splitByGroup(r); + const traces: Data[] = groups.map((g, i) => ({ + type: "scatter", + mode: "lines", + x: g.x as Array, + y: g.y as number[], + name: g.group || undefined, + fill: props.stack ? "tonexty" : i === 0 ? "tozeroy" : "tonexty", + stackgroup: props.stack ? "one" : undefined, + groupnorm: props.stack ? props.groupnorm : undefined, + line: props.smooth ? { shape: "spline", smoothing: 1 } : undefined, + })); + const layout: Partial = { + ...(buildAxisLayout(props) as Partial), + showlegend: groups.length > 1 && groups[0]!.group !== "", + }; + return React.createElement(PlotShell, { data: traces, layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Bar.ts b/packages/plotly/src/traces/Bar.ts new file mode 100644 index 000000000..a30825854 --- /dev/null +++ b/packages/plotly/src/traces/Bar.ts @@ -0,0 +1,51 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout, resolve, splitByGroup } from "../helpers/buildTrace"; +import { PlotShell } from "../shell/PlotShell"; + +const BarSchema = z.object({ + data: z.array(z.record(z.string(), z.unknown())).nullable().optional(), + x: z.union([z.array(z.union([z.string(), z.number()])), z.string()]), + y: z.union([z.array(z.number()), z.string()]), + color: z.union([z.array(z.string()), z.string()]).optional(), + orientation: z.enum(["v", "h"]).optional(), + barmode: z.enum(["group", "stack", "overlay", "relative"]).optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Bar = defineComponent({ + name: "Bar", + props: BarSchema, + description: + "Bar chart (Plotly Express style). Two ways to pass data: (1) `data` as an array of objects + `x`/`y` as field names, e.g. Bar(rows, 'month', 'revenue'); (2) `x`/`y` as parallel arrays directly, e.g. Bar(null, ['Jan','Feb','Mar'], [120, 150, 180]). `color` field/array adds grouping. `barmode` controls grouping behaviour: 'group' (default), 'stack', 'overlay', 'relative'.", + component: ({ props }) => { + const r = resolve({ + data: props.data ?? undefined, + x: props.x, + y: props.y, + color: props.color, + }); + if (!r) return null; + const groups = splitByGroup(r); + const orientation = props.orientation ?? "v"; + const traces: Data[] = groups.map((g) => ({ + type: "bar", + orientation, + x: orientation === "v" ? (g.x as Array) : (g.y as number[]), + y: orientation === "v" ? (g.y as number[]) : (g.x as Array), + name: g.group || undefined, + })); + const layout: Partial = { + ...(buildAxisLayout(props) as Partial), + barmode: props.barmode ?? "group", + showlegend: groups.length > 1 && groups[0]!.group !== "", + }; + return React.createElement(PlotShell, { data: traces, layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/BarPolar.ts b/packages/plotly/src/traces/BarPolar.ts new file mode 100644 index 000000000..513a56609 --- /dev/null +++ b/packages/plotly/src/traces/BarPolar.ts @@ -0,0 +1,38 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const BarPolarSchema = z.object({ + r: z.array(z.number()), + theta: z.union([z.array(z.number()), z.array(z.string())]), + width: z.union([z.number(), z.array(z.number())]).optional(), + color: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const BarPolar = defineComponent({ + name: "BarPolar", + props: BarPolarSchema, + description: + "Polar bar chart (a.k.a. wind-rose, radial bar chart). Pass parallel `r` (bar lengths) and `theta` (bar angles, degrees). `width` controls bar widths.", + component: ({ props }) => { + const trace = { + type: "barpolar", + r: props.r, + theta: props.theta, + width: props.width as never, + marker: props.color ? { color: props.color } : undefined, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + polar: { angularaxis: { direction: "clockwise" } }, + showlegend: false, + margin: { t: props.title ? 40 : 16, l: 16, r: 16, b: 16 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Box.ts b/packages/plotly/src/traces/Box.ts new file mode 100644 index 000000000..81426442a --- /dev/null +++ b/packages/plotly/src/traces/Box.ts @@ -0,0 +1,62 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout, resolve, splitByGroup } from "../helpers/buildTrace"; +import { PlotShell } from "../shell/PlotShell"; + +const BoxSchema = z.object({ + data: z.array(z.record(z.string(), z.unknown())).nullable().optional(), + x: z.union([z.array(z.string()), z.string()]).optional(), + y: z.union([z.array(z.number()), z.string()]), + color: z.union([z.array(z.string()), z.string()]).optional(), + showPoints: z.union([z.boolean(), z.enum(["all", "outliers", "suspectedoutliers"])]).optional(), + notched: z.boolean().optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Box = defineComponent({ + name: "Box", + props: BoxSchema, + description: + "Box plot (Q1/median/Q3, 1.5×IQR whiskers, outliers). Pass `y` and optionally `x` (group field/array). `showPoints`: 'all' | 'outliers' (default) | true | false. `notched=true` for confidence-interval notches around the median. Use Violin when distribution shape matters; Box when summary suffices.", + component: ({ props }) => { + const r = resolve({ + data: props.data ?? undefined, + x: props.x ?? props.y, + y: props.y, + color: props.color, + }); + if (!r) return null; + const groups = splitByGroup(r); + const points = + props.showPoints === undefined + ? "outliers" + : props.showPoints === true + ? "all" + : props.showPoints === false + ? false + : props.showPoints; + const traces: Data[] = groups.map((g) => ({ + type: "box", + x: + typeof props.x === "string" || Array.isArray(props.x) + ? (g.x as Array) + : undefined, + y: g.y as number[], + name: g.group || undefined, + boxpoints: points as never, + notched: props.notched, + })); + const layout: Partial = { + ...(buildAxisLayout(props) as Partial), + showlegend: groups.length > 1 && groups[0]!.group !== "", + boxmode: "group", + }; + return React.createElement(PlotShell, { data: traces, layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Candlestick.ts b/packages/plotly/src/traces/Candlestick.ts new file mode 100644 index 000000000..dd52188fe --- /dev/null +++ b/packages/plotly/src/traces/Candlestick.ts @@ -0,0 +1,54 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout } from "../helpers/buildTrace"; +import { PlotShell } from "../shell/PlotShell"; + +const CandlestickSchema = z.object({ + x: z.array(z.union([z.string(), z.number()])), + open: z.array(z.number()), + high: z.array(z.number()), + low: z.array(z.number()), + close: z.array(z.number()), + upColor: z.string().optional(), + downColor: z.string().optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + showRangeSlider: z.boolean().optional(), + height: z.number().positive().optional(), +}); + +export const Candlestick = defineComponent({ + name: "Candlestick", + props: CandlestickSchema, + description: + "OHLC candlestick chart. Pass parallel arrays: `x` (timestamps or labels), `open`, `high`, `low`, `close`. Up days fill green, down days red — override with `upColor`/`downColor`. `showRangeSlider=true` adds a Plotly range slider below the chart.", + component: ({ props }) => { + if (!props.x?.length) return null; + const trace = { + type: "candlestick", + x: props.x as Array, + open: props.open, + high: props.high, + low: props.low, + close: props.close, + increasing: { + line: { color: props.upColor ?? "#16a34a" }, + fillcolor: props.upColor ?? "#16a34a", + }, + decreasing: { + line: { color: props.downColor ?? "#dc2626" }, + fillcolor: props.downColor ?? "#dc2626", + }, + } as unknown as Data; + const layout: Partial = { + ...(buildAxisLayout(props) as Partial), + xaxis: { rangeslider: { visible: props.showRangeSlider ?? false } }, + showlegend: false, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Carpet.ts b/packages/plotly/src/traces/Carpet.ts new file mode 100644 index 000000000..e2e4f5389 --- /dev/null +++ b/packages/plotly/src/traces/Carpet.ts @@ -0,0 +1,37 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const CarpetSchema = z.object({ + a: z.array(z.number()), + b: z.array(z.number()), + x: z.array(z.array(z.number())).optional(), + y: z.array(z.array(z.number())).optional(), + carpet: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Carpet = defineComponent({ + name: "Carpet", + props: CarpetSchema, + description: + "Carpet grid (a,b) — the underlying coordinate system for ScatterCarpet and ContourCarpet. Pass `a`/`b` axis values; `x`/`y` are 2D arrays specifying the grid's mapping to cartesian space. `carpet` is the id used by overlaid traces.", + component: ({ props }) => { + const trace = { + type: "carpet", + carpet: props.carpet ?? "carpet1", + a: props.a, + b: props.b, + x: props.x, + y: props.y, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Choropleth.ts b/packages/plotly/src/traces/Choropleth.ts new file mode 100644 index 000000000..5c2b5dae4 --- /dev/null +++ b/packages/plotly/src/traces/Choropleth.ts @@ -0,0 +1,50 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; +import { resolveColormap } from "../shell/colormap"; + +const ChoroplethSchema = z.object({ + locations: z.array(z.string()), + z: z.array(z.number()), + locationmode: z.enum(["ISO-3", "USA-states", "country names", "geojson-id"]).optional(), + scope: z + .enum(["world", "usa", "europe", "asia", "africa", "north america", "south america"]) + .optional(), + colormap: z.string().optional(), + zmin: z.number().optional(), + zmax: z.number().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Choropleth = defineComponent({ + name: "Choropleth", + props: ChoroplethSchema, + description: + "Choropleth map (region color shading). Pass `locations` (e.g. ISO-3 country codes or US state codes) and parallel `z` values. `locationmode` defaults to 'ISO-3'; use 'USA-states' for two-letter US codes. `scope` zooms the map to a region.", + component: ({ props }) => { + const trace: Data = { + type: "choropleth", + locations: props.locations, + z: props.z, + locationmode: (props.locationmode ?? "ISO-3") as never, + colorscale: resolveColormap(props.colormap ?? "viridis") as never, + zmin: props.zmin, + zmax: props.zmax, + colorbar: { thickness: 12, outlinewidth: 0 }, + }; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + geo: { + scope: props.scope ?? "world", + showframe: false, + projection: { type: "natural earth" }, + } as never, + margin: { t: props.title ? 32 : 8, l: 0, r: 0, b: 0 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 380 }); + }, +}); diff --git a/packages/plotly/src/traces/ChoroplethMap.ts b/packages/plotly/src/traces/ChoroplethMap.ts new file mode 100644 index 000000000..21eb09a13 --- /dev/null +++ b/packages/plotly/src/traces/ChoroplethMap.ts @@ -0,0 +1,53 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; +import { resolveColormap } from "../shell/colormap"; + +const ChoroplethMapSchema = z.object({ + geojson: z.unknown(), + locations: z.array(z.string()), + z: z.array(z.number()), + featureidkey: z.string().optional(), + colormap: z.string().optional(), + zmin: z.number().optional(), + zmax: z.number().optional(), + centerLat: z.number().optional(), + centerLon: z.number().optional(), + zoom: z.number().min(0).max(22).optional(), + style: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ChoroplethMap = defineComponent({ + name: "ChoroplethMap", + props: ChoroplethMapSchema, + description: + "Choropleth on an interactive MapLibre map using a custom GeoJSON FeatureCollection. Pass `geojson` (FeatureCollection), `locations` (feature ids), parallel `z` values, optional `featureidkey` (defaults to 'id', e.g. 'properties.name'). Combine with map zoom/center for region focus.", + component: ({ props }) => { + const trace = { + type: "choroplethmap", + geojson: props.geojson, + locations: props.locations, + z: props.z, + featureidkey: props.featureidkey, + colorscale: resolveColormap(props.colormap ?? "viridis"), + zmin: props.zmin, + zmax: props.zmax, + colorbar: { thickness: 12, outlinewidth: 0 }, + } as unknown as Data; + const layout = { + title: props.title ? { text: props.title } : undefined, + map: { + style: props.style ?? "open-street-map", + center: { lat: props.centerLat ?? 0, lon: props.centerLon ?? 0 }, + zoom: props.zoom ?? 1, + }, + margin: { t: props.title ? 32 : 0, l: 0, r: 0, b: 0 }, + } as unknown as Partial; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 420 }); + }, +}); diff --git a/packages/plotly/src/traces/Cone.ts b/packages/plotly/src/traces/Cone.ts new file mode 100644 index 000000000..c41546f43 --- /dev/null +++ b/packages/plotly/src/traces/Cone.ts @@ -0,0 +1,47 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; +import { resolveColormap } from "../shell/colormap"; + +const ConeSchema = z.object({ + x: z.array(z.number()), + y: z.array(z.number()), + z: z.array(z.number()), + u: z.array(z.number()), + v: z.array(z.number()), + w: z.array(z.number()), + colormap: z.string().optional(), + sizemode: z.enum(["scaled", "absolute"]).optional(), + sizeref: z.number().positive().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Cone = defineComponent({ + name: "Cone", + props: ConeSchema, + description: + "3D vector field as cones (arrows). At each `(x, y, z)` location, draws a cone in the direction `(u, v, w)`. Common for fluid dynamics, electromagnetics, gradient fields.", + component: ({ props }) => { + const trace = { + type: "cone", + x: props.x, + y: props.y, + z: props.z, + u: props.u, + v: props.v, + w: props.w, + colorscale: resolveColormap(props.colormap ?? "viridis"), + sizemode: props.sizemode ?? "scaled", + sizeref: props.sizeref ?? 1, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + margin: { t: props.title ? 32 : 8, l: 0, r: 0, b: 0 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 420 }); + }, +}); diff --git a/packages/plotly/src/traces/Contour.ts b/packages/plotly/src/traces/Contour.ts new file mode 100644 index 000000000..13cafcfe2 --- /dev/null +++ b/packages/plotly/src/traces/Contour.ts @@ -0,0 +1,54 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout } from "../helpers/buildTrace"; +import { resolveColormap } from "../shell/colormap"; +import { PlotShell } from "../shell/PlotShell"; + +const ContourSchema = z.object({ + z: z.array(z.array(z.number())), + x: z.array(z.union([z.string(), z.number()])).optional(), + y: z.array(z.union([z.string(), z.number()])).optional(), + ncontours: z.number().int().positive().optional(), + start: z.number().optional(), + end: z.number().optional(), + step: z.number().optional(), + colormap: z.string().optional(), + showLines: z.boolean().optional(), + showLabels: z.boolean().optional(), + filled: z.boolean().optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Contour = defineComponent({ + name: "Contour", + props: ContourSchema, + description: + "Contour plot of a 2D scalar field `z` (rows×cols). `start`/`end`/`step` define explicit contour levels; otherwise `ncontours` controls auto-leveling. `filled=false` to draw lines only; `showLabels=true` to label contour lines with their value.", + component: ({ props }) => { + const trace: Data = { + type: "contour", + z: props.z, + x: props.x as Array | undefined, + y: props.y as Array | undefined, + ncontours: props.ncontours, + contours: { + start: props.start, + end: props.end, + size: props.step, + showlines: props.showLines ?? true, + showlabels: props.showLabels ?? false, + coloring: props.filled === false ? "lines" : "fill", + }, + colorscale: resolveColormap(props.colormap ?? "viridis") as never, + colorbar: { thickness: 12, outlinewidth: 0 }, + }; + const layout: Partial = buildAxisLayout(props) as Partial; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/ContourCarpet.ts b/packages/plotly/src/traces/ContourCarpet.ts new file mode 100644 index 000000000..c87ea665d --- /dev/null +++ b/packages/plotly/src/traces/ContourCarpet.ts @@ -0,0 +1,40 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; +import { resolveColormap } from "../shell/colormap"; + +const ContourCarpetSchema = z.object({ + carpet: z.string(), + a: z.array(z.number()), + b: z.array(z.number()), + z: z.array(z.number()), + ncontours: z.number().int().positive().optional(), + colormap: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ContourCarpet = defineComponent({ + name: "ContourCarpet", + props: ContourCarpetSchema, + description: + "Contour overlay on a Carpet plot. References the Carpet's `carpet` id and provides `a`/`b`/`z` triples (a flat list of carpet-space samples).", + component: ({ props }) => { + const trace = { + type: "contourcarpet", + carpet: props.carpet, + a: props.a, + b: props.b, + z: props.z, + ncontours: props.ncontours ?? 14, + colorscale: resolveColormap(props.colormap ?? "viridis"), + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/DensityMap.ts b/packages/plotly/src/traces/DensityMap.ts new file mode 100644 index 000000000..b4bd83c5f --- /dev/null +++ b/packages/plotly/src/traces/DensityMap.ts @@ -0,0 +1,49 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; +import { resolveColormap } from "../shell/colormap"; + +const DensityMapSchema = z.object({ + lat: z.array(z.number()), + lon: z.array(z.number()), + z: z.array(z.number()).optional(), + radius: z.number().positive().optional(), + colormap: z.string().optional(), + centerLat: z.number().optional(), + centerLon: z.number().optional(), + zoom: z.number().min(0).max(22).optional(), + style: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const DensityMap = defineComponent({ + name: "DensityMap", + props: DensityMapSchema, + description: + "Heatmap-style density overlay on an interactive map (MapLibre tiles). Pass `lat`/`lon` arrays. Optional `z` weights each point (e.g. magnitude); without it, density = count. `radius` controls smoothing kernel size in pixels.", + component: ({ props }) => { + const trace = { + type: "densitymap", + lat: props.lat, + lon: props.lon, + z: props.z, + radius: props.radius ?? 12, + colorscale: resolveColormap(props.colormap ?? "viridis"), + colorbar: { thickness: 12, outlinewidth: 0 }, + } as unknown as Data; + const layout = { + title: props.title ? { text: props.title } : undefined, + map: { + style: props.style ?? "open-street-map", + center: { lat: props.centerLat ?? 0, lon: props.centerLon ?? 0 }, + zoom: props.zoom ?? 1, + }, + margin: { t: props.title ? 32 : 0, l: 0, r: 0, b: 0 }, + } as unknown as Partial; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 420 }); + }, +}); diff --git a/packages/plotly/src/traces/Funnel.ts b/packages/plotly/src/traces/Funnel.ts new file mode 100644 index 000000000..f9c41dd30 --- /dev/null +++ b/packages/plotly/src/traces/Funnel.ts @@ -0,0 +1,40 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout } from "../helpers/buildTrace"; +import { PlotShell } from "../shell/PlotShell"; + +const FunnelSchema = z.object({ + stages: z.array(z.string()), + values: z.array(z.number()), + orientation: z.enum(["h", "v"]).optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Funnel = defineComponent({ + name: "Funnel", + props: FunnelSchema, + description: + "Funnel chart — horizontal bars per stage with auto-computed conversion percentages between stages. Pass parallel `stages` (labels) and `values` arrays.", + component: ({ props }) => { + const orientation = props.orientation ?? "h"; + const trace = { + type: "funnel", + orientation, + x: orientation === "h" ? props.values : (props.stages as Array), + y: orientation === "h" ? (props.stages as Array) : props.values, + textposition: "inside", + textinfo: "value+percent initial", + } as unknown as Data; + const layout: Partial = { + ...(buildAxisLayout(props) as Partial), + showlegend: false, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/FunnelArea.ts b/packages/plotly/src/traces/FunnelArea.ts new file mode 100644 index 000000000..fc87ea1c1 --- /dev/null +++ b/packages/plotly/src/traces/FunnelArea.ts @@ -0,0 +1,34 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const FunnelAreaSchema = z.object({ + stages: z.array(z.string()), + values: z.array(z.number()), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const FunnelArea = defineComponent({ + name: "FunnelArea", + props: FunnelAreaSchema, + description: + "Triangular funnel-area chart (a.k.a. ribbon funnel). Like Funnel but the area of each stage is proportional to value. Good for visual conversion stories where exact widths matter less than overall shape.", + component: ({ props }) => { + const trace: Data = { + type: "funnelarea", + values: props.values, + labels: props.stages, + textinfo: "label+percent", + }; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + showlegend: false, + margin: { t: props.title ? 40 : 8, l: 8, r: 8, b: 8 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Heatmap.ts b/packages/plotly/src/traces/Heatmap.ts new file mode 100644 index 000000000..17079cabb --- /dev/null +++ b/packages/plotly/src/traces/Heatmap.ts @@ -0,0 +1,60 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout } from "../helpers/buildTrace"; +import { isDivergingColormap, resolveColormap } from "../shell/colormap"; +import { PlotShell } from "../shell/PlotShell"; + +const HeatmapSchema = z.object({ + z: z.array(z.array(z.number())), + x: z.array(z.union([z.string(), z.number()])).optional(), + y: z.array(z.union([z.string(), z.number()])).optional(), + colormap: z.string().optional(), + zmin: z.number().optional(), + zmax: z.number().optional(), + showText: z.boolean().optional(), + textFormat: z.string().optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Heatmap = defineComponent({ + name: "Heatmap", + props: HeatmapSchema, + description: + "2D heatmap of a numeric matrix. `z` is rows×cols. `x`/`y` are optional axis labels. `colormap`: 'viridis' (default), 'inferno', 'plasma', 'magma', 'cividis', 'turbo', 'blues', 'RdBu', 'BrBG', 'PiYG', 'spectral'. Diverging colormaps auto-center on 0 unless `zmin`/`zmax` are provided. `showText=true` overlays cell values; `textFormat` is a Plotly d3-format string (default '.2f').", + component: ({ props }) => { + if (!props.z || props.z.length === 0) return null; + const colorscale = resolveColormap(props.colormap ?? "viridis"); + const div = isDivergingColormap(props.colormap); + + let zmin = props.zmin; + let zmax = props.zmax; + if (div && zmin === undefined && zmax === undefined) { + const flat = props.z.flat().filter(Number.isFinite) as number[]; + const absMax = Math.max(...flat.map(Math.abs)); + zmin = -absMax; + zmax = absMax; + } + + const trace: Data = { + type: "heatmap", + z: props.z, + x: props.x as Array | undefined, + y: props.y as Array | undefined, + colorscale: colorscale as never, + zmin, + zmax, + hoverongaps: false, + texttemplate: props.showText ? `%{z:${props.textFormat ?? ".2f"}}` : undefined, + textfont: props.showText ? { size: 10 } : undefined, + colorbar: { thickness: 12, outlinewidth: 0, tickfont: { size: 10 } }, + }; + const layout: Partial = buildAxisLayout(props) as Partial; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Histogram.ts b/packages/plotly/src/traces/Histogram.ts new file mode 100644 index 000000000..c9dedac2a --- /dev/null +++ b/packages/plotly/src/traces/Histogram.ts @@ -0,0 +1,56 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout, resolve, splitByGroup } from "../helpers/buildTrace"; +import { PlotShell } from "../shell/PlotShell"; + +const HistogramSchema = z.object({ + data: z.array(z.record(z.string(), z.unknown())).nullable().optional(), + x: z.union([z.array(z.number()), z.string()]), + color: z.union([z.array(z.string()), z.string()]).optional(), + nbinsx: z.number().int().positive().optional(), + histnorm: z.enum(["", "percent", "probability", "density", "probability density"]).optional(), + barmode: z.enum(["overlay", "stack", "group"]).optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Histogram = defineComponent({ + name: "Histogram", + props: HistogramSchema, + description: + "Frequency histogram of a single numeric variable. Pass `x` as a field name (with `data`) or a raw array. `color` enables per-group histograms. `histnorm`: '' (count, default), 'probability', 'density', etc. `nbinsx` overrides Plotly's auto-binning.", + component: ({ props }) => { + // Histogram needs only x; treat y as identical to x for resolve(). + const r = resolve({ + data: props.data ?? undefined, + x: props.x, + y: props.x, + color: props.color, + }); + if (!r) return null; + const groups = splitByGroup(r); + const traces: Data[] = groups.map((g) => ({ + type: "histogram", + x: g.x as number[], + nbinsx: props.nbinsx, + histnorm: props.histnorm, + name: g.group || undefined, + opacity: groups.length > 1 ? 0.7 : 1, + })); + const layout: Partial = { + ...(buildAxisLayout({ + title: props.title, + xLabel: props.xLabel, + yLabel: props.yLabel ?? (props.histnorm ? "Density" : "Count"), + }) as Partial), + barmode: props.barmode ?? (groups.length > 1 ? "overlay" : "group"), + showlegend: groups.length > 1 && groups[0]!.group !== "", + }; + return React.createElement(PlotShell, { data: traces, layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Histogram2D.ts b/packages/plotly/src/traces/Histogram2D.ts new file mode 100644 index 000000000..70d0dab5e --- /dev/null +++ b/packages/plotly/src/traces/Histogram2D.ts @@ -0,0 +1,42 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout } from "../helpers/buildTrace"; +import { resolveColormap } from "../shell/colormap"; +import { PlotShell } from "../shell/PlotShell"; + +const Histogram2DSchema = z.object({ + x: z.array(z.number()), + y: z.array(z.number()), + nbinsx: z.number().int().positive().optional(), + nbinsy: z.number().int().positive().optional(), + colormap: z.string().optional(), + histnorm: z.enum(["", "percent", "probability", "density", "probability density"]).optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Histogram2D = defineComponent({ + name: "Histogram2D", + props: Histogram2DSchema, + description: + "Rectangular 2D-histogram heatmap (counts of points falling in each bin). Use for >500 points where a Scatter would overplot. `nbinsx`/`nbinsy` override auto-binning.", + component: ({ props }) => { + const trace = { + type: "histogram2d", + x: props.x, + y: props.y, + nbinsx: props.nbinsx, + nbinsy: props.nbinsy, + histnorm: props.histnorm, + colorscale: resolveColormap(props.colormap ?? "viridis"), + colorbar: { thickness: 12, outlinewidth: 0 }, + } as unknown as Data; + const layout: Partial = buildAxisLayout(props) as Partial; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Histogram2DContour.ts b/packages/plotly/src/traces/Histogram2DContour.ts new file mode 100644 index 000000000..bfc90f6bc --- /dev/null +++ b/packages/plotly/src/traces/Histogram2DContour.ts @@ -0,0 +1,53 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout } from "../helpers/buildTrace"; +import { resolveColormap } from "../shell/colormap"; +import { PlotShell } from "../shell/PlotShell"; + +const Histogram2DContourSchema = z.object({ + x: z.array(z.number()), + y: z.array(z.number()), + ncontours: z.number().int().positive().optional(), + colormap: z.string().optional(), + showPoints: z.boolean().optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Histogram2DContour = defineComponent({ + name: "Histogram2DContour", + props: Histogram2DContourSchema, + description: + "Smooth 2D KDE contour density of a point cloud. Use to show density structure where Histogram2D would feel chunky. `showPoints=true` overlays the raw samples on top.", + component: ({ props }) => { + const traces: Data[] = [ + { + type: "histogram2dcontour", + x: props.x, + y: props.y, + ncontours: props.ncontours ?? 20, + colorscale: resolveColormap(props.colormap ?? "viridis") as never, + colorbar: { thickness: 12, outlinewidth: 0 }, + showscale: true, + }, + ]; + if (props.showPoints) { + traces.push({ + type: "scatter", + mode: "markers", + x: props.x, + y: props.y, + marker: { size: 2, color: "rgba(15,23,42,0.4)" }, + showlegend: false, + hoverinfo: "skip", + }); + } + const layout: Partial = buildAxisLayout(props) as Partial; + return React.createElement(PlotShell, { data: traces, layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Icicle.ts b/packages/plotly/src/traces/Icicle.ts new file mode 100644 index 000000000..b87f8f3ac --- /dev/null +++ b/packages/plotly/src/traces/Icicle.ts @@ -0,0 +1,42 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const IcicleSchema = z.object({ + ids: z.array(z.string()), + parents: z.array(z.string()), + values: z.array(z.number()).optional(), + labels: z.array(z.string()).optional(), + orientation: z.enum(["h", "v"]).optional(), + branchvalues: z.enum(["remainder", "total"]).optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Icicle = defineComponent({ + name: "Icicle", + props: IcicleSchema, + description: + "Icicle chart — rectangular hierarchical layout (think horizontal Sunburst). Same parallel `ids`/`parents`/`values` shape as Treemap. `orientation='h'` (default) stacks horizontally, 'v' vertically.", + component: ({ props }) => { + if (!props.ids?.length) return null; + const trace = { + type: "icicle", + ids: props.ids, + parents: props.parents, + values: props.values, + labels: props.labels ?? props.ids, + branchvalues: props.branchvalues ?? "remainder", + tiling: { orientation: props.orientation ?? "h" }, + hovertemplate: "%{label}
value: %{value}", + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + margin: { t: props.title ? 40 : 8, l: 8, r: 8, b: 8 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Image.ts b/packages/plotly/src/traces/Image.ts new file mode 100644 index 000000000..2c7a3d2f7 --- /dev/null +++ b/packages/plotly/src/traces/Image.ts @@ -0,0 +1,36 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const ImageSchema = z.object({ + z: z.array(z.array(z.array(z.number()))), + source: z.string().optional(), + colormodel: z.enum(["rgb", "rgba", "hsl", "hsla"]).optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Image = defineComponent({ + name: "Image", + props: ImageSchema, + description: + "Render a 2D image / pixel matrix as a Plotly trace. Pass `z` as a height×width×channels array (RGB or RGBA). Use `colormodel` to switch color spaces. Useful for rendering ML model attention maps, tensors, or small generated images inline.", + component: ({ props }) => { + const trace = { + type: "image", + z: props.z, + source: props.source, + colormodel: props.colormodel ?? "rgb", + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + xaxis: { showticklabels: false, showgrid: false, zeroline: false }, + yaxis: { showticklabels: false, showgrid: false, zeroline: false, scaleanchor: "x" }, + margin: { t: props.title ? 32 : 8, l: 8, r: 8, b: 8 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Indicator.ts b/packages/plotly/src/traces/Indicator.ts new file mode 100644 index 000000000..8e574b80d --- /dev/null +++ b/packages/plotly/src/traces/Indicator.ts @@ -0,0 +1,73 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const IndicatorSchema = z.object({ + value: z.number(), + reference: z.number().optional(), + mode: z + .enum(["number", "delta", "gauge", "number+delta", "number+gauge", "gauge+number+delta"]) + .optional(), + title: z.string().optional(), + prefix: z.string().optional(), + suffix: z.string().optional(), + decimals: z.number().int().min(0).optional(), + rangeMin: z.number().optional(), + rangeMax: z.number().optional(), + threshold: z.number().optional(), + height: z.number().positive().optional(), +}); + +export const Indicator = defineComponent({ + name: "Indicator", + props: IndicatorSchema, + description: + "Big-number / delta / gauge indicator. `mode`: 'number' (default) | 'delta' | 'gauge' | combinations like 'number+delta'. For deltas, pass `reference` (the previous value). For gauges, pass `rangeMin`/`rangeMax` (default 0..value*2) and optional `threshold`.", + component: ({ props }) => { + const mode = props.mode ?? "number"; + const usesGauge = mode.includes("gauge"); + const usesDelta = mode.includes("delta"); + const trace = { + type: "indicator", + mode, + value: props.value, + title: props.title ? { text: props.title, font: { size: 13 } } : undefined, + number: { + prefix: props.prefix, + suffix: props.suffix, + valueformat: props.decimals !== undefined ? `,.${props.decimals}f` : ",", + font: { size: 36 }, + }, + delta: usesDelta + ? { + reference: props.reference, + relative: false, + valueformat: ",.1f", + } + : undefined, + gauge: usesGauge + ? { + axis: { + range: [props.rangeMin ?? 0, props.rangeMax ?? props.value * 2], + }, + bar: { color: "#4c78a8" }, + threshold: + props.threshold !== undefined + ? { line: { color: "#dc2626", width: 3 }, value: props.threshold } + : undefined, + } + : undefined, + } as unknown as Data; + const layout: Partial = { + margin: { t: 24, b: 16, l: 24, r: 24 }, + }; + return React.createElement(PlotShell, { + data: [trace], + layout, + height: props.height ?? (usesGauge ? 240 : 160), + }); + }, +}); diff --git a/packages/plotly/src/traces/Isosurface.ts b/packages/plotly/src/traces/Isosurface.ts new file mode 100644 index 000000000..7c0a9c3ce --- /dev/null +++ b/packages/plotly/src/traces/Isosurface.ts @@ -0,0 +1,47 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; +import { resolveColormap } from "../shell/colormap"; + +const IsosurfaceSchema = z.object({ + x: z.array(z.number()), + y: z.array(z.number()), + z: z.array(z.number()), + value: z.array(z.number()), + isomin: z.number().optional(), + isomax: z.number().optional(), + surfaceCount: z.number().int().positive().optional(), + colormap: z.string().optional(), + opacity: z.number().min(0).max(1).optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Isosurface = defineComponent({ + name: "Isosurface", + props: IsosurfaceSchema, + description: + "Isosurface — 3D level-sets of a scalar field. `x`/`y`/`z` are sample point coordinates (typically a structured grid flattened); `value` is the scalar at each. `surfaceCount` controls how many iso-levels are drawn between `isomin` and `isomax`.", + component: ({ props }) => { + const trace = { + type: "isosurface", + x: props.x, + y: props.y, + z: props.z, + value: props.value, + isomin: props.isomin, + isomax: props.isomax, + surface: { count: props.surfaceCount ?? 4 }, + colorscale: resolveColormap(props.colormap ?? "viridis"), + opacity: props.opacity ?? 0.6, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + margin: { t: props.title ? 32 : 8, l: 0, r: 0, b: 0 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 420 }); + }, +}); diff --git a/packages/plotly/src/traces/Line.ts b/packages/plotly/src/traces/Line.ts new file mode 100644 index 000000000..f2578d405 --- /dev/null +++ b/packages/plotly/src/traces/Line.ts @@ -0,0 +1,50 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout, resolve, splitByGroup } from "../helpers/buildTrace"; +import { PlotShell } from "../shell/PlotShell"; + +const LineSchema = z.object({ + data: z.array(z.record(z.string(), z.unknown())).nullable().optional(), + x: z.union([z.array(z.union([z.string(), z.number()])), z.string()]), + y: z.union([z.array(z.number()), z.string()]), + color: z.union([z.array(z.string()), z.string()]).optional(), + smooth: z.boolean().optional(), + showMarkers: z.boolean().optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Line = defineComponent({ + name: "Line", + props: LineSchema, + description: + "Line chart. Same `data`/`x`/`y` patterns as Bar — Express style with field names, or Graph-Objects with parallel arrays. `color` adds multiple series. `smooth=true` uses spline interpolation; `showMarkers=true` overlays point markers.", + component: ({ props }) => { + const r = resolve({ + data: props.data ?? undefined, + x: props.x, + y: props.y, + color: props.color, + }); + if (!r) return null; + const groups = splitByGroup(r); + const traces: Data[] = groups.map((g) => ({ + type: "scatter", + mode: props.showMarkers ? "lines+markers" : "lines", + x: g.x as Array, + y: g.y as number[], + name: g.group || undefined, + line: props.smooth ? { shape: "spline", smoothing: 1.0 } : undefined, + })); + const layout: Partial = { + ...(buildAxisLayout(props) as Partial), + showlegend: groups.length > 1 && groups[0]!.group !== "", + }; + return React.createElement(PlotShell, { data: traces, layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Mesh3D.ts b/packages/plotly/src/traces/Mesh3D.ts new file mode 100644 index 000000000..1afe7f5ae --- /dev/null +++ b/packages/plotly/src/traces/Mesh3D.ts @@ -0,0 +1,47 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; +import { resolveColormap } from "../shell/colormap"; + +const Mesh3DSchema = z.object({ + x: z.array(z.number()), + y: z.array(z.number()), + z: z.array(z.number()), + i: z.array(z.number()).optional(), + j: z.array(z.number()).optional(), + k: z.array(z.number()).optional(), + intensity: z.array(z.number()).optional(), + colormap: z.string().optional(), + opacity: z.number().min(0).max(1).optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Mesh3D = defineComponent({ + name: "Mesh3D", + props: Mesh3DSchema, + description: + "WebGL 3D mesh from a point cloud. Pass `x`/`y`/`z` (vertex coordinates). For an explicit triangulation pass `i`/`j`/`k` (vertex index triples per triangle); without them, Plotly computes a Delaunay/alpha-shape triangulation. Optional `intensity` for vertex coloring.", + component: ({ props }) => { + const trace = { + type: "mesh3d", + x: props.x, + y: props.y, + z: props.z, + i: props.i, + j: props.j, + k: props.k, + intensity: props.intensity, + colorscale: resolveColormap(props.colormap ?? "viridis"), + opacity: props.opacity ?? 1, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + margin: { t: props.title ? 32 : 8, l: 0, r: 0, b: 0 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 420 }); + }, +}); diff --git a/packages/plotly/src/traces/OHLC.ts b/packages/plotly/src/traces/OHLC.ts new file mode 100644 index 000000000..36f2901f5 --- /dev/null +++ b/packages/plotly/src/traces/OHLC.ts @@ -0,0 +1,46 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout } from "../helpers/buildTrace"; +import { PlotShell } from "../shell/PlotShell"; + +const OHLCSchema = z.object({ + x: z.array(z.union([z.string(), z.number()])), + open: z.array(z.number()), + high: z.array(z.number()), + low: z.array(z.number()), + close: z.array(z.number()), + upColor: z.string().optional(), + downColor: z.string().optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const OHLC = defineComponent({ + name: "OHLC", + props: OHLCSchema, + description: + "OHLC bar chart (open-high-low-close). Same shape as Candlestick but without the body fill — preferred when you want a denser time-series view.", + component: ({ props }) => { + const trace = { + type: "ohlc", + x: props.x as Array, + open: props.open, + high: props.high, + low: props.low, + close: props.close, + increasing: { line: { color: props.upColor ?? "#16a34a" } }, + decreasing: { line: { color: props.downColor ?? "#dc2626" } }, + } as unknown as Data; + const layout: Partial = { + ...(buildAxisLayout(props) as Partial), + xaxis: { rangeslider: { visible: false } }, + showlegend: false, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/ParCats.ts b/packages/plotly/src/traces/ParCats.ts new file mode 100644 index 000000000..d23aa19fc --- /dev/null +++ b/packages/plotly/src/traces/ParCats.ts @@ -0,0 +1,45 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const ParCatsSchema = z.object({ + data: z.array(z.record(z.string(), z.unknown())), + dimensions: z.array(z.string()), + color: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ParCats = defineComponent({ + name: "ParCats", + props: ParCatsSchema, + description: + "Parallel categories plot — multi-dimensional ribbon view of categorical fields. Pass `data` rows and `dimensions` (categorical field names). Optional `color` field gives ribbon coloring (numeric or categorical).", + component: ({ props }) => { + const rows = props.data ?? []; + const dims = props.dimensions ?? []; + if (rows.length === 0 || dims.length === 0) return null; + const dimensionsArr = dims.map((d) => ({ + label: d, + values: rows.map((r) => r[d]), + })); + const trace = { + type: "parcats", + dimensions: dimensionsArr, + line: props.color + ? { + color: rows.map((r) => r[props.color as string]), + colorscale: "Viridis", + } + : undefined, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + margin: { t: props.title ? 60 : 32, l: 60, r: 60, b: 24 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 360 }); + }, +}); diff --git a/packages/plotly/src/traces/ParCoords.ts b/packages/plotly/src/traces/ParCoords.ts new file mode 100644 index 000000000..389f7eced --- /dev/null +++ b/packages/plotly/src/traces/ParCoords.ts @@ -0,0 +1,48 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; +import { resolveColormap } from "../shell/colormap"; + +const ParCoordsSchema = z.object({ + data: z.array(z.record(z.string(), z.unknown())), + dimensions: z.array(z.string()), + color: z.string().optional(), + colormap: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ParCoords = defineComponent({ + name: "ParCoords", + props: ParCoordsSchema, + description: + "Parallel coordinates plot — one polyline per row across multiple axes. Pass `data` as row objects and `dimensions` as the field names to plot. `color` is an optional numeric field name for continuous coloring (uses `colormap`, default 'viridis'). Brushing on each axis filters interactively.", + component: ({ props }) => { + const rows = props.data ?? []; + const dims = props.dimensions ?? []; + if (rows.length === 0 || dims.length === 0) return null; + const dimensionsArr = dims.map((d) => ({ + label: d, + values: rows.map((r) => Number(r[d])), + })); + const trace = { + type: "parcoords", + dimensions: dimensionsArr, + line: props.color + ? { + color: rows.map((r) => Number(r[props.color as string])), + colorscale: resolveColormap(props.colormap ?? "viridis"), + showscale: true, + } + : { color: "#4c78a8" }, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + margin: { t: props.title ? 60 : 32, l: 60, r: 60, b: 24 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 360 }); + }, +}); diff --git a/packages/plotly/src/traces/Pie.ts b/packages/plotly/src/traces/Pie.ts new file mode 100644 index 000000000..01e8616b7 --- /dev/null +++ b/packages/plotly/src/traces/Pie.ts @@ -0,0 +1,77 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +// IMPORTANT: Pie and Donut must NOT share a single schema instance — defineComponent +// tags the schema with the component name via schemaIdTags, and the second call +// overwrites the first. Effect: zod's toJSONSchema() only emits the last tag in +// $defs and the parser catalog drops the first component as "unknown". +// Build two structurally identical but distinct schemas instead. +const pieFields = () => + ({ + values: z.array(z.number()), + labels: z.array(z.string()), + hole: z.number().min(0).max(0.95).optional(), + pull: z.union([z.number(), z.array(z.number())]).optional(), + sort: z.boolean().optional(), + rotation: z.number().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), + }) as const; + +const PieSchema = z.object(pieFields()); +const DonutSchema = z.object(pieFields()); + +export const Pie = defineComponent({ + name: "Pie", + props: PieSchema, + description: + "Pie chart. Pass parallel `values` and `labels` arrays. `hole` (0..1) makes a donut. `pull` (0..1) explodes one or all slices. `sort=true` orders slices by value. Use Donut as a shortcut for a 0.5 hole.", + component: ({ props }) => { + const trace: Data = { + type: "pie", + values: props.values, + labels: props.labels, + hole: props.hole, + pull: props.pull as never, + sort: props.sort ?? true, + rotation: props.rotation, + textinfo: "label+percent", + hovertemplate: "%{label}
%{value} (%{percent})", + }; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + showlegend: false, + margin: { t: props.title ? 40 : 8, l: 8, r: 8, b: 8 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); + +export const Donut = defineComponent({ + name: "Donut", + props: DonutSchema, + description: "Donut chart — Pie with `hole=0.5` by default. Same props as Pie.", + component: ({ props }) => { + const trace: Data = { + type: "pie", + values: props.values, + labels: props.labels, + hole: props.hole ?? 0.5, + pull: props.pull as never, + sort: props.sort ?? true, + rotation: props.rotation, + textinfo: "label+percent", + hovertemplate: "%{label}
%{value} (%{percent})", + }; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + showlegend: false, + margin: { t: props.title ? 40 : 8, l: 8, r: 8, b: 8 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Sankey.ts b/packages/plotly/src/traces/Sankey.ts new file mode 100644 index 000000000..823213554 --- /dev/null +++ b/packages/plotly/src/traces/Sankey.ts @@ -0,0 +1,67 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const NodeSchema = z.object({ + id: z.string(), + label: z.string().optional(), + color: z.string().optional(), +}); +const LinkSchema = z.object({ + source: z.string(), + target: z.string(), + value: z.number(), + color: z.string().optional(), +}); + +const SankeySchema = z.object({ + nodes: z.array(NodeSchema), + links: z.array(LinkSchema), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Sankey = defineComponent({ + name: "Sankey", + props: SankeySchema, + description: + "Sankey flow diagram. `nodes`: [{ id, label?, color? }, ...]; `links`: [{ source, target, value, color? }] where source/target reference node ids. Width of each link is proportional to value.", + component: ({ props }) => { + const nodes = props.nodes ?? []; + const links = props.links ?? []; + if (nodes.length === 0) return null; + const idToIndex = new Map(nodes.map((n, i) => [n.id, i])); + const validLinks = links.filter( + (l) => idToIndex.has(l.source) && idToIndex.has(l.target) && l.value > 0, + ); + if (validLinks.length === 0) return null; + + const trace: Data = { + type: "sankey", + orientation: "h", + arrangement: "snap", + node: { + label: nodes.map((n) => n.label ?? n.id), + color: nodes.map((n) => n.color ?? undefined) as never, + pad: 14, + thickness: 16, + line: { color: "rgba(15,23,42,0.10)", width: 0.5 }, + }, + link: { + source: validLinks.map((l) => idToIndex.get(l.source) as number), + target: validLinks.map((l) => idToIndex.get(l.target) as number), + value: validLinks.map((l) => l.value), + color: validLinks.map((l) => l.color ?? "rgba(76,120,168,0.45)"), + }, + }; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + margin: { t: props.title ? 40 : 8, l: 8, r: 8, b: 8 }, + font: { size: 11 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Scatter.ts b/packages/plotly/src/traces/Scatter.ts new file mode 100644 index 000000000..aa80b9716 --- /dev/null +++ b/packages/plotly/src/traces/Scatter.ts @@ -0,0 +1,83 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout, resolve, splitByGroup } from "../helpers/buildTrace"; +import { resolveColormap } from "../shell/colormap"; +import { PlotShell } from "../shell/PlotShell"; + +const ScatterSchema = z.object({ + data: z.array(z.record(z.string(), z.unknown())).nullable().optional(), + x: z.union([z.array(z.number()), z.string()]), + y: z.union([z.array(z.number()), z.string()]), + color: z.union([z.array(z.union([z.string(), z.number()])), z.string()]).optional(), + size: z.union([z.array(z.number()), z.string(), z.number()]).optional(), + colormap: z.string().optional(), + trendline: z.boolean().optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Scatter = defineComponent({ + name: "Scatter", + props: ScatterSchema, + description: + "Scatter plot. `color` may be a categorical column (string values → discrete colors per group) or a numeric column (continuous coloring via `colormap`, default 'viridis'). `size` accepts a constant, an array, or a field name for variable point size.", + component: ({ props }) => { + const r = resolve({ + data: props.data ?? undefined, + x: props.x, + y: props.y, + color: props.color, + }); + if (!r) return null; + + const sizeArr: number[] | number | undefined = (() => { + if (typeof props.size === "number") return props.size; + if (Array.isArray(props.size)) return props.size as number[]; + if (typeof props.size === "string" && props.data) { + return (props.data as Array>).map((row) => { + const v = row[props.size as string]; + return typeof v === "number" ? v : 0; + }); + } + return undefined; + })(); + + if (r.color) { + // Continuous coloring — single trace with colorscale. + const trace: Data = { + type: "scatter", + mode: "markers", + x: r.x as number[], + y: r.y as number[], + marker: { + color: r.color, + colorscale: resolveColormap(props.colormap ?? "viridis") as never, + size: sizeArr ?? 6, + showscale: true, + }, + }; + const layout: Partial = buildAxisLayout(props) as Partial; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + } + + const groups = splitByGroup(r); + const traces: Data[] = groups.map((g) => ({ + type: "scatter", + mode: "markers", + x: g.x as number[], + y: g.y as number[], + name: g.group || undefined, + marker: { size: sizeArr ?? 6 }, + })); + const layout: Partial = { + ...(buildAxisLayout(props) as Partial), + showlegend: groups.length > 1 && groups[0]!.group !== "", + }; + return React.createElement(PlotShell, { data: traces, layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Scatter3D.ts b/packages/plotly/src/traces/Scatter3D.ts new file mode 100644 index 000000000..c11acb584 --- /dev/null +++ b/packages/plotly/src/traces/Scatter3D.ts @@ -0,0 +1,60 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; +import { resolveColormap } from "../shell/colormap"; + +const Scatter3DSchema = z.object({ + x: z.array(z.number()), + y: z.array(z.number()), + z: z.array(z.number()), + mode: z.enum(["markers", "lines", "lines+markers"]).optional(), + color: z.union([z.array(z.number()), z.string()]).optional(), + size: z.union([z.array(z.number()), z.number()]).optional(), + colormap: z.string().optional(), + text: z.array(z.string()).optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + zLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Scatter3D = defineComponent({ + name: "Scatter3D", + props: Scatter3DSchema, + description: + "WebGL 3D scatter / line plot. Pass `x`/`y`/`z` arrays. `color` may be a constant string or numeric array (uses `colormap`, default 'viridis'). `mode='lines'` for 3D paths.", + component: ({ props }) => { + const numericColor = Array.isArray(props.color) ? (props.color as number[]) : undefined; + const trace: Data = { + type: "scatter3d", + x: props.x, + y: props.y, + z: props.z, + mode: props.mode ?? "markers", + text: props.text, + marker: { + size: (props.size as never) ?? 4, + color: (numericColor ?? props.color) as never, + colorscale: numericColor + ? (resolveColormap(props.colormap ?? "viridis") as never) + : undefined, + showscale: !!numericColor, + }, + line: { width: 2, color: numericColor ? undefined : (props.color as string) }, + }; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + scene: { + xaxis: { title: { text: props.xLabel ?? "x" } }, + yaxis: { title: { text: props.yLabel ?? "y" } }, + zaxis: { title: { text: props.zLabel ?? "z" } }, + }, + margin: { t: props.title ? 32 : 8, l: 0, r: 0, b: 0 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 420 }); + }, +}); diff --git a/packages/plotly/src/traces/ScatterCarpet.ts b/packages/plotly/src/traces/ScatterCarpet.ts new file mode 100644 index 000000000..332628721 --- /dev/null +++ b/packages/plotly/src/traces/ScatterCarpet.ts @@ -0,0 +1,37 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const ScatterCarpetSchema = z.object({ + carpet: z.string(), + a: z.array(z.number()), + b: z.array(z.number()), + mode: z.enum(["markers", "lines", "lines+markers"]).optional(), + text: z.array(z.string()).optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ScatterCarpet = defineComponent({ + name: "ScatterCarpet", + props: ScatterCarpetSchema, + description: + "Scatter trace overlaid on a Carpet plot. References the Carpet's `carpet` id and provides `a`/`b` coordinates in carpet space. Compose alongside a Carpet trace in a Figure.", + component: ({ props }) => { + const trace = { + type: "scattercarpet", + carpet: props.carpet, + a: props.a, + b: props.b, + mode: props.mode ?? "markers", + text: props.text, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/ScatterGL.ts b/packages/plotly/src/traces/ScatterGL.ts new file mode 100644 index 000000000..7bd4596e0 --- /dev/null +++ b/packages/plotly/src/traces/ScatterGL.ts @@ -0,0 +1,68 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout, resolve, splitByGroup } from "../helpers/buildTrace"; +import { resolveColormap } from "../shell/colormap"; +import { PlotShell } from "../shell/PlotShell"; + +const ScatterGLSchema = z.object({ + data: z.array(z.record(z.string(), z.unknown())).nullable().optional(), + x: z.union([z.array(z.number()), z.string()]), + y: z.union([z.array(z.number()), z.string()]), + color: z.union([z.array(z.union([z.string(), z.number()])), z.string()]).optional(), + size: z.union([z.array(z.number()), z.number()]).optional(), + colormap: z.string().optional(), + mode: z.enum(["markers", "lines", "lines+markers"]).optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ScatterGL = defineComponent({ + name: "ScatterGL", + props: ScatterGLSchema, + description: + "WebGL-accelerated scatter — same API as Scatter but renders thousands to millions of points without choking the canvas. Use for high-cardinality scatter (>10k points). Loses some text-rendering features but is dramatically faster.", + component: ({ props }) => { + const r = resolve({ + data: props.data ?? undefined, + x: props.x, + y: props.y, + color: props.color, + }); + if (!r) return null; + if (r.color) { + const trace: Data = { + type: "scattergl", + mode: props.mode ?? "markers", + x: r.x as number[], + y: r.y as number[], + marker: { + color: r.color, + colorscale: resolveColormap(props.colormap ?? "viridis") as never, + size: (props.size as never) ?? 4, + showscale: true, + }, + }; + const layout: Partial = buildAxisLayout(props) as Partial; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + } + const groups = splitByGroup(r); + const traces: Data[] = groups.map((g) => ({ + type: "scattergl", + mode: props.mode ?? "markers", + x: g.x as number[], + y: g.y as number[], + name: g.group || undefined, + marker: { size: (props.size as never) ?? 4 }, + })); + const layout: Partial = { + ...(buildAxisLayout(props) as Partial), + showlegend: groups.length > 1 && groups[0]!.group !== "", + }; + return React.createElement(PlotShell, { data: traces, layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/ScatterGeo.ts b/packages/plotly/src/traces/ScatterGeo.ts new file mode 100644 index 000000000..c1b8ec231 --- /dev/null +++ b/packages/plotly/src/traces/ScatterGeo.ts @@ -0,0 +1,51 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const ScatterGeoSchema = z.object({ + lat: z.array(z.number()), + lon: z.array(z.number()), + text: z.array(z.string()).optional(), + size: z.union([z.array(z.number()), z.number()]).optional(), + color: z.union([z.array(z.string()), z.string()]).optional(), + scope: z + .enum(["world", "usa", "europe", "asia", "africa", "north america", "south america"]) + .optional(), + mode: z.enum(["markers", "lines", "lines+markers"]).optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ScatterGeo = defineComponent({ + name: "ScatterGeo", + props: ScatterGeoSchema, + description: + "Geographic scatter / lines on a globe (cartesian projection). Pass `lat` and `lon` arrays. `text` for hover labels per point. `mode='lines'` draws great-circle paths between consecutive lat/lon pairs.", + component: ({ props }) => { + const trace: Data = { + type: "scattergeo", + lat: props.lat, + lon: props.lon, + text: props.text, + mode: props.mode ?? "markers", + marker: { + size: (props.size as never) ?? 6, + color: props.color as never, + }, + }; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + geo: { + scope: props.scope ?? "world", + showland: true, + landcolor: "rgba(15,23,42,0.04)", + projection: { type: "natural earth" }, + } as never, + margin: { t: props.title ? 32 : 8, l: 0, r: 0, b: 0 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 380 }); + }, +}); diff --git a/packages/plotly/src/traces/ScatterMap.ts b/packages/plotly/src/traces/ScatterMap.ts new file mode 100644 index 000000000..b533c4b74 --- /dev/null +++ b/packages/plotly/src/traces/ScatterMap.ts @@ -0,0 +1,51 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const ScatterMapSchema = z.object({ + lat: z.array(z.number()), + lon: z.array(z.number()), + text: z.array(z.string()).optional(), + size: z.union([z.array(z.number()), z.number()]).optional(), + color: z.union([z.array(z.string()), z.string()]).optional(), + mode: z.enum(["markers", "lines", "lines+markers"]).optional(), + centerLat: z.number().optional(), + centerLon: z.number().optional(), + zoom: z.number().min(0).max(22).optional(), + style: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ScatterMap = defineComponent({ + name: "ScatterMap", + props: ScatterMapSchema, + description: + "Interactive map points using MapLibre tiles (no token required as of plotly.js 3.x). Pass `lat`/`lon` arrays. `centerLat`/`centerLon`/`zoom` set the initial viewport. `style`: 'open-street-map' (default), 'carto-positron', 'carto-darkmatter', 'white-bg', 'basic'.", + component: ({ props }) => { + const trace = { + type: "scattermap", + lat: props.lat, + lon: props.lon, + text: props.text, + mode: props.mode ?? "markers", + marker: { + size: props.size ?? 8, + color: props.color, + }, + } as unknown as Data; + const layout = { + title: props.title ? { text: props.title } : undefined, + map: { + style: props.style ?? "open-street-map", + center: { lat: props.centerLat ?? 0, lon: props.centerLon ?? 0 }, + zoom: props.zoom ?? 1, + }, + margin: { t: props.title ? 32 : 0, l: 0, r: 0, b: 0 }, + } as unknown as Partial; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 420 }); + }, +}); diff --git a/packages/plotly/src/traces/ScatterMatrix.ts b/packages/plotly/src/traces/ScatterMatrix.ts new file mode 100644 index 000000000..5f6c7a554 --- /dev/null +++ b/packages/plotly/src/traces/ScatterMatrix.ts @@ -0,0 +1,61 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const ScatterMatrixSchema = z.object({ + data: z.array(z.record(z.string(), z.unknown())), + dimensions: z.array(z.string()), + color: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ScatterMatrix = defineComponent({ + name: "ScatterMatrix", + props: ScatterMatrixSchema, + description: + "Pairwise scatter matrix (a.k.a. SPLOM / pair plot). Pass `data` as an array of objects and `dimensions` as the field names to plot pairwise. Optional `color` field for categorical grouping (one color per unique value).", + component: ({ props }) => { + const rows = props.data ?? []; + const dims = props.dimensions ?? []; + if (rows.length === 0 || dims.length < 2) return null; + + const dimensionsArr = dims.map((d) => ({ + label: d, + values: rows.map((r) => r[d] as number), + })); + + let marker: Record = { size: 4, line: { width: 0 } }; + if (props.color) { + const groups = Array.from(new Set(rows.map((r) => String(r[props.color as string])))); + const groupIdx = rows.map((r) => groups.indexOf(String(r[props.color as string]))); + marker = { + size: 4, + color: groupIdx, + colorscale: groups.map((_, i) => [ + i / Math.max(1, groups.length - 1), + `hsl(${(i * 360) / groups.length}, 60%, 50%)`, + ]), + showscale: false, + line: { width: 0 }, + }; + } + + const trace = { + type: "splom", + dimensions: dimensionsArr, + marker, + diagonal: { visible: false }, + showupperhalf: false, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + dragmode: "select", + hovermode: "closest", + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/ScatterPolar.ts b/packages/plotly/src/traces/ScatterPolar.ts new file mode 100644 index 000000000..fc390a3fc --- /dev/null +++ b/packages/plotly/src/traces/ScatterPolar.ts @@ -0,0 +1,43 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const ScatterPolarSchema = z.object({ + r: z.array(z.number()), + theta: z.union([z.array(z.number()), z.array(z.string())]), + mode: z.enum(["markers", "lines", "lines+markers"]).optional(), + fill: z.enum(["none", "toself", "tonext"]).optional(), + thetaUnit: z.enum(["radians", "degrees"]).optional(), + color: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ScatterPolar = defineComponent({ + name: "ScatterPolar", + props: ScatterPolarSchema, + description: + "Polar scatter / line / radar plot. Pass parallel `r` (radii) and `theta` (angles in degrees by default; pass `thetaUnit='radians'` to switch). `mode`: 'markers' | 'lines' | 'lines+markers'. `fill='toself'` makes a closed radar-style polygon.", + component: ({ props }) => { + const trace: Data = { + type: "scatterpolar", + r: props.r, + theta: props.theta as never, + mode: props.mode ?? "markers", + fill: props.fill, + marker: props.color ? { color: props.color } : undefined, + line: props.color ? { color: props.color } : undefined, + thetaunit: (props.thetaUnit ?? "degrees") as never, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + polar: { angularaxis: { direction: "clockwise" } }, + showlegend: false, + margin: { t: props.title ? 40 : 16, l: 16, r: 16, b: 16 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/ScatterPolarGL.ts b/packages/plotly/src/traces/ScatterPolarGL.ts new file mode 100644 index 000000000..9124709f1 --- /dev/null +++ b/packages/plotly/src/traces/ScatterPolarGL.ts @@ -0,0 +1,38 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const ScatterPolarGLSchema = z.object({ + r: z.array(z.number()), + theta: z.union([z.array(z.number()), z.array(z.string())]), + mode: z.enum(["markers", "lines", "lines+markers"]).optional(), + color: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ScatterPolarGL = defineComponent({ + name: "ScatterPolarGL", + props: ScatterPolarGLSchema, + description: + "WebGL-accelerated polar scatter. Same API as ScatterPolar but for very-high-point-count polar data. Use when ScatterPolar feels sluggish past ~10k points.", + component: ({ props }) => { + const trace = { + type: "scatterpolargl", + r: props.r, + theta: props.theta, + mode: props.mode ?? "markers", + marker: props.color ? { color: props.color } : undefined, + line: props.color ? { color: props.color } : undefined, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + polar: { angularaxis: { direction: "clockwise" } }, + showlegend: false, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/ScatterSmith.ts b/packages/plotly/src/traces/ScatterSmith.ts new file mode 100644 index 000000000..62383d287 --- /dev/null +++ b/packages/plotly/src/traces/ScatterSmith.ts @@ -0,0 +1,38 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const ScatterSmithSchema = z.object({ + real: z.array(z.number()), + imag: z.array(z.number()), + text: z.array(z.string()).optional(), + mode: z.enum(["markers", "lines", "lines+markers"]).optional(), + color: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ScatterSmith = defineComponent({ + name: "ScatterSmith", + props: ScatterSmithSchema, + description: + "Smith chart — complex impedance / reflection coefficient scatter. Used in RF/microwave engineering. Pass parallel `real` and `imag` arrays of normalized impedance values.", + component: ({ props }) => { + const trace = { + type: "scattersmith", + real: props.real, + imag: props.imag, + mode: props.mode ?? "markers", + text: props.text, + marker: props.color ? { color: props.color } : undefined, + line: props.color ? { color: props.color } : undefined, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 420 }); + }, +}); diff --git a/packages/plotly/src/traces/ScatterTernary.ts b/packages/plotly/src/traces/ScatterTernary.ts new file mode 100644 index 000000000..1a0a8a797 --- /dev/null +++ b/packages/plotly/src/traces/ScatterTernary.ts @@ -0,0 +1,49 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const ScatterTernarySchema = z.object({ + a: z.array(z.number()), + b: z.array(z.number()), + c: z.array(z.number()), + text: z.array(z.string()).optional(), + mode: z.enum(["markers", "lines", "lines+markers"]).optional(), + size: z.union([z.array(z.number()), z.number()]).optional(), + color: z.union([z.array(z.string()), z.string()]).optional(), + aLabel: z.string().optional(), + bLabel: z.string().optional(), + cLabel: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const ScatterTernary = defineComponent({ + name: "ScatterTernary", + props: ScatterTernarySchema, + description: + "Ternary scatter — points in a triangle whose three corners are constrained to sum to a constant. Pass parallel `a`/`b`/`c` arrays (compositions). Common in chemistry, soil science, mineralogy.", + component: ({ props }) => { + const trace = { + type: "scatterternary", + a: props.a, + b: props.b, + c: props.c, + mode: props.mode ?? "markers", + text: props.text, + marker: { size: props.size ?? 8, color: props.color }, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + ternary: { + sum: 100, + aaxis: { title: { text: props.aLabel ?? "a" } }, + baxis: { title: { text: props.bLabel ?? "b" } }, + caxis: { title: { text: props.cLabel ?? "c" } }, + } as never, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 420 }); + }, +}); diff --git a/packages/plotly/src/traces/StreamTube.ts b/packages/plotly/src/traces/StreamTube.ts new file mode 100644 index 000000000..56cb71ab0 --- /dev/null +++ b/packages/plotly/src/traces/StreamTube.ts @@ -0,0 +1,45 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; +import { resolveColormap } from "../shell/colormap"; + +const StreamTubeSchema = z.object({ + x: z.array(z.number()), + y: z.array(z.number()), + z: z.array(z.number()), + u: z.array(z.number()), + v: z.array(z.number()), + w: z.array(z.number()), + colormap: z.string().optional(), + maxdisplayed: z.number().int().positive().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const StreamTube = defineComponent({ + name: "StreamTube", + props: StreamTubeSchema, + description: + "3D stream tubes — integrated paths through a vector field starting from given seed points. Same input shape as Cone (`x,y,z,u,v,w`). `maxdisplayed` caps the number of tubes for performance.", + component: ({ props }) => { + const trace = { + type: "streamtube", + x: props.x, + y: props.y, + z: props.z, + u: props.u, + v: props.v, + w: props.w, + colorscale: resolveColormap(props.colormap ?? "viridis"), + maxdisplayed: props.maxdisplayed, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + margin: { t: props.title ? 32 : 8, l: 0, r: 0, b: 0 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 420 }); + }, +}); diff --git a/packages/plotly/src/traces/Sunburst.ts b/packages/plotly/src/traces/Sunburst.ts new file mode 100644 index 000000000..283f2daa2 --- /dev/null +++ b/packages/plotly/src/traces/Sunburst.ts @@ -0,0 +1,42 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const SunburstSchema = z.object({ + ids: z.array(z.string()), + parents: z.array(z.string()), + values: z.array(z.number()).optional(), + labels: z.array(z.string()).optional(), + branchvalues: z.enum(["remainder", "total"]).optional(), + colormap: z.string().optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Sunburst = defineComponent({ + name: "Sunburst", + props: SunburstSchema, + description: + "Hierarchical sunburst. Pass parallel arrays: `ids` (unique node ids), `parents` (parent id of each node — empty string for the root), `values` (leaf-only or all-nodes; controlled by `branchvalues`). Optional `labels` (defaults to `ids`). `branchvalues='total'` if values include parent sums; 'remainder' (default) if Plotly should sum from leaves.", + component: ({ props }) => { + if (!props.ids?.length || !props.parents?.length) return null; + const trace: Data = { + type: "sunburst", + ids: props.ids, + parents: props.parents, + values: props.values, + labels: props.labels ?? props.ids, + branchvalues: props.branchvalues ?? "remainder", + hovertemplate: + "%{label}
value: %{value}
share: %{percentEntry:.1%} of parent", + }; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + margin: { t: props.title ? 40 : 8, l: 8, r: 8, b: 8 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Surface.ts b/packages/plotly/src/traces/Surface.ts new file mode 100644 index 000000000..aa79a0944 --- /dev/null +++ b/packages/plotly/src/traces/Surface.ts @@ -0,0 +1,52 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; +import { resolveColormap } from "../shell/colormap"; + +const SurfaceSchema = z.object({ + z: z.array(z.array(z.number())), + x: z.array(z.number()).optional(), + y: z.array(z.number()).optional(), + colormap: z.string().optional(), + showContours: z.boolean().optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + zLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Surface = defineComponent({ + name: "Surface", + props: SurfaceSchema, + description: + "WebGL 3D surface plot. Pass `z` as a height×width matrix; optional `x`/`y` axis values. `showContours=true` projects contour lines onto the surface for shape readability.", + component: ({ props }) => { + const trace = { + type: "surface", + z: props.z, + x: props.x, + y: props.y, + colorscale: resolveColormap(props.colormap ?? "viridis"), + contours: props.showContours + ? { + z: { show: true, usecolormap: true, highlightcolor: "#ffffff", project: { z: true } }, + } + : undefined, + showscale: true, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + scene: { + xaxis: { title: { text: props.xLabel ?? "x" } }, + yaxis: { title: { text: props.yLabel ?? "y" } }, + zaxis: { title: { text: props.zLabel ?? "z" } }, + }, + margin: { t: props.title ? 32 : 8, l: 0, r: 0, b: 0 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 420 }); + }, +}); diff --git a/packages/plotly/src/traces/Table.ts b/packages/plotly/src/traces/Table.ts new file mode 100644 index 000000000..ec961dee5 --- /dev/null +++ b/packages/plotly/src/traces/Table.ts @@ -0,0 +1,67 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const TableSchema = z.object({ + headers: z.array(z.string()), + values: z.array(z.array(z.union([z.string(), z.number()]))), + align: z.enum(["left", "center", "right"]).optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +// Defensive normalization — the LLM frequently emits Tables where: +// • the number of value columns < the number of headers, or +// • columns have different lengths. +// Rather than rendering empty space, pad to a consistent rectangle and use +// "—" for cells the model never filled in. This makes incomplete data +// visibly incomplete instead of looking like an empty table. +function normalize( + headers: string[], + values: Array>, +): Array> { + const numCols = headers.length; + const cols = Array.from({ length: numCols }, (_, i) => values[i] ?? []); + const rowCount = Math.max(0, ...cols.map((c) => c.length)); + return cols.map((c) => { + if (c.length === rowCount) return c; + return [...c, ...Array(rowCount - c.length).fill("—")]; + }); +} + +export const Table = defineComponent({ + name: "Table", + props: TableSchema, + description: + "Plotly table — COLUMN-oriented. `headers` is the list of column titles. `values` is an array of columns, where each column is an array of cells (e.g. `Table(['Name','Age'], [['Alice','Bob'], [30, 25]])`). Pass ALL columns matching headers length — short or missing columns are padded with '—' so missing data is visible.", + component: ({ props }) => { + const headers = props.headers ?? []; + if (headers.length === 0) return null; + const cols = normalize(headers, props.values ?? []); + const trace = { + type: "table", + header: { + values: headers, + align: props.align ?? "left", + fill: { color: "rgba(15,23,42,0.04)" }, + font: { size: 11, color: "rgba(15,23,42,0.85)" }, + line: { color: "rgba(15,23,42,0.10)" }, + }, + cells: { + values: cols, + align: props.align ?? "left", + font: { size: 12, color: "#0f172a" }, + line: { color: "rgba(15,23,42,0.06)" }, + height: 28, + }, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + margin: { t: props.title ? 32 : 4, l: 4, r: 4, b: 4 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Treemap.ts b/packages/plotly/src/traces/Treemap.ts new file mode 100644 index 000000000..c16c1a132 --- /dev/null +++ b/packages/plotly/src/traces/Treemap.ts @@ -0,0 +1,41 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; + +const TreemapSchema = z.object({ + ids: z.array(z.string()), + parents: z.array(z.string()), + values: z.array(z.number()).optional(), + labels: z.array(z.string()).optional(), + branchvalues: z.enum(["remainder", "total"]).optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Treemap = defineComponent({ + name: "Treemap", + props: TreemapSchema, + description: + "Hierarchical treemap. Pass parallel arrays: `ids`, `parents` (root parent is empty string ''), `values`, optional `labels`. `branchvalues='total'` if values include parent sums; 'remainder' (default) sums from leaves.", + component: ({ props }) => { + if (!props.ids?.length || !props.parents?.length) return null; + const trace: Data = { + type: "treemap", + ids: props.ids, + parents: props.parents, + values: props.values, + labels: props.labels ?? props.ids, + branchvalues: props.branchvalues ?? "remainder", + hovertemplate: + "%{label}
value: %{value}
share: %{percentEntry:.1%} of parent", + }; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + margin: { t: props.title ? 40 : 8, l: 8, r: 8, b: 8 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Violin.ts b/packages/plotly/src/traces/Violin.ts new file mode 100644 index 000000000..3424e8ed7 --- /dev/null +++ b/packages/plotly/src/traces/Violin.ts @@ -0,0 +1,60 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout, resolve, splitByGroup } from "../helpers/buildTrace"; +import { PlotShell } from "../shell/PlotShell"; + +const ViolinSchema = z.object({ + data: z.array(z.record(z.string(), z.unknown())).nullable().optional(), + x: z.union([z.array(z.string()), z.string()]).optional(), + y: z.union([z.array(z.number()), z.string()]), + color: z.union([z.array(z.string()), z.string()]).optional(), + showBox: z.boolean().optional(), + showPoints: z.union([z.boolean(), z.enum(["all", "outliers", "suspectedoutliers"])]).optional(), + side: z.enum(["both", "positive", "negative"]).optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Violin = defineComponent({ + name: "Violin", + props: ViolinSchema, + description: + "Violin plot. Pass `y` (the numeric variable) and optionally `x` (a categorical grouping field/array) to compare distributions across groups. `showBox=true` overlays the box; `showPoints` controls jittered point overlay ('all' | 'outliers' | true | false). For ridgeline-style stacking, set `side='positive'`.", + component: ({ props }) => { + // Violin always needs y; x is optional for grouping. + const r = resolve({ + data: props.data ?? undefined, + x: props.x ?? props.y, + y: props.y, + color: props.color, + }); + if (!r) return null; + const groups = splitByGroup(r); + const points = + props.showPoints === undefined ? false : props.showPoints === true ? "all" : props.showPoints; + const traces: Data[] = groups.map((g) => ({ + type: "violin", + x: + typeof props.x === "string" || Array.isArray(props.x) + ? (g.x as Array) + : undefined, + y: g.y as number[], + name: g.group || undefined, + box: { visible: props.showBox ?? true }, + meanline: { visible: true }, + points: points as never, + side: props.side, + })); + const layout = { + ...(buildAxisLayout(props) as Partial), + showlegend: groups.length > 1 && groups[0]!.group !== "", + violinmode: "group", + } as Partial; + return React.createElement(PlotShell, { data: traces, layout, height: props.height }); + }, +}); diff --git a/packages/plotly/src/traces/Volume.ts b/packages/plotly/src/traces/Volume.ts new file mode 100644 index 000000000..495c005d7 --- /dev/null +++ b/packages/plotly/src/traces/Volume.ts @@ -0,0 +1,47 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { PlotShell } from "../shell/PlotShell"; +import { resolveColormap } from "../shell/colormap"; + +const VolumeSchema = z.object({ + x: z.array(z.number()), + y: z.array(z.number()), + z: z.array(z.number()), + value: z.array(z.number()), + isomin: z.number().optional(), + isomax: z.number().optional(), + surfaceCount: z.number().int().positive().optional(), + colormap: z.string().optional(), + opacity: z.number().min(0).max(1).optional(), + title: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Volume = defineComponent({ + name: "Volume", + props: VolumeSchema, + description: + "Volume rendering of a 3D scalar field — multiple translucent isosurfaces stacked. Like Isosurface but with semi-transparent layering for the full volumetric structure. Same input shape (`x,y,z,value`).", + component: ({ props }) => { + const trace = { + type: "volume", + x: props.x, + y: props.y, + z: props.z, + value: props.value, + isomin: props.isomin, + isomax: props.isomax, + surface: { count: props.surfaceCount ?? 12 }, + colorscale: resolveColormap(props.colormap ?? "viridis"), + opacity: props.opacity ?? 0.15, + } as unknown as Data; + const layout: Partial = { + title: props.title ? { text: props.title } : undefined, + margin: { t: props.title ? 32 : 8, l: 0, r: 0, b: 0 }, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height ?? 420 }); + }, +}); diff --git a/packages/plotly/src/traces/Waterfall.ts b/packages/plotly/src/traces/Waterfall.ts new file mode 100644 index 000000000..852694edb --- /dev/null +++ b/packages/plotly/src/traces/Waterfall.ts @@ -0,0 +1,45 @@ +"use client"; +import { defineComponent } from "@openuidev/react-lang"; +import type { Data, Layout } from "plotly.js"; +import React from "react"; +import { z } from "zod/v4"; +import { buildAxisLayout } from "../helpers/buildTrace"; +import { PlotShell } from "../shell/PlotShell"; + +const WaterfallSchema = z.object({ + x: z.array(z.union([z.string(), z.number()])), + y: z.array(z.number()), + measure: z.array(z.enum(["relative", "total", "absolute"])).optional(), + orientation: z.enum(["v", "h"]).optional(), + title: z.string().optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + height: z.number().positive().optional(), +}); + +export const Waterfall = defineComponent({ + name: "Waterfall", + props: WaterfallSchema, + description: + "Waterfall chart — running totals with up/down deltas. `x` is category labels, `y` is the delta value at each step. `measure` per step: 'relative' (default — adds to running total), 'total' (subtotal), 'absolute' (resets running total).", + component: ({ props }) => { + const orientation = props.orientation ?? "v"; + const measure = props.measure ?? props.y.map(() => "relative" as const); + const trace = { + type: "waterfall", + orientation, + x: orientation === "v" ? (props.x as Array) : props.y, + y: orientation === "v" ? props.y : (props.x as Array), + measure, + connector: { line: { color: "rgba(15,23,42,0.18)" } }, + increasing: { marker: { color: "#16a34a" } }, + decreasing: { marker: { color: "#dc2626" } }, + totals: { marker: { color: "#4c78a8" } }, + } as unknown as Data; + const layout: Partial = { + ...(buildAxisLayout(props) as Partial), + showlegend: false, + }; + return React.createElement(PlotShell, { data: [trace], layout, height: props.height }); + }, +}); diff --git a/packages/plotly/styles/plotly.css b/packages/plotly/styles/plotly.css new file mode 100644 index 000000000..7eb6320ff --- /dev/null +++ b/packages/plotly/styles/plotly.css @@ -0,0 +1,38 @@ +@keyframes openui-plotly-shimmer { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} + +@keyframes openui-plotly-mount { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.openui-plotly-mount { + animation: openui-plotly-mount 320ms cubic-bezier(0.22, 1, 0.36, 1) backwards; +} + +/* Plotly's default modebar can clash with the chat surface — ensure it sits + above the chart on hover but stays out of the way otherwise. */ +.openui-plotly-mount .modebar-container { + z-index: 2; +} +.openui-plotly-mount:hover .modebar-container { + opacity: 1; +} + +/* Soften Plotly's hover label transitions to match the rest of OpenUI */ +.openui-plotly-mount .hoverlayer .hovertext { + filter: drop-shadow(0 1px 2px rgba(15, 23, 42, 0.06)) + drop-shadow(0 4px 12px rgba(15, 23, 42, 0.08)); +} diff --git a/packages/plotly/tsconfig.json b/packages/plotly/tsconfig.json new file mode 100644 index 000000000..59b6ae42e --- /dev/null +++ b/packages/plotly/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": false, + "declarationMap": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules", "dist", "tsdown.config.ts"] +} diff --git a/packages/plotly/tsdown.config.ts b/packages/plotly/tsdown.config.ts new file mode 100644 index 000000000..6ee8f6fcd --- /dev/null +++ b/packages/plotly/tsdown.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm", "cjs"], + dts: false, + sourcemap: true, + clean: false, + treeshake: true, + external: [ + "react", + "react-dom", + "react/jsx-runtime", + /^plotly\.js/, + "plotly.js-dist-min", + /^react-plotly\.js/, + /^@openuidev\//, + /^zod/, + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46f5095dc..93be238ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1152,6 +1152,52 @@ importers: specifier: ^22.15.32 version: 22.15.32 + packages/plotly: + dependencies: + '@openuidev/react-headless': + specifier: workspace:^ + version: link:../react-headless + '@openuidev/react-lang': + specifier: workspace:^ + version: link:../react-lang + '@openuidev/react-ui': + specifier: workspace:^ + version: link:../react-ui + plotly.js-dist-min: + specifier: ^3.0.0 + version: 3.5.1 + react: + specifier: ^18.3.1 || ^19.0.0 + version: 19.2.4 + react-dom: + specifier: ^18.0.0 || ^19.0.0 + version: 19.2.4(react@19.2.4) + react-plotly.js: + specifier: ^2.6.0 + version: 2.6.0(plotly.js@3.5.1(mapbox-gl@1.13.3))(react@19.2.4) + zod: + specifier: ^3.25.0 || ^4.0.0 + version: 4.3.6 + devDependencies: + '@types/node': + specifier: ^22.15.32 + version: 22.15.32 + '@types/plotly.js': + specifier: ^3.0.0 + version: 3.0.10 + '@types/plotly.js-dist-min': + specifier: ^2.3.4 + version: 2.3.4 + '@types/react': + specifier: '>=18.3.1' + version: 19.2.14 + '@types/react-dom': + specifier: '>=18.3.1' + version: 19.2.3(@types/react@19.2.14) + '@types/react-plotly.js': + specifier: ^2.6.3 + version: 2.6.4 + packages/react-email: dependencies: '@openuidev/react-lang': @@ -2322,6 +2368,10 @@ packages: '@chevrotain/utils@11.1.2': resolution: {integrity: sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==} + '@choojs/findup@0.2.1': + resolution: {integrity: sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==} + hasBin: true + '@chromatic-com/storybook@3.2.6': resolution: {integrity: sha512-FDmn5Ry2DzQdik+eq2sp/kJMMT36Ewe7ONXUXM2Izd97c7r6R/QyGli8eyh/F0iyqVvbLveNYFyF0dBOJNwLqw==} engines: {node: '>=16.0.0', yarn: '>=1.22.18'} @@ -3935,11 +3985,53 @@ packages: resolution: {integrity: sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==} engines: {node: '>=8'} + '@mapbox/geojson-rewind@0.5.2': + resolution: {integrity: sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==} + hasBin: true + + '@mapbox/geojson-types@1.0.2': + resolution: {integrity: sha512-e9EBqHHv3EORHrSfbR9DqecPNn+AmuAoQxV6aL8Xu30bJMJR1o8PZLZzpk1Wq7/NfCbuhmakHTPYRhoqLsXRnw==} + + '@mapbox/jsonlint-lines-primitives@2.0.2': + resolution: {integrity: sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==} + engines: {node: '>= 0.6'} + + '@mapbox/mapbox-gl-supported@1.5.0': + resolution: {integrity: sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==} + peerDependencies: + mapbox-gl: '>=0.32.1 <2.0.0' + '@mapbox/node-pre-gyp@2.0.3': resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} engines: {node: '>=18'} hasBin: true + '@mapbox/point-geometry@0.1.0': + resolution: {integrity: sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==} + + '@mapbox/tiny-sdf@1.2.5': + resolution: {integrity: sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw==} + + '@mapbox/tiny-sdf@2.2.0': + resolution: {integrity: sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==} + + '@mapbox/unitbezier@0.0.0': + resolution: {integrity: sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==} + + '@mapbox/unitbezier@0.0.1': + resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==} + + '@mapbox/vector-tile@1.3.1': + resolution: {integrity: sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==} + + '@mapbox/whoots-js@3.1.0': + resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==} + engines: {node: '>=6.0.0'} + + '@maplibre/maplibre-gl-style-spec@20.4.0': + resolution: {integrity: sha512-AzBy3095fTFPjDjmWpR2w6HVRAZJ6hQZUCwk5Plz6EyfnfuQW1odeW5i2Ai47Y6TBA2hQnC+azscjBSALpaWgw==} + hasBin: true + '@mastra/client-js@1.11.2': resolution: {integrity: sha512-CCjrC1TIuu1hKhnRZPumVpW3ePL3xTafxBY93hIaxPUm/8F/dS1symv4YD0hgFQv/ajiVrjeQcWK+zp2KovDCA==} engines: {node: '>=22.13.0'} @@ -4981,6 +5073,25 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@plotly/d3-sankey-circular@0.33.1': + resolution: {integrity: sha512-FgBV1HEvCr3DV7RHhDsPXyryknucxtfnLwPtCKKxdolKyTFYoLX/ibEfX39iFYIL7DYbVeRtP43dbFcrHNE+KQ==} + + '@plotly/d3-sankey@0.7.2': + resolution: {integrity: sha512-2jdVos1N3mMp3QW0k2q1ph7Gd6j5PY1YihBrwpkFnKqO+cqtZq3AdEYUeSGXMeLsBDQYiqTVcihYfk8vr5tqhw==} + + '@plotly/d3@3.8.2': + resolution: {integrity: sha512-wvsNmh1GYjyJfyEBPKJLTMzgf2c2bEbSIL50lmqVUi+o1NHaLPi1Lb4v7VxXXJn043BhNyrxUrWI85Q+zmjOVA==} + + '@plotly/mapbox-gl@1.13.4': + resolution: {integrity: sha512-sR3/Pe5LqT/fhYgp4rT4aSFf1rTsxMbGiH6Hojc7PH36ny5Bn17iVFUjpzycafETURuFbLZUfjODO8LvSI+5zQ==} + engines: {node: '>=6.4.0'} + + '@plotly/point-cluster@3.1.9': + resolution: {integrity: sha512-MwaI6g9scKf68Orpr1pHZ597pYx9uP8UEFXLPbsCmuw3a84obwz6pnMXGc90VhgDNeNiLEdlmuK7CPo+5PIxXw==} + + '@plotly/regl@2.1.2': + resolution: {integrity: sha512-Mdk+vUACbQvjd0m/1JJjOOafmkp/EpmHjISsopEz5Av44CBq7rPC05HHNbYGKVyNUF2zmEoBS/TT0pd0SPFFyw==} + '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -7983,6 +8094,21 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' + '@turf/area@7.3.5': + resolution: {integrity: sha512-sSn80wPT7XfBIDN3vurCPxhk9W4U8ozS/XImSqeLN8qveTICOxzZkhsGDMp0CuncaN+plWut4a2TdNM7mzZB6Q==} + + '@turf/bbox@7.3.5': + resolution: {integrity: sha512-oG1ya/HtBjAIg4TimbWx+nOYPbY0bCvt82Bq8tm6sBw3qqtbOyRSfDz79Sq90TnH7DXJprJ1qnVGKNtZ6jemfw==} + + '@turf/centroid@7.3.5': + resolution: {integrity: sha512-hkWaqwGFdOn6Tf0EWfn2yn1XZ1FWE1h2C5ZWstDMu/FxYO5DB+YjlmOFPl4K6SmSOEgdV07eK2vDCyPeTHqKGA==} + + '@turf/helpers@7.3.5': + resolution: {integrity: sha512-E/NMGV5MwbjjP7AJXBtsanC3yY8N2MQ87IGdIgkB2ji5AtBpwnH4L3gEqpYN4RlCJJWbLbzO91BbKv2waUd0eg==} + + '@turf/meta@7.3.5': + resolution: {integrity: sha512-r+ohqxoyqeigFB0oFrQx/YEHIkOKqcKpCjvZkvZs7Tkv+IFco5MezAd2zd4rzK+0DfFgDP3KpJc7HqrYjvEjhg==} + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -8142,6 +8268,9 @@ packages: '@types/express@4.17.25': resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} + '@types/geojson-vt@3.2.5': + resolution: {integrity: sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==} + '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} @@ -8184,6 +8313,12 @@ packages: '@types/lodash@4.17.18': resolution: {integrity: sha512-KJ65INaxqxmU6EoCiJmRPZC9H9RVWCRd349tXM2M3O5NA7cY6YL7c0bHAHQ93NOfTObEQ004kd2QVHs/r0+m4g==} + '@types/mapbox__point-geometry@0.1.4': + resolution: {integrity: sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==} + + '@types/mapbox__vector-tile@1.3.4': + resolution: {integrity: sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -8211,6 +8346,15 @@ packages: '@types/node@25.3.2': resolution: {integrity: sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==} + '@types/pbf@3.0.5': + resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==} + + '@types/plotly.js-dist-min@2.3.4': + resolution: {integrity: sha512-ISwLFV6Zs/v3DkaRFLyk2rvYAfVdnYP2VVVy7h+fBDWw52sn7sMUzytkWiN4M75uxr1uz1uiBioePTDpAfoFIg==} + + '@types/plotly.js@3.0.10': + resolution: {integrity: sha512-q+MgO4aajC2HrO7FllTYWzrpdfbTjboSMfjkz/aXKjg1v7HNo1zMEFfAW7quKfk6SL+bH74A5ThBEps/7hZxOA==} + '@types/prismjs@1.26.6': resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==} @@ -8225,6 +8369,9 @@ packages: peerDependencies: '@types/react': ^19.2.0 + '@types/react-plotly.js@2.6.4': + resolution: {integrity: sha512-AU6w1u3qEGM0NmBA69PaOgNc0KPFA/+qkH6Uu9EBTJ45/WYOUoXi9AF5O15PRM2klpHSiHAAs4WnlI+OZAFmUA==} + '@types/react-syntax-highlighter@15.5.13': resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} @@ -8255,6 +8402,9 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/supercluster@7.1.3': + resolution: {integrity: sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -8761,6 +8911,9 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + abs-svg-path@0.1.1: + resolution: {integrity: sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -8779,6 +8932,11 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn@7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} @@ -8925,10 +9083,17 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} + array-bounds@1.0.1: + resolution: {integrity: sha512-8wdW3ZGk6UjMPJx/glyEt0sLzzwAE1bhToPsO1W2pbpR2gULyxe3BjSiuJFheP50T/GgODVPz2fuMUmIywt8cQ==} + array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} + array-find-index@1.0.2: + resolution: {integrity: sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==} + engines: {node: '>=0.10.0'} + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} @@ -8936,6 +9101,12 @@ packages: resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} engines: {node: '>= 0.4'} + array-normalize@1.1.4: + resolution: {integrity: sha512-fCp0wKFLjvSPmCn4F5Tiw4M3lpMZoHlCjfcs7nNzuj3vqQQ1/a8cgB9DXcpDSn18c+coLnaW7rqfcYCvKbyJXg==} + + array-range@1.0.1: + resolution: {integrity: sha512-shdaI1zT3CVNL2hnx9c0JMc0ZogGaxDs5e85akgHWKYa0yVbIyp06Ind3dVkTj/uuFrzaHBOyqFzo+VV6aXgtA==} + array.prototype.findlast@1.2.5: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} @@ -9155,6 +9326,10 @@ packages: bare-url@2.4.0: resolution: {integrity: sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==} + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -9181,6 +9356,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + binary-search-bounds@2.0.5: + resolution: {integrity: sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==} + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -9190,6 +9368,15 @@ packages: birpc@4.0.0: resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + bit-twiddle@1.0.2: + resolution: {integrity: sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==} + + bitmap-sdf@1.0.4: + resolution: {integrity: sha512-1G3U4n5JE6RAiALMxu0p1XmeZkTeCwGKykzsLTCqVzfSDaN6S7fKnkIkfejogz+iwqBWc0UYAIKnKHNN7pSfDg==} + + bl@2.2.1: + resolution: {integrity: sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==} + body-parser@1.20.4: resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -9449,6 +9636,9 @@ packages: cjs-module-lexer@1.4.3: resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + clamp@1.0.1: + resolution: {integrity: sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA==} + class-transformer@0.5.1: resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} @@ -9512,6 +9702,9 @@ packages: collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + color-alpha@1.1.3: + resolution: {integrity: sha512-krPYBO1RSO5LH4AGb/b6z70O1Ip2o0F0+0cVFN5FN99jfQtZFT08rQyg+9oOBNJYAn3SRwJIFC8jUEOKz7PisA==} + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -9519,12 +9712,37 @@ packages: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-id@1.1.0: + resolution: {integrity: sha512-2iRtAn6dC/6/G7bBIo0uupVrIne1NsQJvJxZOBCzQOfk7jRq97feaDZ3RdzuHakRXXnHGNwglto3pqtRx1sX0g==} + color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-name@2.1.0: + resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} + engines: {node: '>=12.20'} + + color-normalize@1.5.0: + resolution: {integrity: sha512-rUT/HDXMr6RFffrR53oX3HGWkDOP9goSAQGBkUaAYKjOE2JxozccdGyufageWDlInRAjm/jYPrf/Y38oa+7obw==} + + color-parse@1.4.3: + resolution: {integrity: sha512-BADfVl/FHkQkyo8sRBwMYBqemqsgnu7JZAwUgvBvuwwuNUZAhSvLTbsEErS5bQXzOjDR0dWzJ4vXN2Q+QoPx0A==} + + color-parse@2.0.2: + resolution: {integrity: sha512-eCtOz5w5ttWIUcaKLiktF+DxZO1R9KLNY/xhbV6CkhM7sR3GhVghmt6X6yOnzeaM24po+Z9/S1apbXMwA3Iepw==} + + color-rgba@2.4.0: + resolution: {integrity: sha512-Nti4qbzr/z2LbUWySr7H9dk3Rl7gZt7ihHAxlgT4Ho90EXWkjtkL1avTleu9yeGuqrt/chxTB6GKK8nZZ6V0+Q==} + + color-rgba@3.0.0: + resolution: {integrity: sha512-PPwZYkEY3M2THEHHV6Y95sGUie77S7X8v+h1r6LSAPF3/LL2xJ8duUXSrkic31Nzc4odPwHgUbiX/XuTYzQHQg==} + + color-space@2.3.2: + resolution: {integrity: sha512-BcKnbOEsOarCwyoLstcoEztwT0IJxqqQkNwDuA3a65sICvvHL2yoeV13psoDFh5IuiOMnIOKdQDwB4Mk3BypiA==} + colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} @@ -9599,6 +9817,10 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} + concurrently@9.2.0: resolution: {integrity: sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==} engines: {node: '>=18'} @@ -9682,6 +9904,9 @@ packages: cose-base@2.2.0: resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + country-regex@1.1.0: + resolution: {integrity: sha512-iSPlClZP8vX7MC3/u6s3lrDuoQyhQukh5LyABJ3hvfzbQ3Yyayd4fp04zjLnfi267B/B2FkumcWWgrbban7sSA==} + crc-32@1.2.2: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} @@ -9715,12 +9940,33 @@ packages: peerDependencies: postcss: ^8.0.9 + css-font-size-keywords@1.0.0: + resolution: {integrity: sha512-Q+svMDbMlelgCfH/RVDKtTDaf5021O486ZThQPIpahnIjUkMUslC+WuOQSWTgGSrNCH08Y7tYNEmmy0hkfMI8Q==} + + css-font-stretch-keywords@1.0.1: + resolution: {integrity: sha512-KmugPO2BNqoyp9zmBIUGwt58UQSfyk1X5DbOlkb2pckDXFSAfjsD5wenb88fNrD6fvS+vu90a/tsPpb9vb0SLg==} + + css-font-style-keywords@1.0.1: + resolution: {integrity: sha512-0Fn0aTpcDktnR1RzaBYorIxQily85M2KXRpzmxQPgh8pxUN9Fcn00I8u9I3grNr1QXVgCl9T5Imx0ZwKU973Vg==} + + css-font-weight-keywords@1.0.0: + resolution: {integrity: sha512-5So8/NH+oDD+EzsnF4iaG4ZFHQ3vaViePkL1ZbZ5iC/KrsCY+WHq/lvOgrtmuOQ9pBBZ1ADGpaf+A4lj1Z9eYA==} + + css-font@1.2.0: + resolution: {integrity: sha512-V4U4Wps4dPDACJ4WpgofJ2RT5Yqwe1lEH6wlOOaIxMi0gTjdIijsc5FmxQlZ7ZZyKQkkutqqvULOp07l9c7ssA==} + + css-global-keywords@1.0.1: + resolution: {integrity: sha512-X1xgQhkZ9n94WDwntqst5D/FKkmiU0GlJSFZSV3kLvyJ1WC5VeyoXDOuleUD+SIuH9C7W05is++0Woh0CGfKjQ==} + css-in-js-utils@3.1.0: resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + css-system-font-keywords@1.0.0: + resolution: {integrity: sha512-1umTtVd/fXS25ftfjB71eASCrYhilmEsvDEI6wG/QplnmlfmVM5HkZ/ZX46DT5K3eblFPgLUHt5BRCb0YXkSFA==} + css-tree@1.1.3: resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} engines: {node: '>=8.0.0'} @@ -9740,6 +9986,9 @@ packages: css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + csscolorparser@1.0.3: + resolution: {integrity: sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -9788,6 +10037,9 @@ packages: resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} engines: {node: '>=0.10'} + d3-array@1.2.4: + resolution: {integrity: sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==} + d3-array@2.12.1: resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} @@ -9807,6 +10059,9 @@ packages: resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} engines: {node: '>=12'} + d3-collection@1.0.7: + resolution: {integrity: sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==} + d3-color@3.1.0: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} @@ -9819,6 +10074,9 @@ packages: resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} engines: {node: '>=12'} + d3-dispatch@1.0.6: + resolution: {integrity: sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==} + d3-dispatch@3.0.1: resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} engines: {node: '>=12'} @@ -9840,18 +10098,34 @@ packages: resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} engines: {node: '>=12'} + d3-force@1.2.1: + resolution: {integrity: sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==} + d3-force@3.0.0: resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} engines: {node: '>=12'} + d3-format@1.4.5: + resolution: {integrity: sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==} + d3-format@3.1.0: resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} engines: {node: '>=12'} + d3-geo-projection@2.9.0: + resolution: {integrity: sha512-ZULvK/zBn87of5rWAfFMc9mJOipeSo57O+BBitsKIXmU4rTVAnX1kSsJkE0R+TxY8pGNoM1nbyRRE7GYHhdOEQ==} + hasBin: true + + d3-geo@1.12.1: + resolution: {integrity: sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==} + d3-geo@3.1.1: resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} engines: {node: '>=12'} + d3-hierarchy@1.1.9: + resolution: {integrity: sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==} + d3-hierarchy@3.1.2: resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} engines: {node: '>=12'} @@ -9871,6 +10145,9 @@ packages: resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} engines: {node: '>=12'} + d3-quadtree@1.0.7: + resolution: {integrity: sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==} + d3-quadtree@3.0.1: resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} engines: {node: '>=12'} @@ -9901,14 +10178,23 @@ packages: resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} engines: {node: '>=12'} + d3-time-format@2.3.0: + resolution: {integrity: sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==} + d3-time-format@4.1.0: resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} engines: {node: '>=12'} + d3-time@1.1.0: + resolution: {integrity: sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==} + d3-time@3.1.0: resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} engines: {node: '>=12'} + d3-timer@1.0.10: + resolution: {integrity: sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==} + d3-timer@3.0.1: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} @@ -9927,6 +10213,10 @@ packages: resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} engines: {node: '>=12'} + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + dagre-d3-es@7.0.14: resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} @@ -10070,6 +10360,9 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + defined@1.0.1: + resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} + defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} @@ -10099,6 +10392,9 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-kerning@2.1.2: + resolution: {integrity: sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw==} + detect-libc@1.0.3: resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} engines: {node: '>=0.10'} @@ -10176,6 +10472,9 @@ packages: resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} engines: {node: '>=12'} + draw-svg-path@1.0.0: + resolution: {integrity: sha512-P8j3IHxcgRMcY6sDzr0QvJDLzBnJJqpTG33UZ2Pvp8rw0apCHhJCWqYprqrXjrgHnJ6tuhP1iTJSAodPDHxwkg==} + dset@3.1.4: resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} engines: {node: '>=4'} @@ -10189,13 +10488,29 @@ packages: oxc-resolver: optional: true + dtype@2.0.0: + resolution: {integrity: sha512-s2YVcLKdFGS0hpFqJaTwscsyt0E8nNFdmo73Ocd81xNPj4URI4rj6D60A+vFMIw7BXWlb4yRkEwfBqcZzPGiZg==} + engines: {node: '>= 0.8.0'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + dup@1.0.0: + resolution: {integrity: sha512-Bz5jxMMC0wgp23Zm15ip1x8IhYRqJvF3nFC0UInJUDkN1z4uNPk9jTnfCUJXbOGiQ1JbXLQsiV41Fb+HXcj5BA==} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + duplexify@3.7.1: + resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} + + earcut@2.2.4: + resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==} + + earcut@3.0.2: + resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -10213,6 +10528,9 @@ packages: electron-to-chromium@1.5.313: resolution: {integrity: sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==} + elementary-circuits-directed-graph@1.3.1: + resolution: {integrity: sha512-ZEiB5qkn2adYmpXGnJKkxT8uJHlW/mxmBpmeqawEHzPxh9HkLD4/1mFYX5l0On+f6rcPIt8/EWlRU2Vo3fX6dQ==} + embla-carousel-react@8.6.0: resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==} peerDependencies: @@ -10327,6 +10645,20 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + + es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + + es6-symbol@3.1.4: + resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} + engines: {node: '>=0.12'} + + es6-weak-map@2.0.3: + resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} + esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -10386,6 +10718,11 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + eslint-config-next@16.1.6: resolution: {integrity: sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==} peerDependencies: @@ -10542,6 +10879,10 @@ packages: esm-env@1.2.2: resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + esniff@2.0.1: + resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} + engines: {node: '>=0.10'} + espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -10605,6 +10946,9 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -10728,6 +11072,9 @@ packages: exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} @@ -10738,6 +11085,10 @@ packages: externality@1.0.2: resolution: {integrity: sha512-LyExtJWKxtgVzmgtEHyQtLFpw1KFhQphF9nTG8TpAIVkiI/xQ3FJh75tRFLYl4hkn7BNIIdLJInuDAavX35pMw==} + falafel@2.2.5: + resolution: {integrity: sha512-HuC1qF9iTnHDnML9YZAdCDQwT0yKl/U55K4XSUXqGAA2GLoafFgWRqdAbhWJxXaYD4pyoVxAJ8wH670jMpI9DQ==} + engines: {node: '>=0.4.0'} + fast-content-type-parse@3.0.0: resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} @@ -10768,6 +11119,9 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-isnumeric@1.1.4: + resolution: {integrity: sha512-1mM8qOr2LYz8zGaUdmiqRDiuue00Dxjgcb1NQR7TnhLVh6sQyngP9xvLo7Sl7LZpP/sk5eb+bcyWXw530NTBZw==} + fast-json-patch@3.1.1: resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} @@ -10877,9 +11231,18 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + flatten-vertex-data@1.0.2: + resolution: {integrity: sha512-BvCBFK2NZqerFTdMDgqfHBwxYWnxeCkwONsw6PvBMcUXqo8U/KDWwmXhqx1x2kLIg7DqIsJfOaJFOmlua3Lxuw==} + flow-enums-runtime@0.0.6: resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} + font-atlas@2.1.0: + resolution: {integrity: sha512-kP3AmvX+HJpW4w3d+PiPR2X6E1yvsBXt2yhuCw+yReO9F1WYhvZwx3c95DGZGwg9xYzDGrgJYa885xmVA+28Cg==} + + font-measure@1.2.2: + resolution: {integrity: sha512-mRLEpdrWzKe9hbfaF3Qpr06TAjquuBVP5cHy4b3hyeNdjc9i0PO6HniGsX5vjL5OWv7+Bd++NiooNpT/s8BvIA==} + fontfaceobserver@2.3.0: resolution: {integrity: sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==} @@ -10939,6 +11302,9 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} + from2@2.3.0: + resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -11072,10 +11438,19 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + geojson-vt@3.2.1: + resolution: {integrity: sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==} + + geojson-vt@4.0.2: + resolution: {integrity: sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-canvas-context@1.0.2: + resolution: {integrity: sha512-LnpfLf/TNzr9zVOGiIY6aKCz8EKuXmlYNV7CM2pUjBa/B+c2I15tS7KLySep75+FuerJdmArvJLcsAXWEy2H0A==} + get-east-asian-width@1.5.0: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} @@ -11099,6 +11474,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -11132,6 +11511,18 @@ packages: github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + gl-mat4@1.2.0: + resolution: {integrity: sha512-sT5C0pwB1/e9G9AvAoLsoaJtbMGjfd/jfxo8jMCKqYYEnjZuFvqV5rehqar0538EmssjdDeiEWnKyBSTw7quoA==} + + gl-matrix@3.4.4: + resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} + + gl-text@1.4.0: + resolution: {integrity: sha512-o47+XBqLCj1efmuNyCHt7/UEJmB9l66ql7pnobD6p+sgmBUdzfMZXIF0zD2+KRfpd99DJN+QXdvTFAGCKCVSmQ==} + + gl-util@3.1.3: + resolution: {integrity: sha512-dvRTggw5MSkJnCbh74jZzSoTOGnVYK+Bt+Ckqm39CVcl6+zSsxqWk4lr5NKhkqXHL6qvZAU9h17ZF8mIskY9mA==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -11160,6 +11551,10 @@ packages: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} + global-prefix@4.0.0: + resolution: {integrity: sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==} + engines: {node: '>=16'} + globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -11180,6 +11575,52 @@ packages: resolution: {integrity: sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==} engines: {node: '>=20'} + glsl-inject-defines@1.0.3: + resolution: {integrity: sha512-W49jIhuDtF6w+7wCMcClk27a2hq8znvHtlGnrYkSWEr8tHe9eA2dcnohlcAmxLYBSpSSdzOkRdyPTrx9fw49+A==} + + glsl-resolve@0.0.1: + resolution: {integrity: sha512-xxFNsfnhZTK9NBhzJjSBGX6IOqYpvBHxxmo+4vapiljyGNCY0Bekzn0firQkQrazK59c1hYxMDxYS8MDlhw4gA==} + + glsl-token-assignments@2.0.2: + resolution: {integrity: sha512-OwXrxixCyHzzA0U2g4btSNAyB2Dx8XrztY5aVUCjRSh4/D0WoJn8Qdps7Xub3sz6zE73W3szLrmWtQ7QMpeHEQ==} + + glsl-token-defines@1.0.0: + resolution: {integrity: sha512-Vb5QMVeLjmOwvvOJuPNg3vnRlffscq2/qvIuTpMzuO/7s5kT+63iL6Dfo2FYLWbzuiycWpbC0/KV0biqFwHxaQ==} + + glsl-token-depth@1.1.2: + resolution: {integrity: sha512-eQnIBLc7vFf8axF9aoi/xW37LSWd2hCQr/3sZui8aBJnksq9C7zMeUYHVJWMhFzXrBU7fgIqni4EhXVW4/krpg==} + + glsl-token-descope@1.0.2: + resolution: {integrity: sha512-kS2PTWkvi/YOeicVjXGgX5j7+8N7e56srNDEHDTVZ1dcESmbmpmgrnpjPcjxJjMxh56mSXYoFdZqb90gXkGjQw==} + + glsl-token-inject-block@1.1.0: + resolution: {integrity: sha512-q/m+ukdUBuHCOtLhSr0uFb/qYQr4/oKrPSdIK2C4TD+qLaJvqM9wfXIF/OOBjuSA3pUoYHurVRNao6LTVVUPWA==} + + glsl-token-properties@1.0.1: + resolution: {integrity: sha512-dSeW1cOIzbuUoYH0y+nxzwK9S9O3wsjttkq5ij9ZGw0OS41BirKJzzH48VLm8qLg+au6b0sINxGC0IrGwtQUcA==} + + glsl-token-scope@1.1.2: + resolution: {integrity: sha512-YKyOMk1B/tz9BwYUdfDoHvMIYTGtVv2vbDSLh94PT4+f87z21FVdou1KNKgF+nECBTo0fJ20dpm0B1vZB1Q03A==} + + glsl-token-string@1.0.1: + resolution: {integrity: sha512-1mtQ47Uxd47wrovl+T6RshKGkRRCYWhnELmkEcUAPALWGTFe2XZpH3r45XAwL2B6v+l0KNsCnoaZCSnhzKEksg==} + + glsl-token-whitespace-trim@1.0.0: + resolution: {integrity: sha512-ZJtsPut/aDaUdLUNtmBYhaCmhIjpKNg7IgZSfX5wFReMc2vnj8zok+gB/3Quqs0TsBSX/fGnqUUYZDqyuc2xLQ==} + + glsl-tokenizer@2.1.5: + resolution: {integrity: sha512-XSZEJ/i4dmz3Pmbnpsy3cKh7cotvFlBiZnDOwnj/05EwNp2XrhQ4XKJxT7/pDt4kp4YcpRSKz8eTV7S+mwV6MA==} + + glslify-bundle@5.1.1: + resolution: {integrity: sha512-plaAOQPv62M1r3OsWf2UbjN0hUYAB7Aph5bfH58VxJZJhloRNbxOL9tl/7H71K7OLJoSJ2ZqWOKk3ttQ6wy24A==} + + glslify-deps@1.3.2: + resolution: {integrity: sha512-7S7IkHWygJRjcawveXQjRXLO2FTjijPDYC7QfZyAQanY+yGLCFHYnPtsGT9bdyHiwPTw/5a1m1M9hamT2aBpag==} + + glslify@7.1.1: + resolution: {integrity: sha512-bud98CJ6kGZcP9Yxcsi7Iz647wuDz3oN+IZsjCRi5X1PI7t/xPKeL0mOwXJjo+CRZMqvq0CkSJiywCcY7kVYog==} + hasBin: true + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -11215,6 +11656,9 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} + grid-index@1.1.0: + resolution: {integrity: sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==} + gzip-size@7.0.0: resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -11240,6 +11684,12 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-hover@1.0.1: + resolution: {integrity: sha512-0G6w7LnlcpyDzpeGUTuT0CEw05+QlMuGVk1IHNAlHrGJITGodjZu3x8BNDUMfKJSZXNB2ZAclqc1bvrd+uUpfg==} + + has-passive-events@1.0.0: + resolution: {integrity: sha512-2vSj6IeIsgvsRMyeQ0JaCX5Q3lX4zMn5HpoVc7MEhQ6pv8Iq9rsXjsp+E5ZwaT7T0xhMT0KmU8gtt1EFVdbJiw==} + has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -11514,6 +11964,10 @@ packages: resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ini@4.1.3: + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + inline-style-parser@0.2.4: resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} @@ -11594,6 +12048,9 @@ packages: resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} + is-browser@2.1.0: + resolution: {integrity: sha512-F5rTJxDQ2sW81fcfOR1GnCXT6sVJC104fCyfj+mjpwNEwaPYSn5fte5jiHmBg3DHsIoL/l8Kvw5VN5SsTRcRFQ==} + is-bun-module@2.0.0: resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} @@ -11641,6 +12098,14 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} + is-finite@1.1.0: + resolution: {integrity: sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==} + engines: {node: '>=0.10.0'} + + is-firefox@1.0.3: + resolution: {integrity: sha512-6Q9ITjvWIm0Xdqv+5U12wgOKEM2KoBw4Y926m0OFkvlCxnbG94HKAsVz8w3fWcfAS5YA2fJORXX1dLrkprCCxA==} + engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -11676,6 +12141,9 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} + is-mobile@4.0.0: + resolution: {integrity: sha512-mlcHZA84t1qLSuWkt2v0I2l61PYdyQDt4aG1mLIXF5FDMm4+haBCxCPYSr/uwqQNRk1MiTizn0ypEuRAOLRAew==} + is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} @@ -11695,10 +12163,18 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-obj@1.0.1: + resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} + engines: {node: '>=0.10.0'} + is-path-inside@4.0.0: resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} engines: {node: '>=12'} + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -11739,10 +12215,16 @@ packages: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} + is-string-blank@1.0.1: + resolution: {integrity: sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw==} + is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} + is-svg-path@1.0.2: + resolution: {integrity: sha512-Lj4vePmqpPR1ZnRctHv8ltSh1OrSxHkhUkd7wi+VQdcdP15/KvQFyk7LhNuM7ZW0EVbJz8kZLVmL9quLrfq4Kg==} + is-symbol@1.1.1: resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} @@ -11779,6 +12261,9 @@ packages: resolution: {integrity: sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==} engines: {node: '>=18'} + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -11788,6 +12273,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.5: + resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} + engines: {node: '>=18'} + isexe@4.0.0: resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} engines: {node: '>=20'} @@ -11940,6 +12429,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==} + json-with-bigint@3.5.8: resolution: {integrity: sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==} @@ -11967,6 +12459,12 @@ packages: resolution: {integrity: sha512-EkxoDTk8ufHqHlf9QxGwcxeLkWRR3iOuYfRpfORgYfqc8s13bgb+YtRY59NK5ZpRaCwq1kqA6a5lpX8C/eLphQ==} hasBin: true + kdbush@3.0.0: + resolution: {integrity: sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==} + + kdbush@4.0.2: + resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -12346,9 +12844,20 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + map-limit@0.0.1: + resolution: {integrity: sha512-pJpcfLPnIF/Sk3taPW21G/RQsEEirGaFpCW3oXRwH9dnFHPHNGjNyvh++rdmC2fNqEaTw2MhYJraoJWAHx8kEg==} + map-or-similar@1.5.0: resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} + mapbox-gl@1.13.3: + resolution: {integrity: sha512-p8lJFEiqmEQlyv+DQxFAOG/XPWN0Wp7j/Psq93Zywz7qt9CcUKFYDBOoOEKzqe6gudHVJY8/Bhqw6VDpX2lSBg==} + engines: {node: '>=6.4.0'} + + maplibre-gl@4.7.1: + resolution: {integrity: sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==} + engines: {node: '>=16.14.0', npm: '>=8.1.0'} + markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} @@ -12389,6 +12898,10 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + math-log2@1.0.1: + resolution: {integrity: sha512-9W0yGtkaMAkf74XGYVy4Dqw3YUMnTNB2eeiw9aQbUl4A3KmuCEHTt2DgAB07ENzOYAjsYSAYufkAq0Zd+jU7zA==} + engines: {node: '>=0.10.0'} + md-to-react-email@5.0.5: resolution: {integrity: sha512-OvAXqwq57uOk+WZqFFNCMZz8yDp8BD3WazW1wAKHUrPbbdr89K9DWS6JXY09vd9xNdPNeurI8DU/X4flcfaD8A==} peerDependencies: @@ -12825,6 +13338,9 @@ packages: react-dom: optional: true + mouse-event-offset@3.0.2: + resolution: {integrity: sha512-s9sqOs5B1Ykox3Xo8b3Ss2IQju4UwlW6LSR+Q5FXWpprJ5fzMLefIIItr3PH8RwzfGy6gxs/4GAmiNuZScE25w==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -12842,6 +13358,9 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + murmurhash-js@1.0.0: + resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==} + mustache@4.2.0: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true @@ -12866,9 +13385,17 @@ packages: engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} hasBin: true + native-promise-only@0.8.1: + resolution: {integrity: sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + needle@2.9.1: + resolution: {integrity: sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==} + engines: {node: '>= 4.4.x'} + hasBin: true + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -12893,6 +13420,9 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + next@15.5.12: resolution: {integrity: sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -13028,6 +13558,12 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + normalize-svg-path@0.1.0: + resolution: {integrity: sha512-1/kmYej2iedi5+ROxkRESL/pI02pkg0OBnaR4hJkSIX6+ORzepwbuUXfrdZaPjysTsJInj0Rj5NuX027+dMBvA==} + + normalize-svg-path@1.1.0: + resolution: {integrity: sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==} + npm-package-arg@11.0.3: resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} engines: {node: ^16.14.0 || >=18.0.0} @@ -13050,6 +13586,10 @@ packages: nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + number-is-integer@1.0.1: + resolution: {integrity: sha512-Dq3iuiFBkrbmuQjGFFF3zckXNCQoSD37/SdSbgcBailUx6knDvDwb5CympBgcoWHy36sfS12u74MHYkXyHq6bg==} + engines: {node: '>=0.10.0'} + numbro@2.5.0: resolution: {integrity: sha512-xDcctDimhzko/e+y+Q2/8i3qNC9Svw1QgOkSkQoO0kIPI473tR9QRbo2KP88Ty9p8WbPy+3OpTaAIzehtuHq+A==} @@ -13154,6 +13694,9 @@ packages: resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} engines: {node: '>= 0.8'} + once@1.3.3: + resolution: {integrity: sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -13309,6 +13852,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parenthesis@3.1.8: + resolution: {integrity: sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw==} + parse-entities@2.0.0: resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} @@ -13323,6 +13869,15 @@ packages: resolution: {integrity: sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==} engines: {node: '>=10'} + parse-rect@1.2.0: + resolution: {integrity: sha512-4QZ6KYbnE6RTwg9E0HpLchUM9EZt6DnDxajFZZDSV4p/12ZJEvPO702DZpGvRYEPo00yKDys7jASi+/w7aO8LA==} + + parse-svg-path@0.1.2: + resolution: {integrity: sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==} + + parse-unit@1.0.1: + resolution: {integrity: sha512-hrqldJHokR3Qj88EIlV/kAyAi/G5R2+R56TBANxNMy0uPlYcttx0jnMW6Yx5KsKPSbC3KddM/7qQm3+0wEXKxg==} + parse5-htmlparser2-tree-adapter@6.0.1: resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} @@ -13394,12 +13949,22 @@ packages: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} + pbf@3.3.0: + resolution: {integrity: sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==} + hasBin: true + peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} perfect-debounce@2.1.0: resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + + pick-by-alias@1.2.0: + resolution: {integrity: sha512-ESj2+eBxhGrcA1azgHs7lARG5+5iLakc/6nlfbpjcLl00HuuUOIuORhYXN4D1HfvMSKuVtFQjAlnwi1JHEeDIw==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -13455,10 +14020,20 @@ packages: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} + plotly.js-dist-min@3.5.1: + resolution: {integrity: sha512-N2R4RXKFSRTapdMc/+CDIbvcgFk3HNhs3tR6cKK499tNrSo7C3X7p20Wqi7qpf03s+3oH+DC/hDINYlsE8hxxA==} + + plotly.js@3.5.1: + resolution: {integrity: sha512-rUzQ7Q46whi1aWT2JlvKE2dnryNORPrxyy66OGSMXgejK3XgD4ysD9SapMOI1RzzUJyX0KbfVDFcB9/DqOyH9Q==} + engines: {node: '>=18.0.0'} + pngjs@3.4.0: resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} engines: {node: '>=4.0.0'} + point-in-polygon@1.1.0: + resolution: {integrity: sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==} + points-on-curve@0.2.0: resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} @@ -13469,6 +14044,9 @@ packages: resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} engines: {node: '>=10'} + polybooljs@1.2.2: + resolution: {integrity: sha512-ziHW/02J0XuNuUtmidBc6GXE8YohYydp3DWPWXYsd7O721TjcmN+k6ezjdwkDqep+gnWnFY+yqZHvzElra2oCg==} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -13700,6 +14278,12 @@ packages: posthog-js@1.358.1: resolution: {integrity: sha512-teipwLZtfErKDrURiUlLMnmpjgjGlni15JxyJ7oRaSlT3sX4E/mgvNatHIbWnp+7z1zYm3Jz5BYwGqwgyesRnw==} + potpack@1.0.2: + resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==} + + potpack@2.1.0: + resolution: {integrity: sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==} + powershell-utils@0.1.0: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} @@ -13766,6 +14350,9 @@ packages: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} + probe-image-size@7.3.0: + resolution: {integrity: sha512-7CaDeBwiAbh6ohXsvLbAZhO7wzsZAmaevfxe39qvCwRh8LyaZfDlBGGLU1CCTgrTLtCOdwBBhjOrIHaIIimHfQ==} + proc-log@4.2.0: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -13810,6 +14397,9 @@ packages: resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} + protocol-buffers-schema@3.6.1: + resolution: {integrity: sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -13856,6 +14446,12 @@ packages: quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + quickselect@2.0.0: + resolution: {integrity: sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==} + + quickselect@3.0.0: + resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==} + radash@12.1.1: resolution: {integrity: sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA==} engines: {node: '>=14.18.0'} @@ -13876,6 +14472,9 @@ packages: radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + raf@3.4.1: + resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -14009,6 +14608,12 @@ packages: '@types/react': optional: true + react-plotly.js@2.6.0: + resolution: {integrity: sha512-g93xcyhAVCSt9kV1svqG1clAEdL6k3U+jjuSzfTV7owaSU9Go6Ph8bl25J+jKfKvIGAEYpe4qj++WHJuc9IaeA==} + peerDependencies: + plotly.js: '>1.34.0' + react: '>0.13.0' + react-promise-suspense@0.3.4: resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} @@ -14093,6 +14698,9 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + readable-stream@1.0.34: + resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -14210,6 +14818,21 @@ packages: resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} hasBin: true + regl-error2d@2.0.12: + resolution: {integrity: sha512-r7BUprZoPO9AbyqM5qlJesrSRkl+hZnVKWKsVp7YhOl/3RIpi4UDGASGJY0puQ96u5fBYw/OlqV24IGcgJ0McA==} + + regl-line2d@3.1.3: + resolution: {integrity: sha512-fkgzW+tTn4QUQLpFKsUIE0sgWdCmXAM3ctXcCgoGBZTSX5FE2A0M7aynz7nrZT5baaftLrk9te54B+MEq4QcSA==} + + regl-scatter2d@3.4.0: + resolution: {integrity: sha512-DavKQlHsI+iHZuLgOL+yGkg+sPd94CS+7FCBWkcQ6s/TbaNfUsF9eN591fjjSWIoKrGNfb/SEGhsXR5lXjqZ2w==} + + regl-splom@1.0.14: + resolution: {integrity: sha512-OiLqjmPRYbd7kDlHC6/zDf6L8lxgDC65BhC8JirhP4ykrK4x22ZyS+BnY8EUinXKDeMgmpRwCvUmk7BK4Nweuw==} + + regl@2.1.1: + resolution: {integrity: sha512-+IOGrxl3FZ8ZM9ixCWQZzFRiRn7Rzn9bu3iFHwg/yz4tlOUQgbO4PHLgG+1ZT60zcIV8tief6Qrmyl8qcoJP0g==} + rehype-harden@1.1.8: resolution: {integrity: sha512-Qn7vR1xrf6fZCrkm9TDWi/AB4ylrHy+jqsNm1EHOAmbARYA6gsnVJBq/sdBh6kmT4NEZxH5vgIjrscefJAOXcw==} @@ -14279,6 +14902,9 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve-protobuf-schema@2.1.0: + resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} + resolve-workspace-root@2.0.1: resolution: {integrity: sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==} @@ -14286,6 +14912,9 @@ packages: resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} engines: {node: '>=10'} + resolve@0.6.3: + resolution: {integrity: sha512-UHBY3viPlJKf85YijDUcikKX6tmF4SokIDp518ZDVT92JNDcG5uKIthaT/owt3Sar0lwtOafsQuwrg22/v2Dwg==} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -14529,6 +15158,9 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shallow-copy@0.0.1: + resolution: {integrity: sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==} + sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -14659,6 +15291,9 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stack-trace@0.0.9: + resolution: {integrity: sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ==} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -14676,6 +15311,9 @@ packages: standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + static-eval@2.1.1: + resolution: {integrity: sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==} + statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -14707,6 +15345,12 @@ packages: resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} engines: {node: '>= 0.10.0'} + stream-parser@0.3.1: + resolution: {integrity: sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==} + + stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + streamdown@2.5.0: resolution: {integrity: sha512-/tTnURfIOxZK/pqJAxsfCvETG/XCJHoWnk3jq9xLcuz6CSpnjjuxSRBTTL4PKGhxiZQf0lqPxGhImdpwcZ2XwA==} peerDependencies: @@ -14716,6 +15360,9 @@ packages: streamx@2.25.0: resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==} + string-split-by@1.0.0: + resolution: {integrity: sha512-KaJKY+hfpzNyet/emP81PJA9hTVSfxNLS9SFTWxdCnnW1/zOOwiV248+EfoX7IQFcBaOp4G5YE6xTJMF+pLg6A==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -14751,6 +15398,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -14807,6 +15457,9 @@ packages: strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + strongly-connected-components@1.0.1: + resolution: {integrity: sha512-i0TFx4wPcO0FwX+4RkLJi1MxmcTv90jNZgxMu9XRnMXMeFUY1VJlIoXpZunPUvUUqbCT1pg5PEkFqqpcaElNaA==} + structured-clone-es@2.0.0: resolution: {integrity: sha512-5UuAHmBLXYPCl22xWJrFuGmIhBKQzxISPVz6E7nmTmTcAOpUzlbjKJsRrCE4vADmMQ0dzeCnlWn9XufnAGf76Q==} @@ -14854,6 +15507,12 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + supercluster@7.1.5: + resolution: {integrity: sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==} + + supercluster@8.0.1: + resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} + supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} @@ -14900,6 +15559,15 @@ packages: resolution: {integrity: sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw==} engines: {node: '>=18'} + svg-arc-to-cubic-bezier@3.2.0: + resolution: {integrity: sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==} + + svg-path-bounds@1.0.2: + resolution: {integrity: sha512-H4/uAgLWrppIC0kHsb2/dWUYSmb4GE5UqH06uqWBcg6LBjX2fu0A8+JrO2/FJPZiSsNOKZAhyFFgsLTdYUvSqQ==} + + svg-path-sdf@1.1.3: + resolution: {integrity: sha512-vJJjVq/R5lSr2KLfVXVAStktfcfa1pNFjFOgyJnzZFXlO/fDZ5DmM8FpnSKKzLPfEYTVeXuVBTHF296TpxuJVg==} + svgo@4.0.1: resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==} engines: {node: '>=16'} @@ -15020,6 +15688,12 @@ packages: resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} engines: {node: '>=18'} + through2@0.6.5: + resolution: {integrity: sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==} + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + tiny-emitter@2.1.0: resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} @@ -15033,6 +15707,9 @@ packages: resolution: {integrity: sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA==} engines: {node: ^16.14.0 || >= 17.3.0} + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -15052,6 +15729,12 @@ packages: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} + tinyqueue@2.0.3: + resolution: {integrity: sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==} + + tinyqueue@3.0.0: + resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} + tinyrainbow@1.2.0: resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} @@ -15082,6 +15765,12 @@ packages: tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + to-float32@1.1.0: + resolution: {integrity: sha512-keDnAusn/vc+R3iEiSDw8TOF7gPiTLdK1ArvWtYbJQiVfmRg6i/CAvbKq3uIS0vWroAC7ZecN3DjQKw3aSklUg==} + + to-px@1.1.0: + resolution: {integrity: sha512-bfg3GLYrGoEzrGoE05TAL/Uw+H/qrf2ptr9V3W7U0lkjjyYnIfgxmVLUfhQ1hZpIQwin81uxhDjvUkDYsC0xWw==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -15097,6 +15786,10 @@ packages: tokenx@1.3.0: resolution: {integrity: sha512-NLdXTEZkKiO0gZuLtMoZKjCXTREXeZZt8nnnNeyoXtNZAfG/GKGSbQtLU5STspc0rMSwcA+UJfWZkbNU01iKmQ==} + topojson-client@3.1.0: + resolution: {integrity: sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==} + hasBin: true + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -15226,6 +15919,9 @@ packages: type-level-regexp@0.1.17: resolution: {integrity: sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==} + type@2.7.3: + resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -15242,6 +15938,12 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typedarray-pool@1.2.0: + resolution: {integrity: sha512-YTSQbzX43yvtpfRtIDAYygoYtgT+Rpjuxy9iOpczrjpXLgGoyG7aS5USJXV2d3nn8uHTeb9rXDvzS27zUg5KYQ==} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + typescript-eslint@8.56.1: resolution: {integrity: sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -15403,6 +16105,9 @@ packages: resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} engines: {node: ^20.19.0 || >=22.12.0} + unquote@1.1.1: + resolution: {integrity: sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} @@ -15504,6 +16209,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-diff@1.1.0: + resolution: {integrity: sha512-rCiBPiHxZwT4+sBhEbChzpO5hYHjm91kScWgdHf4Qeafs6Ba7MBl+d9GlGv72bcTZQO0sLmtQS1pHSWoCLtN/A==} + uqr@0.1.2: resolution: {integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==} @@ -15555,6 +16263,7 @@ packages: uuid@10.0.0: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@11.1.0: @@ -15876,6 +16585,9 @@ packages: vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + vt-pbf@3.1.3: + resolution: {integrity: sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==} + vue-bundle-renderer@2.2.0: resolution: {integrity: sha512-sz/0WEdYH1KfaOm0XaBmRZOWgYTEvUDt6yPYaUzl4E52qzgWLlknaPPTTZmp6benaPTlQAI/hN1x3tAzZygycg==} @@ -15921,6 +16633,9 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + weak-map@1.0.8: + resolution: {integrity: sha512-lNR9aAefbGPpHO7AEnY0hCFjz1eTkWCXYvkTRrTHs9qv8zJp+SkVYpzfLIFXQQiG3tVvbNFQgVg2bQS8YGgxyw==} + web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} @@ -15931,6 +16646,9 @@ packages: web-vitals@5.1.0: resolution: {integrity: sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==} + webgl-context@2.2.0: + resolution: {integrity: sha512-q/fGIivtqTT7PEoF07axFIlHNk/XCPaYpq64btnepopSWvKNFkoORlQYgqDigBIuGA1ExnFd/GnSUnBNEPQY7Q==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -16003,6 +16721,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + which@6.0.1: resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -16020,6 +16743,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + world-calendars@1.0.4: + resolution: {integrity: sha512-VGRnLJS+xJmGDPodgJRnGIDwGu0s+Cr9V2HB3EzlDZ5n0qb8h5SJtGUEkjrphZYAglEiXZ6kiXdmk0H/h/uu/w==} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -16117,6 +16843,10 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xtend@2.2.0: + resolution: {integrity: sha512-SLt5uylT+4aoXxXuwtQp5ZnMMzhDb1Xkg4pEqc00WUJCQifPfV9Ub1VrNhp9kXkrjZD2I2Hl8WnjP37jzZLPZw==} + engines: {node: '>=0.4'} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -17246,6 +17976,10 @@ snapshots: '@chevrotain/utils@11.1.2': {} + '@choojs/findup@0.2.1': + dependencies: + commander: 2.20.3 + '@chromatic-com/storybook@3.2.6(react@19.2.4)(storybook@8.6.14(prettier@3.5.3))': dependencies: chromatic: 11.29.0 @@ -18846,6 +19580,19 @@ snapshots: dependencies: '@lukeed/csprng': 1.1.0 + '@mapbox/geojson-rewind@0.5.2': + dependencies: + get-stream: 6.0.1 + minimist: 1.2.8 + + '@mapbox/geojson-types@1.0.2': {} + + '@mapbox/jsonlint-lines-primitives@2.0.2': {} + + '@mapbox/mapbox-gl-supported@1.5.0(mapbox-gl@1.13.3)': + dependencies: + mapbox-gl: 1.13.3 + '@mapbox/node-pre-gyp@2.0.3': dependencies: consola: 3.4.2 @@ -18859,6 +19606,32 @@ snapshots: - encoding - supports-color + '@mapbox/point-geometry@0.1.0': {} + + '@mapbox/tiny-sdf@1.2.5': {} + + '@mapbox/tiny-sdf@2.2.0': {} + + '@mapbox/unitbezier@0.0.0': {} + + '@mapbox/unitbezier@0.0.1': {} + + '@mapbox/vector-tile@1.3.1': + dependencies: + '@mapbox/point-geometry': 0.1.0 + + '@mapbox/whoots-js@3.1.0': {} + + '@maplibre/maplibre-gl-style-spec@20.4.0': + dependencies: + '@mapbox/jsonlint-lines-primitives': 2.0.2 + '@mapbox/unitbezier': 0.0.1 + json-stringify-pretty-compact: 4.0.0 + minimist: 1.2.8 + quickselect: 2.0.0 + rw: 1.3.3 + tinyqueue: 3.0.0 + '@mastra/client-js@1.11.2(@cfworker/json-schema@4.1.1)(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@4.3.6))(@types/json-schema@7.0.15)(openapi-types@12.1.3)(zod@4.3.6)': dependencies: '@ai-sdk/ui-utils': 1.2.11(zod@4.3.6) @@ -20058,6 +20831,63 @@ snapshots: '@pkgr/core@0.2.9': {} + '@plotly/d3-sankey-circular@0.33.1': + dependencies: + d3-array: 1.2.4 + d3-collection: 1.0.7 + d3-shape: 1.3.7 + elementary-circuits-directed-graph: 1.3.1 + + '@plotly/d3-sankey@0.7.2': + dependencies: + d3-array: 1.2.4 + d3-collection: 1.0.7 + d3-shape: 1.3.7 + + '@plotly/d3@3.8.2': {} + + '@plotly/mapbox-gl@1.13.4(mapbox-gl@1.13.3)': + dependencies: + '@mapbox/geojson-rewind': 0.5.2 + '@mapbox/geojson-types': 1.0.2 + '@mapbox/jsonlint-lines-primitives': 2.0.2 + '@mapbox/mapbox-gl-supported': 1.5.0(mapbox-gl@1.13.3) + '@mapbox/point-geometry': 0.1.0 + '@mapbox/tiny-sdf': 1.2.5 + '@mapbox/unitbezier': 0.0.0 + '@mapbox/vector-tile': 1.3.1 + '@mapbox/whoots-js': 3.1.0 + csscolorparser: 1.0.3 + earcut: 2.2.4 + geojson-vt: 3.2.1 + gl-matrix: 3.4.4 + grid-index: 1.1.0 + murmurhash-js: 1.0.0 + pbf: 3.3.0 + potpack: 1.0.2 + quickselect: 2.0.0 + rw: 1.3.3 + supercluster: 7.1.5 + tinyqueue: 2.0.3 + vt-pbf: 3.1.3 + transitivePeerDependencies: + - mapbox-gl + + '@plotly/point-cluster@3.1.9': + dependencies: + array-bounds: 1.0.1 + binary-search-bounds: 2.0.5 + clamp: 1.0.1 + defined: 1.0.1 + dtype: 2.0.0 + flatten-vertex-data: 1.0.2 + is-obj: 1.0.1 + math-log2: 1.0.1 + parse-rect: 1.2.0 + pick-by-alias: 1.2.0 + + '@plotly/regl@2.1.2': {} + '@polka/url@1.0.0-next.29': {} '@poppinss/colors@4.1.6': @@ -24350,6 +25180,38 @@ snapshots: dependencies: '@testing-library/dom': 10.4.0 + '@turf/area@7.3.5': + dependencies: + '@turf/helpers': 7.3.5 + '@turf/meta': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 + + '@turf/bbox@7.3.5': + dependencies: + '@turf/helpers': 7.3.5 + '@turf/meta': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 + + '@turf/centroid@7.3.5': + dependencies: + '@turf/helpers': 7.3.5 + '@turf/meta': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 + + '@turf/helpers@7.3.5': + dependencies: + '@types/geojson': 7946.0.16 + tslib: 2.8.1 + + '@turf/meta@7.3.5': + dependencies: + '@turf/helpers': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -24557,6 +25419,10 @@ snapshots: '@types/qs': 6.15.0 '@types/serve-static': 1.15.10 + '@types/geojson-vt@3.2.5': + dependencies: + '@types/geojson': 7946.0.16 + '@types/geojson@7946.0.16': {} '@types/graceful-fs@4.1.9': @@ -24597,6 +25463,14 @@ snapshots: '@types/lodash@4.17.18': {} + '@types/mapbox__point-geometry@0.1.4': {} + + '@types/mapbox__vector-tile@1.3.4': + dependencies: + '@types/geojson': 7946.0.16 + '@types/mapbox__point-geometry': 0.1.4 + '@types/pbf': 3.0.5 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -24628,6 +25502,14 @@ snapshots: dependencies: undici-types: 7.18.2 + '@types/pbf@3.0.5': {} + + '@types/plotly.js-dist-min@2.3.4': + dependencies: + '@types/plotly.js': 3.0.10 + + '@types/plotly.js@3.0.10': {} + '@types/prismjs@1.26.6': {} '@types/qs@6.15.0': {} @@ -24638,6 +25520,11 @@ snapshots: dependencies: '@types/react': 19.2.14 + '@types/react-plotly.js@2.6.4': + dependencies: + '@types/plotly.js': 3.0.10 + '@types/react': 19.2.14 + '@types/react-syntax-highlighter@15.5.13': dependencies: '@types/react': 19.2.14 @@ -24671,6 +25558,10 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/supercluster@7.1.3': + dependencies: + '@types/geojson': 7946.0.16 + '@types/trusted-types@2.0.7': {} '@types/unist@2.0.11': {} @@ -25309,6 +26200,8 @@ snapshots: dependencies: event-target-shim: 5.0.1 + abs-svg-path@0.1.1: {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -25327,6 +26220,8 @@ snapshots: dependencies: acorn: 8.16.0 + acorn@7.4.1: {} + acorn@8.16.0: {} agent-base@7.1.4: {} @@ -25479,11 +26374,15 @@ snapshots: aria-query@5.3.2: {} + array-bounds@1.0.1: {} + array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 is-array-buffer: 3.0.5 + array-find-index@1.0.2: {} + array-flatten@1.1.1: {} array-includes@3.1.9: @@ -25497,6 +26396,12 @@ snapshots: is-string: 1.1.1 math-intrinsics: 1.1.0 + array-normalize@1.1.4: + dependencies: + array-bounds: 1.0.1 + + array-range@1.0.1: {} + array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.8 @@ -25776,6 +26681,8 @@ snapshots: dependencies: bare-path: 3.0.0 + base64-arraybuffer@1.0.2: {} + base64-js@1.5.1: {} baseline-browser-mapping@2.10.0: {} @@ -25792,6 +26699,8 @@ snapshots: binary-extensions@2.3.0: {} + binary-search-bounds@2.0.5: {} + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -25800,6 +26709,15 @@ snapshots: birpc@4.0.0: {} + bit-twiddle@1.0.2: {} + + bitmap-sdf@1.0.4: {} + + bl@2.2.1: + dependencies: + readable-stream: 2.3.8 + safe-buffer: 5.2.1 + body-parser@1.20.4: dependencies: bytes: 3.1.2 @@ -26092,6 +27010,8 @@ snapshots: cjs-module-lexer@1.4.3: {} + clamp@1.0.1: {} + class-transformer@0.5.1: {} class-validator@0.14.4: @@ -26161,6 +27081,10 @@ snapshots: collapse-white-space@2.1.0: {} + color-alpha@1.1.3: + dependencies: + color-parse: 1.4.3 + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -26169,10 +27093,42 @@ snapshots: dependencies: color-name: 1.1.4 + color-id@1.1.0: + dependencies: + clamp: 1.0.1 + color-name@1.1.3: {} color-name@1.1.4: {} + color-name@2.1.0: {} + + color-normalize@1.5.0: + dependencies: + clamp: 1.0.1 + color-rgba: 2.4.0 + dtype: 2.0.0 + + color-parse@1.4.3: + dependencies: + color-name: 1.1.4 + + color-parse@2.0.2: + dependencies: + color-name: 2.1.0 + + color-rgba@2.4.0: + dependencies: + color-parse: 1.4.3 + color-space: 2.3.2 + + color-rgba@3.0.0: + dependencies: + color-parse: 2.0.2 + color-space: 2.3.2 + + color-space@2.3.2: {} + colord@2.9.3: {} colorette@2.0.20: {} @@ -26235,6 +27191,13 @@ snapshots: concat-map@0.0.1: {} + concat-stream@1.6.2: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.8 + typedarray: 0.0.6 + concurrently@9.2.0: dependencies: chalk: 4.1.2 @@ -26314,6 +27277,8 @@ snapshots: dependencies: layout-base: 2.0.1 + country-regex@1.1.0: {} + crc-32@1.2.2: {} crc32-stream@6.0.0: @@ -26347,6 +27312,28 @@ snapshots: dependencies: postcss: 8.5.8 + css-font-size-keywords@1.0.0: {} + + css-font-stretch-keywords@1.0.1: {} + + css-font-style-keywords@1.0.1: {} + + css-font-weight-keywords@1.0.0: {} + + css-font@1.2.0: + dependencies: + css-font-size-keywords: 1.0.0 + css-font-stretch-keywords: 1.0.1 + css-font-style-keywords: 1.0.1 + css-font-weight-keywords: 1.0.0 + css-global-keywords: 1.0.1 + css-system-font-keywords: 1.0.0 + pick-by-alias: 1.2.0 + string-split-by: 1.0.0 + unquote: 1.1.1 + + css-global-keywords@1.0.1: {} + css-in-js-utils@3.1.0: dependencies: hyphenate-style-name: 1.1.0 @@ -26359,6 +27346,8 @@ snapshots: domutils: 3.2.2 nth-check: 2.1.1 + css-system-font-keywords@1.0.0: {} + css-tree@1.1.3: dependencies: mdn-data: 2.0.14 @@ -26378,6 +27367,8 @@ snapshots: css.escape@1.5.1: {} + csscolorparser@1.0.3: {} + cssesc@3.0.0: {} cssnano-preset-default@7.0.11(postcss@8.5.8): @@ -26447,6 +27438,8 @@ snapshots: cytoscape@3.33.1: {} + d3-array@1.2.4: {} + d3-array@2.12.1: dependencies: internmap: 1.0.1 @@ -26469,6 +27462,8 @@ snapshots: dependencies: d3-path: 3.1.0 + d3-collection@1.0.7: {} + d3-color@3.1.0: {} d3-contour@4.0.2: @@ -26479,6 +27474,8 @@ snapshots: dependencies: delaunator: 5.1.0 + d3-dispatch@1.0.6: {} + d3-dispatch@3.0.1: {} d3-drag@3.0.0: @@ -26498,18 +27495,40 @@ snapshots: dependencies: d3-dsv: 3.0.1 + d3-force@1.2.1: + dependencies: + d3-collection: 1.0.7 + d3-dispatch: 1.0.6 + d3-quadtree: 1.0.7 + d3-timer: 1.0.10 + d3-force@3.0.0: dependencies: d3-dispatch: 3.0.1 d3-quadtree: 3.0.1 d3-timer: 3.0.1 + d3-format@1.4.5: {} + d3-format@3.1.0: {} + d3-geo-projection@2.9.0: + dependencies: + commander: 2.20.3 + d3-array: 1.2.4 + d3-geo: 1.12.1 + resolve: 1.22.11 + + d3-geo@1.12.1: + dependencies: + d3-array: 1.2.4 + d3-geo@3.1.1: dependencies: d3-array: 3.2.4 + d3-hierarchy@1.1.9: {} + d3-hierarchy@3.1.2: {} d3-interpolate@3.0.1: @@ -26522,6 +27541,8 @@ snapshots: d3-polygon@3.0.1: {} + d3-quadtree@1.0.7: {} + d3-quadtree@3.0.1: {} d3-random@3.0.1: {} @@ -26554,14 +27575,22 @@ snapshots: dependencies: d3-path: 3.1.0 + d3-time-format@2.3.0: + dependencies: + d3-time: 1.1.0 + d3-time-format@4.1.0: dependencies: d3-time: 3.1.0 + d3-time@1.1.0: {} + d3-time@3.1.0: dependencies: d3-array: 3.2.4 + d3-timer@1.0.10: {} + d3-timer@3.0.1: {} d3-transition@3.0.1(d3-selection@3.0.0): @@ -26614,6 +27643,11 @@ snapshots: d3-transition: 3.0.1(d3-selection@3.0.0) d3-zoom: 3.0.0 + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + dagre-d3-es@7.0.14: dependencies: d3: 7.9.0 @@ -26715,6 +27749,8 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + defined@1.0.1: {} + defu@6.1.4: {} delaunator@5.1.0: @@ -26733,6 +27769,8 @@ snapshots: destroy@1.2.0: {} + detect-kerning@2.1.2: {} + detect-libc@1.0.3: {} detect-libc@2.1.2: {} @@ -26802,18 +27840,38 @@ snapshots: dotenv@17.3.1: {} + draw-svg-path@1.0.0: + dependencies: + abs-svg-path: 0.1.1 + normalize-svg-path: 0.1.0 + dset@3.1.4: {} dts-resolver@2.1.3: {} + dtype@2.0.0: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 + dup@1.0.0: {} + duplexer@0.1.2: {} + duplexify@3.7.1: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 2.3.8 + stream-shift: 1.0.3 + + earcut@2.2.4: {} + + earcut@3.0.2: {} + eastasianwidth@0.2.0: {} editorconfig@1.0.7: @@ -26829,6 +27887,10 @@ snapshots: electron-to-chromium@1.5.313: {} + elementary-circuits-directed-graph@1.3.1: + dependencies: + strongly-connected-components: 1.0.1 + embla-carousel-react@8.6.0(react@19.2.3): dependencies: embla-carousel: 8.6.0 @@ -26989,6 +28051,31 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + es5-ext@0.10.64: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + + es6-iterator@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + + es6-symbol@3.1.4: + dependencies: + d: 1.0.2 + ext: 1.7.0 + + es6-weak-map@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esast-util-from-estree@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -27163,6 +28250,14 @@ snapshots: escape-string-regexp@5.0.0: {} + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + eslint-config-next@16.1.6(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 16.1.6 @@ -27230,7 +28325,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: @@ -27252,7 +28347,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.29.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -27411,6 +28506,13 @@ snapshots: esm-env@1.2.2: {} + esniff@2.0.1: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.3 + espree@10.4.0: dependencies: acorn: 8.16.0 @@ -27479,6 +28581,11 @@ snapshots: etag@1.8.1: {} + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-target-shim@5.0.1: {} eventemitter3@4.0.7: {} @@ -27695,6 +28802,10 @@ snapshots: exsolve@1.0.8: {} + ext@1.7.0: + dependencies: + type: 2.7.3 + extend-shallow@2.0.1: dependencies: is-extendable: 0.1.1 @@ -27708,6 +28819,11 @@ snapshots: pathe: 1.1.2 ufo: 1.6.3 + falafel@2.2.5: + dependencies: + acorn: 7.4.1 + isarray: 2.0.5 + fast-content-type-parse@3.0.0: {} fast-copy@3.0.2: {} @@ -27738,6 +28854,10 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-isnumeric@1.1.4: + dependencies: + is-string-blank: 1.0.1 + fast-json-patch@3.1.1: {} fast-json-stable-stringify@2.1.0: {} @@ -27868,8 +28988,20 @@ snapshots: flatted@3.3.3: {} + flatten-vertex-data@1.0.2: + dependencies: + dtype: 2.0.0 + flow-enums-runtime@0.0.6: {} + font-atlas@2.1.0: + dependencies: + css-font: 1.2.0 + + font-measure@1.2.2: + dependencies: + css-font: 1.2.0 + fontfaceobserver@2.3.0: {} for-each@0.3.5: @@ -27917,6 +29049,11 @@ snapshots: fresh@2.0.0: {} + from2@2.3.0: + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.8 + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -28045,8 +29182,14 @@ snapshots: gensync@1.0.0-beta.2: {} + geojson-vt@3.2.1: {} + + geojson-vt@4.0.2: {} + get-caller-file@2.0.5: {} + get-canvas-context@1.0.2: {} + get-east-asian-width@1.5.0: {} get-intrinsic@1.3.0: @@ -28073,6 +29216,8 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@6.0.1: {} + get-stream@8.0.1: {} get-stream@9.0.1: @@ -28109,6 +29254,40 @@ snapshots: github-slugger@2.0.0: {} + gl-mat4@1.2.0: {} + + gl-matrix@3.4.4: {} + + gl-text@1.4.0: + dependencies: + bit-twiddle: 1.0.2 + color-normalize: 1.5.0 + css-font: 1.2.0 + detect-kerning: 2.1.2 + es6-weak-map: 2.0.3 + flatten-vertex-data: 1.0.2 + font-atlas: 2.1.0 + font-measure: 1.2.2 + gl-util: 3.1.3 + is-plain-obj: 1.1.0 + object-assign: 4.1.1 + parse-rect: 1.2.0 + parse-unit: 1.0.1 + pick-by-alias: 1.2.0 + regl: 2.1.1 + to-px: 1.1.0 + typedarray-pool: 1.2.0 + + gl-util@3.1.3: + dependencies: + is-browser: 2.1.0 + is-firefox: 1.0.3 + is-plain-obj: 1.1.0 + number-is-integer: 1.0.1 + object-assign: 4.1.1 + pick-by-alias: 1.2.0 + weak-map: 1.0.8 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -28147,6 +29326,12 @@ snapshots: dependencies: ini: 4.1.1 + global-prefix@4.0.0: + dependencies: + ini: 4.1.3 + kind-of: 6.0.3 + which: 4.0.0 + globals@11.12.0: {} globals@14.0.0: {} @@ -28167,6 +29352,88 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.4.0 + glsl-inject-defines@1.0.3: + dependencies: + glsl-token-inject-block: 1.1.0 + glsl-token-string: 1.0.1 + glsl-tokenizer: 2.1.5 + + glsl-resolve@0.0.1: + dependencies: + resolve: 0.6.3 + xtend: 2.2.0 + + glsl-token-assignments@2.0.2: {} + + glsl-token-defines@1.0.0: + dependencies: + glsl-tokenizer: 2.1.5 + + glsl-token-depth@1.1.2: {} + + glsl-token-descope@1.0.2: + dependencies: + glsl-token-assignments: 2.0.2 + glsl-token-depth: 1.1.2 + glsl-token-properties: 1.0.1 + glsl-token-scope: 1.1.2 + + glsl-token-inject-block@1.1.0: {} + + glsl-token-properties@1.0.1: {} + + glsl-token-scope@1.1.2: {} + + glsl-token-string@1.0.1: {} + + glsl-token-whitespace-trim@1.0.0: {} + + glsl-tokenizer@2.1.5: + dependencies: + through2: 0.6.5 + + glslify-bundle@5.1.1: + dependencies: + glsl-inject-defines: 1.0.3 + glsl-token-defines: 1.0.0 + glsl-token-depth: 1.1.2 + glsl-token-descope: 1.0.2 + glsl-token-scope: 1.1.2 + glsl-token-string: 1.0.1 + glsl-token-whitespace-trim: 1.0.0 + glsl-tokenizer: 2.1.5 + murmurhash-js: 1.0.0 + shallow-copy: 0.0.1 + + glslify-deps@1.3.2: + dependencies: + '@choojs/findup': 0.2.1 + events: 3.3.0 + glsl-resolve: 0.0.1 + glsl-tokenizer: 2.1.5 + graceful-fs: 4.2.11 + inherits: 2.0.4 + map-limit: 0.0.1 + resolve: 1.22.11 + + glslify@7.1.1: + dependencies: + bl: 2.2.1 + concat-stream: 1.6.2 + duplexify: 3.7.1 + falafel: 2.2.5 + from2: 2.3.0 + glsl-resolve: 0.0.1 + glsl-token-whitespace-trim: 1.0.0 + glslify-bundle: 5.1.1 + glslify-deps: 1.3.2 + minimist: 1.2.8 + resolve: 1.22.11 + stack-trace: 0.0.9 + static-eval: 2.1.1 + through2: 2.0.5 + xtend: 4.0.2 + gopd@1.2.0: {} gpt-tokenizer@3.4.0: {} @@ -28208,6 +29475,8 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 + grid-index@1.1.0: {} + gzip-size@7.0.0: dependencies: duplexer: 0.1.2 @@ -28241,6 +29510,14 @@ snapshots: has-flag@4.0.0: {} + has-hover@1.0.1: + dependencies: + is-browser: 2.1.0 + + has-passive-events@1.0.0: + dependencies: + is-browser: 2.1.0 + has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 @@ -28599,6 +29876,8 @@ snapshots: ini@4.1.1: {} + ini@4.1.3: {} + inline-style-parser@0.2.4: {} inline-style-prefixer@7.0.1: @@ -28697,6 +29976,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-browser@2.1.0: {} + is-bun-module@2.0.0: dependencies: semver: 7.7.4 @@ -28734,6 +30015,10 @@ snapshots: dependencies: call-bound: 1.0.4 + is-finite@1.1.0: {} + + is-firefox@1.0.3: {} + is-fullwidth-code-point@3.0.0: {} is-generator-function@1.1.0: @@ -28764,6 +30049,8 @@ snapshots: is-map@2.0.3: {} + is-mobile@4.0.0: {} + is-module@1.0.0: {} is-negative-zero@2.0.3: {} @@ -28777,8 +30064,12 @@ snapshots: is-number@7.0.0: {} + is-obj@1.0.1: {} + is-path-inside@4.0.0: {} + is-plain-obj@1.1.0: {} + is-plain-obj@4.1.0: {} is-potential-custom-element-name@1.0.1: {} @@ -28812,11 +30103,15 @@ snapshots: is-stream@4.0.1: {} + is-string-blank@1.0.1: {} + is-string@1.1.1: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-svg-path@1.0.2: {} + is-symbol@1.1.1: dependencies: call-bound: 1.0.4 @@ -28852,12 +30147,16 @@ snapshots: dependencies: system-architecture: 0.1.0 + isarray@0.0.1: {} + isarray@1.0.0: {} isarray@2.0.5: {} isexe@2.0.0: {} + isexe@3.1.5: {} + isexe@4.0.0: {} istanbul-lib-coverage@3.2.2: {} @@ -29053,6 +30352,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-pretty-compact@4.0.0: {} + json-with-bigint@3.5.8: {} json5@1.0.2: @@ -29082,6 +30383,10 @@ snapshots: dependencies: commander: 8.3.0 + kdbush@3.0.0: {} + + kdbush@4.0.2: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -29408,8 +30713,66 @@ snapshots: dependencies: tmpl: 1.0.5 + map-limit@0.0.1: + dependencies: + once: 1.3.3 + map-or-similar@1.5.0: {} + mapbox-gl@1.13.3: + dependencies: + '@mapbox/geojson-rewind': 0.5.2 + '@mapbox/geojson-types': 1.0.2 + '@mapbox/jsonlint-lines-primitives': 2.0.2 + '@mapbox/mapbox-gl-supported': 1.5.0(mapbox-gl@1.13.3) + '@mapbox/point-geometry': 0.1.0 + '@mapbox/tiny-sdf': 1.2.5 + '@mapbox/unitbezier': 0.0.0 + '@mapbox/vector-tile': 1.3.1 + '@mapbox/whoots-js': 3.1.0 + csscolorparser: 1.0.3 + earcut: 2.2.4 + geojson-vt: 3.2.1 + gl-matrix: 3.4.4 + grid-index: 1.1.0 + murmurhash-js: 1.0.0 + pbf: 3.3.0 + potpack: 1.0.2 + quickselect: 2.0.0 + rw: 1.3.3 + supercluster: 7.1.5 + tinyqueue: 2.0.3 + vt-pbf: 3.1.3 + + maplibre-gl@4.7.1: + dependencies: + '@mapbox/geojson-rewind': 0.5.2 + '@mapbox/jsonlint-lines-primitives': 2.0.2 + '@mapbox/point-geometry': 0.1.0 + '@mapbox/tiny-sdf': 2.2.0 + '@mapbox/unitbezier': 0.0.1 + '@mapbox/vector-tile': 1.3.1 + '@mapbox/whoots-js': 3.1.0 + '@maplibre/maplibre-gl-style-spec': 20.4.0 + '@types/geojson': 7946.0.16 + '@types/geojson-vt': 3.2.5 + '@types/mapbox__point-geometry': 0.1.4 + '@types/mapbox__vector-tile': 1.3.4 + '@types/pbf': 3.0.5 + '@types/supercluster': 7.1.3 + earcut: 3.0.2 + geojson-vt: 4.0.2 + gl-matrix: 3.4.4 + global-prefix: 4.0.0 + kdbush: 4.0.2 + murmurhash-js: 1.0.0 + pbf: 3.3.0 + potpack: 2.1.0 + quickselect: 3.0.0 + supercluster: 8.0.1 + tinyqueue: 3.0.0 + vt-pbf: 3.1.3 + markdown-extensions@2.0.0: {} markdown-table@3.0.4: {} @@ -29437,6 +30800,8 @@ snapshots: math-intrinsics@1.1.0: {} + math-log2@1.0.1: {} + md-to-react-email@5.0.5(react@19.2.3): dependencies: marked: 7.0.4 @@ -30382,6 +31747,8 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + mouse-event-offset@3.0.2: {} + mri@1.2.0: {} mrmime@2.0.1: {} @@ -30392,6 +31759,8 @@ snapshots: muggle-string@0.4.1: {} + murmurhash-js@1.0.0: {} + mustache@4.2.0: {} mute-stream@3.0.0: {} @@ -30408,8 +31777,18 @@ snapshots: napi-postinstall@0.3.4: {} + native-promise-only@0.8.1: {} + natural-compare@1.4.0: {} + needle@2.9.1: + dependencies: + debug: 3.2.7 + iconv-lite: 0.4.24 + sax: 1.5.0 + transitivePeerDependencies: + - supports-color + negotiator@0.6.3: {} negotiator@0.6.4: {} @@ -30425,6 +31804,8 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + next-tick@1.1.0: {} + next@15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2): dependencies: '@next/env': 15.5.12 @@ -30681,6 +32062,12 @@ snapshots: normalize-path@3.0.0: {} + normalize-svg-path@0.1.0: {} + + normalize-svg-path@1.1.0: + dependencies: + svg-arc-to-cubic-bezier: 3.2.0 + npm-package-arg@11.0.3: dependencies: hosted-git-info: 7.0.2 @@ -30705,6 +32092,10 @@ snapshots: nullthrows@1.1.1: {} + number-is-integer@1.0.1: + dependencies: + is-finite: 1.1.0 + numbro@2.5.0: dependencies: bignumber.js: 9.3.1 @@ -30936,6 +32327,10 @@ snapshots: on-headers@1.1.0: {} + once@1.3.3: + dependencies: + wrappy: 1.0.2 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -31167,6 +32562,8 @@ snapshots: dependencies: callsites: 3.1.0 + parenthesis@3.1.8: {} + parse-entities@2.0.0: dependencies: character-entities: 1.2.4 @@ -31192,6 +32589,14 @@ snapshots: dependencies: pngjs: 3.4.0 + parse-rect@1.2.0: + dependencies: + pick-by-alias: 1.2.0 + + parse-svg-path@0.1.2: {} + + parse-unit@1.0.1: {} + parse5-htmlparser2-tree-adapter@6.0.1: dependencies: parse5: 6.0.1 @@ -31247,10 +32652,19 @@ snapshots: pathval@2.0.0: {} + pbf@3.3.0: + dependencies: + ieee754: 1.2.1 + resolve-protobuf-schema: 2.1.0 + peberminta@0.9.0: {} perfect-debounce@2.1.0: {} + performance-now@2.1.0: {} + + pick-by-alias@1.2.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -31322,8 +32736,61 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + plotly.js-dist-min@3.5.1: {} + + plotly.js@3.5.1(mapbox-gl@1.13.3): + dependencies: + '@plotly/d3': 3.8.2 + '@plotly/d3-sankey': 0.7.2 + '@plotly/d3-sankey-circular': 0.33.1 + '@plotly/mapbox-gl': 1.13.4(mapbox-gl@1.13.3) + '@plotly/regl': 2.1.2 + '@turf/area': 7.3.5 + '@turf/bbox': 7.3.5 + '@turf/centroid': 7.3.5 + base64-arraybuffer: 1.0.2 + color-normalize: 1.5.0 + color-rgba: 3.0.0 + country-regex: 1.1.0 + d3-force: 1.2.1 + d3-format: 1.4.5 + d3-geo: 1.12.1 + d3-geo-projection: 2.9.0 + d3-hierarchy: 1.1.9 + d3-interpolate: 3.0.1 + d3-time: 1.1.0 + d3-time-format: 2.3.0 + fast-isnumeric: 1.1.4 + gl-mat4: 1.2.0 + gl-text: 1.4.0 + has-hover: 1.0.1 + has-passive-events: 1.0.0 + is-mobile: 4.0.0 + maplibre-gl: 4.7.1 + mouse-event-offset: 3.0.2 + native-promise-only: 0.8.1 + parse-svg-path: 0.1.2 + point-in-polygon: 1.1.0 + polybooljs: 1.2.2 + probe-image-size: 7.3.0 + regl-error2d: 2.0.12 + regl-line2d: 3.1.3 + regl-scatter2d: 3.4.0 + regl-splom: 1.0.14 + strongly-connected-components: 1.0.1 + svg-path-sdf: 1.1.3 + tinycolor2: 1.6.0 + topojson-client: 3.1.0 + webgl-context: 2.2.0 + world-calendars: 1.0.4 + transitivePeerDependencies: + - mapbox-gl + - supports-color + pngjs@3.4.0: {} + point-in-polygon@1.1.0: {} + points-on-curve@0.2.0: {} points-on-path@0.2.1: @@ -31335,6 +32802,8 @@ snapshots: dependencies: '@babel/runtime': 7.27.6 + polybooljs@1.2.2: {} + possible-typed-array-names@1.1.0: {} postcss-calc@10.1.1(postcss@8.5.8): @@ -31566,6 +33035,10 @@ snapshots: query-selector-shadow-dom: 1.0.1 web-vitals: 5.1.0 + potpack@1.0.2: {} + + potpack@2.1.0: {} + powershell-utils@0.1.0: {} preact@10.28.4: {} @@ -31613,6 +33086,14 @@ snapshots: prismjs@1.30.0: {} + probe-image-size@7.3.0: + dependencies: + lodash.merge: 4.6.2 + needle: 2.9.1 + stream-parser: 0.3.1 + transitivePeerDependencies: + - supports-color + proc-log@4.2.0: {} process-nextick-args@2.0.1: {} @@ -31665,6 +33146,8 @@ snapshots: '@types/node': 22.15.32 long: 5.3.2 + protocol-buffers-schema@3.6.1: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -31708,6 +33191,10 @@ snapshots: quick-format-unescaped@4.0.4: {} + quickselect@2.0.0: {} + + quickselect@3.0.0: {} + radash@12.1.1: {} radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): @@ -31775,6 +33262,10 @@ snapshots: radix3@1.1.2: {} + raf@3.4.1: + dependencies: + performance-now: 2.1.0 + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -32091,6 +33582,12 @@ snapshots: - supports-color - utf-8-validate + react-plotly.js@2.6.0(plotly.js@3.5.1(mapbox-gl@1.13.3))(react@19.2.4): + dependencies: + plotly.js: 3.5.1(mapbox-gl@1.13.3) + prop-types: 15.8.1 + react: 19.2.4 + react-promise-suspense@0.3.4: dependencies: fast-deep-equal: 2.0.1 @@ -32267,6 +33764,13 @@ snapshots: dependencies: pify: 2.3.0 + readable-stream@1.0.34: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -32442,6 +33946,56 @@ snapshots: dependencies: jsesc: 3.1.0 + regl-error2d@2.0.12: + dependencies: + array-bounds: 1.0.1 + color-normalize: 1.5.0 + flatten-vertex-data: 1.0.2 + object-assign: 4.1.1 + pick-by-alias: 1.2.0 + to-float32: 1.1.0 + update-diff: 1.1.0 + + regl-line2d@3.1.3: + dependencies: + array-bounds: 1.0.1 + array-find-index: 1.0.2 + array-normalize: 1.1.4 + color-normalize: 1.5.0 + earcut: 2.2.4 + es6-weak-map: 2.0.3 + flatten-vertex-data: 1.0.2 + object-assign: 4.1.1 + parse-rect: 1.2.0 + pick-by-alias: 1.2.0 + to-float32: 1.1.0 + + regl-scatter2d@3.4.0: + dependencies: + '@plotly/point-cluster': 3.1.9 + array-bounds: 1.0.1 + color-id: 1.1.0 + color-normalize: 1.5.0 + flatten-vertex-data: 1.0.2 + glslify: 7.1.1 + parse-rect: 1.2.0 + pick-by-alias: 1.2.0 + to-float32: 1.1.0 + update-diff: 1.1.0 + + regl-splom@1.0.14: + dependencies: + array-bounds: 1.0.1 + array-range: 1.0.1 + color-alpha: 1.1.3 + flatten-vertex-data: 1.0.2 + parse-rect: 1.2.0 + pick-by-alias: 1.2.0 + raf: 3.4.1 + regl-scatter2d: 3.4.0 + + regl@2.1.1: {} + rehype-harden@1.1.8: dependencies: unist-util-visit: 5.1.0 @@ -32566,10 +34120,16 @@ snapshots: resolve-pkg-maps@1.0.0: {} + resolve-protobuf-schema@2.1.0: + dependencies: + protocol-buffers-schema: 3.6.1 + resolve-workspace-root@2.0.1: {} resolve.exports@2.0.3: {} + resolve@0.6.3: {} + resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -32925,6 +34485,8 @@ snapshots: setprototypeof@1.2.0: {} + shallow-copy@0.0.1: {} + sharp@0.34.5: dependencies: '@img/colour': 1.0.0 @@ -33080,6 +34642,8 @@ snapshots: stable-hash@0.0.5: {} + stack-trace@0.0.9: {} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -33094,6 +34658,10 @@ snapshots: standard-as-callback@2.1.0: {} + static-eval@2.1.1: + dependencies: + escodegen: 2.1.0 + statuses@1.5.0: {} statuses@2.0.2: {} @@ -33119,6 +34687,14 @@ snapshots: stream-buffers@2.2.0: {} + stream-parser@0.3.1: + dependencies: + debug: 2.6.9 + transitivePeerDependencies: + - supports-color + + stream-shift@1.0.3: {} + streamdown@2.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: clsx: 2.1.1 @@ -33151,6 +34727,10 @@ snapshots: - bare-abort-controller - react-native-b4a + string-split-by@1.0.0: + dependencies: + parenthesis: 3.1.8 + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -33219,6 +34799,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + string_decoder@0.10.31: {} + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -33268,6 +34850,8 @@ snapshots: dependencies: js-tokens: 9.0.1 + strongly-connected-components@1.0.1: {} + structured-clone-es@2.0.0: {} structured-headers@0.4.1: {} @@ -33324,6 +34908,14 @@ snapshots: tinyglobby: 0.2.15 ts-interface-checker: 0.1.13 + supercluster@7.1.5: + dependencies: + kdbush: 3.0.0 + + supercluster@8.0.1: + dependencies: + kdbush: 4.0.2 + supports-color@10.2.2: {} supports-color@5.5.0: @@ -33388,6 +34980,23 @@ snapshots: magic-string: 0.30.21 zimmerframe: 1.1.4 + svg-arc-to-cubic-bezier@3.2.0: {} + + svg-path-bounds@1.0.2: + dependencies: + abs-svg-path: 0.1.1 + is-svg-path: 1.0.2 + normalize-svg-path: 1.1.0 + parse-svg-path: 0.1.2 + + svg-path-sdf@1.1.3: + dependencies: + bitmap-sdf: 1.0.4 + draw-svg-path: 1.0.0 + is-svg-path: 1.0.2 + parse-svg-path: 0.1.2 + svg-path-bounds: 1.0.2 + svgo@4.0.1: dependencies: commander: 11.1.0 @@ -33538,6 +35147,16 @@ snapshots: throttleit@2.1.0: {} + through2@0.6.5: + dependencies: + readable-stream: 1.0.34 + xtend: 4.0.2 + + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + tiny-emitter@2.1.0: {} tiny-invariant@1.3.3: {} @@ -33546,6 +35165,8 @@ snapshots: tinyclip@0.1.12: {} + tinycolor2@1.6.0: {} + tinyexec@0.3.2: {} tinyexec@1.0.2: {} @@ -33559,6 +35180,10 @@ snapshots: tinypool@1.1.1: {} + tinyqueue@2.0.3: {} + + tinyqueue@3.0.0: {} + tinyrainbow@1.2.0: {} tinyrainbow@2.0.0: {} @@ -33577,6 +35202,12 @@ snapshots: tmpl@1.0.5: {} + to-float32@1.1.0: {} + + to-px@1.1.0: + dependencies: + parse-unit: 1.0.1 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -33587,6 +35218,10 @@ snapshots: tokenx@1.3.0: {} + topojson-client@3.1.0: + dependencies: + commander: 2.20.3 + totalist@3.0.1: {} tough-cookie@5.1.2: @@ -33710,6 +35345,8 @@ snapshots: type-level-regexp@0.1.17: {} + type@2.7.3: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -33743,6 +35380,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typedarray-pool@1.2.0: + dependencies: + bit-twiddle: 1.0.2 + dup: 1.0.0 + + typedarray@0.0.6: {} + typescript-eslint@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3): dependencies: '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3) @@ -33944,6 +35588,8 @@ snapshots: picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 + unquote@1.1.1: {} + unrs-resolver@1.11.1: dependencies: napi-postinstall: 0.3.4 @@ -34028,6 +35674,8 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-diff@1.1.0: {} + uqr@0.1.2: {} uri-js@4.4.1: @@ -34399,6 +36047,12 @@ snapshots: vscode-uri@3.1.0: {} + vt-pbf@3.1.3: + dependencies: + '@mapbox/point-geometry': 0.1.0 + '@mapbox/vector-tile': 1.3.1 + pbf: 3.3.0 + vue-bundle-renderer@2.2.0: dependencies: ufo: 1.6.3 @@ -34447,12 +36101,18 @@ snapshots: dependencies: defaults: 1.0.4 + weak-map@1.0.8: {} + web-namespaces@2.0.1: {} web-streams-polyfill@4.0.0-beta.3: {} web-vitals@5.1.0: {} + webgl-context@2.2.0: + dependencies: + get-canvas-context: 1.0.2 + webidl-conversions@3.0.1: {} webidl-conversions@5.0.0: {} @@ -34563,6 +36223,10 @@ snapshots: dependencies: isexe: 2.0.0 + which@4.0.0: + dependencies: + isexe: 3.1.5 + which@6.0.1: dependencies: isexe: 4.0.0 @@ -34576,6 +36240,10 @@ snapshots: word-wrap@1.2.5: {} + world-calendars@1.0.4: + dependencies: + object-assign: 4.1.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -34638,6 +36306,8 @@ snapshots: xmlchars@2.2.0: {} + xtend@2.2.0: {} + xtend@4.0.2: {} xxhash-wasm@1.1.0: {}