UNPKG

@swimlane/ngx-graph

Version:
1 lines 168 kB
{"version":3,"file":"swimlane-ngx-graph.mjs","sources":["../../../projects/swimlane/ngx-graph/src/lib/utils/id.ts","../../../projects/swimlane/ngx-graph/src/lib/enums/panning.enum.ts","../../../projects/swimlane/ngx-graph/src/lib/enums/mini-map-position.enum.ts","../../../projects/swimlane/ngx-graph/src/lib/utils/throttle.ts","../../../projects/swimlane/ngx-graph/src/lib/utils/color-sets.ts","../../../projects/swimlane/ngx-graph/src/lib/utils/color.helper.ts","../../../projects/swimlane/ngx-graph/src/lib/utils/view-dimensions.helper.ts","../../../projects/swimlane/ngx-graph/src/lib/utils/visibility-observer.ts","../../../projects/swimlane/ngx-graph/src/lib/graph/layouts/dagre.ts","../../../projects/swimlane/ngx-graph/src/lib/graph/layouts/dagreCluster.ts","../../../projects/swimlane/ngx-graph/src/lib/graph/layouts/dagreNodesOnly.ts","../../../projects/swimlane/ngx-graph/src/lib/graph/layouts/d3ForceDirected.ts","../../../projects/swimlane/ngx-graph/src/lib/graph/layouts/colaForceDirected.ts","../../../projects/swimlane/ngx-graph/src/lib/graph/layouts/layout.service.ts","../../../projects/swimlane/ngx-graph/src/lib/graph/mouse-wheel.directive.ts","../../../projects/swimlane/ngx-graph/src/lib/graph/graph.component.ts","../../../projects/swimlane/ngx-graph/src/lib/graph/graph.component.html","../../../projects/swimlane/ngx-graph/src/lib/graph/graph.module.ts","../../../projects/swimlane/ngx-graph/src/lib/ngx-graph.module.ts","../../../projects/swimlane/ngx-graph/src/public_api.ts","../../../projects/swimlane/ngx-graph/src/swimlane-ngx-graph.ts"],"sourcesContent":["const cache = {};\n\n/**\n * Generates a short id.\n *\n */\nexport function id(): string {\n let newId = ('0000' + ((Math.random() * Math.pow(36, 4)) << 0).toString(36)).slice(-4);\n\n newId = `a${newId}`;\n\n // ensure not already used\n if (!cache[newId]) {\n cache[newId] = true;\n return newId;\n }\n\n return id();\n}\n","export enum PanningAxis {\n Both = 'both',\n Horizontal = 'horizontal',\n Vertical = 'vertical'\n}\n","export enum MiniMapPosition {\n UpperLeft = 'UpperLeft',\n UpperRight = 'UpperRight'\n}\n","/**\n * Throttle a function\n *\n * @export\n * @param {*} func\n * @param {number} wait\n * @param {*} [options]\n * @returns\n */\nexport function throttle(context: any, func: any, wait: number, options?: any) {\n options = options || {};\n let args: any;\n let result: any;\n let timeout = null;\n let previous = 0;\n\n function later() {\n previous = options.leading === false ? 0 : +new Date();\n timeout = null;\n result = func.apply(context, args);\n }\n\n return function (..._arguments: any[]) {\n const now = +new Date();\n\n if (!previous && options.leading === false) {\n previous = now;\n }\n\n const remaining = wait - (now - previous);\n args = _arguments;\n\n if (remaining <= 0) {\n clearTimeout(timeout);\n timeout = null;\n previous = now;\n result = func.apply(context, args);\n } else if (!timeout && options.trailing !== false) {\n timeout = setTimeout(later, remaining);\n }\n\n return result;\n };\n}\n\n/**\n * Throttle decorator\n *\n * class MyClass {\n * throttleable(10)\n * myFn() { ... }\n * }\n *\n * @export\n * @param {number} duration\n * @param {*} [options]\n * @returns\n */\nexport function throttleable(duration: number, options?: any) {\n return function innerDecorator(target, key, descriptor) {\n return {\n configurable: true,\n enumerable: descriptor.enumerable,\n get: function getter() {\n Object.defineProperty(this, key, {\n configurable: true,\n enumerable: descriptor.enumerable,\n value: throttle(this, descriptor.value, duration, options)\n });\n\n return this[key];\n }\n };\n };\n}\n","export const colorSets = [\n {\n name: 'vivid',\n selectable: true,\n group: 'Ordinal',\n domain: [\n '#647c8a',\n '#3f51b5',\n '#2196f3',\n '#00b862',\n '#afdf0a',\n '#a7b61a',\n '#f3e562',\n '#ff9800',\n '#ff5722',\n '#ff4514'\n ]\n },\n {\n name: 'natural',\n selectable: true,\n group: 'Ordinal',\n domain: [\n '#bf9d76',\n '#e99450',\n '#d89f59',\n '#f2dfa7',\n '#a5d7c6',\n '#7794b1',\n '#afafaf',\n '#707160',\n '#ba9383',\n '#d9d5c3'\n ]\n },\n {\n name: 'cool',\n selectable: true,\n group: 'Ordinal',\n domain: [\n '#a8385d',\n '#7aa3e5',\n '#a27ea8',\n '#aae3f5',\n '#adcded',\n '#a95963',\n '#8796c0',\n '#7ed3ed',\n '#50abcc',\n '#ad6886'\n ]\n },\n {\n name: 'fire',\n selectable: true,\n group: 'Ordinal',\n domain: ['#ff3d00', '#bf360c', '#ff8f00', '#ff6f00', '#ff5722', '#e65100', '#ffca28', '#ffab00']\n },\n {\n name: 'solar',\n selectable: true,\n group: 'Continuous',\n domain: [\n '#fff8e1',\n '#ffecb3',\n '#ffe082',\n '#ffd54f',\n '#ffca28',\n '#ffc107',\n '#ffb300',\n '#ffa000',\n '#ff8f00',\n '#ff6f00'\n ]\n },\n {\n name: 'air',\n selectable: true,\n group: 'Continuous',\n domain: [\n '#e1f5fe',\n '#b3e5fc',\n '#81d4fa',\n '#4fc3f7',\n '#29b6f6',\n '#03a9f4',\n '#039be5',\n '#0288d1',\n '#0277bd',\n '#01579b'\n ]\n },\n {\n name: 'aqua',\n selectable: true,\n group: 'Continuous',\n domain: [\n '#e0f7fa',\n '#b2ebf2',\n '#80deea',\n '#4dd0e1',\n '#26c6da',\n '#00bcd4',\n '#00acc1',\n '#0097a7',\n '#00838f',\n '#006064'\n ]\n },\n {\n name: 'flame',\n selectable: false,\n group: 'Ordinal',\n domain: [\n '#A10A28',\n '#D3342D',\n '#EF6D49',\n '#FAAD67',\n '#FDDE90',\n '#DBED91',\n '#A9D770',\n '#6CBA67',\n '#2C9653',\n '#146738'\n ]\n },\n {\n name: 'ocean',\n selectable: false,\n group: 'Ordinal',\n domain: [\n '#1D68FB',\n '#33C0FC',\n '#4AFFFE',\n '#AFFFFF',\n '#FFFC63',\n '#FDBD2D',\n '#FC8A25',\n '#FA4F1E',\n '#FA141B',\n '#BA38D1'\n ]\n },\n {\n name: 'forest',\n selectable: false,\n group: 'Ordinal',\n domain: [\n '#55C22D',\n '#C1F33D',\n '#3CC099',\n '#AFFFFF',\n '#8CFC9D',\n '#76CFFA',\n '#BA60FB',\n '#EE6490',\n '#C42A1C',\n '#FC9F32'\n ]\n },\n {\n name: 'horizon',\n selectable: false,\n group: 'Ordinal',\n domain: [\n '#2597FB',\n '#65EBFD',\n '#99FDD0',\n '#FCEE4B',\n '#FEFCFA',\n '#FDD6E3',\n '#FCB1A8',\n '#EF6F7B',\n '#CB96E8',\n '#EFDEE0'\n ]\n },\n {\n name: 'neons',\n selectable: false,\n group: 'Ordinal',\n domain: [\n '#FF3333',\n '#FF33FF',\n '#CC33FF',\n '#0000FF',\n '#33CCFF',\n '#33FFFF',\n '#33FF66',\n '#CCFF33',\n '#FFCC00',\n '#FF6600'\n ]\n },\n {\n name: 'picnic',\n selectable: false,\n group: 'Ordinal',\n domain: [\n '#FAC51D',\n '#66BD6D',\n '#FAA026',\n '#29BB9C',\n '#E96B56',\n '#55ACD2',\n '#B7332F',\n '#2C83C9',\n '#9166B8',\n '#92E7E8'\n ]\n },\n {\n name: 'night',\n selectable: false,\n group: 'Ordinal',\n domain: [\n '#2B1B5A',\n '#501356',\n '#183356',\n '#28203F',\n '#391B3C',\n '#1E2B3C',\n '#120634',\n '#2D0432',\n '#051932',\n '#453080',\n '#75267D',\n '#2C507D',\n '#4B3880',\n '#752F7D',\n '#35547D'\n ]\n },\n {\n name: 'nightLights',\n selectable: false,\n group: 'Ordinal',\n domain: [\n '#4e31a5',\n '#9c25a7',\n '#3065ab',\n '#57468b',\n '#904497',\n '#46648b',\n '#32118d',\n '#a00fb3',\n '#1052a2',\n '#6e51bd',\n '#b63cc3',\n '#6c97cb',\n '#8671c1',\n '#b455be',\n '#7496c3'\n ]\n }\n];\n","import { range } from 'd3-array';\nimport { scaleBand, scaleLinear, scaleOrdinal, scaleQuantile } from 'd3-scale';\n\nimport { colorSets } from './color-sets';\n\nexport class ColorHelper {\n scale: any;\n colorDomain: any[];\n domain: any;\n customColors: any;\n\n constructor(scheme, domain, customColors?) {\n if (typeof scheme === 'string') {\n scheme = colorSets.find(cs => {\n return cs.name === scheme;\n });\n }\n this.colorDomain = scheme.domain;\n this.domain = domain;\n this.customColors = customColors;\n\n this.scale = this.generateColorScheme(scheme, this.domain);\n }\n\n generateColorScheme(scheme, domain) {\n if (typeof scheme === 'string') {\n scheme = colorSets.find(cs => {\n return cs.name === scheme;\n });\n }\n return scaleOrdinal().range(scheme.domain).domain(domain);\n }\n\n getColor(value) {\n if (value === undefined || value === null) {\n throw new Error('Value can not be null');\n }\n\n if (typeof this.customColors === 'function') {\n return this.customColors(value);\n }\n\n const formattedValue = value.toString();\n let found: any; // todo type customColors\n if (this.customColors && this.customColors.length > 0) {\n found = this.customColors.find(mapping => {\n return mapping.name.toLowerCase() === formattedValue.toLowerCase();\n });\n }\n\n if (found) {\n return found.value;\n } else {\n return this.scale(value);\n }\n }\n}\n","export interface ViewDimensions {\n width: number;\n height: number;\n}\n\nexport function calculateViewDimensions({ width, height }): ViewDimensions {\n let chartWidth = width;\n let chartHeight = height;\n\n chartWidth = Math.max(0, chartWidth);\n chartHeight = Math.max(0, chartHeight);\n\n return {\n width: Math.floor(chartWidth),\n height: Math.floor(chartHeight)\n };\n}\n","import { Output, EventEmitter, NgZone, Directive, ElementRef } from '@angular/core';\n\n/**\n * Visibility Observer\n */\n@Directive({\n // tslint:disable-next-line:directive-selector\n selector: 'visibility-observer',\n standalone: false\n})\nexport class VisibilityObserver {\n @Output() visible: EventEmitter<any> = new EventEmitter();\n\n timeout: any;\n isVisible: boolean = false;\n\n constructor(private element: ElementRef, private zone: NgZone) {\n this.runCheck();\n }\n\n destroy(): void {\n clearTimeout(this.timeout);\n }\n\n onVisibilityChange(): void {\n // trigger zone recalc for columns\n this.zone.run(() => {\n this.isVisible = true;\n this.visible.emit(true);\n });\n }\n\n runCheck(): void {\n const check = () => {\n if (!this.element) {\n return;\n }\n\n // https://davidwalsh.name/offsetheight-visibility\n const { offsetHeight, offsetWidth } = this.element.nativeElement;\n\n if (offsetHeight && offsetWidth) {\n clearTimeout(this.timeout);\n this.onVisibilityChange();\n } else {\n clearTimeout(this.timeout);\n this.zone.runOutsideAngular(() => {\n this.timeout = setTimeout(() => check(), 100);\n });\n }\n };\n\n this.zone.runOutsideAngular(() => {\n this.timeout = setTimeout(() => check());\n });\n }\n}\n","import { Layout } from '../../models/layout.model';\nimport { Graph } from '../../models/graph.model';\nimport { id } from '../../utils/id';\nimport * as dagre from 'dagre';\nimport { Edge } from '../../models/edge.model';\n\nexport enum Orientation {\n LEFT_TO_RIGHT = 'LR',\n RIGHT_TO_LEFT = 'RL',\n TOP_TO_BOTTOM = 'TB',\n BOTTOM_TO_TOM = 'BT'\n}\nexport enum Alignment {\n CENTER = 'C',\n UP_LEFT = 'UL',\n UP_RIGHT = 'UR',\n DOWN_LEFT = 'DL',\n DOWN_RIGHT = 'DR'\n}\n\nexport interface DagreSettings {\n orientation?: Orientation;\n marginX?: number;\n marginY?: number;\n edgePadding?: number;\n rankPadding?: number;\n nodePadding?: number;\n align?: Alignment;\n acyclicer?: 'greedy' | undefined;\n ranker?: 'network-simplex' | 'tight-tree' | 'longest-path';\n multigraph?: boolean;\n compound?: boolean;\n}\n\nexport class DagreLayout implements Layout {\n defaultSettings: DagreSettings = {\n orientation: Orientation.LEFT_TO_RIGHT,\n marginX: 20,\n marginY: 20,\n edgePadding: 100,\n rankPadding: 100,\n nodePadding: 50,\n multigraph: true,\n compound: true\n };\n settings: DagreSettings = {};\n\n dagreGraph: any;\n dagreNodes: any;\n dagreEdges: any;\n\n run(graph: Graph): Graph {\n this.createDagreGraph(graph);\n dagre.layout(this.dagreGraph);\n\n graph.edgeLabels = this.dagreGraph._edgeLabels;\n\n for (const dagreNodeId in this.dagreGraph._nodes) {\n const dagreNode = this.dagreGraph._nodes[dagreNodeId];\n const node = graph.nodes.find(n => n.id === dagreNode.id);\n node.position = {\n x: dagreNode.x,\n y: dagreNode.y\n };\n node.dimension = {\n width: dagreNode.width,\n height: dagreNode.height\n };\n }\n\n return graph;\n }\n\n updateEdge(graph: Graph, edge: Edge): Graph {\n const sourceNode = graph.nodes.find(n => n.id === edge.source);\n const targetNode = graph.nodes.find(n => n.id === edge.target);\n\n // determine new arrow position\n const dir = sourceNode.position.y <= targetNode.position.y ? -1 : 1;\n const startingPoint = {\n x: sourceNode.position.x,\n y: sourceNode.position.y - dir * (sourceNode.dimension.height / 2)\n };\n const endingPoint = {\n x: targetNode.position.x,\n y: targetNode.position.y + dir * (targetNode.dimension.height / 2)\n };\n\n // generate new points\n edge.points = [startingPoint, endingPoint];\n return graph;\n }\n\n createDagreGraph(graph: Graph): any {\n const settings = Object.assign({}, this.defaultSettings, this.settings);\n this.dagreGraph = new dagre.graphlib.Graph({ compound: settings.compound, multigraph: settings.multigraph });\n\n this.dagreGraph.setGraph({\n rankdir: settings.orientation,\n marginx: settings.marginX,\n marginy: settings.marginY,\n edgesep: settings.edgePadding,\n ranksep: settings.rankPadding,\n nodesep: settings.nodePadding,\n align: settings.align,\n acyclicer: settings.acyclicer,\n ranker: settings.ranker,\n multigraph: settings.multigraph,\n compound: settings.compound\n });\n\n // Default to assigning a new object as a label for each new edge.\n this.dagreGraph.setDefaultEdgeLabel(() => {\n return {\n /* empty */\n };\n });\n\n this.dagreNodes = graph.nodes.map(n => {\n const node: any = Object.assign({}, n);\n node.width = n.dimension.width;\n node.height = n.dimension.height;\n node.x = n.position.x;\n node.y = n.position.y;\n return node;\n });\n\n this.dagreEdges = graph.edges.map(l => {\n const newLink: any = Object.assign({}, l);\n if (!newLink.id) {\n newLink.id = id();\n }\n return newLink;\n });\n\n for (const node of this.dagreNodes) {\n if (!node.width) {\n node.width = 20;\n }\n if (!node.height) {\n node.height = 30;\n }\n\n // update dagre\n this.dagreGraph.setNode(node.id, node);\n }\n\n // update dagre\n for (const edge of this.dagreEdges) {\n if (settings.multigraph) {\n this.dagreGraph.setEdge(edge.source, edge.target, edge, edge.id);\n } else {\n this.dagreGraph.setEdge(edge.source, edge.target);\n }\n }\n\n return this.dagreGraph;\n }\n}\n","import { Layout } from '../../models/layout.model';\nimport { Graph } from '../../models/graph.model';\nimport { id } from '../../utils/id';\nimport * as dagre from 'dagre';\nimport { Edge } from '../../models/edge.model';\nimport { Node, ClusterNode } from '../../models/node.model';\nimport { DagreSettings, Orientation } from './dagre';\n\nexport class DagreClusterLayout implements Layout {\n defaultSettings: DagreSettings = {\n orientation: Orientation.LEFT_TO_RIGHT,\n marginX: 20,\n marginY: 20,\n edgePadding: 100,\n rankPadding: 100,\n nodePadding: 50,\n multigraph: true,\n compound: true\n };\n settings: DagreSettings = {};\n\n dagreGraph: any;\n dagreNodes: Node[];\n dagreClusters: ClusterNode[];\n dagreEdges: any;\n\n run(graph: Graph): Graph {\n this.createDagreGraph(graph);\n dagre.layout(this.dagreGraph);\n\n graph.edgeLabels = this.dagreGraph._edgeLabels;\n\n const dagreToOutput = node => {\n const dagreNode = this.dagreGraph._nodes[node.id];\n return {\n ...node,\n position: {\n x: dagreNode.x,\n y: dagreNode.y\n },\n dimension: {\n width: dagreNode.width,\n height: dagreNode.height\n }\n };\n };\n\n graph.clusters = (graph.clusters || []).map(dagreToOutput);\n graph.nodes = graph.nodes.map(dagreToOutput);\n\n return graph;\n }\n\n updateEdge(graph: Graph, edge: Edge): Graph {\n const sourceNode = graph.nodes.find(n => n.id === edge.source);\n const targetNode = graph.nodes.find(n => n.id === edge.target);\n\n // determine new arrow position\n const dir = sourceNode.position.y <= targetNode.position.y ? -1 : 1;\n const startingPoint = {\n x: sourceNode.position.x,\n y: sourceNode.position.y - dir * (sourceNode.dimension.height / 2)\n };\n const endingPoint = {\n x: targetNode.position.x,\n y: targetNode.position.y + dir * (targetNode.dimension.height / 2)\n };\n\n // generate new points\n edge.points = [startingPoint, endingPoint];\n return graph;\n }\n\n createDagreGraph(graph: Graph): any {\n const settings = Object.assign({}, this.defaultSettings, this.settings);\n this.dagreGraph = new dagre.graphlib.Graph({ compound: settings.compound, multigraph: settings.multigraph });\n this.dagreGraph.setGraph({\n rankdir: settings.orientation,\n marginx: settings.marginX,\n marginy: settings.marginY,\n edgesep: settings.edgePadding,\n ranksep: settings.rankPadding,\n nodesep: settings.nodePadding,\n align: settings.align,\n acyclicer: settings.acyclicer,\n ranker: settings.ranker,\n multigraph: settings.multigraph,\n compound: settings.compound\n });\n\n // Default to assigning a new object as a label for each new edge.\n this.dagreGraph.setDefaultEdgeLabel(() => {\n return {\n /* empty */\n };\n });\n\n this.dagreNodes = graph.nodes.map((n: Node) => {\n const node: any = Object.assign({}, n);\n node.width = n.dimension.width;\n node.height = n.dimension.height;\n node.x = n.position.x;\n node.y = n.position.y;\n return node;\n });\n\n this.dagreClusters = graph.clusters || [];\n\n this.dagreEdges = graph.edges.map(l => {\n const newLink: any = Object.assign({}, l);\n if (!newLink.id) {\n newLink.id = id();\n }\n return newLink;\n });\n\n for (const node of this.dagreNodes) {\n this.dagreGraph.setNode(node.id, node);\n }\n\n for (const cluster of this.dagreClusters) {\n this.dagreGraph.setNode(cluster.id, cluster);\n cluster.childNodeIds.forEach(childNodeId => {\n this.dagreGraph.setParent(childNodeId, cluster.id);\n });\n }\n\n // update dagre\n for (const edge of this.dagreEdges) {\n if (settings.multigraph) {\n this.dagreGraph.setEdge(edge.source, edge.target, edge, edge.id);\n } else {\n this.dagreGraph.setEdge(edge.source, edge.target);\n }\n }\n\n return this.dagreGraph;\n }\n}\n","import { Layout } from '../../models/layout.model';\nimport { Graph } from '../../models/graph.model';\nimport { id } from '../../utils/id';\nimport * as dagre from 'dagre';\nimport { Edge } from '../../models/edge.model';\nimport { DagreSettings, Orientation } from './dagre';\n\nexport interface DagreNodesOnlySettings extends DagreSettings {\n curveDistance?: number;\n}\n\nconst DEFAULT_EDGE_NAME = '\\x00';\nconst GRAPH_NODE = '\\x00';\nconst EDGE_KEY_DELIM = '\\x01';\n\nexport class DagreNodesOnlyLayout implements Layout {\n defaultSettings: DagreNodesOnlySettings = {\n orientation: Orientation.LEFT_TO_RIGHT,\n marginX: 20,\n marginY: 20,\n edgePadding: 100,\n rankPadding: 100,\n nodePadding: 50,\n curveDistance: 20,\n multigraph: true,\n compound: true\n };\n settings: DagreNodesOnlySettings = {};\n\n dagreGraph: any;\n dagreNodes: any;\n dagreEdges: any;\n\n run(graph: Graph): Graph {\n this.createDagreGraph(graph);\n dagre.layout(this.dagreGraph);\n\n graph.edgeLabels = this.dagreGraph._edgeLabels;\n\n for (const dagreNodeId in this.dagreGraph._nodes) {\n const dagreNode = this.dagreGraph._nodes[dagreNodeId];\n const node = graph.nodes.find(n => n.id === dagreNode.id);\n node.position = {\n x: dagreNode.x,\n y: dagreNode.y\n };\n node.dimension = {\n width: dagreNode.width,\n height: dagreNode.height\n };\n }\n for (const edge of graph.edges) {\n this.updateEdge(graph, edge);\n }\n\n return graph;\n }\n\n updateEdge(graph: Graph, edge: Edge): Graph {\n const sourceNode = graph.nodes.find(n => n.id === edge.source);\n const targetNode = graph.nodes.find(n => n.id === edge.target);\n const rankAxis: 'x' | 'y' = this.settings.orientation === 'BT' || this.settings.orientation === 'TB' ? 'y' : 'x';\n const orderAxis: 'x' | 'y' = rankAxis === 'y' ? 'x' : 'y';\n const rankDimension = rankAxis === 'y' ? 'height' : 'width';\n // determine new arrow position\n const dir = sourceNode.position[rankAxis] <= targetNode.position[rankAxis] ? -1 : 1;\n const startingPoint = {\n [orderAxis]: sourceNode.position[orderAxis],\n [rankAxis]: sourceNode.position[rankAxis] - dir * (sourceNode.dimension[rankDimension] / 2)\n };\n const endingPoint = {\n [orderAxis]: targetNode.position[orderAxis],\n [rankAxis]: targetNode.position[rankAxis] + dir * (targetNode.dimension[rankDimension] / 2)\n };\n\n const curveDistance = this.settings.curveDistance || this.defaultSettings.curveDistance;\n // generate new points\n edge.points = [\n startingPoint,\n {\n [orderAxis]: startingPoint[orderAxis],\n [rankAxis]: startingPoint[rankAxis] - dir * curveDistance\n },\n {\n [orderAxis]: endingPoint[orderAxis],\n [rankAxis]: endingPoint[rankAxis] + dir * curveDistance\n },\n endingPoint\n ];\n const edgeLabelId = `${edge.source}${EDGE_KEY_DELIM}${edge.target}${EDGE_KEY_DELIM}${DEFAULT_EDGE_NAME}`;\n const matchingEdgeLabel = graph.edgeLabels[edgeLabelId];\n if (matchingEdgeLabel) {\n matchingEdgeLabel.points = edge.points;\n }\n return graph;\n }\n\n createDagreGraph(graph: Graph): any {\n const settings = Object.assign({}, this.defaultSettings, this.settings);\n this.dagreGraph = new dagre.graphlib.Graph({ compound: settings.compound, multigraph: settings.multigraph });\n this.dagreGraph.setGraph({\n rankdir: settings.orientation,\n marginx: settings.marginX,\n marginy: settings.marginY,\n edgesep: settings.edgePadding,\n ranksep: settings.rankPadding,\n nodesep: settings.nodePadding,\n align: settings.align,\n acyclicer: settings.acyclicer,\n ranker: settings.ranker,\n multigraph: settings.multigraph,\n compound: settings.compound\n });\n\n // Default to assigning a new object as a label for each new edge.\n this.dagreGraph.setDefaultEdgeLabel(() => {\n return {\n /* empty */\n };\n });\n\n this.dagreNodes = graph.nodes.map(n => {\n const node: any = Object.assign({}, n);\n node.width = n.dimension.width;\n node.height = n.dimension.height;\n node.x = n.position.x;\n node.y = n.position.y;\n return node;\n });\n\n this.dagreEdges = graph.edges.map(l => {\n const newLink: any = Object.assign({}, l);\n if (!newLink.id) {\n newLink.id = id();\n }\n return newLink;\n });\n\n for (const node of this.dagreNodes) {\n if (!node.width) {\n node.width = 20;\n }\n if (!node.height) {\n node.height = 30;\n }\n\n // update dagre\n this.dagreGraph.setNode(node.id, node);\n }\n\n // update dagre\n for (const edge of this.dagreEdges) {\n if (settings.multigraph) {\n this.dagreGraph.setEdge(edge.source, edge.target, edge, edge.id);\n } else {\n this.dagreGraph.setEdge(edge.source, edge.target);\n }\n }\n\n return this.dagreGraph;\n }\n}\n","import { Layout } from '../../models/layout.model';\nimport { Graph } from '../../models/graph.model';\nimport { Node } from '../../models/node.model';\nimport { id } from '../../utils/id';\nimport { forceCollide, forceLink, forceManyBody, forceSimulation } from 'd3-force';\nimport { Edge } from '../../models/edge.model';\nimport { Observable, Subject } from 'rxjs';\nimport { NodePosition } from '../../models';\n\nexport interface D3ForceDirectedSettings {\n force?: any;\n forceLink?: any;\n}\nexport interface D3Node {\n id?: string;\n x: number;\n y: number;\n width?: number;\n height?: number;\n fx?: number;\n fy?: number;\n}\nexport interface D3Edge {\n source: string | D3Node;\n target: string | D3Node;\n midPoint: NodePosition;\n}\nexport interface D3Graph {\n nodes: D3Node[];\n edges: D3Edge[];\n}\nexport interface MergedNode extends D3Node, Node {\n id: string;\n}\n\nexport function toD3Node(maybeNode: string | D3Node): D3Node {\n if (typeof maybeNode === 'string') {\n return {\n id: maybeNode,\n x: 0,\n y: 0\n };\n }\n return maybeNode;\n}\n\nexport class D3ForceDirectedLayout implements Layout {\n defaultSettings: D3ForceDirectedSettings = {\n force: forceSimulation<any>().force('charge', forceManyBody().strength(-150)).force('collide', forceCollide(5)),\n forceLink: forceLink<any, any>()\n .id(node => node.id)\n .distance(() => 100)\n };\n settings: D3ForceDirectedSettings = {};\n\n inputGraph: Graph;\n outputGraph: Graph;\n d3Graph: D3Graph;\n outputGraph$: Subject<Graph> = new Subject();\n\n draggingStart: { x: number; y: number };\n\n run(graph: Graph): Observable<Graph> {\n this.inputGraph = graph;\n this.d3Graph = {\n nodes: [...this.inputGraph.nodes.map(n => ({ ...n }))] as any,\n edges: [...this.inputGraph.edges.map(e => ({ ...e }))] as any\n };\n this.outputGraph = {\n nodes: [],\n edges: [],\n edgeLabels: []\n };\n this.outputGraph$.next(this.outputGraph);\n this.settings = Object.assign({}, this.defaultSettings, this.settings);\n if (this.settings.force) {\n this.settings.force\n .nodes(this.d3Graph.nodes)\n .force('link', this.settings.forceLink.links(this.d3Graph.edges))\n .alpha(0.5)\n .restart()\n .on('tick', () => {\n this.outputGraph$.next(this.d3GraphToOutputGraph(this.d3Graph));\n });\n }\n\n return this.outputGraph$.asObservable();\n }\n\n updateEdge(graph: Graph, edge: Edge): Observable<Graph> {\n const settings = Object.assign({}, this.defaultSettings, this.settings);\n if (settings.force) {\n settings.force\n .nodes(this.d3Graph.nodes)\n .force('link', settings.forceLink.links(this.d3Graph.edges))\n .alpha(0.5)\n .restart()\n .on('tick', () => {\n this.outputGraph$.next(this.d3GraphToOutputGraph(this.d3Graph));\n });\n }\n\n return this.outputGraph$.asObservable();\n }\n\n d3GraphToOutputGraph(d3Graph: D3Graph): Graph {\n this.outputGraph.nodes = this.d3Graph.nodes.map((node: MergedNode) => ({\n ...node,\n id: node.id || id(),\n position: {\n x: node.x,\n y: node.y\n },\n dimension: {\n width: (node.dimension && node.dimension.width) || 20,\n height: (node.dimension && node.dimension.height) || 20\n },\n transform: `translate(${node.x - ((node.dimension && node.dimension.width) || 20) / 2 || 0}, ${\n node.y - ((node.dimension && node.dimension.height) || 20) / 2 || 0\n })`\n }));\n\n this.outputGraph.edges = this.d3Graph.edges.map(edge => ({\n ...edge,\n source: toD3Node(edge.source).id,\n target: toD3Node(edge.target).id,\n points: [\n {\n x: toD3Node(edge.source).x,\n y: toD3Node(edge.source).y\n },\n {\n x: toD3Node(edge.target).x,\n y: toD3Node(edge.target).y\n }\n ]\n }));\n\n this.outputGraph.edgeLabels = this.outputGraph.edges;\n return this.outputGraph;\n }\n\n onDragStart(draggingNode: Node, $event: MouseEvent): void {\n this.settings.force.alphaTarget(0.3).restart();\n const node = this.d3Graph.nodes.find(d3Node => d3Node.id === draggingNode.id);\n if (!node) {\n return;\n }\n this.draggingStart = { x: $event.x - node.x, y: $event.y - node.y };\n node.fx = $event.x - this.draggingStart.x;\n node.fy = $event.y - this.draggingStart.y;\n }\n\n onDrag(draggingNode: Node, $event: MouseEvent): void {\n if (!draggingNode) {\n return;\n }\n const node = this.d3Graph.nodes.find(d3Node => d3Node.id === draggingNode.id);\n if (!node) {\n return;\n }\n node.fx = $event.x - this.draggingStart.x;\n node.fy = $event.y - this.draggingStart.y;\n }\n\n onDragEnd(draggingNode: Node, $event: MouseEvent): void {\n if (!draggingNode) {\n return;\n }\n const node = this.d3Graph.nodes.find(d3Node => d3Node.id === draggingNode.id);\n if (!node) {\n return;\n }\n\n this.settings.force.alphaTarget(0);\n node.fx = undefined;\n node.fy = undefined;\n }\n}\n","import { Layout } from '../../models/layout.model';\nimport { Graph } from '../../models/graph.model';\nimport { Node, ClusterNode } from '../../models/node.model';\nimport { id } from '../../utils/id';\nimport { d3adaptor, ID3StyleLayoutAdaptor, Layout as ColaLayout, Group, InputNode, Link, Rectangle } from 'webcola';\nimport * as d3Dispatch from 'd3-dispatch';\nimport * as d3Force from 'd3-force';\nimport * as d3Timer from 'd3-timer';\nimport { Edge } from '../../models/edge.model';\nimport { Observable, Subject } from 'rxjs';\nimport { ViewDimensions } from '../../utils/view-dimensions.helper';\n\nexport interface ColaForceDirectedSettings {\n force?: ColaLayout & ID3StyleLayoutAdaptor;\n forceModifierFn?: (force: ColaLayout & ID3StyleLayoutAdaptor) => ColaLayout & ID3StyleLayoutAdaptor;\n onTickListener?: (internalGraph: ColaGraph) => void;\n viewDimensions?: ViewDimensions;\n}\nexport interface ColaGraph {\n groups: Group[];\n nodes: InputNode[];\n links: Array<Link<number>>;\n}\nexport function toNode(nodes: InputNode[], nodeRef: InputNode | number): InputNode {\n if (typeof nodeRef === 'number') {\n return nodes[nodeRef];\n }\n return nodeRef;\n}\n\nexport class ColaForceDirectedLayout implements Layout {\n defaultSettings: ColaForceDirectedSettings = {\n force: d3adaptor({\n ...d3Dispatch,\n ...d3Force,\n ...d3Timer\n })\n .linkDistance(150)\n .avoidOverlaps(true),\n viewDimensions: {\n width: 600,\n height: 600\n }\n };\n settings: ColaForceDirectedSettings = {};\n\n inputGraph: Graph;\n outputGraph: Graph;\n internalGraph: ColaGraph & { groupLinks?: Edge[] };\n outputGraph$: Subject<Graph> = new Subject();\n\n draggingStart: { x: number; y: number };\n\n run(graph: Graph): Observable<Graph> {\n this.inputGraph = graph;\n if (!this.inputGraph.clusters) {\n this.inputGraph.clusters = [];\n }\n this.internalGraph = {\n nodes: [\n ...this.inputGraph.nodes.map(n => ({\n ...n,\n width: n.dimension ? n.dimension.width : 20,\n height: n.dimension ? n.dimension.height : 20\n }))\n ] as any,\n groups: [\n ...this.inputGraph.clusters.map(\n (cluster): Group => ({\n padding: 5,\n groups: cluster.childNodeIds\n .map(nodeId => <any>this.inputGraph.clusters.findIndex(node => node.id === nodeId))\n .filter(x => x >= 0),\n leaves: cluster.childNodeIds\n .map(nodeId => <any>this.inputGraph.nodes.findIndex(node => node.id === nodeId))\n .filter(x => x >= 0)\n })\n )\n ],\n links: [\n ...this.inputGraph.edges\n .map(e => {\n const sourceNodeIndex = this.inputGraph.nodes.findIndex(node => e.source === node.id);\n const targetNodeIndex = this.inputGraph.nodes.findIndex(node => e.target === node.id);\n if (sourceNodeIndex === -1 || targetNodeIndex === -1) {\n return undefined;\n }\n return {\n ...e,\n source: sourceNodeIndex,\n target: targetNodeIndex\n };\n })\n .filter(x => !!x)\n ] as any,\n groupLinks: [\n ...this.inputGraph.edges\n .map(e => {\n const sourceNodeIndex = this.inputGraph.nodes.findIndex(node => e.source === node.id);\n const targetNodeIndex = this.inputGraph.nodes.findIndex(node => e.target === node.id);\n if (sourceNodeIndex >= 0 && targetNodeIndex >= 0) {\n return undefined;\n }\n return e;\n })\n .filter(x => !!x)\n ]\n };\n this.outputGraph = {\n nodes: [],\n clusters: [],\n edges: [],\n edgeLabels: []\n };\n this.outputGraph$.next(this.outputGraph);\n this.settings = Object.assign({}, this.defaultSettings, this.settings);\n if (this.settings.force) {\n this.settings.force = this.settings.force\n .nodes(this.internalGraph.nodes)\n .groups(this.internalGraph.groups)\n .links(this.internalGraph.links)\n .alpha(0.5)\n .on('tick', () => {\n if (this.settings.onTickListener) {\n this.settings.onTickListener(this.internalGraph);\n }\n this.outputGraph$.next(this.internalGraphToOutputGraph(this.internalGraph));\n });\n if (this.settings.viewDimensions) {\n this.settings.force = this.settings.force.size([\n this.settings.viewDimensions.width,\n this.settings.viewDimensions.height\n ]);\n }\n if (this.settings.forceModifierFn) {\n this.settings.force = this.settings.forceModifierFn(this.settings.force);\n }\n this.settings.force.start();\n }\n\n return this.outputGraph$.asObservable();\n }\n\n updateEdge(graph: Graph, edge: Edge): Observable<Graph> {\n const settings = Object.assign({}, this.defaultSettings, this.settings);\n if (settings.force) {\n settings.force.start();\n }\n\n return this.outputGraph$.asObservable();\n }\n\n internalGraphToOutputGraph(internalGraph: any): Graph {\n this.outputGraph.nodes = internalGraph.nodes.map(node => ({\n ...node,\n id: node.id || id(),\n position: {\n x: node.x,\n y: node.y\n },\n dimension: {\n width: (node.dimension && node.dimension.width) || 20,\n height: (node.dimension && node.dimension.height) || 20\n },\n transform: `translate(${node.x - ((node.dimension && node.dimension.width) || 20) / 2 || 0}, ${\n node.y - ((node.dimension && node.dimension.height) || 20) / 2 || 0\n })`\n }));\n\n this.outputGraph.edges = internalGraph.links\n .map(edge => {\n const source: any = toNode(internalGraph.nodes, edge.source);\n const target: any = toNode(internalGraph.nodes, edge.target);\n return {\n ...edge,\n source: source.id,\n target: target.id,\n points: [\n (source.bounds as Rectangle).rayIntersection(target.bounds.cx(), target.bounds.cy()),\n (target.bounds as Rectangle).rayIntersection(source.bounds.cx(), source.bounds.cy())\n ]\n };\n })\n .concat(\n internalGraph.groupLinks.map(groupLink => {\n const sourceNode = internalGraph.nodes.find(foundNode => (foundNode as any).id === groupLink.source);\n const targetNode = internalGraph.nodes.find(foundNode => (foundNode as any).id === groupLink.target);\n const source =\n sourceNode || internalGraph.groups.find(foundGroup => (foundGroup as any).id === groupLink.source);\n const target =\n targetNode || internalGraph.groups.find(foundGroup => (foundGroup as any).id === groupLink.target);\n return {\n ...groupLink,\n source: source.id,\n target: target.id,\n points: [\n (source.bounds as Rectangle).rayIntersection(target.bounds.cx(), target.bounds.cy()),\n (target.bounds as Rectangle).rayIntersection(source.bounds.cx(), source.bounds.cy())\n ]\n };\n })\n );\n\n this.outputGraph.clusters = internalGraph.groups.map((group, index): ClusterNode => {\n const inputGroup = this.inputGraph.clusters[index];\n return {\n ...inputGroup,\n dimension: {\n width: group.bounds ? group.bounds.width() : 20,\n height: group.bounds ? group.bounds.height() : 20\n },\n position: {\n x: group.bounds ? group.bounds.x + group.bounds.width() / 2 : 0,\n y: group.bounds ? group.bounds.y + group.bounds.height() / 2 : 0\n }\n };\n });\n this.outputGraph.edgeLabels = this.outputGraph.edges;\n return this.outputGraph;\n }\n\n onDragStart(draggingNode: Node, $event: MouseEvent): void {\n const nodeIndex = this.outputGraph.nodes.findIndex(foundNode => foundNode.id === draggingNode.id);\n const node = this.internalGraph.nodes[nodeIndex];\n if (!node) {\n return;\n }\n this.draggingStart = { x: node.x - $event.x, y: node.y - $event.y };\n node.fixed = 1;\n this.settings.force.start();\n }\n\n onDrag(draggingNode: Node, $event: MouseEvent): void {\n if (!draggingNode) {\n return;\n }\n const nodeIndex = this.outputGraph.nodes.findIndex(foundNode => foundNode.id === draggingNode.id);\n const node = this.internalGraph.nodes[nodeIndex];\n if (!node) {\n return;\n }\n node.x = this.draggingStart.x + $event.x;\n node.y = this.draggingStart.y + $event.y;\n }\n\n onDragEnd(draggingNode: Node, $event: MouseEvent): void {\n if (!draggingNode) {\n return;\n }\n const nodeIndex = this.outputGraph.nodes.findIndex(foundNode => foundNode.id === draggingNode.id);\n const node = this.internalGraph.nodes[nodeIndex];\n if (!node) {\n return;\n }\n\n node.fixed = 0;\n }\n}\n","import { Injectable } from '@angular/core';\nimport { Layout } from '../../models/layout.model';\nimport { DagreLayout } from './dagre';\nimport { DagreClusterLayout } from './dagreCluster';\nimport { DagreNodesOnlyLayout } from './dagreNodesOnly';\nimport { D3ForceDirectedLayout } from './d3ForceDirected';\nimport { ColaForceDirectedLayout } from './colaForceDirected';\n\nconst layouts = {\n dagre: DagreLayout,\n dagreCluster: DagreClusterLayout,\n dagreNodesOnly: DagreNodesOnlyLayout,\n d3ForceDirected: D3ForceDirectedLayout,\n colaForceDirected: ColaForceDirectedLayout\n};\n\n@Injectable()\nexport class LayoutService {\n getLayout(name: string): Layout {\n if (layouts[name]) {\n return new layouts[name]();\n } else {\n throw new Error(`Unknown layout type '${name}'`);\n }\n }\n}\n","import { Directive, Output, HostListener, EventEmitter } from '@angular/core';\n\n/**\n * Mousewheel directive\n * https://github.com/SodhanaLibrary/angular2-examples/blob/master/app/mouseWheelDirective/mousewheel.directive.ts\n *\n * @export\n */\n// tslint:disable-next-line: directive-selector\n@Directive({\n selector: '[mouseWheel]',\n standalone: false\n})\nexport class MouseWheelDirective {\n @Output()\n mouseWheelUp = new EventEmitter();\n @Output()\n mouseWheelDown = new EventEmitter();\n\n @HostListener('mousewheel', ['$event'])\n onMouseWheelChrome(event: any): void {\n this.mouseWheelFunc(event);\n }\n\n @HostListener('DOMMouseScroll', ['$event'])\n onMouseWheelFirefox(event: any): void {\n this.mouseWheelFunc(event);\n }\n\n @HostListener('wheel', ['$event'])\n onWheel(event: any): void {\n this.mouseWheelFunc(event);\n }\n\n @HostListener('onmousewheel', ['$event'])\n onMouseWheelIE(event: any): void {\n this.mouseWheelFunc(event);\n }\n\n mouseWheelFunc(event: any): void {\n if (window.event) {\n event = window.event;\n }\n\n const delta: number = Math.max(-1, Math.min(1, event.wheelDelta || -event.detail || event.deltaY || event.deltaX));\n // Firefox don't have native support for wheel event, as a result delta values are reverse\n const isWheelMouseUp: boolean = event.wheelDelta ? delta > 0 : delta < 0;\n const isWheelMouseDown: boolean = event.wheelDelta ? delta < 0 : delta > 0;\n if (isWheelMouseUp) {\n this.mouseWheelUp.emit(event);\n } else if (isWheelMouseDown) {\n this.mouseWheelDown.emit(event);\n }\n\n // for IE\n event.returnValue = false;\n\n // for Chrome and Firefox\n if (event.preventDefault) {\n event.preventDefault();\n }\n }\n}\n","// rename transition due to conflict with d3 transition\nimport { animate, style, transition as ngTransition, trigger } from '@angular/animations';\nimport {\n AfterViewInit,\n ChangeDetectionStrategy,\n Component,\n ContentChild,\n ElementRef,\n EventEmitter,\n HostListener,\n Input,\n OnDestroy,\n OnInit,\n Output,\n QueryList,\n TemplateRef,\n ViewChildren,\n ViewEncapsulation,\n NgZone,\n ChangeDetectorRef,\n OnChanges,\n SimpleChanges\n} from '@angular/core';\nimport { select } from 'd3-selection';\nimport * as shape from 'd3-shape';\nimport * as ease from 'd3-ease';\nimport 'd3-transition';\nimport { Observable, Subscription, of, fromEvent as observableFromEvent, Subject } from 'rxjs';\nimport { first, debounceTime, takeUntil } from 'rxjs/operators';\nimport { identity, scale, smoothMatrix, toSVG, transform, translate } from 'transformation-matrix';\nimport { Layout } from '../models/layout.model';\nimport { LayoutService } from './layouts/layout.service';\nimport { Edge } from '../models/edge.model';\nimport { Node, ClusterNode, CompoundNode } from '../models/node.model';\nimport { Graph } from '../models/graph.model';\nimport { id } from '../utils/id';\nimport { PanningAxis } from '../enums/panning.enum';\nimport { MiniMapPosition } from '../enums/mini-map-position.enum';\nimport { throttleable } from '../utils/throttle';\nimport { ColorHelper } from '../utils/color.helper';\nimport { ViewDimensions, calculateViewDimensions } from '../utils/view-dimensions.helper';\nimport { VisibilityObserver } from '../utils/visibility-observer';\n\n/**\n * Matrix\n */\nexport interface Matrix {\n a: number;\n b: number;\n c: number;\n d: number;\n e: number;\n f: number;\n}\n\nexport interface NgxGraphZoomOptions {\n autoCenter?: boolean;\n force?: boolean;\n}\n\nexport enum NgxGraphStates {\n Init = 'init',\n Subscribe = 'subscribe',\n Transform = 'transform',\n /* eslint-disable @typescript-eslint/no-shadow */\n Output = 'output'\n}\n\nexport interface NgxGraphStateChangeEvent {\n state: NgxGraphStates;\n}\n\n@Component({\n selector: 'ngx-graph',\n styleUrls: ['./graph.component.scss'],\n templateUrl: 'graph.component.html',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n animations: [\n trigger('animationState', [\n ngTransition(':enter', [style({ opacity: 0 }), animate('500ms 100ms', style({ opacity: 1 }))])\n ])\n ],\n standalone: false\n})\nexport class GraphComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit {\n @Input() nodes: Node[] = [];\n @Input() clusters: ClusterNode[] = [];\n @Input() compoundNodes: CompoundNode[] = [];\n @Input() links: Edge[] = [];\n @Input() activeEntries: any[] = [];\n @Input() curve: any;\n @Input() draggingEnabled = true;\n @Input() nodeHeight: number;\n @Input() nodeMaxHeight: number;\n @Input() nodeMinHeight: number;\n @Input() nodeWidth: number;\n @Input() nodeMinWidth: number;\n @Input() nodeMaxWidth: number;\n @Input() panningEnabled: boolean = true;\n @Input() panningAxis: PanningAxis = PanningAxis.Both;\n @Input() enableZoom = true;\n @Input() zoomSpeed = 0.1;\n @Input() minZoomLevel = 0.1;\n @Input() maxZoomLevel = 4.0;\n @Input() autoZoom = false;\n @Input() panOnZoom = true;\n @Input() animate? = false;\n @Input() autoCenter = false;\n @Input() update$: Observable<any>;\n @Input() center$: Observable<any>;\n @Input() zoomToFit$: Observable<NgxGraphZoomOptions>;\n @Input() panToNode$: Observable<any>;\n @Input() layout: string | Layout;\n @Input() layoutSettings: any;\n @Input() enableTrackpadSupport = false;\n @Input() showMiniMap: boolean = false;\n @Input() miniMapMaxWidth: number = 100;\n @Input() miniMapMaxHeight: number;\n @Input() miniMapPosition: MiniMapPosition = MiniMapPosition.UpperRight;\n @Input() view: [number, number];\n @Input() scheme: any = 'cool';\n @Input() customColors: any;\n @Input() deferDisplayUntilPosition: boolean = false;\n @Input() centerNodesOnPositionChange = true;\n @Input() enablePreUpdateTransform = true;\n\n @Output() select = new EventEmitter();\n @Output() activate: EventEmitter<any> = new EventEmitter();\n @Output() deactivate: EventEmitter<any> = new EventEmitter();\n @Output() zoomChange: EventEmitter<number> = new EventEmitter();\n @Output() clickHandler: EventEmitter<MouseEvent> = new EventEmitter();\n @Output() stateChange: EventEmitter<NgxGraphStateChangeEvent> = new EventEmitter();\n\n @ContentChild('linkTemplate') linkTemplate: TemplateRef<any>;\n @ContentChild('nodeTemplate') nodeTemplate: TemplateRef<any>;\n @ContentChild('clusterTemplate') clusterTemplate: TemplateRef<any>;\n @ContentChild('defsTemplate') defsTemplate: TemplateRef<any>;\n @ContentChild('miniMapNodeTemplate') miniMapNodeTemplate: TemplateRef<any>;\n\n @ViewChildren('nodeElement') nodeElements: QueryList<ElementRef>;\n @ViewChildren('linkElement') linkElements: QueryList<ElementRef>;\n\n public chartWidth: any;\n\n private isMouseMoveCalled: boolean = false;\n\n graphSubscription: Subscription = new Subscription();\n colors: ColorHelper;\n dims: ViewDimensions;\n seriesDomain: any;\n transform: string;\n isPanning = false;\n isDragging = false;\n draggingNode: Node;\n initialized = false;\n graph: Graph;\n graphDims: any = { width: 0, height: 0 };\n _oldLinks: Edge[] = [];\n oldNodes: Set<string> = new Set();\n oldClusters: Set<string> = new Set();\n oldCompoundNodes: Set<string> = new Set();\n transformationMatrix: Matrix = identity();\n _touchLastX = null;\n _touchLastY = null;\n minimapScaleCoefficient: number = 3;\n minimapTransform: string;\n minimapOffsetX: number = 0;\n minimapOffsetY: number = 0;\n isMinimapPanning = false;\n minimapClipPathId: string;\n width: number;\n height: number;\n resizeSubscription: any;\n visibilityObserver: VisibilityObserver;\n private destroy$ = new Subject<void>();\n\n constructor(\n private el: ElementRef,\n public zone: NgZone,\n public cd: ChangeDetectorRef,\n private layoutService: LayoutService\n ) {}\n\n @Input()\n groupResultsBy: (node: any) => string = node => node.label;\n\n /**\n * Get the current zoom level\n */\n get zoomLevel() {\n return this.transformationMatrix.a;\n }\n\n /**\n * Set the current zoom level\n */\n @Input('zoomLevel')\n set zoomLevel(level) {\n this.zoomTo(Number(level));\n }\n\n /**\n * Get the current `x` position of the graph\n */\n get panOffsetX() {\n return this.transformationMatrix.e;\n }\n\n /**\n * Set the current `x` position of the graph\n */\n @Input('panOffsetX')\n set panOffsetX(x) {\n this.panTo(Number(x), null);\n }\n\n /**\n * Get the current `y` position of the graph\n */\n get panOffsetY() {\n return this.transformationMatrix.f;\n }\n\n /**\n * Set the current `y` position of the graph\n */\n @Input('panOffsetY')\n set panOffsetY(y) {\n this.panTo(null, Number(y));\n }\n\n /**\n * Angular lifecycle event\n *\n *\n * @memberOf GraphComponent\n */\n ngOnInit(): void {\n if (this.update$) {\n this.update$.pipe(takeUntil(this.destroy$)).subscribe(() => {\n this.update();\n });\n }\n\n if (this.center$) {\n this.center$.pipe(takeUntil(this.destroy$)).subscribe(() => {\n this.center();\n });\n }\n\n if (this.zoomToFit$) {\n this.zoomToFit$.pipe(takeUntil(this.destroy$)).subscribe(options => {\n this.zoomToFit(options ? options : {});\n });\n }\n\n if (this.panToNode$) {\n this.panToNode$.pipe(takeUntil(this.destroy$)).subscribe((nodeId: string) => {\n this.panToNodeId(nodeId);\n });\n }\n\n this.minimapClipPathId = `minimapClip${id()}`;\n this.stateChange.emit({ state: NgxGraphStates.Subscribe });\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n this.basicUpdate();\n const { layoutSettings } = changes;\n this.setLayout(this.layout);\n if (layoutSettings) {\n this.setLayoutSettings(this.layoutSettings);\n }\n if (this.layout && this.nodes.length && this.li