[wip] composer nsd
[osm/LW-UI.git] / static / TopologyComposer / js / model_graph_editor.js
1 /*
2 Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
3 Copyright 2018 EveryUP srl
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 */
17 if (typeof TCD3 === 'undefined') {
18 var TCD3 = {};
19 }
20
21 TCD3.ModelGraphEditor = (function () {
22 'use strict';
23
24 var DEBUG = true;
25 var SHIFT_BUTTON = 16;
26 var IMAGE_PATH = "/static/assets/img/";
27
28
29 ModelGraphEditor.prototype = new TCD3.GraphEditor();
30 ModelGraphEditor.prototype.constructor = ModelGraphEditor;
31 ModelGraphEditor.prototype.parent = TCD3.GraphEditor.prototype;
32
33 /**
34 * Constructor
35 */
36 function ModelGraphEditor(args) {
37
38 log("Constructor");
39
40 }
41
42
43 ModelGraphEditor.prototype.init = function (args) {
44 this.parent.init.call(this, args);
45 var self = this;
46 this.desc_id = args.desc_id || undefined; //TODO remove it
47
48 this.type_property = {};
49 this.type_property["unrecognized"] = args.gui_properties["nodes"]["default"];
50
51 this._edit_mode = args.edit_mode || false;
52
53 Object.keys(args.gui_properties["nodes"]).forEach(function (key, index) {
54
55 this.type_property[key] = args.gui_properties["nodes"][key];
56
57 this.type_property[key]["shape"] = this.parent.get_d3_symbol(this.type_property[key]["shape"]);
58 if (this.type_property[key]["image"] !== undefined) {
59 this.type_property[key]["image"] = IMAGE_PATH + this.type_property[key]["image"];
60 }
61
62 }, this);
63
64 if (args.gui_properties["edges"]) {
65 this.type_property_link = args.gui_properties["edges"];
66 var link_types = ['unrecognized'].concat(Object.keys(self.type_property_link))
67 this.defs.selectAll("marker")
68 .data(link_types)
69 .enter()
70 .append("svg:marker") // This section adds in the arrows
71 .attr("id", function (d) {
72 return d;
73 })
74 .attr("viewBox", "-5 -5 10 10")
75 .attr("refX", 13) /*must be smarter way to calculate shift*/
76 .attr("refY", 0)
77 .attr("markerUnits", "userSpaceOnUse")
78 .attr("markerWidth", 12)
79 .attr("markerHeight", 12)
80 .attr("orient", "auto")
81 .append("path")
82 .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z")
83 .attr('fill', function (d) {
84 return self.type_property_link[d].color;
85 });
86 }
87
88 this.customBehavioursOnEvents = args.behaviorsOnEvents || undefined;
89
90
91 var data_url = args.data_url || undefined;
92 if (!args.graph_data && args.data_url) {
93 d3.json(data_url, function (error, data) {
94 //console.log(JSON.stringify(data))
95 self.d3_graph.nodes = data.vertices;
96 self.d3_graph.links = data.edges;
97 self.d3_graph.graph_parameters = data.graph_parameters;
98 self.model = data.model;
99 self.refreshGraphParameters(self.d3_graph.graph_parameters);
100 self.refresh();
101 self.startForce();
102 //if(args.filter_base != undefined)
103
104 setTimeout(function () {
105 self.handleForce(true);
106 self.handleFiltersParams(args.filter_base);
107 }, 500);
108
109 });
110 } else {
111 this.updateData(args)
112 }
113 }
114
115 /**
116 * Update data of the graph.
117 * @param {Object} Required. An object that specifies tha data of the new node.
118 * @returns {boolean}
119 */
120 ModelGraphEditor.prototype.updateData = function (args) {
121 this.d3_graph.nodes = args.vertices;
122 this.d3_graph.links = args.edges;
123 this.d3_graph.graph_parameters = args.graph_parameters;
124 this.model = args.model;
125 this.refreshGraphParameters(this.d3_graph.graph_parameters);
126 this.cleanAll();
127 this.refresh();
128 this.startForce();
129 this.handleForce(this.forceSimulationActive);
130 //this.force.restart();
131 //if(args.filter_base != undefined)
132
133 if(args.filter_base){
134 var self = this;
135 setTimeout(function () {
136 self.handleForce(true);
137 self.handleFiltersParams(args.filter_base);
138 }, 500);
139 }
140 };
141
142 /**
143 * Add a new node to the graph.
144 * @param {Object} Required. An object that specifies tha data of the new node.
145 * @returns {boolean}
146 */
147 ModelGraphEditor.prototype.addNode = function (node, success, error) {
148 var self = this;
149 var current_layer = self.getCurrentView();
150 var node_type = node.info.type;
151 if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].addable) {
152 if (self.model.layer[current_layer].nodes[node_type].addable.callback) {
153 console.log(self.model.callback)
154 var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].addable.callback].class;
155 var controller = new TCD3.OsmController();
156 controller[self.model.layer[current_layer].nodes[node_type].addable.callback](self, node, function (result) {
157 console.log(result)
158 console.log(node)
159 self.updateData(result);
160 // self.parent.addNode.call(self, node);
161 success && success();
162 }, error);
163
164 } else {
165
166 log('addNode: callback undefined in model spec.');
167 error && error("You can't add a " + node.info.type + ", callback undefined.");
168 }
169 } else {
170 //FIXME Error handling????
171 log("You can't add a " + node.info.type + " in a current layer " + current_layer);
172 error && error("You can't add a " + node.info.type + " in a current layer " + current_layer);
173 }
174 };
175
176
177 /**
178 * Update the data properties of the node
179 * @param {Object} Required. An object that specifies tha data of the node.
180 * @returns {boolean}
181 */
182 ModelGraphEditor.prototype.updateDataNode = function (node, args, success, error) {
183 console.log(node)
184 var controller = new TCD3.OsmController();
185 controller.updateNode(this,node, args, function(){
186
187 }, error);
188 };
189
190 /**
191 * Update the data properties of the node
192 * @param {Object} Required. An object that specifies tha data of the node.
193 * @returns {boolean}
194 */
195 ModelGraphEditor.prototype.updateGraphParams = function (args, success, error) {
196 var controller = new TCD3.OsmController();
197 controller.updateGraphParams(args, function(){
198
199 }, error);
200 };
201
202 /**
203 * Remove a node from graph and related links.
204 * @param {String} Required. Id of node to remove.
205 * @returns {boolean}
206 */
207 ModelGraphEditor.prototype.removeNode = function (node, success, error) {
208 console.log('removeNode', JSON.stringify(node))
209 var self = this;
210 var current_layer = self.getCurrentView();
211 var node_type = node.info.type;
212 if (node.info.desc_id == undefined) {
213 node.info.desc_id = self.desc_id;
214 }
215 if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].removable) {
216 if (self.model.layer[current_layer].nodes[node_type].removable.callback) {
217 var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].removable.callback].class;
218 var controller = new TCD3.OsmController();
219 controller[self.model.layer[current_layer].nodes[node_type].removable.callback](self, node, function () {
220 self.parent.removeNode.call(self, node);
221 success && success();
222 }, error);
223 } else {
224
225 log('removeNode: callback undefined in model spec.');
226 error && error("You can't remove a " + node.info.type + ", callback undefined.");
227 }
228 } else {
229 //FIXME we need to manage alert in a different way: FAILBACK
230 log("You can't remove a " + node.info.type);
231 error && error("You can't remove a " + node.info.type);
232 }
233 };
234
235 /**
236 * Add a new link to graph.
237 * @param {Object} Required. An object that specifies tha data of the new Link.
238 * @returns {boolean}
239 */
240 ModelGraphEditor.prototype.addLink = function (s, d, success, error) {
241 var self = this;
242 var source_id = s.id;
243 var target_id = d.id;
244 var source_type = s.info.type;
245 var destination_type = d.info.type;
246 var link = {
247 source: s,
248 target: d,
249 view: this.filter_parameters.link.view[0],
250 group: this.filter_parameters.link.group,
251 desc_id: this.desc_id
252 };
253 log("addLink: " + JSON.stringify(link))
254 var current_layer = self.getCurrentView()
255 if (self.model.layer[current_layer].allowed_edges && self.model.layer[current_layer].allowed_edges[source_type] && self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type]) {
256
257 if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback) {
258 var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback;
259 console.log(callback, self.model.callback)
260 var direct_edge = 'direct_edge' in self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type] ? self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type]['direct_edge'] : false;
261 link.directed_edge = direct_edge;
262 var c = self.model.callback[callback].class;
263 var controller = new TCD3.OsmController();
264 controller[callback](self, link, function () {
265 self._deselectAllNodes();
266 self.parent.addLink.call(self, link);
267 if (success)
268 success();
269 }, error);
270 } else {
271 log('addLink: callback undefined in model spec.');
272 error && error("You can't add a link, callback undefined.");
273 }
274
275 } else {
276 //FIXME we need to manage alert in a different way: FAILBACK
277 log("You can't link a " + source_type + " with a " + destination_type);
278
279 error && error("You can't link a " + source_type + " with a " + destination_type);
280 }
281 };
282
283 /**
284 * Remove a link from graph.
285 * @param {String} Required. The identifier of link to remove.
286 * @returns {boolean}
287 */
288 ModelGraphEditor.prototype.removeLink = function (link, success, error) {
289 var self = this;
290 var s = link.source;
291 var d = link.target;
292 var source_type = s.info.type;
293 var destination_type = d.info.type;
294 var current_layer = self.getCurrentView();
295 if (self.model.layer[current_layer].allowed_edges && self.model.layer[current_layer].allowed_edges[source_type] && self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type] &&
296 self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable
297 ) {
298 if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback) {
299 var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback;
300 var c = self.model.callback[callback].class;
301 var controller = new TCD3.OsmController();
302 controller[callback](self, link, function () {
303 self._deselectAllNodes();
304 self._deselectAllLinks();
305 self.parent.removeLink.call(self, link.index);
306 success && success();
307 }, error);
308 } else {
309 log('removeLink: callback undefined in model spec.');
310 error && error("You can't remove a link, callback undefined.");
311 }
312
313 } else {
314 //FIXME we need to manage alert in a different way: FAILBACK
315 log("You can't delete the link");
316 error && error("You can't delete the link");
317 }
318
319
320 };
321
322
323 ModelGraphEditor.prototype.savePositions = function (data) {
324 var vertices = {};
325 this.node.each(function (d) {
326 vertices[d.id] = {};
327 vertices[d.id]['x'] = d.x;
328 vertices[d.id]['y'] = d.y;
329 });
330 new TCD3.GraphRequests().savePositions({
331 'vertices': vertices
332 });
333
334 };
335
336 /**
337 * Internal functions
338 */
339
340 ModelGraphEditor.prototype._setupBehaviorsOnEvents = function (layer) {
341
342 var self = this;
343 var contextMenuLinksAction = [{
344 title: 'Delete Link',
345 action: function (elm, link, i) {
346 self.removeLink(link, null, showAlert);
347 },
348 edit_mode: true
349 }];
350 var contextMenuNodesAction = [
351 {
352 title: 'Delete',
353 action: function (elm, d, i) {
354 self.removeNode(d, null, showAlert);
355 },
356 edit_mode: true
357 }
358
359 ];
360 if (this.customBehavioursOnEvents) {
361 contextMenuNodesAction = contextMenuNodesAction.concat(this.customBehavioursOnEvents['behaviors'].nodes);
362 }
363
364
365 if (self.model && self.model.layer && self.model.layer[layer] && self.model.layer[layer].action && self.model.layer[layer].action.node) {
366 for (var i in self.model.layer[layer].action.node) {
367 var action = self.model.layer[layer].action.node[i]
368 contextMenuNodesAction.push({
369 title: action.title,
370 action: function (elm, d, i) {
371 var callback = action.callback;
372 var c = self.model.callback[callback].class;
373 var controller = new TCD3[c]();
374 var args = {
375 elm: elm,
376 d: d,
377 i: i
378 };
379
380 controller[callback](self, args);
381 },
382 edit_mode: (action.edit_mode !== undefined) ? action.edit_mode : undefined
383 });
384 }
385 }
386
387 this.behavioursOnEvents = {
388 'nodes': {
389 'click': function (d) {
390
391 d3.event.preventDefault();
392
393 if (self._edit_mode && self.lastKeyDown === SHIFT_BUTTON && self._selected_node !== undefined) {
394 self.addLink(self._selected_node, d, null, showAlert);
395 } else {
396 self._selectNodeExclusive(this, d);
397 }
398
399 },
400 'mouseover': function (d) {
401 self.link.style('stroke-width', function (l) {
402 if (d === l.source || d === l.target)
403 return 4;
404 else
405 return 2;
406 });
407 },
408 'mouseout': function (d) {
409 self.link.style('stroke-width', 2);
410 },
411 'contextmenu': d3.contextMenu(contextMenuNodesAction, {
412 'edit_mode': self._edit_mode,
413 'layer': layer,
414 'type_object': 'node'
415 })
416 },
417 'links': {
418 'click': function (d) {
419 self._selectLinkExclusive(this, d);
420 },
421 'dblclick': function (event) {
422
423 },
424 'mouseover': function (d) {
425 d3.select(this).style('stroke-width', 4);
426 },
427 'mouseout': function (d) {
428 if (d !== self._selected_link)
429 d3.select(this).style('stroke-width', 2);
430 },
431 'contextmenu': d3.contextMenu(contextMenuLinksAction, {
432 'edit_mode': self._edit_mode,
433 'layer': layer,
434 'type_object': 'link'
435 })
436 }
437 }
438 };
439
440 ModelGraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) {
441
442 this.parent.handleFiltersParams.call(this, filtersParams, notFireEvent);
443 if (filtersParams && filtersParams.link && filtersParams.link.view)
444 this._setupBehaviorsOnEvents(filtersParams.link.view[0]);
445 };
446
447 ModelGraphEditor.prototype.getAvailableNodes = function () {
448 log('getAvailableNodes');
449 log(this.model);
450 if (this.model && this.model.layer[this.getCurrentView()] !== undefined)
451 return this.model.layer[this.getCurrentView()].nodes;
452 return [];
453 };
454
455
456 ModelGraphEditor.prototype.getTypeProperty = function () {
457 return this.type_property;
458 };
459
460 ModelGraphEditor.prototype.getCurrentGroup = function () {
461 return this.filter_parameters.node.group[0];
462 };
463
464 ModelGraphEditor.prototype.getCurrentView = function () {
465 return this.filter_parameters.link.view[0];
466 };
467 ModelGraphEditor.prototype.getCurrentFilters = function () {
468 return this.filter_parameters;
469 };
470
471 ModelGraphEditor.prototype.getGraphParams = function () {
472 return this.d3_graph.graph_parameters;
473 };
474
475 /**
476 * Log utility
477 */
478 function log(text) {
479 if (DEBUG)
480 console.log("::ModelGraphEditor::", text);
481 }
482
483
484 return ModelGraphEditor;
485
486
487 }(this));
488
489 if (typeof module === 'object') {
490 module.exports = TCD3.ModelGraphEditor;
491 }