semiotic 3.7.2 → 3.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -131,9 +131,9 @@ When the catalog doesn't fit, three HOCs take a layout function emitting scene p
131
131
 
132
132
  Layout signature differs by family:
133
133
  - **XY/Ordinal**: `layout: (ctx) => { nodes, overlays? }`. `ctx`: `data`, `scales` ({x,y} XY | {o,r,projection} ordinal), `dimensions` (plot rect — center-anchored for radial ordinal, top-left otherwise), `theme`, `resolveColor(key)`, `config`.
134
- - **Network**: `layout: (ctx) => { sceneNodes?, sceneEdges?, labels?, overlays? }`. `ctx`: `nodes`, `edges`, `dimensions`, `theme`, `resolveColor(key)`, `config`. Run external positioners (`d3-flextree`, `dagre`) then emit network scene primitives (circle/rect/arc nodes; line/bezier/curved edges).
134
+ - **Network**: `layout: (ctx) => { sceneNodes?, sceneEdges?, labels?, overlays? }`. `ctx`: `nodes`, `edges`, `dimensions`, `theme`, `resolveColor(key)`, `config`, `selection` (shared-selection predicate `{ isActive, predicate(datum) }` from `LinkedCharts`, `null` when unwired — dim/highlight by it). Run external positioners (`d3-flextree`, `dagre`) then emit network scene primitives (circle/rect/arc nodes; line/bezier/curved edges).
135
135
 
136
- `semiotic/recipes` ships pure layout functions (`waffleLayout`, `calendarLayout`, `marimekkoLayout`, `bulletLayout`, `parallelCoordinatesLayout`, `flextreeLayout`, `dagreLayout`). BYO heavy deps (`d3-flextree`, `dagre`) in user code.
136
+ `semiotic/recipes` ships pure layout functions (`waffleLayout`, `calendarLayout`, `marimekkoLayout`, `bulletLayout`, `parallelCoordinatesLayout`, `flextreeLayout`, `dagreLayout`, `lineageDagLayout`). BYO heavy deps (`d3-flextree`, `dagre`) in user code. `lineageDagLayout` renders a pre-positioned **layered lineage/DAG** (reads logical layer/row coords, no re-layout) with composite node glyphs (one hit-rect per node + icon/label/store-chip chrome in `overlays`), level-of-detail collapse (full→compact→icon→dot), distinct dashed back-edges, and host-driven reach-dimming (`layoutConfig.reachableIds`) + selection (`layoutConfig.selectedId` / shared `ctx.selection`).
137
137
 
138
138
  **Chrome (labels/axes/legends): the recipe owns it.** Recipes emit own chrome via `overlays` return field (ReactNode painted on top). Built-in axes via `showAxes` on the HOC work for layouts respecting the standard scale. Recipe convention: `showXxx` boolean toggles, `xxxFormat` callbacks. Shipped recipes' toggles: marimekko `showCategoryLabels`, bullet `showLabels`+`showTicks`, parallelCoordinates `showAxes`, flextree/dagre `showLabels`, waffle/calendar none.
139
139
 
@@ -143,13 +143,16 @@ Layout signature differs by family:
143
143
  - Coords are plot-relative (frame translates by `margin`). Read `ctx.dimensions.plot`. Radial ordinal: `plot.x = -width/2`, `plot.y = -height/2` (center-translated).
144
144
  - Layouts needing axis domains: pass `xExtent`/`yExtent` (XY) or `oExtent`/`rExtent` (ordinal) — those flow through scale construction *before* the layout runs.
145
145
  - Streaming layouts: ingest via ref (`push`/`pushMany`); layout re-runs on each ingest. Overlays update on data-change paths, NOT per-frame.
