-
Notifications
You must be signed in to change notification settings - Fork 298
Description
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.
After dragging desirable display
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!


