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