| /* |
| * Copyright 2018-present Open Networking Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the 'License'); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an 'AS IS' BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| import { EventEmitter } from '@angular/core'; |
| import { Link } from './link'; |
| import { Node } from './node'; |
| import * as d3 from 'd3-force'; |
| import {LogService} from 'gui2-fw-lib'; |
| |
| const FORCES = { |
| LINKS: 1 / 50, |
| COLLISION: 1, |
| GRAVITY: 0.4, |
| FRICTION: 0.7 |
| }; |
| |
| const CHARGES = { |
| device: -80, |
| host: -200, |
| region: -80, |
| _def_: -120 |
| }; |
| |
| const LINK_DISTANCE = { |
| // note: key is link.type |
| direct: 100, |
| optical: 120, |
| UiEdgeLink: 100, |
| _def_: 50, |
| }; |
| |
| const LINK_STRENGTH = { |
| // note: key is link.type |
| // range: {0.0 ... 1.0} |
| _def_: 0.1 |
| }; |
| |
| export interface Options { |
| width: number; |
| height: number; |
| } |
| |
| export class ForceDirectedGraph { |
| public ticker: EventEmitter<d3.Simulation<Node, Link>> = new EventEmitter(); |
| public simulation: d3.Simulation<any, any>; |
| |
| public nodes: Node[] = []; |
| public links: Link[] = []; |
| |
| constructor(options: Options, public log: LogService) { |
| this.initSimulation(options); |
| } |
| |
| initNodes() { |
| if (!this.simulation) { |
| throw new Error('simulation was not initialized yet'); |
| } |
| |
| this.simulation.nodes(this.nodes); |
| } |
| |
| initLinks() { |
| if (!this.simulation) { |
| throw new Error('simulation was not initialized yet'); |
| } |
| |
| // Initializing the links force simulation |
| this.simulation.force('links', |
| d3.forceLink(this.links) |
| .strength(this.strength.bind(this)) |
| .distance(this.distance.bind(this)) |
| ); |
| } |
| |
| charges(node) { |
| const nodeType = node.nodeType; |
| return CHARGES[nodeType] || CHARGES._def_; |
| } |
| |
| distance(node) { |
| const nodeType = node.nodeType; |
| return LINK_DISTANCE[nodeType] || LINK_DISTANCE._def_; |
| } |
| |
| strength(node) { |
| const nodeType = node.nodeType; |
| return LINK_STRENGTH[nodeType] || LINK_STRENGTH._def_; |
| } |
| |
| initSimulation(options: Options) { |
| if (!options || !options.width || !options.height) { |
| throw new Error('missing options when initializing simulation'); |
| } |
| |
| /** Creating the simulation */ |
| if (!this.simulation) { |
| const ticker = this.ticker; |
| |
| // Creating the force simulation and defining the charges |
| this.simulation = d3.forceSimulation() |
| .force('charge', |
| d3.forceManyBody().strength(this.charges.bind(this))) |
| // .distanceMin(100).distanceMax(500)) |
| .force('gravity', |
| d3.forceManyBody().strength(FORCES.GRAVITY)) |
| .force('friction', |
| d3.forceManyBody().strength(FORCES.FRICTION)); |
| |
| // Connecting the d3 ticker to an angular event emitter |
| this.simulation.on('tick', function () { |
| ticker.emit(this); |
| }); |
| |
| this.initNodes(); |
| // this.initLinks(); |
| } |
| |
| /** Updating the central force of the simulation */ |
| this.simulation.force('centers', d3.forceCenter(options.width / 2, options.height / 2)); |
| |
| /** Restarting the simulation internal timer */ |
| this.simulation.restart(); |
| } |
| |
| stopSimulation() { |
| this.simulation.stop(); |
| this.log.debug('Simulation stopped'); |
| } |
| |
| restartSimulation() { |
| this.simulation.restart(); |
| this.log.debug('Simulation restarted'); |
| } |
| } |