146
+ - **On `NetworkCustomChart`, a `layoutConfig` change re-runs the layout (`buildScene`) WITHOUT re-ingesting the node/edge topology** — so drive interaction state, styling, or animation progress through `layoutConfig` for cheap per-frame updates; swapping `nodes`/`edges` (or the chart `width`/`height`) is the heavier path that re-ingests. Custom overlays are read straight from the store at render time (the frame's repaint re-reads them), so a recipe returning fresh JSX every layout call needs no per-frame `setState`.
146
147
  - Custom layouts own their colors — always prefer `ctx.resolveColor(key)` over hardcoded literals. `CategoryColorProvider` integration is XY-only; for cross-chart sync on network/ordinal customLayouts, pass matching `colorScheme` to each.
148
+ - `NetworkCustomChart` accepts `selection` / `linkedHover` / `chartId` like the built-in network HOCs: hover/click emit into the shared selection store, and the resolved predicate arrives as `ctx.selection` so the layout can dim/highlight by a cross-chart selection. Host-owned per-render dimming (e.g. a graph-reachability set) is orthogonal — pass it through `layoutConfig` and read `ctx.config`.
147
149
  - Tooltips: emit datum keys matching user-visible accessor names. Avoid underscored synthetic keys (default tooltip filters those out).
148
150
 
149
151
  ## Coordinated Views
150
152
 
151
153
  **LinkedCharts** — `selections`. **CategoryColorProvider** — `colors`|`categories` + `colorScheme`.
152
- Chart props: `selection`, `linkedHover`, `linkedBrush`. Hooks: `useSelection`, `useLinkedHover`, `useBrushSelection`.
154
+ Chart props: `selection`, `linkedHover`, `linkedBrush`. Hooks: `useSelection`, `useLinkedHover`, `useBrushSelection`. Works for `NetworkCustomChart` too — the resolved selection predicate is threaded into the custom layout as `ctx.selection`.
155
+ **`useSelectionActions(name)`** — write-only access (`selectPoints`/`clear`) that does NOT subscribe to selection state, so a *container* can push a selection (e.g. from a hover handler) without re-rendering; only the leaf consumers reading the selection re-render. The provider-at-top / consumers-at-leaves pattern for interaction-heavy coordinated views.
153
156
  **Shared categories inside LinkedCharts → wrap in `CategoryColorProvider`.** Gives identical per-category colors AND makes LinkedCharts render one unified legend (suppressing individual chart legends). Without it, mismatched colors and duplicate legends.
154
157
  **Linked crosshair**: `linkedHover={{ name: "sync", mode: "x-position", xField: "time" }}`. Click locks crosshair (dashed white); click/Escape unlocks.
155
158
  **Linked series highlight** (series↔bar cross-highlight): `linkedHover={{ name: "sync", mode: "series" }}` auto-resolves the chart's series-identity field (colorBy/lineBy/areaBy/stackBy/groupBy) and keys the linked selection off it — no hand-wired `fields`. Add `seriesField: "region"` to override (align charts whose series live under different prop names). Modes are exclusive: `mode` is `"field"` (default) | `"x-position"` (crosshair) | `"series"`.
package/README.md CHANGED
@@ -12,17 +12,15 @@ Simple charts in 5 lines. Network graphs, streaming data, and coordinated
12
12
  dashboards when you need them. Structured schemas and an MCP server so
13
13
  AI coding assistants generate correct chart code on the first try.
14
14
 
15
- ## What's New in 3.7.2
15
+ ## What's New in 3.7.4
16
16
 
17
- 3.7.2 is a deployment and docs-polish patch release:
17
+ 3.7.4 is a network-chart and maintenance patch release:
18
18
 
19
- - `semiotic-mcp --http` now runs in stateless Streamable HTTP mode, with JSON responses,
20
- health endpoints, optional `MCP_ALLOWED_HOSTS` host-header protection, and serverless-friendly
21
- request teardown.
22
- - `deploy/cloud-run` contains a minimal Google Cloud Run wrapper for publishing the MCP server as a
23
- public read-only connector for ChatGPT, Claude, and other MCP clients.
24
- - The Accessibility docs' visible navigation tree and bidirectional BarChart examples now honor
25
- dark mode, and the contested-annotations blog demo renders its editorial-status callouts reliably.
19
+ - `semiotic/recipes` adds lineage DAG helpers for laying out data-flow and KStreams-style network
20
+ diagrams.
21
+ - Network custom layouts now preserve selection metadata through streaming layout and render paths.
22
+ - Docs add the KStreams DAG recipe and expand custom network chart guidance, alongside
23
+ dependency updates for Prettier, TypeScript ESLint, Rollup, and Sharp.
26
24
 
27
25
  ```jsx
28
26
  import { LineChart } from "semiotic/xy"
@@ -423,13 +421,15 @@ Add to your MCP client config (e.g. `claude_desktop_config.json` for Claude Desk
423
421
  }
424
422
  ```
425
423
 
426
- No API keys or authentication required. The server runs locally via stdio. HTTP mode is also available for inspectors, web clients, and ChatGPT Apps SDK experiments: `npx semiotic-mcp --http --port 3001`. In 3.7.2 HTTP mode is stateless: each request gets a fresh read-only MCP server + transport, so it can autoscale on serverless hosts without sticky sessions.
424
+ No API keys or authentication required. The server runs locally via stdio. HTTP mode is also available for inspectors, web clients, and ChatGPT Apps SDK experiments: `npx semiotic-mcp --http --port 3001`. Since 3.7.2, HTTP mode is stateless: each request gets a fresh read-only MCP server + transport, so it can autoscale on serverless hosts without sticky sessions.
427
425
 
428
426
  For ChatGPT developer mode, expose the HTTP endpoint over HTTPS with a tunnel and create a connector that points at `https://<your-tunnel>/mcp`. The experimental Apps SDK surface is `renderInteractiveChart`, which returns a `text/html;profile=mcp-app` widget template plus a hidden SVG payload rendered by Semiotic on the MCP server.
429
427
 
430
428
  For a hosted deployment, see `deploy/cloud-run`. The wrapper runs the published `semiotic-mcp`
431
429
  binary, exposes `/mcp` plus health endpoints, and supports `MCP_ALLOWED_HOSTS` for production
432
- host-header allowlisting.
430
+ host-header allowlisting. For ChatGPT Apps domain verification, set
431
+ `OPENAI_APPS_CHALLENGE_TOKEN` so HTTP mode serves the raw token from
432
+ `/.well-known/openai-apps-challenge`.
433
433
 
434
434
  ### Tools
435
435
 
@@ -33854,6 +33854,7 @@ var port = Number.isFinite(parsedPort) ? parsedPort : 3001;
33854
33854
  async function main() {
33855
33855
  if (httpMode) {
33856
33856
  const allowedHosts = (process.env.MCP_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
33857
+ const openaiAppsChallengeToken = (process.env.OPENAI_APPS_CHALLENGE_TOKEN || "").trim();
33857
33858
  const healthBody = () => JSON.stringify({
33858
33859
  status: "ok",
33859
33860
  name: "semiotic-mcp",
@@ -33895,6 +33896,14 @@ async function main() {
33895
33896
  res.end(healthBody());
33896
33897
  return;
33897
33898
  }
33899
+ if (req.method === "GET" && pathname === "/.well-known/openai-apps-challenge" && openaiAppsChallengeToken) {
33900
+ res.writeHead(200, {
33901
+ "Content-Type": "text/plain; charset=utf-8",
33902
+ "Cache-Control": "no-store"
33903
+ });
33904
+ res.end(openaiAppsChallengeToken);
33905
+ return;
33906
+ }
33898
33907
  if (pathname !== "/" && pathname !== "/mcp") {
33899
33908
  res.writeHead(404, { "Content-Type": "application/json" });
33900
33909
  res.end(JSON.stringify({ error: "Not found" }));
package/ai/schema.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "name": "semiotic",
4
- "version": "3.7.2",
4
+ "version": "3.7.4",
5
5
  "description": "React data visualization library for charts, networks, and beyond",
6
6
  "tools": [
7
7
  {
@@ -1,8 +1,8 @@
1
1
  import * as React from "react";
2
2
  import type { ResolutionMode } from "./store/SelectionStore";
3
3
  type LegendInteractionMode = "highlight" | "isolate" | "none";
4
- export { useSelection, useLinkedHover, useBrushSelection, useFilteredData } from "./store/useSelection";
5
- export type { UseSelectionOptions, UseSelectionResult, UseLinkedHoverOptions, UseLinkedHoverResult, UseBrushSelectionOptions, UseBrushSelectionResult } from "./store/useSelection";
4
+ export { useSelection, useSelectionActions, useLinkedHover, useBrushSelection, useFilteredData } from "./store/useSelection";
5
+ export type { UseSelectionOptions, UseSelectionResult, UseSelectionActionsResult, UseLinkedHoverOptions, UseLinkedHoverResult, UseBrushSelectionOptions, UseBrushSelectionResult } from "./store/useSelection";
6
6
  export { useChartObserver } from "./store/useObservation";
7
7
  export type { UseChartObserverOptions, UseChartObserverResult } from "./store/useObservation";
8
8
  /** Hook: returns true when a parent LinkedCharts is handling the legend. */
@@ -29,7 +29,7 @@ export interface NetworkCustomChartProps<TNode extends Datum = Datum, TEdge exte
29
29
  * `legendGroups` array yourself and pass it through `frameProps.legend`.
30
30
  */
31
31
  /** Additional StreamNetworkFrame props for advanced customization. */
32
- frameProps?: Partial<Omit<StreamNetworkFrameProps, "nodes" | "edges" | "chartType" | "size" | "customNetworkLayout" | "layoutConfig">>;
32
+ frameProps?: Partial<Omit<StreamNetworkFrameProps, "nodes" | "edges" | "chartType" | "size" | "customNetworkLayout" | "layoutConfig" | "layoutSelection">>;
33
33
  }
34
34
  /**
35
35
  * NetworkCustomChart — escape hatch for bespoke network geometry.
@@ -0,0 +1,129 @@
1
+ import type { ReactNode } from "react";
2
+ import type { NetworkCustomLayout } from "../stream/networkCustomLayout";
3
+ import type { Datum } from "../charts/shared/datumTypes";
4
+ /**
5
+ * Level of detail for a node glyph.
6
+ * - `full` container + icon + type label + truncated name + store-slot chips
7
+ * - `compact` container + icon + truncated name
8
+ * - `icon` container + icon only
9
+ * - `dot` a single ~5px circle (minimap density)
10
+ */
11
+ export type LineageLod = "full" | "compact" | "icon" | "dot";
12
+ export interface LineageStoreSlot {
13
+ storeName: string;
14
+ slotIndex: number;
15
+ }
16
+ export interface LineageDagConfig {
17
+ /** Number of layers (x domain). Computed from node `x` extents when omitted. */
18
+ layerCount?: number;
19
+ /** Largest layer's row count (y domain). Computed from the data when omitted. */
20
+ maxLayerSize?: number;
21
+ /** Target full-glyph width / height in px. Shrunk to fit the plot. @default 172 / 54 */
22
+ nodeWidth?: number;
23
+ nodeHeight?: number;
24
+ /** Minimum gap reserved between glyphs when fitting. @default 26 / 18 */
25
+ minGapX?: number;
26
+ minGapY?: number;
27
+ /** Force a level of detail; `"auto"` derives it from the fitted glyph size. @default "auto" */
28
+ lod?: LineageLod | "auto";
29
+ /**
30
+ * Caller-supplied "reachable" set. When present, nodes/edges **outside**
31
+ * it dim to `dimOpacity`. This is the controlled-dimming channel: the host
32
+ * computes the set (e.g. downstream BFS of the hovered node) and re-renders.
33
+ * Independent of, and composes with, `NetworkLayoutContext.selection`.
34
+ */
35
+ reachableIds?: Iterable<string> | null;
36
+ /** Currently-selected node id — drawn with the selection ring. Host-owned. */
37
+ selectedId?: string | null;
38
+ /** Opacity for dimmed (out-of-reach / unselected) marks. @default 0.14 */
39
+ dimOpacity?: number;
40
+ /** Logical layer index (0 = leftmost). @default "x" */
41
+ layerAccessor?: string;
42
+ /** Logical row offset within the layer (centered on 0). @default "y" */
43
+ rowAccessor?: string;
44
+ /** Node partition → fill family. @default "partition" */
45
+ partitionAccessor?: string;
46
+ /** Node semantic → icon. @default "semantic" */
47
+ semanticAccessor?: string;
48
+ /** Human label. @default "label" */
49
+ labelAccessor?: string;
50
+ /** Store list — `string[]` or `{storeName,slotIndex}[]`. @default "stores" */
51
+ storesAccessor?: string;
52
+ /** Edge "closes a cycle" flag. @default "isBackEdge" */
53
+ backEdgeAccessor?: string;
54
+ /** Edge type, drives edge color. @default "edgeType" */
55
+ edgeTypeAccessor?: string;
56
+ /** Fill per partition. Overridable; recipe ships dark-theme-friendly defaults. */
57
+ partitionColors?: Partial<Record<string, string>>;
58
+ /** Edge stroke per edgeType, plus `back` for back-edges. */
59
+ edgeColors?: Partial<Record<string, string>>;
60
+ /** Selection-ring stroke. @default var(--semiotic-focus, #ffcc33) */
61
+ accentColor?: string;
62
+ /** Container border stroke. @default var(--semiotic-border, #555) */
63
+ borderColor?: string;
64
+ /** Draw the store-slot chips in `full` LOD. @default true */
65
+ showStoreChips?: boolean;
66
+ /** Chip fill. @default var(--semiotic-info, #6a8caf) */
67
+ storeChipColor?: string;
68
+ /** Base edge opacity (non-dimmed). @default 0.5 */
69
+ edgeOpacity?: number;
70
+ /** Forward-edge stroke width in px. @default 1.25 */
71
+ edgeWidth?: number;
72
+ /** Back-edge stroke width in px. @default `edgeWidth`, else 1.5 */
73
+ backEdgeWidth?: number;
74
+ /**
75
+ * Render the per-node icon. Receives the resolved semantic/partition, the
76
+ * pixel size to draw within, and a color hint. Return any SVG node. When
77
+ * omitted a labelled fallback chip is drawn. The demo passes a KStreams
78
+ * icon set here — keeping the recipe domain-agnostic.
79
+ */
80
+ renderIcon?: (info: {
81
+ semantic: string;
82
+ partition: string;
83
+ size: number;
84
+ color: string;
85
+ node: Datum;
86
+ }) => ReactNode;
87
+ /** Small type label shown above the name in `full` LOD. @default the semantic. */
88
+ typeLabel?: (info: {
89
+ semantic: string;
90
+ partition: string;
91
+ node: Datum;
92
+ }) => string;
93
+ }
94
+ /**
95
+ * `lineageDagLayout` — a reusable layout recipe for **pre-positioned layered
96
+ * lineage / DAG graphs** with rich, composite node glyphs. Used here for the
97
+ * Kafka Streams topology viewer, but domain-agnostic: it reads logical
98
+ * `x` (layer) / `y` (row) coordinates the caller already computed (e.g. from a
99
+ * `dagLayoutFromGraph` pipeline) and maps them into the plot — it never runs a
100
+ * force sim or re-lays-out, so output is deterministic.
101
+ *
102
+ * **Composite glyphs as one hit-testable unit.** Each node emits exactly one
103
+ * `rect` (or `circle` in `dot` LOD) scene node — that single mark owns the
104
+ * canvas hit area and carries the node datum/id. All glyph chrome (semantic
105
+ * icon, type label, truncated name, per-store chips, selection ring) is drawn
106
+ * in the returned `overlays` layer, which is `pointer-events: none`, so it
107
+ * decorates without ever intercepting a hover. Hover/click therefore always
108
+ * resolve to the underlying node as a unit — see §5.2(a) of the spec.
109
+ *
110
+ * **Controlled dimming + selection, from outside.** `config.reachableIds`
111
+ * (host-computed set) dims everything outside it; `config.selectedId` draws
112
+ * the selection ring; both are owned by the host, never by the frame. The
113
+ * shared `NetworkLayoutContext.selection` predicate (from `LinkedCharts`)
114
+ * composes on top — a node dims if excluded by *either* cue.
115
+ *
116
+ * @example
117
+ * ```tsx
118
+ * import { NetworkCustomChart } from "semiotic/network"
119
+ * import { lineageDagLayout } from "semiotic/recipes"
120
+ *
121
+ * <NetworkCustomChart
122
+ * nodes={dagNodes} // each: { id, x: layer, y: row, partition, semantic, stores, label }
123
+ * edges={dagEdges} // each: { source, target, edgeType, isBackEdge }
124
+ * layout={lineageDagLayout}
125
+ * layoutConfig={{ layerCount, maxLayerSize, reachableIds, selectedId }}
126
+ * />
127
+ * ```
128
+ */
129
+ export declare const lineageDagLayout: NetworkCustomLayout<LineageDagConfig>;
@@ -13,6 +13,8 @@ export { flextreeLayout } from "./recipes/flextree";
13
13
  export type { FlextreeConfig } from "./recipes/flextree";
14
14
  export { dagreLayout } from "./recipes/dagre";
15
15
  export type { DagreConfig } from "./recipes/dagre";
16
+ export { lineageDagLayout } from "./recipes/lineageDag";
17
+ export type { LineageDagConfig, LineageLod, LineageStoreSlot } from "./recipes/lineageDag";
16
18
  export { marimekkoLayout } from "./recipes/marimekko";
17
19
  export type { MarimekkoConfig } from "./recipes/marimekko";
18
20
  export { bulletLayout } from "./recipes/bullet";
@@ -32,8 +32,8 @@ export type { StreamOrdinalFrameProps, StreamOrdinalFrameHandle, OrdinalChartTyp
32
32
  export type { StreamNetworkFrameProps, StreamNetworkFrameHandle, NetworkChartType, NetworkSceneNode, NetworkSceneEdge, NetworkLabel, ThresholdAlertConfig } from "./stream/networkTypes";
33
33
  export type { SelectionConfig, LinkedHoverProp, LinkedBrushProp } from "./charts/shared/types";
34
34
  export type { LinkedChartsProps } from "./LinkedCharts";
35
- export { useSelection, useLinkedHover, useBrushSelection, useFilteredData } from "./LinkedCharts";
36
- export type { UseSelectionOptions, UseSelectionResult, UseLinkedHoverOptions, UseLinkedHoverResult, UseBrushSelectionOptions, UseBrushSelectionResult } from "./LinkedCharts";
35
+ export { useSelection, useSelectionActions, useLinkedHover, useBrushSelection, useFilteredData } from "./LinkedCharts";
36
+ export type { UseSelectionOptions, UseSelectionResult, UseSelectionActionsResult, UseLinkedHoverOptions, UseLinkedHoverResult, UseBrushSelectionOptions, UseBrushSelectionResult } from "./LinkedCharts";
37
37
  export { useChartObserver } from "./LinkedCharts";
38
38
  export type { UseChartObserverOptions, UseChartObserverResult } from "./LinkedCharts";
39
39
  export type { ChartObservation, OnObservationCallback } from "./store/ObservationStore";
@@ -23,6 +23,28 @@ export interface UseSelectionResult {
23
23
  clientId: string;
24
24
  }
25
25
  export declare function useSelection(options: UseSelectionOptions): UseSelectionResult;
26
+ export interface UseSelectionActionsResult {
27
+ /** Set a point selection (categorical values) under this client's clause. */
28
+ selectPoints: (fieldValues: Record<string, unknown[]>) => void;
29
+ /** Clear this client's clause. */
30
+ clear: () => void;
31
+ /** This client's ID. */
32
+ clientId: string;
33
+ }
34
+ /**
35
+ * Write-only access to a named selection that **does not subscribe** to the
36
+ * selection state — selecting only the stable `setClause`/`clearClause`
37
+ * actions, so the calling component never re-renders when the selection
38
+ * changes.
39
+ *
40
+ * Use this when a *container* needs to push a selection (e.g. from a hover
41
+ * handler) but only the leaf consumers (the charts reading the selection)
42
+ * should re-render. Pairs with `LinkedCharts` for the
43
+ * provider-at-top / consumers-at-leaves pattern: the writer stays out of the
44
+ * re-render path, avoiding per-interaction reconciliation + allocation in the
45
+ * container subtree. For read + write, use `useSelection`.
46
+ */
47
+ export declare function useSelectionActions(name: string, clientId?: string): UseSelectionActionsResult;
26
48
  export interface UseLinkedHoverOptions {
27
49
  /** Selection name. Defaults to "hover" */
28
50
  name?: string;
@@ -1,6 +1,33 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { NetworkSceneNode, NetworkSceneEdge, NetworkLabel, RealtimeNode, RealtimeEdge } from "./networkTypes";
3
3
  import type { ThemeSemanticColors } from "./types";
4
+ import type { Datum } from "../charts/shared/datumTypes";
5
+ /**
6
+ * The shared selection state, projected into the custom-layout context.
7
+ *
8
+ * When the chart participates in a `LinkedCharts` / selection store (via
9
+ * `NetworkCustomChart`'s `selection` / `linkedHover` props), the frame
10
+ * threads the resolved predicate here so a custom layout can dim or
11
+ * highlight marks by the *shared* selection — the same predicate the
12
+ * built-in HOCs apply to `nodeStyle`. Mirrors `SelectionHookResult`.
13
+ *
14
+ * `predicate` receives the **raw** datum (the user object you passed in
15
+ * `nodes`, i.e. `node.data ?? node`), matching the `d.data || d`
16
+ * convention the built-in network charts use. `isActive` is `false`
17
+ * when no selection clause is present — when `false`, treat every mark
18
+ * as selected (draw at full weight).
19
+ *
20
+ * This is orthogonal to `config` (your `layoutConfig` blob): use `config`
21
+ * for host-owned highlight sets you compute yourself (e.g. a graph
22
+ * reachability set), and `selection` for cross-chart coordination that
23
+ * rides the shared store.
24
+ */
25
+ export interface NetworkLayoutSelection {
26
+ /** Whether a selection clause is currently active. */
27
+ isActive: boolean;
28
+ /** Returns `true` when the raw datum matches the active selection. */
29
+ predicate: (datum: Datum) => boolean;
30
+ }
4
31
  /**
5
32
  * customLayout escape hatch for `StreamNetworkFrame`.
6
33
  *
@@ -54,6 +81,14 @@ export interface NetworkLayoutContext<C extends object = Record<string, unknown>
54
81
  resolveColor: (key: string) => string;
55
82
  /** User-supplied config blob threaded through `layoutConfig`. */
56
83
  config: C;
84
+ /**
85
+ * Shared-selection projection. Present when the chart is wired to a
86
+ * `LinkedCharts` / selection store; `null` otherwise. Use
87
+ * `selection.isActive` + `selection.predicate(node.data ?? node)` to
88
+ * dim/highlight marks by the cross-chart selection. See
89
+ * {@link NetworkLayoutSelection}.
90
+ */
91
+ selection?: NetworkLayoutSelection | null;
57
92
  }
58
93
  export interface NetworkLayoutResult {
59
94
  /** Positioned scene primitives. Circles, rects, or arcs. */
@@ -453,6 +453,11 @@ export interface NetworkPipelineConfig {
453
453
  customNetworkLayout?: import("./networkCustomLayout").NetworkCustomLayout;
454
454
  /** User-supplied config blob threaded through to NetworkLayoutContext.config. */
455
455
  layoutConfig?: object;
456
+ /** Resolved shared-selection predicate, surfaced to a custom layout as
457
+ * `NetworkLayoutContext.selection`. Render-only — deliberately kept out of
458
+ * the layout/ingest-affecting signature so a selection change re-runs
459
+ * `buildScene` (re-emitting dimmed marks) without a re-ingest or re-layout. */
460
+ layoutSelection?: import("./networkCustomLayout").NetworkLayoutSelection | null;
456
461
  }
457
462
  export interface StreamNetworkFrameProps<T = Datum> {
458
463
  chartType: NetworkChartType;
@@ -566,6 +571,10 @@ export interface StreamNetworkFrameProps<T = Datum> {
566
571
  customNetworkLayout?: import("./networkCustomLayout").NetworkCustomLayout;
567
572
  /** User-supplied config blob threaded through to NetworkLayoutContext.config. */
568
573
  layoutConfig?: object;
574
+ /** Resolved shared-selection predicate, surfaced to a custom layout as
575
+ * `NetworkLayoutContext.selection`. Set by `NetworkCustomChart` from its
576
+ * `selection` / `linkedHover` wiring; render-only (no re-ingest on change). */
577
+ layoutSelection?: import("./networkCustomLayout").NetworkLayoutSelection | null;
569
578
  }
570
579
  export interface StreamNetworkFrameHandle {
571
580
  push(edge: EdgePush): void;