| 1 | import { useMemo, useState } from "react" |
| 2 | import { NetworkCustomChart } from "semiotic/network" |
| 3 | import { adjacencyFlowLayout, aggregateAdjacencyFlow } from "semiotic/recipes" |
| 4 | |
| 5 | const nodes = [ |
| 6 | { id: "A", label: "A", group: "ABC" }, |
| 7 | { id: "B", label: "B", group: "ABC" }, |
| 8 | { id: "C", label: "C", group: "ABC" }, |
| 9 | { id: "D", label: "D", group: "DEF" }, |
| 10 | { id: "E", label: "E", group: "DEF" }, |
| 11 | { id: "F", label: "F", group: "DEF" }, |
| 12 | { id: "G", label: "G", group: "GHI" }, |
| 13 | { id: "H", label: "H", group: "GHI" }, |
| 14 | { id: "I", label: "I", group: "GHI" }, |
| 15 | ] |
| 16 | |
| 17 | const edges = [ |
| 18 | { source: "A", target: "B", value: 10 }, |
| 19 | { source: "B", target: "C", value: 25 }, |
| 20 | { source: "C", target: "A", value: 3 }, |
| 21 | { source: "C", target: "D", value: 8 }, |
| 22 | { source: "D", target: "E", value: 7 }, |
| 23 | { source: "E", target: "F", value: 12 }, |
| 24 | { source: "F", target: "D", value: 2 }, |
| 25 | { source: "F", target: "G", value: 9 }, |
| 26 | { source: "G", target: "H", value: 22 }, |
| 27 | { source: "H", target: "I", value: 14 }, |
| 28 | { source: "I", target: "G", value: 5 }, |
| 29 | ] |
| 30 | |
| 31 | export default function AdjacencyFlowExample() { |
| 32 | const [expandedGroups, setExpandedGroups] = useState( |
| 33 | () => new Set(["ABC", "DEF", "GHI"]) |
| 34 | ) |
| 35 | const { nodes: visibleNodes, edges: visibleEdges } = useMemo( |
| 36 | () => aggregateAdjacencyFlow(nodes, edges, { |
| 37 | groupAccessor: "group", |
| 38 | expandedGroups, |
| 39 | includeInternalFlows: true, |
| 40 | }), |
| 41 | [expandedGroups] |
| 42 | ) |
| 43 | const toggleGroup = (group) => { |
| 44 | if (!group) return |
| 45 | setExpandedGroups((current) => { |
| 46 | const next = new Set(current) |
| 47 | if (next.has(group)) next.delete(group) |
| 48 | else next.add(group) |
| 49 | return next |
| 50 | }) |
| 51 | } |
| 52 | |
| 53 | return ( |
| 54 | <NetworkCustomChart |
| 55 | nodes={visibleNodes} |
| 56 | edges={visibleEdges} |
| 57 | nodeIDAccessor="id" |
| 58 | sourceAccessor="source" |
| 59 | targetAccessor="target" |
| 60 | layout={adjacencyFlowLayout} |
| 61 | layoutConfig={{ |
| 62 | showArrows: true, |
| 63 | showValues: true, |
| 64 | colorMode: "single", |
| 65 | maxCellSize: 92, |
| 66 | cornerRadius: 9, |
| 67 | }} |
| 68 | style={{ |
| 69 | "--semiotic-adjacency-flow-arrow-fill": "rgba(255, 255, 255, 0.72)", |
| 70 | }} |
| 71 | tooltip={(d) => d.source |
| 72 | ? <strong>{d.source} → {d.target}: {d.value}</strong> |
| 73 | : <strong>{d.label}</strong>} |
| 74 | onClick={(d) => toggleGroup(d.group)} |
| 75 | title="Adjacency flow journey" |
| 76 | description="Ordered weighted flows; forward routes are upper-right and reversals lower-left." |
| 77 | summary="Three phases dominate, with backtracking concentrated in the middle phase." |
| 78 | accessibleTable |
| 79 | width={900} |
| 80 | height={700} |
| 81 | /> |
| 82 | ) |
| 83 | } |
| 84 | |