fix composer bug
[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 self = this;
185 var controller = new TCD3.OsmController();
186 controller.updateNode(this,node, args, function(result){
187 self.updateData(result);
188 success && success();
189 }, error);
190 };
191
192 /**
193 * Update the data properties of the node
194 * @param {Object} Required. An object that specifies tha data of the node.
195 * @returns {boolean}
196 */
197 ModelGraphEditor.prototype.updateGraphParams = function (args, success, error) {
198 var self = this;
199 var controller = new TCD3.OsmController();
200 controller.updateGraphParams(args, function(result){
201 self.updateData(result);
202 success && success();
203 }, error);
204 };
205
206 /**
207 * Remove a node from graph and related links.
208 * @param {String} Required. Id of node to remove.
209 * @returns {boolean}
210 */
211 ModelGraphEditor.prototype.removeNode = function (node, success, error) {
212 console.log('removeNode', JSON.stringify(node))
213 var self = this;
214 var current_layer = self.getCurrentView();
215 var node_type = node.info.type;
216 if (node.info.desc_id == undefined) {
217 node.info.desc_id = self.desc_id;
218 }
219 if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].removable) {
220 if (self.model.layer[current_layer].nodes[node_type].removable.callback) {
221 var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].removable.callback].class;
222 var controller = new TCD3.OsmController();
223 controller[self.model.layer[current_layer].nodes[node_type].removable.callback](self, node, function (result) {
224 self._deselectAllNodes();
225 self.updateData(result);
226 success && success();
227 }, error);
228 } else {
229
230 log('removeNode: callback undefined in model spec.');
231 error && error("You can't remove a " + node.info.type + ", callback undefined.");
232 }
233 } else {
234 //FIXME we need to manage alert in a different way: FAILBACK
235 log("You can't remove a " + node.info.type);
236 error && error("You can't remove a " + node.info.type);
237 }
238 };
239
240 /**
241 * Add a new link to graph.
242 * @param {Object} Required. An object that specifies tha data of the new Link.
243 * @returns {boolean}
244 */
245 ModelGraphEditor.prototype.addLink = function (s, d, success, error) {
246 var self = this;
247 var source_id = s.id;
248 var target_id = d.id;
249 var source_type = s.info.type;
250 var destination_type = d.info.type;
251 var link = {
252 source: s,
253 target: d,
254 view: this.filter_parameters.link.view[0],
255 group: this.filter_parameters.link.group,
256 desc_id: this.desc_id
257 };
258 log("addLink: " + JSON.stringify(link))
259 var current_layer = self.getCurrentView()
260 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]) {
261
262 if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback) {
263 var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback;
264 console.log(callback, self.model.callback)
265 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;
266 link.directed_edge = direct_edge;
267 var c = self.model.callback[callback].class;
268 var controller = new TCD3.OsmController();
269 controller[callback](self, link, function (result) {
270 self._deselectAllNodes();
271 self.updateData(result);
272 success && success();
273 }, error);
274 } else {
275 log('addLink: callback undefined in model spec.');
276 error && error("You can't add a link, callback undefined.");
277 }
278
279 } else {
280 //FIXME we need to manage alert in a different way: FAILBACK
281 log("You can't link a " + source_type + " with a " + destination_type);
282
283 error && error("You can't link a " + source_type + " with a " + destination_type);
284 }
285 };
286
287 /**
288 * Remove a link from graph.
289 * @param {String} Required. The identifier of link to remove.
290 * @returns {boolean}
291 */
292 ModelGraphEditor.prototype.removeLink = function (link, success, error) {
293 var self = this;
294 var s = link.source;
295 var d = link.target;
296 var source_type = s.info.type;
297 var destination_type = d.info.type;
298 var current_layer = self.getCurrentView();
299 console.log(self.model.layer[current_layer])
300 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] &&
301 self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable
302 ) {
303 if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback) {
304 var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback;
305 var c = self.model.callback[callback].class;
306 var controller = new TCD3.OsmController();
307 controller[callback](self, link, function (result) {
308 self._deselectAllNodes();
309 self._deselectAllLinks();
310
311 self.updateData(result);
312 // success && success();
313 success && success();
314 }, error);
315 } else {
316 log('removeLink: callback undefined in model spec.');
317 error && error("You can't remove a link, callback undefined.");
318 }
319
320 } else {
321 //FIXME we need to manage alert in a different way: FAILBACK
322 log("You can't delete the link");
323 error && error("You can't delete the link");
324 }
325
326
327 };
328
329
330 ModelGraphEditor.prototype.savePositions = function (data) {
331 var vertices = {};
332 this.node.each(function (d) {
333 vertices[d.id] = {};
334 vertices[d.id]['x'] = d.x;
335 vertices[d.id]['y'] = d.y;
336 });
337 new TCD3.GraphRequests().savePositions({
338 'vertices': vertices
339 });
340
341 };
342
343 /**
344 * Internal functions
345 */
346
347 ModelGraphEditor.prototype._setupBehaviorsOnEvents = function (layer) {
348
349 var self = this;
350 var contextMenuLinksAction = [{
351 title: 'Delete Link',
352 action: function (elm, link, i) {
353 self.removeLink(link, null, showAlert);
354 },
355 edit_mode: true
356 }];
357 var contextMenuNodesAction = [
358 {
359 title: 'Delete',
360 action: function (elm, d, i) {
361 self.removeNode(d, null, showAlert);
362 },
363 edit_mode: true
364 }
365
366 ];
367 if (this.customBehavioursOnEvents) {
368 contextMenuNodesAction = contextMenuNodesAction.concat(this.customBehavioursOnEvents['behaviors'].nodes);
369 }
370
371
372 if (self.model && self.model.layer && self.model.layer[layer] && self.model.layer[layer].action && self.model.layer[layer].action.node) {
373 for (var i in self.model.layer[layer].action.node) {
374 var action = self.model.layer[layer].action.node[i]
375 contextMenuNodesAction.push({
376 title: action.title,
377 action: function (elm, d, i) {
378 var callback = action.callback;
379 var c = self.model.callback[callback].class;
380 var controller = new TCD3[c]();
381 var args = {
382 elm: elm,
383 d: d,
384 i: i
385 };
386
387 controller[callback](self, args);
388 },
389 edit_mode: (action.edit_mode !== undefined) ? action.edit_mode : undefined
390 });
391 }
392 }
393
394 this.behavioursOnEvents = {
395 'nodes': {
396 'click': function (d) {
397
398 d3.event.preventDefault();
399
400 if (self._edit_mode && self.lastKeyDown === SHIFT_BUTTON && self._selected_node !== undefined) {
401 self.addLink(self._selected_node, d, null, showAlert);
402 } else {
403 self._selectNodeExclusive(this, d);
404 }
405
406 },
407 'mouseover': function (d) {
408 self.link.style('stroke-width', function (l) {
409 if (d === l.source || d === l.target)
410 return 4;
411 else
412 return 2;
413 });
414 },
415 'mouseout': function (d) {
416 self.link.style('stroke-width', 2);
417 },
418 'contextmenu': d3.contextMenu(contextMenuNodesAction, {
419 'edit_mode': self._edit_mode,
420 'layer': layer,
421 'type_object': 'node'
422 })
423 },
424 'links': {
425 'click': function (d) {
426 self._selectLinkExclusive(this, d);
427 },
428 'dblclick': function (event) {
429
430 },
431 'mouseover': function (d) {
432 d3.select(this).style('stroke-width', 4);
433 },
434 'mouseout': function (d) {
435 if (d !== self._selected_link)
436 d3.select(this).style('stroke-width', 2);
437 },
438 'contextmenu': d3.contextMenu(contextMenuLinksAction, {
439 'edit_mode': self._edit_mode,
440 'layer': layer,
441 'type_object': 'link'
442 })
443 }
444 }
445 };
446
447 ModelGraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) {
448
449 this.parent.handleFiltersParams.call(this, filtersParams, notFireEvent);
450 if (filtersParams && filtersParams.link && filtersParams.link.view)
451 this._setupBehaviorsOnEvents(filtersParams.link.view[0]);
452 };
453
454 ModelGraphEditor.prototype.getAvailableNodes = function () {
455 log('getAvailableNodes');
456 log(this.model);
457 if (this.model && this.model.layer[this.getCurrentView()] !== undefined)
458 return this.model.layer[this.getCurrentView()].nodes;
459 return [];
460 };
461
462
463 ModelGraphEditor.prototype.getTypeProperty = function () {
464 return this.type_property;
465 };
466
467 ModelGraphEditor.prototype.getCurrentGroup = function () {
468 return this.filter_parameters.node.group[0];
469 };
470
471 ModelGraphEditor.prototype.getCurrentView = function () {
472 return this.filter_parameters.link.view[0];
473 };
474 ModelGraphEditor.prototype.getCurrentFilters = function () {
475 return this.filter_parameters;
476 };
477
478 ModelGraphEditor.prototype.getGraphParams = function () {
479 return this.d3_graph.graph_parameters;
480 };
481
482 /**
483 * Log utility
484 */
485 function log(text) {
486 if (DEBUG)
487 console.log("::ModelGraphEditor::", text);
488 }
489
490
491 return ModelGraphEditor;
492
493
494 }(this));
495
496 if (typeof module === 'object') {
497 module.exports = TCD3.ModelGraphEditor;
498 }