Skip to content

Commit

Permalink
#SDSV-5 Vertical Layout
Browse files Browse the repository at this point in the history
  • Loading branch information
jrmartin committed Jan 31, 2024
1 parent 592b0a5 commit 9bffcfd
Showing 1 changed file with 130 additions and 84 deletions.
214 changes: 130 additions & 84 deletions src/components/GraphViewer/GraphViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ const TOP_DOWN = {
return graph.hierarchyVariant;
}
};
const LEFT_RIGHT = {
label : "Vertical Layout",
layout : "lr",
maxNodesLevel : (graph) => {
return graph.hierarchyVariant;
}
};
const RADIAL_OUT = {
label : "Radial View",
layout : "null",
Expand Down Expand Up @@ -78,17 +85,109 @@ const GraphViewer = (props) => {
}
}
matchIndex = nodes.findIndex( n => n.id === conflict.id );
let furthestLeft = conflict?.xPos;
if ( nodes[i].collapsed ) {
furthestLeft = conflict.xPos - ((((matchIndex - i )/2)) * nodeSpace );
nodes[i].xPos =furthestLeft;
if ( selectedLayout.layout === TOP_DOWN.layout ) {
let furthestLeft = conflict?.xPos;
if ( nodes[i].collapsed ) {
furthestLeft = conflict.xPos - ((((matchIndex - i )/2)) * nodeSpace );
nodes[i].xPos =furthestLeft;
}
positionsMap[level] = furthestLeft + nodeSpace;
nodes[i].fx = nodes[i].xPos;
nodes[i].fy = 50 * nodes[i].level;
} else if ( selectedLayout.layout === LEFT_RIGHT.layout ) {
let furthestLeft = conflict?.yPos;
if ( nodes[i].collapsed ) {
furthestLeft = conflict.yPos - ((((matchIndex - i )/2)) * nodeSpace );
nodes[i].yPos =furthestLeft;
}
positionsMap[level] = furthestLeft + nodeSpace;
nodes[i].fy = nodes[i].yPos;
nodes[i].fx = 50 * nodes[i].level;
}
positionsMap[level] = furthestLeft + nodeSpace;
nodes[i].fx = nodes[i].xPos;
nodes[i].fy = 50 * nodes[i].level;
}
}

const algorithm = (levels, furthestLeft) => {
let positionsMap = {};
let levelsMapKeys = Object.keys(levels);

levelsMapKeys.forEach( level => {
furthestLeft = 0 - (Math.ceil(levels[level].length)/2 * nodeSpace );
positionsMap[level] = furthestLeft + nodeSpace;
levels[level]?.sort( (a, b) => {
if (a?.id < b?.id) return -1;
else return 1;
});
});

// Start assigning the graph from the bottom up
let neighbors = 0;
levelsMapKeys.reverse().forEach( level => {
let collapsedInLevel = levels[level].filter( n => n.collapsed);
let notcollapsedInLevel = levels[level].filter( n => !n.collapsed);
levels[level].forEach ( (n, index) => {
neighbors = n?.neighbors?.filter(neighbor => { return neighbor.level > n.level });
if ( !n.collapsed ) {
if ( neighbors?.length > 0 ) {
let max = Number.MIN_SAFE_INTEGER, min = Number.MAX_SAFE_INTEGER;
neighbors.forEach( neighbor => {
if ( selectedLayout.layout === TOP_DOWN.layout ) {
if ( neighbor.xPos > max ) { max = neighbor.xPos };
if ( neighbor.xPos <= min ) { min = neighbor.xPos };
} else if ( selectedLayout.layout === LEFT_RIGHT.layout ) {
if ( neighbor.yPos > max ) { max = neighbor.yPos };
if ( neighbor.yPos <= min ) { min = neighbor.yPos };
}
});
if ( selectedLayout.layout === TOP_DOWN.layout ) {
n.xPos = min === max ? min : min + ((max - min) * .5);
} else if ( selectedLayout.layout === LEFT_RIGHT.layout ) {
n.yPos = min === max ? min : min + ((max - min) * .5);
}
positionsMap[n.level] = n.yPos + nodeSpace;
if ( notcollapsedInLevel?.length > 0 && collapsedInLevel.length > 0) {
updateNodes(levels[level], n, positionsMap, level, index);
}

if ( selectedLayout.layout === TOP_DOWN.layout ) {
positionsMap[n.level] = n.xPos + nodeSpace;
n.fx = n.xPos;
n.fy = 50 * n.level;
} else if ( selectedLayout.layout === LEFT_RIGHT.layout ) {
positionsMap[n.level] = n.yPos + nodeSpace;
n.fy = n.yPos;
n.fx = 50 * n.level;
}
} else {
if ( selectedLayout.layout === TOP_DOWN.layout ) {
n.xPos = positionsMap[n.level] + nodeSpace;
positionsMap[n.level] = n.xPos;
n.fx = n.xPos;
n.fy = 50 * n.level;
} else if ( selectedLayout.layout === LEFT_RIGHT.layout ) {
n.yPos = positionsMap[n.level] + nodeSpace;
positionsMap[n.level] = n.yPos;
n.fy = n.yPos;
n.fx = 50 * n.level;
}
}
}else {
if ( selectedLayout.layout === TOP_DOWN.layout ) {
n.xPos = positionsMap[n.level] + nodeSpace;
positionsMap[n.level] = n.xPos;
n.fx = n.xPos;
n.fy = 50 * n.level;
} else if ( selectedLayout.layout === LEFT_RIGHT.layout ) {
n.yPos = positionsMap[n.level] + nodeSpace;
positionsMap[n.level] = n.yPos;
n.fy = n.yPos;
n.fx = 50 * n.level;
}
}
})
});
}

