blob: 50e4f44461d6a0323fac71c7cf129a982cad9c17 [file] [log] [blame]
lombardoffb37bca2018-05-03 16:20:04 +02001/*
2 Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
lombardofre6eb7432018-10-28 19:43:46 +01003 Copyright 2018 EveryUP srl
lombardoffb37bca2018-05-03 16:20:04 +02004
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
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
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.
16*/
lombardoffb37bca2018-05-03 16:20:04 +020017
lombardofre6eb7432018-10-28 19:43:46 +010018if (typeof TCD3 === 'undefined') {
19 var TCD3 = {};
20}
21
22TCD3.GraphEditor = (function () {
lombardoffb37bca2018-05-03 16:20:04 +020023 'use strict';
24
25 var DEBUG = true;
26 var SHIFT_BUTTON = 16;
27 var CANC_BUTTON = 46;
lombardofre6eb7432018-10-28 19:43:46 +010028 var nominal_text_size = 14;
lombardoffb37bca2018-05-03 16:20:04 +020029 var nominal_stroke = 1.5;
lombardofre6eb7432018-10-28 19:43:46 +010030 var EventHandler = TCD3.Event;
lombardoffb37bca2018-05-03 16:20:04 +020031
32
33 /**
34 * Constructor
35 */
36 function GraphEditor(args) {
37 log("Constructor");
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 = {
44 node: {
45 type: [],
46 group: [],
47 },
48 link: {
49 group: [],
50 view: [],
51 }
52 };
53 this.current_view_id = '';
54 // graph data initailization
55 this.d3_graph = {
56 nodes: [],
57 links: [],
58 graph_parameters: {}
59
60 };
61
62
63 }
64
65
lombardoffb37bca2018-05-03 16:20:04 +020066 GraphEditor.prototype.init = function (args) {
lombardofre6eb7432018-10-28 19:43:46 +010067 args = args || {};
lombardoffb37bca2018-05-03 16:20:04 +020068 var self = this;
lombardofre6eb7432018-10-28 19:43:46 +010069 this.width = args.width || 1500;
70 this.height = args.height || 1500;
lombardoffb37bca2018-05-03 16:20:04 +020071 this.forceSimulationActive = false;
72
lombardoffb37bca2018-05-03 16:20:04 +020073 var min_zoom = 0.1;
74 var max_zoom = 7;
75 this._setupBehaviorsOnEvents();
76 this._setupFiltersBehaviors(args);
77
78 this.type_property = {
79 "unrecognized": {
80 "shape": d3.symbolCircle,
lombardofre6eb7432018-10-28 19:43:46 +010081 "color": "#fff",
82 "node_label_color": "#000",
lombardoffb37bca2018-05-03 16:20:04 +020083 "size": 15
84 },
85 };
86
87 this.type_property_link = {
88 "unrecognized": {
lombardofr1e320062018-10-30 22:16:25 +010089 "color": "lightgray"
lombardoffb37bca2018-05-03 16:20:04 +020090 },
91 };
92
93 this.force = d3.forceSimulation()
lombardofre6eb7432018-10-28 19:43:46 +010094 .force("charge", d3.forceManyBody())
lombardofr1e320062018-10-30 22:16:25 +010095 .force("collide", d3.forceCollide().radius(40))
lombardofre6eb7432018-10-28 19:43:46 +010096 // .force("link", d3.forceLink().distance(80).iterations(1).id(function (d) {
lombardofr1e320062018-10-30 22:16:25 +010097 .force("link", d3.forceLink().distance(function(d){
98 return d.short ? 1 : 100;
99 }).id(function (d) {
lombardoffb37bca2018-05-03 16:20:04 +0200100 return d.id;
101 }))
102 .force("center", d3.forceCenter(this.width / 2, this.height / 2));
103
lombardofre6eb7432018-10-28 19:43:46 +0100104 var zoom = d3.zoom().scaleExtent([min_zoom, max_zoom]);
lombardoffb37bca2018-05-03 16:20:04 +0200105
106 var size = d3.scalePow().exponent(2)
107 .domain([1, 100])
108 .range([8, 24]);
109
lombardofre6eb7432018-10-28 19:43:46 +0100110 this.svg = d3.select("#graph_editor_container").append("svg")
lombardoffb37bca2018-05-03 16:20:04 +0200111 .attr("id", "graph_svg")
lombardofre6eb7432018-10-28 19:43:46 +0100112 .attr("preserveAspectRatio", "xMinYMid")
lombardofr1e320062018-10-30 22:16:25 +0100113 .attr("width", '100%')
114 .attr("height", '100%');
lombardoffb37bca2018-05-03 16:20:04 +0200115
116 //End Arrow style
117 this.defs = this.svg.append("svg:defs");
118
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
122 .attr("id", String)
123 .attr("viewBox", "-5 -5 10 10")
124 .attr("refX", 13) //must be smarter way to calculate shift
125 .attr("refY", 0)
126 .attr("markerUnits", "userSpaceOnUse")
127 .attr("markerWidth", 12)
128 .attr("markerHeight", 12)
129 .attr("orient", "auto")
130 .append("path")
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']);
133
134 d3.select(window)
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;
lombardofre6eb7432018-10-28 19:43:46 +0100140 if (self.lastKeyDown === CANC_BUTTON && self._selected_node !== undefined) {
lombardoffb37bca2018-05-03 16:20:04 +0200141 self.removeNode(self._selected_node, null, showAlert);
lombardofre6eb7432018-10-28 19:43:46 +0100142 } else if (self.lastKeyDown === CANC_BUTTON && self._selected_link !== undefined) {
lombardoffb37bca2018-05-03 16:20:04 +0200143 self.removeLink(self._selected_link, null, showAlert);
144 }
145
146 })
147 .on('keyup', function () {
148 log('keyup' + self.lastKeyDown);
149 self.lastKeyDown = -1;
150 });
151 var popup = this.svg.append("g")
152 .attr("id", "popup")
153 .attr("class", "popup")
154 .attr("opacity", "0")
155 .attr("transform", "translate(1 1)")
156 .call(d3.drag()
lombardofre6eb7432018-10-28 19:43:46 +0100157 .on("start", dragstarted)
158 .on("drag", dragged)
159 .on("end", dragended));
lombardoffb37bca2018-05-03 16:20:04 +0200160
161 function dragstarted(d) {
lombardofre6eb7432018-10-28 19:43:46 +0100162 //d3.select(this).raise().classed("active", true);
lombardoffb37bca2018-05-03 16:20:04 +0200163 }
164
165 function dragged(d) {
166 //console.log(JSON.stringify(d))
lombardofre6eb7432018-10-28 19:43:46 +0100167 d3.select(this).attr("transform", function () {
168 return "translate(" + d3.event.x + "," + d3.event.y + ")";
lombardoffb37bca2018-05-03 16:20:04 +0200169
170 })
171 }
172
173 function dragended(d) {
lombardofre6eb7432018-10-28 19:43:46 +0100174 //d3.select(this).classed("active", false);
lombardoffb37bca2018-05-03 16:20:04 +0200175 }
176
177 var chart = $("#graph_svg");
178 this.aspect = chart.width() / chart.height();
lombardofre6eb7432018-10-28 19:43:46 +0100179 this.container = $("#graph_editor_container");
180 $(window).on("resize", function () {
lombardoffb37bca2018-05-03 16:20:04 +0200181
lombardofre6eb7432018-10-28 19:43:46 +0100182 self.width = self.container.width();
lombardoffb37bca2018-05-03 16:20:04 +0200183 self.height = self.container.height();
lombardofre6eb7432018-10-28 19:43:46 +0100184 chart.attr("width", self.container.width());
185 chart.attr("height", self.container.height());
lombardofr1e320062018-10-30 22:16:25 +0100186 });
lombardoffb37bca2018-05-03 16:20:04 +0200187
188 }
189
190
191 GraphEditor.prototype.get_d3_symbol =
192 function (myString) {
lombardofr99f922f2018-07-17 17:27:36 +0200193
lombardoffb37bca2018-05-03 16:20:04 +0200194 switch (myString) {
lombardofre6eb7432018-10-28 19:43:46 +0100195 case "circle":
196 return d3.symbolCircle;
197 case "square":
198 return d3.symbolSquare;
199 case "diamond":
200 return d3.symbolDiamond;
201 case "triangle":
202 return d3.symbolTriangle;
203 case "star":
204 return d3.symbolStar;
205 case "cross":
206 return d3.symbolCross;
207 default:
208 // if the string is not recognized
209 return d3.symbolCross;
lombardoffb37bca2018-05-03 16:20:04 +0200210 }
211
lombardofre6eb7432018-10-28 19:43:46 +0100212 };
lombardoffb37bca2018-05-03 16:20:04 +0200213
lombardofre6eb7432018-10-28 19:43:46 +0100214 GraphEditor.prototype.get_name_from_d3_symbol =
lombardoffb37bca2018-05-03 16:20:04 +0200215 function (mySymbol) {
lombardoffb37bca2018-05-03 16:20:04 +0200216 switch (mySymbol) {
lombardofre6eb7432018-10-28 19:43:46 +0100217 case d3.symbolCircle:
218 return "circle";
219 case d3.symbolSquare:
220 return "square";
221 case d3.symbolDiamond:
222 return "diamond";
223 case d3.symbolTriangle:
224 return "triangle";
225 case d3.symbolStar:
226 return "star";
227 case d3.symbolCross:
228 return "cross";
229 default:
230 // if the string is not recognized
231 return "unknown";
lombardoffb37bca2018-05-03 16:20:04 +0200232 //return d3.symbolCircleUnknown;
233 }
234
lombardofre6eb7432018-10-28 19:43:46 +0100235 };
lombardoffb37bca2018-05-03 16:20:04 +0200236
237 /**
238 * Start or Stop force layout
239 * @param {boolean} Required. Value true: start, false: stop
240 * @returns {boolean}
241 */
242 GraphEditor.prototype.handleForce = function (start) {
243 if (start)
244 this.force.stop();
245 this.forceSimulationActive = start;
246 this.node.each(function (d) {
247 d.fx = (start) ? null : d.x;
248 d.fy = (start) ? null : d.y;
249 });
250
251 if (start)
252 this.force.restart();
253
254 this.eventHandler.fire("force_status_changed_on", start);
255 };
256
257 /**
258 * Handle the parameters of basic filters: node type, view, group
259 * @param {Object} Required.
260 *
261 */
262 GraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) {
lombardofre6eb7432018-10-28 19:43:46 +0100263 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
lombardoffb37bca2018-05-03 16:20:04 +0200265 this.cleanAll();
266 this.refresh();
267 this.startForce();
268 this.force.restart();
269 this._deselectAllNodes();
270 this.handleForce(this.forceSimulationActive);
271 if (!notFireEvent)
272 this.eventHandler.fire("filters_changed", filtersParams);
273
274 };
275
276 /**
277 * Add a new node to the graph.
278 * @param {Object} Required. An object that specifies tha data of the new node.
279 * @returns {boolean}
280 */
281 GraphEditor.prototype.addNode = function (args) {
282 if (args.id && args.info && args.info.type) {
283 args.fixed = true;
284 this.force.stop();
285 this.cleanAll();
286 this.d3_graph.nodes.push(args);
287 this.refresh();
288 this.startForce();
289 this.force.restart();
290 this.handleForce(this.forceSimulationActive);
291 return true;
292 }
293
294 return false;
295
296 };
297
298 /**
299 * Update the data properties of the node
300 * @param {Object} Required. An object that specifies tha data of the node.
301 * @returns {boolean}
302 */
303 GraphEditor.prototype.updateDataNode = function (args) {
304
305 };
306
307 /**
308 * Remove a node from graph and related links.
309 * @param {String} Required. Id of node to remove.
310 * @returns {boolean}
311 */
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) {
lombardofre6eb7432018-10-28 19:43:46 +0100316 if (n.id === node_id) {
lombardoffb37bca2018-05-03 16:20:04 +0200317 object.splice(index, 1);
lombardoffb37bca2018-05-03 16:20:04 +0200318 }
lombardoffb37bca2018-05-03 16:20:04 +0200319 });
lombardofre6eb7432018-10-28 19:43:46 +0100320
lombardoffb37bca2018-05-03 16:20:04 +0200321 var self = this;
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);
326 }
lombardoffb37bca2018-05-03 16:20:04 +0200327 });
328 var links_removed = 0;
329 links_to_remove.forEach(function (l_index) {
330 self.d3_graph['links'].splice(l_index - links_removed, 1);
331 links_removed++;
332 });
333 this.cleanAll();
334 this.refresh();
335 this.startForce();
336 this.force.restart();
337
338 return true;
339 }
340 return false;
341 };
342
343
344 /**
345 * Add a new link to graph.
346 * @param {Object} Required. An object that specifies tha data of the new Link.
347 * @returns {boolean}
348 */
349 GraphEditor.prototype.addLink = function (link) {
lombardofre6eb7432018-10-28 19:43:46 +0100350 console.log("addLink" + JSON.stringify(link));
lombardoffb37bca2018-05-03 16:20:04 +0200351 if (link.source && link.target) {
352 this.force.stop();
353 this.cleanAll();
354 this.d3_graph.links.push(link);
355 this.refresh();
356 this.startForce();
357 this.force.restart();
358 return true;
359 }
360
361 return false;
362 };
363
364 /**
365 * Remove a link from graph.
366 * @param {String} Required. The identifier of link to remove.
367 * @returns {boolean}
368 */
369 GraphEditor.prototype.removeLink = function (link_id) {
370 var self = this;
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);
375
376 self.cleanAll();
377 self.refresh();
378 self.startForce();
379 self.force.restart();
380 return true;
381 }
382
383 });
384 }
385
386 return false;
387 };
388
389
390 /**
391 * Force a refresh of GraphView
392 * @returns {}
393 */
394 GraphEditor.prototype.refresh = function () {
395
396 //log(data)
397 var self = this;
398
lombardofre428af72018-11-15 17:25:25 +0100399 var link = this.svg
lombardoffb37bca2018-05-03 16:20:04 +0200400 .selectAll()
401 .data(self.d3_graph.links
402 .filter(this.link_filter_cb)
lombardofre428af72018-11-15 17:25:25 +0100403 );
404 link.exit().remove();
405 this.link = link.enter().append("g")
lombardoffb37bca2018-05-03 16:20:04 +0200406 .attr("class", "link cleanable")
407 .append("path")
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");
413 })
414 .attr("marker-end", function (d) {
415 if (!d.directed_edge)
416 return '';
417
418 var marker_url = (d.type_link) ? d.type_link : "unrecognized"
419 return (d.directed_edge ? "url(#" + marker_url + ")" : '');
420 });
421
lombardofre428af72018-11-15 17:25:25 +0100422 var nodeContainer = this.svg
lombardoffb37bca2018-05-03 16:20:04 +0200423 .selectAll()
424 .data(self.d3_graph.nodes
lombardofre428af72018-11-15 17:25:25 +0100425 .filter(this.node_filter_cb));
426 nodeContainer.exit().remove();
427 nodeContainer.enter()
lombardoffb37bca2018-05-03 16:20:04 +0200428 .append("g")
429 // .attr("class", "nodosdads")
430 .attr("class", "node cleanable");
431
lombardofre428af72018-11-15 17:25:25 +0100432 var nodes_symbols = this.svg.selectAll('.node')
lombardoffb37bca2018-05-03 16:20:04 +0200433 .data(self.d3_graph.nodes
434 .filter(this.node_filter_cb))
435
436 .filter(function (d) {
lombardofre6eb7432018-10-28 19:43:46 +0100437 return (d.info.type === undefined) || (self._node_property_by_type(d.info.type, 'image', d) === undefined)
lombardofre428af72018-11-15 17:25:25 +0100438 });
439 nodes_symbols.exit().remove();
lombardoffb37bca2018-05-03 16:20:04 +0200440
lombardofre428af72018-11-15 17:25:25 +0100441 nodes_symbols.append("svg:path")
lombardoffb37bca2018-05-03 16:20:04 +0200442 .attr("d", d3.symbol()
443 .size(function (d) {
444 return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d), 2) / 4;
445 })
446 .type(function (d) {
447 // console.log(d.info.type, 'shape', self.current_view_id)
448 return (self._node_property_by_type(d.info.type, 'shape', d));
449 })
450 )
451 .style("fill", function (d) {
452 return self._node_property_by_type(d.info.type, 'color', d);
453 })
454 .attr("transform", function () {
455 return "rotate(-45)";
456
457 })
458 .attr("stroke-width", 2.4)
459
460 .attr("class", "node_path")
461 .attr("id", function (d) {
462 return "path_" + d.id;
463 })
464
465 .call(d3.drag()
466 .on("start", dragstarted)
467 .on("drag", dragged)
468 .on("end", dragended));
469
470 var figure_node = this.svg.selectAll('.node')
471 .data(self.d3_graph.nodes
472 .filter(this.node_filter_cb))
473
474 .filter(function (d) {
475 return self._node_property_by_type(d.info.type, 'image', d) != undefined
476 });
lombardofre428af72018-11-15 17:25:25 +0100477 figure_node.exit().remove();
lombardoffb37bca2018-05-03 16:20:04 +0200478 figure_node.append("svg:image")
479 .attr("xlink:href", function (d) {
480 return self._node_property_by_type(d.info.type, 'image', d)
481 })
482 .attr("x", function (d) {
483 return -self._node_property_by_type(d.info.type, 'size', d) / 2
484 })
485 .attr("y", function (d) {
486 return -self._node_property_by_type(d.info.type, 'size', d) / 2
487 })
488 .attr("width", function (d) {
489 return self._node_property_by_type(d.info.type, 'size', d)
490 })
491 .attr("height", function (d) {
492 return self._node_property_by_type(d.info.type, 'size', d)
493 })
494 .style("stroke", "black")
495 .style("stroke-width", "1px")
496
497 .attr("class", "node_path")
498 .attr("id", function (d) {
499 return "path_" + d.id;
500 })
501 .call(d3.drag()
502 .on("start", dragstarted)
503 .on("drag", dragged)
504 .on("end", dragended));
505
506 figure_node.append("svg:path")
507 .attr("d", d3.symbol()
508 .size(function (d) {
509 return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d) + 7, 2) / 4;
510 })
511 .type(function (d) {
512 return (self.get_d3_symbol('circle'));
513 })
514 )
515 .style("fill", 'transparent')
516 .attr("transform", function () {
517 return "rotate(-45)";
518
519 })
520 .attr("stroke-width", 2.4)
521
522 .attr("class", "hidden_circle")
523 .attr("id", function (d) {
524 return "path_" + d.id;
525 })
526
527 .call(d3.drag()
528 .on("start", dragstarted)
529 .on("drag", dragged)
530 .on("end", dragended));
531
532
lombardoffb37bca2018-05-03 16:20:04 +0200533 this.node = this.svg.selectAll('.node')
534 .data(self.d3_graph.nodes
535 .filter(this.node_filter_cb)).selectAll("image, path, circle");
536
537
lombardoffb37bca2018-05-03 16:20:04 +0200538 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"]);
543
544 this.link
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"]);
549
550
lombardoffb37bca2018-05-03 16:20:04 +0200551 this.text = this.svg.selectAll(".node")
552 .data(self.d3_graph.nodes
553 .filter(this.node_filter_cb))
554 .append("svg:text")
lombardofre6eb7432018-10-28 19:43:46 +0100555 .attr("class", "node_text cleanable")
556 .attr("dy", function (d) {
557 return "-5";
lombardoffb37bca2018-05-03 16:20:04 +0200558 })
559 .attr("pointer-events", "none")
560 .style("font-size", nominal_text_size + "px")
lombardofre6eb7432018-10-28 19:43:46 +0100561 .style("font-family", "'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif")
lombardoffb37bca2018-05-03 16:20:04 +0200562 .style("fill", function (d) {
563 return self._node_property_by_type(d.info.type, 'node_label_color', d);
564 })
lombardofre6eb7432018-10-28 19:43:46 +0100565 //.style("text-anchor", "middle")
lombardoffb37bca2018-05-03 16:20:04 +0200566 .text(function (d) {
lombardofre6eb7432018-10-28 19:43:46 +0100567 if(d.info && d.info.property.custom_label && d.info.property.custom_label !==''){
568 return d.info.property.custom_label
569 } else
570 return d.id;
lombardoffb37bca2018-05-03 16:20:04 +0200571 });
572
573
lombardoffb37bca2018-05-03 16:20:04 +0200574 function dragstarted(d) {
575 d.draggednode = true;
576 if (!d3.event.active) self.force.alphaTarget(0.3).restart();
577 d.fx = d.x;
578 d.fy = d.y;
579
580 }
581
582 function dragged(d) {
583 d.fx = d3.event.x;
584 d.fy = d3.event.y;
585 }
586
587 function dragended(d) {
588 d.draggednode = false;
589 if (!d3.event.active) self.force.alphaTarget(0);
590 if (self.forceSimulationActive) {
591 d.fx = null;
592 d.fy = null;
593 } else {
594 d.fx = d.x;
595 d.fy = d.y;
596 self.force.stop();
597 self.forceSimulationActive = false;
598 }
599 }
600
601
602 };
603
604 /**
605 * Start force layout on Graph.
606 *
607 */
608 GraphEditor.prototype.startForce = function () {
lombardofre6eb7432018-10-28 19:43:46 +0100609 this.force.stop();
610 var self = this;
lombardoffb37bca2018-05-03 16:20:04 +0200611 this.force
612 .nodes(this.d3_graph.nodes)
613 .on("tick", ticked);
614
615
616 this.force
617 .force("link")
618 .links(this.d3_graph.links);
619
620 function ticked() {
621 self.node.attr("cx", function (d) {
lombardofre6eb7432018-10-28 19:43:46 +0100622 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));
623 })
lombardoffb37bca2018-05-03 16:20:04 +0200624 .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));
626 });
627
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;
633 });
634
635 self.node.attr("transform", function (d) {
636 return "translate(" + d.x + "," + d.y + ")";
637 });
638 self.text.attr("transform", function (d) {
lombardofre6eb7432018-10-28 19:43:46 +0100639 var label_pos_y = d.y + self._node_property_by_type(d.info.type, 'size', d)/2 +nominal_text_size;
lombardoffb37bca2018-05-03 16:20:04 +0200640 return "translate(" + d.x + "," + label_pos_y + ")";
641 });
lombardofre6eb7432018-10-28 19:43:46 +0100642 }
lombardoffb37bca2018-05-03 16:20:04 +0200643
644
645 };
646
647 /**
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.
651 * @returns {}
652 */
653 GraphEditor.prototype.addListener = function (event_name, cb) {
654 this.eventHandler.addL(event_name, cb);
lombardofre6eb7432018-10-28 19:43:46 +0100655 };
lombardoffb37bca2018-05-03 16:20:04 +0200656
657 /**
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.
661 * @returns {}
662 */
663 GraphEditor.prototype.removeListener = function (event_name, cb) {
664
lombardofre6eb7432018-10-28 19:43:46 +0100665 };
lombardoffb37bca2018-05-03 16:20:04 +0200666
667
668 GraphEditor.prototype.setNodeClass = function (class_name, filter_cb) {
669 log("setNodeClass");
670 var self = this;
671 this.svg.selectAll('.node').classed(class_name, false);
672 this.svg.selectAll('.node')
673 .classed(class_name, filter_cb);
lombardofre6eb7432018-10-28 19:43:46 +0100674 };
lombardoffb37bca2018-05-03 16:20:04 +0200675
676 GraphEditor.prototype.setLinkClass = function (class_name, filter_cb) {
677 log("setLinkClass");
678 var self = this;
679 this.svg.selectAll('.link').classed(class_name, false);
680 this.svg.selectAll('.link')
681 .classed(class_name, filter_cb);
lombardofre6eb7432018-10-28 19:43:46 +0100682 };
lombardoffb37bca2018-05-03 16:20:04 +0200683
lombardofre6eb7432018-10-28 19:43:46 +0100684 GraphEditor.prototype.showNodeInfo = function (args) {
lombardoffb37bca2018-05-03 16:20:04 +0200685 this.addLinesToPopup(args['node_info'], "Info about node selected")
686 this.handlePopupVisibility(true, 'right')
lombardofre6eb7432018-10-28 19:43:46 +0100687 };
688
689 GraphEditor.prototype.addLinesToPopup = function (data, title) {
lombardoffb37bca2018-05-03 16:20:04 +0200690 var self = this;
691 var index = 1;
692 var translate_y = 0;
693 var width_popup = 400;
694 var height_popup = 0;
695
696 d3.selectAll(".popupcleanable").remove(); // clean
697
698 var popupbg = d3.select(".popup").append("rect")
699 .attr("id", "popupbg")
700 .attr("class", "popup bg popupcleanable cleanable")
701 .attr("width", "400")
702 .attr("height", "0")
703 .attr("rx", 10) // set the x corner curve radius
704 .attr("ry", 10); // set the y corner curve radius
705
706
707 d3.select(".popup").append("svg:path")
708 .attr("d", d3.symbol()
709 .size(function (d) {
710 return 80
711 })
712 .type(function (d) {
lombardofre6eb7432018-10-28 19:43:46 +0100713 console.log("popup")
lombardoffb37bca2018-05-03 16:20:04 +0200714 return (self.get_d3_symbol());
715 })
716 )
717 .style("fill", 'red')
718 .attr("transform", function () {
719 return "translate(380,15) rotate(-45)";
720
721 })
722 .attr("stroke-width", 2.4)
723 .attr("id", "close_popup")
724 .attr("class", "popupcleanable cleanable")
lombardofre6eb7432018-10-28 19:43:46 +0100725 .on("click", function (d) {
lombardoffb37bca2018-05-03 16:20:04 +0200726 self.handlePopupVisibility(false);
727 });
728
729 d3.select(".popup").append("text")
730 .attr("class", "popup title popupcleanable cleanable")
731 .attr("x", "10")
732 .attr("y", "20")
733 .text(title);
734
735 for (var i in data) {
736 //console.log(i, data, data[i])
737 //var typeofvalue = typeof data[i];
738 var record = data[i];
lombardofre6eb7432018-10-28 19:43:46 +0100739 index = this._addRecordToPopup(i, record, index)
lombardoffb37bca2018-05-03 16:20:04 +0200740
741 }
742
743 };
744
745 GraphEditor.prototype._addRecordToPopup = function (key, record, index, tab) {
746 //console.log("_addRecordToPopup", key, record, index)
747 var translate_y = 23 * index;
lombardofre6eb7432018-10-28 19:43:46 +0100748 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
lombardoffb37bca2018-05-03 16:20:04 +0200752 //console.log(key, record)
753 var summary_g = summary.append("g");
lombardofre6eb7432018-10-28 19:43:46 +0100754 summary_g.append("rect")
755 .attr("class", "popup summary bg popupcleanable cleanable")
756 .attr("width", "380")
757 .attr("height", "20");
lombardoffb37bca2018-05-03 16:20:04 +0200758
lombardofre6eb7432018-10-28 19:43:46 +0100759 summary_g.append("text")
760 .attr("class", "popup summary popupcleanable cleanable")
761 .attr("x", (tab) ? tab : 10)
762 .attr("y", "17")
763 .attr("width", "100")
764 .text(function (d) {
765 return key.toUpperCase() + ":";
766 });
lombardoffb37bca2018-05-03 16:20:04 +0200767
lombardofre6eb7432018-10-28 19:43:46 +0100768 summary_g.append("text")
769 .attr("class", "popup summary popupcleanable cleanable")
770 .attr("x", "370")
771 .attr("y", "17")
772 .attr("text-anchor", "end")
773 .text(function (d) {
774 return record
775 });
lombardoffb37bca2018-05-03 16:20:04 +0200776 }
777 else {//is a record simple complex: have a list of sub record key:value
lombardofre6eb7432018-10-28 19:43:46 +0100778 //index ++;
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])
784 var curr_key = k;
785 var recordValue = record[r][k]
lombardoffb37bca2018-05-03 16:20:04 +0200786
lombardofre6eb7432018-10-28 19:43:46 +0100787 index++;
788 this._addRecordToPopup(curr_key, recordValue, index, 20)
lombardoffb37bca2018-05-03 16:20:04 +0200789 }
lombardofre6eb7432018-10-28 19:43:46 +0100790 }
lombardoffb37bca2018-05-03 16:20:04 +0200791
792 }
793
794 translate_y = 30 * index++;
795 d3.select('#popupbg').attr("height", translate_y);
796 return index;
797 };
798
799
lombardoffb37bca2018-05-03 16:20:04 +0200800 /**
801 * Remove all the graph objects from the view
802 */
803 GraphEditor.prototype.cleanAll = function () {
804 this.svg.selectAll('.cleanable').remove();
805 };
806
807 /**
808 * Internal functions
809 */
810
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]
815 };
816
817 //type recognized
818 if (this.type_property[type]) {
819
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
824
825 return this.type_property[type][property]
826
827 }
828
829 } else { //type unrecognized
830 return unrecognized(this.type_property, property)
831 }
832
833 };
834
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];
841 } else {
842 return this.type_property_link['unrecognized'][property];
843 }
844
845 }
846
847
848 /**
849 *
850 *
851 *
852 */
853 GraphEditor.prototype._setupFiltersBehaviors = function (args) {
854
855 var self = this;
856
857 this.node_filter_cb = args.node_filter_cb || function (d) {
858
859 var cond_view = true,
860 cond_group = 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) {
864
865 if (self.filter_parameters.node.type.indexOf(d.info.type) < 0)
866 cond_view = false;
867 }
868
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)
873 cond_group = false;
874 });
875
876
877 }
878
879
880 return cond_view && cond_group;
881 };
882
883 this.link_filter_cb = args.link_filter_cb || function (d) {
884 var cond_view = true,
885 cond_group = true;
886
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)
891 cond_view = false;
892 });
893 }
894
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)
899 cond_group = false;
900 });
901 }
902 return cond_view && cond_group;
903 };
904
905 };
906
907 /**
908 *
909 *
910 */
911 GraphEditor.prototype._setupBehaviorsOnEvents = function () {
912 log("_setupBehaviorsOnEvents");
913 var self = this;
914 this.behavioursOnEvents = {
915 'nodes': {
916 'click': function (d) {
917 d3.event.preventDefault();
918 log('click', d);
lombardofre6eb7432018-10-28 19:43:46 +0100919 if (self.lastKeyDown === SHIFT_BUTTON && self._selected_node !== undefined) {
lombardoffb37bca2018-05-03 16:20:04 +0200920 var source_id = self._selected_node.id;
921 var target_id = d.id;
lombardofr99f922f2018-07-17 17:27:36 +0200922 log("--" + JSON.stringify(self.filter_parameters.link.view));
lombardoffb37bca2018-05-03 16:20:04 +0200923 var new_link = {
924 source: source_id,
925 target: target_id,
926 view: self.filter_parameters.link.view[0],
927 group: self.filter_parameters.link.group[0],
928 };
929 self.addLink(new_link);
930 self._deselectAllNodes();
931 } else {
932 self._selectNodeExclusive(this, d);
933 }
934
935 },
936 'mouseover': function (d) {
937
938 },
lombardofre6eb7432018-10-28 19:43:46 +0100939 'mouseout': function (d) {
940 },
lombardoffb37bca2018-05-03 16:20:04 +0200941 'dblclick': function (d) {
942 d3.event.preventDefault();
943 log('dblclick');
944 },
945 'contextmenu': function (d, i) {
946 d3.event.preventDefault();
947 log("contextmenu node");
948 self.eventHandler.fire("right_click_node", d);
949 }
950 },
951 'links': {
952 'click': function (event) {
953
954 },
955 'dblclick': function (event) {
956
957 }
958 }
959 };
960 };
961
962 /**
963 * Deselect previously selected nodes
964 *
965 */
966 GraphEditor.prototype._deselectAllNodes = function () {
967 log("_deselectAllNodes");
968 this.node.classed("node_selected", false);
969 this._selected_node = undefined;
970 };
971
972 GraphEditor.prototype._deselectAllLinks = function () {
973 log("_deselectAllLinks");
974 this.link.classed("link_selected", false).style('stroke-width', 2);
975 this._selected_link = undefined;
976 };
977 /**
978 * Select node in exclusive mode
979 * @param {Object} Required. Element selected on click event
980 */
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__;
lombardofre6eb7432018-10-28 19:43:46 +0100989 if(this._selected_node){
990 this.eventHandler.fire("node:selected", this._selected_node)
991 } else {
992 this.eventHandler.fire("node:deselected", this._selected_node)
993 }
lombardoffb37bca2018-05-03 16:20:04 +0200994 };
995
996 /**
997 * Select node in exclusive mode
998 * @param {Object} Required. Element selected on click event
999 */
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__;
1009 };
1010
1011 /**
1012 * Callback to resize SVG element on window resize
1013 */
1014 GraphEditor.prototype.resizeSvg = function (width, height) {
1015 log("resizeSvg");
lombardofr99f922f2018-07-17 17:27:36 +02001016 //log(event);
lombardoffb37bca2018-05-03 16:20:04 +02001017 this.width = width || this.width;
1018 this.height = height || this.height;
1019 this.svg.attr('width', width);
1020 this.svg.attr('height', height);
1021
1022 }
1023
lombardofre6eb7432018-10-28 19:43:46 +01001024 GraphEditor.prototype.handlePopupVisibility = function (visible, side) {
lombardoffb37bca2018-05-03 16:20:04 +02001025 var opacity = (visible) ? 1 : 0;
1026
lombardofre6eb7432018-10-28 19:43:46 +01001027 var translate_op = (side === "left") ? "translate(50 50)" : "translate(" + (this.width - 450).toString() + " 50)";
lombardoffb37bca2018-05-03 16:20:04 +02001028
1029 if (!visible) {
1030 d3.selectAll(".popupcleanable").remove();
1031 d3.select(".popup")
1032 .attr("transform", "translate(-1 -1)");
1033 } else {
1034 d3.select(".popup")
1035 .attr("transform", translate_op);
1036 }
1037 d3.select(".popup").attr("opacity", opacity);
1038 };
1039
1040 GraphEditor.prototype.refreshGraphParameters = function (graphParameters) {
1041 this.eventHandler.fire("refresh_graph_parameters", graphParameters);
lombardofre6eb7432018-10-28 19:43:46 +01001042 };
lombardoffb37bca2018-05-03 16:20:04 +02001043
1044 /**
1045 * Log utility
1046 */
1047 function log(text) {
1048 if (DEBUG)
1049 console.log("::GraphEditor::", text);
1050 }
1051
1052
lombardoffb37bca2018-05-03 16:20:04 +02001053 return GraphEditor;
1054
1055
1056}(this));
1057
1058if (typeof module === 'object') {
lombardofre6eb7432018-10-28 19:43:46 +01001059 module.exports = TCD3.GraphEditor;
1060}