blob: 46d3ba708e6a9c27ffc90f9f544058e1db397b5d [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';
19import * as d3 from 'd3';
20
21const FORCES = {
22 LINKS: 1 / 50,
23 COLLISION: 1,
Sean Condon021f0fa2018-12-06 23:31:11 -080024 CHARGE: -10
Sean Condon0c577f62018-11-18 22:40:05 +000025};
26
27export interface Options {
28 width: number;
29 height: number;
30}
31
32export class ForceDirectedGraph {
33 public ticker: EventEmitter<d3.Simulation<Node, Link>> = new EventEmitter();
34 public simulation: d3.Simulation<any, any>;
35
36 public nodes: Node[] = [];
37 public links: Link[] = [];
38
39 constructor(options: Options) {
40 this.initSimulation(options);
41 }
42
43 initNodes() {
44 if (!this.simulation) {
45 throw new Error('simulation was not initialized yet');
46 }
47
48 this.simulation.nodes(this.nodes);
49 }
50
51 initLinks() {
52 if (!this.simulation) {
53 throw new Error('simulation was not initialized yet');
54 }
55
56 // Initializing the links force simulation
57 this.simulation.force('links',
58 d3.forceLink(this.links)
59 .strength(FORCES.LINKS)
60 );
61 }
62
63 initSimulation(options: Options) {
64 if (!options || !options.width || !options.height) {
65 throw new Error('missing options when initializing simulation');
66 }
67
68 /** Creating the simulation */
69 if (!this.simulation) {
70 const ticker = this.ticker;
71
72 // Creating the force simulation and defining the charges
73 this.simulation = d3.forceSimulation()
74 .force('charge',
75 d3.forceManyBody()
76 .strength(FORCES.CHARGE)
77 );
78
79 // Connecting the d3 ticker to an angular event emitter
80 this.simulation.on('tick', function () {
81 ticker.emit(this);
82 });
83
84 this.initNodes();
85 this.initLinks();
86 }
87
88 /** Updating the central force of the simulation */
89 this.simulation.force('centers', d3.forceCenter(options.width / 2, options.height / 2));
90
91 /** Restarting the simulation internal timer */
92 this.simulation.restart();
93 }
94
95 stopSimulation() {
96 this.simulation.stop();
97 }
98}