const getPrunedTree = () => {
let nodesById = Object.fromEntries(window.datasets[props.graph_id].graph?.nodes?.map(node => [node.id, node]));
window.datasets[props.graph_id].graph?.links?.forEach(link => {
Expand All @@ -107,7 +206,6 @@ const GraphViewer = (props) => {

let levelsMap = window.datasets[props.graph_id].graph.levelsMap;
// // Calculate level with max amount of nodes
let maxLevel = Object.keys(levelsMap).reduce((a, b) => levelsMap[a].filter( l => !l.collapsed ).length > levelsMap[b].filter( l => !l.collapsed ).length ? a : b);

(function traverseTree(node = nodesById[window.datasets[props.graph_id].graph?.nodes?.[0].id]) {
visibleNodes.push(node);
Expand All @@ -118,80 +216,24 @@ const GraphViewer = (props) => {
nodes?.forEach(traverseTree);
})(); // IIFE

if ( selectedLayout.layout === TOP_DOWN.layout ){
let levels = {};
visibleNodes.forEach( n => {
if ( levels[n.level] ){
levels[n.level].push(n);
} else {
levels[n.level] = [n];
}
})
let levels = {};
visibleNodes.forEach( n => {
if ( levels[n.level] ){
levels[n.level].push(n);
} else {
levels[n.level] = [n];
}
})

// Calculate level with max amount of nodes
let highestLevel = Object.keys(levels).length;
let maxLevel = Object.keys(levels).reduce((a, b) => levels[a].length > levels[b].length ? a : b);
let maxLevelNodes = levels[maxLevel];
// Calculate level with max amount of nodes
let maxLevel = Object.keys(levels).reduce((a, b) => levels[a].length > levels[b].length ? a : b);
let maxLevelNodes = levels[maxLevel];

// Space between nodes
// The furthestLeft a node can be
let furthestLeft = 0 - (Math.ceil(maxLevelNodes.length)/2 * nodeSpace );
let positionsMap = {};
// Space between nodes
// The furthestLeft a node can be
let furthestLeft = 0 - (Math.ceil(maxLevelNodes.length)/2 * nodeSpace );

let levelsMapKeys = Object.keys(levels);

levelsMapKeys.forEach( level => {
furthestLeft = 0 - (Math.ceil(levels[level].length)/2 * nodeSpace );
positionsMap[level] = furthestLeft + nodeSpace;
levels[level]?.sort( (a, b) => {
if (a?.id < b?.id) {
return -1;
}
if (a?.id > b?.id) {
return 1;
}
return 1;
});
});

// Start assigning the graph from the bottom up
let neighbors = 0;
levelsMapKeys.reverse().forEach( level => {
let collapsedInLevel = levels[level].filter( n => n.collapsed);
let notcollapsedInLevel = levels[level].filter( n => !n.collapsed);
levels[level].forEach ( (n, index) => {
neighbors = n?.neighbors?.filter(neighbor => { return neighbor.level > n.level });
if ( !n.collapsed ) {
if ( neighbors?.length > 0 ) {
let max = Number.MIN_SAFE_INTEGER, min = Number.MAX_SAFE_INTEGER;
neighbors.forEach( neighbor => {
if ( neighbor.xPos > max ) { max = neighbor.xPos };
if ( neighbor.xPos <= min ) { min = neighbor.xPos };
});
n.xPos = min === max ? min : min + ((max - min) * .5);
positionsMap[n.level] = n.xPos + nodeSpace;
if ( notcollapsedInLevel?.length > 0 && collapsedInLevel.length > 0) {
updateNodes(levels[level], n, positionsMap, level, index);
}
positionsMap[n.level] = n.xPos + nodeSpace;
n.fx = n.xPos;
n.fy = 50 * n.level;
} else {
n.xPos = positionsMap[n.level] + nodeSpace;
positionsMap[n.level] = n.xPos;
n.fx = n.xPos;
n.fy = 50 * n.level;

}
}else {
n.xPos = positionsMap[n.level] + nodeSpace;
positionsMap[n.level] = n.xPos ;
n.fx = n.xPos;
n.fy = 50 * n.level;
}
})
});
}
algorithm(levels, furthestLeft);

const graph = { nodes : visibleNodes, links : visibleLinks, levelsMap : levelsMap, hierarchyVariant : maxLevel * 20 };
return graph;
Expand All @@ -202,7 +244,7 @@ const GraphViewer = (props) => {
const [selectedNode, setSelectedNode] = useState(null);
const [highlightNodes, setHighlightNodes] = useState(new Set());
const [highlightLinks, setHighlightLinks] = useState(new Set());
const [selectedLayout, setSelectedLayout] = React.useState(RADIAL_OUT);
const [selectedLayout, setSelectedLayout] = React.useState(TOP_DOWN);
const [layoutAnchorEl, setLayoutAnchorEl] = React.useState(null);
const [cameraPosition, setCameraPosition] = useState({ x : 0 , y : 0 });
const open = Boolean(layoutAnchorEl);
Expand Down Expand Up @@ -318,7 +360,7 @@ const GraphViewer = (props) => {
};

const setForce = () => {
if ( selectedLayout.layout !== TOP_DOWN.layout ){
if ( selectedLayout.layout !== TOP_DOWN.layout || selectedLayout.layout !== LEFT_RIGHT.layout ){
let force = -100;
graphRef?.current?.ggv?.current.d3Force('link').distance(0).strength(1);
graphRef?.current?.ggv?.current.d3Force("charge").strength(force * 2);
Expand Down Expand Up @@ -543,14 +585,14 @@ const GraphViewer = (props) => {
data={data}
// Create the Graph as 2 Dimensional
d2={true}
cooldownTicks={selectedLayout.layout === TOP_DOWN.layout ? 0 : data?.nodes?.length}
cooldownTicks={ ( selectedLayout.layout === TOP_DOWN.layout || selectedLayout.layout === LEFT_RIGHT.layout) ? 0 : data?.nodes?.length }
onEngineStop={onEngineStop}
// Links properties
linkColor = {handleLinkColor}
linkWidth={2}
dagLevelDistance={selectedLayout.layout === TOP_DOWN.layout ? 60 : 0}
dagLevelDistance={( selectedLayout.layout !== TOP_DOWN.layout && selectedLayout.layout !== LEFT_RIGHT.layout ) ? 0 : 60}
linkDirectionalParticles={1}
forceRadial={selectedLayout.layout === TOP_DOWN.layout ? 0 : 15}
forceRadial={( selectedLayout.layout !== TOP_DOWN.layout && selectedLayout.layout !== LEFT_RIGHT.layout ) ? 15 : 0}
linkDirectionalParticleWidth={link => highlightLinks.has(link) ? 4 : 0}
linkCanvasObjectMode={'replace'}
onLinkHover={handleLinkHover}
Expand All @@ -561,6 +603,9 @@ const GraphViewer = (props) => {
if ( selectedLayout.layout === TOP_DOWN.layout ){
node.fx = node.xPos;
node.fy = 50 * node.level;
} else if ( selectedLayout.layout === LEFT_RIGHT.layout ){
node.fx = 50 * node.level;
node.fy = node.yPos;
}
return 100 / (node.level + 1);
}}
Expand Down Expand Up @@ -596,6 +641,7 @@ const GraphViewer = (props) => {
>
<MenuItem selected={RADIAL_OUT.layout === selectedLayout.layout} onClick={() => handleLayoutChange(RADIAL_OUT)}>{RADIAL_OUT.label}</MenuItem>
<MenuItem selected={TOP_DOWN.layout === selectedLayout.layout} onClick={() => handleLayoutChange(TOP_DOWN)}>{TOP_DOWN.label}</MenuItem>
<MenuItem selected={LEFT_RIGHT.layout === selectedLayout.layout} onClick={() => handleLayoutChange(LEFT_RIGHT)}>{LEFT_RIGHT.label}</MenuItem>
</Menu>
<IconButton area-label="ZoomIn" onClick={(e) => zoomIn()}>
<Tooltip id="button-report" title="Zoom In">
Expand Down

0 comments on commit 9bffcfd

Please sign in to comment.