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