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