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

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

added gravity-param and charge considers node size

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