2 Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
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
8 http://www.apache.org/licenses/LICENSE-2.0
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.
16 if (typeof dreamer
=== 'undefined') {
21 dreamer
.GraphEditor
= (function (global
) {
25 var SHIFT_BUTTON
= 16;
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/";
38 function GraphEditor(args
) {
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
= {
55 this.current_view_id
= '';
56 // graph data initailization
69 GraphEditor
.prototype.init = function (args
) {
72 this.width
= 550//args.width || 500;
73 this.height
= 550// args.height || 500;
74 this.forceSimulationActive
= false;
77 this.width
= this.width
- this.width
* 0.007;
78 this.height
= this.height
- this.height
* 0.07;
80 //console.log("this.width", this.width, "this.height", this.height);
83 this._setupBehaviorsOnEvents();
84 this._setupFiltersBehaviors(args
);
86 this.type_property
= {
88 "shape": d3
.symbolCircle
,
90 "node_label_color": "black",
95 this.type_property_link
= {
102 this.force
= d3
.forceSimulation()
103 .force("collide", d3
.forceCollide().radius(40))
104 .force("link", d3
.forceLink().distance(80).iterations(1).id(function (d
) {
107 .force("center", d3
.forceCenter(this.width
/ 2, this.height
/ 2));
109 var zoom
= d3
.zoom().scaleExtent([min_zoom
, max_zoom
])
111 var size
= d3
.scalePow().exponent(2)
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
);
122 this.defs
= this.svg
.append("svg:defs");
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
128 .attr("viewBox", "-5 -5 10 10")
129 .attr("refX", 13) //must be smarter way to calculate shift
131 .attr("markerUnits", "userSpaceOnUse")
132 .attr("markerWidth", 12)
133 .attr("markerHeight", 12)
134 .attr("orient", "auto")
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']);
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
);
152 .on('keyup', function () {
153 log('keyup' + self
.lastKeyDown
);
154 self
.lastKeyDown
= -1;
156 var popup
= this.svg
.append("g")
158 .attr("class", "popup")
159 .attr("opacity", "0")
160 .attr("transform", "translate(1 1)")
162 .on("start", dragstarted
)
164 .on("end", dragended
));
166 function dragstarted(d
) {
167 //d3.select(this).raise().classed("active", true);
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
+")";
178 function dragended(d
) {
179 //d3.select(this).classed("active", false);
182 var chart
= $("#graph_svg");
183 this.aspect
= chart
.width() / chart
.height();
184 this.container
= chart
.parent();
185 $(window
).on("resize", function() {
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");
198 GraphEditor
.prototype.get_d3_symbol
=
199 function (myString
) {
203 return d3
.symbolCircle
;
206 return d3
.symbolSquare
;
209 return d3
.symbolDiamond
;
212 return d3
.symbolTriangle
;
215 return d3
.symbolStar
;
218 return d3
.symbolCross
;
221 // if the string is not recognized
222 return d3
.symbolCross
;
223 //return d3.symbolCircleUnknown;
228 GraphEditor
.prototype.get_name_from_d3_symbol
=
229 function (mySymbol
) {
232 case d3
.symbolCircle
:
235 case d3
.symbolSquare
:
238 case d3
.symbolDiamond
:
241 case d3
.symbolTriangle
:
251 // if the string is not recognized
253 //return d3.symbolCircleUnknown;
259 * Start or Stop force layout
260 * @param {boolean} Required. Value true: start, false: stop
263 GraphEditor
.prototype.handleForce = function (start
) {
266 this.forceSimulationActive
= start
;
267 this.node
.each(function (d
) {
268 d
.fx
= (start
) ? null : d
.x
;
269 d
.fy
= (start
) ? null : d
.y
;
273 this.force
.restart();
275 this.eventHandler
.fire("force_status_changed_on", start
);
279 * Handle the parameters of basic filters: node type, view, group
280 * @param {Object} Required.
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
290 this.force
.restart();
291 this._deselectAllNodes();
292 this.handleForce(this.forceSimulationActive
);
294 this.eventHandler
.fire("filters_changed", filtersParams
);
299 * Add a new node to the graph.
300 * @param {Object} Required. An object that specifies tha data of the new node.
303 GraphEditor
.prototype.addNode = function (args
) {
304 if (args
.id
&& args
.info
&& args
.info
.type
) {
308 this.d3_graph
.nodes
.push(args
);
311 this.force
.restart();
312 this.handleForce(this.forceSimulationActive
);
321 * Update the data properties of the node
322 * @param {Object} Required. An object that specifies tha data of the node.
325 GraphEditor
.prototype.updateDataNode = function (args
) {
330 * Remove a node from graph and related links.
331 * @param {String} Required. Id of node to remove.
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);
344 //TODO trovare una metodo piu efficace
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
);
353 var links_removed
= 0;
354 links_to_remove
.forEach(function (l_index
) {
355 self
.d3_graph
['links'].splice(l_index
- links_removed
, 1);
361 this.force
.restart();
370 * Add a new link to graph.
371 * @param {Object} Required. An object that specifies tha data of the new Link.
374 GraphEditor
.prototype.addLink = function (link
) {
375 console
.log(JSON
.stringify(link
))
376 if (link
.source
&& link
.target
) {
379 this.d3_graph
.links
.push(link
);
382 this.force
.restart();
390 * Remove a link from graph.
391 * @param {String} Required. The identifier of link to remove.
394 GraphEditor
.prototype.removeLink = function (link_id
) {
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);
404 self
.force
.restart();
416 * Force a refresh of GraphView
419 GraphEditor
.prototype.refresh = function () {
426 .data(self
.d3_graph
.links
427 .filter(this.link_filter_cb
)
430 .attr("class", "link cleanable")
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");
438 .attr("marker-end", function (d
) {
439 if (!d
.directed_edge
)
442 var marker_url
= (d
.type_link
) ? d
.type_link
: "unrecognized"
443 return (d
.directed_edge
? "url(#" + marker_url
+ ")" : '');
446 this.nodeContainer
= this.svg
448 .data(self
.d3_graph
.nodes
449 .filter(this.node_filter_cb
))
452 // .attr("class", "nodosdads")
453 .attr("class", "node cleanable");
455 this.svg
.selectAll('.node')
456 .data(self
.d3_graph
.nodes
457 .filter(this.node_filter_cb
))
459 .filter(function (d
) {
460 return (d
.info
.type
== undefined) || (self
._node_property_by_type(d
.info
.type
, 'image', d
) == undefined)
464 .attr("d", d3
.symbol()
466 return Math
.PI
* Math
.pow(self
._node_property_by_type(d
.info
.type
, 'size', d
), 2) / 4;
469 // console.log(d.info.type, 'shape', self.current_view_id)
470 return (self
._node_property_by_type(d
.info
.type
, 'shape', d
));
473 .style("fill", function (d
) {
474 return self
._node_property_by_type(d
.info
.type
, 'color', d
);
476 .attr("transform", function () {
477 return "rotate(-45)";
480 .attr("stroke-width", 2.4)
482 .attr("class", "node_path")
483 .attr("id", function (d
) {
484 return "path_" + d
.id
;
488 .on("start", dragstarted
)
490 .on("end", dragended
));
492 var figure_node
= this.svg
.selectAll('.node')
493 .data(self
.d3_graph
.nodes
494 .filter(this.node_filter_cb
))
496 .filter(function (d
) {
497 return self
._node_property_by_type(d
.info
.type
, 'image', d
) != undefined
500 figure_node
.append("svg:image")
501 .attr("xlink:href", function (d
) {
502 return self
._node_property_by_type(d
.info
.type
, 'image', d
)
504 .attr("x", function (d
) {
505 return -self
._node_property_by_type(d
.info
.type
, 'size', d
) / 2
507 .attr("y", function (d
) {
508 return -self
._node_property_by_type(d
.info
.type
, 'size', d
) / 2
510 .attr("width", function (d
) {
511 return self
._node_property_by_type(d
.info
.type
, 'size', d
)
513 .attr("height", function (d
) {
514 return self
._node_property_by_type(d
.info
.type
, 'size', d
)
516 .style("stroke", "black")
517 .style("stroke-width", "1px")
519 .attr("class", "node_path")
520 .attr("id", function (d
) {
521 return "path_" + d
.id
;
524 .on("start", dragstarted
)
526 .on("end", dragended
));
528 figure_node
.append("svg:path")
529 .attr("d", d3
.symbol()
531 return Math
.PI
* Math
.pow(self
._node_property_by_type(d
.info
.type
, 'size', d
) + 7, 2) / 4;
534 return (self
.get_d3_symbol('circle'));
537 .style("fill", 'transparent')
538 .attr("transform", function () {
539 return "rotate(-45)";
542 .attr("stroke-width", 2.4)
544 .attr("class", "hidden_circle")
545 .attr("id", function (d
) {
546 return "path_" + d
.id
;
550 .on("start", dragstarted
)
552 .on("end", dragended
));
556 this.node
= this.svg
.selectAll('.node')
557 .data(self
.d3_graph
.nodes
558 .filter(this.node_filter_cb
)).selectAll("image, path, circle");
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"]);
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"]);
576 this.text
= this.svg
.selectAll(".node")
577 .data(self
.d3_graph
.nodes
578 .filter(this.node_filter_cb
))
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) {
589 return (-self
._node_property_by_type(d
.info
.type
, 'size', d
)/2).toString()
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
);
598 .style("text-anchor", "middle")
605 function dragstarted(d
) {
606 d
.draggednode
= true;
607 if (!d3
.event
.active
) self
.force
.alphaTarget(0.3).restart();
613 function dragged(d
) {
618 function dragended(d
) {
619 d
.draggednode
= false;
620 if (!d3
.event
.active
) self
.force
.alphaTarget(0);
621 if (self
.forceSimulationActive
) {
628 self
.forceSimulationActive
= false;
636 * Start force layout on Graph.
639 GraphEditor
.prototype.startForce = function () {
643 .nodes(this.d3_graph
.nodes
)
649 .links(this.d3_graph
.links
);
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
));
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
));
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
;
666 self
.node
.attr("transform", function (d
) {
667 return "translate(" + d
.x
+ "," + d
.y
+ ")";
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
+ ")";
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.
685 GraphEditor
.prototype.addListener = function (event_name
, cb
) {
686 this.eventHandler
.addL(event_name
, cb
);
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.
695 GraphEditor
.prototype.removeListener = function (event_name
, cb
) {
700 GraphEditor
.prototype.setNodeClass = function (class_name
, filter_cb
) {
703 this.svg
.selectAll('.node').classed(class_name
, false);
704 this.svg
.selectAll('.node')
705 .classed(class_name
, filter_cb
);
708 GraphEditor
.prototype.setLinkClass = function (class_name
, filter_cb
) {
711 this.svg
.selectAll('.link').classed(class_name
, false);
712 this.svg
.selectAll('.link')
713 .classed(class_name
, filter_cb
);
716 GraphEditor
.prototype.showNodeInfo = function(args
){
717 this.addLinesToPopup(args
['node_info'], "Info about node selected")
718 this.handlePopupVisibility(true, 'right')
720 GraphEditor
.prototype.addLinesToPopup = function(data
, title
) {
724 var width_popup
= 400;
725 var height_popup
= 0;
727 d3
.selectAll(".popupcleanable").remove(); // clean
729 var popupbg
= d3
.select(".popup").append("rect")
730 .attr("id", "popupbg")
731 .attr("class", "popup bg popupcleanable cleanable")
732 .attr("width", "400")
734 .attr("rx", 10) // set the x corner curve radius
735 .attr("ry", 10); // set the y corner curve radius
738 d3
.select(".popup").append("svg:path")
739 .attr("d", d3
.symbol()
744 return (self
.get_d3_symbol());
747 .style("fill", 'red')
748 .attr("transform", function () {
749 return "translate(380,15) rotate(-45)";
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);
759 d3
.select(".popup").append("text")
760 .attr("class", "popup title popupcleanable cleanable")
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
)
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");
789 summary_g
.append("text")
790 .attr("class", "popup summary popupcleanable cleanable")
791 .attr("x", (tab
)? tab
: 10)
793 .attr("width", "100")
795 return key
.toUpperCase() + ":";
798 summary_g
.append("text")
799 .attr("class", "popup summary popupcleanable cleanable")
802 .attr("text-anchor", "end")
803 .text(function(d
){return record
});
805 else {//is a record simple complex: have a list of sub record key:value
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])
813 var recordValue
= record
[r
][k
]
816 this._addRecordToPopup(curr_key
, recordValue
, index
, 20)
822 translate_y
= 30 * index
++;
823 d3
.select('#popupbg').attr("height", translate_y
);
830 * Remove all the graph objects from the view
832 GraphEditor
.prototype.cleanAll = function () {
833 this.svg
.selectAll('.cleanable').remove();
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
]
847 if (this.type_property
[type
]) {
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
854 return this.type_property
[type
][property
]
858 } else { //type unrecognized
859 return unrecognized(this.type_property
, property
)
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
];
871 return this.type_property_link
['unrecognized'][property
];
882 GraphEditor
.prototype._setupFiltersBehaviors = function (args
) {
886 this.node_filter_cb
= args
.node_filter_cb
|| function (d
) {
888 var cond_view
= 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) {
894 if (self
.filter_parameters
.node
.type
.indexOf(d
.info
.type
) < 0)
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)
909 return cond_view
&& cond_group
;
912 this.link_filter_cb
= args
.link_filter_cb
|| function (d
) {
913 var cond_view
= true,
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)
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)
931 return cond_view
&& cond_group
;
940 GraphEditor
.prototype._setupBehaviorsOnEvents = function () {
941 log("_setupBehaviorsOnEvents");
943 this.behavioursOnEvents
= {
945 'click': function (d
) {
946 d3
.event
.preventDefault();
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
));
955 view
: self
.filter_parameters
.link
.view
[0],
956 group
: self
.filter_parameters
.link
.group
[0],
958 self
.addLink(new_link
);
959 self
._deselectAllNodes();
961 self
._selectNodeExclusive(this, d
);
965 'mouseover': function (d
) {
968 'mouseout': function (d
) {},
969 'dblclick': function (d
) {
970 d3
.event
.preventDefault();
973 'contextmenu': function (d
, i
) {
974 d3
.event
.preventDefault();
975 log("contextmenu node");
976 self
.eventHandler
.fire("right_click_node", d
);
980 'click': function (event
) {
983 'dblclick': function (event
) {
991 * Deselect previously selected nodes
994 GraphEditor
.prototype._deselectAllNodes = function () {
995 log("_deselectAllNodes");
996 this.node
.classed("node_selected", false);
997 this._selected_node
= undefined;
1000 GraphEditor
.prototype._deselectAllLinks = function () {
1001 log("_deselectAllLinks");
1002 this.link
.classed("link_selected", false).style('stroke-width', 2);
1003 this._selected_link
= undefined;
1006 * Select node in exclusive mode
1007 * @param {Object} Required. Element selected on click event
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__
;
1020 * Select node in exclusive mode
1021 * @param {Object} Required. Element selected on click event
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__
;
1035 * Callback to resize SVG element on window resize
1037 GraphEditor
.prototype.resizeSvg = function (width
, height
) {
1040 this.width
= width
|| this.width
;
1041 this.height
= height
|| this.height
;
1042 this.svg
.attr('width', width
);
1043 this.svg
.attr('height', height
);
1047 GraphEditor
.prototype.handlePopupVisibility = function(visible
, side
) {
1048 var opacity
= (visible
) ? 1 : 0;
1050 var translate_op
= (side
== "left") ? "translate(50 50)" : "translate("+(this.width
- 450).toString()+" 50)";
1053 d3
.selectAll(".popupcleanable").remove();
1055 .attr("transform", "translate(-1 -1)");
1058 .attr("transform", translate_op
);
1060 d3
.select(".popup").attr("opacity", opacity
);
1063 GraphEditor
.prototype.refreshGraphParameters = function (graphParameters
) {
1064 this.eventHandler
.fire("refresh_graph_parameters", graphParameters
);
1070 function log(text
) {
1072 console
.log("::GraphEditor::", text
);
1082 if (typeof module
=== 'object') {
1083 module
.exports
= dreamer
.GraphEditor
;