4ec7650e608ad8c1169b1f70130b0c1537506dbe
[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 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] &&
300 self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable
301 ) {
302 if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback) {
303 var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback;
304 var c = self.model.callback[callback].class;
305 var controller = new TCD3.OsmController();
306 controller[callback](self, link, function () {
307 self._deselectAllNodes();
308 self._deselectAllLinks();
309 self.parent.removeLink.call(self, link.index);
310 success && success();
311 }, error);
312 } else {
313 log('removeLink: callback undefined in model spec.');
314 error && error("You can't remove a link, callback undefined.");
315 }
316
317 } else {
318 //FIXME we need to manage alert in a different way: FAILBACK
319 log("You can't delete the link");
320 error && error("You can't delete the link");
321 }
322
323
324 };
325
326
327 ModelGraphEditor.prototype.savePositions = function (data) {
328 var vertices = {};
329 this.node.each(function (d) {
330 vertices[d.id] = {};
331 vertices[d.id]['x'] = d.x;
332 vertices[d.id]['y'] = d.y;
333 });
334 new TCD3.GraphRequests().savePositions({
335 'vertices': vertices
336 });
337
338 };
339
340 /**
341 * Internal functions
342 */
343
344 ModelGraphEditor.prototype._setupBehaviorsOnEvents = function (layer) {
345
346 var self = this;
347 var contextMenuLinksAction = [{
348 title: 'Delete Link',
349 action: function (elm, link, i) {
350 self.removeLink(link, null, showAlert);
351 },
352 edit_mode: true
353 }];
354 var contextMenuNodesAction = [
355 {
356 title: 'Delete',
357 action: function (elm, d, i) {
358 self.removeNode(d, null, showAlert);
359 },
360 edit_mode: true
361 }
362
363 ];
364 if (this.customBehavioursOnEvents) {
365 contextMenuNodesAction = contextMenuNodesAction.concat(this.customBehavioursOnEvents['behaviors'].nodes);
366 }
367
368
369 if (self.model && self.model.layer && self.model.layer[layer] && self.model.layer[layer].action && self.model.layer[layer].action.node) {
370 for (var i in self.model.layer[layer].action.node) {
371 var action = self.model.layer[layer].action.node[i]
372 contextMenuNodesAction.push({
373 title: action.title,
374 action: function (elm, d, i) {
375 var callback = action.callback;
376 var c = self.model.callback[callback].class;
377 var controller = new TCD3[c]();
378 var args = {
379 elm: elm,
380 d: d,
381 i: i
382 };
383
384 controller[callback](self, args);
385 },
386 edit_mode: (action.edit_mode !== undefined) ? action.edit_mode : undefined
387 });
388 }
389 }
390
391 this.behavioursOnEvents = {
392 'nodes': {
393 'click': function (d) {
394
395 d3.event.preventDefault();
396
397 if (self._edit_mode && self.lastKeyDown === SHIFT_BUTTON && self._selected_node !== undefined) {
398 self.addLink(self._selected_node, d, null, showAlert);
399 } else {
400 self._selectNodeExclusive(this, d);
401 }
402
403 },
404 'mouseover': function (d) {
405 self.link.style('stroke-width', function (l) {
406 if (d === l.source || d === l.target)
407 return 4;
408 else
409 return 2;
410 });
411 },
412 'mouseout': function (d) {
413 self.link.style('stroke-width', 2);
414 },
415 'contextmenu': d3.contextMenu(contextMenuNodesAction, {
416 'edit_mode': self._edit_mode,
417 'layer': layer,
418 'type_object': 'node'
419 })
420 },
421 'links': {
422 'click': function (d) {
423 self._selectLinkExclusive(this, d);
424 },
425 'dblclick': function (event) {
426
427 },
428 'mouseover': function (d) {
429 d3.select(this).style('stroke-width', 4);
430 },
431 'mouseout': function (d) {
432 if (d !== self._selected_link)
433 d3.select(this).style('stroke-width', 2);
434 },
435 'contextmenu': d3.contextMenu(contextMenuLinksAction, {
436 'edit_mode': self._edit_mode,
437 'layer': layer,
438 'type_object': 'link'
439 })
440 }
441 }
442 };
443
444 ModelGraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) {
445
446 this.parent.handleFiltersParams.call(this, filtersParams, notFireEvent);
447 if (filtersParams && filtersParams.link && filtersParams.link.view)
448 this._setupBehaviorsOnEvents(filtersParams.link.view[0]);
449 };
450
451 ModelGraphEditor.prototype.getAvailableNodes = function () {
452 log('getAvailableNodes');
453 log(this.model);
454 if (this.model && this.model.layer[this.getCurrentView()] !== undefined)
455 return this.model.layer[this.getCurrentView()].nodes;
456 return [];
457 };
458
459
460 ModelGraphEditor.prototype.getTypeProperty = function () {
461 return this.type_property;
462 };
463
464 ModelGraphEditor.prototype.getCurrentGroup = function () {
465 return this.filter_parameters.node.group[0];
466 };
467
468 ModelGraphEditor.prototype.getCurrentView = function () {
469 return this.filter_parameters.link.view[0];
470 };
471 ModelGraphEditor.prototype.getCurrentFilters = function () {
472 return this.filter_parameters;
473 };
474
475 ModelGraphEditor.prototype.getGraphParams = function () {
476 return this.d3_graph.graph_parameters;
477 };
478
479 /**
480 * Log utility
481 */
482 function log(text) {
483 if (DEBUG)
484 console.log("::ModelGraphEditor::", text);
485 }
486
487
488 return ModelGraphEditor;
489
490
491 }(this));
492
493 if (typeof module === 'object') {
494 module.exports = TCD3.ModelGraphEditor;
495 }