/** * SankeyChart — where a quantity came from and where it ended up. * * ```tsx * * * * * * * * ``` * * ## What it answers that the other charts do not * * Every other chart here takes one set of things and measures them. This one * takes two and says how much of the first became the second. A treemap cuts a * total into its parts, a funnel counts what survived each step, a waterfall * carries a balance from one figure to another — none of them can say that * *this* source fed *that* destination, because none of them draws a thing that * has two ends. * * So the question to bring to it is a routing question. Which campaigns * produced which signups; which budget lines paid for which departments; what * the traffic that arrived on the landing page went on to do. If the answer * does not need a source *and* a target, one of the simpler charts will read * better at the same size. * * ## The ribbon is the reading * * A ribbon's thickness is its value, on one scale shared by the whole diagram, * so a ribbon twice as thick is twice as much wherever it is on the page. That * is the only quantity here: the horizontal distance a ribbon travels is the * number of columns between its ends and means nothing else, and the vertical * order within a column is chosen to keep the ribbons from crossing rather than * to rank anything. * * A node's height is what passes through it — the larger of what arrives and * what leaves, which are the same number unless some of it went nowhere. Where * they differ, give the node an explicit `value` to pin it; the diagram cannot * infer a loss it was never told about. * * ## Columns are the flow's, not the caller's * * Nothing about the order of the `nodes` array decides where a node is drawn. A * node that receives from another has to be drawn after it or its ribbon would * run backwards, so the columns come out of the links. `align` only settles the * cases the flow leaves open, and the default pushes every node that feeds * nothing into the last column, so the diagram ends on a straight edge of * destinations instead of a ragged one. * * ## Bad rows are dropped rather than fatal * * Flow data is nearly always joined together from somewhere nobody in the room * owns, and it arrives with rows that name a node that is not there, carry a * zero, or close a loop. A loop in particular has no left-to-right reading at * all. All of them are dropped and counted, and `onDropLinks` reports how many * — so a screen can say "3 rows could not be drawn" instead of going blank or * quietly showing less than it was given. */ import { type ReactNode } from 'react'; import { type ViewProps } from 'react-native'; import { type ChartAccessibilityProps } from '../../primitives/chart-accessibility.js'; import { type SankeyAlign } from './sankey-layout.js'; export type { SankeyAlign } from './sankey-layout.js'; export type SankeyChartStatus = 'loading' | 'ready'; export interface SankeyNode { /** Stable key the links name. Must be unique; a repeat is ignored. */ id: string; /** Name drawn beside the bar. Defaults to the id. */ label?: string; /** Explicit colour, instead of the node's place in the palette. */ color?: string; /** * Pins what passes through the node, for a stage that loses some of what it * received to somewhere the links do not describe. Left unset the node is * worth the larger of what arrives and what leaves. */ value?: number; } export interface SankeyLink { /** `id` of the node it leaves. */ source: string; /** `id` of the node it reaches. */ target: string; /** How much travels. Zero, negative and non-finite rows are dropped. */ value: number; /** Explicit colour, instead of taking the source node's. */ color?: string; } /** The selected node and what runs through it, for something drawn inside the chart. */ export declare function useSankeyChart(): { activeId: string | null; activeNode: SankeyNode | null; /** What passes through it — what the bar's height is drawn from. */ activeValue: number; /** What arrives. Zero at a node the flow starts from. */ incoming: number; /** What leaves. Zero at a node the flow ends at. */ outgoing: number; }; export interface SankeyChartProps extends ViewProps, ChartAccessibilityProps { className?: string; /** The stages. Order does not decide position — the links do. */ nodes: SankeyNode[]; /** What travels between them. */ links: SankeyLink[]; /** * How tall the diagram is drawn, in points. * * The width is the card's, but nothing in a flow says how deep it should be: * a diagram of four nodes and one of forty are the same data at two heights, * and which of them is right is a question about the screen. */ height?: number; /** How thick a node's bar is, in points. */ nodeWidth?: number; /** * The gap between two nodes in a column, in points. * * A maximum rather than a promise. A crowded column gives its spacing up * before it gives up the height of its bars, because the bar is the reading. */ nodePadding?: number; /** Which column a node goes in where the flow leaves a choice. */ align?: SankeyAlign; /** Relaxation rounds spent untangling the ribbons. */ iterations?: number; /** How far a ribbon bends, `0` for a straight diagonal and `0.5` for an S. */ curve?: number; /** The first hue. The rest of the palette follows from the theme's tokens. */ color?: string; /** Milliseconds for one column to draw itself. */ animationDuration?: number; /** Milliseconds between one column starting and the next. `0` for all at once. */ staggerDelay?: number; /** `loading` draws a plain placeholder until the data arrives. */ status?: SankeyChartStatus; /** Selected node. Leave unset to let the chart track it. */ activeId?: string | null; /** Fires with the selected node's id, or `null` when the selection is cleared. */ onActiveIdChange?: (id: string | null) => void; /** * Fires with how many link rows could not be drawn — ones naming a node that * is not there, carrying nothing, or closing a loop. `0` after a clean render, * so a banner can be shown and taken away from the same signal. */ onDropLinks?: (count: number) => void; children?: ReactNode; } /** Imperative handle: re-run the entrance, for a "replay" control. */ export interface SankeyChartHandle { replay: () => void; } export interface SankeyChartLinksProps { /** A ribbon's opacity at rest. */ opacity?: number; /** A ribbon's opacity when its node is selected. */ activeOpacity?: number; /** And when something else is. */ dimOpacity?: number; } /** * The ribbons. * * Drawn before the bars so a bar sits on top of the flows that meet it, which * is what gives a node a clean edge to arrive at instead of a fringe of ribbon * ends poking through it. * * Translucent at rest, and that is not decoration. Ribbons cross — that is the * shape of routed data — and an opaque one hides whatever passes under it, so * the reader loses the smaller of every pair. At this opacity a crossing reads * as two ribbons rather than as one with a notch in it. */ declare function SankeyChartLinks({ opacity, activeOpacity, dimOpacity, }: SankeyChartLinksProps): import("react").JSX.Element | null; declare namespace SankeyChartLinks { var displayName: string; var slot: "svg"; } export interface SankeyChartNodesProps { /** Corner radius on a node's bar, in points. */ radius?: number; /** A bar's opacity when something else is selected. */ dimOpacity?: number; } /** * The bars the ribbons run between. * * Solid where the ribbons are translucent, because a node is the one thing on * the diagram that is not crossing anything else — it is the edge the flow * arrives at, and it reads as an edge only if nothing shows through it. */ declare function SankeyChartNodes({ radius, dimOpacity }: SankeyChartNodesProps): import("react").JSX.Element | null; declare namespace SankeyChartNodes { var displayName: string; var slot: "svg"; } export interface SankeyChartLabelsProps { className?: string; /** Format the figure beside a name. Defaults to a compact number. */ formatValue?: (value: number, node: SankeyNode) => string; /** Show the figure under the name. */ showValue?: boolean; /** * Hide the name on a bar shorter than this, in points. * * A diagram of forty nodes has bars a few points tall, and forty names at * that spacing overlap into a grey band that hides the flow behind it. The * names that are dropped are the smallest ones, which is where the tooltip * takes over. */ minHeight?: number; } /** * The names, and the press targets that go with them. * * Outside the bars rather than on them. A node's bar is as thick as it was * asked to be — ten points by default — and no name fits inside ten points, so * putting the name on the bar means widening every bar to suit the longest * label and losing the width the ribbons need. * * Which side a name goes on is decided by the column: the last column reads * inwards from the right edge, everything else outwards to the right. So the * names stay inside the chart's box at both ends, instead of the leftmost and * rightmost ones being clipped. * * The target is the label's row, not the bar. A node worth one percent of the * flow is a two-point sliver and cannot be hit; the row it sits in can, and it * is padded out to a proper target where the sliver is smaller than one. */ declare function SankeyChartLabels({ className, formatValue, showValue, minHeight, }: SankeyChartLabelsProps): import("react").JSX.Element | null; declare namespace SankeyChartLabels { var displayName: string; var slot: "overlay"; } export interface SankeyChartTooltipProps { className?: string; /** Format the figures. Defaults to a compact number. */ formatValue?: (value: number) => string; } /** * What the selected node carries: the total through it, and what that total is * made of at each end. * * In and out are shown separately because they are the two readings a flow * diagram is for, and they are only the same number when nothing was lost. A * node where they differ is the interesting one on the whole chart, and a * single total would hide exactly that. * * Anchored beside the node and clamped to the plot, so it never leaves the box * it belongs to — a card half off the edge of a phone is a card nobody can read. */ declare function SankeyChartTooltip({ className, formatValue }: SankeyChartTooltipProps): import("react").JSX.Element | null; declare namespace SankeyChartTooltip { var displayName: string; var slot: "overlay"; } export interface SankeyChartSkeletonProps { color?: string; } /** * The loading state: a few plain bars and the ribbons between them, carrying no * values. * * Every bar the same height and every ribbon the same thickness, deliberately. * A placeholder with varied thicknesses would be an invented routing, and a * reader cannot tell an invented one from a real one until it changes under * them — which is worse than showing nothing, because it is showing something * wrong. */ declare function SankeyChartSkeleton({ color }: SankeyChartSkeletonProps): import("react").JSX.Element | null; declare namespace SankeyChartSkeleton { var displayName: string; var slot: "svg"; } export interface SankeyChartHeaderProps extends ViewProps { className?: string; /** Small line above the value — what the flow is of. */ title?: string; /** The readout. The largest thing on the card, and the first thing read. */ value?: string; /** One muted line under the value — a period, a comparison, a caveat. */ caption?: string; /** Trailing slot — a control, a badge, a range picker. */ children?: ReactNode; } /** * The strip above the diagram: what the flow is of and what it totals. * * The value is not derived even though there are sources to add up, because the * formatting is not the chart's to guess: 128400 is a count, a currency or a * rate depending on what was routed. */ declare function SankeyChartHeader({ className, title, value, caption, children, ...props }: SankeyChartHeaderProps): import("react").JSX.Element; declare namespace SankeyChartHeader { var displayName: string; var slot: "header"; } export declare const SankeyChart: import("react").ForwardRefExoticComponent> & { Header: typeof SankeyChartHeader; Links: typeof SankeyChartLinks; Nodes: typeof SankeyChartNodes; Labels: typeof SankeyChartLabels; Tooltip: typeof SankeyChartTooltip; Skeleton: typeof SankeyChartSkeleton; }; //# sourceMappingURL=index.d.ts.map