Skip to content

ngx-graph going out of page | Alignment issue #608

@saikatge

Description

@saikatge

Issue:
We are using ngx-graph library for displaying a graph.
My X-axis coordinates ranges from (-2000,2000) and Y-axis coordinates ranges from (-2000,2000). The graph is displayed at the left of the page where the coordinates above X axis example (2000,2000) and (-2000, 2000) are going out out display. We need to drag it and bring it to center.
But with every page refresh the coordinates are again going out of page.

Desirable output: The graph should display at the center of the page on every page refresh.

Error image screenshot:
Image

After dragging desirable display

Image

My custom layout file is :

import { Graph, Layout, Edge } from '@swimlane/ngx-graph';
import * as dagre from 'dagre';

export enum Alignment {
CENTER = 'C',
UP_LEFT = 'UL',
UP_RIGHT = 'UR',
DOWN_LEFT = 'DL',
DOWN_RIGHT = 'DR'
}

export enum Orientation {
LEFT_TO_RIGHT = 'LR',
RIGHT_TO_LEFT = 'RL',
TOP_TO_BOTTOM = 'TB',
BOTTOM_TO_TOM = 'BT'
}

export const layoutSetting ={
orientation: Orientation.LEFT_TO_RIGHT,
rankPadding: 180,
align: Alignment.DOWN_LEFT,
}

export interface DagreSettings {
orientation?: Orientation;
marginX?: number;
marginY?: number;
edgePadding?: number;
rankPadding?: number;
nodePadding?: number;
align?: Alignment;
acyclicer?: 'greedy' | undefined;
ranker?: 'network-simplex' | 'tight-tree' | 'longest-path';
multigraph?: boolean;
compound?: boolean;
}

export interface DagreNodesOnlySettings extends DagreSettings {
curveDistance?: number;
}

const DEFAULT_EDGE_NAME = '\x00';
const GRAPH_NODE = '\x00';
const EDGE_KEY_DELIM = '\x01';

