2d9218b3f3d9b6e6138edf55507530bb026300f2
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
) {
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
)
404 link
.exit().remove();
405 this.link
= link
.enter().append("g")
406 .attr("class", "link cleanable")
408 .attr("class", "link")
409 .attr("class", "cleanable")
410 .style("stroke-width", nominal_stroke
)
411 .style("stroke", function (d
) {
412 return self
._link_property_by_type((d
.type_link
) ? d
.type_link
: "unrecognized", "color");
414 .attr("marker-end", function (d
) {
415 if (!d
.directed_edge
)
418 var marker_url
= (d
.type_link
) ? d
.type_link
: "unrecognized"
419 return (d
.directed_edge
? "url(#" + marker_url
+ ")" : '');
422 var nodeContainer
= this.svg
424 .data(self
.d3_graph
.nodes
425 .filter(this.node_filter_cb
));
426 nodeContainer
.exit().remove();
427 nodeContainer
.enter()
429 // .attr("class", "nodosdads")
430 .attr("class", "node cleanable");
432 var nodes_symbols
= this.svg
.selectAll('.node')
433 .data(self
.d3_graph
.nodes
434 .filter(this.node_filter_cb
))
436 .filter(function (d
) {
437 return (d
.info
.type
=== undefined) || (self
._node_property_by_type(d
.info
.type
, 'image', d
) === undefined)
439 nodes_symbols
.exit().remove();
441 nodes_symbols
.append("svg:path")
442 .attr("d", d3
.symbol()
444 return Math
.PI
* Math
.pow(self
._node_property_by_type(d
.info
.type
, 'size', d
), 2) / 4;
447 // console.log(d.info.type, 'shape', self.current_view_id)
448 return (self
._node_property_by_type(d
.info
.type
, 'shape', d
));
451 .style("fill", function (d
) {
452 return self
._node_property_by_type(d
.info
.type
, 'color', d
);
454 .attr("transform", function () {
455 return "rotate(-45)";
458 .attr("stroke-width", 2.4)
460 .attr("class", "node_path")
461 .attr("id", function (d
) {
462 return "path_" + d
.id
;
466 .on("start", dragstarted
)
468 .on("end", dragended
));
470 var figure_node
= this.svg
.selectAll('.node')
471 .data(self
.d3_graph
.nodes
472 .filter(this.node_filter_cb
))
474 .filter(function (d
) {
475 return self
._node_property_by_type(d
.info
.type
, 'image', d
) != undefined
477 figure_node
.exit().remove();
478 figure_node
.append("svg:image")
479 .attr("xlink:href", function (d
) {
480 return self
._node_property_by_type(d
.info
.type
, 'image', d
)
482 .attr("x", function (d
) {
483 return -self
._node_property_by_type(d
.info
.type
, 'size', d
) / 2
485 .attr("y", function (d
) {
486 return -self
._node_property_by_type(d
.info
.type
, 'size', d
) / 2
488 .attr("width", function (d
) {
489 return self
._node_property_by_type(d
.info
.type
, 'size', d
)
491 .attr("height", function (d
) {
492 return self
._node_property_by_type(d
.info
.type
, 'size', d
)
494 .style("stroke", "black")
495 .style("stroke-width", "1px")
497 .attr("class", "node_path")
498 .attr("id", function (d
) {
499 return "path_" + d
.id
;
502 .on("start", dragstarted
)
504 .on("end", dragended
));
506 figure_node
.append("svg:path")
507 .attr("d", d3
.symbol()
509 return Math
.PI
* Math
.pow(self
._node_property_by_type(d
.info
.type
, 'size', d
) + 7, 2) / 4;
512 return (self
.get_d3_symbol('circle'));
515 .style("fill", 'transparent')
516 .attr("transform", function () {
517 return "rotate(-45)";
520 .attr("stroke-width", 2.4)
522 .attr("class", "hidden_circle")
523 .attr("id", function (d
) {
524 return "path_" + d
.id
;
528 .on("start", dragstarted
)
530 .on("end", dragended
));
533 this.node
= this.svg
.selectAll('.node')
534 .data(self
.d3_graph
.nodes
535 .filter(this.node_filter_cb
)).selectAll("image, path, circle");
538 this.node
.on("contextmenu", self
.behavioursOnEvents
.nodes
["contextmenu"])
539 .on("mouseover", self
.behavioursOnEvents
.nodes
["mouseover"])
540 .on("mouseout", self
.behavioursOnEvents
.nodes
["mouseout"])
541 .on('click', self
.behavioursOnEvents
.nodes
["click"])
542 .on('dblclick', self
.behavioursOnEvents
.nodes
["dblclick"]);
545 .on("contextmenu", self
.behavioursOnEvents
.links
["contextmenu"])
546 .on("mouseover", self
.behavioursOnEvents
.links
["mouseover"])
547 .on('click', self
.behavioursOnEvents
.links
["click"])
548 .on("mouseout", self
.behavioursOnEvents
.links
["mouseout"]);
551 this.text
= this.svg
.selectAll(".node")
552 .data(self
.d3_graph
.nodes
553 .filter(this.node_filter_cb
))
555 .attr("class", "node_text cleanable")
556 .attr("dy", function (d
) {
559 .attr("pointer-events", "none")
560 .style("font-size", nominal_text_size
+ "px")
561 .style("font-family", "'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif")
562 .style("fill", function (d
) {
563 return self
._node_property_by_type(d
.info
.type
, 'node_label_color', d
);
565 //.style("text-anchor", "middle")
567 if(d
.info
&& d
.info
.property
.custom_label
&& d
.info
.property
.custom_label
!==''){
568 return d
.info
.property
.custom_label
574 function dragstarted(d
) {
575 d
.draggednode
= true;
576 if (!d3
.event
.active
) self
.force
.alphaTarget(0.3).restart();
582 function dragged(d
) {
587 function dragended(d
) {
588 d
.draggednode
= false;
589 if (!d3
.event
.active
) self
.force
.alphaTarget(0);
590 if (self
.forceSimulationActive
) {
597 self
.forceSimulationActive
= false;
605 * Start force layout on Graph.
608 GraphEditor
.prototype.startForce = function () {
612 .nodes(this.d3_graph
.nodes
)
618 .links(this.d3_graph
.links
);
621 self
.node
.attr("cx", function (d
) {
622 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
));
624 .attr("cy", function (d
) {
625 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
));
628 self
.link
.attr("d", function (d
) {
629 var dx
= d
.target
.x
- d
.source
.x
,
630 dy
= d
.target
.y
- d
.source
.y
,
631 dr
= Math
.sqrt(dx
* dx
+ dy
* dy
);
632 return "M" + d
.source
.x
+ "," + d
.source
.y
+ "," + d
.target
.x
+ "," + d
.target
.y
;
635 self
.node
.attr("transform", function (d
) {
636 return "translate(" + d
.x
+ "," + d
.y
+ ")";
638 self
.text
.attr("transform", function (d
) {
639 var label_pos_y
= d
.y
+ self
._node_property_by_type(d
.info
.type
, 'size', d
)/2 +nominal_text_size
;
640 return "translate(" + d
.x
+ "," + label_pos_y
+ ")";
648 * This method attaches an event handler.
649 * @param {String} Required. A String that specifies the name of the event.
650 * @param {Function} Required. Specifies the function to run when the event occurs.
653 GraphEditor
.prototype.addListener = function (event_name
, cb
) {
654 this.eventHandler
.addL(event_name
, cb
);
658 * This method removes an event handler that has been attached with the addListener() method.
659 * @param {String} Required. A String that specifies the name of the event to remove.
660 * @param {Function} Required. Specifies the function to remove.
663 GraphEditor
.prototype.removeListener = function (event_name
, cb
) {
668 GraphEditor
.prototype.setNodeClass = function (class_name
, filter_cb
) {
671 this.svg
.selectAll('.node').classed(class_name
, false);
672 this.svg
.selectAll('.node')
673 .classed(class_name
, filter_cb
);
676 GraphEditor
.prototype.setLinkClass = function (class_name
, filter_cb
) {
679 this.svg
.selectAll('.link').classed(class_name
, false);
680 this.svg
.selectAll('.link')
681 .classed(class_name
, filter_cb
);
684 GraphEditor
.prototype.showNodeInfo = function (args
) {
685 this.addLinesToPopup(args
['node_info'], "Info about node selected")
686 this.handlePopupVisibility(true, 'right')
689 GraphEditor
.prototype.addLinesToPopup = function (data
, title
) {
693 var width_popup
= 400;
694 var height_popup
= 0;
696 d3
.selectAll(".popupcleanable").remove(); // clean
698 var popupbg
= d3
.select(".popup").append("rect")
699 .attr("id", "popupbg")
700 .attr("class", "popup bg popupcleanable cleanable")
701 .attr("width", "400")
703 .attr("rx", 10) // set the x corner curve radius
704 .attr("ry", 10); // set the y corner curve radius
707 d3
.select(".popup").append("svg:path")
708 .attr("d", d3
.symbol()
714 return (self
.get_d3_symbol());
717 .style("fill", 'red')
718 .attr("transform", function () {
719 return "translate(380,15) rotate(-45)";
722 .attr("stroke-width", 2.4)
723 .attr("id", "close_popup")
724 .attr("class", "popupcleanable cleanable")
725 .on("click", function (d
) {
726 self
.handlePopupVisibility(false);
729 d3
.select(".popup").append("text")
730 .attr("class", "popup title popupcleanable cleanable")
735 for (var i
in data
) {
736 //console.log(i, data, data[i])
737 //var typeofvalue = typeof data[i];
738 var record
= data
[i
];
739 index
= this._addRecordToPopup(i
, record
, index
)
745 GraphEditor
.prototype._addRecordToPopup = function (key
, record
, index
, tab
) {
746 //console.log("_addRecordToPopup", key, record, index)
747 var translate_y
= 23 * index
;
748 var summary
= d3
.select(".popup").append("g")
749 .attr("class", "popup summary d popupcleanable cleanable")
750 .attr("transform", "translate(10 " + translate_y
+ ")");
751 if (Object
.prototype.toString
.call(record
) !== '[object Array]') { //is a record simple key:value
752 //console.log(key, record)
753 var summary_g
= summary
.append("g");
754 summary_g
.append("rect")
755 .attr("class", "popup summary bg popupcleanable cleanable")
756 .attr("width", "380")
757 .attr("height", "20");
759 summary_g
.append("text")
760 .attr("class", "popup summary popupcleanable cleanable")
761 .attr("x", (tab
) ? tab
: 10)
763 .attr("width", "100")
765 return key
.toUpperCase() + ":";
768 summary_g
.append("text")
769 .attr("class", "popup summary popupcleanable cleanable")
772 .attr("text-anchor", "end")
777 else {//is a record simple complex: have a list of sub record key:value
779 this._addRecordToPopup(key
, "", index
)
780 for (var r
in record
) {
781 //console.log(i, r, record, record[r])
782 for (var k
in record
[r
]) {
783 //console.log(i, r, k, record[r][k])
785 var recordValue
= record
[r
][k
]
788 this._addRecordToPopup(curr_key
, recordValue
, index
, 20)
794 translate_y
= 30 * index
++;
795 d3
.select('#popupbg').attr("height", translate_y
);
801 * Remove all the graph objects from the view
803 GraphEditor
.prototype.cleanAll = function () {
804 this.svg
.selectAll('.cleanable').remove();
811 GraphEditor
.prototype._node_property_by_type = function (type
, property
, node
) {
812 //console.log(type, property, layer, group)
813 var unrecognized = function (ui_prop
, property
) {
814 return ui_prop
['unrecognized'][property
]
818 if (this.type_property
[type
]) {
820 if (this.type_property
[type
]['property']) {
821 var filt_property
= this.type_property
[type
]['property']
822 return this.type_property
[type
][node
.info
[filt_property
]][property
]
823 } else { // type without property spec
825 return this.type_property
[type
][property
]
829 } else { //type unrecognized
830 return unrecognized(this.type_property
, property
)
835 GraphEditor
.prototype._link_property_by_type = function (type
, property
) {
836 //log(type + "-" + property)
837 if (this.type_property_link
[type
] != undefined && this.type_property_link
[type
][property
] != undefined) {
838 //if(property == "shape")
839 // log("dentro" + this.type_property[type][property])
840 return this.type_property_link
[type
][property
];
842 return this.type_property_link
['unrecognized'][property
];
853 GraphEditor
.prototype._setupFiltersBehaviors = function (args
) {
857 this.node_filter_cb
= args
.node_filter_cb
|| function (d
) {
859 var cond_view
= true,
861 //log(d.info.type + " " + self.filter_parameters.node.type + " group: " + self.filter_parameters.node.group + "- " + d.info.group)
862 // check filter by node type
863 if (self
.filter_parameters
.node
.type
.length
> 0) {
865 if (self
.filter_parameters
.node
.type
.indexOf(d
.info
.type
) < 0)
869 // check filter by group
870 if (self
.filter_parameters
.node
.group
.length
> 0) {
871 self
.filter_parameters
.node
.group
.forEach(function (group
) {
872 if (d
.info
.group
.indexOf(group
) < 0)
880 return cond_view
&& cond_group
;
883 this.link_filter_cb
= args
.link_filter_cb
|| function (d
) {
884 var cond_view
= true,
887 // check filter by view
888 if (self
.filter_parameters
.link
.view
.length
> 0) {
889 self
.filter_parameters
.link
.view
.forEach(function (view
) {
890 if (d
.view
.indexOf(view
) < 0)
895 // check filter by group
896 if (self
.filter_parameters
.link
.group
.length
> 0) {
897 self
.filter_parameters
.link
.group
.forEach(function (group
) {
898 if (d
.group
.indexOf(group
) < 0)
902 return cond_view
&& cond_group
;
911 GraphEditor
.prototype._setupBehaviorsOnEvents = function () {
912 log("_setupBehaviorsOnEvents");
914 this.behavioursOnEvents
= {
916 'click': function (d
) {
917 d3
.event
.preventDefault();
919 if (self
.lastKeyDown
=== SHIFT_BUTTON
&& self
._selected_node
!== undefined) {
920 var source_id
= self
._selected_node
.id
;
921 var target_id
= d
.id
;
922 log("--" + JSON
.stringify(self
.filter_parameters
.link
.view
));
926 view
: self
.filter_parameters
.link
.view
[0],
927 group
: self
.filter_parameters
.link
.group
[0],
929 self
.addLink(new_link
);
930 self
._deselectAllNodes();
932 self
._selectNodeExclusive(this, d
);
936 'mouseover': function (d
) {
939 'mouseout': function (d
) {
941 'dblclick': function (d
) {
942 d3
.event
.preventDefault();
945 'contextmenu': function (d
, i
) {
946 d3
.event
.preventDefault();
947 log("contextmenu node");
948 self
.eventHandler
.fire("right_click_node", d
);
952 'click': function (event
) {
955 'dblclick': function (event
) {
963 * Deselect previously selected nodes
966 GraphEditor
.prototype._deselectAllNodes = function () {
967 log("_deselectAllNodes");
968 this.node
.classed("node_selected", false);
969 this._selected_node
= undefined;
972 GraphEditor
.prototype._deselectAllLinks = function () {
973 log("_deselectAllLinks");
974 this.link
.classed("link_selected", false).style('stroke-width', 2);
975 this._selected_link
= undefined;
978 * Select node in exclusive mode
979 * @param {Object} Required. Element selected on click event
981 GraphEditor
.prototype._selectNodeExclusive = function (node_instance
, node_id
) {
982 log("_selectNodeExclusive ");
983 var activeClass
= "node_selected";
984 var alreadyIsActive
= d3
.select(node_instance
).classed(activeClass
);
985 this._deselectAllNodes();
986 this._deselectAllLinks();
987 d3
.select(node_instance
).classed(activeClass
, !alreadyIsActive
);
988 this._selected_node
= (alreadyIsActive
) ? undefined : node_instance
.__data__
;
989 if(this._selected_node
){
990 this.eventHandler
.fire("node:selected", this._selected_node
)
992 this.eventHandler
.fire("node:deselected", this._selected_node
)
997 * Select node in exclusive mode
998 * @param {Object} Required. Element selected on click event
1000 GraphEditor
.prototype._selectLinkExclusive = function (link_instance
, link_id
) {
1001 log("_selectLinkExclusive ");
1002 var activeClass
= "link_selected";
1003 var alreadyIsActive
= d3
.select(link_instance
).classed(activeClass
);
1004 this._deselectAllNodes();
1005 this._deselectAllLinks();
1006 d3
.select(link_instance
).classed(activeClass
, !alreadyIsActive
);
1007 d3
.select(link_instance
).style('stroke-width', 4)
1008 this._selected_link
= link_instance
.__data__
;
1012 * Callback to resize SVG element on window resize
1014 GraphEditor
.prototype.resizeSvg = function (width
, height
) {
1017 this.width
= width
|| this.width
;
1018 this.height
= height
|| this.height
;
1019 this.svg
.attr('width', width
);
1020 this.svg
.attr('height', height
);
1024 GraphEditor
.prototype.handlePopupVisibility = function (visible
, side
) {
1025 var opacity
= (visible
) ? 1 : 0;
1027 var translate_op
= (side
=== "left") ? "translate(50 50)" : "translate(" + (this.width
- 450).toString() + " 50)";
1030 d3
.selectAll(".popupcleanable").remove();
1032 .attr("transform", "translate(-1 -1)");
1035 .attr("transform", translate_op
);
1037 d3
.select(".popup").attr("opacity", opacity
);
1040 GraphEditor
.prototype.refreshGraphParameters = function (graphParameters
) {
1041 this.eventHandler
.fire("refresh_graph_parameters", graphParameters
);
1047 function log(text
) {
1049 console
.log("::GraphEditor::", text
);
1058 if (typeof module
=== 'object') {
1059 module
.exports
= TCD3
.GraphEditor
;