Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

grid layout support #514

Open
n-bes opened this issue Dec 20, 2024 · 7 comments
Open

grid layout support #514

n-bes opened this issue Dec 20, 2024 · 7 comments

Comments

@n-bes
Copy link

n-bes commented Dec 20, 2024

Hi. I don't want to manually calculate location of nodes with Precalculated layout. Is it possible to add Grid layout to the graph?

The goal is to visualize c4 arch models and provide way to put nodes on different mixed layouts (Horisontal / Vertical)

demo

Or it can be achieved with ELK?

@n-bes
Copy link
Author

n-bes commented Dec 20, 2024

Or it can be achieved with ELK?

I tried, but it didn't work:

import { useCallback, useMemo } from 'react'
import { VisSingleContainer, VisGraph } from '@unovis/react'
import { GraphLayoutType, GraphNodeShape } from '@unovis/ts'

import { data, NodeDatum, LinkDatum, panels } from './data'


export default function ForceLayoutGraph (): JSX.Element {
    const cfg = (groupName: string) => {
        switch (groupName) {
            case 'root': return {
                'elk.direction': 'RIGHT',
            }
            case 'us-east-2a': return {
                'elk.direction': 'DOWN',
            }
            case 'subnets': return {
                'elk.direction': 'DOWN',
            }
        }
    };

    return (
        <div className='chart'>
            <VisSingleContainer data={data} height={'99vh'}>
                <VisGraph<NodeDatum, LinkDatum>
                    nodeLabelTrim={false}
                    layoutType={GraphLayoutType.Elk}
                    layoutElkSettings={cfg}
                    nodeLabel={useCallback((n: NodeDatum) => n.id, [])}
                    nodeShape={GraphNodeShape.Square}
                    nodeStrokeWidth={1.5}
                    nodeStroke={useCallback((n: NodeDatum) => n.color, [])}
                    layoutElkNodeGroups={useMemo(() => [
                        (d: NodeDatum): string | null => d.group ?? null,
                        (d: NodeDatum): string | null => d.subGroup ?? null,
                    ], [])}
                    panels={panels}
                    disableZoom
                />
            </VisSingleContainer>
        </div>
    )
}
import {GraphNodeShape, Position} from "@unovis/ts";

export type NodeDatum = {
    id: string;
    group?: string;
    subGroup?: string;
    shape?: string;
    color?: string;
}

export type LinkDatum = {
    source: string;
    target: string;
    color: string;
}

export const data = {
    nodes: [
        {
            id: 'vpc',
        },
        {
            id: '192.168.0.0/25',
            group: 'us-east-2a',
            subGroup: 'subnets',
        },
        {
            id: '192.168.3.192/26',
            group: 'us-east-2a',
            subGroup: 'subnets',
        },
        {
            id: '192.8.3.191/33',
            group: 'us-east-2a',
            subGroup: 'subnets',
        },

        {
            id: 'master-0',
            group: 'us-east-2a',
            subGroup: 'nodes',
        },
        {
            id: 'workload-name',
            group: 'us-east-2a',
            subGroup: 'nodes',
        },
    ],
    links: [
        {
            source: 'vpc',
            target: '192.168.0.0/25'
        },
        {
            source: 'vpc',
            target: '192.168.3.192/26'
        },
        {
            source: 'vpc',
            target: '192.8.3.191/33'
        },

        {
            source: '192.168.0.0/25',
            target: 'master-0'
        },
        {
            source: '192.168.3.192/26',
            target: 'master-0'
        },
        {
            source: '192.8.3.191/33',
            target: 'workload-name'
        },
    ],
}

export const panels = [
    {
        label: 'us-east-2a',
        labelPosition: Position.Bottom,

        nodes: ['192.168.0.0/25', '192.168.3.192/26', '192.8.3.191/33', 'master-0', 'workload-name'],

        dashedOutline: true,
        padding: { top: 50, right: 50, bottom: 50, left: 50 },
    },
    {
        nodes: ['192.168.0.0/25', '192.168.3.192/26', '192.8.3.191/33'],
        label: 'subnets',
    },
    {
        nodes: ['master-0', 'workload-name'],
        label: 'workload',
    },
]

@rokotyan
Copy link
Contributor

I'm sure Elk can do it, but configuring it can be a struggle. I would suggest that you first try to achieve the desired result in their interactive editor https://rtsys.informatik.uni-kiel.de/elklive/examples.html and then migrate that configuration to Unovis.

@n-bes
Copy link
Author

n-bes commented Dec 23, 2024

I checked example Mixed Directions

node outsideTopToBottom {
    elk.direction: DOWN
    nodeLabels.placement: "H_LEFT V_TOP OUTSIDE"
    label "topToBottom"
    
    node leftToRight {
        elk.direction: RIGHT
        nodeLabels.placement: "H_LEFT V_BOTTOM OUTSIDE"
        node n1
        node n2
        edge n1->n2
        label "leftToRight"
    }
    node bottomToTop {
        elk.direction: UP
        nodeLabels.placement: "H_LEFT V_TOP OUTSIDE"
        node n1
        node n2
        edge n1->n2
        label "bottomToTop"
    }
    
    edge bottomToTop -> leftToRight
}

Снимок экрана 2024-12-23 в 12 10 32

It looks like what i need, but in elk.direction do not passed to unovis correctly (i already provided example). Could you help me with it?

@rokotyan
Copy link
Contributor

@n-bes I'll try to take a look after the holidays, around the second week of January

@n-bes
Copy link
Author

n-bes commented Jan 15, 2025

but configuring it can be a struggle.

Yeah 💯

I found this config and played little bit with examples:

