blob: 3407482e14c5f71df8ac72ff080ec08ef3104f1e [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';
Sean Condon3dd062f2020-04-14 09:25:00 +010020import {LogService} from 'org_onosproject_onos/web/gui2-fw-lib/public_api';
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 Condon5f7d3bc2019-04-15 11:18:34 +010040 UiDeviceLink: 100,
Sean Condon50855cf2018-12-23 15:37:42 +000041 _def_: 50,
42};
43
Sean Condon28884332019-03-21 14:07:00 +000044/**
45 * note: key is link.type
46 * range: {0.0 ... 1.0}
47 */
Sean Condon50855cf2018-12-23 15:37:42 +000048const LINK_STRENGTH = {
Sean Condon28884332019-03-21 14:07:00 +000049 _def_: 0.5
Sean Condon0c577f62018-11-18 22:40:05 +000050};
51
52export interface Options {
53 width: number;
54 height: number;
55}
56
Sean Condon0d064ec2019-02-04 21:53:53 +000057/**
58 * The inspiration for this approach comes from
59 * https://medium.com/netscape/visualizing-data-with-angular-and-d3-209dde784aeb
Sean Condon28884332019-03-21 14:07:00 +000060 *
61 * Do yourself a favour and read https://d3indepth.com/force-layout/
Sean Condon0d064ec2019-02-04 21:53:53 +000062 */
Sean Condon0c577f62018-11-18 22:40:05 +000063export class ForceDirectedGraph {
64 public ticker: EventEmitter<d3.Simulation<Node, Link>> = new EventEmitter();
65 public simulation: d3.Simulation<any, any>;
Sean Condon28884332019-03-21 14:07:00 +000066 public canvasOptions: Options;
Sean Condon0c577f62018-11-18 22:40:05 +000067 public nodes: Node[] = [];
68 public links: Link[] = [];
69
Sean Condon50855cf2018-12-23 15:37:42 +000070 constructor(options: Options, public log: LogService) {
Sean Condon28884332019-03-21 14:07:00 +000071 this.canvasOptions = options;
72 const ticker = this.ticker;
73
74 // Creating the force simulation and defining the charges
75 this.simulation = d3.forceSimulation()
76 .force('charge',
77 d3.forceManyBody().strength(this.charges.bind(this)))
78 // .distanceMin(100).distanceMax(500))
79 .force('gravity',
80 d3.forceManyBody().strength(FORCES.GRAVITY))
81 .force('friction',
82 d3.forceManyBody().strength(FORCES.FRICTION))
83 .force('center',
84 d3.forceCenter(this.canvasOptions.width / 2, this.canvasOptions.height / 2))
85 .force('x', d3.forceX())
86 .force('y', d3.forceY())
87 .on('tick', () => {
88 ticker.emit(this.simulation); // ForceSvgComponent.ngOnInit listens
89 });
90
Sean Condon0c577f62018-11-18 22:40:05 +000091 }
92
Sean Condon28884332019-03-21 14:07:00 +000093 /**
94 * Assigning updated node and restarting the simulation
95 * Setting alpha to 0.3 and it will count down to alphaTarget=0
96 */
97 public reinitSimulation() {
Sean Condon0c577f62018-11-18 22:40:05 +000098 this.simulation.nodes(this.nodes);
Sean Condon28884332019-03-21 14:07:00 +000099 this.simulation.force('link',
Sean Condon0c577f62018-11-18 22:40:05 +0000100 d3.forceLink(this.links)
Sean Condon5f7d3bc2019-04-15 11:18:34 +0100101 .strength(LINK_STRENGTH._def_)
Sean Condon50855cf2018-12-23 15:37:42 +0000102 .distance(this.distance.bind(this))
Sean Condon0c577f62018-11-18 22:40:05 +0000103 );
Sean Condon28884332019-03-21 14:07:00 +0000104 this.simulation.alpha(0.3).restart();
Sean Condon0c577f62018-11-18 22:40:05 +0000105 }
106
Sean Condon28884332019-03-21 14:07:00 +0000107 charges(node: Node) {
Sean Condon50855cf2018-12-23 15:37:42 +0000108 const nodeType = node.nodeType;
109 return CHARGES[nodeType] || CHARGES._def_;
110 }
111
Sean Condon28884332019-03-21 14:07:00 +0000112 distance(link: Link) {
113 const linkType = link.type;
Sean Condon28884332019-03-21 14:07:00 +0000114 return LINK_DISTANCE[linkType] || LINK_DISTANCE._def_;
Sean Condon50855cf2018-12-23 15:37:42 +0000115 }
116
Sean Condon0c577f62018-11-18 22:40:05 +0000117 stopSimulation() {
118 this.simulation.stop();
Sean Condon50855cf2018-12-23 15:37:42 +0000119 this.log.debug('Simulation stopped');
120 }
121
Sean Condon28884332019-03-21 14:07:00 +0000122 public restartSimulation(alpha: number = 0.3) {
123 this.simulation.alpha(alpha).restart();
124 this.log.debug('Simulation restarted. Alpha:', alpha);
Sean Condon0c577f62018-11-18 22:40:05 +0000125 }
126}