Skip to content

Add a horizontal scrollbar to ngx-graph with a large amount of data #554

@Harvi29

Description

@Harvi29

Hello,
I'm working with a huge dataset. My client needs to see the whole dataset that (obviously) doesn't fit a single page width. If I set a fixed width to the parent container then the scroll does not work when zooming is enabled in the ngx-graph.

Is there a way to add a horizontal scroll bar to the ngx-graph when dealing with large data?

HTML code:

<div class="space-top-16 vc-form-container" [vcProgressSpinner]="loading">
    @if (loaded) {
    <ngx-graph
        class="mss-graph-container"
        [layout]="dagreLayout"
        [curve]="curve"
        [links]="links"
        [nodes]="nodes"
        [autoZoom]="true"
        [autoCenter]="true">
        <ng-template #nodeTemplate let-node>
            <svg
                height="80"
                [attr.width]="node.data.children && node.data.children.length > 0 ? '112' : '72'"
                [attr.max-width]="112">
                <foreignObject
                    height="80"
                    [attr.width]="node.data.children && node.data.children.length > 0 ? '112' : '72'"
                    [attr.max-width]="112">
                    <div class="vc-mss-device-node-wrapper">
                        <div class="vc-mss-device-node" (click)="$event.preventDefault(); showWidget(node)">
                            <span
                                class="vc-mss-device-icon material-icons"
                                [style.border-color]="getBorderColor()"
                                [style.color]="node.data.icon.color"
                                >{{ node.data.icon.label }}</span
                            >
                            <div
                                xmlns="http://www.w3.org/1999/xhtml"
                                class="vc-mss-device-node-text body-s-1"
                                [matTooltip]="node.label"
                                [matTooltipPosition]="'above'">
                                {{ node.label }} - {{ node.data.type }}
                            </div>
                        </div>

                        @if (node.data.children && node.data.children.length > 0) {
                        <div class="vc-mss-device-node">
                            <button
                                mat-icon-button
                                class="vc-mss-device-expand-icon"
                                (click)="openOrCloseNode(node); selectedNode = null">
                                <mat-icon>{{ node.data.expanded ? 'expand_more' : 'expand_less' }}</mat-icon>
                            </button>

                            <span>({{ node.data.children.length }})</span>
                        </div>
                        }
                    </div>
                </foreignObject>
            </svg>
        </ng-template>
        <ng-template #linkTemplate let-link>
            <svg:g class="edge" xmlns:svg="http://www.w3.org/2000/svg">
                <path class="edge line mss-device-overview-line" [attr.d]="link.line"></path>
            </svg:g>
        </ng-template>
    </ngx-graph>
    } @if (selectedNode) {
    <div class="vc-mss-device-node-widget vc-form-container">
        <div class="vc-mss-device-title">
            <h3
                class="headline-s vc-mss-device-overview-high-emphasis-color"
                i18n="@@MSS.SITE_DETAIL.TABS.DEVICE_OVERVIEW.NODE_OVERVIEW">
                Node overview
            </h3>
            <vc-button
                class="vc-mss-device-close-button"
                mode="icon"
                iconName="close"
                iconColor="var(--text-low-emphasis)"
                (trigger)="selectedNode = null"></vc-button>
        </div>

        <div class="vc-mss-device-item-container">
            <div class="vc-mss-device-node-item vc-mss-device-overview-medium-emphasis-color">
                <span class="body-m-1" i18n="@@MSS.SITE_DETAIL.TABS.DEVICE_OVERVIEW.FULL_NAME">Full name: </span>
                <span class="body-m-2">{{ selectedNode.label }}</span>
            </div>
            <div class="vc-mss-device-node-item">
                <span
                    class="body-m-1 vc-mss-device-overview-medium-emphasis-color"
                    i18n="@@MSS.SITE_DETAIL.TABS.DEVICE_OVERVIEW.DISCOVERED_BY"
                    >Discovered by:
                </span>
                <span class="body-m-2 vc-mss-device-overview-high-emphasis-color">{{ selectedNode.data.type }}</span>
            </div>
        </div>

        <div class="vc-mss-device-node-action-wrapper vc-form-container">
            <div class="vc-mss-device-node-action-description">
                <h4 class="body-m-2" i18n="@@MSS.SITE_DETAIL.TABS.DEVICE_OVERVIEW.PING_THE_DEVICE">Ping the device</h4>
                <span class="body-s" i18n="@@MSS.SITE_DETAIL.TABS.DEVICE_OVERVIEW.PING_DESCRIPTION"
                    >Packet Loss and Response Time Analyzer</span
                >
            </div>
            <vc-button
                label="Ping"
                i18n-label="@@MSS.SITE_DETAIL.TABS.DEVICE_OVERVIEW.PING"
                mode="basic"
                (trigger)="pingDevice()"></vc-button>
        </div>

        <div class="vc-mss-device-node-action-wrapper vc-form-container">
            <div class="vc-mss-device-node-action-description">
                <h4 class="body-m-2" i18n="@@MSS.SITE_DETAIL.TABS.DEVICE_OVERVIEW.EVENTS">Events</h4>
                <span class="body-s" i18n="@@MSS.SITE_DETAIL.TABS.DEVICE_OVERVIEW.EVENTS_DESCRIPTION"
                    >View related events</span
                >
            </div>
            <vc-button
                label="View"
                i18n-label="@@MSS.SITE_DETAIL.TABS.DEVICE_OVERVIEW.VIEW"
                mode="basic"
                (trigger)="navigateToEventsSearch()"></vc-button>
        </div>

        <div class="vc-mss-device-node-action-wrapper vc-form-container">
            <div class="vc-mss-device-node-action-description">
                <h4 class="body-m-2" i18n="@@MSS.SITE_DETAIL.TABS.DEVICE_OVERVIEW.FINDINGS">Findings</h4>
                <span class="body-s" i18n="@@MSS.SITE_DETAIL.TABS.DEVICE_OVERVIEW.FINDINGS_DESCRIPTION"
                    >View related Findings</span
                >
            </div>
            <vc-button
                label="View"
                i18n-label="@@MSS.SITE_DETAIL.TABS.DEVICE_OVERVIEW.VIEW"
                mode="basic"
                (trigger)="navigateToFindingsSearch()"></vc-button>
        </div>
    </div>
    }

    <div class="vc-form-container vc-mss-device-legend">
        @for (type of assetTypes; track type) {
        <div class="vc-mss-device-legend-item body-s-1">
            <vc-icon [name]="getNodeIcon(type).label" [color]="getNodeIcon(type).color"></vc-icon>
            {{ getAssetTypeLabel(type) }}
        </div>
        }
    </div>