export const DEFAULT_ELK_SETTINGS = {
hierarchyHandling: 'INCLUDE_CHILDREN',
'nodePlacement.strategy': 'NETWORK_SIMPLEX',
'elk.padding': '[top=15.0,left=15.0,bottom=15.0,right=15.0]',
'spacing.nodeNodeBetweenLayers': '50',
'spacing.edgeNodeBetweenLayers': '50',
'spacing.nodeNode': '10',
}

// elk.hierarchyHandling: "INCLUDE_CHILDREN"
// elk.direction: LEFT

node outsideTopToBottom {
   elk.direction: DOWN
   nodeLabels.placement: "H_LEFT V_TOP OUTSIDE"
   label "topToBottom"
   
   node leftToRight {
       elk.direction: RIGHT
       nodeLabels.placement: "H_LEFT V_BOTTOM OUTSIDE"
       node n1
       node n2
       edge n1->n2
       label "leftToRight"
   }
   node bottomToTop {
       elk.direction: UP
       nodeLabels.placement: "H_LEFT V_TOP OUTSIDE"
       node n1
       node n2
       edge n1->n2
       label "bottomToTop"
   }
   
   edge bottomToTop.n1 -> leftToRight.n1
}

With the "hierarchyHandling" value overwritten, the behavior changes, but the problems persist with the subgroup:

    const cfg = (groupName: string) => {
        switch (groupName) {
            case 'us-east-2a': {
                return {
                    'elk.direction': 'DOWN'
                }
            }
            case 'root': {
                return {
                    'elk.hierarchyHandling': "INHERIT",
                }
            }
        }
    };

@n-bes
Copy link
Author

n-bes commented Jan 15, 2025

dc-1/ dc-2 layout can be controlled
cloud layout cant be controlled

Снимок экрана 2025-01-16 в 00 19 36

export type NodeDatum = {
    id: string;
    group?: string;
    subGroup?: string;
    shape?: string;
}

export type LinkDatum = {
    source: string;
    target: string;
}

export const data = {
    nodes: [
        { id: 'random-device'},

        { id: '192.168.1.0/24', group: 'dc-1', subGroup: 'subnets'},
        { id: '192.168.2.0/24', group: 'dc-1', subGroup: 'subnets'},
        { id: 'node-1',         group: 'dc-1', subGroup: 'workload' },
        { id: 'node-2',         group: 'dc-1', subGroup: 'workload' },

        { id: '192.168.3.0/24', group: 'dc-2', subGroup: 'subnets'},
        { id: '192.168.4.0/24', group: 'dc-2', subGroup: 'subnets'},
        { id: 'node-3',         group: 'dc-2', subGroup: 'workload' },
        { id: 'node-4',         group: 'dc-2', subGroup: 'workload' },

    ],
    links: [
        { source: 'random-device', target: '192.168.1.0/24' },
        { source: 'random-device', target: '192.168.2.0/24' },
        { source: 'random-device', target: '192.168.3.0/24' },
        { source: 'random-device', target: '192.168.4.0/24' },

        { source: '192.168.1.0/24', target: 'node-1' },
        { source: '192.168.1.0/24', target: 'node-2' },
        { source: '192.168.2.0/24', target: 'node-1' },
        { source: '192.168.2.0/24', target: 'node-2' },

        { source: '192.168.3.0/24', target: 'node-3' },
        { source: '192.168.3.0/24', target: 'node-4' },
        { source: '192.168.4.0/24', target: 'node-3' },
        { source: '192.168.4.0/24', target: 'node-4' },
    ],
}

export const panels = [
    {
        nodes: [
            '192.168.1.0/24',
            '192.168.2.0/24',
            '192.168.3.0/24',
            '192.168.4.0/24',

            'node-1',
            'node-2',
            'node-3',
            'node-4',
        ],
        label: 'cloud',
        dashedOutline: true,
        padding: { top: 50, right: 50, bottom: 50, left: 50 },
    },
    {
        nodes: [
            '192.168.1.0/24',
            '192.168.2.0/24',

            'node-1',
            'node-2',
        ],
        label: 'dc-1',
    },
    {
        nodes: [
            '192.168.3.0/24',
            '192.168.4.0/24',

            'node-3',
            'node-4',
        ],
        label: 'dc-2',
    },
]
export default function ParallelGraph (): JSX.Element {
    const cfg = (groupName: string) => {
        switch (groupName) {
            case 'cloud':{
                return {
                    'elk.direction': 'LEFT'
                }
            }
            case 'dc-1': {
                return {
                    'elk.direction': 'DOWN'
                }
            }
            case 'dc-2': {
                return {
                    'elk.direction': 'RIGHT'
                }
            }
            case 'root': {
                return {
                    'elk.hierarchyHandling': "INHERIT", # SEPARATE_CHILDREN works too
                    'elk.direction': 'DOWN'
                }
            }
        }
    };


    return (
        <div className='chart'>
            <VisSingleContainer data={data} height={'99vh'}>
                <VisGraph<NodeDatum, LinkDatum>
                    nodeLabelTrim={false}
                    layoutType={GraphLayoutType.Elk}
                    layoutElkSettings={cfg}
                    nodeLabel={useCallback((n: NodeDatum) => n.id, [])}
                    nodeShape={GraphNodeShape.Square}
                    nodeStrokeWidth={1.5}
                    nodeStroke={useCallback((n: NodeDatum) => n.color, [])}
                    layoutElkNodeGroups={useMemo(() => [
                        (d: NodeDatum): string | null => d.group ?? null,
                        (d: NodeDatum): string | null => d.subGroup ?? null,
                    ], [])}
                    panels={panels}
                    disableZoom
                />
            </VisSingleContainer>
        </div>
    )
}

@rokotyan
Copy link
Contributor

@n-bes Have you tried specifying a different layout algorithm for cloud?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants