Merge pull request #5205 from solgenomics/topic/generic_trial_upload
[sgn.git] / js / source / legacy / brapi / StudyComparison.js
blobf2254f551e69cb0d03983dbcf6ed825bb920d04e
1 (function (global, factory) {
2         typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3         typeof define === 'function' && define.amd ? define(factory) :
4         (global.StudyComparison = factory());
5 }(this, (function () { 'use strict';
7 // creates a new comparison object
8 function main(){
9         
10         var scomp = {};
11         // size options and things
12         var opts = {
13             'size':570,
14             'axes':30,
15             'margins':10,
16             'trait':""
17         };
18         
19         var link_maker = null;
20         
21         var rsquare_format = d3.format(".3f");
22         
23         function accessionAccessor(a){
24             return d3.mean(a.variables[opts.trait])
25         }
26         
27         var observationUnit_data = [];
28         var hist_data = [];
29         var grid_data = [];
30         
31         // sets the variable to be used for the histogram and comparison grid
32         scomp.setVariable = function(variable){
33             opts.trait = variable;
34             return scomp;
35         };
36         
37         scomp.links = function(link_m){
38             link_maker = link_m;
39             return scomp;
40         };
41         
42         // sets an option
43         scomp.setOpt = function(opt,val){
44             opts[opt] = val;
45         };
46         
47         // gets an option
48         scomp.getOpt = function(opt){
49             return opts[opt];
50         };
51         
52         // loads and parses an array of observationUnits to be displayed in the comparison
53         // returns a list of compareable (shared) traits.
54         scomp.loadData = function(observationUnits){
55             console.log("loads",observationUnits);
56             observationUnit_data = observationUnits.slice(0);
57             
58             var nester = d3.nest().key(function(unit){
59                 return unit.studyDbId;
60             }).key(function(unit){
61                 return unit.germplasmDbId;
62             });
63             
64             var studyVariables = {};
65             var nested = nester.entries(observationUnits);
66             nested.forEach(function(study){
67                 study.accessions = {};
68                 var thisStudyVariables = {};
69                 study.values.forEach(function(accession){
70                     study.accessions[accession.key] = accession;
71                     accession.variables = {};
72                     accession.values.forEach(function(unit){
73                         unit.observations.forEach(function(obs){
74                             if (accession.variables[obs.observationVariableName]==undefined) {
75                                 accession.variables[obs.observationVariableName] = [];
76                                 thisStudyVariables[obs.observationVariableName] = true;
77                             }
78                             accession.variables[obs.observationVariableName].push(obs.value);
79                         });
80                     });
81                 });
82                 d3.keys(thisStudyVariables).forEach(function(k){
83                     studyVariables[k] = studyVariables[k]?studyVariables[k]+1:1;
84                 });
85                 delete study.values;
86             });
87             
88             var sharedVars = d3.keys(studyVariables).filter(function(k){
89                 return studyVariables[k]==nested.length;
90             });
91             
92             var paired_grid = [];
93             for (var row = 0; row < nested.length-1; row++) {
94                 paired_grid.push([]);
95                 for (var col = 1; col < nested.length; col++) {
96                     if (row<col){
97                         paired_grid[row].push([nested[row],nested[col]]);
98                     }
99                     else {
100                         paired_grid[row].push(null);
101                     }
102                     
103                 }
104             }
105             
106             hist_data = nested;
107             grid_data = paired_grid;
108             return sharedVars;
109         };
110         
111         // draws a histogram.
112         scomp.multiHist = function(selector){
113             // set up svg canvas and create layers
114             var prev = d3.select(selector)
115                 .selectAll(".mulhst-svg")
116                 .data([hist_data]);
117             prev.exit().remove();
118             var newSvg = prev.enter()
119                 .append("svg")
120                 .classed("mulhst-svg",true)
121                 .attr("width",opts.size+opts.axes)
122                 .attr("height",opts.size+opts.axes)
123                 .attr("viewBox","0 0 "+(opts.size+opts.axes)+" "+(opts.size+opts.axes));
124             newSvg.append("g")
125                 .classed("mulhst-main",true)
126                 .attr("transform","translate(0,"+opts.axes+")");
127             newSvg.append("g").classed("mulhst-xaxis",true)
128                 .attr("transform","translate(0,"+(opts.size)+")");            newSvg.append("g")
129                 .classed("mulhst-yaxis",true)
130                 .attr("transform","translate("+opts.axes+","+opts.axes+")");
131             var lgnd = newSvg.append("g")
132                 .classed("mulhst-legend",true);
133             lgnd.append("rect").classed("mulhst-legend-bg",true)
134                 .attr("fill","white").attr("fill-opacity",0.65)
135                 .attr("stroke","black")
136                 .attr("x",-opts.margins).attr("y",-opts.margins);
137             lgnd.append("g").classed("mulhst-legend-main",true);
138             var builtSvg = newSvg.merge(prev);
139             var main = builtSvg.select(".mulhst-main");
140             scomp.mhist = builtSvg.node();
141             var xaxis = builtSvg.select(".mulhst-xaxis");
142             var yaxis = builtSvg.select(".mulhst-yaxis");
143             var legend = builtSvg.select(".mulhst-legend");
144             
145             
146             // filter and bin accessions
147             var allaccessions = hist_data.reduce(function(a,d){return a.concat(d3.values(d.accessions))},[]);
148             var total_extent = d3.extent(allaccessions,accessionAccessor);
149             var bin_guess = 15;
150             var x = d3.scaleLinear().domain(total_extent).range([opts.axes,(opts.size-opts.margins)]).nice(bin_guess);
151             var histogram = d3.histogram().domain(x.domain()).thresholds(x.ticks(bin_guess)).value(accessionAccessor);
152             
153             // create bins for each study at each position
154             var bins = [];
155             var kernels = [];
156             var study_ids = [];
157             hist_data.forEach(function(study){
158                 var studyAccessions = d3.values(study.accessions);
159                 var studyBins = histogram(studyAccessions)
160                     .filter(function(bin){return bin.length!=0;});
161                 studyBins.forEach(function(bin){
162                     bin.study = study;
163                 });
164                 study_ids.push(study.key);
165                 bins.push.apply(bins, studyBins);
166             });
167             
168             // set up colors and the y-axis
169             var color = d3.scaleOrdinal(d3.schemeCategory10).domain(study_ids);
170             var y = d3.scaleLinear().domain([0,d3.max(bins,function(bin){return bin.length})])
171                 .range([(opts.size-opts.axes),opts.margins]);
172             
173             // Perform kernel estimations. Values are normalized to prevent the 
174             // order of magnitude from affecting the smoothing degree.    
175             var kernelNormY = y.copy();
176             kernelNormY.range([0,100]);
177             var kernelNormX = x.copy();
178             kernelNormX.range([0,100]);
179             hist_data.forEach(function(study){
180                 var studyAccessions = d3.values(study.accessions);
181                 console.log("a",x.ticks(bin_guess).map(kernelNormX));
182                 console.log("b",studyAccessions.map(accessionAccessor).map(kernelNormX));
183                 kernels.push({
184                     'study':study,
185                     'path':kernelDensityEstimator(kernelEpanechnikov(10), x.ticks(bin_guess).map(kernelNormX))
186                         (studyAccessions.map(accessionAccessor).map(kernelNormX)).map(function(d){
187                             return [d[0],d[1]*1000]
188                         })
189                 });
190             });
191             
192             // draw axes
193             var xax = d3.axisBottom(x);
194             xaxis.call(xax);
195             var yax = d3.axisLeft(y);
196             yaxis.call(yax);
197         
198             // draw bars and lower
199             var bars = main.selectAll(".mulhst-bar").data(bins);
200             bars.exit().remove();
201             var newBars = bars.enter().append("g").classed("mulhst-bar",true);
202             newBars.append("rect").attr("opacity",0.5);
203             var allBars = newBars.merge(bars).order();
204             allBars.select("rect")
205                 .attr("x",function(d){
206                     return x(d.x0)+1
207                 })
208                 .attr("y",function(d){
209                     return y(d.length)
210                 })
211                 .attr("width",function(d){
212                     return x(d.x1) - x(d.x0)-1
213                 })
214                 .attr("height",function(d){
215                     return y(0)-y(d.length)
216                 })
217                 .attr("fill",function(d){
218                     return color(d.study.key);
219                 })
220                 .lower();
221             
222             // kernel line function
223             var kernelCurve = d3.line().curve(d3.curveBasis)
224                 .x(function(d){return x(kernelNormX.invert(d[0]));})
225                 .y(function(d){return y(kernelNormY.invert(d[1]));});
226             
227             // draw kernel estimation backgrounds (white border)
228             var lineBGs = main.selectAll(".mulhst-kernel-bg").data(kernels);
229             lineBGs.exit().remove();
230             var newLines = lineBGs.enter().append("path").classed("mulhst-kernel-bg",true);
231             var allLines = newLines.merge(lineBGs)
232                 .attr("d",function(d){
233                     console.log(d.path);
234                     return kernelCurve(d.path);
235                 })
236                 .attr("stroke","white")
237                 .attr("fill","none")
238                 .attr("stroke-width",4)
239                 .attr("stroke-opacity",0.75)
240                 .raise();
241                 
242             // draw kernel estimation curves
243             var lines = main.selectAll(".mulhst-kernel").data(kernels);
244             lines.exit().remove();
245             var newLines = lines.enter().append("path").classed("mulhst-kernel",true);
246             var allLines = newLines.merge(lines)
247                 .attr("d",function(d){
248                     console.log(d.path);
249                     return kernelCurve(d.path);
250                 })
251                 .attr("stroke",function(d){
252                     return color(d.study.key);
253                 })
254                 .attr("fill","none")
255                 .attr("stroke-width",2)
256                 .attr("stroke-opacity",0.8)
257                 .raise();
258             
259             // draw legend items
260             var legents = legend.select(".mulhst-legend-main").selectAll(".mulhst-legend-entry").data(hist_data);
261             legents.exit().remove();
262             var newLegents = legents.enter().append("g").classed("mulhst-legend-entry",true);
263             newLegents.append("circle")
264                 .attr("fill-opacity",0.6)
265                 .attr("stroke-width",2)
266                 .attr("cx",8).attr("cy",8).attr("r",8);
267             newLegents.append("text").attr("font-size",12).attr("x",22).attr("y",13);
268             var allLegents = newLegents.merge(legents);
269             allLegents.attr("transform",function(d,i){
270                     return d3.zoomIdentity.translate(0,22*i);
271                 })
272                 .on("click",scomp.multiHist.toggle);
273             allLegents.select("circle").attr("fill",function(d){
274                 return color(d.key);
275             })
276             .attr("stroke",function(d){
277                 return color(d.key);
278             });
279             allLegents.select("text").text(function(d){
280                 return d3.values(d.accessions)[0].values[0].studyName
281             });
282             
283             // transform legend into upper-right corner
284             var bbox = legend.select(".mulhst-legend-main").node().getBBox();
285             var bgwidth = bbox.width+opts.margins*2;
286             var bgheight = bbox.height+opts.margins*2;
287             legend.select(".mulhst-legend-bg")
288                 .attr("width",bgwidth)
289                 .attr("height",bbox.height+opts.margins*2);
290             legend.attr("transform","translate("+(opts.size-bgwidth)+","+(opts.margins*2+opts.axes)+")");
291         };
292         
293         // set the histogram to show specfic studies
294         scomp.multiHist.showStudies = function(studies){
295             if(!scomp.mhist)return; // this function affects the histogram, if it doesnt exist, exit
296             
297             var fadet = d3.transition();
298             d3.select(scomp.mhist).selectAll(".mulhst-bar")
299                 .transition(fadet)
300                 .style("opacity",function(d){
301                     return studies.indexOf(d.study)!=-1?null:0;
302                 });
303             d3.select(scomp.mhist).selectAll(".mulhst-bar,.mulhst-kernel,.mulhst-kernel-bg")
304                 .transition(fadet)
305                 .style("opacity",function(d){
306                     return studies.indexOf(d.study)!=-1?null:0;
307                 });
308             d3.select(scomp.mhist).selectAll(".mulhst-legend-entry")
309                 .attr("isNotShown",function(d){
310                     return studies.indexOf(d)!=-1?null:true;
311                 })
312                 .select("circle")
313                 .transition(fadet)
314                 .style("fill-opacity",function(d){
315                     return studies.indexOf(d)!=-1?null:0;
316                 });
317         };
318         
319         // toggle the visibility of a study (this is a click event on the legend item)
320         scomp.multiHist.toggle = function(toggleTo){
321             if(!scomp.mhist)return; // this function affects the histogram, if it doesnt exist, exit
322             
323             // resets attributes to match selected.
324             d3.select(scomp.mhist).selectAll(".mulhst-legend-entry")
325                 .attr("isDeselected",function(){
326                     var curr = d3.select(this).attr("isNotShown");
327                     return curr?true:null;
328                 });
329                 
330             var entry = d3.select(this); 
331             if (!(toggleTo===true||toggleTo===false)){
332                 toggleTo = !entry.attr("isDeselected");
333             }
334             entry.attr("isDeselected",function(){
335                 return toggleTo?true:null;
336             });
337             scomp.multiHist.showSelected();
338         };
339         
340         // resets the histogram to what the user manually selected (via clicking)
341         scomp.multiHist.showSelected = function(){
342             if(!scomp.mhist)return; // this function affects the histogram, if it doesnt exist, exit
343             
344             var selected = d3.select(scomp.mhist).selectAll(".mulhst-legend-entry")
345                 .filter(function(){
346                     return !d3.select(this).attr("isDeselected");
347                 })
348                 .nodes().map(function(node){
349                     return d3.select(node).datum()
350                 });
351             scomp.multiHist.showStudies(selected);
352             return scomp;
353         };
354         
355         // draws the comparison grid
356         scomp.graphGrid = function(selector){
357             
358             // set up svg canvas
359             var prev = d3.select(selector)
360                 .selectAll(".grapgr-svg")
361                 .data([grid_data]);
362             prev.exit().remove();
363             var newSvg = prev.enter()
364                 .append("svg")
365                 .classed("grapgr-svg",true)
366                 .attr("width",opts.size+opts.axes)
367                 .attr("height",opts.size+opts.axes)
368                 .attr("viewBox","0 0 "+(opts.size+opts.axes)+" "+(opts.size+opts.axes));
369             newSvg.append("g")
370                 .attr("transform","translate("+opts.axes+","+opts.axes+")")
371                 .append("g")
372                 .classed("grapgr-main",true);
373             newSvg.append("g")
374                 .classed("grapgr-topaxis",true)
375                 .attr("transform","translate("+(opts.axes+opts.margins)+","+(opts.axes+opts.margins)+")");
376             newSvg.append("g")
377                 .classed("grapgr-leftaxis",true)
378                 .attr("transform","translate("+(opts.axes+opts.margins)+","+(opts.axes+opts.margins)+")");
379             var builtSvg = newSvg.merge(prev);
380             var main = builtSvg.select(".grapgr-main")
381                 .attr("transform","translate(0, 0) scale(1,1)");
382             scomp.ggrid = main;
383             var taxis = builtSvg.select(".grapgr-topaxis");
384             var laxis = builtSvg.select(".grapgr-leftaxis");
385             
386             var tooltip = newSvg.append("a")
387                 .classed("grapgr-tooltip",true)
388                 .attr('opacity',0)
389                 .attr('transform', 'translate(' + 0 + ',' + -100 + ')')
390                 .attr("target","_blank");
391             tooltip.append('rect')
392                 .attr('x',-2).attr('y',-15)
393                 .attr('width',100)
394                 .attr('height',16)
395                 .attr('fill','black');
396             tooltip.append('text')
397                 .attr('fill','white')
398                 .attr('text-decoration','underline')
399                 .attr('y',-3)
400                 .attr('x',1)
401                 .attr('font-size','12');
402             tooltip = tooltip.merge(d3.select('.grapgr-tooltip'));
403             var tooltip_timeout = false;
404             var tooltip_hold = false;
405             function set_tooltip(viz,x,y,text,link){
406                 if (tooltip_hold) return;
407                 console.log(viz,x,y,text);
408                 var tt = d3.select('.grapgr-tooltip');
409                 var ttrect = tt.select('rect');
410                 var tttext = tt.select('text');
411                 if (!viz){
412                     if (!tooltip_timeout) tooltip_timeout = setTimeout(function(){
413                         tooltip_timeout = false;
414                         if (tooltip_hold) return;
415                         tt.attr('opacity',0);
416                         tt.attr('transform', 'translate(' + 0 + ',' + -100 + ')');
417                     },200);
418                     return
419                 }
420                 clearTimeout(tooltip_timeout);
421                 tooltip_timeout = false;
422                 if (tt.attr('opacity')==1 && text!==undefined && tttext.text()==text) return;
423                 tt.attr('opacity',1);
424                 tt.attr('transform', 'translate(' + x + ',' + y + ')');
425                 tttext.text(text);
426                 ttrect.attr('width',tttext.node().getComputedTextLength()+6);
427                 if(link){
428                     tt.attr('href',link).style('cursor','pointer');
429                 } else {
430                     tt.attr('href',null).on('click',function(){return false;}).style('cursor','auto');
431                 }
432             }
433             tooltip.on('mousemove',function(){
434                 tooltip_hold=true;
435             });
436             tooltip.on('mouseout',function(){
437                 tooltip_hold=false;
438                 set_tooltip(false);
439             });
440                     
441             // calculate grid size
442             var cellSize = (opts.size-(opts.margins*(grid_data.length+1)))/grid_data.length;
443             var cellOffset = (opts.size-opts.margins)/grid_data.length;
444             
445             // make rows
446             var rows = main.selectAll(".grapgr-row").data(function(d){return d;});
447             var newRows = rows.enter().append("g").classed("grapgr-row",true);
448             rows.exit().remove();
449             var allRows = newRows.merge(rows)
450                 .attr("ryp",function(d,i){return i;})
451                 .attr("transform",function(d,i){
452                     var y = opts.margins+cellOffset*i;
453                     var x = opts.margins;
454                     return "translate("+x+","+y+")";
455                 });
456                 
457             // add cells to rows
458             var cells = allRows.selectAll(".grapgr-cell").data(function(d){return d;});
459             var newCells = cells.enter().append("g").classed("grapgr-cell",true);
460             newCells.append("rect").classed("grapgr-cell-bg",true)
461                 .attr("fill","white")
462                 .attr("width",opts.size)
463                 .attr("height",opts.size)
464                 .attr("stroke","black");
465             newCells.append("g").classed("grapgr-graph-points",true);
466             newCells.append("g").classed("grapgr-graph-xaxis",true)
467                 .attr('transform', 'translate(0,'+(opts.size-opts.axes)+')');
468             newCells.append("g").classed("grapgr-graph-yaxis",true)
469                 .attr('transform', 'translate('+(opts.axes)+',0)');
470             cells.exit().remove();
471             var allCells = newCells.merge(cells)
472                 .attr("opacity",1)
473                 .attr("cxp",function(d,i){return i;})
474                 .attr("cyp",function(d,i){return d3.select(this.parentNode).attr("ryp");})
475                 .attr("transform",function(d,i){
476                     var x = cellOffset*i;
477                     var y = 0;
478                     var s = cellSize/opts.size;
479                     return "translate("+x+","+y+") scale("+s+")";
480                 });
481             
482             // sort null and drawable cells
483             var nullCells = allCells.filter(function(d){return d==null;});
484             var drawCells = allCells.filter(function(d){return d!=null;});
485                 
486             // draw graphs
487             drawCells.each(function(d,i){
488                 var xStudy = d[0];
489                 var yStudy = d[1];
490                 var accessions = {};
491                 
492                 // find accessions which are measured in both studies
493                 d3.entries(xStudy.accessions).forEach(function(kv){
494                     accessions[kv.key] = {'xObs':kv.value};
495                     accessions[kv.key].xVals = kv.value.variables[opts.trait];
496                     if (accessions[kv.key].xVals){
497                         accessions[kv.key].xAvg = d3.mean(accessions[kv.key].xVals);
498                     }
499                 });
500                 d3.entries(yStudy.accessions).forEach(function(kv){
501                     if (accessions[kv.key]){
502                         accessions[kv.key].yObs = kv.value;
503                         accessions[kv.key].yVals = kv.value.variables[opts.trait];
504                         if (accessions[kv.key].yVals){
505                             accessions[kv.key].yAvg = d3.mean(accessions[kv.key].yVals);
506                         }
507                     }
508                 });
509                 var accessions = d3.values(accessions).filter(function(a){
510                     return a.xVals&&a.yVals&&a.xAvg!=undefined&&a.yAvg!=undefined;
511                 });
512                 
513                 // set the cell to null if there are no matching accessions.
514                 if (accessions.length<2){
515                     d3.select(this).datum(null);
516                     return
517                 }
518                 
519                 // find the x/y extents and the total extent, create scales
520                 var xtent = d3.extent(accessions,function(d){return d.xAvg});
521                 var ytent = d3.extent(accessions,function(d){return d.yAvg});
522                 var ttent = d3.extent(xtent.concat(ytent));
523                 var x = d3.scaleLinear().domain(ttent).range([opts.axes,(opts.size-opts.margins)]);
524                 var y = d3.scaleLinear().domain(ttent).range([opts.size-opts.axes,opts.margins]);
525                 
526                 // draw cell axes
527                 var xaxF = d3.axisBottom(x);
528                 var xaxis = d3.select(this).select(".grapgr-graph-xaxis").call(xaxF);
529                 var yaxF = d3.axisLeft(y);
530                 var yaxis = d3.select(this).select(".grapgr-graph-yaxis").call(yaxF);
531                 
532                 // draw data points
533                 var pointLayer = d3.select(this).select(".grapgr-graph-points");
534                 var points = pointLayer.selectAll(".grapgr-graph-point").data(accessions);
535                 var newPoints = points.enter().append("circle")
536                     .classed("grapgr-graph-point",true)
537                     .attr("fill","red")
538                     .attr("stroke","red")
539                     .attr("stroke-width",4)
540                     .attr("stroke-opacity",0.1)
541                     .attr("r",3);
542                 points.exit().remove();
543                 var allPoints = newPoints.merge(points)
544                     .attr("cx",function(d){
545                         return x(d.xAvg);
546                     })
547                     .attr("cy",function(d){
548                         return y(d.yAvg);
549                     });
550                 
551                 allPoints.on('mouseover',function(d){
552                     var newLoc = d3.mouse(d3.select('.grapgr-tooltip').node().parentNode);
553                     var name = d.xObs.values[0].germplasmName;
554                     var link = link_maker?link_maker(d.xObs.values[0].germplasmDbId):undefined;
555                     set_tooltip(true,newLoc[0],newLoc[1],name,link);
556                 }).on('mouseout',function(){
557                     set_tooltip(false);
558                 });
559                     
560                 // draw trendline
561                 var regression = leastSquares(
562                     accessions.map(function(d){return d.xAvg}),
563                     accessions.map(function(d){return d.yAvg}));
564                 var line = d3.line()
565                     .x(function(d){return x(d[0]);})
566                     .y(function(d){return y(d[1]);});
567                 var pathd = [];
568                 var yentint = regression.slope*ttent[0]+regression.yintercept;
569                 var xentint = (ttent[0]-regression.yintercept)/regression.slope;
570                 
571                 if ((yentint < xentint && regression.slope>0) || (yentint > xentint && regression.slope<0) ){
572                     pathd.push([xentint,ttent[0]]);
573                 } 
574                 else {
575                     pathd.push([ttent[0],yentint]);
576                 }
577                 var youtint = regression.slope*ttent[1]+regression.yintercept;
578                 var xoutint = (ttent[1]-regression.yintercept)/regression.slope;
579                 if ((xoutint < youtint && regression.slope>0) || (xoutint > youtint && regression.slope<0) ){
580                     pathd.push([xoutint,ttent[1]]);
581                 } 
582                 else {
583                     pathd.push([ttent[1],youtint]);
584                 }
585                 var tline = d3.select(this).selectAll(".grapgr-graph-tline").data([pathd]);
586                 tline.exit().remove();
587                 tline.enter().append("path")
588                     .classed("grapgr-graph-tline",true).merge(tline)
589                     .attr("stroke","blue")
590                     .attr("d",function(d){return line(d)});
591                 
592                 var rsquare = d3.select(this).selectAll(".grapgr-graph-rsquare").data([regression.rSquare]);
593                 rsquare.exit().remove();
594                 rsquare.enter().append("text")
595                     .classed("grapgr-graph-rsquare",true).merge(rsquare)
596                     .attr("stroke","none")
597                     .attr("fill","blue")
598                     .attr("transform","translate("+(opts.axes+14)+","+(opts.margins+14)+")")
599                     .text(function(d){return "r\u00B2 = "+rsquare_format(d)});
600             });
601             
602             // sort null and drawable cells again, accounting for missmatched studies
603             nullCells = allCells.filter(function(d){return d==null;});
604             drawCells = allCells.filter(function(d){return d!=null;});
605             
606             // color placeholder and recolor former placeholders
607             nullCells.selectAll(".grapgr-cell>* *, .grapgr-graph-tline, .grapgr-graph-rsquare").remove();
608             nullCells.on("click",null)
609                 .select(".grapgr-cell-bg")
610                 .attr("fill","#f8f8f8")
611                 .attr("stroke","#ddd");
612             drawCells.select(".grapgr-cell-bg")
613                 .attr("fill","white")
614                 .attr("stroke","black");
615             
616             // makes comparison axes
617             var topLabels = grid_data[0].map(function(d){
618                 return d3.values(d[1].accessions)[0].values[0].studyName
619             });
620             var topLabelPos = grid_data[0].map(function(d,i){
621                 return cellOffset*i + cellSize/2;
622             });
623             var topScale = d3.scaleOrdinal()
624                 .domain(topLabels)
625                 .range(topLabelPos);
626             var topaxis = d3.axisTop(topScale);
627             taxis.selectAll("*").remove();
628             taxis.call(topaxis).attr("font-size",12);
629             taxis.select(".domain").style("opacity",0);
630             taxis.selectAll(".tick>line").style("opacity",0);
631             taxis.selectAll(".tick>line").style("opacity",0);
632             var leftLabels = grid_data.map(function(d){
633                 return d3.values(d[d.length-1][0].accessions)[0].values[0].studyName
634             });
635             var leftLabelPos = grid_data.map(function(d,i){
636                 return cellOffset*i + cellSize/2;
637             });
638             var leftScale = d3.scaleOrdinal()
639                 .domain(leftLabels)
640                 .range(leftLabelPos);
641             var leftaxis = d3.axisLeft(leftScale);
642             laxis.selectAll("*").remove();
643             laxis.call(leftaxis).attr("font-size",12);
644             laxis.select(".domain").style("opacity",0);
645             laxis.selectAll(".tick>line").style("opacity",0);
646             laxis.selectAll(".tick>text")
647                 .attr("text-anchor","middle")
648                 .attr("transform","rotate(-90) translate(9,-12)");
649             
650             // if we have a grid larger than 1x1, enable zooming/selecting!
651             if(grid_data.length>1){
652                 drawCells.on("click",cellZoomIn);
653             }
654             
655             // zooms in on a cell and selects the relevant histogram series
656             function cellZoomIn(d){
657                 var self=this;
658                 drawCells.on("click",null);
659                 var k = (opts.size-2*opts.margins)/cellSize;
660                 var margin_offset = opts.margins - opts.margins/k;
661                 var xPos = d3.select(this).attr("cxp")*cellOffset + margin_offset;
662                 var yPos = d3.select(this).attr("cyp")*cellOffset + margin_offset;
663                 main.transition()
664                     .on("end",function(){
665                         drawCells.on("click",cellZoomOut);
666                     })
667                     .attr("transform",d3.zoomIdentity.scale(k).translate(-xPos,-yPos))
668                     .selectAll(".grapgr-cell")
669                     .filter(function(d){return self!=this;})
670                     .attr("opacity",0);
671                 topScale.range(topLabelPos.map(function(v){return (v-xPos+margin_offset)*k}));
672                 taxis.transition().call(topaxis);
673                 leftScale.range(leftLabelPos.map(function(v){return (v-yPos+margin_offset)*k}));
674                 laxis.transition().call(leftaxis);
675                 scomp.multiHist.showStudies(d);
676             }
677             
678             // zooms out, resets histogram
679             function cellZoomOut(d){
680                 drawCells.on("click",null);
681                 main.transition()
682                     .on("end",function(){
683                         drawCells.on("click",cellZoomIn);
684                     })
685                     .attr("transform",d3.zoomIdentity)
686                     .selectAll(".grapgr-cell")
687                     .attr("opacity",1);
688                 topScale.range(topLabelPos);
689                 taxis.transition().call(topaxis);
690                 leftScale.range(leftLabelPos);
691                 laxis.transition().call(leftaxis);
692                 scomp.multiHist.showSelected();
693             }
694             return scomp;
695         };
696         
697         return scomp;
698     }
700     // regression (http://bl.ocks.org/benvandyke/8459843)
701     function leastSquares(xSeries, ySeries) {
702         var reduceSumFunc = function(prev, cur) { return prev + cur; };
703         
704         var xBar = xSeries.reduce(reduceSumFunc) * 1.0 / xSeries.length;
705         var yBar = ySeries.reduce(reduceSumFunc) * 1.0 / ySeries.length;
707         var ssXX = xSeries.map(function(d) { return Math.pow(d - xBar, 2); })
708             .reduce(reduceSumFunc);
709         
710         var ssYY = ySeries.map(function(d) { return Math.pow(d - yBar, 2); })
711             .reduce(reduceSumFunc);
712             
713         var ssXY = xSeries.map(function(d, i) { return (d - xBar) * (ySeries[i] - yBar); })
714             .reduce(reduceSumFunc);
715         
716         var slope = ssXY / ssXX;
717         var yintercept = yBar - (xBar * slope);
718         var xintercept = (-yintercept)/slope;
719         var rSquare = Math.pow(ssXY, 2) / (ssXX * ssYY);
720         
721         return {'slope':slope, 'yintercept':yintercept, 'xintercept':xintercept, 'rSquare':rSquare};
722     }
724     // kernel estimation (https://bl.ocks.org/mbostock/4341954)
725     function kernelDensityEstimator(kernel, X) {
726         return function(V) {
727             return X.map(function(x) {
728                 return [x, d3.mean(V, function(v) { return kernel(x - v); })];
729             });
730         };
731     }
732     function kernelEpanechnikov(k) {
733         return function(v) {
734             return Math.abs(v /= k) <= 1 ? 0.75 * (1 - v * v) / k : 0;
735         };
736     }
738 return main;
740 })));