export class NetworkLayout implements Layout {
defaultSettings: DagreNodesOnlySettings = {
orientation: Orientation.LEFT_TO_RIGHT,
marginX: 2000,
marginY: 2000,
edgePadding: 330,
rankPadding: 200,
nodePadding: 250,
curveDistance: 37,
multigraph: false,
compound: true
};
settings: DagreNodesOnlySettings = {};
dagreGraph: any;
dagreNodes: any;
dagreEdges: any;
dragedNode: boolean;

public run(graph: Graph): Graph {
this.createDagreGraph(graph);
dagre.layout(this.dagreGraph);

graph.edgeLabels = this.dagreGraph._edgeLabels;
if(this.dagreGraph._nodes){
  for (const dagreNodeId in this.dagreGraph._nodes) {
    const dagreNode = this.dagreGraph._nodes[dagreNodeId];
    if(dagreNode && dagreNode.id){
      const node = graph.nodes.find(n => n.id === dagreNode.id);
      node.position={
        x:0,
        y:0
      }
      node.position = {
          x: node.data.x,
          y: node.data.y * -1
      };
      node.dimension = {
        width: dagreNode.width,
        height: dagreNode.height
      };
    }
  }
}

for (const edge of graph.edges) {
  this.updateEdge(graph, edge);
}
return graph;

}

public updateEdge(graph: Graph, edge: Edge): Graph {
if(edge.source && edge.target){
const sourceNode = graph.nodes.find(n => n.id === edge.source);
const targetNode = graph.nodes.find(n => n.id === edge.target);
if(sourceNode && targetNode){
const rankAxis: 'x' | 'y' = this.settings.orientation === 'BT' || this.settings.orientation === 'TB' ? 'y' : 'x';
const orderAxis: 'x' | 'y' = rankAxis === 'y' ? 'x' : 'y';
const rankDimension = rankAxis === 'y' ? 'height' : 'width';
// determine new arrow position
const dir = sourceNode.position[rankAxis] <= targetNode.position[rankAxis] ? -1 : 1;
const startingPoint = {
[orderAxis]: sourceNode.position[orderAxis],
[rankAxis]: sourceNode.position[rankAxis] - dir * ((sourceNode.dimension[rankDimension]-this.defaultSettings.curveDistance)/ 2)
};
const endingPoint = {
[orderAxis]: targetNode.position[orderAxis],
[rankAxis]: targetNode.position[rankAxis] + dir * ((targetNode.dimension[rankDimension]-this.defaultSettings.curveDistance) / 2)
};

    const curveDistance = this.settings.curveDistance || this.defaultSettings.curveDistance;
    // generate new points
    edge['points'] = [
      startingPoint,
      {
        [rankAxis]: (startingPoint[rankAxis] + endingPoint[rankAxis]) / 2,
        [orderAxis]: startingPoint[orderAxis],
        ['source']:edge.source,
        ['target']:edge.target
      },
      {
        [orderAxis]: endingPoint[orderAxis],
        [rankAxis]: (startingPoint[rankAxis] + endingPoint[rankAxis]) / 2,
        ['source']:edge.source,
        ['target']:edge.target
      },
      endingPoint
    ];
    const edgeLabelId = `${edge.source}${EDGE_KEY_DELIM}${edge.target}${EDGE_KEY_DELIM}${DEFAULT_EDGE_NAME}`;
    const matchingEdgeLabel = graph?.edgeLabels?.[edgeLabelId];
    if (matchingEdgeLabel) {
      matchingEdgeLabel.points = edge?.points;
    }
    return graph;
  }

}

}

public createDagreGraph(graph: Graph): any {
const settings = Object.assign({}, this.defaultSettings, this.settings);
this.dagreGraph = new dagre.graphlib.Graph({ compound: settings.compound, multigraph: settings.multigraph });
this.dagreGraph.setGraph({
rankdir: settings.orientation,
marginx: this.defaultSettings.marginX,
marginy: this.defaultSettings.marginY,
edgesep: this.defaultSettings.edgePadding,
ranksep: settings.rankPadding,
nodesep: this.defaultSettings.nodePadding,
align: settings.align,
acyclicer: settings.acyclicer,
ranker: settings.ranker,
multigraph: settings.multigraph,
compound: settings.compound
});

// Default to assigning a new object as a label for each new edge.
this.dagreGraph.setDefaultEdgeLabel(() => {
  return {
    /* empty */
  };
});

this.dagreNodes = graph.nodes.map(n => {
  const node: any = Object.assign({}, n);
  node.width = n.dimension.width;
  node.height = n.dimension.height;
  node.x = n.position.x;
  node.y = n.position.y;
  return node;
});

this.dagreEdges = graph.edges.map(l => {
  let linkId: number = 1;
  const newLink: any = Object.assign({}, l);
  if (!newLink.id) {
    newLink.id = linkId;
    linkId++;
  }
  return newLink;
});

for (const node of this.dagreNodes) {
  if (!node.width) {
    node.width = 20;
  }
  if (!node.height) {
    node.height = 30;
  }

  // update dagre
  this.dagreGraph.setNode(node.id, node);
}

// update dagre
for (const edge of this.dagreEdges) {
  if (settings.multigraph) {
    this.dagreGraph.setEdge(edge.source, edge.target, edge, edge.id);
  } else {
    this.dagreGraph.setEdge(edge.source, edge.target);
  }
}
return this.dagreGraph;

}

onDragEnd(Node){
let dragedNode=Node;
if(this.dagreNodes){
this.dagreNodes.forEach(nodeItm => {
if(nodeItm?.id===dragedNode?.id){
if(nodeItm.data.x!==dragedNode.position.x || nodeItm.data.y!==dragedNode.position.y ){
this.dragedNode=true;
}
}
});
}
}

}`

MY CSS file is

.page{
float:left;
.network-page{
height: 100%;
width: 100%;
float:left;
}
}
.menu-container-network{
height: 100%;
width:100%;
}

.cloud-label{
transform: translate(30px, 55px);
}
.copyTemplate{
.copyClipBoard{
background-color: royalblue;
color:#fff;
}
.copy-btn{
float:right;
margin-top:30px;
}
.copy-template-content{
margin-top:20px;
display: block;
width:100%;
}
}
.non-cloud-label{
transform: translate(30px, 42px);
}
.node text{
font-size: 15px !important;
}
.cloud{
transform:scale(0.1);
}
.toolbar-show{
background: #1b2a33;
z-index: 9;
}
.toolbar-hide{
background:transparent;
}
.mat-slide-toggle-bar {
background-color: rgb(236 235 235 / 38%) !important;
}
.network-navigation-tab{
position: absolute;
height: 40px;
color:#fff;
width: fill-available;
width: -webkit-fill-available;
width: -moz-available;
;
.action-pull-left{
position: absolute;
z-index: 9;
display: flex;
align-items: center;
height: 100%;
}
.action-pull-right{
position: absolute;
z-index: 9;
}
.action-btn{
float:left !important;
margin-left:10px;
display: flex;
align-items: center;
height: 100%;
.disabledIcon{
pointer-events: none;
cursor: no-drop;
opacity: 0.6;
background-color: #ddd !important;
}
span{
border-radius: 3px;
font-size: 16px !important;
padding: 4px 7px;
}
.save{
background-color: #2f4efb;
margin-right: 12px;
}
.resetIcon{
background-color: red;
}
}
.panWidget {
label{
vertical-align: middle;
}
.disabledIcon{
cursor: no-drop;
pointer-events: none;
opacity:0.5;
}
.zoomInZoomOutBtn{
margin-top:10px;
font-size: 24px;
margin-left:8px;
}
}
.group-pannel{
display: flex;
align-items: center;
height: 100%;
float: left;
margin-left: 50px;
label{
margin-bottom:0px !important;
}
.expand{
align-items: center;
display: flex;
}
.expand-btn {
.mat-raised-button{
margin-left: 12px;
line-height: 2;
}
}
.p-component.p-disabled{
pointer-events: none;
cursor:not-allowed !important;
background: #ccc !important;
opacity: unset !important;
.p-dropdown-label{
color: rgba(0, 0, 0, 0.26) !important;
}
}
.p-dropdown-trigger-icon,.p-dropdown-label{
color: #fff !important;
font-size: 13px !important;
}
p-dropdown{
margin-left: 10px;
}
.p-dropdown{
background: royalblue !important;
.p-inputtext{
font-size: 13px !important;
padding: 0.129rem 0.429rem !important;
}
.p-dropdown-panel{
font-size: 13px !important;
}
}
.p-dropdown{
height: calc(0.7em + 0.75rem + 2px);
}
}
}
.ngx-charts{
width:100% !important;
}
.mask{
.ngx-charts-outer{
background-color: #000;
opacity: 0.5;
}
}
.ngx-charts-outer{
cursor:grab !important;
}

svg text#chk {
font-size: 24px;
fill: #00f;
}
.expandgroup{
transform: scale(0.6);
path{
transform: translate(44px, 46px);
background-color: #fff;
}
:hover{
cursor:all-scroll;
}
}
.assetCount{
fill:#fafae1;
font-weight: 800;
}
.rectAsset{
stroke-width:3;
stroke:rgb(230 227 227);
stroke-linejoin: round;
cursor:move !important;
}
.primaryChild-Asset{
stroke: blue !important;
}
.logicalCircuit{
fill:#ccc;
stroke:#ccc;
}
.logicalCircuit:hover{
filter: drop-shadow(0px 0px 10px #c4c5c4);
}

.asset-mat-menu {
border: 2px solid #2c404d !important;
background: #0c1419 !important;
min-height: 30px !important;
.mat-menu-item[disabled] {
pointer-events: auto!important;
}
}
.asset-mat-menu .mat-menu-item:hover:not([disabled]), .asset-mat-menu .mat-menu-item-highlighted:not([disabled]) {
background: #1b2a33 !important;
}
.asset-mat-menu .mat-menu-content:not(:empty) .mat-menu-item {
font-size: 1em;
padding-right: 20px !important;
padding-left: 15px !important;
}
.asset-mat-menu .mat-menu-item{
line-height: 34px !important;
height: 34px !important;
}
.asset-mat-menu .mat-menu-item-submenu-trigger::after {
color: white;
}
.incident-tag{
text{
fill:#fff !important;
font-size: 11px !important;
}
}

.network-toolbar-icon{
float:right;
margin-right: 25px;
color:#fff;
line-height: 3;
z-index: 99;
display: flex;
padding-top: 8px;
position: relative;
.mat-slide-toggle.mat-primary.mat-checked .mat-slide-toggle-bar,.mat-slide-toggle .mat-slide-toggle-thumb,.mat-slide-toggle.mat-primary.mat-checked .mat-slide-toggle-thumb{
background-color: #3f51b5 !important;
}
.mat-slide-toggle-bar, .mat-slide-toggle-label-before .mat-slide-toggle-bar{
background-color:#fff;
}
}
.groupService{
text{
font-weight: normal;
font-size: 12px;
fill:#fff;
}
}

.script-pannel{
padding-top:5px;
width:100%;
.p-component.p-disabled{
pointer-events: none;
cursor:not-allowed !important;
background: #ccc !important;
opacity: unset !important;
.p-dropdown-label{
color: rgba(0, 0, 0, 0.26) !important;
}
}
.p-dropdown-trigger-icon,.p-dropdown-label{
color: #fff !important;
font-size: 13px !important;
}
.p-dropdown{
background: transparent !important;
.p-inputtext{
font-size: 13px !important;
padding: 0.129rem 0.429rem !important;
}
.p-dropdown-panel{
font-size: 13px !important;
}
}
.p-dropdown-items-wrapper,.p-dropdown-items{
font-size: 14px !important;
}
.p-dropdown{
height: calc(0.8em + 0.75rem + 2px);
}
}

.domain-filter-dropdown.p-dropdown-panel.p-component{
width: 14% !important;
}

HTML code snippet:

    <!--Asset Display-->
        <ng-template #nodeTemplate let-node>
          <svg:g (mouseover)="onDragStoreAssetPosition(node);"  class="node" [ngClass]="node.data.isActiveAsset|| node.data.isChildService? 'activeAsset' :'notActiveAsset'"  >
            <defs>
              <linearGradient [attr.id]="'assetGradient'+node.id" x1="0%" y1="100%" x2="0%" y2="0%">
                <stop offset="0%" [attr.stop-color]="node.data.nodeColor['stop2']" />
                <stop offset="100%" [attr.stop-color]="node.data.nodeColor['stop1']" />
              </linearGradient>
            </defs>
            <svg:rect id="asset-node"   ngx-tooltip [tooltipPlacement]="'top'" [tooltipType]="'tooltip'" [tooltipTitle]="node.label" 
              class="rectAsset MainAsset"  *ngIf="!node.data.isChildService && !node.data.isGroup"  rx="6" ry="6" (contextmenu)="onRightClick($event, 'asset', node);highlightClickedNode(node,$event);" (click)="nodeAssetInfo(node,$event);highlightClickedNode(node,$event)" [attr.width]="node.dimension.width" [attr.height]="node.dimension.height" [attr.fill]="'url(#assetGradient'+node.id+')'">
              <animateTransform *ngIf="node.data.blinking" attributeType="XML" attributeName="transform" type="scale"  values="1;1.2;1" additive="sum" begin="0s" dur="1s" repeatCount="indefinite"/>
            </svg:rect>
            <svg:rect id="childAsset-node"   ngx-tooltip [tooltipPlacement]="'top'" [tooltipType]="'tooltip'" [tooltipTitle]="node.label" 
              class="rectAsset childServiceAsset" [ngClass]="node.data.isPrimaryChildAsset? 'primaryChild-Asset' :'scondaryChild-Asset'" *ngIf="node.data.isChildService && !node.data.isGroup"  rx="6" ry="6" (contextmenu)="onRightClick($event, 'asset', node);highlightClickedNode(node,$event);" (click)="redirectToChildServiceLayer(node,$event);highlightClickedNode(node,$event)" [attr.width]="node.dimension.width" [attr.height]="node.dimension.height" [attr.fill]="'url(#assetGradient'+node.id+')'">
            </svg:rect>
            <svg:rect id="group-node" (dblclick)="expandAssetGroup(node.label,node.id,$event)"  ngx-tooltip [tooltipPlacement]="'top'" [tooltipType]="'tooltip'" [tooltipTitle]="node.label" 
              class="rectAsset childServiceAsset" *ngIf="node.data.isGroup"  rx="6" ry="6" (contextmenu)="onRightClick($event, 'asset', node);highlightClickedNode(node,$event);" (click)="nodeAssetInfo(node,$event);highlightClickedNode(node,$event)" [attr.width]="node.dimension.width" [attr.height]="node.dimension.height" [attr.fill]="'url(#assetGradient'+node.id+')'">
            </svg:rect>
            <svg:g class="incident-counter incident-tag" *ngIf="node.data.incidentCounter>0 && !contingencyGraphValues">
              <svg:circle [attr.cx]="node.dimension.width" cy="8" r="12" [attr.x]="node.dimension.width" [attr.y]="node.dimension.height">
              </svg:circle>
              <svg:text [attr.x]="node.dimension.width" y="12" text-anchor="middle">{{node.data.incidentCounter}}</svg:text>
            </svg:g>
            <svg:g class="tag-counter incident-tag" *ngIf="node.data.tagCounter>0 && !contingencyGraphValues">
              <svg:circle  [attr.cx]="-5" cy="8" r="12"></svg:circle>
              <svg:text [attr.x]="-5" y="12" text-anchor="middle"  >{{node.data.tagCounter}}</svg:text>
            </svg:g>
          
            <svg:text text-anchor="middle" [attr.x]="20" [attr.y]="-10" fill="#fff" (click)="nodeAssetInfo(node,$event);highlightClickedNode(node,$event)">{{node.label}}</svg:text>
             <!--Group asset count dispaly-->
             <svg:text *ngIf="node.data.isGroup" (dblclick)="expandAssetGroup(node.label,node.id,$event);"(click)="nodeAssetInfo(node,$event);highlightClickedNode(node,$event);" [attr.x]="node.dimension.width/2" [attr.y]="node.dimension.height/2" dominant-baseline="middle" text-anchor="middle" class="assetCount">{{node.data.assetCount}}</svg:text>
             <!--Group expand icon on node-->
            <svg:g class="expandgroup" *ngIf="node.data.isGroup" (click)="expandAssetGroup(node.label,node.id,$event)" >
              <path d="M5.666,13.666c0-1.104,0.896-2,2-2h4v-4c0-1.104,0.896-2,2-2c1.104,0,2,0.896,2,2v4h4c1.104,0,2,0.896,2,2
              c0,1.104-0.896,2-2,2h-4v4c0,1.104-0.896,2-2,2c-1.104,0-2-0.896-2-2v-4h-4C6.562,15.666,5.666,14.77,5.666,13.666z M27.332,1.5
              v24.332c0,0.829-0.673,1.5-1.5,1.5H1.5c-0.828,0-1.5-0.671-1.5-1.5V1.5C0,0.671,0.672,0,1.5,0h24.332
              C26.659,0,27.332,0.671,27.332,1.5z M24.332,3H3v21.332h21.332V3z" />
        </svg:g>
      </svg:g>
    </ng-template>

Please not I have tried setting [autoCenter] = "true" and [view]="[3000, 1500]" parameters in HTML
After setting view as [view]="[3000, 1500]"
the page layout is but the topmost coordinates are still out of display. Any help would be appreciated!

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions