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

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

small changes to label generation, + node-size scaling relative currently visible nodes

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