ff4a21f8fbb3ba301155581ecff24a9a8255b5e3
[osm/LW-UI.git] / static / TopologyComposer / js / 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
18 if (typeof TCD3 === 'undefined') {
19 var TCD3 = {};
20 }
21
22 TCD3.GraphEditor = (function () {
23 'use strict';
24
25 var DEBUG = true;
26 var SHIFT_BUTTON = 16;
27 var CANC_BUTTON = 46;
28 var nominal_text_size = 14;
29 var nominal_stroke = 1.5;
30 var EventHandler = TCD3.Event;
31
32
33 /**
34 * Constructor
35 */
36 function GraphEditor(args) {
37 log("Constructor");
38 this.eventHandler = new EventHandler();
39 this.lastKeyDown = -1;
40 this._selected_node = undefined;
41 this._selected_link = undefined;
42 this._edit_mode = true;
43 this.filter_parameters = {
44 node: {
45 type: [],
46 group: [],
47 },
48 link: {
49 group: [],
50 view: [],
51 }
52 };
53 this.current_view_id = '';
54 // graph data initailization
55 this.d3_graph = {
56 nodes: [],
57 links: [],
58 graph_parameters: {}
59
60 };
61
62
63 }
64
65
66 GraphEditor.prototype.init = function (args) {
67 args = args || {};
68 var self = this;
69 this.width = args.width || 1500;
70 this.height = args.height || 1500;
71 this.forceSimulationActive = false;
72
73 var min_zoom = 0.1;
74 var max_zoom = 7;
75 this._setupBehaviorsOnEvents();
76 this._setupFiltersBehaviors(args);
77
78 this.type_property = {
79 "unrecognized": {
80 "shape": d3.symbolCircle,
81 "color": "#fff",
82 "node_label_color": "#000",
83 "size": 15
84 },
85 };
86
87 this.type_property_link = {
88 "unrecognized": {
89 "color": "lightgray"
90 },
91 };
92
93 this.force = d3.forceSimulation()
94 .force("charge", d3.forceManyBody())
95 .force("collide", d3.forceCollide().radius(40))
96 // .force("link", d3.forceLink().distance(80).iterations(1).id(function (d) {
97 .force("link", d3.forceLink().distance(function(d){
98 return d.short ? 1 : 100;
99 }).id(function (d) {
100 return d.id;
101 }))
102 .force("center", d3.forceCenter(this.width / 2, this.height / 2));
103
104 var zoom = d3.zoom().scaleExtent([min_zoom, max_zoom]);
105
106 var size = d3.scalePow().exponent(2)
107 .domain([1, 100])
108 .range([8, 24]);
109
110 this.svg = d3.select("#graph_editor_container").append("svg")
111 .attr("id", "graph_svg")
112 .attr("preserveAspectRatio", "xMinYMid")
113 .attr("width", '100%')
114 .attr("height", '100%');
115
116 //End Arrow style
117 this.defs = this.svg.append("svg:defs");
118
119 this.defs.selectAll("marker")
120 .data(["unrecognized"]) // Different link/path types can be defined here
121 .enter().append("svg:marker") // This section adds in the arrows
122 .attr("id", String)
123 .attr("viewBox", "-5 -5 10 10")
124 .attr("refX", 13) //must be smarter way to calculate shift
125 .attr("refY", 0)
126 .attr("markerUnits", "userSpaceOnUse")
127 .attr("markerWidth", 12)
128 .attr("markerHeight", 12)
129 .attr("orient", "auto")
130 .append("path")
131 .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z")
132 .attr('fill', this.type_property_link['unrecognized']['color']);
133
134 d3.select(window)
135 .on('keydown', function () {
136 log('keydown ' + d3.event.keyCode);
137 //d3.event.preventDefault();
138 if (self.lastKeyDown !== -1) return;
139 self.lastKeyDown = d3.event.keyCode;
140 if (self.lastKeyDown === CANC_BUTTON && self._selected_node !== undefined) {
141 self.removeNode(self._selected_node, null, showAlert);
142 } else if (self.lastKeyDown === CANC_BUTTON && self._selected_link !== undefined) {
143 self.removeLink(self._selected_link, null, showAlert);
144 }
145
146 })
147 .on('keyup', function () {
148 log('keyup' + self.lastKeyDown);
149 self.lastKeyDown = -1;
150 });
151 var popup = this.svg.append("g")
152 .attr("id", "popup")
153 .attr("class", "popup")
154 .attr("opacity", "0")
155 .attr("transform", "translate(1 1)")
156 .call(d3.drag()
157 .on("start", dragstarted)
158 .on("drag", dragged)
159 .on("end", dragended));
160
161 function dragstarted(d) {
162 //d3.select(this).raise().classed("active", true);
163 }
164
165 function dragged(d) {
166 //console.log(JSON.stringify(d))
167 d3.select(this).attr("transform", function () {
168 return "translate(" + d3.event.x + "," + d3.event.y + ")";
169
170 })
171 }
172
173 function dragended(d) {
174 //d3.select(this).classed("active", false);
175 }
176
177 var chart = $("#graph_svg");
178 this.aspect = chart.width() / chart.height();
179 this.container = $("#graph_editor_container");
180 $(window).on("resize", function () {
181
182 self.width = self.container.width();
183 self.height = self.container.height();
184 chart.attr("width", self.container.width());
185 chart.attr("height", self.container.height());
186 });
187
188 }
189
190
191 GraphEditor.prototype.get_d3_symbol =
192 function (myString) {
193
194 switch (myString) {
195 case "circle":
196 return d3.symbolCircle;
197 case "square":
198 return d3.symbolSquare;
199 case "diamond":
200 return d3.symbolDiamond;
201 case "triangle":
202 return d3.symbolTriangle;
203 case "star":
204 return d3.symbolStar;
205 case "cross":
206 return d3.symbolCross;
207 default:
208 // if the string is not recognized
209 return d3.symbolCross;
210 }
211
212 };
213
214 GraphEditor.prototype.get_name_from_d3_symbol =
215 function (mySymbol) {
216 switch (mySymbol) {
217 case d3.symbolCircle:
218 return "circle";
219 case d3.symbolSquare:
220 return "square";
221 case d3.symbolDiamond:
222 return "diamond";
223 case d3.symbolTriangle:
224 return "triangle";
225 case d3.symbolStar:
226 return "star";
227 case d3.symbolCross:
228 return "cross";
229 default:
230 // if the string is not recognized
231 return "unknown";
232 //return d3.symbolCircleUnknown;
233 }
234
235 };
236
237 /**
238 * Start or Stop force layout
239 * @param {boolean} Required. Value true: start, false: stop
240 * @returns {boolean}
241 */
242 GraphEditor.prototype.handleForce = function (start) {
243 if (start)
244 this.force.stop();
245 this.forceSimulationActive = start;
246 this.node.each(function (d) {
247 d.fx = (start) ? null : d.x;
248 d.fy = (start) ? null : d.y;
249 });
250
251 if (start)
252 this.force.restart();
253
254 this.eventHandler.fire("force_status_changed_on", start);
255 };
256
257 /**
258 * Handle the parameters of basic filters: node type, view, group
259 * @param {Object} Required.
260 *
261 */
262 GraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) {
263 this.filter_parameters = (filtersParams !== undefined) ? filtersParams : this.filter_parameters;
264 this.current_view_id = (this.filter_parameters !== undefined && this.filter_parameters.link.view[0] !== undefined) ? this.filter_parameters.link.view[0] : this.current_view_id
265 this.cleanAll();
266 this.refresh();
267 this.startForce();
268 this.force.restart();
269 this._deselectAllNodes();
270 this.handleForce(this.forceSimulationActive);
271 if (!notFireEvent)
272 this.eventHandler.fire("filters_changed", filtersParams);
273
274 };
275
276 /**
277 * Add a new node to the graph.
278 * @param {Object} Required. An object that specifies tha data of the new node.
279 * @returns {boolean}
280 */
281 GraphEditor.prototype.addNode = function (args) {
282 if (args.id && args.info && args.info.type) {
283 args.fixed = true;
284 this.force.stop();
285 this.cleanAll();
286 this.d3_graph.nodes.push(args);
287 this.refresh();
288 this.startForce();
289 this.force.restart();
290 this.handleForce(this.forceSimulationActive);
291 return true;
292 }
293
294 return false;
295
296 };
297
298 /**
299 * Update the data properties of the node
300 * @param {Object} Required. An object that specifies tha data of the node.
301 * @returns {boolean}
302 */
303 GraphEditor.prototype.updateDataNode = function (args) {
304
305 };
306
307 /**
308 * Remove a node from graph and related links.
309 * @param {String} Required. Id of node to remove.
310 * @returns {boolean}
311 */
312 GraphEditor.prototype.removeNode = function (node) {
313 if (node != undefined) {
314 var node_id = node.id;
315 this.d3_graph['nodes'].forEach(function (n, index, object) {
316 if (n.id === node_id) {
317 object.splice(index, 1);
318 }
319 });
320
321 var self = this;
322 var links_to_remove = [];
323 this.d3_graph['links'].forEach(function (l, index, object) {
324 if (node_id === l.source.id || node_id === l.target.id) {
325 links_to_remove.push(index);
326 }
327 });
328 var links_removed = 0;
329 links_to_remove.forEach(function (l_index) {
330 self.d3_graph['links'].splice(l_index - links_removed, 1);
331 links_removed++;
332 });
333 this.cleanAll();
334 this.refresh();
335 this.startForce();
336 this.force.restart();
337
338 return true;
339 }
340 return false;
341 };
342
343
344 /**
345 * Add a new link to graph.
346 * @param {Object} Required. An object that specifies tha data of the new Link.
347 * @returns {boolean}
348 */
349 GraphEditor.prototype.addLink = function (link) {
350 console.log("addLink" + JSON.stringify(link));
351 if (link.source && link.target) {
352 this.force.stop();
353 this.cleanAll();
354 this.d3_graph.links.push(link);
355 this.refresh();
356 this.startForce();
357 this.force.restart();
358 return true;
359 }
360
361 return false;
362 };
363
364 /**
365 * Remove a link from graph.
366 * @param {String} Required. The identifier of link to remove.
367 * @returns {boolean}
368 */
369 GraphEditor.prototype.removeLink = function (link_id) {
370 var self = this;
371 if (link_id !== 'undefined') {
372 this.d3_graph['links'].forEach(function (l, index, object) {
373 if (link_id === l.index) {
374 object.splice(index, 1);
375
376 self.cleanAll();
377 self.refresh();
378 self.startForce();
379 self.force.restart();
380 return true;
381 }
382
383 });
384 }
385
386 return false;
387 };
388
389
390 /**
391 * Force a refresh of GraphView
392 * @returns {}
393 */
394 GraphEditor.prototype.refresh = function () {
395
396 //log(data)
397 var self = this;
398
399 this.link = this.svg
400 .selectAll()
401 .data(self.d3_graph.links
402 .filter(this.link_filter_cb)
403 )
404 .enter().append("g")
405 .attr("class", "link cleanable")
406 .append("path")
407 .attr("class", "link")
408 .attr("class", "cleanable")
409 .style("stroke-width", nominal_stroke)
410 .style("stroke", function (d) {
411 return self._link_property_by_type((d.type_link) ? d.type_link : "unrecognized", "color");
412 })
413 .attr("marker-end", function (d) {
414 if (!d.directed_edge)
415 return '';
416
417 var marker_url = (d.type_link) ? d.type_link : "unrecognized"
418 return (d.directed_edge ? "url(#" + marker_url + ")" : '');
419 });
420
421 this.nodeContainer = this.svg
422 .selectAll()
423 .data(self.d3_graph.nodes
424 .filter(this.node_filter_cb))
425 .enter()
426 .append("g")
427 // .attr("class", "nodosdads")
428 .attr("class", "node cleanable");
429
430 this.svg.selectAll('.node')
431 .data(self.d3_graph.nodes
432 .filter(this.node_filter_cb))
433
434 .filter(function (d) {
435 return (d.info.type === undefined) || (self._node_property_by_type(d.info.type, 'image', d) === undefined)
436 })
437
438 .append("svg:path")
439 .attr("d", d3.symbol()
440 .size(function (d) {
441 return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d), 2) / 4;
442 })
443 .type(function (d) {
444 // console.log(d.info.type, 'shape', self.current_view_id)
445 return (self._node_property_by_type(d.info.type, 'shape', d));
446 })
447 )
448 .style("fill", function (d) {
449 return self._node_property_by_type(d.info.type, 'color', d);
450 })
451 .attr("transform", function () {
452 return "rotate(-45)";
453
454 })
455 .attr("stroke-width", 2.4)
456
457 .attr("class", "node_path")
458 .attr("id", function (d) {
459 return "path_" + d.id;
460 })
461
462 .call(d3.drag()
463 .on("start", dragstarted)
464 .on("drag", dragged)
465 .on("end", dragended));
466
467 var figure_node = this.svg.selectAll('.node')
468 .data(self.d3_graph.nodes
469 .filter(this.node_filter_cb))
470
471 .filter(function (d) {
472 return self._node_property_by_type(d.info.type, 'image', d) != undefined
473 });
474
475 figure_node.append("svg:image")
476 .attr("xlink:href", function (d) {
477 return self._node_property_by_type(d.info.type, 'image', d)
478 })
479 .attr("x", function (d) {
480 return -self._node_property_by_type(d.info.type, 'size', d) / 2
481 })
482 .attr("y", function (d) {
483 return -self._node_property_by_type(d.info.type, 'size', d) / 2
484 })
485 .attr("width", function (d) {
486 return self._node_property_by_type(d.info.type, 'size', d)
487 })
488 .attr("height", function (d) {
489 return self._node_property_by_type(d.info.type, 'size', d)
490 })
491 .style("stroke", "black")
492 .style("stroke-width", "1px")
493
494 .attr("class", "node_path")
495 .attr("id", function (d) {
496 return "path_" + d.id;
497 })
498 .call(d3.drag()
499 .on("start", dragstarted)
500 .on("drag", dragged)
501 .on("end", dragended));
502
503 figure_node.append("svg:path")
504 .attr("d", d3.symbol()
505 .size(function (d) {
506 return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d) + 7, 2) / 4;
507 })
508 .type(function (d) {
509 return (self.get_d3_symbol('circle'));
510 })
511 )
512 .style("fill", 'transparent')
513 .attr("transform", function () {
514 return "rotate(-45)";
515
516 })
517 .attr("stroke-width", 2.4)
518
519 .attr("class", "hidden_circle")
520 .attr("id", function (d) {
521 return "path_" + d.id;
522 })
523
524 .call(d3.drag()
525 .on("start", dragstarted)
526 .on("drag", dragged)
527 .on("end", dragended));
528
529
530 this.node = this.svg.selectAll('.node')
531 .data(self.d3_graph.nodes
532 .filter(this.node_filter_cb)).selectAll("image, path, circle");
533
534
535 this.node.on("contextmenu", self.behavioursOnEvents.nodes["contextmenu"])
536 .on("mouseover", self.behavioursOnEvents.nodes["mouseover"])
537 .on("mouseout", self.behavioursOnEvents.nodes["mouseout"])
538 .on('click', self.behavioursOnEvents.nodes["click"])
539 .on('dblclick', self.behavioursOnEvents.nodes["dblclick"]);
540
541 this.link
542 .on("contextmenu", self.behavioursOnEvents.links["contextmenu"])
543 .on("mouseover", self.behavioursOnEvents.links["mouseover"])
544 .on('click', self.behavioursOnEvents.links["click"])
545 .on("mouseout", self.behavioursOnEvents.links["mouseout"]);
546
547
548 this.text = this.svg.selectAll(".node")
549 .data(self.d3_graph.nodes
550 .filter(this.node_filter_cb))
551 .append("svg:text")
552 .attr("class", "node_text cleanable")
553 .attr("dy", function (d) {
554 return "-5";
555 })
556 .attr("pointer-events", "none")
557 .style("font-size", nominal_text_size + "px")
558 .style("font-family", "'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif")
559 .style("fill", function (d) {
560 return self._node_property_by_type(d.info.type, 'node_label_color', d);
561 })
562 //.style("text-anchor", "middle")
563 .text(function (d) {
564 if(d.info && d.info.property.custom_label && d.info.property.custom_label !==''){
565 return d.info.property.custom_label
566 } else
567 return d.id;
568 });
569
570
571 function dragstarted(d) {
572 d.draggednode = true;
573 if (!d3.event.active) self.force.alphaTarget(0.3).restart();
574 d.fx = d.x;
575 d.fy = d.y;
576
577 }
578
579 function dragged(d) {
580 d.fx = d3.event.x;
581 d.fy = d3.event.y;
582 }
583
584 function dragended(d) {
585 d.draggednode = false;
586 if (!d3.event.active) self.force.alphaTarget(0);
587 if (self.forceSimulationActive) {
588 d.fx = null;
589 d.fy = null;
590 } else {
591 d.fx = d.x;
592 d.fy = d.y;
593 self.force.stop();
594 self.forceSimulationActive = false;
595 }
596 }
597
598
599 };
600
601 /**
602 * Start force layout on Graph.
603 *
604 */
605 GraphEditor.prototype.startForce = function () {
606 this.force.stop();
607 var self = this;
608 this.force
609 .nodes(this.d3_graph.nodes)
610 .on("tick", ticked);
611
612
613 this.force
614 .force("link")
615 .links(this.d3_graph.links);
616
617 function ticked() {
618 self.node.attr("cx", function (d) {
619 return d.x = Math.max(self._node_property_by_type(d.info.type, 'size', d), Math.min(self.width - self._node_property_by_type(d.info.type, 'size', d), d.x));
620 })
621 .attr("cy", function (d) {
622 return d.y = Math.max(self._node_property_by_type(d.info.type, 'size', d), Math.min(self.height - self._node_property_by_type(d.info.type, 'size', d), d.y));
623 });
624
625 self.link.attr("d", function (d) {
626 var dx = d.target.x - d.source.x,
627 dy = d.target.y - d.source.y,
628 dr = Math.sqrt(dx * dx + dy * dy);
629 return "M" + d.source.x + "," + d.source.y + "," + d.target.x + "," + d.target.y;
630 });
631
632 self.node.attr("transform", function (d) {
633 return "translate(" + d.x + "," + d.y + ")";
634 });
635 self.text.attr("transform", function (d) {
636 var label_pos_y = d.y + self._node_property_by_type(d.info.type, 'size', d)/2 +nominal_text_size;
637 return "translate(" + d.x + "," + label_pos_y + ")";
638 });
639 }
640
641
642 };
643
644 /**
645 * This method attaches an event handler.
646 * @param {String} Required. A String that specifies the name of the event.
647 * @param {Function} Required. Specifies the function to run when the event occurs.
648 * @returns {}
649 */
650 GraphEditor.prototype.addListener = function (event_name, cb) {
651 this.eventHandler.addL(event_name, cb);
652 };
653
654 /**
655 * This method removes an event handler that has been attached with the addListener() method.
656 * @param {String} Required. A String that specifies the name of the event to remove.
657 * @param {Function} Required. Specifies the function to remove.
658 * @returns {}
659 */
660 GraphEditor.prototype.removeListener = function (event_name, cb) {
661
662 };
663
664
665 GraphEditor.prototype.setNodeClass = function (class_name, filter_cb) {
666 log("setNodeClass");
667 var self = this;
668 this.svg.selectAll('.node').classed(class_name, false);
669 this.svg.selectAll('.node')
670 .classed(class_name, filter_cb);
671 };
672
673 GraphEditor.prototype.setLinkClass = function (class_name, filter_cb) {
674 log("setLinkClass");
675 var self = this;
676 this.svg.selectAll('.link').classed(class_name, false);
677 this.svg.selectAll('.link')
678 .classed(class_name, filter_cb);
679 };
680
681 GraphEditor.prototype.showNodeInfo = function (args) {
682 this.addLinesToPopup(args['node_info'], "Info about node selected")
683 this.handlePopupVisibility(true, 'right')
684 };
685
686 GraphEditor.prototype.addLinesToPopup = function (data, title) {
687 var self = this;
688 var index = 1;
689 var translate_y = 0;
690 var width_popup = 400;
691 var height_popup = 0;
692
693 d3.selectAll(".popupcleanable").remove(); // clean
694
695 var popupbg = d3.select(".popup").append("rect")
696 .attr("id", "popupbg")
697 .attr("class", "popup bg popupcleanable cleanable")
698 .attr("width", "400")
699 .attr("height", "0")
700 .attr("rx", 10) // set the x corner curve radius
701 .attr("ry", 10); // set the y corner curve radius
702
703
704 d3.select(".popup").append("svg:path")
705 .attr("d", d3.symbol()
706 .size(function (d) {
707 return 80
708 })
709 .type(function (d) {
710 console.log("popup")
711 return (self.get_d3_symbol());
712 })
713 )
714 .style("fill", 'red')
715 .attr("transform", function () {
716 return "translate(380,15) rotate(-45)";
717
718 })
719 .attr("stroke-width", 2.4)
720 .attr("id", "close_popup")
721 .attr("class", "popupcleanable cleanable")
722 .on("click", function (d) {
723 self.handlePopupVisibility(false);
724 });
725
726 d3.select(".popup").append("text")
727 .attr("class", "popup title popupcleanable cleanable")
728 .attr("x", "10")
729 .attr("y", "20")
730 .text(title);
731
732 for (var i in data) {
733 //console.log(i, data, data[i])
734 //var typeofvalue = typeof data[i];
735 var record = data[i];
736 index = this._addRecordToPopup(i, record, index)
737
738 }
739
740 };
741
742 GraphEditor.prototype._addRecordToPopup = function (key, record, index, tab) {
743 //console.log("_addRecordToPopup", key, record, index)
744 var translate_y = 23 * index;
745 var summary = d3.select(".popup").append("g")
746 .attr("class", "popup summary d popupcleanable cleanable")
747 .attr("transform", "translate(10 " + translate_y + ")");
748 if (Object.prototype.toString.call(record) !== '[object Array]') { //is a record simple key:value
749 //console.log(key, record)
750 var summary_g = summary.append("g");
751 summary_g.append("rect")
752 .attr("class", "popup summary bg popupcleanable cleanable")
753 .attr("width", "380")
754 .attr("height", "20");
755
756 summary_g.append("text")
757 .attr("class", "popup summary popupcleanable cleanable")
758 .attr("x", (tab) ? tab : 10)
759 .attr("y", "17")
760 .attr("width", "100")
761 .text(function (d) {
762 return key.toUpperCase() + ":";
763 });
764
765 summary_g.append("text")
766 .attr("class", "popup summary popupcleanable cleanable")
767 .attr("x", "370")
768 .attr("y", "17")
769 .attr("text-anchor", "end")
770 .text(function (d) {
771 return record
772 });
773 }
774 else {//is a record simple complex: have a list of sub record key:value
775 //index ++;
776 this._addRecordToPopup(key, "", index)
777 for (var r in record) {
778 //console.log(i, r, record, record[r])
779 for (var k in record[r]) {
780 //console.log(i, r, k, record[r][k])
781 var curr_key = k;
782 var recordValue = record[r][k]
783
784 index++;
785 this._addRecordToPopup(curr_key, recordValue, index, 20)
786 }
787 }
788
789 }
790
791 translate_y = 30 * index++;
792 d3.select('#popupbg').attr("height", translate_y);
793 return index;
794 };
795
796
797 /**
798 * Remove all the graph objects from the view
799 */
800 GraphEditor.prototype.cleanAll = function () {
801 this.svg.selectAll('.cleanable').remove();
802 };
803
804 /**
805 * Internal functions
806 */
807
808 GraphEditor.prototype._node_property_by_type = function (type, property, node) {
809 //console.log(type, property, layer, group)
810 var unrecognized = function (ui_prop, property) {
811 return ui_prop['unrecognized'][property]
812 };
813
814 //type recognized
815 if (this.type_property[type]) {
816
817 if (this.type_property[type]['property']) {
818 var filt_property = this.type_property[type]['property']
819 return this.type_property[type][node.info[filt_property]][property]
820 } else { // type without property spec
821
822 return this.type_property[type][property]
823
824 }
825
826 } else { //type unrecognized
827 return unrecognized(this.type_property, property)
828 }
829
830 };
831
832 GraphEditor.prototype._link_property_by_type = function (type, property) {
833 //log(type + "-" + property)
834 if (this.type_property_link[type] != undefined && this.type_property_link[type][property] != undefined) {
835 //if(property == "shape")
836 // log("dentro" + this.type_property[type][property])
837 return this.type_property_link[type][property];
838 } else {
839 return this.type_property_link['unrecognized'][property];
840 }
841
842 }
843
844
845 /**
846 *
847 *
848 *
849 */
850 GraphEditor.prototype._setupFiltersBehaviors = function (args) {
851
852 var self = this;
853
854 this.node_filter_cb = args.node_filter_cb || function (d) {
855
856 var cond_view = true,
857 cond_group = true;
858 //log(d.info.type + " " + self.filter_parameters.node.type + " group: " + self.filter_parameters.node.group + "- " + d.info.group)
859 // check filter by node type
860 if (self.filter_parameters.node.type.length > 0) {
861
862 if (self.filter_parameters.node.type.indexOf(d.info.type) < 0)
863 cond_view = false;
864 }
865
866 // check filter by group
867 if (self.filter_parameters.node.group.length > 0) {
868 self.filter_parameters.node.group.forEach(function (group) {
869 if (d.info.group.indexOf(group) < 0)
870 cond_group = false;
871 });
872
873
874 }
875
876
877 return cond_view && cond_group;
878 };
879
880 this.link_filter_cb = args.link_filter_cb || function (d) {
881 var cond_view = true,
882 cond_group = true;
883
884 // check filter by view
885 if (self.filter_parameters.link.view.length > 0) {
886 self.filter_parameters.link.view.forEach(function (view) {
887 if (d.view.indexOf(view) < 0)
888 cond_view = false;
889 });
890 }
891
892 // check filter by group
893 if (self.filter_parameters.link.group.length > 0) {
894 self.filter_parameters.link.group.forEach(function (group) {
895 if (d.group.indexOf(group) < 0)
896 cond_group = false;
897 });
898 }
899 return cond_view && cond_group;
900 };
901
902 };
903
904 /**
905 *
906 *
907 */
908 GraphEditor.prototype._setupBehaviorsOnEvents = function () {
909 log("_setupBehaviorsOnEvents");
910 var self = this;
911 this.behavioursOnEvents = {
912 'nodes': {
913 'click': function (d) {
914 d3.event.preventDefault();
915 log('click', d);
916 if (self.lastKeyDown === SHIFT_BUTTON && self._selected_node !== undefined) {
917 var source_id = self._selected_node.id;
918 var target_id = d.id;
919 log("--" + JSON.stringify(self.filter_parameters.link.view));
920 var new_link = {
921 source: source_id,
922 target: target_id,
923 view: self.filter_parameters.link.view[0],
924 group: self.filter_parameters.link.group[0],
925 };
926 self.addLink(new_link);
927 self._deselectAllNodes();
928 } else {
929 self._selectNodeExclusive(this, d);
930 }
931
932 },
933 'mouseover': function (d) {
934
935 },
936 'mouseout': function (d) {
937 },
938 'dblclick': function (d) {
939 d3.event.preventDefault();
940 log('dblclick');
941 },
942 'contextmenu': function (d, i) {
943 d3.event.preventDefault();
944 log("contextmenu node");
945 self.eventHandler.fire("right_click_node", d);
946 }
947 },
948 'links': {
949 'click': function (event) {
950
951 },
952 'dblclick': function (event) {
953
954 }
955 }
956 };
957 };
958
959 /**
960 * Deselect previously selected nodes
961 *
962 */
963 GraphEditor.prototype._deselectAllNodes = function () {
964 log("_deselectAllNodes");
965 this.node.classed("node_selected", false);
966 this._selected_node = undefined;
967 };
968
969 GraphEditor.prototype._deselectAllLinks = function () {
970 log("_deselectAllLinks");
971 this.link.classed("link_selected", false).style('stroke-width', 2);
972 this._selected_link = undefined;
973 };
974 /**
975 * Select node in exclusive mode
976 * @param {Object} Required. Element selected on click event
977 */
978 GraphEditor.prototype._selectNodeExclusive = function (node_instance, node_id) {
979 log("_selectNodeExclusive ");
980 var activeClass = "node_selected";
981 var alreadyIsActive = d3.select(node_instance).classed(activeClass);
982 this._deselectAllNodes();
983 this._deselectAllLinks();
984 d3.select(node_instance).classed(activeClass, !alreadyIsActive);
985 this._selected_node = (alreadyIsActive) ? undefined : node_instance.__data__;
986 if(this._selected_node){
987 this.eventHandler.fire("node:selected", this._selected_node)
988 } else {
989 this.eventHandler.fire("node:deselected", this._selected_node)
990 }
991 };
992
993 /**
994 * Select node in exclusive mode
995 * @param {Object} Required. Element selected on click event
996 */
997 GraphEditor.prototype._selectLinkExclusive = function (link_instance, link_id) {
998 log("_selectLinkExclusive ");
999 var activeClass = "link_selected";
1000 var alreadyIsActive = d3.select(link_instance).classed(activeClass);
1001 this._deselectAllNodes();
1002 this._deselectAllLinks();
1003 d3.select(link_instance).classed(activeClass, !alreadyIsActive);
1004 d3.select(link_instance).style('stroke-width', 4)
1005 this._selected_link = link_instance.__data__;
1006 };
1007
1008 /**
1009 * Callback to resize SVG element on window resize
1010 */
1011 GraphEditor.prototype.resizeSvg = function (width, height) {
1012 log("resizeSvg");
1013 //log(event);
1014 this.width = width || this.width;
1015 this.height = height || this.height;
1016 this.svg.attr('width', width);
1017 this.svg.attr('height', height);
1018
1019 }
1020
1021 GraphEditor.prototype.handlePopupVisibility = function (visible, side) {
1022 var opacity = (visible) ? 1 : 0;
1023
1024 var translate_op = (side === "left") ? "translate(50 50)" : "translate(" + (this.width - 450).toString() + " 50)";
1025
1026 if (!visible) {
1027 d3.selectAll(".popupcleanable").remove();
1028 d3.select(".popup")
1029 .attr("transform", "translate(-1 -1)");
1030 } else {
1031 d3.select(".popup")
1032 .attr("transform", translate_op);
1033 }
1034 d3.select(".popup").attr("opacity", opacity);
1035 };
1036
1037 GraphEditor.prototype.refreshGraphParameters = function (graphParameters) {
1038 this.eventHandler.fire("refresh_graph_parameters", graphParameters);
1039 };
1040
1041 /**
1042 * Log utility
1043 */
1044 function log(text) {
1045 if (DEBUG)
1046 console.log("::GraphEditor::", text);
1047 }
1048
1049
1050 return GraphEditor;
1051
1052
1053 }(this));
1054
1055 if (typeof module === 'object') {
1056 module.exports = TCD3.GraphEditor;
1057 }