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