mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
* sankey * sanki2 * numine data example * remove showcase * address PR review comments for sankey chart - move @types/d3-sankey to devDependencies - use targeted d3-selection/d3-transition imports in SankeyLink - add explicit return type to useSankeyController - extract LABEL_LINE_HEIGHT/LABEL_LINE_GAP module-level constants - add readonly modifiers to SankeyNodeDatum, SankeyLinkDatum, SankeyData - add comment explaining d3-sankey runtime type assertions in SankeyNode Co-authored-by: Hanzo Dev <[email protected]> * address PR review comments for sankey chart Co-authored-by: Hanzo Dev <[email protected]> * review fixes --------- Co-authored-by: Hanzo Dev <[email protected]>
72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
import { sankey, sankeyLinkHorizontal, sankeyJustify } from 'd3-sankey';
|
|
import React from 'react';
|
|
|
|
import type { ChartMargin } from '../types';
|
|
import type { SankeyData, SankeyLinkExtended, SankeyNodeExtended } from './types';
|
|
|
|
import { calculateInnerSize } from 'toolkit/components/charts/utils/calculateInnerSize';
|
|
import { useClientRect } from 'toolkit/hooks/useClientRect';
|
|
|
|
import { DEFAULT_SANKEY_NODE_PADDING, DEFAULT_SANKEY_NODE_WIDTH } from './constants';
|
|
|
|
interface Props {
|
|
data: SankeyData;
|
|
margin?: ChartMargin;
|
|
nodeWidth?: number;
|
|
nodePadding?: number;
|
|
}
|
|
|
|
interface SankeyLayout {
|
|
nodes: Array<SankeyNodeExtended>;
|
|
links: Array<SankeyLinkExtended>;
|
|
}
|
|
|
|
interface UseSankeyControllerResult {
|
|
ref: React.Ref<SVGSVGElement> | undefined;
|
|
readonly rect: DOMRect | null;
|
|
readonly innerWidth: number;
|
|
readonly innerHeight: number;
|
|
readonly nodes: Array<SankeyNodeExtended>;
|
|
readonly links: Array<SankeyLinkExtended>;
|
|
readonly linkPathGenerator: (link: SankeyLinkExtended) => string | null;
|
|
}
|
|
|
|
export function useSankeyController({ data, margin, nodeWidth, nodePadding }: Props): UseSankeyControllerResult {
|
|
const [ rect, ref ] = useClientRect<SVGSVGElement>();
|
|
|
|
const { innerWidth, innerHeight } = calculateInnerSize(rect, margin);
|
|
|
|
const layout: SankeyLayout = React.useMemo(() => {
|
|
if (innerWidth <= 0 || innerHeight <= 0) {
|
|
return { nodes: [], links: [] };
|
|
}
|
|
|
|
const sankeyGenerator = sankey<SankeyData['nodes'][number], SankeyData['links'][number]>()
|
|
.nodeId((d) => d.id)
|
|
.nodeWidth(nodeWidth ?? DEFAULT_SANKEY_NODE_WIDTH)
|
|
.nodePadding(nodePadding ?? DEFAULT_SANKEY_NODE_PADDING)
|
|
.nodeAlign(sankeyJustify)
|
|
.extent([ [ 0, 0 ], [ innerWidth, innerHeight ] ]);
|
|
|
|
const { nodes, links } = sankeyGenerator({
|
|
nodes: data.nodes.map((d) => ({ ...d })),
|
|
links: data.links.map((d) => ({ ...d })),
|
|
});
|
|
|
|
// After layout, source/target on each link are resolved node objects, not ids.
|
|
return { nodes: nodes as Array<SankeyNodeExtended>, links: links as unknown as Array<SankeyLinkExtended> };
|
|
}, [ data, innerWidth, innerHeight, nodeWidth, nodePadding ]);
|
|
|
|
const linkPathGenerator: (link: SankeyLinkExtended) => string | null = React.useMemo(() => sankeyLinkHorizontal(), []);
|
|
|
|
return React.useMemo(() => ({
|
|
ref,
|
|
rect,
|
|
innerWidth,
|
|
innerHeight,
|
|
nodes: layout.nodes,
|
|
links: layout.links,
|
|
linkPathGenerator,
|
|
}), [ ref, rect, innerWidth, innerHeight, layout, linkPathGenerator ]);
|
|
}
|