function createCancerPlots(id, scatter_data_json, kaplan_data_json, bestCutoff_in) { var scatter_data = scatter_data_json; var kaplan_data = kaplan_data_json; var bestCutoff = bestCutoff_in; var selectedCutoff = bestCutoff; var quantile20 = 0; var quantile80 = 0; var settings = { 'transitionTime': 1000, 'labelSpace': 60, 'labelWidth': 35, 'labelHeight': 20, xAxisLabel: 'Expression level (TPM)', yAxisLabel: 'Time after diagnosis (years)', yTickFormat: '2.0f', xMin: 0, yMin: 0, color: [], dimension: {w:890, h:660}, padding: {t:150+60, r:150+60, b:160, l:50}, legend:{x:680,y:-110,size:11} }; var $divScatter = $('#scatter'+id); var $divContainer = $divScatter.closest('.cancer'); var plot = $divScatter.scatterPlot(scatter_data, settings); var kmPlot; // Days lables & legend var lgnd = {2:{'name':'Density dead', 'clss':'dead'}, 3:{'name':'Density alive', 'clss':'alive'}, 4:{'name':'Density dead under cut off', 'clss':'under'}, 5:{'name':'Density dead over cut off', 'clss':'over'}}; for (var k in lgnd) { var clss = lgnd[k].clss; // Median labels var g = plot.vis.append('g') .attr("class", "median median_"+clss); g.append('path'); g.append('rect') .attr("class", "area") .attr('rx', 5) .attr('ry', 5) .attr('width', plot.settings.labelWidth) .attr('height', plot.settings.labelHeight); g.append('text') .attr('text-anchor', 'middle') .attr('dx', plot.settings.labelWidth/2) .attr('dy', plot.settings.labelHeight/2+3); setLabelPos(plot, clss, k); // legend var l = plot.vis.append('g') .attr("class", "legend legend"+k) .attr("transform", "translate("+plot.settings.legend.x+","+(plot.settings.legend.y + k*(plot.settings.legend.size+2))+")"); l.append("path") .datum(get_gauss(plot.settings.legend.size, k<4)) .attr("class", "area area_"+clss) .attr("d", d3.area() .x(function(d) { return d[0]; }) .y0(plot.settings.legend.size) .y1(function(d) { return d[1]; }) ); l.append("text") .attr("x", plot.settings.legend.size+2) .attr("y", plot.settings.legend.size/2) .attr("dy", ".35em") .text(lgnd[k].name); } plot.vis.selectAll('.legend0 rect').remove(); plot.vis.selectAll('.legend0') .append('path') .attr("transform", "translate(0, "+plot.settings.legend.size/2+")") .attr('d', d3.symbol().size(60).type(getD3Symbol('cross'))); plot.vis.selectAll('.legend1 rect').remove(); plot.vis.selectAll('.legend1') .append('path') .attr("transform", "translate(0, "+plot.settings.legend.size/2+")") .attr('d', d3.symbol().size(60).type(getD3Symbol('circle'))); // TPM density var xd = plot.vis.append('g') .attr('class', 'tpm_density') .attr("transform", "translate(0,-" + plot.settings.padding.t + ")"); xd.append("path").attr("class", "area area0"); xd.append("path").attr("class", "area area1"); // DAYS density var yd = plot.vis.append('g') .attr('class', 'days_density') .attr("transform", "translate(" + (plot.w+10) + ", 0)"); yd.append("path").attr("class", "area area_under"); yd.append("path").attr("class", "area area_over"); // P-value plot var p_y = d3.scaleLinear() .range([plot.settings.padding.b-60, 0]); var p_yAxis = d3.axisLeft() .scale(p_y) .ticks(5); var p_line = d3.line() .x(function(d) { var lx = plot.x(d.x); return lx==-Infinity?1:lx; }) .y(function(d) { var ly = p_y(d.y); return ly==-Infinity?1:ly; }); var xp = plot.vis .append('g') .attr('class', 'p_value') .attr('transform', 'translate(0,' + (plot.h+50)+ ')'); xp.append('rect') .attr('class', 'plotBG') .attr('width', plot.w) .attr('height', plot.settings.padding.b-60); xp.append('g') .attr('class', 'p-axis axis') .call(p_yAxis) .append('text') .attr('transform', 'rotate(-90)') .attr('y', 10) .attr('x', -2) .attr('class', 'label') .style('text-anchor', 'end') .text('P score'); xp.append('path') .attr('class', 'p_value') .attr('fill', 'none') .attr('stroke', '#000'); var pl = plot.vis.append('g') .attr("class", "legend legend6") .attr("transform", "translate("+plot.settings.legend.x+","+(plot.h+60)+")"); pl.append("rect") .attr("width", plot.settings.legend.size) .attr("height", plot.settings.legend.size) .style("fill", '#000'); pl.append("text") .attr("x", plot.settings.legend.size+2) .attr("y", plot.settings.legend.size/2) .attr("dy", ".35em") .text('P score'); // Dead medain plot var m_y = d3.scaleLinear() .range([plot.settings.padding.b-60, 0]); var m_yAxis = d3.axisRight() .scale(m_y) .ticks(5); var m_line = d3.line() .x(function(d) { var lx = plot.x(d.x); return lx==-Infinity?1:lx; }) .y(function(d) { var ly = m_y(d.y); return ly==-Infinity?0:ly; }); xp.append('g') .attr('class', 'm-axis axis') .attr('transform', 'translate('+plot.w+',0)') .call(m_yAxis) .append('text') .attr('transform', 'rotate(-90)') .attr('y', -5) .attr('x', -2) .attr('class', 'label') .style('text-anchor', 'end') .text('Medain separation'); xp.append('path') .attr('class', 'm_value') .attr('fill', 'none') .attr('stroke', '#d90036'); var ml = plot.vis.append('g') .attr("class", "legend legend7") .attr("transform", "translate("+plot.settings.legend.x+","+(plot.h+60+plot.settings.legend.size+2)+")"); ml.append("rect") .attr("width", plot.settings.legend.size) .attr("height", plot.settings.legend.size) .style("fill", '#d90036'); ml.append("text") .attr("x", plot.settings.legend.size+2) .attr("y", plot.settings.legend.size/2) .attr("dy", ".35em") .text('Dead median separation'); // Cut off line var g = plot.vis.append('g') .attr("class", "cutoff") .attr("title", "Cut off (TPM "+selectedCutoff.toPrecision(2)+")") .attr("xVal", selectedCutoff); g.append('line') .attr("class", "hiddenline") .attr("pointer-events", "all") .attr('y1', -plot.settings.padding.t+plot.settings.labelSpace) .attr('y2', plot.settings.dimension.h-plot.settings.padding.t-10); g.append('line') .attr("pointer-events", "none") .attr('y1', -plot.settings.padding.t+plot.settings.labelSpace) .attr('y2', plot.settings.dimension.h-plot.settings.padding.t-10); g.append('text') .attr("transform", "rotate(-90)") .attr("y", 10) .attr("x", -plot.h-47) .text("Cut off"); g.call(d3.drag() .on("drag", function(d) { xPos = Math.max(plot.x(quantile20), Math.min(d3.event.x, plot.x(quantile80))); d3.select(this).attr("transform", "translate(" + xPos + "," + 0 + ")") }) .on("end", function(d) { xPos = Math.max(plot.x(quantile20), Math.min(d3.event.x, plot.x(quantile80))); $divScatter.trigger('setCutoff', plot.x.invert(xPos), true); }) ); // FIX DATA var median; var sc_x = d3.scaleLinear().range([0, plot.settings.padding.r-10-plot.settings.labelSpace]); var sc_y = d3.scaleLinear().range([plot.settings.padding.t-10, plot.settings.labelSpace]); $divScatter.on('fixdata', function(e, skipPvalue) { var t = d3.transition().duration(plot.settings.transitionTime); // Survival analysis var surv = get_survival_data(kaplan_data, selectedCutoff, true, true); quantile20 = surv.q20; quantile80 = surv.q80; if (selectedCutoff < quantile20 || selectedCutoff > quantile80) { $divScatter.trigger('setCutoff', selectedCutoff, false); return false; } $divContainer.find('.pValue').html(surv.p.pValue.toPrecision(2)); $divContainer.find('.tpm_median').attr('value', surv.median['tpm']).html(Math.round(surv.median['tpm']*100)/100); $divContainer.find('.days_median').html(Math.round(surv.median['days']*100)/100); $divContainer.find('.5y_over').html((surv.survival_5y['over']||0)+'%'); $divContainer.find('.5y_under').html((surv.survival_5y['under']||0)+'%'); $divContainer.find('.3y_over').html((surv.survival_3y['over']||0)+'%'); $divContainer.find('.3y_under').html((surv.survival_3y['under']||0)+'%'); if (kmPlot) { kmPlot.fixData(surv.km, kmPlot.y, kmPlot.settings.showCensor); kmPlot.vis.selectAll('.legend0 text').text('Low expression (n='+surv.under+')'); kmPlot.vis.selectAll('.legend1 text').text('High expression (n='+surv.over+')'); kmPlot.vis.selectAll(".group0 path") .transition(t) .attr('d', kmPlot.line(surv.km[0])); kmPlot.vis.selectAll(".group1 path") .transition(t) .attr('d', kmPlot.line(surv.km[1])); } else { kmPlot = $divContainer.find('.kaplanplot').html('').kaplanMeierPlot({'groups':surv.groups, 'data':surv.km}, {transitionTime:plot.settings.transitionTime, xAxisLabel:'Time (years)', yAxisLabel:'Survival probability', yMin:0, yMax:1, color:[], dimension:{w:890, h:420}, padding:{t:30, r:210, b:40, l:50}, legend:{x:680,y:20,size:11}}); } // TPM density median = surv.median; var tpm_kde_max = 0; for (var i in surv.tpm_data) { if (!surv.tpm_data.hasOwnProperty(i)) continue; var fd = surv.tpm_data[i]; var tpm_kde = fd.length>1? ss.kernelDensityEstimation(fd) : function(d) { return 0; }; var tpm_bins = d3.histogram().domain(plot.x.domain()).thresholds(500)(fd); tpm_bins.map(function(d) { d.kde = tpm_kde(d.x0); tpm_kde_max = Math.max(tpm_kde_max, d.kde); }); plot.vis.selectAll(".tpm_density path.area"+i).datum(tpm_bins); } sc_y.domain([0, tpm_kde_max*1.01]); // Days density var days_kde_max = 0; for (var k in surv.days_data) { if (!surv.days_data.hasOwnProperty(k)) continue; var dd = surv.days_data[k]; var days_kde = dd.length>1? ss.kernelDensityEstimation(dd) : function(d) { return 0; }; var days_bins = d3.histogram().domain(plot.y.domain()).thresholds(500)(dd); days_bins.map(function(d) { d.kde = days_kde(d.x0); days_kde_max = Math.max(days_kde_max, d.kde); }); plot.vis.selectAll('.days_density path.area_'+k).datum(days_bins); } sc_x.domain([0, days_kde_max*1.01]); // P-value & median-diff plot if (!skipPvalue) { var step = (quantile80 - quantile20) / 100; var pxMin = 0; var pMin = 1; var pMax = 0; var pData = []; var mMin = 1; var mMax = 0; var mData = []; for (var f=quantile20; f<=quantile80; f+=step) { var surv = get_survival_data(kaplan_data, f); pData.push({'x':f, 'y':surv.p.pValue}); pMax = Math.max(pMax, surv.p.pValue); if (pMin > surv.p.pValue) { pMin = surv.p.pValue; pxMin = (surv.over+surv.under==kaplan_data.length)? bestCutoff : f; } var mVal = Math.abs(surv.median['over']-surv.median['under']); mData.push({'x':f, 'y':mVal}); mMax = Math.max(mMax, mVal); mMin = Math.min(mMin, mVal); } p_y.domain([pMin, pMax]); plot.vis.selectAll("g.p_value .p-axis").transition(t).call(p_yAxis); plot.vis.selectAll("g.p_value path.p_value").datum(pData); m_y.domain([mMax, mMin]); plot.vis.selectAll("g.p_value .m-axis").transition(t).call(m_yAxis); plot.vis.selectAll("g.p_value path.m_value").datum(mData); $divContainer.find('a.best').attr('value', pxMin).html(Math.round(pxMin*100)/100); } }).trigger('fixdata'); // RESCALE $divScatter.on('rescale', function(e, scale, time) { var t = d3.transition().duration(time? time : plot.settings.transitionTime); // TPM plot.vis.selectAll('.tpm_density path') .transition(t) .attr("d", d3.area() .x(function(d) { return plot.x(d.x0)==-Infinity?0:plot.x(d.x0); }) .y0(plot.settings.padding.t-10) .y1(function(d) { return Math.max(1e-35, sc_y(d.kde)); }) ); // Days plot.vis.selectAll('.days_density path') .transition(t) .attr("d", d3.area() .y(function(d) { return plot.y(d.x0); }) .x0(0) .x1(function(d) { return Math.max(1e-35, sc_x(d.kde)); }) ); // Cutoff plot.vis.selectAll("g.cutoff") .transition(t) .attr("transform", "translate(" + Math.max(1e-35, plot.x(selectedCutoff)) + "," + 0 + ")"); // P-value plot.vis.selectAll("g.p_value path.p_value") .transition(t) .attr('d', function(d) { return p_line(d); }); plot.vis.selectAll("g.p_value path.m_value") .transition(t) .attr('d', function(d) { return m_line(d); }); // Median labels var medians = []; medians.push({'clss':'dead', 'median':median[0], 'pos':median[0]>median[1]?3:2, 'f':plot.x, 'title':'Median dead ([d] TPM)'}); medians.push({'clss':'alive', 'median':median[1], 'pos':median[0]>median[1]?2:3, 'f':plot.x, 'title':'Median alive ([d] TPM)'}); medians.push({'clss':'under', 'median':median['under'], 'pos':median['under']>median['over']?4:5, 'f':plot.y, 'title':'Median dead under cut off ([d] years)'}); medians.push({'clss':'over', 'median':median['over'], 'pos':median['under']>median['over']?5:4, 'f':plot.y, 'title':'Median dead over cut off ([d] years)'}); for (var m in medians) { if (!medians.hasOwnProperty(m)) continue; setLabelPos(plot, medians[m].clss, medians[m].pos, medians[m].median.toPrecision(2), medians[m].title.replace('[d]', medians[m].median.toPrecision(3)), Math.max(1e-35, medians[m].f(medians[m].median))); } }).trigger('rescale', '', 0); $divScatter.on('setCutoff', function(e, cutoff, skipPvalue) { selectedCutoff = Math.max(quantile20, Math.min(quantile80, cutoff)); $divContainer.find('input.cutoff').val(Math.round(selectedCutoff*100)/100); d3.select(this).selectAll('.cutoff') .attr('title', 'Cut off (TPM '+Math.round(selectedCutoff*100)/100+')') .attr("xVal", selectedCutoff); $(this).trigger('fixdata', skipPvalue).trigger('rescale'); }); }