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").length
> 0) ? $("#palette").width() : 0;
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
) {
231 case d3
.symbolCircle
:
234 case d3
.symbolSquare
:
237 case d3
.symbolDiamond
:
240 case d3
.symbolTriangle
:
250 // if the string is not recognized
252 //return d3.symbolCircleUnknown;
258 * Start or Stop force layout
259 * @param {boolean} Required. Value true: start, false: stop
262 GraphEditor
.prototype.handleForce = function (start
) {
265 this.forceSimulationActive
= start
;
266 this.node
.each(function (d
) {
267 d
.fx
= (start
) ? null : d
.x
;
268 d
.fy
= (start
) ? null : d
.y
;
272 this.force
.restart();
274 this.eventHandler
.fire("force_status_changed_on", start
);
278 * Handle the parameters of basic filters: node type, view, group
279 * @param {Object} Required.
282 GraphEditor
.prototype.handleFiltersParams = function (filtersParams
, notFireEvent
) {
283 console
.log("handleFiltersParams", filtersParams
)
284 this.filter_parameters
= (filtersParams
!= undefined) ? filtersParams
: this.filter_parameters
;
285 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
289 this.force
.restart();
290 this._deselectAllNodes();
291 this.handleForce(this.forceSimulationActive
);
293 this.eventHandler
.fire("filters_changed", filtersParams
);
298 * Add a new node to the graph.
299 * @param {Object} Required. An object that specifies tha data of the new node.
302 GraphEditor
.prototype.addNode = function (args
) {
303 if (args
.id
&& args
.info
&& args
.info
.type
) {
307 this.d3_graph
.nodes
.push(args
);
310 this.force
.restart();
311 this.handleForce(this.forceSimulationActive
);
320 * Update the data properties of the node
321 * @param {Object} Required. An object that specifies tha data of the node.
324 GraphEditor
.prototype.updateDataNode = function (args
) {
329 * Remove a node from graph and related links.
330 * @param {String} Required. Id of node to remove.
333 GraphEditor
.prototype.removeNode = function (node
) {
334 if (node
!= undefined) {
335 var node_id
= node
.id
;
336 this.d3_graph
['nodes'].forEach(function (n
, index
, object
) {
337 if (n
.id
== node_id
) {
338 object
.splice(index
, 1);
343 //TODO trovare una metodo piu efficace
345 var links_to_remove
= [];
346 this.d3_graph
['links'].forEach(function (l
, index
, object
) {
347 if (node_id
=== l
.source
.id
|| node_id
=== l
.target
.id
) {
348 links_to_remove
.push(index
);
352 var links_removed
= 0;
353 links_to_remove
.forEach(function (l_index
) {
354 self
.d3_graph
['links'].splice(l_index
- links_removed
, 1);
360 this.force
.restart();
369 * Add a new link to graph.
370 * @param {Object} Required. An object that specifies tha data of the new Link.
373 GraphEditor
.prototype.addLink = function (link
) {
374 console
.log("addLink" + JSON
.stringify(link
))
375 if (link
.source
&& link
.target
) {
378 this.d3_graph
.links
.push(link
);
381 this.force
.restart();
389 * Remove a link from graph.
390 * @param {String} Required. The identifier of link to remove.
393 GraphEditor
.prototype.removeLink = function (link_id
) {
395 if (link_id
!== 'undefined') {
396 this.d3_graph
['links'].forEach(function (l
, index
, object
) {
397 if (link_id
=== l
.index
) {
398 object
.splice(index
, 1);
403 self
.force
.restart();
415 * Force a refresh of GraphView
418 GraphEditor
.prototype.refresh = function () {
425 .data(self
.d3_graph
.links
426 .filter(this.link_filter_cb
)
429 .attr("class", "link cleanable")
431 .attr("class", "link")
432 .attr("class", "cleanable")
433 .style("stroke-width", nominal_stroke
)
434 .style("stroke", function (d
) {
435 return self
._link_property_by_type((d
.type_link
) ? d
.type_link
: "unrecognized", "color");
437 .attr("marker-end", function (d
) {
438 if (!d
.directed_edge
)
441 var marker_url
= (d
.type_link
) ? d
.type_link
: "unrecognized"
442 return (d
.directed_edge
? "url(#" + marker_url
+ ")" : '');
445 this.nodeContainer
= this.svg
447 .data(self
.d3_graph
.nodes
448 .filter(this.node_filter_cb
))
451 // .attr("class", "nodosdads")
452 .attr("class", "node cleanable");
454 this.svg
.selectAll('.node')
455 .data(self
.d3_graph
.nodes
456 .filter(this.node_filter_cb
))
458 .filter(function (d
) {
459 return (d
.info
.type
== undefined) || (self
._node_property_by_type(d
.info
.type
, 'image', d
) == undefined)
463 .attr("d", d3
.symbol()
465 return Math
.PI
* Math
.pow(self
._node_property_by_type(d
.info
.type
, 'size', d
), 2) / 4;
468 // console.log(d.info.type, 'shape', self.current_view_id)
469 return (self
._node_property_by_type(d
.info
.type
, 'shape', d
));
472 .style("fill", function (d
) {
473 return self
._node_property_by_type(d
.info
.type
, 'color', d
);
475 .attr("transform", function () {
476 return "rotate(-45)";
479 .attr("stroke-width", 2.4)
481 .attr("class", "node_path")
482 .attr("id", function (d
) {
483 return "path_" + d
.id
;
487 .on("start", dragstarted
)
489 .on("end", dragended
));
491 var figure_node
= this.svg
.selectAll('.node')
492 .data(self
.d3_graph
.nodes
493 .filter(this.node_filter_cb
))
495 .filter(function (d
) {
496 return self
._node_property_by_type(d
.info
.type
, 'image', d
) != undefined
499 figure_node
.append("svg:image")
500 .attr("xlink:href", function (d
) {
501 return self
._node_property_by_type(d
.info
.type
, 'image', d
)
503 .attr("x", function (d
) {
504 return -self
._node_property_by_type(d
.info
.type
, 'size', d
) / 2
506 .attr("y", function (d
) {
507 return -self
._node_property_by_type(d
.info
.type
, 'size', d
) / 2
509 .attr("width", function (d
) {
510 return self
._node_property_by_type(d
.info
.type
, 'size', d
)
512 .attr("height", function (d
) {
513 return self
._node_property_by_type(d
.info
.type
, 'size', d
)
515 .style("stroke", "black")
516 .style("stroke-width", "1px")
518 .attr("class", "node_path")
519 .attr("id", function (d
) {
520 return "path_" + d
.id
;
523 .on("start", dragstarted
)
525 .on("end", dragended
));
527 figure_node
.append("svg:path")
528 .attr("d", d3
.symbol()
530 return Math
.PI
* Math
.pow(self
._node_property_by_type(d
.info
.type
, 'size', d
) + 7, 2) / 4;
533 return (self
.get_d3_symbol('circle'));
536 .style("fill", 'transparent')
537 .attr("transform", function () {
538 return "rotate(-45)";
541 .attr("stroke-width", 2.4)
543 .attr("class", "hidden_circle")
544 .attr("id", function (d
) {
545 return "path_" + d
.id
;
549 .on("start", dragstarted
)
551 .on("end", dragended
));
555 this.node
= this.svg
.selectAll('.node')
556 .data(self
.d3_graph
.nodes
557 .filter(this.node_filter_cb
)).selectAll("image, path, circle");
561 this.node
.on("contextmenu", self
.behavioursOnEvents
.nodes
["contextmenu"])
562 .on("mouseover", self
.behavioursOnEvents
.nodes
["mouseover"])
563 .on("mouseout", self
.behavioursOnEvents
.nodes
["mouseout"])
564 .on('click', self
.behavioursOnEvents
.nodes
["click"])
565 .on('dblclick', self
.behavioursOnEvents
.nodes
["dblclick"]);
568 .on("contextmenu", self
.behavioursOnEvents
.links
["contextmenu"])
569 .on("mouseover", self
.behavioursOnEvents
.links
["mouseover"])
570 .on('click', self
.behavioursOnEvents
.links
["click"])
571 .on("mouseout", self
.behavioursOnEvents
.links
["mouseout"]);
575 this.text
= this.svg
.selectAll(".node")
576 .data(self
.d3_graph
.nodes
577 .filter(this.node_filter_cb
))
579 .attr("class", "nodetext")
580 .attr("class", "cleanable")
581 .attr("dy", function(d
) {
582 if (self
._node_property_by_type(d
.info
.type
, 'image', d
) == undefined) {
588 return (-self
._node_property_by_type(d
.info
.type
, 'size', d
)/2).toString()
591 .attr("pointer-events", "none")
592 .style("font-size", nominal_text_size
+ "px")
593 .style("font-family", "Lucida Console")
594 .style("fill", function (d
) {
595 return self
._node_property_by_type(d
.info
.type
, 'node_label_color', d
);
597 .style("text-anchor", "middle")
604 function dragstarted(d
) {
605 d
.draggednode
= true;
606 if (!d3
.event
.active
) self
.force
.alphaTarget(0.3).restart();
612 function dragged(d
) {
617 function dragended(d
) {
618 d
.draggednode
= false;
619 if (!d3
.event
.active
) self
.force
.alphaTarget(0);
620 if (self
.forceSimulationActive
) {
627 self
.forceSimulationActive
= false;
635 * Start force layout on Graph.
638 GraphEditor
.prototype.startForce = function () {
642 .nodes(this.d3_graph
.nodes
)
648 .links(this.d3_graph
.links
);
651 self
.node
.attr("cx", function (d
) {
652 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 .attr("cy", function (d
) {
655 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
));
658 self
.link
.attr("d", function (d
) {
659 var dx
= d
.target
.x
- d
.source
.x
,
660 dy
= d
.target
.y
- d
.source
.y
,
661 dr
= Math
.sqrt(dx
* dx
+ dy
* dy
);
662 return "M" + d
.source
.x
+ "," + d
.source
.y
+ "," + d
.target
.x
+ "," + d
.target
.y
;
665 self
.node
.attr("transform", function (d
) {
666 return "translate(" + d
.x
+ "," + d
.y
+ ")";
668 self
.text
.attr("transform", function (d
) {
669 var label_pos_y
= d
.y
+ self
._node_property_by_type(d
.info
.type
, 'size', d
) + 10;
670 return "translate(" + d
.x
+ "," + label_pos_y
+ ")";
679 * This method attaches an event handler.
680 * @param {String} Required. A String that specifies the name of the event.
681 * @param {Function} Required. Specifies the function to run when the event occurs.
684 GraphEditor
.prototype.addListener = function (event_name
, cb
) {
685 this.eventHandler
.addL(event_name
, cb
);
689 * This method removes an event handler that has been attached with the addListener() method.
690 * @param {String} Required. A String that specifies the name of the event to remove.
691 * @param {Function} Required. Specifies the function to remove.
694 GraphEditor
.prototype.removeListener = function (event_name
, cb
) {
699 GraphEditor
.prototype.setNodeClass = function (class_name
, filter_cb
) {
702 this.svg
.selectAll('.node').classed(class_name
, false);
703 this.svg
.selectAll('.node')
704 .classed(class_name
, filter_cb
);
707 GraphEditor
.prototype.setLinkClass = function (class_name
, filter_cb
) {
710 this.svg
.selectAll('.link').classed(class_name
, false);
711 this.svg
.selectAll('.link')
712 .classed(class_name
, filter_cb
);
715 GraphEditor
.prototype.showNodeInfo = function(args
){
716 this.addLinesToPopup(args
['node_info'], "Info about node selected")
717 this.handlePopupVisibility(true, 'right')
719 GraphEditor
.prototype.addLinesToPopup = function(data
, title
) {
723 var width_popup
= 400;
724 var height_popup
= 0;
726 d3
.selectAll(".popupcleanable").remove(); // clean
728 var popupbg
= d3
.select(".popup").append("rect")
729 .attr("id", "popupbg")
730 .attr("class", "popup bg popupcleanable cleanable")
731 .attr("width", "400")
733 .attr("rx", 10) // set the x corner curve radius
734 .attr("ry", 10); // set the y corner curve radius
737 d3
.select(".popup").append("svg:path")
738 .attr("d", d3
.symbol()
743 console
.log("popiup")
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
;