"use client";
import * as React from "react";
import { cn } from "./lib/utils";
/**
* SankeyChart - two-level flow diagram with bezier ribbons
*/
interface SankeyNode {
id: string;
label: string;
side: "left" | "right";
}
interface SankeyFlow {
from: string;
to: string;
value: number;
}
interface SankeyChartProps {
nodes: SankeyNode[];
flows: SankeyFlow[];
}
const Ribbon = ({
from,
to,
value,
}: { from: { x: number; y: number }; to: { x: number; y: number }; value: number }) => {
const width = Math.max(3, value * 20); // scale value to pixel width
const amperage = Math.max(width * 4, 40);
// Cubic bezier curve points
// from bottom-right to to bottom-left with control points
const d = `M ${from.x} ${from.y}
C ${from.x + amperage} ${from.y},
${to.x - amperage} ${to.y},
${to.x} ${to.y}`;
return (
);
};
export const SankeyChart = React.forwardRef(
function SankeyChart(props, ref) {
const { nodes, flows } = props;
// Separate left and right nodes
const leftNodes = nodes.filter(n => n.side === "left");
const rightNodes = nodes.filter(n => n.side === "right");
// Create lookup maps
const leftNodeMap = new Map(leftNodes.map(n => [n.id, n]));
const rightNodeMap = new Map(rightNodes.map(n => [n.id, n]));
// Calculate node positions
const nodeCount = Math.max(leftNodes.length, rightNodes.length);
const nodeSpacing = 80;
const diagramWidth = 600;
const diagramHeight = (nodeCount + 1) * nodeSpacing;
// Position left nodes vertically
const leftPositions = new Map(
leftNodes.map((n, i) => [n.id, { x: 50, y: 50 + i * nodeSpacing }]),
);
// Position right nodes vertically
const rightPositions = new Map(
rightNodes.map((n, i) => [n.id, { x: diagramWidth - 50, y: 50 + i * nodeSpacing }]),
);
// Sort flows by value for rendering order
const sortedFlows = [...flows].sort((a, b) => b.value - a.value);
return (
);
},
);
SankeyChart.displayName = "SankeyChart";