Skip to content

Commit

Permalink
Update focus order in sankey chart for vertical navigation (microsoft…
Browse files Browse the repository at this point in the history
…#31469)

Co-authored-by: Shubhabrata Ghosh <shubhabrata08@gmail.com>
  • Loading branch information
2 people authored and miroslavstastny committed Jun 14, 2024
1 parent cda70c3 commit d7558f2
Show file tree
Hide file tree
Showing 4 changed files with 6,505 additions and 4,037 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Update focus order in sankey chart for vertical navigation",
"packageName": "@fluentui/react-charting",
"email": "kumarkshitij@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,46 @@ export class SankeyChartBase extends React.Component<ISankeyChartProps, ISankeyC
borderColorsForNodes,
);

// In FocusZone, the focus order is determined by the rendering order of the elements. We need to find
// a rendering order such that the focus moves through the nodes and links in a logical sequence.
// Rendering the nodes and links layer by layer in a vertical order seems to be the most effective solution
// with FocusZone. Although this focus order may not be entirely logical, it ensures that the focus moves
// sequentially and prevents links (especially skip layer links) from being rendered over the nodes.
const nodeLinkDomOrderArray: { layer: number; type: string; index: number }[] = [];
nodes.sort((a: SNode, b: SNode) => {
if (a.x0 !== b.x0) {
return a.x0! - b.x0!;
}
return a.y0! - b.y0!;
});
nodes.forEach((item: SNode, index) => {
nodeLinkDomOrderArray.push({ layer: item.layer!, type: 'node', index: index });
});
links.sort((a: SLink, b: SLink) => {
const asx0 = (a.source as SNode).x0;
const bsx0 = (b.source as SNode).x0;
if (asx0 !== bsx0) {
return asx0! - bsx0!;
}
return a.y0! - b.y0!;
});
links.forEach((item: SLink, index) => {
nodeLinkDomOrderArray.push({ layer: (item.source as SNode).layer!, type: 'link', index: index });
});
nodeLinkDomOrderArray.sort((a, b) => {
if (a.layer !== b.layer) {
return a.layer - b.layer;
}

if (a.type > b.type) {
return -1;
}
if (a.type < b.type) {
return 1;
}
return 0;
});

// NOTE: I don't love this approach to caching the "select" result. Is it still valid from render-to-render?
// although local testing seems to indicate so, I do not trust that React will always support that instance.
// It might be better to perform this `fetch` within the `_showTooltip` and `_hideTooltip` methods.
Expand Down Expand Up @@ -792,12 +832,35 @@ export class SankeyChartBase extends React.Component<ISankeyChartProps, ISankeyC
ref={(rootElem: HTMLDivElement) => (this.chartContainer = rootElem)}
onMouseLeave={this._onCloseCallout}
>
<FocusZone direction={FocusZoneDirection.bidirectional}>
{/*
- Horizontal navigation has been disabled because the nodes and links are rendered vertically,
causing the left/right arrow keys to move focus up or down to the previous or next sibling element.
- Bidirectional navigation has been disabled because it causes the up/down arrow keys to move the focus
in a non-sequential and erratic manner within a 2D grid.
*/}
<FocusZone direction={FocusZoneDirection.vertical}>
<svg width={width} height={height} id={this._chartId}>
<g className={classNames.links} strokeOpacity={1}>
{linkData}
</g>
<g className={classNames.nodes}>{nodeData}</g>
{nodeLinkDomOrderArray.map(item => {
if (item.type === 'node') {
return (
<g key={nodes[item.index].nodeId} className={classNames.nodes}>
{nodeData![item.index]}
</g>
);
} else {
return (
<g
key={`${(links[item.index].source as SNode).nodeId}-${
(links[item.index].target as SNode).nodeId
}`}
className={classNames.links}
strokeOpacity={1}
>
{linkData![item.index]}
</g>
);
}
})}
{calloutProps.isCalloutVisible && (
<Callout {...calloutProps}>
<ChartHoverCard
Expand Down
Loading

0 comments on commit d7558f2

Please sign in to comment.