blob: 24dd0292655feddac6b83da2f46c7c62c283b5f2 [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 = {
23 LINKS: 1 / 50,
24 COLLISION: 1,
Sean Condon50855cf2018-12-23 15:37:42 +000025 GRAVITY: 0.4,
26 FRICTION: 0.7
27};
28
29const CHARGES = {
30 device: -80,
31 host: -200,
32 region: -80,
33 _def_: -120
34};
35
36const LINK_DISTANCE = {
37 // note: key is link.type
38 direct: 100,
39 optical: 120,
40 UiEdgeLink: 100,
41 _def_: 50,
42};
43
44const LINK_STRENGTH = {
45 // note: key is link.type
46 // range: {0.0 ... 1.0}
47 _def_: 0.1
Sean Condon0c577f62018-11-18 22:40:05 +000048};
49
50export interface Options {
51 width: number;
52 height: number;
53}
54
Sean Condon0d064ec2019-02-04 21:53:53 +000055/**
56 * The inspiration for this approach comes from
57 * https://medium.com/netscape/visualizing-data-with-angular-and-d3-209dde784aeb
58 */
Sean Condon0c577f62018-11-18 22:40:05 +000059export class ForceDirectedGraph {
60 public ticker: EventEmitter<d3.Simulation<Node, Link>> = new EventEmitter();
61 public simulation: d3.Simulation<any, any>;
62
63 public nodes: Node[] = [];
64 public links: Link[] = [];
65
Sean Condon50855cf2018-12-23 15:37:42 +000066 constructor(options: Options, public log: LogService) {
Sean Condon0c577f62018-11-18 22:40:05 +000067 this.initSimulation(options);
68 }
69
70 initNodes() {
71 if (!this.simulation) {
72 throw new Error('simulation was not initialized yet');
73 }
74
75 this.simulation.nodes(this.nodes);
76 }
77
78 initLinks() {
79 if (!this.simulation) {
80 throw new Error('simulation was not initialized yet');
81 }
82
83 // Initializing the links force simulation
84 this.simulation.force('links',
85 d3.forceLink(this.links)
Sean Condon50855cf2018-12-23 15:37:42 +000086 .strength(this.strength.bind(this))
87 .distance(this.distance.bind(this))
Sean Condon0c577f62018-11-18 22:40:05 +000088 );
89 }
90
Sean Condon50855cf2018-12-23 15:37:42 +000091 charges(node) {
92 const nodeType = node.nodeType;
93 return CHARGES[nodeType] || CHARGES._def_;
94 }
95
96 distance(node) {
97 const nodeType = node.nodeType;
98 return LINK_DISTANCE[nodeType] || LINK_DISTANCE._def_;
99 }
100
101 strength(node) {
102 const nodeType = node.nodeType;
103 return LINK_STRENGTH[nodeType] || LINK_STRENGTH._def_;
104 }
105
Sean Condon0c577f62018-11-18 22:40:05 +0000106 initSimulation(options: Options) {
107 if (!options || !options.width || !options.height) {
108 throw new Error('missing options when initializing simulation');
109 }
110
111 /** Creating the simulation */
112 if (!this.simulation) {
113 const ticker = this.ticker;
114
115 // Creating the force simulation and defining the charges
116 this.simulation = d3.forceSimulation()
117 .force('charge',
Sean Condon50855cf2018-12-23 15:37:42 +0000118 d3.forceManyBody().strength(this.charges.bind(this)))
119 // .distanceMin(100).distanceMax(500))
120 .force('gravity',
121 d3.forceManyBody().strength(FORCES.GRAVITY))
122 .force('friction',
123 d3.forceManyBody().strength(FORCES.FRICTION));
Sean Condon0c577f62018-11-18 22:40:05 +0000124
125 // Connecting the d3 ticker to an angular event emitter
126 this.simulation.on('tick', function () {
127 ticker.emit(this);
128 });
129
130 this.initNodes();
Sean Condon50855cf2018-12-23 15:37:42 +0000131 // this.initLinks();
Sean Condon0c577f62018-11-18 22:40:05 +0000132 }
133
134 /** Updating the central force of the simulation */
135 this.simulation.force('centers', d3.forceCenter(options.width / 2, options.height / 2));
136
137 /** Restarting the simulation internal timer */
138 this.simulation.restart();
139 }
140
141 stopSimulation() {
142 this.simulation.stop();
Sean Condon50855cf2018-12-23 15:37:42 +0000143 this.log.debug('Simulation stopped');
144 }
145
146 restartSimulation() {
147 this.simulation.restart();
148 this.log.debug('Simulation restarted');
Sean Condon0c577f62018-11-18 22:40:05 +0000149 }
150}