| 1 | import { NetworkCustomChart } from "semiotic/network" |
| 2 | import { LinkedCharts, useSelection } from "semiotic" |
| 3 | import { lineageDagLayout } from "semiotic/recipes" |
| 4 | // Your domain pipeline owns layout; Semiotic only *reads* the result: |
| 5 | import { dagLayoutFromGraph, reachableFrom, subgraphFrom } from "./kstreamsPipeline" |
| 6 | |
| 7 | // The recipe stays domain-agnostic — the app supplies the glyph vocabulary. |
| 8 | const PARTITION_COLORS = { |
| 9 | "topic-source": "#1f8a70", "topic-sink": "#c0552d", |
| 10 | "topic-bridge": "#3f5e8c", processor: "#3a3a52", |
| 11 | } |
| 12 | function renderKstreamsIcon({ semantic, partition, size, color }) { |
| 13 | // topic → a log/database glyph; processor → a colored chip + semantic symbol. |
| 14 | return /* svg */ null |
| 15 | } |
| 16 | |
| 17 | function toChartData(layout) { |
| 18 | return { |
| 19 | // logical x = layer, y = row — the recipe maps these into the plot. |
| 20 | nodes: layout.nodes.map((dn) => ({ |
| 21 | id: dn.id, x: dn.x, y: dn.y, |
| 22 | partition: dn.node.partition, semantic: dn.node.semantic, |
| 23 | label: dn.node.label, stores: dn.node.stores, |
| 24 | })), |
| 25 | edges: layout.edges.map((de) => ({ |
| 26 | id: de.id, source: de.source, target: de.target, |
| 27 | edgeType: de.edgeType, isBackEdge: de.isBackEdge, |
| 28 | })), |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | function LineageViews() { |
| 33 | const [rootId, setRootId] = useState("orders") |
| 34 | const [selectedId, setSelectedId] = useState(null) |
| 35 | const [hoveredId, setHoveredId] = useState(null) |
| 36 | |
| 37 | // Shared selection store — click emits here; both charts consume it. |
| 38 | const sel = useSelection({ name: "kstreams", fields: ["id"] }) |
| 39 | |
| 40 | const main = useMemo(() => toChartData(dagLayoutFromGraph(subgraphFrom(full, rootId))), [rootId]) |
| 41 | const mini = useMemo(() => toChartData(dagLayoutFromGraph(full)), []) |
| 42 | // Hover → downstream reach set → dim everything else, in BOTH views. |
| 43 | const reachableIds = hoveredId ? [...reachableFrom(full, hoveredId)] : null |
| 44 | |
| 45 | const onHover = (obs) => setHoveredId(obs.type === "hover" ? obs.datum?.id : null) |
| 46 | const onPick = (d) => { setSelectedId(d.id); sel.selectPoints({ id: [d.id] }) } |
| 47 | |
| 48 | return ( |
| 49 | <LinkedCharts> |
| 50 | <NetworkCustomChart |
| 51 | nodes={main.nodes} edges={main.edges} layout={lineageDagLayout} |
| 52 | layoutConfig={{ |
| 53 | layerCount: ..., maxLayerSize: ..., reachableIds, selectedId, |
| 54 | renderIcon: renderKstreamsIcon, partitionColors: PARTITION_COLORS, |
| 55 | }} |
| 56 | selection={{ name: "kstreams" }} // consume the shared store → ring |
| 57 | onObservation={onHover} // hover → reach preview |
| 58 | onClick={onPick} // click → select (+ emit to store) |
| 59 | width={680} height={460} |
| 60 | /> |
| 61 | {/* A second NetworkCustomChart with lod:"dot" is the minimap, sharing the |
| 62 | same selection store — proving LinkedCharts works for custom layouts. */} |
| 63 | </LinkedCharts> |
| 64 | ) |
| 65 | } |