mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-20 02:38:53 +00:00
Fix sankey diagram passthrough ordering (#28012)
This commit is contained in:
@@ -14,6 +14,8 @@ interface PassThroughNode {
|
||||
id: string;
|
||||
value: number;
|
||||
depth: number;
|
||||
sourceId: string;
|
||||
targetId: string;
|
||||
}
|
||||
|
||||
interface GraphLink extends GraphEdge {
|
||||
@@ -152,6 +154,8 @@ export function createPassThroughNode(
|
||||
id: `${sourceId}-${targetId}-${depth}`,
|
||||
value,
|
||||
depth,
|
||||
sourceId,
|
||||
targetId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -237,6 +241,79 @@ export function groupNodesBySection(
|
||||
return nodesPerSection;
|
||||
}
|
||||
|
||||
export function sortNodesInSections(
|
||||
nodesPerSection: Record<number, Node[]>,
|
||||
depths: number[]
|
||||
): Record<number, Node[]> {
|
||||
const sortedSections: Record<number, Node[]> = {};
|
||||
|
||||
depths.forEach((depth, depthIndex) => {
|
||||
const sectionNodes = nodesPerSection[depth] || [];
|
||||
|
||||
// Sort nodes to minimize crossings
|
||||
const sortedNodes = [...sectionNodes].sort((a, b) => {
|
||||
const aIsPassthrough = isPassThroughNode(a);
|
||||
const bIsPassthrough = isPassThroughNode(b);
|
||||
|
||||
// Both are passthrough nodes - sort by source position
|
||||
if (aIsPassthrough && bIsPassthrough) {
|
||||
// Find positions of source nodes in previous section (use already sorted section)
|
||||
if (depthIndex > 0) {
|
||||
const prevDepth = depths[depthIndex - 1];
|
||||
const prevSection =
|
||||
sortedSections[prevDepth] || nodesPerSection[prevDepth] || [];
|
||||
|
||||
const aSourceIndex = prevSection.findIndex((n) => {
|
||||
const nodeId = isPassThroughNode(n) ? n.id : (n as GraphNode).id;
|
||||
return nodeId === a.sourceId;
|
||||
});
|
||||
const bSourceIndex = prevSection.findIndex((n) => {
|
||||
const nodeId = isPassThroughNode(n) ? n.id : (n as GraphNode).id;
|
||||
return nodeId === b.sourceId;
|
||||
});
|
||||
|
||||
if (
|
||||
aSourceIndex !== bSourceIndex &&
|
||||
aSourceIndex !== -1 &&
|
||||
bSourceIndex !== -1
|
||||
) {
|
||||
return aSourceIndex - bSourceIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to target node positions in next section (not sorted yet, use original)
|
||||
if (depthIndex < depths.length - 1) {
|
||||
const nextDepth = depths[depthIndex + 1];
|
||||
const nextSection = nodesPerSection[nextDepth] || [];
|
||||
|
||||
const aTargetIndex = nextSection.findIndex((n) => {
|
||||
const nodeId = isPassThroughNode(n) ? n.id : (n as GraphNode).id;
|
||||
return nodeId === a.targetId;
|
||||
});
|
||||
const bTargetIndex = nextSection.findIndex((n) => {
|
||||
const nodeId = isPassThroughNode(n) ? n.id : (n as GraphNode).id;
|
||||
return nodeId === b.targetId;
|
||||
});
|
||||
|
||||
if (
|
||||
aTargetIndex !== bTargetIndex &&
|
||||
aTargetIndex !== -1 &&
|
||||
bTargetIndex !== -1
|
||||
) {
|
||||
return aTargetIndex - bTargetIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
sortedSections[depth] = sortedNodes;
|
||||
});
|
||||
|
||||
return sortedSections;
|
||||
}
|
||||
|
||||
export function createSectionNodes(nodes: Node[]): SectionNode[] {
|
||||
return nodes.map(
|
||||
(node: Node): SectionNode => ({
|
||||
@@ -337,10 +414,11 @@ function processNodes(
|
||||
);
|
||||
|
||||
const nodesPerSection = groupNodesBySection(nodes, passThroughNodes);
|
||||
const sortedNodesPerSection = sortNodesInSections(nodesPerSection, depths);
|
||||
let globalValueToSizeRatio = 0;
|
||||
|
||||
const sections = depths.map((depth) => {
|
||||
const sectionNodes = createSectionNodes(nodesPerSection[depth] || []);
|
||||
const sectionNodes = createSectionNodes(sortedNodesPerSection[depth] || []);
|
||||
const availableSpace = sectionSize - (sectionNodes.length + 1) * nodeGap;
|
||||
const totalValue = sectionNodes.reduce(
|
||||
(acc: number, node: SectionNode) => acc + node.value,
|
||||
|
||||
@@ -67,6 +67,8 @@ describe("Sankey Layout Functions", () => {
|
||||
id: "test",
|
||||
value: 10,
|
||||
depth: 1,
|
||||
sourceId: "source",
|
||||
targetId: "target",
|
||||
};
|
||||
expect(isPassThroughNode(passThroughNode)).toBe(true);
|
||||
});
|
||||
@@ -142,7 +144,14 @@ describe("Sankey Layout Functions", () => {
|
||||
];
|
||||
|
||||
const passThroughNodes = [
|
||||
{ id: "pt1", depth: 1, passThrough: true, value: 5 },
|
||||
{
|
||||
id: "pt1",
|
||||
depth: 1,
|
||||
passThrough: true,
|
||||
value: 5,
|
||||
sourceId: "node1",
|
||||
targetId: "node2",
|
||||
},
|
||||
];
|
||||
|
||||
const result = groupNodesBySection(
|
||||
@@ -195,6 +204,8 @@ describe("Sankey Layout Functions", () => {
|
||||
passThrough: true,
|
||||
value: 5,
|
||||
depth: 1,
|
||||
sourceId: "source",
|
||||
targetId: "target",
|
||||
};
|
||||
|
||||
const result = createSectionNodes([passThroughNode]);
|
||||
@@ -316,13 +327,15 @@ describe("Sankey Layout Functions", () => {
|
||||
|
||||
describe("createPassThroughNode", () => {
|
||||
it("should create a pass-through node", () => {
|
||||
const result = createPassThroughNode("source-target", "section1", 2, 15);
|
||||
const result = createPassThroughNode("source", "target", 2, 15);
|
||||
|
||||
expect(result).toEqual({
|
||||
passThrough: true,
|
||||
id: "source-target-section1-2",
|
||||
id: "source-target-2",
|
||||
value: 15,
|
||||
depth: 2,
|
||||
sourceId: "source",
|
||||
targetId: "target",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user