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

Last change on this file since 5306 was 5306, checked in by xnrn@gmx.net, 10 years ago

added dynamic weight threshold; handle loading overview in dynamic mode (loadDetailInfo)

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