[wip] composer nsd
[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 var link = this.svg
400 .selectAll()
401 .data(self.d3_graph.links
402 .filter(this.link_filter_cb)
403 );
404 link.exit().remove();
405 this.link = link.enter().append("g")
406 .attr("class", "link cleanable")
407 .append("path")
408 .attr("class", "link")
409 .attr("class", "cleanable")
410 .style("stroke-width", nominal_stroke)
411 .style("stroke", function (d) {
412 return self._link_property_by_type((d.type_link) ? d.type_link : "unrecognized", "color");
413 })
414 .attr("marker-end", function (d) {
415 if (!d.directed_edge)
416 return '';
417
418 var marker_url = (d.type_link) ? d.type_link : "unrecognized"
419 return (d.directed_edge ? "url(#" + marker_url + ")" : '');
420 });
421
422 var nodeContainer = this.svg
423 .selectAll()
424 .data(self.d3_graph.nodes
425 .filter(this.node_filter_cb));
426 nodeContainer.exit().remove();
427 nodeContainer.enter()
428 .append("g")
429 // .attr("class", "nodosdads")
430 .attr("class", "node cleanable");
431
432 var nodes_symbols = this.svg.selectAll('.node')
433 .data(self.d3_graph.nodes
434 .filter(this.node_filter_cb))
435
436 .filter(function (d) {
437 return (d.info.type === undefined) || (self._node_property_by_type(d.info.type, 'image', d) === undefined)
438 });
439 nodes_symbols.exit().remove();
440
441 nodes_symbols.append("svg:path")
442 .attr("d", d3.symbol()
443 .size(function (d) {
444 return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d), 2) / 4;
445 })
446 .type(function (d) {
447 // console.log(d.info.type, 'shape', self.current_view_id)
448 return (self._node_property_by_type(d.info.type, 'shape', d));
449 })
450 )
451 .style("fill", function (d) {
452 return self._node_property_by_type(d.info.type, 'color', d);
453 })
454 .attr("transform", function () {
455 return "rotate(-45)";
456
457 })
458 .attr("stroke-width", 2.4)
459
460 .attr("class", "node_path")
461 .attr("id", function (d) {
462 return "path_" + d.id;
463 })
464
465 .call(d3.drag()
466 .on("start", dragstarted)
467 .on("drag", dragged)
468 .on("end", dragended));
469
470 var figure_node = this.svg.selectAll('.node')
471 .data(self.d3_graph.nodes
472 .filter(this.node_filter_cb))
473
474 .filter(function (d) {
475 return self._node_property_by_type(d.info.type, 'image', d) != undefined
476 });
477 figure_node.exit().remove();
478 figure_node.append("svg:image")
479 .attr("xlink:href", function (d) {
480 return self._node_property_by_type(d.info.type, 'image', d)
481 })
482 .attr("x", function (d) {
483 return -self._node_property_by_type(d.info.type, 'size', d) / 2
484 })
485 .attr("y", function (d) {
486 return -self._node_property_by_type(d.info.type, 'size', d) / 2
487 })
488 .attr("width", function (d) {
489 return self._node_property_by_type(d.info.type, 'size', d)
490 })
491 .attr("height", function (d) {
492 return self._node_property_by_type(d.info.type, 'size', d)
493 })
494 .style("stroke", "black")
495 .style("stroke-width", "1px")
496
497 .attr("class", "node_path")
498 .attr("id", function (d) {
499 return "path_" + d.id;
500 })
501 .call(d3.drag()
502 .on("start", dragstarted)
503 .on("drag", dragged)
504 .on("end", dragended));
505
506 figure_node.append("svg:path")
507 .attr("d", d3.symbol()
508 .size(function (d) {
509 return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d) + 7, 2) / 4;
510 })
511 .type(function (d) {
512 return (self.get_d3_symbol('circle'));
513 })
514 )
515 .style("fill", 'transparent')
516 .attr("transform", function () {
517 return "rotate(-45)";
518
519 })
520 .attr("stroke-width", 2.4)
521
522 .attr("class", "hidden_circle")
523 .attr("id", function (d) {
524 return "path_" + d.id;
525 })
526
527 .call(d3.drag()
528 .on("start", dragstarted)
529 .on("drag", dragged)
530 .on("end", dragended));
531
532
533 this.node = this.svg.selectAll('.node')
534 .data(self.d3_graph.nodes
535 .filter(this.node_filter_cb)).selectAll("image, path, circle");
536
537
538 this.node.on("contextmenu", self.behavioursOnEvents.nodes["contextmenu"])
539 .on("mouseover", self.behavioursOnEvents.nodes["mouseover"])
540 .on("mouseout", self.behavioursOnEvents.nodes["mouseout"])
541 .on('click', self.behavioursOnEvents.nodes["click"])
542 .on('dblclick', self.behavioursOnEvents.nodes["dblclick"]);
543
544 this.link
545 .on("contextmenu", self.behavioursOnEvents.links["contextmenu"])
546 .on("mouseover", self.behavioursOnEvents.links["mouseover"])
547 .on('click', self.behavioursOnEvents.links["click"])
548 .on("mouseout", self.behavioursOnEvents.links["mouseout"]);
549
550
551 this.text = this.svg.selectAll(".node")
552 .data(self.d3_graph.nodes
553 .filter(this.node_filter_cb))
554 .append("svg:text")
555 .attr("class", "node_text cleanable")
556 .attr("dy", function (d) {
557 return "-5";
558 })
559 .attr("pointer-events", "none")
560 .style("font-size", nominal_text_size + "px")
561 .style("font-family", "'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif")
562 .style("fill", function (d) {
563 return self._node_property_by_type(d.info.type, 'node_label_color', d);
564 })
565 //.style("text-anchor", "middle")
566 .text(function (d) {
567 if(d.info && d.info.property.custom_label && d.info.property.custom_label !==''){
568 return d.info.property.custom_label
569 } else
570 return d.id;
571 });
572
573
574 function dragstarted(d) {
575 d.draggednode = true;
576 if (!d3.event.active) self.force.alphaTarget(0.3).restart();
577 d.fx = d.x;
578 d.fy = d.y;
579
580 }
581
582 function dragged(d) {
583 d.fx = d3.event.x;
584 d.fy = d3.event.y;
585 }
586
587 function dragended(d) {
588 d.draggednode = false;
589 if (!d3.event.active) self.force.alphaTarget(0);
590 if (self.forceSimulationActive) {
591 d.fx = null;
592 d.fy = null;
593 } else {
594 d.fx = d.x;
595 d.fy = d.y;
596 self.force.stop();
597 self.forceSimulationActive = false;
598 }
599 }
600
601
602 };
603
604 /**
605 * Start force layout on Graph.
606 *
607 */
608 GraphEditor.prototype.startForce = function () {
609 this.force.stop();
610 var self = this;
611 this.force
612 .nodes(this.d3_graph.nodes)
613 .on("tick", ticked);
614
615
616 this.force
617 .force("link")
618 .links(this.d3_graph.links);
619
620 function ticked() {
621 self.node.attr("cx", function (d) {
622 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));
623 })
624 .attr("cy", function (d) {
625 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));
626 });
627
628 self.link.attr("d", function (d) {
629 var dx = d.target.x - d.source.x,
630 dy = d.target.y - d.source.y,
631 dr = Math.sqrt(dx * dx + dy * dy);
632 return "M" + d.source.x + "," + d.source.y + "," + d.target.x + "," + d.target.y;
633 });
634
635 self.node.attr("transform", function (d) {
636 return "translate(" + d.x + "," + d.y + ")";
637 });
638 self.text.attr("transform", function (d) {
639 var label_pos_y = d.y + self._node_property_by_type(d.info.type, 'size', d)/2 +nominal_text_size;
640 return "translate(" + d.x + "," + label_pos_y + ")";
641 });
642 }
643
644
645 };
646
647 /**
648 * This method attaches an event handler.
649 * @param {String} Required. A String that specifies the name of the event.
650 * @param {Function} Required. Specifies the function to run when the event occurs.
651 * @returns {}
652 */
653 GraphEditor.prototype.addListener = function (event_name, cb) {
654 this.eventHandler.addL(event_name, cb);
655 };
656
657 /**
658 * This method removes an event handler that has been attached with the addListener() method.
659 * @param {String} Required. A String that specifies the name of the event to remove.
660 * @param {Function} Required. Specifies the function to remove.
661 * @returns {}
662 */
663 GraphEditor.prototype.removeListener = function (event_name, cb) {
664
665 };
666
667
668 GraphEditor.prototype.setNodeClass = function (class_name, filter_cb) {
669 log("setNodeClass");
670 var self = this;
671 this.svg.selectAll('.node').classed(class_name, false);
672 this.svg.selectAll('.node')
673 .classed(class_name, filter_cb);
674 };
675
676 GraphEditor.prototype.setLinkClass = function (class_name, filter_cb) {
677 log("setLinkClass");
678 var self = this;
679 this.svg.selectAll('.link').classed(class_name, false);
680 this.svg.selectAll('.link')
681 .classed(class_name, filter_cb);
682 };
683
684 GraphEditor.prototype.showNodeInfo = function (args) {
685 this.addLinesToPopup(args['node_info'], "Info about node selected")
686 this.handlePopupVisibility(true, 'right')
687 };
688
689 GraphEditor.prototype.addLinesToPopup = function (data, title) {
690 var self = this;
691 var index = 1;
692 var translate_y = 0;
693 var width_popup = 400;
694 var height_popup = 0;
695
696 d3.selectAll(".popupcleanable").remove(); // clean
697
698 var popupbg = d3.select(".popup").append("rect")
699 .attr("id", "popupbg")
700 .attr("class", "popup bg popupcleanable cleanable")
701 .attr("width", "400")
702 .attr("height", "0")
703 .attr("rx", 10) // set the x corner curve radius
704 .attr("ry", 10); // set the y corner curve radius
705
706
707 d3.select(".popup").append("svg:path")
708 .attr("d", d3.symbol()
709 .size(function (d) {
710 return 80
711 })
712 .type(function (d) {
713 console.log("popup")
714 return (self.get_d3_symbol());
715 })
716 )
717 .style("fill", 'red')
718 .attr("transform", function () {
719 return "translate(380,15) rotate(-45)";
720
721 })
722 .attr("stroke-width", 2.4)
723 .attr("id", "close_popup")
724 .attr("class", "popupcleanable cleanable")
725 .on("click", function (d) {
726 self.handlePopupVisibility(false);
727 });
728
729 d3.select(".popup").append("text")
730 .attr("class", "popup title popupcleanable cleanable")
731 .attr("x", "10")
732 .attr("y", "20")
733 .text(title);
734
735 for (var i in data) {
736 //console.log(i, data, data[i])
737 //var typeofvalue = typeof data[i];
738 var record = data[i];
739 index = this._addRecordToPopup(i, record, index)
740
741 }
742
743 };
744
745 GraphEditor.prototype._addRecordToPopup = function (key, record, index, tab) {
746 //console.log("_addRecordToPopup", key, record, index)
747 var translate_y = 23 * index;
748 var summary = d3.select(".popup").append("g")
749 .attr("class", "popup summary d popupcleanable cleanable")
750 .attr("transform", "translate(10 " + translate_y + ")");
751 if (Object.prototype.toString.call(record) !== '[object Array]') { //is a record simple key:value
752 //console.log(key, record)
753 var summary_g = summary.append("g");
754 summary_g.append("rect")
755 .attr("class", "popup summary bg popupcleanable cleanable")
756 .attr("width", "380")
757 .attr("height", "20");
758
759 summary_g.append("text")
760 .attr("class", "popup summary popupcleanable cleanable")
761 .attr("x", (tab) ? tab : 10)
762 .attr("y", "17")
763 .attr("width", "100")
764 .text(function (d) {
765 return key.toUpperCase() + ":";
766 });
767
768 summary_g.append("text")
769 .attr("class", "popup summary popupcleanable cleanable")
770 .attr("x", "370")
771 .attr("y", "17")
772 .attr("text-anchor", "end")
773 .text(function (d) {
774 return record
775 });
776 }
777 else {//is a record simple complex: have a list of sub record key:value
778 //index ++;
779 this._addRecordToPopup(key, "", index)
780 for (var r in record) {
781 //console.log(i, r, record, record[r])
782 for (var k in record[r]) {
783 //console.log(i, r, k, record[r][k])
784 var curr_key = k;
785 var recordValue = record[r][k]
786
787 index++;
788 this._addRecordToPopup(curr_key, recordValue, index, 20)
789 }
790 }
791
792 }
793
794 translate_y = 30 * index++;
795 d3.select('#popupbg').attr("height", translate_y);
796 return index;
797 };
798
799
800 /**
801 * Remove all the graph objects from the view
802 */
803 GraphEditor.prototype.cleanAll = function () {
804 this.svg.selectAll('.cleanable').remove();
805 };
806
807 /**
808 * Internal functions
809 */
810
811 GraphEditor.prototype._node_property_by_type = function (type, property, node) {
812 //console.log(type, property, layer, group)
813 var unrecognized = function (ui_prop, property) {
814 return ui_prop['unrecognized'][property]
815 };
816
817 //type recognized
818 if (this.type_property[type]) {
819
820 if (this.type_property[type]['property']) {
821 var filt_property = this.type_property[type]['property']
822 return this.type_property[type][node.info[filt_property]][property]
823 } else { // type without property spec
824
825 return this.type_property[type][property]
826
827 }
828
829 } else { //type unrecognized
830 return unrecognized(this.type_property, property)
831 }
832
833 };
834
835 GraphEditor.prototype._link_property_by_type = function (type, property) {
836 //log(type + "-" + property)
837 if (this.type_property_link[type] != undefined && this.type_property_link[type][property] != undefined) {
838 //if(property == "shape")
839 // log("dentro" + this.type_property[type][property])
840 return this.type_property_link[type][property];
841 } else {
842 return this.type_property_link['unrecognized'][property];
843 }
844
845 }
846
847
848 /**
849 *
850 *
851 *
852 */
853 GraphEditor.prototype._setupFiltersBehaviors = function (args) {
854
855 var self = this;
856
857 this.node_filter_cb = args.node_filter_cb || function (d) {
858
859 var cond_view = true,
860 cond_group = true;
861 //log(d.info.type + " " + self.filter_parameters.node.type + " group: " + self.filter_parameters.node.group + "- " + d.info.group)
862 // check filter by node type
863 if (self.filter_parameters.node.type.length > 0) {
864
865 if (self.filter_parameters.node.type.indexOf(d.info.type) < 0)
866 cond_view = false;
867 }
868
869 // check filter by group
870 if (self.filter_parameters.node.group.length > 0) {
871 self.filter_parameters.node.group.forEach(function (group) {
872 if (d.info.group.indexOf(group) < 0)
873 cond_group = false;
874 });
875
876
877 }
878
879
880 return cond_view && cond_group;
881 };
882
883 this.link_filter_cb = args.link_filter_cb || function (d) {
884 var cond_view = true,
885 cond_group = true;
886
887 // check filter by view
888 if (self.filter_parameters.link.view.length > 0) {
889 self.filter_parameters.link.view.forEach(function (view) {
890 if (d.view.indexOf(view) < 0)
891 cond_view = false;
892 });
893 }
894
895 // check filter by group
896 if (self.filter_parameters.link.group.length > 0) {
897 self.filter_parameters.link.group.forEach(function (group) {
898 if (d.group.indexOf(group) < 0)
899 cond_group = false;
900 });
901 }
902 return cond_view && cond_group;
903 };
904
905 };
906
907 /**
908 *
909 *
910 */
911 GraphEditor.prototype._setupBehaviorsOnEvents = function () {
912 log("_setupBehaviorsOnEvents");
913 var self = this;
914 this.behavioursOnEvents = {
915 'nodes': {
916 'click': function (d) {
917 d3.event.preventDefault();
918 log('click', d);
919 if (self.lastKeyDown === SHIFT_BUTTON && self._selected_node !== undefined) {
920 var source_id = self._selected_node.id;
921 var target_id = d.id;
922 log("--" + JSON.stringify(self.filter_parameters.link.view));
923 var new_link = {
924 source: source_id,
925 target: target_id,
926 view: self.filter_parameters.link.view[0],
927 group: self.filter_parameters.link.group[0],
928 };
929 self.addLink(new_link);
930 self._deselectAllNodes();
931 } else {
932 self._selectNodeExclusive(this, d);
933 }
934
935 },
936 'mouseover': function (d) {
937
938 },
939 'mouseout': function (d) {
940 },
941 'dblclick': function (d) {
942 d3.event.preventDefault();
943 log('dblclick');
944 },
945 'contextmenu': function (d, i) {
946 d3.event.preventDefault();
947 log("contextmenu node");
948 self.eventHandler.fire("right_click_node", d);
949 }
950 },
951 'links': {
952 'click': function (event) {
953
954 },
955 'dblclick': function (event) {
956
957 }
958 }
959 };
960 };
961
962 /**
963 * Deselect previously selected nodes
964 *
965 */
966 GraphEditor.prototype._deselectAllNodes = function () {
967 log("_deselectAllNodes");
968 this.node.classed("node_selected", false);
969 this._selected_node = undefined;
970 };
971
972 GraphEditor.prototype._deselectAllLinks = function () {
973 log("_deselectAllLinks");
974 this.link.classed("link_selected", false).style('stroke-width', 2);
975 this._selected_link = undefined;
976 };
977 /**
978 * Select node in exclusive mode
979 * @param {Object} Required. Element selected on click event
980 */
981 GraphEditor.prototype._selectNodeExclusive = function (node_instance, node_id) {
982 log("_selectNodeExclusive ");
983 var activeClass = "node_selected";
984 var alreadyIsActive = d3.select(node_instance).classed(activeClass);
985 this._deselectAllNodes();
986 this._deselectAllLinks();
987 d3.select(node_instance).classed(activeClass, !alreadyIsActive);
988 this._selected_node = (alreadyIsActive) ? undefined : node_instance.__data__;
989 if(this._selected_node){
990 this.eventHandler.fire("node:selected", this._selected_node)
991 } else {
992 this.eventHandler.fire("node:deselected", this._selected_node)
993 }
994 };
995
996 /**
997 * Select node in exclusive mode
998 * @param {Object} Required. Element selected on click event
999 */
1000 GraphEditor.prototype._selectLinkExclusive = function (link_instance, link_id) {
1001 log("_selectLinkExclusive ");
1002 var activeClass = "link_selected";
1003 var alreadyIsActive = d3.select(link_instance).classed(activeClass);
1004 this._deselectAllNodes();
1005 this._deselectAllLinks();
1006 d3.select(link_instance).classed(activeClass, !alreadyIsActive);
1007 d3.select(link_instance).style('stroke-width', 4)
1008 this._selected_link = link_instance.__data__;
1009 };
1010
1011 /**
1012 * Callback to resize SVG element on window resize
1013 */
1014 GraphEditor.prototype.resizeSvg = function (width, height) {
1015 log("resizeSvg");
1016 //log(event);
1017 this.width = width || this.width;
1018 this.height = height || this.height;
1019 this.svg.attr('width', width);
1020 this.svg.attr('height', height);
1021
1022 }
1023
1024 GraphEditor.prototype.handlePopupVisibility = function (visible, side) {
1025 var opacity = (visible) ? 1 : 0;
1026
1027 var translate_op = (side === "left") ? "translate(50 50)" : "translate(" + (this.width - 450).toString() + " 50)";
1028
1029 if (!visible) {
1030 d3.selectAll(".popupcleanable").remove();
1031 d3.select(".popup")
1032 .attr("transform", "translate(-1 -1)");
1033 } else {
1034 d3.select(".popup")
1035 .attr("transform", translate_op);
1036 }
1037 d3.select(".popup").attr("opacity", opacity);
1038 };
1039
1040 GraphEditor.prototype.refreshGraphParameters = function (graphParameters) {
1041 this.eventHandler.fire("refresh_graph_parameters", graphParameters);
1042 };
1043
1044 /**
1045 * Log utility
1046 */
1047 function log(text) {
1048 if (DEBUG)
1049 console.log("::GraphEditor::", text);
1050 }
1051
1052
1053 return GraphEditor;
1054
1055
1056 }(this));
1057
1058 if (typeof module === 'object') {
1059 module.exports = TCD3.GraphEditor;
1060 }