</div>

SCSS:

:host {
    display: block;
    height: 100%;
    width: 100%;

    .vc-mss-device-node-wrapper {
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 8px;

        .vc-mss-device-node {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 8px;

            .vc-mss-device-icon {
                display: flex;
                justify-content: center;
                align-items: center;
                height: 36px;
                width: 36px;
                border: 1px solid;
                border-radius: 50%;
            }

            .vc-mss-device-node-text {
                width: 56px;
                text-align: center;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
            }
        }

        .vc-mss-device-node {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 4px;
            cursor: pointer;

            button.vc-mss-device-expand-icon {
                height: 24px !important;
                width: 24px !important;
                box-shadow: none !important;

                .mat-icon {
                    height: 24px;
                    width: 24px;
                    line-height: 24px;
                    font-size: 24px;
                }

                &.mat-mdc-icon-button:not([disabled]) {
                    &:hover,
                    &:focus-visible {
                        border-radius: 4px !important;
                        background-color: var(--primary-50);
                    }

                    &:focus-visible {
                        outline: 1px solid var(--primary-600);
                        outline-offset: 1px;
                    }
                }
            }
        }
    }

    .mss-graph-container {
        display: block;
        height: calc(100vh - 345px);
        width: 100%;
    }

    .mss-device-overview-item {
        cursor: pointer;
    }

    .mss-device-overview-line {
        stroke: var(--outline);
        stroke-width: 1;
    }

    .vc-mss-device-node-widget {
        display: flex;
        flex-direction: column;
        gap: 16px;
        position: absolute;
        top: 32px;
        right: 16px;
        width: 430px;
        height: calc(100% - 50px);
        overflow: auto;

        .vc-mss-device-title {
            display: inline-flex;
            justify-content: space-between;
            align-items: flex-start;

            h3 {
                margin: 0;
            }

            .vc-mss-device-close-button {
                position: relative;
                top: -8px;
                right: -8px;
            }
        }

        .vc-mss-device-item-container {
            display: flex;
            flex-direction: column;
            gap: 8px;

            .vc-mss-device-node-item {
                display: inline-flex;
                gap: 6px;
            }
        }

        .vc-mss-device-node-action-wrapper {
            display: flex;
            flex-direction: row;
            justify-content: space-between;
            align-items: center;

            .vc-mss-device-node-action-description {
                display: flex;
                flex-direction: column;
                gap: 8px;

                h4 {
                    margin: 0;
                }
            }
        }
    }

    .vc-mss-device-legend {
        display: flex;
        flex-direction: column;
        gap: 8px;
        position: absolute;
        top: 32px;
        left: 16px;
        width: 150px;
        background-color: var(--ghost-white);

        .vc-mss-device-legend-item {
            display: inline-flex;
            align-items: center;
            gap: 8px;
        }
    }

    .vc-mss-device-overview-high-emphasis-color {
        color: var(--text-high-emphasis);
    }

    .vc-mss-device-overview-medium-emphasis-color {
        color: var(--text-medium-emphasis);
    }
}

Thanks,

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions