blob: 312b69b47575796b72ebd5c51ecc68610c74bee5 [file] [log] [blame]
Sean Condon0c577f62018-11-18 22:40:05 +00001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the 'License');
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an 'AS IS' BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16import { EventEmitter } from '@angular/core';
17import { Link } from './link';
18import { Node } from './node';
Sean Condon50855cf2018-12-23 15:37:42 +000019import * as d3 from 'd3-force';
20import {LogService} from 'gui2-fw-lib';
Sean Condon0c577f62018-11-18 22:40:05 +000021
22const FORCES = {
Sean Condon0c577f62018-11-18 22:40:05 +000023 COLLISION: 1,
Sean Condon50855cf2018-12-23 15:37:42 +000024 GRAVITY: 0.4,
25 FRICTION: 0.7
26};
27
28const CHARGES = {
Sean Condon28884332019-03-21 14:07:00 +000029 device: -800,
30 host: -2000,
31 region: -800,
32 _def_: -1200
Sean Condon50855cf2018-12-23 15:37:42 +000033};
34
35const LINK_DISTANCE = {
36 // note: key is link.type
37 direct: 100,
38 optical: 120,
Sean Condona3ce00b2019-04-10 11:44:01 +010039 UiEdgeLink: 3,
Sean Condon50855cf2018-12-23 15:37:42 +000040 _def_: 50,
41};
42
Sean Condon28884332019-03-21 14:07:00 +000043/**
44 * note: key is link.type
45 * range: {0.0 ... 1.0}
46 */
Sean Condon50855cf2018-12-23 15:37:42 +000047const LINK_STRENGTH = {
Sean Condon28884332019-03-21 14:07:00 +000048 _def_: 0.5
Sean Condon0c577f62018-11-18 22:40:05 +000049};
50
51export interface Options {
52 width: number;
53 height: number;
54}
55
Sean Condon0d064ec2019-02-04 21:53:53 +000056/**
57 * The inspiration for this approach comes from
58 * https://medium.com/netscape/visualizing-data-with-angular-and-d3-209dde784aeb
Sean Condon28884332019-03-21 14:07:00 +000059 *
60 * Do yourself a favour and read https://d3indepth.com/force-layout/
Sean Condon0d064ec2019-02-04 21:53:53 +000061 */
Sean Condon0c577f62018-11-18 22:40:05 +000062export class ForceDirectedGraph {
63 public ticker: EventEmitter<d3.Simulation<Node, Link>> = new EventEmitter();
64 public simulation: d3.Simulation<any, any>;
Sean Condon28884332019-03-21 14:07:00 +000065 public canvasOptions: Options;
Sean Condon0c577f62018-11-18 22:40:05 +000066 public nodes: Node[] = [];
67 public links: Link[] = [];
68
Sean Condon50855cf2018-12-23 15:37:42 +000069 constructor(options: Options, public log: LogService) {
Sean Condon28884332019-03-21 14:07:00 +000070 this.canvasOptions = options;
71 const ticker = this.ticker;
72
73 // Creating the force simulation and defining the charges
74 this.simulation = d3.forceSimulation()
75 .force('charge',
76 d3.forceManyBody().strength(this.charges.bind(this)))
77 // .distanceMin(100).distanceMax(500))
78 .force('gravity',
79 d3.forceManyBody().strength(FORCES.GRAVITY))
80 .force('friction',
81 d3.forceManyBody().strength(FORCES.FRICTION))
82 .force('center',
83 d3.forceCenter(this.canvasOptions.width / 2, this.canvasOptions.height / 2))
84 .force('x', d3.forceX())
85 .force('y', d3.forceY())
86 .on('tick', () => {
87 ticker.emit(this.simulation); // ForceSvgComponent.ngOnInit listens
88 });
89
Sean Condon0c577f62018-11-18 22:40:05 +000090 }
91
Sean Condon28884332019-03-21 14:07:00 +000092 /**
93 * Assigning updated node and restarting the simulation
94 * Setting alpha to 0.3 and it will count down to alphaTarget=0
95 */
96 public reinitSimulation() {
Sean Condon0c577f62018-11-18 22:40:05 +000097 this.simulation.nodes(this.nodes);
Sean Condon28884332019-03-21 14:07:00 +000098 this.simulation.force('link',
Sean Condon0c577f62018-11-18 22:40:05 +000099 d3.forceLink(this.links)
Sean Condon50855cf2018-12-23 15:37:42 +0000100 .strength(this.strength.bind(this))
101 .distance(this.distance.bind(this))
Sean Condon0c577f62018-11-18 22:40:05 +0000102 );
Sean Condon28884332019-03-21 14:07:00 +0000103 this.simulation.alpha(0.3).restart();
Sean Condon0c577f62018-11-18 22:40:05 +0000104 }
105
Sean Condon28884332019-03-21 14:07:00 +0000106 charges(node: Node) {
Sean Condon50855cf2018-12-23 15:37:42 +0000107 const nodeType = node.nodeType;
108 return CHARGES[nodeType] || CHARGES._def_;
109 }
110
Sean Condon28884332019-03-21 14:07:00 +0000111 distance(link: Link) {
112 const linkType = link.type;
113 this.log.debug('Link type', linkType, LINK_DISTANCE[linkType]);
114 return LINK_DISTANCE[linkType] || LINK_DISTANCE._def_;
Sean Condon50855cf2018-12-23 15:37:42 +0000115 }
116
Sean Condon28884332019-03-21 14:07:00 +0000117 strength(link: Link) {
118 const linkType = link.type;
119 this.log.debug('Link type', linkType, LINK_STRENGTH[linkType]);
120 return LINK_STRENGTH[linkType] || LINK_STRENGTH._def_;
Sean Condon0c577f62018-11-18 22:40:05 +0000121 }
122
123 stopSimulation() {
124 this.simulation.stop();
Sean Condon50855cf2018-12-23 15:37:42 +0000125 this.log.debug('Simulation stopped');
126 }
127
Sean Condon28884332019-03-21 14:07:00 +0000128 public restartSimulation(alpha: number = 0.3) {
129 this.simulation.alpha(alpha).restart();
130 this.log.debug('Simulation restarted. Alpha:', alpha);
Sean Condon0c577f62018-11-18 22:40:05 +0000131 }
132}