From cec818fd94a03271fb866c96c240e7bf2f071338 Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Mon, 27 Feb 2023 15:18:53 -0500 Subject: [PATCH] docs(topology): add layouts demo and documentation (#8680) * add layouts * Add layout descriptions, update demo --------- Co-authored-by: Jeffrey Phillips --- .../examples/TopologyCustomNodesDemo.tsx | 2 +- .../TopologyView/examples/TopologyLayouts.md | 76 ++++ .../examples/TopologyLayoutsDemo.tsx | 349 ++++++++++++++++++ .../examples/TopologyPanZoomDemo.tsx | 2 +- .../examples/TopologySelectableDemo.tsx | 2 +- .../examples/TopologySidebarDemo.tsx | 2 +- .../examples/TopologyToolbarDemo.tsx | 2 +- .../examples/topology-example.css | 1 + 8 files changed, 431 insertions(+), 5 deletions(-) create mode 100644 packages/react-topology/src/components/TopologyView/examples/TopologyLayouts.md create mode 100644 packages/react-topology/src/components/TopologyView/examples/TopologyLayoutsDemo.tsx diff --git a/packages/react-topology/src/components/TopologyView/examples/TopologyCustomNodesDemo.tsx b/packages/react-topology/src/components/TopologyView/examples/TopologyCustomNodesDemo.tsx index 424bde60eaa..a31a1d1fa71 100644 --- a/packages/react-topology/src/components/TopologyView/examples/TopologyCustomNodesDemo.tsx +++ b/packages/react-topology/src/components/TopologyView/examples/TopologyCustomNodesDemo.tsx @@ -49,7 +49,7 @@ const BadgeColors = [ const CustomNode: React.FC = ({ element }) => { const data = element.getData(); - const Icon = data.alternate ? Icon2 : Icon1; + const Icon = data.isAlternate ? Icon2 : Icon1; const badgeColors = BadgeColors.find(badgeColor => badgeColor.name === data.badge); return ( diff --git a/packages/react-topology/src/components/TopologyView/examples/TopologyLayouts.md b/packages/react-topology/src/components/TopologyView/examples/TopologyLayouts.md new file mode 100644 index 00000000000..f4eddd93e4d --- /dev/null +++ b/packages/react-topology/src/components/TopologyView/examples/TopologyLayouts.md @@ -0,0 +1,76 @@ +--- +id: Layouts +section: topology +--- + +Note: Topology lives in its own package at [`@patternfly/react-topology`](https://www.npmjs.com/package/@patternfly/react-topology) + +import { + action, + createTopologyControlButtons, + defaultControlButtonsOptions, + BreadthFirstLayout, + ColaLayout, + ColaGroupsLayout, + ConcentricLayout, + DagreLayout, + DefaultEdge, + DefaultGroup, + DefaultNode, + ForceLayout, + GridLayout, + GraphComponent, + ModelKind, + NodeShape, + NodeStatus, + observer, + GRAPH_LAYOUT_END_EVENT, + TopologyControlBar, + TopologyView, + Visualization, + VisualizationProvider, + VisualizationSurface, + withDragNode, + withPanZoom +} from '@patternfly/react-topology'; +import { Select, SelectOption, SelectVariant, ToolbarItem } from '@patternfly/react-core'; +import Icon1 from '@patternfly/react-icons/dist/esm/icons/regions-icon'; +import Icon2 from '@patternfly/react-icons/dist/esm/icons/folder-open-icon'; + +import './topology-example.css'; + +### Layouts +Layouts will help to position the nodes on the graph in some manner as defined by the chosen layout. There are many algorithms +for positioning nodes based on many factors (side of nodes, distance between nodes, edges, etc). Patternfy react-topology provides +some layouts that you can choose to customize your topology graph with: + +##### Force: +This layout is built on top of the d3-force layout provided by [d3/d3-force](https://github.com/d3/d3-force). + +##### Dagre: +This layout is built on top of the dagrejs dagre layout provided by [dagrejs/dagre](https://github.com/dagrejs/dagre). + +##### Cola: +This layout is built on top of the WebCola layout provided by [tgdwyer/WebCola](://github.com/tgdwyer/WebCola). This layout uses `force simulation` +by default, but can be turned off by setting the options `layoutOnDrag` flag to false. + +##### ColaGroups: +This layout uses the Cola layout recursively on each group such that the group's children locations are set before setting the group's location +relative to other groups/nodes at its level. + +##### Grid: +This is a basic grid layout. It orders the nodes in a grid making the grid as `square` as possible. +Grid layout works well to distribute nodes without taking into consideration edges + +##### Concentric: +Concentric layouts have better results focused on high connectivity. It places the nodes in a circular pattern. + +##### BreadthFirst: +This layout uses a breadth first algorithm to place the nodes. A BreadthFirst layout may help in these cases, providing +a natural "levels" approach that can be combined with other algorithms to help users to identify the dependencies between elements. + +Note: this first version currently doesn't manage the overflow of a row + +### Examples +```ts file='./TopologyLayoutsDemo.tsx' +``` diff --git a/packages/react-topology/src/components/TopologyView/examples/TopologyLayoutsDemo.tsx b/packages/react-topology/src/components/TopologyView/examples/TopologyLayoutsDemo.tsx new file mode 100644 index 00000000000..3c9f004f3b3 --- /dev/null +++ b/packages/react-topology/src/components/TopologyView/examples/TopologyLayoutsDemo.tsx @@ -0,0 +1,349 @@ +import * as React from 'react'; +import { + Dropdown, + DropdownItem, + DropdownPosition, + DropdownToggle, + Split, + SplitItem, + ToolbarItem +} from '@patternfly/react-core'; +// eslint-disable-next-line patternfly-react/import-tokens-icons +import { RegionsIcon as Icon1, FolderOpenIcon as Icon2 } from '@patternfly/react-icons'; +import { + action, + createTopologyControlButtons, + defaultControlButtonsOptions, + DefaultEdge, + DefaultGroup, + DefaultNode, + GraphComponent, + GRAPH_LAYOUT_END_EVENT, + ModelKind, + NodeModel, + NodeShape, + observer, + TopologyView, + TopologyControlBar, + Visualization, + VisualizationProvider, + VisualizationSurface, + ComponentFactory, + Model, + Node, + NodeStatus, + Graph, + Layout, + LayoutFactory, + ForceLayout, + ColaLayout, + ConcentricLayout, + DagreLayout, + GridLayout, + BreadthFirstLayout, + ColaGroupsLayout, + withDragNode, + WithDragNodeProps, + withPanZoom +} from '@patternfly/react-topology'; + +const NODE_DIAMETER = 75; +const NODE_SHAPE = NodeShape.ellipse; + +const NODES: NodeModel[] = [ + { + id: 'node-0', + type: 'node', + label: 'Node 0', + width: NODE_DIAMETER, + height: NODE_DIAMETER, + shape: NODE_SHAPE, + status: NodeStatus.danger, + data: { + isAlternate: false + } + }, + { + id: 'node-1', + type: 'node', + label: 'Node 1', + width: NODE_DIAMETER, + height: NODE_DIAMETER, + shape: NODE_SHAPE, + status: NodeStatus.success, + data: { + isAlternate: false + } + }, + { + id: 'node-2', + type: 'node', + label: 'Node 2', + width: NODE_DIAMETER, + height: NODE_DIAMETER, + shape: NODE_SHAPE, + status: NodeStatus.warning, + data: { + isAlternate: true + } + }, + { + id: 'node-3', + type: 'node', + label: 'Node 3', + width: NODE_DIAMETER, + height: NODE_DIAMETER, + shape: NODE_SHAPE, + status: NodeStatus.info, + data: { + isAlternate: false + } + }, + { + id: 'node-4', + type: 'node', + label: 'Node 4', + width: NODE_DIAMETER, + height: NODE_DIAMETER, + shape: NODE_SHAPE, + status: NodeStatus.default, + data: { + isAlternate: true + } + }, + { + id: 'node-5', + type: 'node', + label: 'Node 5', + width: NODE_DIAMETER, + height: NODE_DIAMETER, + shape: NODE_SHAPE, + data: { + isAlternate: false + } + }, + { + id: 'Group-1', + children: ['node-0', 'node-1', 'node-2'], + type: 'group', + group: true, + label: 'Group-1', + style: { + padding: 40 + } + } +]; + +const EDGES = [ + { + id: 'edge-node-4-node-5', + type: 'edge', + source: 'node-4', + target: 'node-5' + }, + { + id: 'edge-node-0-node-2', + type: 'edge', + source: 'node-0', + target: 'node-2' + } +]; + +const customLayoutFactory: LayoutFactory = (type: string, graph: Graph): Layout | undefined => { + switch (type) { + case 'BreadthFirst': + return new BreadthFirstLayout(graph); + case 'Cola': + return new ColaLayout(graph); + case 'ColaNoForce': + return new ColaLayout(graph, { layoutOnDrag: false }); + case 'Concentric': + return new ConcentricLayout(graph); + case 'Dagre': + return new DagreLayout(graph); + case 'Force': + return new ForceLayout(graph); + case 'Grid': + return new GridLayout(graph); + case 'ColaGroups': + return new ColaGroupsLayout(graph, { layoutOnDrag: false }); + default: + return new ColaLayout(graph, { layoutOnDrag: false }); + } +}; + +type CustomNodeProps = { + element: Node; +} & WithDragNodeProps; + +const CustomNode: React.FC = observer(({ element, ...rest }) => { + const data = element.getData(); + const Icon = data.isAlternate ? Icon2 : Icon1; + + return ( + + + + + + ); +}); + +const customComponentFactory: ComponentFactory = (kind: ModelKind, type: string) => { + switch (type) { + case 'group': + return DefaultGroup; + default: + switch (kind) { + case ModelKind.graph: + return withPanZoom()(GraphComponent); + case ModelKind.node: + return withDragNode()(CustomNode); + case ModelKind.edge: + return DefaultEdge; + default: + return undefined; + } + } +}; + +export const LayoutsDemo: React.FC = () => { + const [layoutDropdownOpen, setLayoutDropdownOpen] = React.useState(false); + const [layout, setLayout] = React.useState('ColaNoForce'); + + const controller = React.useMemo(() => { + const model: Model = { + nodes: NODES, + edges: EDGES, + graph: { + id: 'g1', + type: 'graph', + layout: 'ColaNoForce' + } + }; + + const newController = new Visualization(); + newController.registerLayoutFactory(customLayoutFactory); + newController.registerComponentFactory(customComponentFactory); + + newController.addEventListener(GRAPH_LAYOUT_END_EVENT, () => { + newController.getGraph().fit(80); + }); + + newController.fromModel(model, false); + return newController; + }, []); + + const updateLayout = (newLayout: string) => { + setLayout(newLayout); + setLayoutDropdownOpen(false); + }; + + React.useEffect(() => { + if (controller && controller.getGraph().getLayout() !== layout) { + const model: Model = { + nodes: NODES, + edges: EDGES, + graph: { + id: 'g1', + type: 'graph', + layout + } + }; + + controller.fromModel(model, false); + } + }, [controller, layout]); + + const layoutDropdown = ( + + + + + + setLayoutDropdownOpen(!layoutDropdownOpen)}>{layout}} + isOpen={layoutDropdownOpen} + dropdownItems={[ + { + updateLayout('Force'); + }} + > + Force + , + { + updateLayout('Dagre'); + }} + > + Dagre + , + { + updateLayout('Cola'); + }} + > + Cola + , + { + updateLayout('ColaGroups'); + }} + > + ColaGroups + , + updateLayout('ColaNoForce')}> + ColaNoForce + , + updateLayout('Grid')}> + Grid + , + updateLayout('Concentric')}> + Concentric + , + updateLayout('BreadthFirst')}> + BreadthFirst + + ]} + /> + + + ); + + return ( + {layoutDropdown}} + controlBar={ + { + controller.getGraph().scaleBy(4 / 3); + }), + zoomOutCallback: action(() => { + controller.getGraph().scaleBy(0.75); + }), + fitToScreenCallback: action(() => { + controller.getGraph().fit(80); + }), + resetViewCallback: action(() => { + controller.getGraph().reset(); + controller.getGraph().layout(); + }), + legend: false + })} + /> + } + > + + + + + ); +}; diff --git a/packages/react-topology/src/components/TopologyView/examples/TopologyPanZoomDemo.tsx b/packages/react-topology/src/components/TopologyView/examples/TopologyPanZoomDemo.tsx index a0fd94a813c..1edaafa09c5 100644 --- a/packages/react-topology/src/components/TopologyView/examples/TopologyPanZoomDemo.tsx +++ b/packages/react-topology/src/components/TopologyView/examples/TopologyPanZoomDemo.tsx @@ -50,7 +50,7 @@ const BadgeColors = [ const CustomNode: React.FC = ({ element }) => { const data = element.getData(); - const Icon = data.alternate ? Icon2 : Icon1; + const Icon = data.isAlternate ? Icon2 : Icon1; const badgeColors = BadgeColors.find(badgeColor => badgeColor.name === data.badge); return ( diff --git a/packages/react-topology/src/components/TopologyView/examples/TopologySelectableDemo.tsx b/packages/react-topology/src/components/TopologyView/examples/TopologySelectableDemo.tsx index 0ca5c8c4a8b..c12bb69b466 100644 --- a/packages/react-topology/src/components/TopologyView/examples/TopologySelectableDemo.tsx +++ b/packages/react-topology/src/components/TopologyView/examples/TopologySelectableDemo.tsx @@ -51,7 +51,7 @@ const BadgeColors = [ const CustomNode: React.FC = ({ element, onSelect, selected }) => { const data = element.getData(); - const Icon = data.alternate ? Icon2 : Icon1; + const Icon = data.isAlternate ? Icon2 : Icon1; const badgeColors = BadgeColors.find(badgeColor => badgeColor.name === data.badge); return ( diff --git a/packages/react-topology/src/components/TopologyView/examples/TopologySidebarDemo.tsx b/packages/react-topology/src/components/TopologyView/examples/TopologySidebarDemo.tsx index d0a7bb8e5cb..3945e8acd4c 100644 --- a/packages/react-topology/src/components/TopologyView/examples/TopologySidebarDemo.tsx +++ b/packages/react-topology/src/components/TopologyView/examples/TopologySidebarDemo.tsx @@ -47,7 +47,7 @@ const BadgeColors = [ const CustomNode: React.FC = ({ element, onSelect, selected }) => { const data = element.getData(); - const Icon = data.alternate ? Icon2 : Icon1; + const Icon = data.isAlternate ? Icon2 : Icon1; const badgeColors = BadgeColors.find(badgeColor => badgeColor.name === data.badge); return ( diff --git a/packages/react-topology/src/components/TopologyView/examples/TopologyToolbarDemo.tsx b/packages/react-topology/src/components/TopologyView/examples/TopologyToolbarDemo.tsx index 2722ad7db00..43d4ed8e150 100644 --- a/packages/react-topology/src/components/TopologyView/examples/TopologyToolbarDemo.tsx +++ b/packages/react-topology/src/components/TopologyView/examples/TopologyToolbarDemo.tsx @@ -179,7 +179,7 @@ interface CustomNodeProps { const CustomNode: React.FC = observer(({ element }) => { const data = element.getData(); - const Icon = data.alternate ? Icon2 : Icon1; + const Icon = data.isAlternate ? Icon2 : Icon1; const badgeColors = BadgeColors.find(badgeColor => badgeColor.name === data.badge); const { viewOptions } = element.getController().getState(); diff --git a/packages/react-topology/src/components/TopologyView/examples/topology-example.css b/packages/react-topology/src/components/TopologyView/examples/topology-example.css index 2e5c7cc9522..fa35e1f748c 100644 --- a/packages/react-topology/src/components/TopologyView/examples/topology-example.css +++ b/packages/react-topology/src/components/TopologyView/examples/topology-example.css @@ -4,6 +4,7 @@ .ws-react-t-custom-edges, .ws-react-t-custom-nodes, .ws-react-t-getting-started, +.ws-react-t-layouts, .ws-react-t-panzoom, .ws-react-t-selection, .ws-react-t-sidebar,