1 | |
---|
2 | |
---|
3 | var item_li = null; |
---|
4 | |
---|
5 | var svg = null; // main svg-element |
---|
6 | var data_all = null; // global holder for all (input) data |
---|
7 | var nodes_sel = null; // global holder for selected data (selected nodes) |
---|
8 | var data_show = null; // global holder for data to show closure over nodes_sel |
---|
9 | var nest = {}; |
---|
10 | var detail_data = null; // global holder for detail-data (in html) |
---|
11 | |
---|
12 | |
---|
13 | var index_container_selector = "#index-container"; |
---|
14 | var graph_container_selector = '#infovis'; |
---|
15 | var navi_container_selector = '#navigate'; |
---|
16 | var detail_container_selector = "#detail-container"; |
---|
17 | var detail_info_holder_selector = '#detail-info-holder'; |
---|
18 | |
---|
19 | var graph_container = null; |
---|
20 | var index_container = null; |
---|
21 | |
---|
22 | var input_prefix = "input-"; |
---|
23 | var select_rect_min_size = 5; |
---|
24 | var min_circle = 4; |
---|
25 | var comp_reg_url = "http://catalog.clarin.eu/ds/ComponentRegistry/?item="; |
---|
26 | /*var source_file = "../scripts/cmd-dep-graph-d3_all_svg.json"*/ |
---|
27 | var source_file = "file:/C:/Users/m/3/clarin/_repo/SMC/output/cmd-dep-graph.d3.js" |
---|
28 | var detail_file = "file:/C:/Users/m/3/clarin/_repo/SMC/output/smc_stats_detail.html" |
---|
29 | |
---|
30 | var opts = {"depth-before": {"value":2, "min":0, "max":10, "widget":"slider"}, |
---|
31 | "depth-after":{"value":2, "min":0, "max":10, "widget":"slider"}, |
---|
32 | "link-distance": {"value":80, "min":10, "max":200, "widget":"slider" }, |
---|
33 | "charge":{"value":400, "min":10, "max":1000, "widget":"slider" }, |
---|
34 | "node-weight": {"value":"1", "values":["1","usage"], "widget":"selectone" }, |
---|
35 | "curve": {"value":"straight", "values":["straight","arc"], "widget":"selectone" }, |
---|
36 | "layout": {"value":"horizontal-tree", "values":["vertical-tree", "horizontal-tree", "weak-tree","force","dot"], "widget":"selectone" }, |
---|
37 | "labels": {"value":"hide", "values":["show","hide"], "widget":"selectone" }, |
---|
38 | }; |
---|
39 | |
---|
40 | |
---|
41 | |
---|
42 | /** gets the data for the graph and calls rendering of the lists |
---|
43 | * @name initGraph |
---|
44 | * @function |
---|
45 | */ |
---|
46 | function initGraph () |
---|
47 | { |
---|
48 | graph_container = $(graph_container_selector); |
---|
49 | |
---|
50 | fillOpts(navi_container_selector); |
---|
51 | |
---|
52 | $('#infovis-wrapper').resizable( { |
---|
53 | start: function(event, ui) { |
---|
54 | graph_container.hide(); |
---|
55 | }, |
---|
56 | stop: function(event, ui) { |
---|
57 | graph_container.show(); |
---|
58 | renderGraph(); |
---|
59 | } |
---|
60 | } |
---|
61 | ); |
---|
62 | |
---|
63 | |
---|
64 | $("#navigate .slider").slider(); |
---|
65 | |
---|
66 | // load data |
---|
67 | d3.json(source_file , |
---|
68 | function(json) { |
---|
69 | // return if data missing |
---|
70 | if (json==null) { notify("source data missing: " + source_file ); return null} |
---|
71 | data_all = json; |
---|
72 | data_all.links.forEach(function(d) { |
---|
73 | //resolve numeric index to node references |
---|
74 | src_ix = d.source; |
---|
75 | d.source = data_all.nodes[src_ix]; |
---|
76 | d.source.index = src_ix; |
---|
77 | trg_ix = d.target; |
---|
78 | d.target = data_all.nodes[trg_ix]; |
---|
79 | d.target.index = trg_ix; |
---|
80 | src_key = d.source.key; |
---|
81 | trg_key = d.target.key; |
---|
82 | }); |
---|
83 | // generate lookup hashes for neighbours; |
---|
84 | add_lookups(data_all); |
---|
85 | |
---|
86 | // should be delivered by the data directly |
---|
87 | data_all.nodes.forEach(function(d,i) { |
---|
88 | d.x = d.init_x; |
---|
89 | d.y = d.init_y; |
---|
90 | }); |
---|
91 | |
---|
92 | renderIndex(); |
---|
93 | |
---|
94 | }); |
---|
95 | } |
---|
96 | |
---|
97 | /** put grouped list of nodes into the target container*/ |
---|
98 | function renderIndex () { |
---|
99 | renderNodeList (data_all.nodes, index_container_selector) |
---|
100 | } |
---|
101 | |
---|
102 | |
---|
103 | /** generate the detail lists |
---|
104 | @param nodes |
---|
105 | */ |
---|
106 | function renderDetail (nodes) { |
---|
107 | renderNodeList (nodes, detail_container_selector); |
---|
108 | } |
---|
109 | |
---|
110 | |
---|
111 | /** generate a grouped (by type) list of nodes |
---|
112 | @param nodes - accepts an array of nodes (like in data.nodes) |
---|
113 | */ |
---|
114 | function renderNodeList (nodes, target_container_selector) { |
---|
115 | |
---|
116 | nest = d3.nest() |
---|
117 | .key(function(d) { return d.type; }) |
---|
118 | .sortValues(function(a, b) { return d3.ascending(a.name, b.name); }) |
---|
119 | .entries(nodes); |
---|
120 | |
---|
121 | target_container = d3.select(target_container_selector); |
---|
122 | target_container.selectAll("div").remove(); |
---|
123 | |
---|
124 | |
---|
125 | |
---|
126 | var group_divs = target_container.selectAll("div").data(nest) |
---|
127 | .enter().append("div") |
---|
128 | .attr("id", function (d) { return "detail-" + d.key }) |
---|
129 | .classed("cmds-ui-block init-show", 1); |
---|
130 | |
---|
131 | var group_headers = group_divs.append("div").classed("header", 1) |
---|
132 | .text(function (d) { return d.key + " |" + d.values.length + "|"}); |
---|
133 | |
---|
134 | var list = group_divs.append("div").classed("content",1) |
---|
135 | .append("ul"); |
---|
136 | var item_li = list.selectAll(".node-item") |
---|
137 | .data(function(d) { return d.values; }) |
---|
138 | .enter().append("li") |
---|
139 | .attr("class", "node-item") |
---|
140 | .text(function (d) { return d.name}) |
---|
141 | .on("click", function(d) { d.selected= d.selected ? 0 : 1 ; updateSelected() }); |
---|
142 | |
---|
143 | |
---|
144 | /* slightly different behaviour for the main-index and rendering of the selected nodes in the detail-view */ |
---|
145 | // console.log("target_container:" + target_container_selector); |
---|
146 | if (target_container_selector == index_container_selector) { |
---|
147 | index_container = target_container; |
---|
148 | item_li.attr("id", function (d) { return "n-" + d.name }); |
---|
149 | item_li.classed("highlight", function (d) { return d.selected }); |
---|
150 | |
---|
151 | } else { |
---|
152 | var item_detail = item_li.append("div") |
---|
153 | .classed("node-detail", 1); |
---|
154 | |
---|
155 | item_detail.append("a") |
---|
156 | .attr("href",function (d) { if (d.type.toLowerCase()=='datcat') return d.id |
---|
157 | else return comp_reg_url + d.id }) |
---|
158 | .text(function (d) { return d.id }); |
---|
159 | item_detail.append("div").html( |
---|
160 | function (d) { |
---|
161 | var detail_info_div = getDetailInfo(d.type.toLowerCase(), d.key); |
---|
162 | if (detail_info_div) { |
---|
163 | return detail_info_div |
---|
164 | } else { |
---|
165 | return "<div>No detail</div>"; |
---|
166 | } |
---|
167 | }); |
---|
168 | |
---|
169 | } |
---|
170 | |
---|
171 | handleUIBlock($(target_container_selector).find(".cmds-ui-block")); |
---|
172 | |
---|
173 | } |
---|
174 | |
---|
175 | function filterIndex (search_string){ |
---|
176 | var filtered_index_nodes = data_all.nodes.filter(function(d, i) { |
---|
177 | // console.log(d.name.indexOf(search_string)); |
---|
178 | return d.name.toLowerCase().indexOf(search_string) > -1; |
---|
179 | }); |
---|
180 | |
---|
181 | renderIndex(filtered_index_nodes, index_container_selector); |
---|
182 | } |
---|
183 | |
---|
184 | |
---|
185 | function renderGraph () { |
---|
186 | renderGraph(data_show, graph_container); |
---|
187 | } |
---|
188 | |
---|
189 | /** render the data as graph into target-container */ |
---|
190 | function renderGraph (data, target_container=graph_container) { |
---|
191 | |
---|
192 | data = dataToShow(nodes_sel); |
---|
193 | |
---|
194 | if (data == null) { |
---|
195 | $(target_container).text("no data to show"); |
---|
196 | return; |
---|
197 | } else { |
---|
198 | $(target_container).text(""); |
---|
199 | } |
---|
200 | |
---|
201 | // information about the displayed data |
---|
202 | notify("show nodes: " + data_show.nodes.length + "; " |
---|
203 | + "show links: " + data_show.links.length); |
---|
204 | |
---|
205 | |
---|
206 | |
---|
207 | |
---|
208 | var w = $(target_container).width(), |
---|
209 | h = $(target_container).height(); |
---|
210 | |
---|
211 | |
---|
212 | // console.log (w + '-' + h); |
---|
213 | var force = d3.layout.force() |
---|
214 | .nodes(data.nodes) |
---|
215 | .links(data.links) |
---|
216 | .size([w, h]) |
---|
217 | // .gravity(0.3) |
---|
218 | .linkDistance(parseInt(opt("link-distance"))) |
---|
219 | .charge(parseInt(opt("charge")) * -1) |
---|
220 | .on("tick", tick) |
---|
221 | .start(); |
---|
222 | // console.log ("gravity: " + force.gravity() ); |
---|
223 | |
---|
224 | // remove old render: |
---|
225 | d3.select(graph_container_selector).selectAll("svg").remove(); |
---|
226 | |
---|
227 | svg = d3.select(graph_container_selector).append("svg:svg") |
---|
228 | .attr("width", w) .attr("height", h); |
---|
229 | |
---|
230 | // Per-type markers, as they don't inherit styles. |
---|
231 | svg.append("svg:defs").selectAll("marker") |
---|
232 | .data(["uses"]) |
---|
233 | .enter().append("svg:marker") |
---|
234 | .attr("id", String) |
---|
235 | .attr("viewBox", "0 -5 10 10") |
---|
236 | .attr("refX", 15) |
---|
237 | .attr("refY", -1.5) |
---|
238 | .attr("markerWidth", 6) |
---|
239 | .attr("markerHeight", 6) |
---|
240 | .attr("orient", "auto") |
---|
241 | .append("svg:path") |
---|
242 | .attr("d", "M0,-3L10,0L0,3"); |
---|
243 | |
---|
244 | var path = svg.append("svg:g").selectAll("path") |
---|
245 | .data(force.links()) |
---|
246 | .enter().append("svg:path") |
---|
247 | /* .attr("class", function(d) { return "link uses"; })*/ |
---|
248 | .classed("link", 1) |
---|
249 | .classed("uses", 1) |
---|
250 | .classed("highlight", function(d) { d.highlight } ) |
---|
251 | .attr("marker-end", function(d) { return "url(#uses)"; }); |
---|
252 | /* .style("stroke-width", function(d) { return Math.sqrt(d.value); });*/ |
---|
253 | |
---|
254 | |
---|
255 | |
---|
256 | var circle = svg.append("svg:g") |
---|
257 | .selectAll("circle") |
---|
258 | .data(force.nodes()) |
---|
259 | .enter().append("svg:circle") |
---|
260 | /* .attr("r", 6)*/ |
---|
261 | .attr("r", function(d) { if (opt("node-weight")=="1"){ return min_circle } |
---|
262 | else {return (Math.sqrt(d.count)>min_circle ? Math.sqrt(d.count) * 2 : min_circle); } }) |
---|
263 | /* .attr("x", function(d) {return d.x;}) |
---|
264 | .attr("y", function(d) {return d.y;})*/ |
---|
265 | .call(force.drag); |
---|
266 | |
---|
267 | circle.append("title") |
---|
268 | .text(function(d) { return d.name; }); |
---|
269 | |
---|
270 | svg.selectAll("circle") |
---|
271 | .attr("class", function(d) { return "type-" + d.type.toLowerCase()}) |
---|
272 | .classed("selected", function(d) { return d.selected; }) |
---|
273 | .on("click", function(d) {d.selected= d.selected ? 0 : 1; updateSelected() }) |
---|
274 | .on("mouseover", highlight("in")).on("mouseout", highlight("out")); |
---|
275 | |
---|
276 | /* |
---|
277 | var textgroup = svg.append("svg:g").selectAll("g") |
---|
278 | .data(data.nodes) |
---|
279 | .enter().append("svg:g") |
---|
280 | .attr("class", function(d) { return "type-" + d.type.toLowerCase()}) |
---|
281 | .on("click", function(d) {d.selected= d.selected ? 0 : 1; updateSelected() }); |
---|
282 | |
---|
283 | |
---|
284 | textgroup.attr("data-key", function (d) { return d.name } ); |
---|
285 | |
---|
286 | // A copy of the text with a thick white stroke for legibility. |
---|
287 | textgroup.append("svg:text") |
---|
288 | .attr("x", 8) |
---|
289 | .attr("y", ".31em") |
---|
290 | .attr("class", "shadow") |
---|
291 | |
---|
292 | .text(function(d) { return d.name; }); |
---|
293 | |
---|
294 | textgroup.append("svg:text") |
---|
295 | .attr("x", 8) |
---|
296 | .attr("y", ".31em") |
---|
297 | .text(function(d) { return d.name; }); |
---|
298 | */ |
---|
299 | |
---|
300 | function tick(e) { |
---|
301 | var link_distance_int = parseInt(opt("link-distance")); |
---|
302 | var k = 10 * e.alpha; |
---|
303 | if (opt("layout")=='dot') { |
---|
304 | data.links.forEach(function(d, i) { |
---|
305 | d.source.x = (d.source.init_x / 150 * link_distance_int) ; |
---|
306 | d.target.x = (d.target.init_x / 150 * link_distance_int); |
---|
307 | |
---|
308 | }); |
---|
309 | } else if (opt("layout")=='weak-tree') { |
---|
310 | data.links.forEach(function(d, i) { |
---|
311 | d.source.x -= k; |
---|
312 | d.target.x += k; |
---|
313 | }); |
---|
314 | } else if (opt("layout")=='vertical-tree') { |
---|
315 | var ky= 1.4 * e.alpha, kx = .4 * e.alpha; |
---|
316 | data.links.forEach(function(d, i) { |
---|
317 | if (d.source.level==0) { d.source.y = 20 }; |
---|
318 | d.target.x += (d.source.x - d.target.x) * kx; |
---|
319 | d.target.y += (d.source.y - d.target.y + link_distance_int) * ky; |
---|
320 | }); |
---|
321 | } else if (opt("layout")=='horizontal-tree') { |
---|
322 | var kx= 1.4 * e.alpha, ky = .4 * e.alpha; |
---|
323 | data.links.forEach(function(d, i) { |
---|
324 | if (d.source.level==0) { d.source.x = 20 }; |
---|
325 | d.target.y += (d.source.y - d.target.y) * ky; |
---|
326 | d.target.x += (d.source.x - d.target.x + link_distance_int ) * kx; |
---|
327 | }); |
---|
328 | } |
---|
329 | |
---|
330 | /* parent foci |
---|
331 | var kx = 1.2 * e.alpha; |
---|
332 | data.links.forEach(function(d, i) { |
---|
333 | d.target.x += (d.target.level * link_distance - d.target.x) * kx; |
---|
334 | });*/ |
---|
335 | |
---|
336 | |
---|
337 | transform(); |
---|
338 | } // end tick() |
---|
339 | |
---|
340 | |
---|
341 | function transform () { |
---|
342 | |
---|
343 | path.attr("d", function(d) { |
---|
344 | // links as elliptical arc path segments |
---|
345 | if (opt("curve")=="arc") |
---|
346 | { var dx = d.target.x - d.source.x, |
---|
347 | dy = d.target.y - d.source.y, |
---|
348 | dr = Math.sqrt(dx * dx + dy * dy); |
---|
349 | return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y; |
---|
350 | } else { |
---|
351 | // or straight |
---|
352 | return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y; |
---|
353 | } |
---|
354 | }); |
---|
355 | |
---|
356 | circle.attr("cx", function(d) {return d.x;}) |
---|
357 | .attr("cy", function(d) {return d.y;}); |
---|
358 | /* circle.attr("transform", function(d) { |
---|
359 | return "translate(" + d.x + "," + d.y + ")"; |
---|
360 | });*/ |
---|
361 | |
---|
362 | /* textgroup.attr("transform", function(d) { |
---|
363 | return "translate(" + d.x + "," + d.y + ")"; |
---|
364 | });*/ |
---|
365 | } |
---|
366 | |
---|
367 | // Highlight selected nodes using the quadtree. |
---|
368 | svg.on("mousedown", function() { |
---|
369 | var m0 = d3.mouse(this); |
---|
370 | |
---|
371 | var rect = d3.select(this).append("rect") |
---|
372 | .style("fill", "#999") |
---|
373 | .style("fill-opacity", .5); |
---|
374 | |
---|
375 | d3.select(window).on("mousemove", function() { |
---|
376 | var m1 = d3.mouse(rect.node()), |
---|
377 | x0 = Math.min(w, m0[0], m1[0]), |
---|
378 | y0 = Math.min(w, m0[1], m1[1]), |
---|
379 | x1 = Math.max(0, m0[0], m1[0]), |
---|
380 | y1 = Math.max(0, m0[1], m1[1]); |
---|
381 | // console.log("DEBUG: mousedown: " + (x1-x0) + ( y1-y0)); |
---|
382 | selectNodes(data.nodes, x0, y0, x1, y1); |
---|
383 | rect.attr("x", x0).attr("y", y0).attr("width", x1 - x0).attr("height", y1 - y0); |
---|
384 | |
---|
385 | }); |
---|
386 | |
---|
387 | d3.select(window).on("mouseup", function() { |
---|
388 | // only change selection, if the rectangle was big enough |
---|
389 | // (mainly to prevent clearing of the graph on clicks that look like mousemoves to the system) |
---|
390 | if (rect.attr("width") > select_rect_min_size && rect.attr("height") > select_rect_min_size) { |
---|
391 | updateSelected(); |
---|
392 | } |
---|
393 | rect.remove(); |
---|
394 | d3.select(window).on("mousemove", null).on("mouseup", null); |
---|
395 | }); |
---|
396 | |
---|
397 | d3.event.preventDefault(); |
---|
398 | }); |
---|
399 | } // end renderGraph |
---|
400 | |
---|
401 | |
---|
402 | /** loads detail info about individual nodes (in html) from separate file |
---|
403 | later used in renderDetail() |
---|
404 | invoked during the (jquery-)initalization */ |
---|
405 | function loadDetailInfo () { |
---|
406 | |
---|
407 | $(detail_info_holder_selector).load(detail_file,function(data) { |
---|
408 | $(detail_container_selector).html(getDetailInfo("summary", "overall")); |
---|
409 | }); |
---|
410 | } |
---|
411 | |
---|
412 | function getDetailInfo(type, id) { |
---|
413 | //notify("getDetailInfo: #" + type + "-" + id ); |
---|
414 | var d = $(detail_info_holder_selector).find("#" + type + "-" + id ); |
---|
415 | // notify(d); |
---|
416 | return d.html(); |
---|
417 | } |
---|
418 | |
---|
419 | |
---|
420 | /** select the nodes within the specified rectangle. */ |
---|
421 | function selectNodes(nodes, x0, y0, x3, y3) { |
---|
422 | |
---|
423 | var points = []; |
---|
424 | nodes.forEach(function(n) { |
---|
425 | if (n && (n.x >= x0) && (n.x < x3) && (n.y >= y0) && (n.y < y3)) { |
---|
426 | points.push(n); |
---|
427 | n.selected = 1; |
---|
428 | } else { |
---|
429 | n.selected = 0; |
---|
430 | } |
---|
431 | /* return x1 >= x3 || y1 >= y3 || x2 < x0 || y2 < y0;*/ |
---|
432 | }); |
---|
433 | return points; |
---|
434 | } |
---|
435 | |
---|
436 | |
---|
437 | function updateSelected () { |
---|
438 | nodes_sel = data_all.nodes.filter(function (d) { return d.selected }); |
---|
439 | |
---|
440 | renderGraph(); |
---|
441 | renderDetail(nodes_sel); |
---|
442 | |
---|
443 | // index_container.selectAll("li").classed("highlight", function (d) { return d.selected }); |
---|
444 | } |
---|
445 | |
---|
446 | |
---|
447 | // Returns an event handler for fading a given chord group. |
---|
448 | function highlight() { |
---|
449 | return function(d, i) { |
---|
450 | // console.log ("fade:" + d.key); |
---|
451 | var connected_subgraph_in = neighboursWithLinks(data_show, d,'in', -1); |
---|
452 | var connected_subgraph_out = neighboursWithLinks(data_show, d,'out', -1); |
---|
453 | var connected_subgraph = {"nodes": [], "links": []}; |
---|
454 | connected_subgraph.nodes = connected_subgraph.nodes.concat(connected_subgraph_in.nodes).concat(connected_subgraph_out.nodes); |
---|
455 | connected_subgraph.links = connected_subgraph.links.concat(connected_subgraph_in.links).concat(connected_subgraph_out.links); |
---|
456 | add_lookups(connected_subgraph); |
---|
457 | svg.selectAll("path.link") |
---|
458 | /* .filter( d.source.index != i && d.target.index != i; })*/ |
---|
459 | /* .transition()*/ |
---|
460 | .classed("highlight", function(p) { return connected_subgraph.links_index[p.source.key + ',' + p.target.key] }) |
---|
461 | .classed("fade", function(p) { return !(connected_subgraph.links_index[p.source.key + ',' + p.target.key]) }); |
---|
462 | |
---|
463 | svg.selectAll("circle") |
---|
464 | /* .filter( d.source.index != i && d.target.index != i; })*/ |
---|
465 | /* .transition()*/ |
---|
466 | .classed("highlight", function(d) { return connected_subgraph.nodes_in[d.key] || connected_subgraph.nodes_out[d.key] }) |
---|
467 | .classed("fade", function(d) { return !(connected_subgraph.nodes_in[d.key] || connected_subgraph.nodes_out[d.key]) }); |
---|
468 | |
---|
469 | }; |
---|
470 | } |
---|
471 | |
---|
472 | |
---|
473 | /** generates the subset of data to display (based on selected nodes + options) |
---|
474 | fills global variable: data_show ! |
---|
475 | */ |
---|
476 | function dataToShow (nodes) { |
---|
477 | data_show = {}; |
---|
478 | data_show.nodes = nodes; |
---|
479 | var data_show_collect = {nodes:[],links:[]}; |
---|
480 | |
---|
481 | nodes.forEach(function(n) { |
---|
482 | var data_add_in = neighboursWithLinks(data_all, n,'in', opt("depth-before")); |
---|
483 | var data_add_out = neighboursWithLinks(data_all, n,'out', opt("depth-after")); |
---|
484 | data_show_collect.nodes = data_show_collect.nodes.concat(data_add_in.nodes).concat(data_add_out.nodes); |
---|
485 | data_show_collect.links = data_show_collect.links.concat(data_add_in.links).concat(data_add_out.links); |
---|
486 | }); |
---|
487 | |
---|
488 | /* deduplicate nodes and edges */ |
---|
489 | data_show.nodes = unique_nodes(nodes.concat(data_show_collect.nodes)); |
---|
490 | data_show.links = unique_links(data_show_collect.links); |
---|
491 | |
---|
492 | // extend the object, with some lookup hashes on neighbourhood |
---|
493 | add_lookups(data_show); |
---|
494 | |
---|
495 | return data_show; |
---|
496 | } |
---|
497 | |
---|
498 | /** generate lookup hashes for neighbours; |
---|
499 | for faster/simpler neighborhood lookup |
---|
500 | from: http://stackoverflow.com/questions/8739072/highlight-selected-node-its-links-and-its-children-in-a-d3-js-force-directed-g |
---|
501 | */ |
---|
502 | function add_lookups(data) { |
---|
503 | |
---|
504 | var links = data.links; |
---|
505 | var neighbours = {"links_index": {}, |
---|
506 | "nodes_in": {}, "nodes_out": {}, |
---|
507 | "links_in": {}, "links_out": {}}; |
---|
508 | |
---|
509 | links.forEach(function(d) { |
---|
510 | src_key = d.source.key; |
---|
511 | trg_key = d.target.key; |
---|
512 | // generate lookup hashes for neighbours; |
---|
513 | neighbours.links_index[src_key + "," + trg_key] = d; |
---|
514 | if (d.source) { |
---|
515 | if (! neighbours.nodes_in[trg_key]) { |
---|
516 | neighbours.nodes_in[trg_key] = [d.source]; |
---|
517 | neighbours.links_in[trg_key] = [d]; |
---|
518 | } else { |
---|
519 | neighbours.nodes_in[trg_key].push(d.source); |
---|
520 | neighbours.links_in[trg_key].push(d); |
---|
521 | } |
---|
522 | } |
---|
523 | if (d.target) { |
---|
524 | if (! neighbours.nodes_out[src_key]) { |
---|
525 | neighbours.nodes_out[src_key] = [d.target]; |
---|
526 | neighbours.links_out[src_key] = [d]; |
---|
527 | } else { |
---|
528 | neighbours.nodes_out[src_key].push(d.target); |
---|
529 | neighbours.links_out[src_key].push(d) ; |
---|
530 | } |
---|
531 | } |
---|
532 | }); |
---|
533 | |
---|
534 | data = $.extend(data, neighbours); |
---|
535 | return data; |
---|
536 | } |
---|
537 | |
---|
538 | |
---|
539 | |
---|
540 | /* item_detail.text(function (d) { return "links_in: " + dataShowCount(d.key, "links_in") + "; links_out: " + dataShowCount(d.key, "links_out") ; |
---|
541 | })*/ |
---|
542 | function dataShowCount(n_key, info_type) { |
---|
543 | |
---|
544 | if (data_show[info_type][n_key]) { |
---|
545 | return data_show[info_type][n_key].length; |
---|
546 | } else { |
---|
547 | return 0 |
---|
548 | } |
---|
549 | } |
---|
550 | |
---|
551 | /** returns appropriate link |
---|
552 | */ |
---|
553 | function neighboring(a, b) { |
---|
554 | return linkedByIndex[a.index + "," + b.index]; |
---|
555 | } |
---|
556 | |
---|
557 | |
---|
558 | function neighboursWithLinks (n, dir, depth=1) { |
---|
559 | return neighboursWithLinks (data_all, n, dir, depth); |
---|
560 | } |
---|
561 | /** access function to retrieve the neighbours from the hashes |
---|
562 | @param dir in|out|any - but "any" branches in unexpected ways (because it goes in and out on every level = it takes all the children of the parent) |
---|
563 | @param depth 0-n - go depth-levels; negative depth := no depth restriction = go to the end of the paths; |
---|
564 | @returns a sub-graph |
---|
565 | */ |
---|
566 | function neighboursWithLinks (data, n, dir, depth=1) { |
---|
567 | if (depth==0) { return {nodes:[], links:[]};} |
---|
568 | |
---|
569 | var n_in = data.nodes_in[n.key] ? data.nodes_in[n.key] : [] ; |
---|
570 | var n_out = data.nodes_out[n.key] ? data.nodes_out[n.key] : [] ; |
---|
571 | var l_in = data.links_in[n.key] ? data.links_in[n.key] : [] ; |
---|
572 | var l_out = data.links_out[n.key] ? data.links_out[n.key] : [] ; |
---|
573 | |
---|
574 | var result_n = {nodes:[], links:[]}; |
---|
575 | if (dir == 'in' ) { result_n.nodes = n_in; result_n.links = l_in; } |
---|
576 | else if (dir == 'out' ) { result_n.nodes = n_out; result_n.links = l_out; } |
---|
577 | else { result_n.nodes = n_out.concat(n_in); result_n.links = l_out.concat(l_in); } |
---|
578 | var n_nextlevel = {nodes:[], links:[]}; |
---|
579 | if (depth > 0 || depth < 0) { |
---|
580 | result_n.nodes.forEach (function(n) |
---|
581 | { var n_neighbours = neighboursWithLinks(data, n, dir, depth - 1); |
---|
582 | n_nextlevel.nodes = n_nextlevel.nodes.concat(n_neighbours.nodes); |
---|
583 | n_nextlevel.links = n_nextlevel.links.concat(n_neighbours.links); |
---|
584 | }) |
---|
585 | } |
---|
586 | result_n.nodes = result_n.nodes.concat(n_nextlevel.nodes); |
---|
587 | result_n.links = result_n.links.concat(n_nextlevel.links); |
---|
588 | |
---|
589 | return result_n; |
---|
590 | |
---|
591 | } |
---|
592 | |
---|
593 | /** deduplicates based on index-property */ |
---|
594 | function unique_nodes(nodes) |
---|
595 | { |
---|
596 | var hash = {}, result = []; |
---|
597 | for ( var i = 0, l = nodes.length; i < l; ++i ) { |
---|
598 | n_key = nodes[i].key; |
---|
599 | if ( !hash[n_key] ) { //it works with objects! in FF, at least |
---|
600 | hash[ n_key ] = true; |
---|
601 | result.push(nodes[i]); |
---|
602 | } |
---|
603 | } |
---|
604 | return result; |
---|
605 | } |
---|
606 | |
---|
607 | /** deduplicates links (based on source-target-index |
---|
608 | based on: http://stackoverflow.com/questions/1890203/unique-for-arrays-in-javascript |
---|
609 | */ |
---|
610 | function unique_links(links) |
---|
611 | { |
---|
612 | var hash = {}, result = []; |
---|
613 | for ( var i = 0, l = links.length; i < l; ++i ) { |
---|
614 | src_key = links[i].source.key; |
---|
615 | trg_key = links[i].target.key; |
---|
616 | key = src_key + "," + trg_key; |
---|
617 | if ( !hash[key] ) { |
---|
618 | hash[ key] = true; |
---|
619 | result.push(links[i]); |
---|
620 | } |
---|
621 | } |
---|
622 | return result; |
---|
623 | } |
---|
624 | |
---|
625 | /** |
---|
626 | gets an option, checking with the values in the navigation-UI |
---|
627 | */ |
---|
628 | function opt(key) { |
---|
629 | |
---|
630 | if ($('#' + input_prefix + key) && (opts[key].value != $('#' + input_prefix + key).val())) { |
---|
631 | opts[key].value = $('#' + input_prefix + key).val(); |
---|
632 | } else if (opts[key].value) { |
---|
633 | return opts[key].value |
---|
634 | } else if (opts[key]) { |
---|
635 | return opts[key] |
---|
636 | } else { |
---|
637 | return "" |
---|
638 | } |
---|
639 | } |
---|
640 | |
---|
641 | function setOpt(input_object) { |
---|
642 | |
---|
643 | var id = $(input_object).attr("id"); |
---|
644 | var val = $(input_object).val(); |
---|
645 | key = id.substring(id.indexOf(input_prefix) + input_prefix.length) |
---|
646 | opts[key].value = val; |
---|
647 | return opts[key].value; |
---|
648 | } |
---|
649 | |
---|
650 | |
---|
651 | function fillOpts(trg_container) { |
---|
652 | |
---|
653 | for ( var key in opts ) { |
---|
654 | if ($('#' + input_prefix + key).length) { |
---|
655 | $('#' + input_prefix + key).value = opts[key].value; |
---|
656 | } else if (trg_container) { |
---|
657 | var new_input_label = "<label>" + key + "</label>"; |
---|
658 | var new_input; |
---|
659 | |
---|
660 | if (opts[key].widget == "slider") { |
---|
661 | [new_input,new_widget] = genSlider(key, opts[key].values); |
---|
662 | } else if (opts[key].widget =="selectone") { |
---|
663 | [new_input,new_widget] = genCombo(key, opts[key].values); |
---|
664 | // set initial value |
---|
665 | $(new_input).val(opts[key].value); |
---|
666 | |
---|
667 | // $(new_input).autocomplete({"source":opts[key].values}); |
---|
668 | } |
---|
669 | |
---|
670 | /* hook changing options + redrawing the graph, when values in navigation changed */ |
---|
671 | new_input.change(function () { |
---|
672 | setOpt(this); |
---|
673 | var related_widget = $(this).data("related-widget"); |
---|
674 | if ( $(related_widget).hasClass("widget-slider")) {$(related_widget).slider("option", "value", $(this).val()); } |
---|
675 | renderGraph(); |
---|
676 | }); |
---|
677 | |
---|
678 | $(trg_container).append(new_input_label, new_input, new_widget); |
---|
679 | |
---|
680 | } |
---|
681 | } |
---|
682 | |
---|
683 | |
---|
684 | } |
---|
685 | |
---|
686 | /** generating my own comboboxes, because very annoying trying to use some of existing jquery plugins (easyui.combo, combobox, jquery-ui.autocomplete) */ |
---|
687 | function genCombo (key, data) { |
---|
688 | |
---|
689 | var select = $("<select id='widget-" + key + "' />") |
---|
690 | select.attr("id", input_prefix + key) |
---|
691 | data.forEach(function(v) { $(select).append("<option value='" + v +"' >" + v + "</option>") }); |
---|
692 | return [select, null]; |
---|
693 | } |
---|
694 | |
---|
695 | function genSlider (key, data) { |
---|
696 | |
---|
697 | var new_input = $("<input />"); |
---|
698 | new_input.attr("id", input_prefix + key) |
---|
699 | .val(opts[key].value) |
---|
700 | /* .attr("type", "text")*/ |
---|
701 | .attr("size", 3); |
---|
702 | |
---|
703 | var new_widget = $("<div class='widget-" + opts[key].widget + "'></div>"); |
---|
704 | new_widget.attr("id", "widget-" + key); |
---|
705 | new_widget.slider( opts[key]); |
---|
706 | |
---|
707 | // set both-ways references between the input-field and its slider - necessary for updating |
---|
708 | new_widget.data("related-input-field",new_input); |
---|
709 | new_input.data("related-widget",new_widget); |
---|
710 | |
---|
711 | // console.log("widget:" + opts[key].widget); |
---|
712 | |
---|
713 | new_widget.bind( "slidechange", function(event, ui) { |
---|
714 | // console.log(ui.value); |
---|
715 | $(this).data("related-input-field").val(ui.value); |
---|
716 | // update the opts-object, but based on the (updated) value of the related input-field |
---|
717 | setOpt($(this).data("related-input-field")); |
---|
718 | renderGraph(); |
---|
719 | }); |
---|
720 | |
---|
721 | return [new_input,new_widget]; |
---|
722 | } |
---|
723 | |
---|
724 | |
---|
725 | function notify (msg) { |
---|
726 | $("#notify").append(msg); |
---|
727 | } |
---|