source: SMC/trunk/SMC/src/web/scripts/js/cmd-dep-graph.js @ 3431

Last change on this file since 3431 was 3431, checked in by vronk, 11 years ago

added external links to profiles, enhance init steps, add function: selectNodeByKey

File size: 37.1 KB
Line 
1
2
3var item_li = null;
4
5var svg  = null; // main svg-element
6var css; 
7var data_all = null; // global holder for all (input) data
8var nodes_sel = []; // global holder for selected data (selected nodes)
9var data_show = null; // global holder for data to show  closure over nodes_sel
10var nest = {}; 
11var detail_data = null; // global holder for detail-data (in html) 
12 
13
14var input_prefix = "input-";
15var select_rect_min_size = 5;
16var first_level_margin = 20;
17var min_circle = 4;
18var max_circle = 50;
19var comp_reg_url = "http://catalog.clarin.eu/ds/ComponentRegistry/?item=";
20var mdrepo_url_search = "http://localhost:8680/exist/apps/cr-xq/mdrepo/index.html?operation=searchRetrieve&x-context=&query=";
21/*http://localhost:8680/exist/apps/cr-xq/mdrepo/fcs?operation=scan&scanClause=cmd:CountryName&x-context=&x-format=htmlpage*/
22var mdrepo_url_scan = "http://localhost:8680/exist/apps/cr-xq/mdrepo/fcs?operation=scan&x-context=&x-format=htmlpage&sort=size&scanClause=";
23/*var source_file = "../scripts/cmd-dep-graph-d3_all_svg.json"*/
24/*var source_file = "file:/C:/Users/m/3/clarin/_repo/SMC/output/cmd-dep-graph.d3.js"
25var detail_file = "file:/C:/Users/m/3/clarin/_repo/SMC/output/smc_stats_detail.html"
26*/
27/*
28var source_file = "/smc/_structure-graph.json";
29var source_file = "cmd-dep-graph.d3.js";
30var detail_file = "smc_stats_detail.html";
31var userdocs_file = "userdocs.html";
32*/
33var source_file = "/smc/cmd-dep-graph_profiles-datcats.json";
34/*var source_file = "/smc/cmd-dep-graph.d3.js";*/
35var detail_file = "smc_stats_detail.html";
36var userdocs_file = "userdocs.html";
37
38var opts = {"depth-before": {"value":2, "min":0, "max":10, "widget":"slider"}, 
39            "depth-after":{"value":2, "min":0, "max":10, "widget":"slider"}, 
40            "link-distance": {"value":120, "min":10, "max":300, "widget":"slider" }, 
41            "charge":{"value":250, "min":10, "max":1000, "widget":"slider" },
42            "friction":{"value":75, "min":1, "max":100, "widget":"slider" },
43            "node-size": {"value":"4", "values":["1","4","8","16","usage"], "widget":"selectone" },
44            "labels": {"value":"show", "values":["show","hide"], "widget":"selectone" },                         
45            "curve": {"value":"straight", "values":["straight","arc"], "widget":"selectone" },
46           "layout": {"value":"horizontal-tree", "values":["vertical-tree", "horizontal-tree", "weak-tree","force","dot", "freeze"], "widget":"selectone" },
47            "selected": {"widget":"hidden" },
48            "link": {"widget":"link", "label":""},
49            "download": {"widget":"link", "label":""},
50            "add_profile": {"widget":"link", "label":"Add profile", "widget":""}
51            };
52
53
54/** temporary helper function
55to easily get the param-data
56*/
57function opt(key) {
58 var val = $("#navigate").data("qi").getParamValue(key);
59 return typeof val == 'undefined' ? "" : val;
60}
61
62/**  gets the data for the graph and calls rendering of the lists
63 * @name initGraph
64 * @function
65 */
66 function initGraph ()
67    {
68
69     // load data
70     d3.json(source_file , 
71                function(json) {       
72                    // return if data missing
73                    if (json==null) { notifyUser("source data missing: " + source_file ); return null}           
74                    data_all = json;
75                    data_all.links.forEach(function(d) { 
76                                        //resolve numeric index to node references
77                                                src_ix = d.source;
78                                                d.source = data_all.nodes[src_ix];
79                                                d.source.index = src_ix;
80                                                trg_ix = d.target;
81                                                d.target = data_all.nodes[trg_ix];
82                                                d.target.index = trg_ix;
83                                                src_key = d.source.key;
84                                                trg_key = d.target.key;
85                                             });
86                // generate lookup hashes for neighbours;                                             
87                 add_lookups(data_all);
88                 
89                 // get min/max on some properties
90                 var init_x_arr = [];
91                    data_all.nodes.forEach(function(d,i){init_x_arr.push(d.init_x);})
92                 data_all.init_x_min = d3.min(init_x_arr);
93                 data_all.init_x_max = d3.max(init_x_arr);
94
95                 var init_level = [];
96                    data_all.nodes.forEach(function(d,i){init_level.push(+d.level);})
97                    data_all.level_min = d3.min(init_level);
98               
99                 var init_count = [];
100                    data_all.nodes.forEach(function(d,i){init_count.push(+d.count);})
101                 
102                 data_all.count_max = d3.max(init_count);
103                 data_all.node_size_ratio = Math.sqrt(data_all.count_max) / max_circle;
104
105                notifyUser("count max: " + data_all.count_max + "; "
106                        + "node_size_ratio: " + data_all.node_size_ratio);
107                 
108                 // should be delivered by the data directly
109                   data_all.nodes.forEach(function(d,i) {
110                       d.x = d.init_x;
111                       d.y = d.init_y;
112                     });
113
114                  // get selected nodes (if any) from param
115                  selected_ids = opt("selected").split(",");
116                  selectNodeByKey(selected_ids);
117                  /*var selected_match = 0;
118                    for (var i = 0; i < selected_ids.length; i++)
119                    {  if (data_all.nodes_index[selected_ids[i]]) {
120                             data_all.nodes_index[selected_ids[i]].selected = 1;
121                             selected_match ++;
122                       }
123                    }   
124                  // if something was selected, update and render Graph and Detail
125                  if (selected_match) { updateSelected();}
126                  */ 
127              renderIndex();
128   
129               
130                });       
131}
132
133/** put grouped list of nodes into the target container*/
134function renderIndex (data, target_container_selector) {
135    data = typeof data !== 'undefined' ? data : data_all.nodes;
136    target_container_selector = typeof target_container_selector !== 'undefined' ? target_container_selector : index_container_selector;
137    renderNodeList (data, target_container_selector); 
138}
139
140
141/** generate the detail lists
142    @param nodes
143*/ 
144function renderDetail (nodes) {   
145    renderNodeList (nodes, detail_container_selector); 
146}
147
148
149
150/** generate a grouped (by type) list of nodes
151    @param nodes - accepts an array of nodes (like in data.nodes)
152*/ 
153function renderNodeList (nodes, target_container_selector) {
154
155    nest = d3.nest()
156    .key(function(d) { return d.type; })
157    .sortValues(function(a, b) { return d3.ascending(a.name, b.name); })
158    .entries(nodes);
159 
160    target_container = d3.select(target_container_selector);
161        target_container.selectAll("div.node-detail").remove();
162       
163        var group_divs = target_container.selectAll("div.node-detail").data(nest)
164                        .enter().append("div")
165                        .attr("id", function (d) { return "detail-" + d.key })
166                        .classed("node-detail cmds-ui-block init-show", 1);
167                       
168      var group_headers = group_divs.append("div").classed("header", 1)
169                        .text(function (d) { return d.key + " |" + d.values.length + "|"});
170                       
171      var list =  group_divs.append("div").classed("content",1)
172                    .append("ul");
173      var item_li = list.selectAll(".node-item")       
174                    .data(function(d) { return d.values; })
175                    .enter().append("li")
176                    .attr("class", "node-item");
177               item_li.append("span")
178                    .text(function (d) { return d.name + ' |' + d.count + '|' })
179                    .on("click", function(d) { d.selected= d.selected ? 0 : 1 ; updateSelected() });
180         
181                   
182            /* slightly different behaviour for the main-index and rendering of the selected nodes in the detail-view */
183          //  console.log("target_container:" + target_container_selector);
184            if (target_container_selector == index_container_selector) {
185                index_container = target_container;
186                item_li.attr("id", function (d) { return "n-" + d.name });
187                item_li.classed("highlight", function (d) { return d.selected });
188/*                item_li.classed("highlight", liveSelected);*/
189               
190               
191              } else {
192                 var item_detail = item_li.append("div");
193                                        /*  .classed("node-detail", 1);*/
194                           
195                 item_detail.append("a")
196                            .attr("href",function (d) { if (d.type.toLowerCase()=='datcat') return d.id 
197                                                        else return comp_reg_url + d.id })
198                            .text(function (d) { return d.id });
199               
200                profile_item_detail = item_detail.filter(function(d, i) { return d.type.toLowerCase()=='profile' }); 
201                profile_item_detail.append("a")
202                            .attr("target",'_blank')
203                            .attr("href",function (d) { return 'profiles/' + d.key + '.html' })
204                            .text(' html-view ');
205               
206/*                profile_item_detail.append("a")*/
207                item_detail.append("a")
208                            .classed("scan", function (d) {  return !(d.type=='Profile') } )
209                            .attr("target",'_blank')
210                            .attr("href",function (d) { if (d.type=='Profile') { return mdrepo_url_search +  'cmd.profile=%22' + d.id + '%22'; }
211                                                          else { return mdrepo_url_scan +  'cmd:' +  d.name; }   }  )
212                            .text(' mdrepo-view ');
213               
214                 item_detail_detail = item_detail.append("div").html(
215                                 function (d) { 
216                                    var detail_info_div = getDetailInfo(d.type.toLowerCase(), d.key);
217                                    if (detail_info_div) {
218                                        return detail_info_div 
219                                    } else { 
220                                        return  "<div>No detail</div>"; 
221                                    }
222                          });
223                         
224                 item_detail_detail.classed("node-detail", 1);
225                           
226              }
227                       
228   handleUIBlock($(target_container_selector).find(".node-detail.cmds-ui-block"));
229 
230}
231
232function filterIndex (search_string){
233    var filtered_index_nodes = data_all.nodes.filter(function(d, i) { 
234     //   console.log(d.name.indexOf(search_string));
235        return d.name.toLowerCase().indexOf(search_string) > -1; 
236    });
237   
238   renderIndex(filtered_index_nodes, index_container_selector);
239}
240
241
242/** render data (data_show) as graph  into target-container (graph_container) */
243function renderGraph (data, target_container) {
244// setting defaults
245// for now, ignore the params, as they are always the same
246    //data = typeof data !== 'undefined' ? data : dataToShow(nodes_sel);
247    data = dataToShow(nodes_sel);
248    target_container = graph_container ;
249   
250    if (data == null) { 
251       $(target_container).text("no data to show"); 
252       return;
253     } else {
254       $(target_container).text("");
255     }
256 
257  // information about the displayed data
258        notifyUser("show nodes: " + data_show.nodes.length + "; "
259                        + "show links: " + data_show.links.length);
260       
261 
262 
263 
264   var w = $(target_container).width(),
265        h = $(target_container).height(); 
266     
267     var ratio = w / (data_all.init_x_max - data_all.init_x_min); 
268    var node_size_int = parseInt(opt("node-size"));
269
270        // console.log (w + '-' + h);
271     var force = d3.layout.force()
272            .nodes(data.nodes)
273            .links(data.links)
274            .size([w, h])
275            //.gravity(0.3)
276            .friction(parseInt(opt("friction")) / 100 )
277            .linkDistance(parseInt(opt("link-distance")))
278            .charge(parseInt(opt("charge")) * -1)
279            .on("tick", tick)
280            .start();
281        if (opt("layout")=='freeze') {
282               data.nodes.forEach(function(d) { d.fixed=true });     
283        } else {
284                 data.nodes.forEach(function(d) { d.fixed=false});
285        }
286               
287           
288//    console.log ("gravity: " + force.gravity() );             
289 
290       // remove old render:
291          d3.select(graph_container_selector).selectAll("svg").remove();
292                 
293        svg = d3.select(graph_container_selector).append("svg:svg")
294            .attr("width", w)        .attr("height", h);
295       
296        // Per-type markers, as they don't inherit styles.
297        svg.append("svg:defs").selectAll("marker")
298          .data(["uses"])
299          .enter().append("svg:marker")
300            .attr("id", String)
301            .attr("viewBox", "0 -5 10 10")
302            .attr("refX", 15)
303            .attr("refY", -1.5)
304            .attr("markerWidth", 6)
305            .attr("markerHeight", 6)
306            .attr("orient", "auto")
307          .append("svg:path")
308            .attr("d", "M0,-3L10,0L0,3");
309       
310        var path = svg.append("svg:g").selectAll("path")
311            .data(force.links())
312            .enter().append("svg:path")
313/*            .attr("class", function(d) { return "link uses"; })*/
314            .classed("link", 1)
315            .classed("uses", 1)
316            .classed("highlight", function(d) { d.highlight } )
317            .attr("marker-end", function(d) { return "url(#uses)"; });
318/*            .style("stroke-width", function(d) { return Math.sqrt(d.value); });*/
319
320           
321         var gnodes = svg.append("svg:g")
322                      .selectAll("g.node")
323                      .data(force.nodes())
324                      .enter().append("g")
325                      .attr("class", function(d) { return "node type-" + d.type.toLowerCase()})
326                      .classed("selected", function(d) { return d.selected; })
327                      .call(force.drag);
328                     
329          // dragging of all selected nodes on freeze layout
330          // this does not work yet
331          /*if (opt("layout")=="freeze") {
332                      gnodes.on("mousedown", function() {
333                            var m0 = d3.mouse(this);
334               
335                       gnodes.on("mousemove", function() {
336                            var m1 = d3.mouse(this),
337                        x0 = Math.min(w, m0[0], m1[0]),
338                        y0 = Math.min(w, m0[1], m1[1]),
339                        x1 = Math.max(0, m0[0], m1[0]),
340                        y1 = Math.max(0, m0[1], m1[1]);
341                        // console.log("DEBUG: mousedown: " + (x1-x0) + ( y1-y0));
342                            x_d = (x1 - x0);
343                            y_d = (y1 - y0);
344                               // y_d = d.y - d.py;
345                               // x_d = d.x - d.px;
346                       
347                            nodes_sel.forEach(function (d) {
348                                d.x += x_d;
349                                d.y += y_d;
350                            });
351                           
352                  });
353               
354                    gnodes.on("mouseup", function() {
355                        gnodes.on("mousemove", null).on("mouseup", null);
356                  });
357               
358                  d3.event.preventDefault();
359                });     
360            }         
361          */ 
362            gnodes.append("svg:circle")
363/*            .attr("r", 6)*/
364                    .on("click", function(d) {d.selected= d.selected ? 0 : 1; updateSelected() })
365                      .on("mouseover", highlight()).on("mouseout", unhighlight())
366            .attr("r", function(d) { if (opt("node-size")=="usage") 
367                                        {return (Math.sqrt(d.count)<=min_circle) ?  min_circle  : Math.sqrt(d.count) / data_all.node_size_ratio;                                       
368                                        }
369                                        else { return node_size_int; }
370                                    })   
371           
372            gnodes.append("title")
373                .text(function (d) { return d.name + ' |' + d.count + '|' })
374/*                  .text(function(d) { return d.name; });*/
375                 
376                 
377     
378         
379        /*
380       svg.selectAll("circle")
381            .attr("class", function(d) { return "type-" + d.type.toLowerCase()})
382            .classed("selected", function(d) { return d.selected; })
383          .on("click", function(d) {d.selected= d.selected ? 0 : 1; updateSelected() })
384          .on("mouseover", highlight("in")).on("mouseout", highlight("out"));
385        */
386       
387        // A copy of the text with a thick white stroke for legibility.
388        //if (opt("labels") =='show') {
389           gnodes.append("svg:text")
390                 .attr("x", 8)
391                 .attr("y", ".31em")
392                 .attr("class", "shadow")
393                 .classed("hide", opt("labels")=='hide')
394                 .text(function(d) { return d.name; });
395           gnodes.append("svg:text")
396                 .attr("x", 8)
397                 .attr("y", ".31em")
398                 .classed("hide", function(d) { return !d.selected && opt("labels")=='hide'})
399                 .text(function(d) { return d.name; });
400        //}
401       
402 
403  var tick_counter=0;
404   function tick(e) {
405    var link_distance_int = parseInt(opt("link-distance"));     
406    var k =  10 * e.alpha;
407          if (opt("layout")=='dot') {
408            var offset = data_all.init_x_min;
409            /*data.links.forEach(function(d, i) {
410              d.source.x = (d.source.init_x / 150 * link_distance_int) ;
411              d.target.x = (d.target.init_x / 150 * link_distance_int);
412           
413            });*/
414           
415            data.nodes.forEach (function(d,i) {
416                d.x = d.init_x * ratio - link_distance_int; 
417            });
418           
419          } else if (opt("layout")=='weak-tree') {
420              data.links.forEach(function(d, i) {
421              d.source.x -= k;
422                d.target.x += k;
423                });
424          } else if (opt("layout")=='vertical-tree') {
425                   var ky= 1.4 * e.alpha, kx = .4 * e.alpha;
426                   data.links.forEach(function(d, i) {
427                     if (d.source.level==data_all.level_min) { d.source.y = first_level_margin };
428                   //  d.target.x += (d.source.x - d.target.x)  * kx;
429                     d.target.y += (d.source.y - d.target.y + link_distance_int) * ky;
430                    });
431           } else if (opt("layout")=='horizontal-tree') {
432                   var kx= 1.4 * e.alpha, ky = .4 * e.alpha;
433                   data.links.forEach(function(d, i) {
434                       if (d.source.level==data_all.level_min) { d.source.x = first_level_margin };
435                       //d.target.y += (d.source.y - d.target.y)  * ky;
436                       d.target.x += (d.source.x - d.target.x + link_distance_int  ) * kx;
437                  });
438           }
439           /*  parent foci
440                  var kx = 1.2 * e.alpha;
441    data.links.forEach(function(d, i) {
442      d.target.x += (d.target.level * link_distance  - d.target.x) * kx;
443      });*/
444     
445      tick_counter ++;   
446       if (tick_counter % 2 == 0) {transform(); }
447   } // end  tick()
448
449
450    function transform () { 
451         
452       path.attr("d", function(d) {
453             // links as elliptical arc path segments
454            if (opt("curve")=="arc") 
455            {   var dx = d.target.x - d.source.x,
456                    dy = d.target.y - d.source.y,
457                    dr = Math.sqrt(dx * dx + dy * dy);
458                return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
459            } else { 
460            // or straight
461                return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
462            } 
463       });
464         
465         /*circle.attr("cx", function(d) {return d.x;})
466                .attr("cy", function(d) {return d.y;});*/
467       gnodes.attr("transform", function(d) {
468            return "translate(" + d.x + "," + d.y + ")";
469       });
470       
471  /*     textgroup.attr("transform", function(d) {
472            return "translate(" + d.x + "," + d.y + ")";
473       });*/
474    }
475       
476        // Highlight selected nodes using the quadtree.
477        svg.on("mousedown", function() {
478          var m0 = d3.mouse(this);
479       
480          var rect = d3.select(this).append("rect")
481              .style("fill", "#999")
482              .style("fill-opacity", .5);
483       
484          d3.select(window).on("mousemove", function() {
485            var m1 = d3.mouse(rect.node()),
486                x0 = Math.min(w, m0[0], m1[0]),
487                y0 = Math.min(w, m0[1], m1[1]),
488                x1 = Math.max(0, m0[0], m1[0]),
489                y1 = Math.max(0, m0[1], m1[1]);
490                // console.log("DEBUG: mousedown: " + (x1-x0) + ( y1-y0));       
491                    selectNodes(data.nodes, x0, y0, x1, y1);                   
492                    rect.attr("x", x0).attr("y", y0).attr("width", x1 - x0).attr("height", y1 - y0);
493                   
494          });
495       
496          d3.select(window).on("mouseup", function() {
497            // only change selection, if the rectangle was big enough
498            // (mainly to prevent clearing of the graph on clicks that look like mousemoves to the system)
499            if (rect.attr("width") > select_rect_min_size && rect.attr("height") > select_rect_min_size) {
500                updateSelected();
501            }
502            rect.remove();
503            d3.select(window).on("mousemove", null).on("mouseup", null);
504          });
505       
506          d3.event.preventDefault();
507        });
508}  // end renderGraph
509
510
511/** loads detail info about individual nodes (in html) from separate file 
512later used in renderDetail()
513invoked during the (jquery-)initalization */
514function loadDetailInfo () {
515     
516  $(detail_info_holder_selector).load(detail_file,function(data) {
517     $(detail_container_selector).find("h3").after(
518     '<div id="detail-summary-overall" class="cmds-ui-block init-show" ><div class="header">Overview</div><div class="content">'
519     + getDetailInfo("summary", "overall") + '</div></div>');
520     
521     handleUIBlock($(detail_container_selector).find(".cmds-ui-block"));
522     
523     // only render detail for initially selected nodes, after the detail info has been loaded
524     if (nodes_sel) { renderDetail(nodes_sel) };
525     
526  });
527 
528  // loading css to store in extra variable, for later use = injecting into exported SVG
529  $.get("scripts/style/cmd-dep-graph.css", function(data) {
530 //  console.log(data)
531    css = data
532  });
533 
534}
535
536function getDetailInfo(type, id) {
537    //notify("getDetailInfo: #" + type + "-" + id );
538    var d = $(detail_info_holder_selector).find("#" + type + "-" + id );
539    // notify(d);
540    return d.html();
541}
542
543/** generates a base64-data encoded url out of the current svg
544does some preprocessing: injects the css and sets the @viewBox, @width and @height attributes to ensure,
545that everything is visible in the exported svg.
546later perhaps even exporting to server, for rendering to PNG, PDF
547http://d3export.cancan.cshl.edu/
548called on mousedown of the download-link, so assumes the <a>-element as this
549*/
550function genDownload (event) {
551//console.log("genDownload:" + this);
552
553    var svg_w = svg.attr("width");
554    var svg_h = svg.attr("height");
555    var bounds = graphBounds();
556    var margin = 30;
557    var link_dist = parseInt(opt("link-distance"));
558   
559    var x, y, w, h;
560    x = Math.floor(bounds["x-min"]) - margin 
561    y = Math.floor(bounds["y-min"]) - margin
562    // add extra space to the right, because of the possible labels
563    w = (bounds["width"] > svg_w) ? bounds["width"] + 2 * margin + link_dist : svg_w + link_dist; 
564    h = (bounds["height"] > svg_h) ? bounds["height"] + 2 * margin : svg_h;
565       
566    var viewBox = x + " " + y + " " + w + " " + h;
567
568  svg.attr("title", "SMC Browser - export")
569        .attr("version", 1.1)
570        .attr("viewBox", viewBox)
571        .attr("width", w)
572        .attr("height", h)
573        .attr("xmlns", "http://www.w3.org/2000/svg");
574      var style = svg.append("style" );
575        style.attr("type", 'text/css');
576        style.text(css);
577
578var html = svg.node().parentNode.innerHTML;
579
580/*    $(html).append("<style type='text/css'><![CDATA[" + css + "]]> </style>" );*/
581     
582       
583    //console.log(html);
584
585    $(this).attr("title", "smc-browser-export.svg")
586      .attr("target", "_blank")
587      .attr("href-lang", "image/svg+xml")
588      .attr("href", "data:image/svg+xml;base64,\n" + btoa(html));
589     
590}
591
592
593/**  select the nodes within the specified rectangle. */
594function selectNodes(nodes, x0, y0, x3, y3) {
595   
596  var points = [];
597  nodes.forEach(function(n) {   
598    if (n && (n.x >= x0) && (n.x < x3) && (n.y >= y0) && (n.y < y3)) {
599            points.push(n);
600            n.selected = 1;
601        } else {
602            n.selected = 0;
603        }
604/*    return x1 >= x3 || y1 >= y3 || x2 < x0 || y2 < y0;*/
605  });
606  return points;
607}
608
609function selectNodeByKey(nodes_keys) {
610
611    // get selected nodes (if any) from param
612      var selected_match = 0;
613        for (var i = 0; i < nodes_keys.length; i++)
614        {  if (data_all.nodes_index[nodes_keys[i]]) {
615                 data_all.nodes_index[nodes_keys[i]].selected = 1;
616                 selected_match ++;
617           }
618        }   
619      // if something was selected, update and render Graph and Detail
620      if (selected_match) { updateSelected();}
621}
622
623function updateSelected () {
624
625    // don't change the selected nodes on freeze-layout
626if (opt("layout")!='freeze') {
627    nodes_sel = data_all.nodes.filter(function (d) { return d.selected });
628   
629    // update param
630      var selected = [];
631      nodes_sel.forEach(function(d) { selected.push(d.key) });
632    $("#navigate").data("qi").setParamValue("selected",selected.join());
633    renderDetail(nodes_sel);
634   
635  }
636   
637    renderGraph();
638   
639 // just need to highlight the selected nodes
640    d3.select(index_container_selector).selectAll("li").classed("highlight", function (d) { return d.selected });
641 //   renderIndex(); - this would be unnecessary and too expensive   
642}
643
644
645/** Returns an event handler for highlighting the path of selected (mouseover) node.
646*/
647function highlight() {
648  return function(d, i) {
649    // console.log ("fade:" + d.key);
650    var connected_subgraph_in = neighboursWithLinks(data_show, d,'in', -1);
651    var connected_subgraph_out = neighboursWithLinks(data_show, d,'out', -1);
652    var connected_subgraph = {"nodes": [], "links": []};
653        connected_subgraph.nodes = connected_subgraph.nodes.concat(connected_subgraph_in.nodes).concat(connected_subgraph_out.nodes);
654        connected_subgraph.links = connected_subgraph.links.concat(connected_subgraph_in.links).concat(connected_subgraph_out.links);
655       add_lookups(connected_subgraph);                 
656    svg.selectAll("path.link")
657/*        .filter( d.source.index != i && d.target.index != i; })*/
658/*      .transition()*/
659        .classed("highlight", function(p) { return connected_subgraph.links_index[p.source.key + ',' + p.target.key] })
660        .classed("fade", function(p) { return !(connected_subgraph.links_index[p.source.key + ',' + p.target.key]) });
661   
662    connected = svg.selectAll("g.node").filter(function(d) { 
663                    return  connected_subgraph.nodes_in[d.key]  || connected_subgraph.nodes_out[d.key] 
664                    })
665                .classed("highlight", 1)
666                .classed("fade", 0);
667               
668    connected.selectAll("text").classed("hide",0);                 
669     not_connected = svg.selectAll("g.node").filter(function(d) { 
670                    return  !(connected_subgraph.nodes_in[d.key]  || connected_subgraph.nodes_out[d.key]) 
671                    })
672                    .classed("highlight",0)
673                    .classed("fade",1);
674    not_connected.selectAll("text").classed("hide",1);                   
675                   
676    /*svg.selectAll("circle")
677/\*        .filter( d.source.index != i && d.target.index != i; })*\/
678/\*      .transition()*\/
679        .classed("highlight", function(d) { return connected_subgraph.nodes_in[d.key]  || connected_subgraph.nodes_out[d.key]  })
680        .classed("fade", function(d) { return !(connected_subgraph.nodes_in[d.key] || connected_subgraph.nodes_out[d.key])   });*/
681/*
682   if (opt("labels") =='show') {
683           gnodes.append("svg:text")
684                 .attr("x", 8)
685                 .attr("y", ".31em")
686                 .attr("class", "shadow")
687                 .text(function(d) { return d.name; });
688           gnodes.append("svg:text")
689                 .attr("x", 8)
690                 .attr("y", ".31em")
691                 .text(function(d) { return d.name; });
692        }
693    */
694  };
695}
696
697function unhighlight() {
698  return function(d, i) {
699                     
700    svg.selectAll("path.link")
701        .classed("highlight", 0 )
702        .classed("fade", 0 );
703   
704     var gnodes = svg.selectAll("g.node")
705        .classed("highlight", 0)
706        .classed("fade", 0);
707       
708        gnodes.selectAll("text").classed("hide",function(d) { return !d.selected && opt("labels")=='hide'});
709       
710  };
711}
712
713
714/**  generates the subset of data to display (based on selected nodes + options)
715fills global variable: data_show !
716*/
717function dataToShow (nodes) {
718     data_show = {};
719     data_show.nodes = nodes;
720        var data_show_collect = {nodes:[],links:[]};
721       
722        nodes.forEach(function(n) {
723                        var data_add_in = neighboursWithLinks(data_all, n,'in', opt("depth-before"));
724                        var data_add_out = neighboursWithLinks(data_all, n,'out', opt("depth-after"));
725                        data_show_collect.nodes = data_show_collect.nodes.concat(data_add_in.nodes).concat(data_add_out.nodes);
726                        data_show_collect.links = data_show_collect.links.concat(data_add_in.links).concat(data_add_out.links);
727                    });
728           
729/*         deduplicate nodes and edges */
730     data_show.nodes = unique_nodes(nodes.concat(data_show_collect.nodes));
731     data_show.links = unique_links(data_show_collect.links);
732     
733     // extend the object, with some lookup hashes on neighbourhood
734     add_lookups(data_show);
735 
736     return data_show;
737    }
738
739/** generate lookup hashes for neighbours;
740    for faster/simpler neighborhood lookup
741from: http://stackoverflow.com/questions/8739072/highlight-selected-node-its-links-and-its-children-in-a-d3-js-force-directed-g
742*/
743function add_lookups(data) {
744
745    var links = data.links;
746    var neighbours = {"links_index": {}, "nodes_index": {}, 
747                      "nodes_in": {}, "nodes_out": {},
748                      "links_in": {}, "links_out": {}};
749       
750        data.nodes.forEach(function(d){
751            neighbours.nodes_index[d.key] = d;
752        });
753       
754        links.forEach(function(d) { 
755                            src_key = d.source.key;
756                            trg_key = d.target.key;
757                         // generate lookup hashes for neighbours;
758                            neighbours.links_index[src_key + "," + trg_key] = d;
759                            if (d.source) { 
760                                    if (! neighbours.nodes_in[trg_key]) { 
761                                        neighbours.nodes_in[trg_key] = [d.source];
762                                        neighbours.links_in[trg_key] = [d];
763                                     }  else {
764                                        neighbours.nodes_in[trg_key].push(d.source);
765                                        neighbours.links_in[trg_key].push(d);
766                                     }
767                             }
768                            if (d.target) { 
769                                    if (! neighbours.nodes_out[src_key]) { 
770                                        neighbours.nodes_out[src_key] = [d.target];
771                                        neighbours.links_out[src_key] = [d];
772                                    } else { 
773                                        neighbours.nodes_out[src_key].push(d.target);
774                                        neighbours.links_out[src_key].push(d) ;
775                                    }
776                             }
777                       });
778                       
779    data = $.extend(data, neighbours);
780    return data; 
781}
782
783
784
785/*                        item_detail.text(function (d) { return "links_in: " +  dataShowCount(d.key, "links_in") +  "; links_out: " +  dataShowCount(d.key, "links_out") ;
786                                                       })*/
787function dataShowCount(n_key, info_type) { 
788
789    if (data_show[info_type][n_key]) {
790        return data_show[info_type][n_key].length;
791       } else {
792        return 0
793       }
794}
795
796/** determines the min/max x/y position of the visible nodes
797 ! neglects the labels!
798 
799 @returns array of [x_min, y_min, x_max, y_max, (x_max - x_min), (y_max - y_min)]
800 */
801function graphBounds () {
802
803var  x_arr = [], y_arr =[];
804
805data_show.nodes.forEach(function(d,i){x_arr.push(d.x); y_arr.push(d.y)})
806
807    x_min = d3.min(x_arr);
808    x_max = d3.max(x_arr);
809    y_min = d3.min(y_arr);
810    y_max = d3.max(y_arr);
811                 
812   return {"x-min": x_min, "y-min": y_min, "x-max": x_max, "x-max": y_max, "width": (x_max - x_min), "height":(y_max - y_min)}
813}                 
814                 
815/** returns appropriate link
816*/
817function neighboring(a, b) {
818  return linkedByIndex[a.index + "," + b.index];
819}
820
821
822/** access function to retrieve the neighbours from the hashes
823@param data base data to search for links (default: data_all)
824@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)
825@param depth 0-n - go depth-levels; negative depth := no depth restriction = go to the end of the paths;
826@returns a sub-graph
827*/
828function neighboursWithLinks (data, n, dir, depth) {
829// setting defaults
830       depth = typeof depth !== 'undefined' ? depth : 1;
831       data = typeof data !== 'undefined' ? data : data_all;
832
833    if (depth==0) { return {nodes:[], links:[]};}
834
835        var n_in = data.nodes_in[n.key] ? data.nodes_in[n.key] : [] ;
836        var n_out = data.nodes_out[n.key] ? data.nodes_out[n.key] : [] ;
837        var l_in = data.links_in[n.key] ? data.links_in[n.key] : [] ;
838        var l_out = data.links_out[n.key] ? data.links_out[n.key] : [] ;
839       
840        var result_n = {nodes:[], links:[]};
841        if (dir == 'in' ) { result_n.nodes = n_in; result_n.links = l_in; }
842                   else if (dir == 'out' ) { result_n.nodes = n_out; result_n.links = l_out; }
843                   else { result_n.nodes = n_out.concat(n_in); result_n.links = l_out.concat(l_in);  }
844        var n_nextlevel = {nodes:[], links:[]};
845        if (depth > 0 || depth < 0) { 
846         result_n.nodes.forEach (function(n) 
847                     { var n_neighbours = neighboursWithLinks(data, n, dir, depth - 1);
848                     n_nextlevel.nodes = n_nextlevel.nodes.concat(n_neighbours.nodes); 
849                     n_nextlevel.links = n_nextlevel.links.concat(n_neighbours.links);
850                     })
851         }
852         result_n.nodes = result_n.nodes.concat(n_nextlevel.nodes);
853         result_n.links = result_n.links.concat(n_nextlevel.links);
854       
855        return result_n;
856       
857}
858
859/** deduplicates based on index-property */
860function unique_nodes(nodes) 
861{
862    var hash = {}, result = [];
863    for ( var i = 0, l = nodes.length; i < l; ++i ) {
864           n_key = nodes[i].key;
865        if ( !hash[n_key] ) { //it works with objects! in FF, at least
866            hash[ n_key ] = true;
867            result.push(nodes[i]);
868        }
869    }
870    return result;
871}
872
873/** deduplicates links (based on source-target-index
874based on: http://stackoverflow.com/questions/1890203/unique-for-arrays-in-javascript
875*/
876function unique_links(links) 
877{
878    var hash = {}, result = [];
879    for ( var i = 0, l = links.length; i < l; ++i ) {
880            src_key = links[i].source.key;
881            trg_key = links[i].target.key;
882            key = src_key + "," + trg_key;
883        if ( !hash[key] ) {
884            hash[ key] = true;
885            result.push(links[i]);
886        }
887    }
888    return result;
889}
Note: See TracBrowser for help on using the repository browser.