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(40))
96 // .force("link", d3.forceLink().distance(80).iterations(1).id(function (d) {
97 .force("link", d3
.forceLink().distance(function(d
){
98 return d
.short ? 1 : 100;
102 .force("center", d3
.forceCenter(this.width
/ 2, this.height
/ 2));
104 var zoom
= d3
.zoom().scaleExtent([min_zoom
, max_zoom
]);
106 var size
= d3
.scalePow().exponent(2)
110 this.svg
= d3
.select("#graph_editor_container").append("svg")
111 .attr("id", "graph_svg")
112 .attr("preserveAspectRatio", "xMinYMid")
113 .attr("width", '100%')
114 .attr("height", '100%');
117 this.defs
= this.svg
.append("svg:defs");
119 this.defs
.selectAll("marker")
120 .data(["unrecognized"]) // Different link/path types can be defined here
121 .enter().append("svg:marker") // This section adds in the arrows
123 .attr("viewBox", "-5 -5 10 10")
124 .attr("refX", 13) //must be smarter way to calculate shift
126 .attr("markerUnits", "userSpaceOnUse")
127 .attr("markerWidth", 12)
128 .attr("markerHeight", 12)
129 .attr("orient", "auto")
131 .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z")
132 .attr('fill', this.type_property_link
['unrecognized']['color']);
135 .on('keydown', function () {
136 log('keydown ' + d3
.event
.keyCode
);
137 //d3.event.preventDefault();
138 if (self
.lastKeyDown
!== -1) return;
139 self
.lastKeyDown
= d3
.event
.keyCode
;
140 if (self
.lastKeyDown
=== CANC_BUTTON
&& self
._selected_node
!== undefined) {
141 self
.removeNode(self
._selected_node
, null, showAlert
);
142 } else if (self
.lastKeyDown
=== CANC_BUTTON
&& self
._selected_link
!== undefined) {
143 self
.removeLink(self
._selected_link
, null, showAlert
);
147 .on('keyup', function () {
148 log('keyup' + self
.lastKeyDown
);
149 self
.lastKeyDown
= -1;
151 var popup
= this.svg
.append("g")
153 .attr("class", "popup")
154 .attr("opacity", "0")
155 .attr("transform", "translate(1 1)")
157 .on("start", dragstarted
)
159 .on("end", dragended
));
161 function dragstarted(d
) {
162 //d3.select(this).raise().classed("active", true);
165 function dragged(d
) {
166 //console.log(JSON.stringify(d))
167 d3
.select(this).attr("transform", function () {
168 return "translate(" + d3
.event
.x
+ "," + d3
.event
.y
+ ")";
173 function dragended(d
) {
174 //d3.select(this).classed("active", false);
177 var chart
= $("#graph_svg");
178 this.aspect
= chart
.width() / chart
.height();
179 this.container
= $("#graph_editor_container");
180 $(window
).on("resize", function () {
182 self
.width
= self
.container
.width();
183 self
.height
= self
.container
.height();
184 chart
.attr("width", self
.container
.width());
185 chart
.attr("height", self
.container
.height());
191 GraphEditor
.prototype.get_d3_symbol
=
192 function (myString
) {
196 return d3
.symbolCircle
;
198 return d3
.symbolSquare
;
200 return d3
.symbolDiamond
;
202 return d3
.symbolTriangle
;
204 return d3
.symbolStar
;
206 return d3
.symbolCross
;
208 // if the string is not recognized
209 return d3
.symbolCross
;
214 GraphEditor
.prototype.get_name_from_d3_symbol
=
215 function (mySymbol
) {
217 case d3
.symbolCircle
:
219 case d3
.symbolSquare
:
221 case d3
.symbolDiamond
:
223 case d3
.symbolTriangle
:
230 // if the string is not recognized
232 //return d3.symbolCircleUnknown;
238 * Start or Stop force layout
239 * @param {boolean} Required. Value true: start, false: stop
242 GraphEditor
.prototype.handleForce = function (start
) {
245 this.forceSimulationActive
= start
;
246 this.node
.each(function (d
) {
247 d
.fx
= (start
) ? null : d
.x
;
248 d
.fy
= (start
) ? null : d
.y
;
252 this.force
.restart();
254 this.eventHandler
.fire("force_status_changed_on", start
);
258 * Handle the parameters of basic filters: node type, view, group
259 * @param {Object} Required.
262 GraphEditor
.prototype.handleFiltersParams = function (filtersParams
, notFireEvent
) {
263 this.filter_parameters
= (filtersParams
!== undefined) ? filtersParams
: this.filter_parameters
;
264 this.current_view_id
= (this.filter_parameters
!== undefined && this.filter_parameters
.link
.view
[0] !== undefined) ? this.filter_parameters
.link
.view
[0] : this.current_view_id
268 this.force
.restart();
269 this._deselectAllNodes();
270 this.handleForce(this.forceSimulationActive
);
272 this.eventHandler
.fire("filters_changed", filtersParams
);
277 * Add a new node to the graph.
278 * @param {Object} Required. An object that specifies tha data of the new node.
281 GraphEditor
.prototype.addNode = function (args
) {
282 if (args
.id
&& args
.info
&& args
.info
.type
) {
286 this.d3_graph
.nodes
.push(args
);
289 this.force
.restart();
290 this.handleForce(this.forceSimulationActive
);
299 * Update the data properties of the node
300 * @param {Object} Required. An object that specifies tha data of the node.
303 GraphEditor
.prototype.updateDataNode = function (args
) {
308 * Remove a node from graph and related links.
309 * @param {String} Required. Id of node to remove.
312 GraphEditor
.prototype.removeNode = function (node
) {
313 if (node
!= undefined) {
314 var node_id
= node
.id
;
315 this.d3_graph
['nodes'].forEach(function (n
, index
, object
) {
316 if (n
.id
=== node_id
) {
317 object
.splice(index
, 1);
322 var links_to_remove
= [];
323 this.d3_graph
['links'].forEach(function (l
, index
, object
) {
324 if (node_id
=== l
.source
.id
|| node_id
=== l
.target
.id
) {
325 links_to_remove
.push(index
);
328 var links_removed
= 0;
329 links_to_remove
.forEach(function (l_index
) {
330 self
.d3_graph
['links'].splice(l_index
- links_removed
, 1);
336 this.force
.restart();
345 * Add a new link to graph.
346 * @param {Object} Required. An object that specifies tha data of the new Link.
349 GraphEditor
.prototype.addLink = function (link
) {
350 console
.log("addLink" + JSON
.stringify(link
));
351 if (link
.source
&& link
.target
) {
354 this.d3_graph
.links
.push(link
);
357 this.force
.restart();
365 * Remove a link from graph.
366 * @param {String} Required. The identifier of link to remove.
369 GraphEditor
.prototype.removeLink = function (link_id
) {
371 if (link_id
!== 'undefined') {
372 this.d3_graph
['links'].forEach(function (l
, index
, object
) {
373 if (link_id
=== l
.index
) {
374 object
.splice(index
, 1);
379 self
.force
.restart();
391 * Force a refresh of GraphView
394 GraphEditor
.prototype.refresh = function () {
401 .data(self
.d3_graph
.links
402 .filter(this.link_filter_cb
)
405 .attr("class", "link cleanable")
407 .attr("class", "link")
408 .attr("class", "cleanable")
409 .style("stroke-width", nominal_stroke
)
410 .style("stroke", function (d
) {
411 return self
._link_property_by_type((d
.type_link
) ? d
.type_link
: "unrecognized", "color");
413 .attr("marker-end", function (d
) {
414 if (!d
.directed_edge
)
417 var marker_url
= (d
.type_link
) ? d
.type_link
: "unrecognized"
418 return (d
.directed_edge
? "url(#" + marker_url
+ ")" : '');
421 this.nodeContainer
= this.svg
423 .data(self
.d3_graph
.nodes
424 .filter(this.node_filter_cb
))
427 // .attr("class", "nodosdads")
428 .attr("class", "node cleanable");
430 this.svg
.selectAll('.node')
431 .data(self
.d3_graph
.nodes
432 .filter(this.node_filter_cb
))
434 .filter(function (d
) {
435 return (d
.info
.type
=== undefined) || (self
._node_property_by_type(d
.info
.type
, 'image', d
) === undefined)
439 .attr("d", d3
.symbol()
441 return Math
.PI
* Math
.pow(self
._node_property_by_type(d
.info
.type
, 'size', d
), 2) / 4;
444 // console.log(d.info.type, 'shape', self.current_view_id)
445 return (self
._node_property_by_type(d
.info
.type
, 'shape', d
));
448 .style("fill", function (d
) {
449 return self
._node_property_by_type(d
.info
.type
, 'color', d
);
451 .attr("transform", function () {
452 return "rotate(-45)";
455 .attr("stroke-width", 2.4)
457 .attr("class", "node_path")
458 .attr("id", function (d
) {
459 return "path_" + d
.id
;
463 .on("start", dragstarted
)
465 .on("end", dragended
));
467 var figure_node
= this.svg
.selectAll('.node')
468 .data(self
.d3_graph
.nodes
469 .filter(this.node_filter_cb
))
471 .filter(function (d
) {
472 return self
._node_property_by_type(d
.info
.type
, 'image', d
) != undefined
475 figure_node
.append("svg:image")
476 .attr("xlink:href", function (d
) {
477 return self
._node_property_by_type(d
.info
.type
, 'image', d
)
479 .attr("x", function (d
) {
480 return -self
._node_property_by_type(d
.info
.type
, 'size', d
) / 2
482 .attr("y", function (d
) {
483 return -self
._node_property_by_type(d
.info
.type
, 'size', d
) / 2
485 .attr("width", function (d
) {
486 return self
._node_property_by_type(d
.info
.type
, 'size', d
)
488 .attr("height", function (d
) {
489 return self
._node_property_by_type(d
.info
.type
, 'size', d
)
491 .style("stroke", "black")
492 .style("stroke-width", "1px")
494 .attr("class", "node_path")
495 .attr("id", function (d
) {
496 return "path_" + d
.id
;
499 .on("start", dragstarted
)
501 .on("end", dragended
));
503 figure_node
.append("svg:path")
504 .attr("d", d3
.symbol()
506 return Math
.PI
* Math
.pow(self
._node_property_by_type(d
.info
.type
, 'size', d
) + 7, 2) / 4;
509 return (self
.get_d3_symbol('circle'));
512 .style("fill", 'transparent')
513 .attr("transform", function () {
514 return "rotate(-45)";
517 .attr("stroke-width", 2.4)
519 .attr("class", "hidden_circle")
520 .attr("id", function (d
) {
521 return "path_" + d
.id
;
525 .on("start", dragstarted
)
527 .on("end", dragended
));
530 this.node
= this.svg
.selectAll('.node')
531 .data(self
.d3_graph
.nodes
532 .filter(this.node_filter_cb
)).selectAll("image, path, circle");
535 this.node
.on("contextmenu", self
.behavioursOnEvents
.nodes
["contextmenu"])
536 .on("mouseover", self
.behavioursOnEvents
.nodes
["mouseover"])
537 .on("mouseout", self
.behavioursOnEvents
.nodes
["mouseout"])
538 .on('click', self
.behavioursOnEvents
.nodes
["click"])
539 .on('dblclick', self
.behavioursOnEvents
.nodes
["dblclick"]);
542 .on("contextmenu", self
.behavioursOnEvents
.links
["contextmenu"])
543 .on("mouseover", self
.behavioursOnEvents
.links
["mouseover"])
544 .on('click', self
.behavioursOnEvents
.links
["click"])
545 .on("mouseout", self
.behavioursOnEvents
.links
["mouseout"]);
548 this.text
= this.svg
.selectAll(".node")
549 .data(self
.d3_graph
.nodes
550 .filter(this.node_filter_cb
))
552 .attr("class", "node_text cleanable")
553 .attr("dy", function (d
) {
556 .attr("pointer-events", "none")
557 .style("font-size", nominal_text_size
+ "px")
558 .style("font-family", "'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif")
559 .style("fill", function (d
) {
560 return self
._node_property_by_type(d
.info
.type
, 'node_label_color', d
);
562 //.style("text-anchor", "middle")
564 if(d
.info
&& d
.info
.property
.custom_label
&& d
.info
.property
.custom_label
!==''){
565 return d
.info
.property
.custom_label
571 function dragstarted(d
) {
572 d
.draggednode
= true;
573 if (!d3
.event
.active
) self
.force
.alphaTarget(0.3).restart();
579 function dragged(d
) {
584 function dragended(d
) {
585 d
.draggednode
= false;
586 if (!d3
.event
.active
) self
.force
.alphaTarget(0);
587 if (self
.forceSimulationActive
) {
594 self
.forceSimulationActive
= false;
602 * Start force layout on Graph.
605 GraphEditor
.prototype.startForce = function () {
609 .nodes(this.d3_graph
.nodes
)
615 .links(this.d3_graph
.links
);
618 self
.node
.attr("cx", function (d
) {
619 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
));
621 .attr("cy", function (d
) {
622 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
));
625 self
.link
.attr("d", function (d
) {
626 var dx
= d
.target
.x
- d
.source
.x
,
627 dy
= d
.target
.y
- d
.source
.y
,
628 dr
= Math
.sqrt(dx
* dx
+ dy
* dy
);
629 return "M" + d
.source
.x
+ "," + d
.source
.y
+ "," + d
.target
.x
+ "," + d
.target
.y
;
632 self
.node
.attr("transform", function (d
) {
633 return "translate(" + d
.x
+ "," + d
.y
+ ")";
635 self
.text
.attr("transform", function (d
) {
636 var label_pos_y
= d
.y
+ self
._node_property_by_type(d
.info
.type
, 'size', d
)/2 +nominal_text_size
;
637 return "translate(" + d
.x
+ "," + label_pos_y
+ ")";
645 * This method attaches an event handler.
646 * @param {String} Required. A String that specifies the name of the event.
647 * @param {Function} Required. Specifies the function to run when the event occurs.
650 GraphEditor
.prototype.addListener = function (event_name
, cb
) {
651 this.eventHandler
.addL(event_name
, cb
);
655 * This method removes an event handler that has been attached with the addListener() method.
656 * @param {String} Required. A String that specifies the name of the event to remove.
657 * @param {Function} Required. Specifies the function to remove.
660 GraphEditor
.prototype.removeListener = function (event_name
, cb
) {
665 GraphEditor
.prototype.setNodeClass = function (class_name
, filter_cb
) {
668 this.svg
.selectAll('.node').classed(class_name
, false);
669 this.svg
.selectAll('.node')
670 .classed(class_name
, filter_cb
);
673 GraphEditor
.prototype.setLinkClass = function (class_name
, filter_cb
) {
676 this.svg
.selectAll('.link').classed(class_name
, false);
677 this.svg
.selectAll('.link')
678 .classed(class_name
, filter_cb
);
681 GraphEditor
.prototype.showNodeInfo = function (args
) {
682 this.addLinesToPopup(args
['node_info'], "Info about node selected")
683 this.handlePopupVisibility(true, 'right')
686 GraphEditor
.prototype.addLinesToPopup = function (data
, title
) {
690 var width_popup
= 400;
691 var height_popup
= 0;
693 d3
.selectAll(".popupcleanable").remove(); // clean
695 var popupbg
= d3
.select(".popup").append("rect")
696 .attr("id", "popupbg")
697 .attr("class", "popup bg popupcleanable cleanable")
698 .attr("width", "400")
700 .attr("rx", 10) // set the x corner curve radius
701 .attr("ry", 10); // set the y corner curve radius
704 d3
.select(".popup").append("svg:path")
705 .attr("d", d3
.symbol()
711 return (self
.get_d3_symbol());
714 .style("fill", 'red')
715 .attr("transform", function () {
716 return "translate(380,15) rotate(-45)";
719 .attr("stroke-width", 2.4)
720 .attr("id", "close_popup")
721 .attr("class", "popupcleanable cleanable")
722 .on("click", function (d
) {
723 self
.handlePopupVisibility(false);
726 d3
.select(".popup").append("text")
727 .attr("class", "popup title popupcleanable cleanable")
732 for (var i
in data
) {
733 //console.log(i, data, data[i])
734 //var typeofvalue = typeof data[i];
735 var record
= data
[i
];
736 index
= this._addRecordToPopup(i
, record
, index
)
742 GraphEditor
.prototype._addRecordToPopup = function (key
, record
, index
, tab
) {
743 //console.log("_addRecordToPopup", key, record, index)
744 var translate_y
= 23 * index
;
745 var summary
= d3
.select(".popup").append("g")
746 .attr("class", "popup summary d popupcleanable cleanable")
747 .attr("transform", "translate(10 " + translate_y
+ ")");
748 if (Object
.prototype.toString
.call(record
) !== '[object Array]') { //is a record simple key:value
749 //console.log(key, record)
750 var summary_g
= summary
.append("g");
751 summary_g
.append("rect")
752 .attr("class", "popup summary bg popupcleanable cleanable")
753 .attr("width", "380")
754 .attr("height", "20");
756 summary_g
.append("text")
757 .attr("class", "popup summary popupcleanable cleanable")
758 .attr("x", (tab
) ? tab
: 10)
760 .attr("width", "100")
762 return key
.toUpperCase() + ":";
765 summary_g
.append("text")
766 .attr("class", "popup summary popupcleanable cleanable")
769 .attr("text-anchor", "end")
774 else {//is a record simple complex: have a list of sub record key:value
776 this._addRecordToPopup(key
, "", index
)
777 for (var r
in record
) {
778 //console.log(i, r, record, record[r])
779 for (var k
in record
[r
]) {
780 //console.log(i, r, k, record[r][k])
782 var recordValue
= record
[r
][k
]
785 this._addRecordToPopup(curr_key
, recordValue
, index
, 20)
791 translate_y
= 30 * index
++;
792 d3
.select('#popupbg').attr("height", translate_y
);
798 * Remove all the graph objects from the view
800 GraphEditor
.prototype.cleanAll = function () {
801 this.svg
.selectAll('.cleanable').remove();
808 GraphEditor
.prototype._node_property_by_type = function (type
, property
, node
) {
809 //console.log(type, property, layer, group)
810 var unrecognized = function (ui_prop
, property
) {
811 return ui_prop
['unrecognized'][property
]
815 if (this.type_property
[type
]) {
817 if (this.type_property
[type
]['property']) {
818 var filt_property
= this.type_property
[type
]['property']
819 return this.type_property
[type
][node
.info
[filt_property
]][property
]
820 } else { // type without property spec
822 return this.type_property
[type
][property
]
826 } else { //type unrecognized
827 return unrecognized(this.type_property
, property
)
832 GraphEditor
.prototype._link_property_by_type = function (type
, property
) {
833 //log(type + "-" + property)
834 if (this.type_property_link
[type
] != undefined && this.type_property_link
[type
][property
] != undefined) {
835 //if(property == "shape")
836 // log("dentro" + this.type_property[type][property])
837 return this.type_property_link
[type
][property
];
839 return this.type_property_link
['unrecognized'][property
];
850 GraphEditor
.prototype._setupFiltersBehaviors = function (args
) {
854 this.node_filter_cb
= args
.node_filter_cb
|| function (d
) {
856 var cond_view
= true,
858 //log(d.info.type + " " + self.filter_parameters.node.type + " group: " + self.filter_parameters.node.group + "- " + d.info.group)
859 // check filter by node type
860 if (self
.filter_parameters
.node
.type
.length
> 0) {
862 if (self
.filter_parameters
.node
.type
.indexOf(d
.info
.type
) < 0)
866 // check filter by group
867 if (self
.filter_parameters
.node
.group
.length
> 0) {
868 self
.filter_parameters
.node
.group
.forEach(function (group
) {
869 if (d
.info
.group
.indexOf(group
) < 0)
877 return cond_view
&& cond_group
;
880 this.link_filter_cb
= args
.link_filter_cb
|| function (d
) {
881 var cond_view
= true,
884 // check filter by view
885 if (self
.filter_parameters
.link
.view
.length
> 0) {
886 self
.filter_parameters
.link
.view
.forEach(function (view
) {
887 if (d
.view
.indexOf(view
) < 0)
892 // check filter by group
893 if (self
.filter_parameters
.link
.group
.length
> 0) {
894 self
.filter_parameters
.link
.group
.forEach(function (group
) {
895 if (d
.group
.indexOf(group
) < 0)
899 return cond_view
&& cond_group
;
908 GraphEditor
.prototype._setupBehaviorsOnEvents = function () {
909 log("_setupBehaviorsOnEvents");
911 this.behavioursOnEvents
= {
913 'click': function (d
) {
914 d3
.event
.preventDefault();
916 if (self
.lastKeyDown
=== SHIFT_BUTTON
&& self
._selected_node
!== undefined) {
917 var source_id
= self
._selected_node
.id
;
918 var target_id
= d
.id
;
919 log("--" + JSON
.stringify(self
.filter_parameters
.link
.view
));
923 view
: self
.filter_parameters
.link
.view
[0],
924 group
: self
.filter_parameters
.link
.group
[0],
926 self
.addLink(new_link
);
927 self
._deselectAllNodes();
929 self
._selectNodeExclusive(this, d
);
933 'mouseover': function (d
) {
936 'mouseout': function (d
) {
938 'dblclick': function (d
) {
939 d3
.event
.preventDefault();
942 'contextmenu': function (d
, i
) {
943 d3
.event
.preventDefault();
944 log("contextmenu node");
945 self
.eventHandler
.fire("right_click_node", d
);
949 'click': function (event
) {
952 'dblclick': function (event
) {
960 * Deselect previously selected nodes
963 GraphEditor
.prototype._deselectAllNodes = function () {
964 log("_deselectAllNodes");
965 this.node
.classed("node_selected", false);
966 this._selected_node
= undefined;
969 GraphEditor
.prototype._deselectAllLinks = function () {
970 log("_deselectAllLinks");
971 this.link
.classed("link_selected", false).style('stroke-width', 2);
972 this._selected_link
= undefined;
975 * Select node in exclusive mode
976 * @param {Object} Required. Element selected on click event
978 GraphEditor
.prototype._selectNodeExclusive = function (node_instance
, node_id
) {
979 log("_selectNodeExclusive ");
980 var activeClass
= "node_selected";
981 var alreadyIsActive
= d3
.select(node_instance
).classed(activeClass
);
982 this._deselectAllNodes();
983 this._deselectAllLinks();
984 d3
.select(node_instance
).classed(activeClass
, !alreadyIsActive
);
985 this._selected_node
= (alreadyIsActive
) ? undefined : node_instance
.__data__
;
986 if(this._selected_node
){
987 this.eventHandler
.fire("node:selected", this._selected_node
)
989 this.eventHandler
.fire("node:deselected", this._selected_node
)
994 * Select node in exclusive mode
995 * @param {Object} Required. Element selected on click event
997 GraphEditor
.prototype._selectLinkExclusive = function (link_instance
, link_id
) {
998 log("_selectLinkExclusive ");
999 var activeClass
= "link_selected";
1000 var alreadyIsActive
= d3
.select(link_instance
).classed(activeClass
);
1001 this._deselectAllNodes();
1002 this._deselectAllLinks();
1003 d3
.select(link_instance
).classed(activeClass
, !alreadyIsActive
);
1004 d3
.select(link_instance
).style('stroke-width', 4)
1005 this._selected_link
= link_instance
.__data__
;
1009 * Callback to resize SVG element on window resize
1011 GraphEditor
.prototype.resizeSvg = function (width
, height
) {
1014 this.width
= width
|| this.width
;
1015 this.height
= height
|| this.height
;
1016 this.svg
.attr('width', width
);
1017 this.svg
.attr('height', height
);
1021 GraphEditor
.prototype.handlePopupVisibility = function (visible
, side
) {
1022 var opacity
= (visible
) ? 1 : 0;
1024 var translate_op
= (side
=== "left") ? "translate(50 50)" : "translate(" + (this.width
- 450).toString() + " 50)";
1027 d3
.selectAll(".popupcleanable").remove();
1029 .attr("transform", "translate(-1 -1)");
1032 .attr("transform", translate_op
);
1034 d3
.select(".popup").attr("opacity", opacity
);
1037 GraphEditor
.prototype.refreshGraphParameters = function (graphParameters
) {
1038 this.eventHandler
.fire("refresh_graph_parameters", graphParameters
);
1044 function log(text
) {
1046 console
.log("::GraphEditor::", text
);
1055 if (typeof module
=== 'object') {
1056 module
.exports
= TCD3
.GraphEditor
;