Skip to content

Commit

Permalink
Graph of Graph
Browse files Browse the repository at this point in the history
  • Loading branch information
joewood committed Feb 4, 2022
1 parent 98d3dcf commit e0d9cd2
Show file tree
Hide file tree
Showing 20 changed files with 1,499 additions and 574 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cSpell.words": [
"Catmull",
"chakra",
"cubehelix",
"dagre",
"drei",
"edgesep",
Expand All @@ -20,5 +21,5 @@
"visx",
"webm",
"Wireframe"
]
],
}
82 changes: 58 additions & 24 deletions example/src/data.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GraphEdge, GraphNode } from "@diagrams/graph";
import { uniq } from "lodash";
import { SimpleEdge, SimpleNode, HierarchicalNode } from "@diagrams/graph";
import { isArray, uniq } from "lodash";

interface Tree {
[index: string]: Tree | string[];
Expand Down Expand Up @@ -28,34 +28,68 @@ const nodeTree = {
},
} as Tree;

function red(t: Tree, parent: string | null): GraphNode[] {
return Object.keys(t).reduce<GraphNode[]>((p, c) => {
const e = t[c];
const parentNode: GraphNode = { name: c, parent };
if (Array.isArray(e)) {
const children = e.map<GraphNode>((child) => ({ name: child, parent: c }));
return [...p, parentNode, ...children];
} else {
const children = red(e, c);
return [...p, parentNode, ...children];
}
}, []);
const styleMap: Record<string, Omit<SimpleNode, "name" | "positionHint" | "type" | "size" | "parent">> = {
Compute: {
backgroundColor: "yellow",
},
Network: {
backgroundColor: "#9090f0",
},
Data: {
backgroundColor: "#90f090",
},
};

// function treeToNodeArray(tree: Tree) {
// return Object.keys(tree).flatMap((node) => branchToNodeArray(Array.isArray(tree[node]) ? null : tree[node], node));
// }

/**
* When parentNode is null, just add child branches.
* Or sub-branch of tree, parentNode is the node created and added
* Or string leaf branch, return node
*/
function branchToNodeArray(
tree: Tree | string[] | null,
branchName?: string,
parentNode?: HierarchicalNode
): HierarchicalNode[] {
const node: HierarchicalNode | undefined =
(branchName && {
...(parentNode ?? {}),
...(styleMap[branchName] ?? {}),
name: branchName!,
parent: parentNode?.name ?? null,
}) ||
undefined;
// is leaf node then branchName is a node, just return the leaf node
if (tree === null) return [node!];
if (isArray(tree)) return [node!, ...tree.flatMap((leaf) => branchToNodeArray(null, leaf, node))];
// otherwise we're in a tree
const t = [
node!,
...Object.entries(tree || {}).flatMap(([childBranchName, childBranch]) =>
branchToNodeArray(childBranch, childBranchName, node)
),
].filter(Boolean);
return t;
}

export const edges: GraphEdge[] = [
{ from: "Compute on Demand", to: "Virtual Compute & Containers", label: "Uses" },
{ from: "Virtual Compute & Containers", to: "Physical Compute", label: "Uses" },
{ from: "Database", to: "Virtual Compute & Containers", label: "Uses" },
{ from: "Database", to: "Load Balancing", label: "Uses" },
export const edges: SimpleEdge[] = [
{ from: "Compute on Demand", to: "Virtual Compute & Containers", label: "Comp Uses VMs" },
{ from: "Virtual Compute & Containers", to: "Physical Compute", label: "VMs on HyperVisor" },
{ from: "Database", to: "Virtual Compute & Containers", label: "DBs on VMs" },
{ from: "Database", to: "Load Balancing", label: "DB Scale using LB" },
];
const connectedNodes = edges.reduce<string[]>((p, c) => [...p, c.from, c.to], []);

export const nodesL3: GraphNode[] = red(nodeTree, null);
export const nodesL3: HierarchicalNode[] = branchToNodeArray(nodeTree);
const roots = nodesL3.filter((node) => !node.parent).map((node) => node.name);
const parents = uniq(nodesL3.map((node) => node.parent).filter(Boolean));
export const nodesL2: GraphNode[] = red(nodeTree, null)
export const nodesL2: HierarchicalNode[] = branchToNodeArray(nodeTree)
.filter((node) => !!node.parent)
.map((node) => ({ ...node, parent: node.parent && roots.includes(node.parent) ? null : node.parent }));
export const nodesLeaf = red(nodeTree, null).filter(
(n) => !parents.includes(n.name) && connectedNodes.includes(n.name)
);
export const nodesLeaf: SimpleNode[] = branchToNodeArray(nodeTree)
.filter((n) => !parents.includes(n.name) && connectedNodes.includes(n.name))
.map((n, i) => ({ ...n, size: { width: ((i * 20) % 100) + 100, height: ((i * 20) % 100) + 100 } }));
console.log("SIMPLE", nodesLeaf);
76 changes: 56 additions & 20 deletions example/src/demo-graph2D.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,76 @@
import { Box } from "@chakra-ui/react";
import { Graph, GraphProps, useNgraph } from "@diagrams/graph";
import { ExpandableGraph, GraphOptions, SimpleGraph } from "@diagrams/graph";
import { useDefaultOptions } from "@diagrams/graph/lib/use-ngraph-simple";
import * as React from "react";
import { FC, useState } from "react";
import { edges, nodesL3, nodesL2, nodesLeaf } from "./data";
import { FC, useCallback, useMemo, useState } from "react";
import { edges, nodesL2, nodesLeaf } from "./data";

export const DemoGraphSimple: FC<{ options: GraphProps["options"] }> = ({ options }) => {
const graph = useNgraph(nodesLeaf, edges, null, { iterations: 500 });
export const DemoGraphSimple: FC<{
options: GraphOptions;
}> = ({ options: _options }) => {
const [selected, setSelected] = useState<string>();
const options = useDefaultOptions(_options);
const expand = useCallback(({ name }: { name: string }) => {
console.log("Name", name);
setSelected(name);
}, []);
const largeNodes = useMemo(
() =>
nodesLeaf.map((node) => ({
...node,
size:
node.name === selected
? {
width: (node.size ?? options.defaultSize).width * 2,
height: (node.size ?? options.defaultSize).height * 2,
}
: node.size,
border: node.name === selected ? "red" : "black",
})),
[options.defaultSize, selected]
);
return (
<Box width="100%" height="100%">
<Graph key="graph" graph={graph} options={options} />;
<SimpleGraph nodes={largeNodes} edges={edges} options={options} onSelectNode={expand} />;
</Box>
);
};

export const DemoGraphLevel2: FC<{ options: GraphProps["options"] }> = ({ options }) => {
export const DemoGraphLevel2: FC<{
options: GraphOptions;
}> = ({ options: _options }) => {
const [expanded, setExpanded] = useState<string[]>([]);
const graph = useNgraph(nodesL2, edges, expanded, { iterations: 500 });
const onSelectNode = React.useCallback((args: { name: string }) => {
const options = useDefaultOptions(_options);

const onSelectNode = useCallback((args: { name: string }) => {
setExpanded((exp) => (exp.includes(args.name) ? exp.filter((e) => e !== args.name) : [...exp, args.name]));
}, []);
return (
<Box width="100%" height="100%">
<Graph graph={graph} onSelectNode={onSelectNode} options={options} />;
<ExpandableGraph
key="expandable"
nodes={nodesL2}
edges={edges}
onSelectNode={onSelectNode}
expanded={expanded}
options={options}
/>
</Box>
);
};

export const DemoGraphLevel3: FC<{ options: GraphProps["options"] }> = ({ options }) => {
export const DemoGraphLevel3: FC<{
options: GraphOptions;
}> = ({ options }) => {
const [expanded, setExpanded] = useState<string[]>([]);
const graph = useNgraph(nodesL3, edges, expanded, { iterations: 500 });
const onSelectNode = React.useCallback((args: { name: string }) => {
setExpanded((exp) => (exp.includes(args.name) ? exp.filter((e) => e !== args.name) : [...exp, args.name]));
}, []);
return (
<Box width="100%" height="100%">
<Graph graph={graph} onSelectNode={onSelectNode} options={options} />;
</Box>
);
return <Box />;
// const graph = useNgraph(nodesL3, edges, expanded, { ...options, physics });
// const onSelectNode = React.useCallback((args: { name: string }) => {
// setExpanded((exp) => (exp.includes(args.name) ? exp.filter((e) => e !== args.name) : [...exp, args.name]));
// }, []);
// return (
// <Box width="100%" height="100%">
// <Graph graph={graph} onSelectNode={onSelectNode} options={options} />;
// </Box>
// );
};
Loading

0 comments on commit e0d9cd2

Please sign in to comment.