source: SMC/trunk/SMC/src/web/scripts/js/smc-graph.js @ 3629

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

force-layout parameters dynamic; (supports edges with weight AND value); flag for arrows

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