2 Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
3 Copyright 2018 EveryUP srl
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
9 http://www.apache.org/licenses/LICENSE-2.0
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.
18 if (typeof TCD3
=== 'undefined') {
22 TCD3
.GraphEditor
= (function () {
26 var SHIFT_BUTTON
= 16;
28 var nominal_text_size
= 14;
29 var nominal_stroke
= 1.5;
30 var EventHandler
= TCD3
.Event
;
36 function GraphEditor(args
) {
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
= {
53 this.current_view_id
= '';
54 // graph data initailization
66 GraphEditor
.prototype.init = function (args
) {
69 this.width
= args
.width
|| 1500;
70 this.height
= args
.height
|| 1500;
71 this.forceSimulationActive
= false;
75 this._setupBehaviorsOnEvents();
76 this._setupFiltersBehaviors(args
);
78 this.type_property
= {
80 "shape": d3
.symbolCircle
,
82 "node_label_color": "#000",
87 this.type_property_link
= {
93 this.force
= d3
.forceSimulation()
94 .force("charge", d3
.forceManyBody())
95 .force("collide", d3
.forceCollide().radius(80))
96 // .force("link", d3.forceLink().distance(80).iterations(1).id(function (d) {
97 .force("link", d3
.forceLink().distance(100).id(function (d
) {
100 .force("center", d3
.forceCenter(this.width
/ 2, this.height
/ 2));
102 var zoom
= d3
.zoom().scaleExtent([min_zoom
, max_zoom
]);
104 var size
= d3
.scalePow().exponent(2)
108 this.svg
= d3
.select("#graph_editor_container").append("svg")
109 .attr("id", "graph_svg")
110 .attr("preserveAspectRatio", "xMinYMid")
111 .attr("width", this.width
)
112 .attr("height", this.height
);
115 this.defs
= this.svg
.append("svg:defs");
117 this.defs
.selectAll("marker")
118 .data(["unrecognized"]) // Different link/path types can be defined here
119 .enter().append("svg:marker") // This section adds in the arrows
121 .attr("viewBox", "-5 -5 10 10")
122 .attr("refX", 13) //must be smarter way to calculate shift
124 .attr("markerUnits", "userSpaceOnUse")
125 .attr("markerWidth", 12)
126 .attr("markerHeight", 12)
127 .attr("orient", "auto")
129 .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z")
130 .attr('fill', this.type_property_link
['unrecognized']['color']);
133 .on('keydown', function () {
134 log('keydown ' + d3
.event
.keyCode
);
135 //d3.event.preventDefault();
136 if (self
.lastKeyDown
!== -1) return;
137 self
.lastKeyDown
= d3
.event
.keyCode
;
138 if (self
.lastKeyDown
=== CANC_BUTTON
&& self
._selected_node
!== undefined) {
139 self
.removeNode(self
._selected_node
, null, showAlert
);
140 } else if (self
.lastKeyDown
=== CANC_BUTTON
&& self
._selected_link
!== undefined) {
141 self
.removeLink(self
._selected_link
, null, showAlert
);
145 .on('keyup', function () {
146 log('keyup' + self
.lastKeyDown
);
147 self
.lastKeyDown
= -1;
149 var popup
= this.svg
.append("g")
151 .attr("class", "popup")
152 .attr("opacity", "0")
153 .attr("transform", "translate(1 1)")
155 .on("start", dragstarted
)
157 .on("end", dragended
));
159 function dragstarted(d
) {
160 //d3.select(this).raise().classed("active", true);
163 function dragged(d
) {
164 //console.log(JSON.stringify(d))
165 d3
.select(this).attr("transform", function () {
166 return "translate(" + d3
.event
.x
+ "," + d3
.event
.y
+ ")";
171 function dragended(d
) {
172 //d3.select(this).classed("active", false);
175 var chart
= $("#graph_svg");
176 this.aspect
= chart
.width() / chart
.height();
177 this.container
= $("#graph_editor_container");
178 $(window
).on("resize", function () {
180 self
.width
= self
.container
.width();
181 self
.height
= self
.container
.height();
182 chart
.attr("width", self
.container
.width());
183 chart
.attr("height", self
.container
.height());
184 }).trigger("resize");
189 GraphEditor
.prototype.get_d3_symbol
=
190 function (myString
) {
194 return d3
.symbolCircle
;
196 return d3
.symbolSquare
;
198 return d3
.symbolDiamond
;
200 return d3
.symbolTriangle
;
202 return d3
.symbolStar
;
204 return d3
.symbolCross
;
206 // if the string is not recognized
207 return d3
.symbolCross
;
212 GraphEditor
.prototype.get_name_from_d3_symbol
=
213 function (mySymbol
) {
215 case d3
.symbolCircle
:
217 case d3
.symbolSquare
:
219 case d3
.symbolDiamond
:
221 case d3
.symbolTriangle
:
228 // if the string is not recognized
230 //return d3.symbolCircleUnknown;
236 * Start or Stop force layout
237 * @param {boolean} Required. Value true: start, false: stop
240 GraphEditor
.prototype.handleForce = function (start
) {
243 this.forceSimulationActive
= start
;
244 this.node
.each(function (d
) {
245 d
.fx
= (start
) ? null : d
.x
;
246 d
.fy
= (start
) ? null : d
.y
;
250 this.force
.restart();
252 this.eventHandler
.fire("force_status_changed_on", start
);
256 * Handle the parameters of basic filters: node type, view, group
257 * @param {Object} Required.
260 GraphEditor
.prototype.handleFiltersParams = function (filtersParams
, notFireEvent
) {
261 this.filter_parameters
= (filtersParams
!== undefined) ? filtersParams
: this.filter_parameters
;
262 this.current_view_id
= (this.filter_parameters
!== undefined && this.filter_parameters
.link
.view
[0] !== undefined) ? this.filter_parameters
.link
.view
[0] : this.current_view_id
266 this.force
.restart();
267 this._deselectAllNodes();
268 this.handleForce(this.forceSimulationActive
);
270 this.eventHandler
.fire("filters_changed", filtersParams
);
275 * Add a new node to the graph.
276 * @param {Object} Required. An object that specifies tha data of the new node.
279 GraphEditor
.prototype.addNode = function (args
) {
280 if (args
.id
&& args
.info
&& args
.info
.type
) {
284 this.d3_graph
.nodes
.push(args
);
287 this.force
.restart();
288 this.handleForce(this.forceSimulationActive
);
297 * Update the data properties of the node
298 * @param {Object} Required. An object that specifies tha data of the node.
301 GraphEditor
.prototype.updateDataNode = function (args
) {
306 * Remove a node from graph and related links.
307 * @param {String} Required. Id of node to remove.
310 GraphEditor
.prototype.removeNode = function (node
) {
311 if (node
!= undefined) {
312 var node_id
= node
.id
;
313 this.d3_graph
['nodes'].forEach(function (n
, index
, object
) {
314 if (n
.id
=== node_id
) {
315 object
.splice(index
, 1);
320 var links_to_remove
= [];
321 this.d3_graph
['links'].forEach(function (l
, index
, object
) {
322 if (node_id
=== l
.source
.id
|| node_id
=== l
.target
.id
) {
323 links_to_remove
.push(index
);
326 var links_removed
= 0;
327 links_to_remove
.forEach(function (l_index
) {
328 self
.d3_graph
['links'].splice(l_index
- links_removed
, 1);
334 this.force
.restart();
343 * Add a new link to graph.
344 * @param {Object} Required. An object that specifies tha data of the new Link.
347 GraphEditor
.prototype.addLink = function (link
) {
348 console
.log("addLink" + JSON
.stringify(link
));
349 if (link
.source
&& link
.target
) {
352 this.d3_graph
.links
.push(link
);
355 this.force
.restart();
363 * Remove a link from graph.
364 * @param {String} Required. The identifier of link to remove.
367 GraphEditor
.prototype.removeLink = function (link_id
) {
369 if (link_id
!== 'undefined') {
370 this.d3_graph
['links'].forEach(function (l
, index
, object
) {
371 if (link_id
=== l
.index
) {
372 object
.splice(index
, 1);
377 self
.force
.restart();
389 * Force a refresh of GraphView
392 GraphEditor
.prototype.refresh = function () {
399 .data(self
.d3_graph
.links
400 .filter(this.link_filter_cb
)
403 .attr("class", "link cleanable")
405 .attr("class", "link")
406 .attr("class", "cleanable")
407 .style("stroke-width", nominal_stroke
)
408 .style("stroke", function (d
) {
409 return self
._link_property_by_type((d
.type_link
) ? d
.type_link
: "unrecognized", "color");
411 .attr("marker-end", function (d
) {
412 if (!d
.directed_edge
)
415 var marker_url
= (d
.type_link
) ? d
.type_link
: "unrecognized"
416 return (d
.directed_edge
? "url(#" + marker_url
+ ")" : '');
419 this.nodeContainer
= this.svg
421 .data(self
.d3_graph
.nodes
422 .filter(this.node_filter_cb
))
425 // .attr("class", "nodosdads")
426 .attr("class", "node cleanable");
428 this.svg
.selectAll('.node')
429 .data(self
.d3_graph
.nodes
430 .filter(this.node_filter_cb
))
432 .filter(function (d
) {
433 return (d
.info
.type
=== undefined) || (self
._node_property_by_type(d
.info
.type
, 'image', d
) === undefined)
437 .attr("d", d3
.symbol()
439 return Math
.PI
* Math
.pow(self
._node_property_by_type(d
.info
.type
, 'size', d
), 2) / 4;
442 // console.log(d.info.type, 'shape', self.current_view_id)
443 return (self
._node_property_by_type(d
.info
.type
, 'shape', d
));
446 .style("fill", function (d
) {
447 return self
._node_property_by_type(d
.info
.type
, 'color', d
);
449 .attr("transform", function () {
450 return "rotate(-45)";
453 .attr("stroke-width", 2.4)
455 .attr("class", "node_path")
456 .attr("id", function (d
) {
457 return "path_" + d
.id
;
461 .on("start", dragstarted
)
463 .on("end", dragended
));
465 var figure_node
= this.svg
.selectAll('.node')
466 .data(self
.d3_graph
.nodes
467 .filter(this.node_filter_cb
))
469 .filter(function (d
) {
470 return self
._node_property_by_type(d
.info
.type
, 'image', d
) != undefined
473 figure_node
.append("svg:image")
474 .attr("xlink:href", function (d
) {
475 return self
._node_property_by_type(d
.info
.type
, 'image', d
)
477 .attr("x", function (d
) {
478 return -self
._node_property_by_type(d
.info
.type
, 'size', d
) / 2
480 .attr("y", function (d
) {
481 return -self
._node_property_by_type(d
.info
.type
, 'size', d
) / 2
483 .attr("width", function (d
) {
484 return self
._node_property_by_type(d
.info
.type
, 'size', d
)
486 .attr("height", function (d
) {
487 return self
._node_property_by_type(d
.info
.type
, 'size', d
)
489 .style("stroke", "black")
490 .style("stroke-width", "1px")
492 .attr("class", "node_path")
493 .attr("id", function (d
) {
494 return "path_" + d
.id
;
497 .on("start", dragstarted
)
499 .on("end", dragended
));
501 figure_node
.append("svg:path")
502 .attr("d", d3
.symbol()
504 return Math
.PI
* Math
.pow(self
._node_property_by_type(d
.info
.type
, 'size', d
) + 7, 2) / 4;
507 return (self
.get_d3_symbol('circle'));
510 .style("fill", 'transparent')
511 .attr("transform", function () {
512 return "rotate(-45)";
515 .attr("stroke-width", 2.4)
517 .attr("class", "hidden_circle")
518 .attr("id", function (d
) {
519 return "path_" + d
.id
;
523 .on("start", dragstarted
)
525 .on("end", dragended
));
528 this.node
= this.svg
.selectAll('.node')
529 .data(self
.d3_graph
.nodes
530 .filter(this.node_filter_cb
)).selectAll("image, path, circle");
533 this.node
.on("contextmenu", self
.behavioursOnEvents
.nodes
["contextmenu"])
534 .on("mouseover", self
.behavioursOnEvents
.nodes
["mouseover"])
535 .on("mouseout", self
.behavioursOnEvents
.nodes
["mouseout"])
536 .on('click', self
.behavioursOnEvents
.nodes
["click"])
537 .on('dblclick', self
.behavioursOnEvents
.nodes
["dblclick"]);
540 .on("contextmenu", self
.behavioursOnEvents
.links
["contextmenu"])
541 .on("mouseover", self
.behavioursOnEvents
.links
["mouseover"])
542 .on('click', self
.behavioursOnEvents
.links
["click"])
543 .on("mouseout", self
.behavioursOnEvents
.links
["mouseout"]);
546 this.text
= this.svg
.selectAll(".node")
547 .data(self
.d3_graph
.nodes
548 .filter(this.node_filter_cb
))
550 .attr("class", "node_text cleanable")
551 .attr("dy", function (d
) {
554 .attr("pointer-events", "none")
555 .style("font-size", nominal_text_size
+ "px")
556 .style("font-family", "'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif")
557 .style("fill", function (d
) {
558 return self
._node_property_by_type(d
.info
.type
, 'node_label_color', d
);
560 //.style("text-anchor", "middle")
562 if(d
.info
&& d
.info
.property
.custom_label
&& d
.info
.property
.custom_label
!==''){
563 return d
.info
.property
.custom_label
569 function dragstarted(d
) {
570 d
.draggednode
= true;
571 if (!d3
.event
.active
) self
.force
.alphaTarget(0.3).restart();
577 function dragged(d
) {
582 function dragended(d
) {
583 d
.draggednode
= false;
584 if (!d3
.event
.active
) self
.force
.alphaTarget(0);
585 if (self
.forceSimulationActive
) {
592 self
.forceSimulationActive
= false;
600 * Start force layout on Graph.
603 GraphEditor
.prototype.startForce = function () {
607 .nodes(this.d3_graph
.nodes
)
613 .links(this.d3_graph
.links
);
616 self
.node
.attr("cx", function (d
) {
617 return d
.x
= Math
.max(self
._node_property_by_type(d
.info
.type
, 'size', d
), Math
.min(self
.width
- self
._node_property_by_type(d
.info
.type
, 'size', d
), d
.x
));
619 .attr("cy", function (d
) {
620 return d
.y
= Math
.max(self
._node_property_by_type(d
.info
.type
, 'size', d
), Math
.min(self
.height
- self
._node_property_by_type(d
.info
.type
, 'size', d
), d
.y
));
623 self
.link
.attr("d", function (d
) {
624 var dx
= d
.target
.x
- d
.source
.x
,
625 dy
= d
.target
.y
- d
.source
.y
,
626 dr
= Math
.sqrt(dx
* dx
+ dy
* dy
);
627 return "M" + d
.source
.x
+ "," + d
.source
.y
+ "," + d
.target
.x
+ "," + d
.target
.y
;
630 self
.node
.attr("transform", function (d
) {
631 return "translate(" + d
.x
+ "," + d
.y
+ ")";
633 self
.text
.attr("transform", function (d
) {
634 var label_pos_y
= d
.y
+ self
._node_property_by_type(d
.info
.type
, 'size', d
)/2 +nominal_text_size
;
635 return "translate(" + d
.x
+ "," + label_pos_y
+ ")";
643 * This method attaches an event handler.
644 * @param {String} Required. A String that specifies the name of the event.
645 * @param {Function} Required. Specifies the function to run when the event occurs.
648 GraphEditor
.prototype.addListener = function (event_name
, cb
) {
649 this.eventHandler
.addL(event_name
, cb
);
653 * This method removes an event handler that has been attached with the addListener() method.
654 * @param {String} Required. A String that specifies the name of the event to remove.
655 * @param {Function} Required. Specifies the function to remove.
658 GraphEditor
.prototype.removeListener = function (event_name
, cb
) {
663 GraphEditor
.prototype.setNodeClass = function (class_name
, filter_cb
) {
666 this.svg
.selectAll('.node').classed(class_name
, false);
667 this.svg
.selectAll('.node')
668 .classed(class_name
, filter_cb
);
671 GraphEditor
.prototype.setLinkClass = function (class_name
, filter_cb
) {
674 this.svg
.selectAll('.link').classed(class_name
, false);
675 this.svg
.selectAll('.link')
676 .classed(class_name
, filter_cb
);
679 GraphEditor
.prototype.showNodeInfo = function (args
) {
680 this.addLinesToPopup(args
['node_info'], "Info about node selected")
681 this.handlePopupVisibility(true, 'right')
684 GraphEditor
.prototype.addLinesToPopup = function (data
, title
) {
688 var width_popup
= 400;
689 var height_popup
= 0;
691 d3
.selectAll(".popupcleanable").remove(); // clean
693 var popupbg
= d3
.select(".popup").append("rect")
694 .attr("id", "popupbg")
695 .attr("class", "popup bg popupcleanable cleanable")
696 .attr("width", "400")
698 .attr("rx", 10) // set the x corner curve radius
699 .attr("ry", 10); // set the y corner curve radius
702 d3
.select(".popup").append("svg:path")
703 .attr("d", d3
.symbol()
709 return (self
.get_d3_symbol());
712 .style("fill", 'red')
713 .attr("transform", function () {
714 return "translate(380,15) rotate(-45)";
717 .attr("stroke-width", 2.4)
718 .attr("id", "close_popup")
719 .attr("class", "popupcleanable cleanable")
720 .on("click", function (d
) {
721 self
.handlePopupVisibility(false);
724 d3
.select(".popup").append("text")
725 .attr("class", "popup title popupcleanable cleanable")
730 for (var i
in data
) {
731 //console.log(i, data, data[i])
732 //var typeofvalue = typeof data[i];
733 var record
= data
[i
];
734 index
= this._addRecordToPopup(i
, record
, index
)
740 GraphEditor
.prototype._addRecordToPopup = function (key
, record
, index
, tab
) {
741 //console.log("_addRecordToPopup", key, record, index)
742 var translate_y
= 23 * index
;
743 var summary
= d3
.select(".popup").append("g")
744 .attr("class", "popup summary d popupcleanable cleanable")
745 .attr("transform", "translate(10 " + translate_y
+ ")");
746 if (Object
.prototype.toString
.call(record
) !== '[object Array]') { //is a record simple key:value
747 //console.log(key, record)
748 var summary_g
= summary
.append("g");
749 summary_g
.append("rect")
750 .attr("class", "popup summary bg popupcleanable cleanable")
751 .attr("width", "380")
752 .attr("height", "20");
754 summary_g
.append("text")
755 .attr("class", "popup summary popupcleanable cleanable")
756 .attr("x", (tab
) ? tab
: 10)
758 .attr("width", "100")
760 return key
.toUpperCase() + ":";
763 summary_g
.append("text")
764 .attr("class", "popup summary popupcleanable cleanable")
767 .attr("text-anchor", "end")
772 else {//is a record simple complex: have a list of sub record key:value
774 this._addRecordToPopup(key
, "", index
)
775 for (var r
in record
) {
776 //console.log(i, r, record, record[r])
777 for (var k
in record
[r
]) {
778 //console.log(i, r, k, record[r][k])
780 var recordValue
= record
[r
][k
]
783 this._addRecordToPopup(curr_key
, recordValue
, index
, 20)
789 translate_y
= 30 * index
++;
790 d3
.select('#popupbg').attr("height", translate_y
);
796 * Remove all the graph objects from the view
798 GraphEditor
.prototype.cleanAll = function () {
799 this.svg
.selectAll('.cleanable').remove();
806 GraphEditor
.prototype._node_property_by_type = function (type
, property
, node
) {
807 //console.log(type, property, layer, group)
808 var unrecognized = function (ui_prop
, property
) {
809 return ui_prop
['unrecognized'][property
]
813 if (this.type_property
[type
]) {
815 if (this.type_property
[type
]['property']) {
816 var filt_property
= this.type_property
[type
]['property']
817 return this.type_property
[type
][node
.info
[filt_property
]][property
]
818 } else { // type without property spec
820 return this.type_property
[type
][property
]
824 } else { //type unrecognized
825 return unrecognized(this.type_property
, property
)
830 GraphEditor
.prototype._link_property_by_type = function (type
, property
) {
831 //log(type + "-" + property)
832 if (this.type_property_link
[type
] != undefined && this.type_property_link
[type
][property
] != undefined) {
833 //if(property == "shape")
834 // log("dentro" + this.type_property[type][property])
835 return this.type_property_link
[type
][property
];
837 return this.type_property_link
['unrecognized'][property
];
848 GraphEditor
.prototype._setupFiltersBehaviors = function (args
) {
852 this.node_filter_cb
= args
.node_filter_cb
|| function (d
) {
854 var cond_view
= true,
856 //log(d.info.type + " " + self.filter_parameters.node.type + " group: " + self.filter_parameters.node.group + "- " + d.info.group)
857 // check filter by node type
858 if (self
.filter_parameters
.node
.type
.length
> 0) {
860 if (self
.filter_parameters
.node
.type
.indexOf(d
.info
.type
) < 0)
864 // check filter by group
865 if (self
.filter_parameters
.node
.group
.length
> 0) {
866 self
.filter_parameters
.node
.group
.forEach(function (group
) {
867 if (d
.info
.group
.indexOf(group
) < 0)
875 return cond_view
&& cond_group
;
878 this.link_filter_cb
= args
.link_filter_cb
|| function (d
) {
879 var cond_view
= true,
882 // check filter by view
883 if (self
.filter_parameters
.link
.view
.length
> 0) {
884 self
.filter_parameters
.link
.view
.forEach(function (view
) {
885 if (d
.view
.indexOf(view
) < 0)
890 // check filter by group
891 if (self
.filter_parameters
.link
.group
.length
> 0) {
892 self
.filter_parameters
.link
.group
.forEach(function (group
) {
893 if (d
.group
.indexOf(group
) < 0)
897 return cond_view
&& cond_group
;
906 GraphEditor
.prototype._setupBehaviorsOnEvents = function () {
907 log("_setupBehaviorsOnEvents");
909 this.behavioursOnEvents
= {
911 'click': function (d
) {
912 d3
.event
.preventDefault();
914 if (self
.lastKeyDown
=== SHIFT_BUTTON
&& self
._selected_node
!== undefined) {
915 var source_id
= self
._selected_node
.id
;
916 var target_id
= d
.id
;
917 log("--" + JSON
.stringify(self
.filter_parameters
.link
.view
));
921 view
: self
.filter_parameters
.link
.view
[0],
922 group
: self
.filter_parameters
.link
.group
[0],
924 self
.addLink(new_link
);
925 self
._deselectAllNodes();
927 self
._selectNodeExclusive(this, d
);
931 'mouseover': function (d
) {
934 'mouseout': function (d
) {
936 'dblclick': function (d
) {
937 d3
.event
.preventDefault();
940 'contextmenu': function (d
, i
) {
941 d3
.event
.preventDefault();
942 log("contextmenu node");
943 self
.eventHandler
.fire("right_click_node", d
);
947 'click': function (event
) {
950 'dblclick': function (event
) {
958 * Deselect previously selected nodes
961 GraphEditor
.prototype._deselectAllNodes = function () {
962 log("_deselectAllNodes");
963 this.node
.classed("node_selected", false);
964 this._selected_node
= undefined;
967 GraphEditor
.prototype._deselectAllLinks = function () {
968 log("_deselectAllLinks");
969 this.link
.classed("link_selected", false).style('stroke-width', 2);
970 this._selected_link
= undefined;
973 * Select node in exclusive mode
974 * @param {Object} Required. Element selected on click event
976 GraphEditor
.prototype._selectNodeExclusive = function (node_instance
, node_id
) {
977 log("_selectNodeExclusive ");
978 var activeClass
= "node_selected";
979 var alreadyIsActive
= d3
.select(node_instance
).classed(activeClass
);
980 this._deselectAllNodes();
981 this._deselectAllLinks();
982 d3
.select(node_instance
).classed(activeClass
, !alreadyIsActive
);
983 this._selected_node
= (alreadyIsActive
) ? undefined : node_instance
.__data__
;
984 if(this._selected_node
){
985 this.eventHandler
.fire("node:selected", this._selected_node
)
987 this.eventHandler
.fire("node:deselected", this._selected_node
)
992 * Select node in exclusive mode
993 * @param {Object} Required. Element selected on click event
995 GraphEditor
.prototype._selectLinkExclusive = function (link_instance
, link_id
) {
996 log("_selectLinkExclusive ");
997 var activeClass
= "link_selected";
998 var alreadyIsActive
= d3
.select(link_instance
).classed(activeClass
);
999 this._deselectAllNodes();
1000 this._deselectAllLinks();
1001 d3
.select(link_instance
).classed(activeClass
, !alreadyIsActive
);
1002 d3
.select(link_instance
).style('stroke-width', 4)
1003 this._selected_link
= link_instance
.__data__
;
1007 * Callback to resize SVG element on window resize
1009 GraphEditor
.prototype.resizeSvg = function (width
, height
) {
1012 this.width
= width
|| this.width
;
1013 this.height
= height
|| this.height
;
1014 this.svg
.attr('width', width
);
1015 this.svg
.attr('height', height
);
1019 GraphEditor
.prototype.handlePopupVisibility = function (visible
, side
) {
1020 var opacity
= (visible
) ? 1 : 0;
1022 var translate_op
= (side
=== "left") ? "translate(50 50)" : "translate(" + (this.width
- 450).toString() + " 50)";
1025 d3
.selectAll(".popupcleanable").remove();
1027 .attr("transform", "translate(-1 -1)");
1030 .attr("transform", translate_op
);
1032 d3
.select(".popup").attr("opacity", opacity
);
1035 GraphEditor
.prototype.refreshGraphParameters = function (graphParameters
) {
1036 this.eventHandler
.fire("refresh_graph_parameters", graphParameters
);
1042 function log(text
) {
1044 console
.log("::GraphEditor::", text
);
1053 if (typeof module
=== 'object') {
1054 module
.exports
= TCD3
.GraphEditor
;