From e29402fec4542549f1195055d7ec81e2da35963a Mon Sep 17 00:00:00 2001 From: jenny-s51 Date: Fri, 10 Feb 2023 14:06:14 -0500 Subject: [PATCH] add layouts --- .../TopologyView/examples/TopologyLayouts.md | 52 +++ .../examples/TopologyLayoutsDemo.tsx | 355 ++++++++++++++++++ .../examples/topology-example.css | 1 + 3 files changed, 408 insertions(+) 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/TopologyLayouts.md b/packages/react-topology/src/components/TopologyView/examples/TopologyLayouts.md new file mode 100644 index 00000000000..fa830b71655 --- /dev/null +++ b/packages/react-topology/src/components/TopologyView/examples/TopologyLayouts.md @@ -0,0 +1,52 @@ +--- +id: Layouts +section: topology +--- + +Note: Topology lives in its own package at [`@patternfly/react-topology`](https://www.npmjs.com/package/@patternfly/react-topology) + +import { + BreadthFirstLayout, + ColaLayout, + ColaGroupsLayout, + ConcentricLayout, + DagreLayout, + DefaultEdge, + DefaultGroup, + DefaultNode, + EdgeStyle, + ForceLayout, + GridLayout, + GraphComponent, + ModelKind, + NodeShape, + observer, + SELECTION_EVENT, + TopologyView, + Visualization, + VisualizationProvider, + VisualizationSurface, + NodeStatus +} from '@patternfly/react-topology'; + +import { Select, SelectOption, SelectVariant, ToolbarItem } from '@patternfly/react-core'; + +import './topology-example.css'; +import Icon1 from '@patternfly/react-icons/dist/esm/icons/regions-icon'; +import Icon2 from '@patternfly/react-icons/dist/esm/icons/folder-open-icon'; + +### Layouts + +You can choose to customize your topology graph with any of the following layouts: + +- Force: [description] +- Dagre: [description] +- Cola: [description] +- ColaGroups: [description] +- ColaNoForce: [description] +- Grid: [description] +- Concentric: [description] +- BreadthFirst: [description] + +```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..346309f180e --- /dev/null +++ b/packages/react-topology/src/components/TopologyView/examples/TopologyLayoutsDemo.tsx @@ -0,0 +1,355 @@ +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 { + DefaultEdge, + DefaultGroup, + DefaultNode, + EdgeStyle, + GraphComponent, + ModelKind, + NodeModel, + NodeShape, + observer, + SELECTION_EVENT, + TopologyView, + Visualization, + VisualizationProvider, + VisualizationSurface, + ComponentFactory, + Model, + Node, + NodeStatus, + Graph, + Layout, + LayoutFactory, + ForceLayout, + ColaLayout, + ConcentricLayout, + DagreLayout, + GridLayout, + BreadthFirstLayout, + ColaGroupsLayout +} from '@patternfly/react-topology'; + +export const NODE_STATUSES = [ + NodeStatus.danger, + NodeStatus.success, + NodeStatus.warning, + NodeStatus.info, + NodeStatus.default +]; + +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: NODE_STATUSES[0], + data: { + badge: 'B', + isAlternate: false + } + }, + { + id: 'node-1', + type: 'node', + label: 'Node 1', + width: NODE_DIAMETER, + height: NODE_DIAMETER, + shape: NODE_SHAPE, + status: NODE_STATUSES[1], + data: { + badge: 'B', + isAlternate: false + } + }, + { + id: 'node-2', + type: 'node', + label: 'Node 2', + width: NODE_DIAMETER, + height: NODE_DIAMETER, + shape: NODE_SHAPE, + status: NODE_STATUSES[2], + data: { + badge: 'A', + isAlternate: true + } + }, + { + id: 'node-3', + type: 'node', + label: 'Node 3', + width: NODE_DIAMETER, + height: NODE_DIAMETER, + shape: NODE_SHAPE, + status: NODE_STATUSES[3], + data: { + badge: 'A', + isAlternate: false + } + }, + { + id: 'node-4', + type: 'node', + label: 'Node 4', + width: NODE_DIAMETER, + height: NODE_DIAMETER, + shape: NODE_SHAPE, + status: NODE_STATUSES[4], + data: { + badge: 'C', + isAlternate: false + } + }, + { + id: 'node-5', + type: 'node', + label: 'Node 5', + width: NODE_DIAMETER, + height: NODE_DIAMETER, + shape: NODE_SHAPE, + data: { + badge: 'C', + isAlternate: true + } + }, + { + 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', + edgeStyle: EdgeStyle.default + }, + { + id: 'edge-node-0-node-2', + type: 'edge', + source: 'node-0', + target: 'node-2', + edgeStyle: EdgeStyle.default + } +]; + +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 }); + } +}; + +interface CustomNodeProps { + element: Node; +} + +const CustomNode: React.FC = observer(({ element }) => { + const data = element.getData(); + const Icon = data.alternate ? Icon2 : Icon1; + + return ( + + + + + + ); +}); + +const customComponentFactory: ComponentFactory = (kind: ModelKind, type: string) => { + switch (type) { + case 'group': + return DefaultGroup; + default: + switch (kind) { + case ModelKind.graph: + return GraphComponent; + case ModelKind.node: + return CustomNode; + case ModelKind.edge: + return DefaultEdge; + default: + return undefined; + } + } +}; + +interface ViewOptions { + showLabels: boolean; + showStatusBackground: boolean; + showDecorators: boolean; + showBadges: boolean; +} + +export const DefaultViewOptions: ViewOptions = { + showLabels: false, + showStatusBackground: false, + showDecorators: false, + showBadges: false +}; +export const LayoutsDemo: React.FC = () => { + const [selectedIds, setSelectedIds] = React.useState([]); + 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 + } + }; + + const newController = new Visualization(); + newController.registerLayoutFactory(customLayoutFactory); + newController.registerComponentFactory(customComponentFactory); + + newController.addEventListener(SELECTION_EVENT, setSelectedIds); + + newController.fromModel(model, false); + return newController; + }, []); + + const updateLayout = (newLayout: string) => { + setLayout(newLayout); + setLayoutDropdownOpen(false); + }; + + React.useEffect(() => { + const model: Model = { + nodes: NODES, + edges: EDGES, + graph: { + id: 'g1', + type: 'graph', + layout + } + }; + + controller.fromModel(model, false); + controller.getGraph().fit(80); + // Don't update on option changes, its handled differently to not re-layout + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [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 + + ]} + /> + + + ); + + const viewToolbar = ( + <> + {layoutDropdown} + + ); + + return ( + + + + + + ); +}; 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 a50682862c4..e0628381fd2 100644 --- a/packages/react-topology/src/components/TopologyView/examples/topology-example.css +++ b/packages/react-topology/src/components/TopologyView/examples/topology-example.css @@ -2,6 +2,7 @@ .ws-react-t-control-bar, .ws-react-t-custom-edges, .ws-react-t-custom-nodes, +.ws-react-t-layouts, .ws-react-t-panzoom, .ws-react-t-selectable, .ws-react-t-with-sidebar {