diff --git a/app/assets/javascripts/changes.js b/app/assets/javascripts/changes.js index 23cac0d..854d01f 100644 --- a/app/assets/javascripts/changes.js +++ b/app/assets/javascripts/changes.js @@ -1,843 +1,829 @@ function foldChange(id, legend_id, json, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif") { // Size var margin = {top: 10, right: 10, bottom: 75, left: 75}, width = W - margin.left - margin.right, height = H - margin.top - margin.bottom, xPadding = [0.07, 0.07], yPadding = [0.07, 0.07], axis_margin = 50; // Colors and stuff var colors = {pos:"#FF0039", neg:"#0031FF", neutral:"#666666"}, baseline_opacity = 0.5, signThres = [{value:-Math.log2(0.001), opacity:0.9, text:'***'}, {value:-Math.log2(0.01), opacity:0.7, text:'**'}, {value:-Math.log2(0.05), opacity:0.5, text:'*'}, {value:0, opacity:0.1, text:'ns'}, {value:'NA', opacity:0.3, text:'na'}]; // General functions function getValuesExtrema(values, selected, which='max') { var val = []; for (var i = 0; i < values.length; i++) { if (['-Inf', 'Inf', 'NA', 'NaN', ''].indexOf(values[i][selected]) == -1) { val.push(values[i][selected]); } } if (which == 'min') { return Math.min.apply(null, val); } else if (which == 'max') { return Math.max.apply(null, val); } }; // Buttons var buttons = d3.select("#d3-buttons"); buttons.html(""); //$.getJSON(data, function(json) { var model_choice = Object.keys(json.data), effect_choice0 = Object.keys(json.data[model_choice[0]]), comp_choice0 = Object.keys(json.data[model_choice[0]][effect_choice0[0]]), axis_choice_x = Object.keys(json.data[model_choice[0]][effect_choice0[0]][comp_choice0[0]][0]), axis_choice_y = axis_choice_x.slice(); axis_choice_y.push(''); //////////////// Draw plot //////////////// var legendContainer = d3.select("#"+legend_id).append("div") .attr('class', 'columns-1') var svgContainer = d3.select("#"+id) .style("height", (height + margin.top + margin.bottom)+"px") var svg = svgContainer.append("svg") .attr("id", "svg-figure") .attr("class", "svg-figure") .attr("width", (width + margin.left + margin.right)+"px") .attr("height",(height + margin.top + margin.bottom)+"px") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // Draw points var plot = svg.selectAll(), plotLabel = svg.selectAll(); // Draw axis var xAxis = svg.append("g") .attr("transform", "translate(0," + height + ")"); var xAxisLegend = svg.append("g") .attr("transform", "translate("+ width/2 +", "+ (height+axis_margin) +")") var yAxis = svg.append("g") var yAxisLegend = svg.append("g") .attr("transform", "translate("+ -axis_margin +", "+ height/2 +")") // Add legend var legend = legendContainer.append("div") .attr("id", "svg-legend") .style("font-family", font_family) legend.append("p") .html("Color key") var colorLegend = legend.append("ul") .style("list-style-type", "none") .style("padding-top", "25px") .selectAll("ul") .data(["pos", "neg"]) .enter().append("li") .attr("id", function(d) { return d;}) .attr("class", "legend legend-no-interaction") .attr("selected", 0) .attr("title", function(d) { return d;}) colorLegendSpan = colorLegend.append("span") var legend_svg_symsize = 15, legend_svg_width = legend_svg_symsize*signThres.length; colorLegendSpanSvg = colorLegendSpan.append("svg") .attr("width", legend_svg_width+"px") .attr("height", legend_svg_symsize+"px") .style("margin-right", "5px") .style("overflow", "visible") for (var i = 0; i < signThres.length; i++) { // (V)(°,,,°)(V) colorLegendSpanSvg.append("g") .attr("transform", "translate("+(i*legend_svg_width/signThres.length)+", -2)") .append("text") .attr("transform", "rotate(-90)") .attr("y", legend_svg_symsize) .text(function(d, j){ if (j == 0) return signThres[i]["text"]; }) colorLegendSpanSvg.append("rect") .attr("transform", "translate("+(i*legend_svg_width/signThres.length)+", 0)") .attr("width", legend_svg_symsize) .attr("height", legend_svg_symsize) .attr("stroke", "none") .attr("fill", function (d){ if (isNaN(signThres[i]["value"])) { return colors.neutral; }; return colors[d]; }) .attr("fill-opacity", signThres[i]["opacity"]) }; colorLegendSpan.append("span") .attr("id", function(d) { return "color-legend-text-"+d;}) .html(function(d) { return d;}) //////////////// Restart function //////////////// var data; var restart = function() { // Define data var selected_model = $("#modelSelect").val(), selected_effect = $('#effectSelect').val(), selected_comp = $("#compSelect").val(); data = JSON.parse(JSON.stringify(json.data[selected_model][selected_effect][selected_comp])); data.forEach(function(d, i){ d['name'] = json.names[i]; }) function filterPval(d) { return d['-log2(p-value)'] >= -Math.log2($("#pThreshold").val()) } function filterFc(d) { return Math.abs(d['log2(fold-change)']) >= Math.log2($("#fcThreshold").val()) } data = data.filter(filterPval); data = data.filter(filterFc); // Define axis var selected_x = $("#xSelect").val(), selected_y = $("#ySelect").val(); // Define axis settings var xMin = getValuesExtrema(data, selected_x, 'min'), xMax = getValuesExtrema(data, selected_x, 'max'), xValue = function(d) { if (d[selected_x] == 'Inf') { return xMax; } else if (d[selected_x] == '-Inf') { return xMin; } else if (isNaN(d[selected_x])) { return 0; } else { return d[selected_x]; }; }, xRange = xMax-xMin, xScale = d3.scaleLinear() .range([0, width]) .domain([xMin-xPadding[0]*xRange, xMax+xPadding[1]*xRange]).nice(), xMap = function(d) { return xScale(xValue(d));}; if (selected_y == '') { var sort_factor = JSON.parse(JSON.stringify(selected_x)); if (sort_factor == '-log2(p-value)') { sort_factor = 'log2(fold-change)'; }; var sMin = getValuesExtrema(data, sort_factor, 'min'), sMax = getValuesExtrema(data, sort_factor, 'max'); function sortByValue(e1, e2) { var v1 = e1[sort_factor], v2 = e2[sort_factor]; if (v1 == 'Inf') { v1 = sMax+1; }; if (v2 == 'Inf') { v2 = sMax+1; }; if (v1 == '-Inf') { v1 = sMin-1; }; if (v2 == '-Inf') { v2 = sMin-1; }; if (isNaN(v1)) { v1 = 0; }; if (isNaN(v2)) { v2 = 0; }; return v1-v2; }; function sortByIndex(e1, e2) { e1['index']-e2['index']; }; data.forEach(function(d, i){ d.index = i; }); data.sort(sortByValue); data.forEach(function(d, i){ d.sorting_index = i; }); data.sort(sortByIndex); selected_y = 'sorting_index'; } var yMin = getValuesExtrema(data, selected_y, 'min'), yMax = getValuesExtrema(data, selected_y, 'max'), yValue = function(d) { if (d[selected_y] == 'Inf') { return yMax; } else if (d[selected_y] == '-Inf') { return yMin; } else if (isNaN(d[selected_y])) { return 0; } else { return d[selected_y]; }; }, yRange = yMax-yMin, yScale = d3.scaleLinear() .range([height, 0]) .domain([yMin-yPadding[0]*yRange, yMax+yPadding[1]*yRange]).nice(), yMap = function(d) { return yScale(yValue(d));}; // Update plot plot = plot.data([]); plot.exit().remove(); plot = plot .data(data) .enter().append("g") .attr("class", "plot"); // Update labels plotLabel = plotLabel.data([]); plotLabel.exit().remove(); plotLabel = plotLabel .data(data) .enter().append("g") .attr("class", "plot-label") if (selected_y == 'sorting_index') { yScale.domain([yMin, yMax]); var flagHeight = (yScale(yMin)-yScale(yMax))/data.length; yScale.range([height-flagHeight/2, flagHeight/2]); plot.attr("transform", function (d) { if (xMin < 0) { if (xValue(d) >= 0) { return "translate(" + xScale(0) + ", " + yMap(d) + ")"; } else { return "translate(" + xMap(d) + ", " + yMap(d) + ")"; } } else { return "translate("+ (xScale(xMin)-5) +", " + yMap(d) + ")"; } }) .append("rect") .attr("width", function (d){ if (xMin < 0) { if (xValue(d) >= 0) { return xMap(d)-xScale(0); } else { return xScale(0)-xMap(d); } } else { return xMap(d)-xScale(xMin)+5; } }) .attr("height", flagHeight) .attr("y", -flagHeight/2) .style("fill", function(d) { if (isNaN(d['-log2(p-value)']) || d['log2(fold-change)'] == 0 || isNaN(d['log2(fold-change)'])) { return colors.neutral; } else if (d['log2(fold-change)'] == 'Inf') { return colors.pos; } else if (d['log2(fold-change)'] == '-Inf') { return colors.neg; } else if (d['log2(fold-change)'] < 0) { return colors.neg; } return colors.pos; }) .style("stroke-opacity", 0) .style("fill-opacity", function(d) { for (var i = 0; i < signThres.length; i++) { if (isNaN(d['-log2(p-value)']) && d['-log2(p-value)'] == signThres[i].value) { return signThres[i].opacity; } if (!isNaN(d['-log2(p-value)']) && d['-log2(p-value)'] >= signThres[i].value) { return signThres[i].opacity; }; }; }) plotLabel.attr("transform", function (d) { if (xMin < 0) { if (xValue(d) >= 0) { return "translate(" + xScale(0) + ", " + yMap(d) + ")"; } else { return "translate(" + xMap(d) + ", " + yMap(d) + ")"; } } else { return "translate("+ (xScale(xMin)-5) +", " + yMap(d) + ")"; } }) .append("rect") .attr("width", function (d){ if (xMin < 0) { if (xValue(d) >= 0) { return xMap(d)-xScale(0); } else { return xScale(0)-xMap(d); } } else { return xMap(d)-xScale(xMin)+5; } }) .attr("height", flagHeight) .attr("y", -flagHeight/2) .attr("opacity", 0); plotLabel.append("text") .attr("dx", function (d){ if (xValue(d) < 0) { return xScale(0)-xMap(d); } return 0; }) .style("text-anchor", function (d) { if (xValue(d) < 0 || xMin >= 0) { return "start"; } else { return "end"; } }) .attr("display", "none") .attr("selected", false) .attr("font-family", font_family) .text(function (d, i) { var label = d.name.split(";"); if (label.length > 1) { return label[label.length-2] +";"+ label[label.length-1]; } else { return label[0]; }; }); } else { plot.attr("transform", function (d) { return "translate(" + xMap(d) + ", " + yMap(d) + ")"; }) .append("path") .attr("class", "symbol") .attr("d", d3.symbol() .type(function(d) { if ([d[selected_x], d[selected_y]].indexOf('Inf') != -1 || [d[selected_x], d[selected_y]].indexOf('-Inf') != -1) { return d3.symbolTriangle; }; return d3.symbolCircle; }) .size(function(d) { if ([d[selected_x], d[selected_y]].indexOf('Inf') != -1 || [d[selected_x], d[selected_y]].indexOf('-Inf') != -1) { return 100; }; return 200; })) .style("fill", function(d) { if (isNaN(d['-log2(p-value)']) || d['log2(fold-change)'] == 0 || isNaN(d['log2(fold-change)'])) { return colors.neutral; } else if (d['log2(fold-change)'] == 'Inf') { return colors.pos; } else if (d['log2(fold-change)'] == '-Inf') { return colors.neg; } else if (d['log2(fold-change)'] < 0) { return colors.neg; } return colors.pos; }) .style("stroke-opacity", 0) .style("fill-opacity", function(d) { for (var i = 0; i < signThres.length; i++) { if (isNaN(d['-log2(p-value)']) && d['-log2(p-value)'] == signThres[i].value) { return signThres[i].opacity; } if (!isNaN(d['-log2(p-value)']) && d['-log2(p-value)'] >= signThres[i].value) { return signThres[i].opacity; }; }; }) .attr("transform", function (d){ if (d[selected_y] == "-Inf") { return "rotate(180)"; }; if (d[selected_x] == "-Inf") { return "rotate(-90)"; }; if (d[selected_x] == "Inf") { return "rotate(90)"; }; }); // Update labels plotLabel.attr("transform", function (d) { return "translate(" + xMap(d) + ", " + yMap(d) + ")"; }) .append("path") .attr("class", "symbol") .attr("d", d3.symbol() .type(function(d) { if ([d[selected_x], d[selected_y]].indexOf('Inf') != -1 || [d[selected_x], d[selected_y]].indexOf('-Inf') != -1) { return d3.symbolTriangle; }; return d3.symbolCircle; }) .size(function(d) { if ([d[selected_x], d[selected_y]].indexOf('Inf') != -1 || [d[selected_x], d[selected_y]].indexOf('-Inf') != -1) { return 100; }; return 200; })) .attr("opacity", 0) .attr("transform", function (d){ if (d[selected_y] == "-Inf") { return "rotate(180)"; }; if (d[selected_x] == "-Inf") { return "rotate(-90)"; }; if (d[selected_x] == "Inf") { return "rotate(90)"; }; }); plotLabel.append("text") .attr("dy", function (d){ return -10; }) .style("text-anchor", function (d) { if (xMap(d) <= width/2) { return "start"; } else { return "end"; } }) .attr("display", "none") .attr("selected", false) .attr("font-family", font_family) .text(function (d, i) { var label = d.name.split(";"); if (label.length > 1) { return label[label.length-2] +";"+ label[label.length-1]; } else { return label[0]; }; }); } showLabels(); displayLabels("plot-label"); // Update axis xAxis.selectAll("*").remove(); xAxisLegend.selectAll("*").remove(); yAxis.selectAll("*").remove(); yAxisLegend.selectAll("*").remove(); svg.selectAll(".frame").remove(); svg.selectAll(".info-line").remove(); // Add axis xAxis.call(d3.axisBottom(xScale).ticks(10)); xAxisLegend.append("text") .text(selected_x) .attr("font-family", font_family) .style("text-anchor", "middle"); if (selected_y != 'sorting_index') { yAxis.call(d3.axisLeft(yScale).ticks(10)) yAxisLegend.append("text") .text(selected_y) .style("text-anchor", "middle") .attr("font-family", font_family) .attr("transform", "rotate(-90)"); // Close the plot svg.append("g") .attr("class", "frame") .attr("transform", "translate(0, 0)") .call(d3.axisTop(xScale).ticks(0) .tickSize(0, 0)); svg.append("g") .attr("class", "frame") .attr("transform", "translate("+width+", 0)") .call(d3.axisRight(yScale).ticks(0) .tickSize(0, 0)); } // Additional lines if (selected_x == 'log2(fold-change)') { svg.append("g") .attr("class", "info-line") .attr("transform", "translate(" + xScale(0) + ", 0)") .call(d3.axisLeft(yScale).ticks(0) .tickSize(0, 0)); }; if (selected_y == 'log2(fold-change)') { svg.append("g") .attr("class", "info-line") .attr("transform", "translate(0," + yScale(0) + ")") .call(d3.axisBottom(xScale).ticks(0) .tickSize(0, 0)); }; if (selected_x == '-log2(p-value)') { for (var i = 0; i < signThres.length; i++) { if (!isNaN(signThres[i].value) && signThres[i].value != 0 && xScale(signThres[i].value) > 0) { svg.append("g") .attr("class", "info-line") .style("stroke-dasharray", ("5, 5")) .attr("transform", "translate(" + xScale(signThres[i].value) + ", 0)") .call(d3.axisLeft(yScale).ticks(0) .tickSize(0, 0)); }; }; }; if (selected_y == '-log2(p-value)') { for (var i = 0; i < signThres.length; i++) { if (!isNaN(signThres[i].value) && signThres[i].value != 0 && yScale(signThres[i].value) < height) { svg.append("g") .attr("class", "info-line") .attr("transform", "translate(0," + yScale(signThres[i].value) + ")") .call(d3.axisBottom(xScale).ticks(0) .tickSize(0, 0)); }; }; }; // Update legend text var color_legend_text = selected_comp.split("-"); $("#color-legend-text-pos").html("more abundant in "+color_legend_text[1]); $("#color-legend-text-neg").html("more abundant in "+color_legend_text[0]); } var exportData = function() { var dataBlob= new Blob([['name\tmean abundance\tlog2(fold-change)\t-log2(p-value)', data.map(function(d){return d.name+'\t'+d['mean abundance']+'\t'+d['log2(fold-change)']+'\t'+d['-log2(p-value)'];}).join('\n')].join('\n')], {type: 'txt'}), link = document.createElement('a'), event = new MouseEvent('click'); link.href = window.URL.createObjectURL(dataBlob); link.download = 'fold_change.txt'; link.dataset.downloadurl = ['txt', link.download, link.href].join(':'); link.dispatchEvent(event); } // Add download button for data $("#export-data-btn").remove(); d3.select("#export-btn") .append("li") .attr("id", "export-data-btn") .append("a") .html("Data TXT") .on("click", exportData) // Labels functions function displayLabels (id) { $("."+id).on("mouseenter", function(d) { d3.select(this.childNodes[1]).attr("display", "inline"); }); $("."+id).on("mouseleave", function(d) { if (this.childNodes[1].getAttribute("selected") == "false") { d3.select(this.childNodes[1]).attr("display", "none"); }; }); $("."+id).on("click", function(d) { if (this.childNodes[1].getAttribute("selected") == "false") { d3.select(this.childNodes[1]).attr("display", "inline"); d3.select(this.childNodes[1]).attr("selected", true); } else { d3.select(this.childNodes[1]).attr("display", "none"); d3.select(this.childNodes[1]).attr("selected", false); } }); }; // Model update function var updateEffect = function() { var keys = Object.keys(json.data[$('#modelSelect').val()]) new_data = [...Array(keys.length)]; for (var i = 0; i < keys.length; i++) { new_data[i] = {label:keys[i], value:keys[i]} } $('#effectSelect').multiselect('dataprovider', new_data) .multiselect("refresh"); updateComp(); restart(); } // Effect update function var updateComp = function() { var keys = Object.keys(json.data[$('#modelSelect').val()][$('#effectSelect').val()]) new_data = [...Array(keys.length)]; for (var i = 0; i < keys.length; i++) { new_data[i] = {label:keys[i], value:keys[i]} } $('#compSelect').multiselect('dataprovider', new_data) .multiselect("refresh"); restart(); } // Display labels button var showLabels = function() { var label = d3.selectAll(".plot-label").select(function(){ return this.childNodes[1];}); if ($("#labelButton").is(':checked')) { label.attr("display", "inline"); label.attr("selected", true); } else { label.attr("display", "none"); label.attr("selected", false); }; }; appendLabelCheckBox(buttons, "Show labels", "Labels", "labelButton", showLabels) // Search in labels var searchLabels = function() { - $("#labelButton").attr("checked", false); - var key = $("#searchInput").val().toUpperCase(); - if (key != '') { - var selected = d3.selectAll(".plot-label").filter(function(){return this.__data__.name.toUpperCase().indexOf(key.toUpperCase()) != -1 }).select(function(){ return this.childNodes[1];}); - non_selected = d3.selectAll(".plot-label").filter(function(){return this.__data__.name.toUpperCase().indexOf(key.toUpperCase()) == -1 }).select(function(){ return this.childNodes[1];}); - selected.attr("display", "inline"); - selected.attr("selected", true); - non_selected.attr("display", "none"); - non_selected.attr("selected", false); - } else { - to_free = d3.selectAll(".plot-label").select(function(){return this.childNodes[1];}); - to_free.attr("display", "none"); - to_free.attr("selected", false); - }; - }; - + searchLabels2("#labelButton", "#searchInput", ".plot-label") + } appendSearchInput(buttons, "Search", "searchInput", searchLabels); // Select axis var xSelect = buttons.append("div") .attr("title", "Chose X axis.") .attr("class", "form-group") xSelect.append("label") .html("X axis") xSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "xSelect") .attr("class", "form-control multiselect figtool-multiselect") .selectAll("option") .data(axis_choice_x) .enter().append("option") .text(function (d){ return d;}); $('#xSelect').val(axis_choice_x[0]) .on('change', restart); var ySelect = buttons.append("div") .attr("title", "Chose Y axis.") .attr("class", "form-group") ySelect.append("label") .html("Y axis") ySelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "ySelect") .attr("class", "form-control multiselect figtool-multiselect") .selectAll("option") .data(axis_choice_y) .enter().append("option") .text(function (d){ return d;}); $('#ySelect').val(axis_choice_y[1]) .on('change', restart); // Select model var modelSelect = buttons.append("div") .attr("title", "Chose model") .attr("class", "form-group") modelSelect.append("label") .html("Model") modelSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "modelSelect") .attr("class", "form-control multiselect figtool-multiselect") .selectAll("option") .data(Object.keys(json.data)) .enter().append("option") .text(function (d){ return d;}); $('#modelSelect').on('change', updateEffect); // Select effect var effectSelect = buttons.append("div") .attr("title", "Chose effect") .attr("class", "form-group") effectSelect.append("label") .html("Effect") effectSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "effectSelect") .attr("class", "form-control multiselect figtool-multiselect") .selectAll("option") .data(Object.keys(json.data[$('#modelSelect').val()])) .enter().append("option") .text(function (d){ return d;}); $('#effectSelect').on('change', updateComp); // Select comparison var compSelect = buttons.append("div") .attr("title", "Chose comparison") .attr("class", "form-group") compSelect.append("label") .html("Comparison") compSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "compSelect") .attr("class", "form-control multiselect figtool-multiselect") .selectAll("option") .data(Object.keys(json.data[$('#modelSelect').val()][$('#effectSelect').val()])) .enter().append("option") .text(function (d){ return d;}); $('#compSelect').on('change', restart); // Select p-value cutoff var pThreshold = buttons.append("div") .attr("title", "Set a p-value cutoff.") .attr("class", "form-group") pThreshold.append("label") .html("P-value cutoff") pThreshold.append("input") .attr("id", "pThreshold") .attr("type", "number") .attr("class", "form-control form-number-field") .attr("min", 0) .attr("max", 1) .attr("step", 0.001) .attr("value", 1) .on("change", restart); // Select fold-change cutoff var fcThreshold = buttons.append("div") .attr("title", "Set a fold-change cutoff.") .attr("class", "form-group") fcThreshold.append("label") .html("Fold-change cutoff") fcThreshold.append("input") .attr("id", "fcThreshold") .attr("type", "number") .attr("class", "form-control form-number-field") .attr("value", 0) .on("change", restart); setMultiselect('.figtool-multiselect'); //resizeMultiselect('#d3-buttons', 1, '#d3-buttons', false); restart(); //}); }; diff --git a/app/assets/javascripts/correlationNetwork.js b/app/assets/javascripts/correlationNetwork.js index ecf6810..c9ec287 100644 --- a/app/assets/javascripts/correlationNetwork.js +++ b/app/assets/javascripts/correlationNetwork.js @@ -1,743 +1,727 @@ function correlationNetwork(id, legend_id, json, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif", color_palette = d3.schemeCategory10) { // Size var margin = {top: 10, right: 10, bottom: 40, left: 10}, width = W - margin.left - margin.right, height = H - margin.top - margin.bottom; // Restrictions drag_size_limit = 80; // Colors, symbols and scaling var colors = d3.scaleOrdinal(color_palette), link_color = [["#999999", "Pos. correlation"], ["#ff3385", "Neg. correlation"]], symbols = d3.scaleOrdinal([d3.symbolCircle, d3.symbolSquare, d3.symbolTriangle, d3.symbolStar, d3.symbolDiamond, d3.symbolCross]), legend_svg_symsize = 15, rRange = [6, 24], size_legend_data = [15, 10, 5], wRange = [0.1*rRange[0], 0.9*rRange[0]], sizeScale = d3.scaleLinear() .range(rRange), widthScale = d3.scaleLinear() .range(wRange), attractionScale = d3.scaleLinear() .range([100, 50]), repulsionScale = d3.scaleLinear() .range([50, 100]), chargeScale = d3.scaleLinear() .range([-100, -200]), sizeOptions = [{"text":"", "value":"", "title":""}, {"text":"Degree", "value":"degree", "title":"Node degree"}, {"text":"Mean abundance", "value":"mean", "title":"Mean abundance"}, {"text":"Max abundance", "value":"max", "title":"Max abundance"}, {"text":"Min abundance", "value":"min", "title":"Min abundance"} ]; oRange = [0.1, 0.3], p_value_legend_data = [[0.001, "***"], [0.01, "**"], [0.05, "*"], [1, "ns"], ["NA", "na"]], stat_settings = {"range":[0.2, 0.6, 0.8, 1], "domain":[1, 0.05, 0.01, 0.001], "text":["ns", "*", "**", "***"]}, opacity = d3.scaleLinear() .range(stat_settings.range) .domain(stat_settings.domain), colorBarSize = 10; // General functions function getSizeExtrema(json, fun, extrema="max", absolute=false) { var values = json.map(function(d){ if (absolute) { return Math.abs(d[fun]); } return d[fun]; }) if (extrema == "max") { return Math.max.apply(null, values); } else if (extrema == "min") { return Math.min.apply(null, values); }; }; function getNodeDegree(links, nodes) { var degree = {}; links.forEach(function (d, i) { if (Object.keys(degree).indexOf(d.target.toString()) == -1) { degree[d.target.toString()] = 1; } else { degree[d.target.toString()] = degree[d.target.toString()]+1; }; if (Object.keys(degree).indexOf(d.source.toString()) == -1) { degree[d.source.toString()] = 1; } else { degree[d.source.toString()] = degree[d.source.toString()]+1; }; }); nodes.forEach(function (d) { if (Object.keys(degree).indexOf(d.id) != -1) { d["degree"] = degree[d.id]; } else { d["degree"] = 0; // Just to avoid scale domain to start at 0... } }); }; function unique(value, index, self) { return self.indexOf(value) === index; } // Buttons var buttons = d3.select("#d3-buttons") buttons.html(""); //$.getJSON(data, function(json) { //////////////// Simulation //////////////// var simulation = d3.forceSimulation() .force("center", d3.forceCenter(width / 2, height / 2)) .force("y", d3.forceY()) .force("x", d3.forceX()); var ticked = function() { link .attr("x1", function(d) { return Math.max(0, Math.min(d.source.x, width)); }) .attr("y1", function(d) { return Math.max(0, Math.min(d.source.y, height)); }) .attr("x2", function(d) { return Math.max(0, Math.min(d.target.x, width)); }) .attr("y2", function(d) { return Math.max(0, Math.min(d.target.y, height)); }); node .attr("transform", function(d) { return "translate(" + Math.max(0, Math.min(d.x, width)) + "," + Math.max(0, Math.min(d.y, height)) + ")"; }); nodeLabel .attr("transform", function(d, i) { return "translate(" + Math.max(0, Math.min(d.x, width)) + "," + Math.max(0, Math.min(d.y, height)) + ")"; }); }; function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.25).restart(); d.fx = d.x; d.fy = d.y; } function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } //////////////// Draw links and nodes //////////////// var legendContainer = d3.select("#"+legend_id).append("div") .attr('class', 'columns-2') var svgContainer = d3.select("#"+id) .style("height", (height + margin.top + margin.bottom)+"px") var svg = svgContainer.append("svg") .attr("id", "svg-figure") .attr("class", "svg-figure network-well") .attr("width", (width + margin.left + margin.right)+"px") .attr("height",(height + margin.top + margin.bottom)+"px") .style("pointer-events", "all") .call(d3.zoom() .scaleExtent([1, 4]) .duration(1000) .translateExtent([[margin.left, margin.top], [width + margin.right, height + margin.top + margin.bottom]]) .on("zoom", zoomed)); var g = svg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); function zoomed() { g.attr("transform", d3.event.transform); }; // Draw links var link = g.append("g") .selectAll(); // Draw Nodes var node = g.append("g") .selectAll("g"); var nodeLabel = g.append("g") .selectAll("g"); // Add legend var legend = legendContainer.append("div") .attr("id", "svg-legend") .style("font-family", font_family) var colorLegend = legend.append("ul") .style("list-style-type", "none") .style("padding-top", "25px") .selectAll("ul"); legend.append("div") .style("border-top", "solid #ccc 1px") var symbolLegend = legend.append("ul") .style("list-style-type", "none") .selectAll("ul"); legend.append("div") .style("border-top", "solid #ccc 1px") var linkLegend = legend.append("ul") .style("list-style-type", "none") .selectAll("ul"); //////////////// NODES COLORS //////////////// var setSymbolColor = function() { var selected_model = $('#modelSelect').val(), selected_effect = $('#effectSelect').val(), color_domain = json.nodes.map(function (d){return d.stat[selected_model][selected_effect]['highest-mean'];}).filter(unique); if (selected_model != '') colors.domain(color_domain); // Update symbols color d3.selectAll(".coloured-symbol") .style("fill", function (d){ if (selected_model != '') return colors(d.stat[selected_model][selected_effect]['highest-mean']); return colors(''); }) .attr("fill-opacity", function (d){ if (selected_model != '') return opacity(d.stat[selected_model][selected_effect]['p-value']); return 1; }); // Update color legend colorLegend = colorLegend.data([]); colorLegend.exit().remove(); colorLegend = colorLegend .data(color_domain) .enter().append("li") .attr("id", function(d) { return d;}) .attr("class", "legend legend-no-interaction") .attr("selected", 0) .attr("title", function(d) { return d;}) colorLegendSpan = colorLegend.append("span") var legend_svg_width = legend_svg_symsize*p_value_legend_data.length; colorLegendSpanSvg = colorLegendSpan.append("svg") .attr("width", legend_svg_width+"px") .attr("height", legend_svg_symsize+"px") .style("margin-right", "5px") .style("overflow", "visible") for (var i = 0; i < p_value_legend_data.length; i++) { // (V)(°,,,°)(V) colorLegendSpanSvg.append("g") .attr("transform", "translate("+(i*legend_svg_width/p_value_legend_data.length)+", -2)") .append("text") .attr("transform", "rotate(-90)") .attr("y", legend_svg_symsize) .text(function(d, j){ if (j == 0 && color_domain.length > 0) return p_value_legend_data[i][1]; }) colorLegendSpanSvg.append("rect") .attr("transform", "translate("+(i*legend_svg_width/p_value_legend_data.length)+", 0)") .attr("width", legend_svg_symsize) .attr("height", legend_svg_symsize) .attr("stroke", "none") .attr("fill", function (d, j){ if (p_value_legend_data[i][0] == "NA") { return "lightgrey"; }; return colors(d); }) .attr("fill-opacity", function (d, j){ if (p_value_legend_data[i][0] == "NA") { return 1; }; return opacity(p_value_legend_data[i][0]); }) }; colorLegendSpan.append("span") .html(function(d) { return d;}) }; //////////////// NODES SHAPE AND SIZE //////////////// var setSymbolSize = function() { var selected_size_factor = $("#sizeSelect").val(), symbols_domain = json.nodes.map(function (d){return d['data-type'];}).filter(unique), size = {}; symbols.domain(symbols_domain); if (selected_size_factor != '') { for (var i = 0; i < symbols_domain.length; i++) { size[symbols_domain[i]] = {value:json.nodes.filter(function (d){return d['data-type'] == symbols_domain[i];}).map(function(d){return Number(d[selected_size_factor]);})}; size[symbols_domain[i]]['min'] = Math.min.apply(null, size[symbols_domain[i]]['value']); size[symbols_domain[i]]['max'] = Math.max.apply(null, size[symbols_domain[i]]['value']); } } // Set symbols size d3.selectAll(".symbol") .transition().duration(400) .attr("d", d3.symbol() .type(function (d){ return symbols(d['data-type']);}) .size(function (d){ if (selected_size_factor != '') { sizeScale.domain([size[d['data-type']]['min'], size[d['data-type']]['max']]); d.r = sizeScale(d[selected_size_factor]); } else { d.r = 20; } return d.r*d.r; })) .attr('radius', function(d){return d.r;}); // Set size-dependent simulation variables var symbol_data = []; d3.selectAll(".symbol").each(function(){return symbol_data.push(d3.select(this).attr("radius"));}); var force_size_factor = (Math.log2(symbol_data.length+1)+1)/2; chargeScale.domain([Math.min.apply(null, symbol_data), Math.max.apply(null, symbol_data)]); if (force_size_factor == 0) { force_size_factor = 1; }; simulation .force("collide", d3.forceCollide() .radius(function (d){ return d.r; }) .iterations(2) .strength(1/force_size_factor)) .force("charge", d3.forceManyBody() .strength(function (d){ return chargeScale(d.r)/force_size_factor; })); // Update symbol legend symbolLegend = symbolLegend.data([]); symbolLegend.exit().remove(); symbolLegend = symbolLegend .data(symbols_domain) .enter().append("li") .attr("id", function(d) { return d;}) .attr("class", "legend legend-no-interaction") .attr("selected", 0) .attr("title", function(d) { return d;}) symbolLegendSpan = symbolLegend.append("span") var legend_svg_width = legend_svg_symsize*size_legend_data.length; symbolLegendSpanSvg = symbolLegendSpan.append("svg") .attr("width", legend_svg_width+"px") .attr("height", legend_svg_symsize+"px") .style("margin-right", "5px") .style("overflow", "visible") for (var i = 0; i < size_legend_data.length; i++) { // (V)(°,,,°)(V) symbolLegendSpanSvg.append("path") .attr("transform", "translate("+(i*legend_svg_width/size_legend_data.length)+", "+legend_svg_symsize/2+")") .attr("d", d3.symbol() .type(function (d){ return symbols(d);}) .size(size_legend_data[i]*size_legend_data[i])) .attr("stroke", "#333") .attr("fill-opacity", 0) }; symbolLegendSpan.append("span") .html(function(d) { return d;}) } //////////////// RESTART //////////////// var restart = function() { var weight_thres = [$("#pThresRange").val(), $("#nThresRange").val()], weight_p_value_thres = $("#weightPvalThres").val(), link_data = JSON.parse(JSON.stringify(json.links.filter(function(d){ if (d.weight >= 0) { return (d.weight >= weight_thres[0] && d['p-value'] <= weight_p_value_thres); } return (d.weight < -weight_thres[1] && d['p-value'] <= weight_p_value_thres); }))); getNodeDegree(link_data, json.nodes); var kept_node = link_data.map(function(d){return d['source'];}).concat(link_data.map(function(d){return d['target'];})).filter(unique), node_data = JSON.parse(JSON.stringify(json.nodes.filter(function(d){return kept_node.indexOf(d.id) != -1;}))), link_width = {weight:link_data.map(function(d){return d.weight;})}; link_width['min'] = Math.min.apply(null, link_width.weight.map(Math.abs)); link_width['max'] = Math.max.apply(null, link_width.weight.map(Math.abs)); widthScale.domain([link_width['min'], link_width['max']]); attractionScale.domain([link_width['min'], link_width['max']]); // Update links link = link.data([]); link.exit().remove(); link = link .data(link_data); link = link.enter() .append("line") .attr("class", "link-line") .attr("stroke-linecap", "round") .attr("stroke", function (d) { if (Number(d.weight) >= 0) { return link_color[0][0]; } else { return link_color[1][0]; }; }) .attr("stroke-width", function (d) { return widthScale(Math.abs(d.weight)); }); // Update nodes node = node.data([]); node.exit().remove(); node = node .data(node_data); node = node.enter() .append("g") .append("path") .attr("class", "coloured-symbol symbol") .attr("stroke", "white"); // Update nodes labels nodeLabel = nodeLabel.data([]); nodeLabel.exit().remove(); nodeLabel = nodeLabel .data(node_data); nodeLabel = nodeLabel.enter() .append("g") .attr('class', 'node-label'); nodeLabel.append("path") .attr("class", "symbol") .attr("stroke", "#333333") .attr("fill-opacity", 0) .attr("stroke-opacity", 0); nodeLabel.append("text") .text(function (d) { var label = d.name.split(";"); if (label.length > 1) return label[label.length-2] +";"+ label[label.length-1]; return label[0]; }) .attr("text-anchor", "start") .attr("font-family", font_family) .attr("display", "none") .attr("selected", false); // Add interactive option if not too many nodes //if (node_data.length < drag_size_limit) { node.call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); nodeLabel.call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); //}; setSymbolSize(); setSymbolColor(); - displayLabels(); + displayLabels(".node-label"); showLabels(); // Restart simulation simulation = simulation .nodes(node_data) .on("tick", ticked); var data_size_factor = 1+node_data.length*node_data.length; simulation .force("link", d3.forceLink() .id(function(d) { return d.id;}) .distance(function(d) { if (d.weight >= 0) { return attractionScale(Math.abs(d.weight))/data_size_factor; } else { return repulsionScale(Math.abs(d.weight))/data_size_factor; }; }) .strength(function(d) { if (d.weight >= 0) { return repulsionScale(Math.abs(d.weight))/100; } else { return attractionScale(Math.abs(d.weight))/100; }; })); simulation.force("link") .links(link_data); simulation.alpha(0.5).restart(); // Update links legend linkLegend = linkLegend.data([]); linkLegend.exit().remove(); linkLegend = linkLegend .data(link_color) .enter().append("li") .attr("id", function(d) { return d[1];}) .attr("class", "legend legend-no-interaction") .attr("selected", 0) .attr("title", function(d) { return d[1];}) linkLegendSpan = linkLegend.append("span") linkLegendSpanSvg = linkLegendSpan.append("svg") .attr("width", "25px") .attr("height", "10px") .style("margin-right", "5px") .style("overflow", "visible") linkLegendSpanSvg.append("rect") .attr("width", 25) .attr("height", 5) .attr("y", 2.5) .attr("stroke", "none") .attr("fill", function(d){return d[0]}) linkLegendSpan.append("span") .html(function(d, i) { return ['Coeff. > ', 'Coeff. < -'][i]+weight_thres[i];}) } // Nodes labels functions - function displayLabels () { - var selected_label = $(".node-label"); - selected_label.on("mouseenter", function() { + function displayLabels (labels) { + $(labels).on("mouseenter", function() { d3.select(this.childNodes[1]).attr("display", "inline"); d3.select(this.childNodes[0]).attr("stroke-opacity", 1); }); - selected_label.on("mouseleave", function() { + $(labels).on("mouseleave", function() { if (this.childNodes[1].getAttribute("selected") == "false") { d3.select(this.childNodes[1]).attr("display", "none"); }; d3.select(this.childNodes[0]).attr("stroke-opacity", 0); }); - selected_label.on("click", function() { + $(labels).on("click", function() { if (this.childNodes[1].getAttribute("selected") == "false") { d3.select(this.childNodes[1]).attr("display", "inline"); d3.select(this.childNodes[1]).attr("selected", true); } else { d3.select(this.childNodes[1]).attr("display", "none"); d3.select(this.childNodes[1]).attr("selected", false); } }); }; // Model update function var updateEffect = function() { var keys = Object.keys(json.legend[$('#modelSelect').val()]) new_data = [...Array(keys.length)]; for (var i = 0; i < keys.length; i++) { new_data[i] = {label:keys[i], value:keys[i]} } $('#effectSelect').multiselect('dataprovider', new_data) .multiselect("refresh"); setSymbolColor(); } // Display labels button var showLabels = function() { var label_text = d3.selectAll(".node-label").select(function(){ return this.childNodes[1];}); if ($("#labelButton").is(':checked')) { label_text.attr("display", "inline"); label_text.attr("selected", true); } else { label_text.attr("display", "none"); label_text.attr("selected", false); }; }; + appendLabelCheckBox(buttons, "Show labels", "Labels", "labelButton", showLabels) + // Search in labels var searchLabels = function() { - $("#labelButton").attr("checked", false); - var key = $("#searchInput").val().toUpperCase(); - if (key != '') { - var selected = d3.selectAll(".node-label").filter(function(){return this.__data__.name.toUpperCase().indexOf(key.toUpperCase()) != -1 }); - non_selected = d3.selectAll(".node-label").filter(function(){return this.__data__.name.toUpperCase().indexOf(key.toUpperCase()) == -1 }); - selected.select(function(){ return this.childNodes[1];}).attr("display", "inline"); - selected.select(function(){ return this.childNodes[1];}).attr("selected", true); - non_selected.select(function(){ return this.childNodes[1];}).attr("display", "none"); - non_selected.select(function(){ return this.childNodes[1];}).attr("selected", false); - } else { - to_free = d3.selectAll(".node-label"); - to_free.select(function(){return this.childNodes[1];}).attr("display", "none"); - to_free.select(function(){return this.childNodes[1];}).attr("selected", false); - }; - }; - - - // Label button and search - appendLabelCheckBox(buttons, "Show labels", "Labels", "labelButton", showLabels) + searchLabels2("#labelButton", "#searchInput", ".node-label") + } appendSearchInput(buttons, "Search", "searchInput", searchLabels); // Select model var modelSelect = buttons.append("div") .attr("title", "Chose model") .attr("class", "form-group") modelSelect.append("label") .html("Color (model)") modelSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "modelSelect") .attr("class", "form-control multiselect figtool-multiselect") .selectAll("option") .data(Object.keys(json.legend)) .enter().append("option") .text(function (d){ return d;}); $('#modelSelect').on('change', updateEffect); // Select effect var effectSelect = buttons.append("div") .attr("title", "Chose effect") .attr("class", "form-group") effectSelect.append("label") .html("Color (effect)") effectSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "effectSelect") .attr("class", "form-control multiselect figtool-multiselect") .selectAll("option") .data(Object.keys(json.legend[$('#modelSelect').val()])) .enter().append("option") .text(function (d){ return d;}); $('#effectSelect').on('change', setSymbolColor); // Select size var sizeSelect = buttons.append("div") .attr("title", "Chose size variable") .attr("class", "form-group") sizeSelect.append("label") .html("Radius") sizeSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "sizeSelect") .attr("class", "form-control multiselect figtool-multiselect") .selectAll("option") $('#sizeSelect').on('change', setSymbolSize); // Button for link weight threshold var weightPvalThres = buttons.append("div") .attr("title", "Set a p-value cutoff for links.") .attr("class", "form-group") weightPvalThres.append("label") .html("Links p-value cutoff") weightPvalThres.append("input") .attr("id", "weightPvalThres") .attr("type", "number") .attr("class", "form-control form-number-field") .attr("min", 0) .attr("max", 1) .attr("step", 0.001) .attr("value", 1) .on("change", restart); var pThresRange = buttons.append("span") .attr("title", "Cut-off for positive correlations.") pThresRange.append("label") .append("p") .html("Pos. corr. cut-off ().") pThresRange.append("input") .attr("id", "pThresRange") .attr("type", "range") .attr("class", "full-width") .attr("min", 0) .attr("max", 1) .attr("step", 0.05) .on("change", restart); $("#pThresRange").val('0.75') var nThresRange = buttons.append("span") .attr("title", "Cut-off for negative correlations.") nThresRange.append("label") .append("p") .html("Neg. corr. cut-off ().") nThresRange.append("input") .attr("id", "nThresRange") .attr("type", "range") .attr("class", "full-width") .attr("min", 0) .attr("max", 1) .attr("step", 0.05) .on("change", restart); $("#nThresRange").val('0.75') setMultiselect('.figtool-multiselect'); //resizeMultiselect('#d3-buttons', 1, '#d3-buttons', false); $('#sizeSelect').multiselect('dataprovider', sizeOptions) .multiselect("refresh"); restart(); //}); }; diff --git a/app/assets/javascripts/figures.js b/app/assets/javascripts/figures.js index 55ddfd9..8caf182 100644 --- a/app/assets/javascripts/figures.js +++ b/app/assets/javascripts/figures.js @@ -1,93 +1,148 @@ function exportFigure(id, format, filename) { var svgData = d3.select(id).node().outerHTML, type = 'txt'; if (format == "svg") { type = 'image/svg+xml'; } else if (format == "html") { type = 'html'; }; var svgBlob= new Blob([svgData], {type: type}), link = document.createElement('a'), event = new MouseEvent('click'); link.href = window.URL.createObjectURL(svgBlob); link.download = filename+'.'+format; link.dataset.downloadurl = [type, link.download, link.href].join(':'); link.dispatchEvent(event); }; -// FIGURE DESCRIPTION -function showDescription() { - document.getElementById("sidebar").style.cssText="width:40vw;overflow:auto;padding-left:10px;padding-right:10px;"; - document.getElementById("description").style.cssText="visibility:visible;opacity:1;"; - $("#sidebar")[0].style["height"]=$("#figure-container").height()+'px'; - document.getElementById("sidebar-icon").style.cssText="transform:rotate(180deg)"; -}; - -function hideDescription() { - document.getElementById("sidebar").style.cssText="width:20px;overflow:hidden;padding-left:2px;padding-right:2px;"; - document.getElementById("description").style.cssText="opacity:0;visibility:hidden;"; - $("#sidebar")[0].style["height"]=$("#figure-container").height()+'px'; - document.getElementById("sidebar-icon").style.cssText="transform:rotate(0deg)"; -}; - // FIGURE BUTTONS/CONTROLS function appendRange(appendTo, title, label, id, min, max, value, onchange) { var range = appendTo.append("div") .attr("title", title) .attr("class", "form-group") range.append("label") .html(label) range.append("input") .attr('class', 'full-width') .attr("id", id) .attr("type", "range") .attr("min", min) .attr("max", max) .attr("value", value) .on("change", onchange); } function appendLabelCheckBox(appendTo, title, label, id, onclick) { var labelCheckBox = appendTo.append("div") .attr("title", title) .attr("class", "form-bool") .append("label") labelCheckBox.append("input") .attr("id", id) .attr('class', 'form-check-input') .attr("type", "checkbox") .on("click", onclick); labelCheckBox.append("p") .html(label); } function appendSearchInput(appendTo, title, id, onclick) { var searchInput = appendTo.append("div") .attr("title", title) .attr("class", "form-group has-feedback") searchInput.append("label") .attr("class", "control-label sr-only") searchInput.append("input") .attr("id", id) .attr("type", "text") .attr("class", "form-control") .attr("placeholder", "Search") .on("keyup", onclick); //searchInput.append("i") // .attr("class", "form-control-feedback fa fa-search") } + function searchLabels1(button, input, labels) { + $(button).attr("checked", false); + var key = $(input).val().toUpperCase().split(' AND '); + if (key != '') { + var selected = d3.selectAll(labels).filter(function(d){ + var matches=false; + for (var i=0;i 1) ? i-1 : 0; var header_els = lines[i].split("\t") var pos_col = 0 for (var k = 0; k max_val){ max_val = n } } if (max_val > 0){ var new_data=[]; for (var j=1 ; j < max_val+1; j++) { new_data.push({label: j, value: j}); } populate_multiselect(id, new_data, value, url) } }; // console.log(file); reader.readAsText(file); }else{ $.ajax({ url: url + "&col_name=" + col_name, type: "get", dataType: "html", beforeSend: function(){ }, success: function(new_data){ populate_multiselect(id, JSON.parse(new_data), value, url) }, error: function(e){ } }); } }; function setSelectFromFileRow(id, file, value = 0, add_blank = null, url = null) { var new_data = []; if (!url){ var reader = new FileReader; reader.onload = function (event) { var lines = event.target.result.split(/\r\n|\r|\n/g); - var i=0 + var i = 0; for (i = 0; i < (lines.length-1); i++) { if (lines[i].charAt(0) != '#') break; } - i = (i > 1) ? i-1 : 0; + i = (i > 0) ? i-1 : 0; var content = lines[i].split("\t") - if (add_blank) new_data.push({label:"", value:""}); if (content.length > 0){ for (var i in content) { new_data.push({label:content[i], value:content[i]}); }; } populate_multiselect(id, new_data, value, url) }; reader.readAsText(file); }else{ $.ajax({ - url: url + "&add_blank=" + ((add_blank == null) ? '' : '1'), + url: url + ((add_blank == null) ? '' : '&add_blank=1'), type: "get", dataType: "html", beforeSend: function(){ }, success: function(new_data){ populate_multiselect(id, JSON.parse(new_data), value, url) }, error: function(e){ } }); } }; // Transform a select input into a nice multiselect field function setMultiselect(id, nonSelectedText = 'Select', where){ $(id).multiselect({ includeSelectAllOption: false, // Would append a value currently not supported by genocrunch_console nonSelectedText: nonSelectedText, numberDisplayed: 1, maxHeight: 150, buttonClass: 'btn btn-secondary', templates: { li: '
  • ', } }); - $(id + ".form-control").addClass('hidden'); + $(id + ".form-control").addClass('hidden'); } function fold_section(fold_bar_class) { $(fold_bar_class).click(function(){ var e = $(this).parent().parent().parent().parent().parent().children().filter(".card-block").first(); //(".field_group_content").first(); var angle = (e.hasClass('hidden')) ? 180 : 0; $(this).filter("i.fa-chevron-down").css({transform:'rotate(' + angle + 'deg)'}); e.toggleClass('hidden'); }); }; diff --git a/app/assets/javascripts/heatmap.js b/app/assets/javascripts/heatmap.js index 16e3d51..598f639 100644 --- a/app/assets/javascripts/heatmap.js +++ b/app/assets/javascripts/heatmap.js @@ -1,874 +1,864 @@ function heatMap(id, legend_id, json, W = 750, H = 750, font_family = "verdana, arial, helvetica, sans-serif") { // Size var margin = {top: 10, right: 150, bottom: 150, left: 10}, width = W - margin.left - margin.right, height = H - margin.top - margin.bottom, dendrogram_space = 30, dendrogram_margin = 3, color_bar_size = 12, color_bar_margin = 3; // Colors and symbols var heatmapColorRange = ["#3366ff", "#f2ffe6", "#ff0066"], topbarColors = d3.scaleOrdinal(d3.schemeCategory20), sidebarColors = {"p-values":d3.scaleLinear() .range(["#e6ffff", "#ffe6e6", "#ff8080", "#ff0000"]) .domain([1, 0.05, 0.01, 0.001]), "correlations":d3.scaleLinear() .range(["#990000", "#ffffe6", "#009900"]) .domain([-1, 0, 1]), "levels":d3.scaleOrdinal(d3.schemeCategory20) }, selector_opacity = 0.5; // Legend var color_scale_labels = ["min", "mean", "max"], color_scale_width = 100, color_scale_font_size = 11; // General functions function getHeatmapRowsExtrema(heatmap, which) { var nrow = heatmap[0].value.length, extrema = [...Array(nrow)]; for (var i = 0; i < nrow; i++) { var ncol = heatmap.length, row = [...Array(ncol)]; for (var j = 0; j < ncol; j++) { row[j] = heatmap[j].value[i]; }; row = row.filter(function(d){return !isNaN(d)}) if (which == "min") { extrema[i] = Math.min.apply(null, row); } else if (which == "max") { extrema[i] = Math.max.apply(null, row); } else if (which == "median") { row.sort(); if (row.length % 2 == 0) { extrema[i] = (Number(row[row.length/2])+Number(row[1+row.length/2]))/2; } else { extrema[i] = row[1+(row.length-1)/2]; } } else if (which == "mean") { var sum = 0; row.forEach(function(d){sum = sum+Number(d);}); extrema[i] = Number(sum)/row.length; } }; return extrema; }; function normalizeHeatmapColors(colorRange, Min, Max, Middle) { var nrow = Min.length, normalized_color = [...Array(nrow)]; for (var i = 0; i < nrow; i++) { var color = d3.scaleLinear() .range(colorRange) .domain([Min[i], Middle[i], Max[i]]); normalized_color[i] = color; }; return normalized_color; }; function generatePvalFilter(data, thres) { var bool = [...Array(data[0]['value'].length)].fill(0), index = [...Array(data[0]['value'].length)].fill(0), count = 0; data.forEach(function(d){ d.value.forEach(function(d, i) { if (d <= thres) { bool[i] = 1; } }) }) bool.forEach(function(d, i){ if (d == 1) { index[i] = count; count += 1; } }) return {bool:bool, index:index}; } function countReplicates(arr, x) { var count = 0; arr.forEach(function (d){ if (d == x) { count += 1; }; }); return count; }; function unique(arr) { var u = []; arr.forEach(function(d){ if (u.indexOf(d) == -1) { u.push(d); } }) return u; } function getLevels(names) { var arr = []; arr.push({"name":"level1", "value":[]}); for (var i = 0; i < names.length; i++) { var nlevels = names[i].split(";").length; for (var j = 0; j < nlevels; j++) { if (j > arr.length) { arr.push({"name":"level"+j, "value":[]}); }; }; }; for (var i = 0; i < names.length; i++) { var names_a = names[i].split(";"); for (var j = 0; j < arr.length; j++) { if (names_a.length > j) { arr[j].value.push(names_a[j]); } else { arr[j].value.push("Unknown"); }; }; }; return arr; }; // Buttons var buttons = d3.select("#d3-buttons"); buttons.html(""); //$.getJSON(data, function(json) { // Set variables depending only on the json var rowMin = getHeatmapRowsExtrema(json.heatmap, "min"), rowMax = getHeatmapRowsExtrema(json.heatmap, "max"), rowMean = getHeatmapRowsExtrema(json.heatmap, "mean"), heatmapColors = normalizeHeatmapColors(heatmapColorRange, rowMin, rowMax, rowMean), nrow = json.heatmap[0].value.length, topbar_data = json.topbar.category, ntopbar = topbar_data.length, top_space = dendrogram_space+dendrogram_margin+color_bar_size*ntopbar+color_bar_margin; // Add levels as an optional sidebar json.sidebar["levels"] = getLevels(json.rownames); //////////////// Draw the figure //////////////// var legendContainer = d3.select("#"+legend_id).append("div") .attr('class', 'columns-1') var svgContainer = d3.select("#"+id) .style("height", (height + margin.top + margin.bottom)+"px") var svg = svgContainer.append("svg") .attr("id", "svg-figure") .attr("class", "svg-figure") .attr("width", (width + margin.left + margin.right)+"px") .attr("height",(height + margin.top + margin.bottom)+"px") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // Draw heatmap var heatmap = svg.selectAll() .data(json.heatmap) .enter().append("g") heatmap.selectAll("rect") .data(function(d, i) { d.index = i; return d.value; }).enter() .append("rect") .attr("x", width/2) .attr("y", height/2) .style("fill", function(d, i) { return heatmapColors[i](d);}); // Draw dendrograms var colDendrogram = svg.selectAll(), rowDendrogram = svg.selectAll(), dendrogram = d3.cluster() .separation(function(a, b) { return 1; }); // Draw topbar var topbar = svg.selectAll() .data(topbar_data) .enter().append("g") var topbarSquares = topbar.selectAll("g") .data(function(d, i) { d.index = i; return d.value; }) .enter() .append("g") .attr("transform", function (d, i) { return "translate(" + (width/2) + "," + (height/2) + ")";}); topbarSquares.append("rect") .attr("height", color_bar_size) .style("fill", function (d) { if (d == "NA") { return "lightgrey"; }; return topbarColors(d); }); topbar.append("text") .attr("x", width) .attr("y", function(d, i){ return (dendrogram_space+dendrogram_margin+color_bar_size*i+color_bar_size); }) .attr("font-size", color_bar_size) .text(function(d){return d.name;}) // Draw sidebar var sidebar = svg.selectAll(), sidebarSquares = null; // Add heatmap labels var colLabel = svg.selectAll() .data(json.colnames) .enter().append("g") .attr("transform", "translate("+ (width/2) +", "+ height +")"); colLabel.append("text") .attr("class", "col-label") .attr("text-anchor", "end") .attr("font-family", font_family) .attr("transform", "rotate(-90)") .attr("display", "none") .text(function (d) { return d;}); var rowLabel = svg.selectAll() .data(json.rownames) .enter().append("g") .attr("class", "rowLabel") .attr("transform", "translate("+ width +", "+ (height/2) +")") var rowLabelBox = rowLabel.append("rect") .attr("class", "row-label-box") .attr("fill", "yellow") .attr("fill-opacity", 0) .attr("y", 0) var rowLabelText = rowLabel.append("text") .attr("class", "row-label heatmap-label") .attr("text-anchor", "end") .attr("filtered", 'false') .attr("selected", 'false') .attr("mass-selected", 'false') .attr("display", "none") .attr("x", margin.right) .attr("font-family", font_family) .text(function (d) { var label = d.split(';'); if (label.length > 1) { return label[label.length - 2]+"; "+label[label.length - 1]; }; return d; }) // Add legend var legend = legendContainer.append("div") .attr("id", "svg-legend") .style("font-family", font_family) // Add color scale to legend var colorScale = legend.append("div") .attr("title", "Color scale") .attr("class", "legend legend-no-interaction") .style("margin-bottom", (color_scale_font_size+15)+"px") colorScale.append("p") .html("Color scale") var colorScaleSvg = colorScale.append("svg") .attr("width", color_scale_width) .attr("height", "20px") .style("margin-right", "15px") .style("overflow", "visible") .style("margin-left", "15px") var colorScaleSvgDefs = colorScaleSvg.append("defs") var linearGradient = colorScaleSvgDefs.append("linearGradient") .attr("id", "linear-gradient"); linearGradient.append("stop") .attr("offset", "0%") .attr("stop-color", heatmapColorRange[0]); linearGradient.append("stop") .attr("offset", "50%") .attr("stop-color", heatmapColorRange[1]); linearGradient.append("stop") .attr("offset", "100%") .attr("stop-color", heatmapColorRange[2]); colorScaleSvg.append("rect") .attr("width", color_scale_width) .attr("height", 20) .style("fill", "url(#linear-gradient)") .style("stroke", "#ccc") colorScaleSvgLabel = colorScaleSvg.append("g") .selectAll() .data(color_scale_labels) .enter() colorScaleSvgLabel.append("rect") .attr("y", 20) .attr("x", function(d, i) { if (i == 0) { return i*color_scale_width/(color_scale_labels.length-1); } return i*color_scale_width/(color_scale_labels.length-1)-1; }) .attr("width", 1) .attr("height", 4) .style("fill", "#333"); colorScaleSvgLabel.append("text") .attr("y", 26+color_scale_font_size) .attr("x", function(d, i){return i*color_scale_width/(color_scale_labels.length-1);}) .attr("font-size", color_scale_font_size) .attr("text-anchor", "middle") .text(function(d){return d;}) colorScale.append("text") .attr("font-family", font_family) .text("z-score"); legend.append("div") .style("border-top", "solid #ccc 1px") .style("height", "6px") .style("border-bottom", "solid #ccc 1px") // Add topbar legend var topbarLegend = legend.append("div") .attr("title", "Topbar color key") topbarLegend.append("p") .html("Topbar color key") for (var i = 0; i < json.topbar.category.length; i++) { var topbarData = unique(json.topbar.category[i].value); topbarLegend.append("span") .attr("class", "sidebar-sub-title") .html(json.topbar.category[i].name) var legendSpan = topbarLegend.append("ul") .style("list-style-type", "none") .selectAll("ul") .data(topbarData) .enter().append("li") .attr("id", function(d) { return d;}) .attr("title", function(d) { return d;}) .append("span") legendSpan.append("svg") .attr("width", "10px") .attr("height", "10px") .style("margin-right", "5px") .append("rect") .attr("width", "10px") .attr("height", "10px") .attr("fill", function(d) { return topbarColors(d); }) legendSpan.append("span") .html(function(d) { return d; }); if (i < json.topbar.category.length-1) { topbarLegend.append("div") .style("border-top", "solid #e6e6e6 1px") } } legend.append("div") .style("border-top", "solid #ccc 1px") .style("height", "6px") .style("border-bottom", "solid #ccc 1px") // Add sidebar legend var sidebarLegend = legend.append("div") .attr("title", "Sidebar color key") .attr("class", "legend legend-no-interaction") .style("margin-bottom", (color_scale_font_size+5)+"px") //////////////// Restart function //////////////// var restart = function() { var selected_sidebar = $("#sidebarSelect").val(), selected_model = $("#modelSelect").val(); // Filter data var p_thres = 1 //$("#pThreshold").val(), sign_filter = generatePvalFilter(json.sidebar["p-values"][selected_model], p_thres), nsign = countReplicates(sign_filter.bool, 1); var nsidebar = 0; if (selected_sidebar != '') { if (Object.keys(json.sidebar[selected_sidebar]).indexOf(selected_model) != -1) { nsidebar = json.sidebar[selected_sidebar][selected_model].length; } else { nsidebar = json.sidebar[selected_sidebar].length; } }; var left_space = dendrogram_space+dendrogram_margin+color_bar_size*nsidebar+color_bar_margin; // Update the heatmap var squareWidth = (width-left_space)/json.heatmap.length, squareHeight = 0; if (nsign != 0) { squareHeight = (height-top_space)/nsign; }; heatmap.selectAll("rect") .transition().duration(150) .attr("x", function(d) { return left_space+squareWidth*(this.parentNode.__data__.index); }) .attr("y", function(d, i) { if (sign_filter.bool[i] == 1) { return top_space+squareHeight*sign_filter.index[i]; } return height/2; }) .attr("width", squareWidth) .attr("height", function(d, i) { return sign_filter.bool[i]*squareHeight; }) // Update dendrogram for columns dendrogram.size([width-left_space, dendrogram_space]); colDendrogram = colDendrogram.data([]); colDendrogram.exit().remove(); colDendrogram = colDendrogram .data(dendrogram(d3.hierarchy(json.colDendrogram)).descendants().slice(1)) .enter().append("path") .attr("d", function(d) { return "M" + (d.x+left_space) + "," + d.y + "V" + d.parent.y + "H" + (d.parent.x+left_space); }) .attr("fill", "none") .attr("stroke", "#999999"); // Update dendrogram for rows rowDendrogram = rowDendrogram.data([]); rowDendrogram.exit().remove(); if (nsign == nrow) { dendrogram.size([height-top_space, dendrogram_space]); rowDendrogram = rowDendrogram .data(dendrogram(d3.hierarchy(json.rowDendrogram)).descendants().slice(1)) .enter().append("path") .attr("d", function(d) { return "M" + d.y + "," + (d.x+top_space) + "H" + d.parent.y + "V" + (d.parent.x+top_space); }) .attr("fill", "none") .attr("stroke", "#999999"); } // Update topbar topbarSquares .transition().duration(150) .attr("transform", function (d, i) { return "translate(" + (left_space+i*squareWidth) + "," + (dendrogram_space+dendrogram_margin+color_bar_size*(this.parentNode.__data__.index)) + ")";}); topbarSquares.selectAll("rect") .attr("width", squareWidth) // Update sidebar sidebar = sidebar.data([]); sidebar.exit().remove(); if (selected_sidebar != '') { sidebar = sidebar .data(function(){ if (Object.keys(json.sidebar[selected_sidebar]).indexOf(selected_model) != -1) { return json.sidebar[selected_sidebar][selected_model]; } else { return json.sidebar[selected_sidebar]; } }) .enter().append("g") sidebarSquares = sidebar.selectAll("g") .data(function(d, i) { d.index = i; if (selected_sidebar == 'levels') { sidebarColors[selected_sidebar].domain(unique(d.value)); } return d.value; }) .enter() .append("g") .attr("transform", function (d, i) { if (sign_filter.bool[i] == 1) { return "translate(" + (dendrogram_space+dendrogram_margin+color_bar_size*(this.parentNode.__data__.index)) + "," + (top_space+squareHeight*sign_filter.index[i]) + ")"; } return "translate(" + (dendrogram_space+dendrogram_margin+color_bar_size*(this.parentNode.__data__.index)) + "," + (height/2) + ")"; }) sidebarSquares.append("rect") .attr("width", color_bar_size) .attr("height", function(d, i) { return sign_filter.bool[i]*squareHeight; }) .attr("fill", function(d) { return sidebarColors[selected_sidebar](d); }) // Add sidebar labels sidebar.append("g") .attr("transform", function(d, i){ return "translate("+(dendrogram_space+dendrogram_margin+color_bar_size+color_bar_size*i)+","+(height+5)+")"; }) .append("text") .attr("transform", "rotate(-90)") .attr("text-anchor", "end") .attr("font-size", color_bar_size) .text(function(d){return d.name;}) } // Update column labels colLabel .transition().duration(150) .attr("transform", function (d, i) { return "translate(" + (left_space+i*squareWidth+squareWidth) + "," + height + ")";}) // Update row labels rowLabel .transition().duration(150) .attr("transform", function (d, i) { if (sign_filter.bool[i] == 1) { return "translate("+ width +", "+ (top_space+squareHeight*sign_filter.index[i]) +")"; }; return "translate("+ width +", "+ (height/2) +")"; }) rowLabelBox .attr("fill-opacity", function(d, i){ if (sign_filter.bool[i] == 0) { return 0; } return this.getAttribute("fill-opacity"); }) .attr("x", left_space-width) .attr("width", width-left_space) .attr("height", function (d, i) { if (sign_filter.bool[i] == 0) { return 0; }; return squareHeight; }) rowLabelText .attr("display", function(d, i){ if (sign_filter.bool[i] == 0) { return "none"; } return this.getAttribute("display"); }) .attr("selected", function(d, i){ if (sign_filter.bool[i] == 0) { return 'false'; } return this.getAttribute("selected"); }) .attr("mass-selected", function(d, i){ if (sign_filter.bool[i] == 0) { return 'false'; } return this.getAttribute("mass-selected"); }) .attr("filtered", function (d, i) { if (sign_filter.bool[i] == 0) { return 'true'; }; return 'false'; }) .attr("y", squareHeight/2) } // Sidebar update function var updateSidebar = function(){ sidebarLegend.html('') var selected_sidebar = $("#sidebarSelect").val(); + if (selected_sidebar == '') { + restart(); + return 0; + } + // For linear scales if (sidebarColors[selected_sidebar].domain().length > 0) { var selected_sidebar_domain = sidebarColors[selected_sidebar].domain(), selected_sidebar_range = sidebarColors[selected_sidebar].range(); sidebarLegend .append("p") .html("Sidebar color scale") var sideBarColorScaleSvg = sidebarLegend.append("svg") .attr("width", color_scale_width) .attr("height", "20px") .style("margin-left", "15px") .style("margin-right", "15px") .style("overflow", "visible") var sideBarColorScaleSvgDefs = sideBarColorScaleSvg.append("defs") var linearGradient = sideBarColorScaleSvgDefs.append("linearGradient") .attr("id", "sidebar-linear-gradient"); for (var i = 0; i < selected_sidebar_domain.length; i++) { linearGradient.append("stop") .attr("offset", (i*100/(selected_sidebar_domain.length-1))+"%") .attr("stop-color", selected_sidebar_range[i]); } sideBarColorScaleSvg.append("rect") .attr("width", color_scale_width) .attr("height", 20) .style("fill", "url(#sidebar-linear-gradient)") .style("stroke", "#ccc") sideBarColorScaleSvgLabel = sideBarColorScaleSvg.append("g") .selectAll() .data(selected_sidebar_domain) .enter() sideBarColorScaleSvgLabel.append("rect") .attr("y", 20) .attr("x", function(d, i) { if (i == 0) { return i*color_scale_width/(selected_sidebar_domain.length-1); } return i*color_scale_width/(selected_sidebar_domain.length-1)-1; }) .attr("width", 1) .attr("height", 4) .style("fill", "#333"); sideBarColorScaleSvgLabel.append("text") .attr("y", 26+color_scale_font_size) .attr("x", function(d, i){return i*color_scale_width/(selected_sidebar_domain.length-1);}) .attr("font-size", color_scale_font_size) .attr("text-anchor", "middle") .text(function(d){return d;}) sidebarLegend.append("text") .attr("font-family", font_family) .text(selected_sidebar); } else { // For ordinal scales sidebarLegend.style("margin-left", "0px") .style("margin-top", "0px") .append("p") .html("Sidebar color key") for (var i = 0; i < json.sidebar[selected_sidebar].length; i++) { var sideBarData = unique(json.sidebar[selected_sidebar][i].value); sidebarColors[selected_sidebar].domain(sideBarData); sidebarLegend.append("span") .attr("class", "sidebar-sub-title") .html(json.sidebar[selected_sidebar][i].name) var legendSpan = sidebarLegend.append("ul") .style("list-style-type", "none") .selectAll("ul") .data(sideBarData) .enter().append("li") .attr("id", function(d) { return d;}) .attr("title", function(d) { return d;}) .append("span") legendSpan.append("svg") .attr("width", "10px") .attr("height", "10px") .style("margin-right", "5px") .append("rect") .attr("width", "10px") .attr("height", "10px") .attr("fill", function(d) { return sidebarColors[selected_sidebar](d); }) legendSpan.append("span") .html(function(d) { return d; }); if (i < json.sidebar[selected_sidebar].length-1) { sidebarLegend.append("div") .style("border-top", "solid #e6e6e6 1px") } } } restart(); } // Label selection function function selectLabel (id) { $("."+id).on("mouseenter", function(d) { d3.select(this.childNodes[1]).attr("display", "inline"); d3.select(this.childNodes[0]).attr("fill-opacity", selector_opacity); }); $("."+id).on("mouseleave", function(d) { d3.select(this.childNodes[0]).attr("fill-opacity", 0); if (this.childNodes[1].getAttribute("selected") == 'false') { d3.select(this.childNodes[1]).attr("display", "none"); } }); $("."+id).on("click", function(d) { if (this.childNodes[1].getAttribute("selected") == 'false') { d3.select(this.childNodes[1]).attr("display", "inline"); d3.select(this.childNodes[1]).attr("selected", 'true'); } else { d3.select(this.childNodes[1]).attr("display", "none"); d3.select(this.childNodes[1]).attr("selected", 'false'); } }); }; // Display labels button var showColLabel = function() { var label = d3.selectAll(".col-label"); if ($("#colLabelButton").is(':checked')) { label.attr("display", "inline"); } else { label.attr("display", "none"); }; }; appendLabelCheckBox(buttons, "Show columns labels", "Col. labels", "colLabelButton", showColLabel) var showRowLabel = function() { var label = d3.selectAll(".row-label").filter(function(){return this.getAttribute("filtered") == 'false'}); if ($("#rowLabelButton").is(':checked')) { label.attr("display", "inline"); label.attr("selected", 'true'); label.attr("mass-selected", 'true'); } else { label.attr("display", "none"); label.attr("selected", 'false'); label.attr("mass-selected", 'false'); }; }; appendLabelCheckBox(buttons, "Show rows labels", "Rows labels", "rowLabelButton", showRowLabel) // Search in labels var searchLabels = function() { - $("#rowLabelButton").attr("checked", false); - var key = $("#searchInput").val().toUpperCase(); - if (key != '') { - var selected = d3.selectAll(".row-label").filter(function(d){return d.toUpperCase().indexOf(key.toUpperCase()) != -1 }).filter(function(d){return this.getAttribute('filtered') == 'false' }); - non_selected = d3.selectAll(".row-label").filter(function(d){return d.toUpperCase().indexOf(key.toUpperCase()) == -1 }); - selected.attr("display", "inline"); - selected.attr("selected", 'true'); - non_selected.attr("display", "none"); - non_selected.attr("selected", 'false'); - } else { - to_free = d3.selectAll(".row-label"); - to_free.attr("display", "none"); - to_free.attr("selected", 'false'); - }; - }; - + searchLabels1("#rowLabelButton", "#searchInput", ".row-label") + } appendSearchInput(buttons, "Search", "searchInput", searchLabels); - // Select model var modelSelect = buttons.append("div") .attr("title", "Chose a model.") .attr("class", "form-group") modelSelect.append("label") .html("Model") modelSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "modelSelect") .attr("class", "form-control multiselect figtool-multiselect") .selectAll("option") .data(Object.keys(json.sidebar['p-values'])) .enter().append("option") .text(function (d){ return d;}); $("#modelSelect").on("change", restart) // Select sidebar var sidebarSelect = buttons.append("div") .attr("title", "Chose a sidebar.") .attr("class", "form-group") sidebarSelect.append("label") .html("Sidebar") sidebarSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "sidebarSelect") .attr("class", "form-control multiselect figtool-multiselect") .selectAll("option") .data(Object.keys(json.sidebar).concat('')) .enter().append("option") .text(function (d){ return d;}); $("#sidebarSelect").on("change", updateSidebar) // Select p-value cutoff /* var pThreshold = buttons.append("div") .attr("title", "Set a p-value cutoff.") .attr("class", "form-group") pThreshold.append("label") .html("P-value cutoff") pThreshold.append("input") .attr("id", "pThreshold") .attr("type", "number") .attr("class", "form-control form-number-field") .attr("min", 0) .attr("max", 1) .attr("step", 0.001) .attr("value", 1) .on("change", restart); */ setMultiselect('.figtool-multiselect'); //resizeMultiselect('#d3-buttons', 1, '#d3-buttons', false); restart(); selectLabel("rowLabel"); updateSidebar(); //}); }; diff --git a/app/assets/javascripts/pca.js b/app/assets/javascripts/pca.js index 904b856..57616a2 100644 --- a/app/assets/javascripts/pca.js +++ b/app/assets/javascripts/pca.js @@ -1,609 +1,596 @@ function pca(id, legend_id, json, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif", color_palette = d3.schemeCategory10) { // Size var margin = {top: 10, right: 10, bottom: 75, left: 75}, width = W - margin.left - margin.right, height = H - margin.top - margin.bottom, left_label_space = 35, bottom_label_space = 30; // Colors and symbols var colors = d3.scaleOrdinal(color_palette), symbols = d3.scaleOrdinal([d3.symbolCircle, d3.symbolSquare, d3.symbolTriangle, d3.symbolStar, d3.symbolDiamond, d3.symbolCross]); // Buttons var buttons = d3.select("#d3-buttons"); buttons.html(""); // $.getJSON(data, function(json) { // Set variables depending only on the primary data var color_select_options = [], json_keys = Object.keys(json[Object.keys(json)[0]][0]), axisValues = Object.keys(json[Object.keys(json)[0]][0].data), nArrowMax = json[Object.keys(json)[0]].length; //space_options = Object.keys(json); //space_options.splice(space_options.indexOf("eig"), 1); for ( var i = 0; i < json_keys.length; i++) { if (["data", "id"].indexOf(json_keys[i]) == -1) { color_select_options.push(json_keys[i]); }; }; //////////////// Draw plot //////////////// var legendContainer = d3.select("#"+legend_id).append("div") .attr('class', 'columns-2') var svgContainer = d3.select("#"+id) .style("height", (height + margin.top + margin.bottom)+"px") var svg = svgContainer.append("svg") .attr("id", "svg-figure") .attr("class", "svg-figure") .attr("width", (width + margin.left + margin.right)+"px") .attr("height",(height + margin.top + margin.bottom)+"px") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // Draw plot var plot = svg.selectAll(); // Add plot labels var plotLabel = svg.selectAll(); // Draw bi-plot (arrows) var biPlot = svg.selectAll(); // Add bi-plot labels var biPlotLabel = svg.selectAll(); // Draw axis var xAxis = svg.append("g") .attr("class", "axis") .attr("transform", "translate(0," + height + ")"); var xAxisLegend = svg.append("g") .attr("transform", "translate("+ width/2 +", "+ (height+bottom_label_space) +")") .attr("class", "axis-label"); var yAxis = svg.append("g") .attr("class", "axis"); var yAxisLegend = svg.append("g") .attr("transform", "translate("+ -left_label_space +", "+ height/2 +")") .attr("class", "axis-label"); // Add legend var legend = legendContainer.append("div") .attr("id", "svg-legend") .style("font-family", font_family) legend.append('p') .html('Color key') var colorLegend = legend.append("ul") .style("list-style-type", "none") .selectAll("ul"); legend.append('p') .html('Symbol key') var symLegend = legend.append("ul") .style("list-style-type", "none") .selectAll("ul"); //////////////// Restart function //////////////// var restart = function() { // Set coordinates settings var space = "ind", X = d3.select("#xSelect").property("value"), Y = d3.select("#ySelect").property("value"), x = [], y = []; json[space].forEach(function (d) { x.push(d.data[X]); y.push(d.data[Y]); }); var xMin = Math.min.apply(null, x), xMax = Math.max.apply(null, x), xPadding = [0.05, 0.03], xRange = xMax-xMin, xScale = d3.scaleLinear() .range([0, width]) .domain([xMin-xPadding[0]*xRange, xMax+xPadding[1]*xRange]).nice(), xValue = function(d) { return d.data[X];}, xMap = function(d) { return xScale(xValue(d));}, yMin = Math.min.apply(null, y), yMax = Math.max.apply(null, y), yPadding = [0.05, 0.03], yRange = yMax-yMin, yScale = d3.scaleLinear() .range([height, 0]) .domain([yMin-yPadding[0]*yRange, yMax+yPadding[1]*yRange]).nice() yValue = function(d) { return d.data[Y];}, yMap = function(d) { return yScale(yValue(d));}; // Set color settings var selected_color_factor = d3.select("#colorSelect").property("value"), selected_symbol_factor = d3.select("#symbolSelect").property("value"), color_factors = [], symbol_factors = []; json[space].forEach(function (d) { if (color_factors.indexOf(d[selected_color_factor]) == -1) { color_factors.push(d[selected_color_factor]); }; if (symbol_factors.indexOf(d[selected_symbol_factor]) == -1) { symbol_factors.push(d[selected_symbol_factor]); }; }); colors.domain(color_factors); symbols.domain(symbol_factors); // Draw plot plot = plot.data([]); plot.exit().remove(); plot = plot .data(json[space]); // Draw dots plot = plot.enter() .append("g") .attr("class", "plot") .attr("transform", function (d) { return "translate(" + xMap(d) + ", " + yMap(d) + ")"; }); plot.append("path") .attr("class", "symbol") .attr("d", d3.symbol() .type(function(d) { return symbols(d[selected_symbol_factor]);}) .size(200)) .style("fill", function(d) { return colors(d[selected_color_factor]);}) .style("stroke-opacity", 0) .style("fill-opacity", 0.8); // Add dots labels plotLabel = plotLabel.data([]); plotLabel.exit().remove(); plotLabel = plotLabel .data(json[space]) .enter() .append("g") .attr("class", "plot-label") .attr("transform", function (d) { return "translate(" + xMap(d) + ", " + yMap(d) + ")"; }); plotLabel.append("path") .attr("class", "symbol") .attr("d", d3.symbol() .type(function(d) { return symbols(d[selected_symbol_factor]);}) .size(200)) .style("opacity", 0) plotLabel.append("text") .attr("class", "plot-label") .attr("dy", -10) .style("text-anchor", function (d) { if (xMap(d) <= width/2) { return "start"; } else { return "end"; }; }) .attr("font-family", font_family) .attr("display", "none") .attr("selected", false) .text(function (d) { var label = d.name.split(";"); if (label.length > 1) { return label[label.length-2] +";"+ label[label.length-1]; } else { return label[0]; }; }); // Draw arrows (bi-plot) var arrow_space = "var", //space_options, nArrows = d3.select("#nArrow").property("value"), minDx = Math.min(xScale(0), width-xScale(0)), minDy = Math.min(yScale(0), height-yScale(0)), minD = Math.min(minDx, minDy), xRadius = Math.abs(xScale.invert(xScale(0)+minD)/1.5), yRadius = Math.abs(yScale.invert(yScale(0)+minD)/1.5), norm = function (d) {return Math.sqrt(Math.pow(xValue(d), 2)+Math.pow(yValue(d), 2));}, angle = function (d) { var a = Math.atan(xValue(d)/yValue(d))*180/Math.PI; if (yValue(d) < 0) { a = a-180; // apply some majik... } return a; }, xMapArrow = function (d) {return xScale(xRadius*xValue(d)/norm(d));}, yMapArrow = function (d) {return yScale(yRadius*yValue(d)/norm(d));}; //arrow_space.splice(arrow_space.indexOf(space), 1); var arrow_data = JSON.parse(JSON.stringify(json[arrow_space])); nArrowMax = arrow_data.length; function sortByNorm(e1, e2) { var v1 = norm(e1), v2 = norm(e2); return v2-v1; }; arrow_data.sort(sortByNorm); arrow_data = arrow_data.splice(0, nArrows); biPlot = biPlot.data([]); biPlot.exit().remove(); biPlot = biPlot .data(arrow_data) .enter() .append("g"); biPlot.append("line") .attr("class", "arrow-line") .attr("x1", xScale(0)) .attr("y1", yScale(0)) .attr("x2", function (d) {return xMapArrow(d);}) .attr("y2", function (d) {return yMapArrow(d);}) .attr("stroke", "#333"); var arrowHead = biPlot.append("g") .attr("transform", function (d) { return "translate(" + xMapArrow(d) + ", " + yMapArrow(d) + ") rotate("+angle(d)+")"; }); arrowHead.append("path") .attr("class", "arrow-head") .attr("d", d3.symbol() .type(d3.symbolTriangle) .size(50)); // Add bi-plot (arrows) labels biPlotLabel = biPlotLabel.data([]); biPlotLabel.exit().remove(); biPlotLabel = biPlotLabel .data(arrow_data) .enter() .append("g") .attr("class", "plot-label") .attr("transform", function (d) { return "translate(" + xMapArrow(d) + ", " + yMapArrow(d) + ")"; }); biPlotLabel.append("path") .attr("class", "arrow-head") .attr("transform", function (d) { return "rotate("+angle(d)+")"; }) .attr("d", d3.symbol() .type(d3.symbolTriangle) .size(50)) .style("opacity", 1); biPlotLabel.append("text") .attr("dy", -10) .style("text-anchor", function (d) { if (xMap(d) <= width/2) { return "start"; } else { return "end"; }; }) .attr("font-family", font_family) - .attr("display", "none") + .attr("display", "inline") .attr("selected", false) .text(function (d) { var label = d.name.split(";"); if (label.length > 1) { return label[label.length-2] +";"+ label[label.length-1]; } else { return label[0]; }; }); displayLabels("plot-label"); showLabels(); // Add axis xAxis.selectAll("*").remove(); xAxis.call(d3.axisBottom(xScale).ticks(10)); xAxisLegend.selectAll("*").remove(); xAxisLegend.append("text") .text(X+" ("+Math.round(json.eig[X])+"%)") .attr("font-family", font_family) .style("text-anchor", "middle"); yAxis.selectAll("*").remove(); yAxis.call(d3.axisLeft(yScale).ticks(10)) yAxisLegend.selectAll("*").remove(); yAxisLegend.append("text") .text(Y+" ("+Math.round(json.eig[Y])+"%)") .attr("font-family", font_family) .style("text-anchor", "middle") .attr("transform", "rotate(-90)"); // Add 0-lines svg.selectAll(".zero-line").remove(); svg.append("g") .attr("class", "zero-line") .attr("transform", "translate(0," + yScale(0) + ")") .call(d3.axisBottom(xScale).ticks(0) .tickSize(0, 0)); svg.append("g") .attr("class", "zero-line") .attr("transform", "translate(" + xScale(0) + ", 0)") .call(d3.axisLeft(yScale).ticks(0) .tickSize(0, 0)); // Close the plot svg.selectAll(".frame").remove(); svg.append("g") .attr("class", "frame") .attr("transform", "translate(0, 0)") .call(d3.axisTop(xScale).ticks(0) .tickSize(0, 0)); svg.append("g") .attr("class", "frame") .attr("transform", "translate("+width+", 0)") .call(d3.axisRight(yScale).ticks(0) .tickSize(0, 0)); // Update legend colorLegend = colorLegend.data([]); colorLegend.exit().remove(); colorLegend = colorLegend .data(color_factors.reverse()) .enter().append("li") .attr("id", function(d) { return d;}) .attr("class", "legend legend-no-interaction") .attr("selected", 0) .attr("title", function(d) { return d;}) colorLegendSpan = colorLegend.append("span") colorLegendSpan.append("svg") .attr("width", "10px") .attr("height", "10px") .style("margin-right", "5px") .style("overflow", "visible") .append("path") .attr("transform", "translate(5, 5)") .attr("d", d3.symbol() .type(function (d, i){ return d3.symbolSquare; }) .size(75)) .attr("stroke", "none") .attr("fill", function (d, i){ return colors(d); }) .attr("fill-opacity", 1) colorLegendSpan.append("span") .html(function(d) { return d;}) symLegend = symLegend.data([]); symLegend.exit().remove(); symLegend = symLegend .data(symbol_factors.reverse()) .enter().append("li") .attr("id", function(d) { return d;}) .attr("class", "legend legend-no-interaction") .attr("selected", 0) .attr("title", function(d) { return d;}) symLegendSpan = symLegend.append("span") symLegendSpan.append("svg") .attr("width", "10px") .attr("height", "10px") .style("margin-right", "5px") .style("overflow", "visible") .append("path") .attr("transform", "translate(5, 5)") .attr("d", d3.symbol() .type(function (d, i){ return symbols(d); }) .size(75)) .attr("stroke", function (d, i){ return "#333"; }) .attr("fill", function (d, i){ return "#333"; }) .attr("fill-opacity", function (d, i){ return 0; }) symLegendSpan.append("span") .html(function(d) { return d;}) }; //////////////// Control buttons //////////////// // Display labels button var showLabels = function() { var labels = d3.selectAll(".plot-label").select(function(){ return this.childNodes[1];}); if ($("#labelButton").is(':checked')) { labels.attr("display", "inline"); labels.attr("selected", true); } else { labels.attr("display", "none"); labels.attr("selected", false); }; }; appendLabelCheckBox(buttons, "Show labels", "Labels", "labelButton", showLabels) // Search in labels var searchLabels = function() { - $("#labelButton").attr("checked", false); - var key = $("#searchInput").val().toUpperCase(); - if (key != '') { - var selected = d3.selectAll(".plot-label").filter(function(){return this.__data__.name.toUpperCase().indexOf(key.toUpperCase()) != -1 }).select(function(){return this.childNodes[1];}), - non_selected = d3.selectAll(".plot-label").filter(function(){return this.__data__.name.toUpperCase().indexOf(key.toUpperCase()) == -1 }).select(function(){return this.childNodes[1];}); - selected.attr("display", "inline"); - selected.attr("selected", true); - non_selected.attr("display", "none"); - non_selected.attr("selected", false); - } else { - to_free = d3.selectAll(".plot-label").select(function(){return this.childNodes[1];}); - to_free.attr("display", "none"); - to_free.attr("selected", false); - }; - }; - + searchLabels2("#labelButton", "#searchInput", ".plot-label") + } appendSearchInput(buttons, "Search", "searchInput", searchLabels); + // Select axis var xSelect = buttons.append("div") .attr("title", "Chose X axis.") .attr("class", "form-group") xSelect.append("label") .html("X axis") xSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "xSelect") .attr("class", "form-control multiselect figtool-multiselect") .on("change", restart) .selectAll("option") .data(axisValues) .enter().append("option") .text(function (d){ return d;}); document.getElementById("xSelect").value = axisValues[0]; var ySelect = buttons.append("div") .attr("title", "Chose Y axis.") .attr("class", "form-group") ySelect.append("label") .html("Y axis") ySelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "ySelect") .attr("class", "form-control multiselect figtool-multiselect") .on("change", restart) .selectAll("option") .data(axisValues) .enter().append("option") .text(function (d){ return d;}); document.getElementById("ySelect").value = axisValues[1]; // Select color variables var colorSelect = buttons.append("div") .attr("title", "Chose variable to use for colors.") .attr("class", "form-group") colorSelect.append("label") .html("Color") colorSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "colorSelect") .attr("class", "form-control multiselect figtool-multiselect") .on("change", restart) .selectAll("option") .data(color_select_options) .enter().append("option") .text(function (d){ return d;}); document.getElementById("colorSelect").value = color_select_options[color_select_options.length-1]; // Select symbol variables var symbolSelect = buttons.append("div") .attr("title", "Chose variable to use for symbols.") .attr("class", "form-group") symbolSelect.append("label") .html("Symbols") symbolSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "symbolSelect") .attr("class", "form-control multiselect figtool-multiselect") .on("change", restart) .selectAll("option") .data(color_select_options) .enter().append("option") .text(function (d){ return d;}); document.getElementById("symbolSelect").value = color_select_options[color_select_options.length-1]; // Select number of arrows var nArrow = buttons.append("div") .attr("title", "Set the number of variables (arrows) to display on the bi-plot.") .attr("class", "form-group") nArrow.append("label") .html("Arrows nb") nArrow.append("input") .attr("id", "nArrow") .attr("type", "number") .attr("class", "form-control form-number-field") .attr("min", 0) .attr("max", nArrowMax) .attr("value", Math.min(5, nArrowMax)) .on("change", restart); setMultiselect('.figtool-multiselect'); // resizeMultiselect('#d3-buttons', 1, '#d3-buttons', false); $("#xSelect").on("change", restart) $("#ySelect").on("change", restart) $("#colorSelect").on("change", restart) $("#symbolSelect").on("change", restart) restart(); // Labels functions function displayLabels (id) { $("."+id).on("mouseenter", function(d) { d3.select(this.childNodes[1]).attr("display", "inline"); }); $("."+id).on("mouseleave", function(d) { if (this.childNodes[1].getAttribute("selected") == "false") { d3.select(this.childNodes[1]).attr("display", "none"); }; }); $("."+id).on("click", function(d) { if (this.childNodes[1].getAttribute("selected") == "false") { d3.select(this.childNodes[1]).attr("display", "inline"); d3.select(this.childNodes[1]).attr("selected", true); } else { d3.select(this.childNodes[1]).attr("display", "none"); d3.select(this.childNodes[1]).attr("selected", false); } }); }; //}); }; diff --git a/app/assets/javascripts/pcoa.js b/app/assets/javascripts/pcoa.js index 5fbca91..8bd750a 100644 --- a/app/assets/javascripts/pcoa.js +++ b/app/assets/javascripts/pcoa.js @@ -1,484 +1,469 @@ function pcoa(id, legend_id, json, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif", color_palette = d3.schemeCategory10) { // Size var margin = {top: 10, right: 10, bottom: 75, left: 75}, width = W - margin.left - margin.right, height = H - margin.top - margin.bottom, left_label_space = 35, bottom_label_space = 30; // Colors and symbols var colors = d3.scaleOrdinal(color_palette), symbols = d3.scaleOrdinal([d3.symbolCircle, d3.symbolSquare, d3.symbolTriangle, d3.symbolStar, d3.symbolDiamond, d3.symbolCross]); // Buttons var buttons = d3.select("#d3-buttons"); buttons.html(""); // $.getJSON(data, function(json) { // Set variables depending only on the primary data var color_select_options = [], data_keys = Object.keys(json.data[0]), axisValues = Object.keys(json.data[0].data); for ( var i = 0; i < data_keys.length; i++) { if (["data", "id"].indexOf(data_keys[i]) == -1) { color_select_options.push(data_keys[i]); }; }; //////////////// Draw plot //////////////// var legendContainer = d3.select("#"+legend_id).append("div") .attr('class', 'columns-2') var svgContainer = d3.select("#"+id) .style("height", (height + margin.top + margin.bottom)+"px") var svg = svgContainer.append("svg") .attr("id", "svg-figure") .attr("class", "svg-figure") .attr("width", (width + margin.left + margin.right)+"px") .attr("height",(height + margin.top + margin.bottom)+"px") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // Draw plot var plot = svg.selectAll(); // Add plot labels var plotLabel = svg.selectAll(); // Draw axis var xAxis = svg.append("g") .attr("class", "axis") .attr("transform", "translate(0," + height + ")"); var xAxisLegend = svg.append("g") .attr("transform", "translate("+ width/2 +", "+ (height+bottom_label_space) +")") .attr("class", "axis-label"); var yAxis = svg.append("g") .attr("class", "axis"); var yAxisLegend = svg.append("g") .attr("transform", "translate("+ -left_label_space +", "+ height/2 +")") .attr("class", "axis-label"); // Add legend var legend = legendContainer.append("div") .attr("id", "svg-legend") .style("font-family", font_family) legend.append('p') .html('Color key') var colorLegend = legend.append("ul") .style("list-style-type", "none") .selectAll("ul"); legend.append('p') .html('Symbol key') var symLegend = legend.append("ul") .style("list-style-type", "none") .selectAll("ul"); //////////////// Restart function //////////////// var restart = function() { // Set coordinates settings var X = d3.select("#xSelect").property("value"), Y = d3.select("#ySelect").property("value"), x = [], y = []; json.data.forEach(function (d) { x.push(d.data[X]); y.push(d.data[Y]); }); var xMin = Math.min.apply(null, x), xMax = Math.max.apply(null, x), xPadding = [0.05, 0.03], xRange = xMax-xMin, xScale = d3.scaleLinear() .range([0, width]) .domain([xMin-xPadding[0]*xRange, xMax+xPadding[1]*xRange]).nice(), xValue = function(d) { return d.data[X];}, xMap = function(d) { return xScale(xValue(d));}, yMin = Math.min.apply(null, y), yMax = Math.max.apply(null, y), yPadding = [0.05, 0.03], yRange = yMax-yMin, yScale = d3.scaleLinear() .range([height, 0]) .domain([yMin-yPadding[0]*yRange, yMax+yPadding[1]*yRange]).nice() yValue = function(d) { return d.data[Y];}, yMap = function(d) { return yScale(yValue(d));}; // Set color settings var selected_color_factor = d3.select("#colorSelect").property("value"), selected_symbol_factor = d3.select("#symbolSelect").property("value"), color_factors = [], symbol_factors = []; json.data.forEach(function (d) { if (color_factors.indexOf(d[selected_color_factor]) == -1) { color_factors.push(d[selected_color_factor]); }; if (symbol_factors.indexOf(d[selected_symbol_factor]) == -1) { symbol_factors.push(d[selected_symbol_factor]); }; }); colors.domain(color_factors); symbols.domain(symbol_factors); // Draw plot plot = plot.data([]); plot.exit().remove(); plot = plot .data(json.data); // Draw dots plot = plot.enter() .append("g") .attr("class", "plot") .attr("transform", function (d) { return "translate(" + xMap(d) + ", " + yMap(d) + ")"; }); plot.append("path") .attr("class", "symbol") .attr("d", d3.symbol() .type(function(d) { return symbols(d[selected_symbol_factor]);}) .size(200)) .style("fill", function(d) { return colors(d[selected_color_factor]);}) .style("stroke-opacity", 0) .style("fill-opacity", 0.8); // Add dots labels plotLabel = plotLabel.data([]); plotLabel.exit().remove(); plotLabel = plotLabel .data(json.data) .enter() .append("g") .attr("class", "plot-label") .attr("transform", function (d) { return "translate(" + xMap(d) + ", " + yMap(d) + ")"; }); plotLabel.append("path") .attr("class", "symbol") .attr("d", d3.symbol() .type(function(d) { return symbols(d[selected_symbol_factor]);}) .size(200)) .style("opacity", 0) plotLabel.append("text") .attr("class", "plot-label") .attr("dy", -10) .style("text-anchor", function (d) { if (xMap(d) <= width/2) { return "start"; } else { return "end"; }; }) .attr("font-family", font_family) .attr("display", "none") .attr("selected", false) .text(function (d) { var label = d.name.split(";"); if (label.length > 1) { return label[label.length-2] +";"+ label[label.length-1]; } else { return label[0]; }; }); displayLabels("plot-label"); showLabels(); // Add axis xAxis.selectAll("*").remove(); xAxis.call(d3.axisBottom(xScale).ticks(10)); xAxisLegend.selectAll("*").remove(); xAxisLegend.append("text") .text(X+" ("+Math.round(json.eig[X])+"%)") .attr("font-family", font_family) .style("text-anchor", "middle"); yAxis.selectAll("*").remove(); yAxis.call(d3.axisLeft(yScale).ticks(10)) yAxisLegend.selectAll("*").remove(); yAxisLegend.append("text") .text(Y+" ("+Math.round(json.eig[Y])+"%)") .attr("font-family", font_family) .style("text-anchor", "middle") .attr("transform", "rotate(-90)"); // Close the plot svg.selectAll(".frame").remove(); svg.append("g") .attr("class", "frame") .attr("transform", "translate(0, 0)") .call(d3.axisTop(xScale).ticks(0) .tickSize(0, 0)); svg.append("g") .attr("class", "frame") .attr("transform", "translate("+width+", 0)") .call(d3.axisRight(yScale).ticks(0) .tickSize(0, 0)); svg.append("g") .attr("class", "frame") .attr("transform", "translate(0," + yScale(0) + ")") .call(d3.axisBottom(xScale).ticks(0) .tickSize(0, 0)); svg.append("g") .attr("class", "frame") .attr("transform", "translate(" + xScale(0) + ", 0)") .call(d3.axisLeft(yScale).ticks(0) .tickSize(0, 0)); // Update legend colorLegend = colorLegend.data([]); colorLegend.exit().remove(); colorLegend = colorLegend .data(color_factors.reverse()) .enter().append("li") .attr("id", function(d) { return d;}) .attr("class", "legend legend-no-interaction") .attr("selected", 0) .attr("title", function(d) { return d;}) colorLegendSpan = colorLegend.append("span") colorLegendSpan.append("svg") .attr("width", "10px") .attr("height", "10px") .style("margin-right", "5px") .style("overflow", "visible") .append("path") .attr("transform", "translate(5, 5)") .attr("d", d3.symbol() .type(function (d, i){ return d3.symbolSquare; }) .size(75)) .attr("stroke", "none") .attr("fill", function (d, i){ return colors(d); }) .attr("fill-opacity", 1) colorLegendSpan.append("span") .html(function(d) { return d;}) symLegend = symLegend.data([]); symLegend.exit().remove(); symLegend = symLegend .data(symbol_factors.reverse()) .enter().append("li") .attr("id", function(d) { return d;}) .attr("class", "legend legend-no-interaction") .attr("selected", 0) .attr("title", function(d) { return d;}) symLegendSpan = symLegend.append("span") symLegendSpan.append("svg") .attr("width", "10px") .attr("height", "10px") .style("margin-right", "5px") .style("overflow", "visible") .append("path") .attr("transform", "translate(5, 5)") .attr("d", d3.symbol() .type(function (d, i){ return symbols(d); }) .size(75)) .attr("stroke", function (d, i){ return "#333"; }) .attr("fill", function (d, i){ return "#333"; }) .attr("fill-opacity", function (d, i){ return 0; }) symLegendSpan.append("span") .html(function(d) { return d;}) }; //////////////// Control buttons //////////////// // Display labels button var showLabels = function() { var labels = d3.selectAll(".plot-label").select(function(){ return this.childNodes[1];}); if ($("#labelButton").is(':checked')) { labels.attr("display", "inline"); labels.attr("selected", true); } else { labels.attr("display", "none"); labels.attr("selected", false); }; }; appendLabelCheckBox(buttons, "Show labels", "Labels", "labelButton", showLabels) // Search in labels var searchLabels = function() { - $("#labelButton").attr("checked", false); - var key = $("#searchInput").val().toUpperCase(); - if (key != '') { - var selected = d3.selectAll(".plot-label").filter(function(){return this.__data__.name.toUpperCase().indexOf(key.toUpperCase()) != -1 }).select(function(){return this.childNodes[1];}), - non_selected = d3.selectAll(".plot-label").filter(function(){return this.__data__.name.toUpperCase().indexOf(key.toUpperCase()) == -1 }).select(function(){return this.childNodes[1];}); - selected.attr("display", "inline"); - selected.attr("selected", true); - non_selected.attr("display", "none"); - non_selected.attr("selected", false); - } else { - to_free = d3.selectAll(".plot-label").select(function(){return this.childNodes[1];}); - to_free.attr("display", "none"); - to_free.attr("selected", false); - - }; - }; - + searchLabels2("#labelButton", "#searchInput", ".plot-label") + } appendSearchInput(buttons, "Search", "searchInput", searchLabels); // Select axis var xSelect = buttons.append("div") .attr("title", "Chose X axis.") .attr("class", "form-group") xSelect.append("label") .html("X axis") xSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "xSelect") .attr("class", "form-control multiselect figtool-multiselect") .on("change", restart) .selectAll("option") .data(axisValues) .enter().append("option") .text(function (d){ return d;}); document.getElementById("xSelect").value = axisValues[0]; var ySelect = buttons.append("div") .attr("title", "Chose Y axis.") .attr("class", "form-group") ySelect.append("label") .html("Y axis") ySelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "ySelect") .attr("class", "form-control multiselect figtool-multiselect") .on("change", restart) .selectAll("option") .data(axisValues) .enter().append("option") .text(function (d){ return d;}); document.getElementById("ySelect").value = axisValues[1]; // Select color variables var colorSelect = buttons.append("div") .attr("title", "Chose variable to use for colors.") .attr("class", "form-group") colorSelect.append("label") .html("Color") colorSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "colorSelect") .attr("class", "form-control multiselect figtool-multiselect") .on("change", restart) .selectAll("option") .data(color_select_options) .enter().append("option") .text(function (d){ return d;}); document.getElementById("colorSelect").value = color_select_options[color_select_options.length-1]; // Select symbol variables var symbolSelect = buttons.append("div") .attr("title", "Chose variable to use for symbols.") .attr("class", "form-group") symbolSelect.append("label") .html("Symbols") symbolSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "symbolSelect") .attr("class", "form-control multiselect figtool-multiselect") .on("change", restart) .selectAll("option") .data(color_select_options) .enter().append("option") .text(function (d){ return d;}); document.getElementById("symbolSelect").value = color_select_options[color_select_options.length-1]; setMultiselect('.figtool-multiselect'); //resizeMultiselect('#d3-buttons', 1, '#d3-buttons', false); $("#xSelect").on("change", restart) $("#ySelect").on("change", restart) $("#colorSelect").on("change", restart) $("#symbolSelect").on("change", restart) restart(); // Labels functions function displayLabels (id) { $("."+id).on("mouseenter", function(d) { d3.select(this.childNodes[1]).attr("display", "inline"); }); $("."+id).on("mouseleave", function(d) { if (this.childNodes[1].getAttribute("selected") == "false") { d3.select(this.childNodes[1]).attr("display", "none"); }; }); $("."+id).on("click", function(d) { if (this.childNodes[1].getAttribute("selected") == "false") { d3.select(this.childNodes[1]).attr("display", "inline"); d3.select(this.childNodes[1]).attr("selected", true); } else { d3.select(this.childNodes[1]).attr("display", "none"); d3.select(this.childNodes[1]).attr("selected", false); } }); }; // }); }; diff --git a/app/assets/javascripts/similarityNetwork.js b/app/assets/javascripts/similarityNetwork.js index 6e1730f..a070fd0 100644 --- a/app/assets/javascripts/similarityNetwork.js +++ b/app/assets/javascripts/similarityNetwork.js @@ -1,646 +1,675 @@ function similarityNetwork(id, legend_id, json0, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif", color_palette = d3.schemeCategory10) { // Size var margin = {top: 10, right: 10, bottom: 40, left: 10}, width = W - margin.left - margin.right, height = H - margin.top - margin.bottom; // Network selector var networkSelector = ["Data", "Metadata", "Fusion"]; // Colors, symbols and scaling var colors = d3.scaleOrdinal(color_palette), sim_color = ["#999999", "Similarity"], symbols = d3.scaleOrdinal([d3.symbolCircle, d3.symbolSquare, d3.symbolTriangle, d3.symbolStar, d3.symbolDiamond, d3.symbolCross]), radius = 20, wRange = [0.1*radius, 0.9*radius]; // General functions function getWeightExtrema(json, index=0, t="max", Abs=false) { var arr = []; for (var i = 0; i < json.length; i++) { if (Abs) { arr.push(Math.abs(json[i].weight[index])); } else { arr.push(json[i].weight[index]); }; }; if (t == "max") { return Math.max.apply(null, arr); } else if (t == "min") { return Math.min.apply(null, arr); }; }; function scale(fRange, value, iRange) { if (iRange[0] != iRange[1]) { return fRange[0] + (fRange[1] - fRange[0]) * (value - iRange[0]) / (iRange[1] - iRange[0]); } else { return (fRange[0] + fRange[1]) / 2; }; }; function organizeLegend(factor, SymSize, legendXSpace, legendYSpace) { var legend_ncol = Math.ceil(SymSize*factor.length/legendYSpace), legend_nrow = Math.ceil(factor.length/legend_ncol), legend_pos = []; for (var i = 0; i < legend_ncol; i++) { for (var j = 0; j < legend_nrow; j++) { if (legend_pos.length < factor.length) { legend_pos.push({x:i*legendXSpace/legend_ncol, y:j*SymSize}); }; }; }; return legend_pos.reverse(); }; // Buttons var buttons = d3.select("#d3-buttons") buttons.html(""); //$.getJSON(data, function(json0) { // Set variables depending only on the primary data // Make a working copy of the data var json = JSON.parse(JSON.stringify(json0)); //////////////// Simulation //////////////// var simulation = d3.forceSimulation() .force("center", d3.forceCenter(width / 2, height / 2)) .force("y", d3.forceY()) .force("x", d3.forceX()) .force("collide", d3.forceCollide() .radius(radius) .iterations(2) .strength(0.5)) .force("charge", d3.forceManyBody() .strength(-100)); var ticked = function() { link .attr("x1", function(d) { return Math.max(0, Math.min(d.source.x, width)); }) .attr("y1", function(d) { return Math.max(0, Math.min(d.source.y, height)); }) .attr("x2", function(d) { return Math.max(0, Math.min(d.target.x, width)); }) .attr("y2", function(d) { return Math.max(0, Math.min(d.target.y, height)); }); node .attr("transform", function(d) { return "translate(" + Math.max(0, Math.min(d.x, width)) + "," + Math.max(0, Math.min(d.y, height)) + ")"; }); nodeLabel .attr("transform", function(d, i) { return "translate(" + Math.max(0, Math.min(d.x, width)) + "," + Math.max(0, Math.min(d.y, height)) + ")"; }); }; function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.25).restart(); d.fx = d.x; d.fy = d.y; } function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } //////////////// Draw links and nodes //////////////// var legendContainer = d3.select("#"+legend_id).append("div") .attr('class', 'columns-2') var svgContainer = d3.select("#"+id) .style("height", (height + margin.top + margin.bottom)+"px") var svg = svgContainer.append("svg") .attr("id", "svg-figure") .attr("class", "svg-figure network-well") .attr("width", (width + margin.left + margin.right)+"px") .attr("height",(height + margin.top + margin.bottom)+"px") .style("pointer-events", "all") .call(d3.zoom() .scaleExtent([1, 4]) .duration(1000) .translateExtent([[margin.left, margin.top], [width + margin.right, height + margin.top + margin.bottom]]) .on("zoom", zoomed)); var g = svg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); function zoomed() { g.attr("transform", d3.event.transform); }; // Draw links var link = g.append("g") .selectAll(); // Draw Nodes var node = g.append("g") .selectAll("g"); var nodeLabel = g.append("g") .selectAll("g"); // Add legend var legend = legendContainer.append("div") .attr("id", "svg-legend") .style("font-family", font_family) var colorLegend = legend.append("ul") .style("list-style-type", "none") .selectAll("ul"); legend.append("div") .style("border-top", "solid #ccc 1px") var symbolLegend = legend.append("ul") .style("list-style-type", "none") .selectAll("ul"); legend.append("div") .style("border-top", "solid #ccc 1px") var linkLegend = legend.append("ul") .style("list-style-type", "none") .selectAll("ul"); //////////////// Nodes color settings //////////////// var colorFactors = Object.keys(json.nodes[0]); colorFactors.splice(colorFactors.indexOf("id"), 1); colorFactors.splice(colorFactors.indexOf("name"), 1); colorFactors.push(null); var setSymbolColor = function() { var color_factor = d3.select("#colorSelect").property("value"), color_labels = []; if (color_factor != "") { for (var i = 0; i < json0.nodes.length; i++) { if(color_labels.indexOf(json0.nodes[i][color_factor]) == -1) { color_labels.push(json0.nodes[i][color_factor]); }; }; }; colors.domain(color_labels); // Set symbols color d3.selectAll(".coloured-symbol") .style("fill", function (d){ if (color_factor != "") return colors(d[color_factor]); return colors(""); }); // Update legend colorLegend = colorLegend.data([]); colorLegend.exit().remove(); colorLegend = colorLegend .data(color_labels) .enter().append("li") .attr("id", function(d) { return d;}) .attr("class", "legend legend-no-interaction") .attr("selected", 0) .attr("title", function(d) { return d;}) colorLegendSpan = colorLegend.append("span") colorLegendSpan.append("svg") .attr("width", "10px") .attr("height", "10px") .style("margin-right", "5px") .style("overflow", "visible") .append("path") .attr("transform", "translate(5, 5)") .attr("d", d3.symbol() .type(d3.symbolSquare) .size(75)) .attr("stroke", "none") .attr("fill", function (d, i){ return colors(d); }) colorLegendSpan.append("span") .html(function(d) { return d;}) }; //////////////// Nodes shape settings //////////////// var setSymbolShape = function() { var shape_factor = d3.select("#symbolSelect").property("value"), shape_labels = []; if (shape_factor != "") { for (var i = 0; i < json0.nodes.length; i++) { if(shape_labels.indexOf(json0.nodes[i][shape_factor]) == -1) { shape_labels.push(json0.nodes[i][shape_factor]); }; }; }; symbols.domain(shape_labels); // Set symbols shape d3.selectAll(".symbol") .attr("d", d3.symbol() .type(function (d){ if (shape_factor != "") return symbols(d[shape_factor]); return symbols(""); }) .size(radius*radius)); symbolLegend = symbolLegend.data([]); symbolLegend.exit().remove(); if (shape_labels.length > 0) { symbolLegend = symbolLegend .data(shape_labels) .enter().append("li") .attr("id", function(d) { return d;}) .attr("class", "legend legend-no-interaction") .attr("selected", 0) .attr("title", function(d) { return d;}) symbolLegendSpan = symbolLegend.append("span") symbolLegendSpanSvg = symbolLegendSpan.append("svg") .attr("width", "10px") .attr("height", "10px") .style("margin-right", "5px") .style("overflow", "visible") .append("path") .attr("transform", "translate(5, 5)") .attr("d", d3.symbol() .type(function(d) { return symbols(d); }) .size(75)) .attr("stroke", "#333") .attr("fill-opacity", 0) symbolLegendSpan.append("span") .html(function(d) { return d;}) } }; //////////////// Control buttons //////////////// // Select colors var colorSelect = buttons.append("div") .attr("title", "Chose variable to use for colors.") .attr("class", "form-group") colorSelect.append("label") .html("Color") colorSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "colorSelect") .attr("class", "form-control multiselect figtool-multiselect") .on("change", setSymbolColor) .selectAll("option") .data(colorFactors) .enter().append("option") .text(function (d){ return d;}); document.getElementById("colorSelect").value = colorFactors[0]; // Button for node size var symbolSelect = buttons.append("div") .attr("title", "Chose variable to use for symbols.") .attr("class", "form-group") symbolSelect.append("label") .html("Symbol") symbolSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "symbolSelect") .attr("class", "form-control multiselect figtool-multiselect") .on("change", setSymbolShape) .selectAll("option") .data(colorFactors) .enter().append("option") .text(function (d){ return d;}); document.getElementById("symbolSelect").value = colorFactors[colorFactors.length-1]; //////////////// Link width settings //////////////// var setLinkWidth = function() { var wIndex = d3.select("#networkSelect").property("value"); var newMin = getWeightExtrema(json.links, wIndex, "min", Abs=true), newMax = getWeightExtrema(json.links, wIndex, "max", Abs=true); link .attr("stroke-width", function (d) { return scale(wRange, Math.abs(d.weight[wIndex]), [newMin, newMax]); }); var force_factor = 1+json.nodes.length*json.nodes.length; simulation .force("link", d3.forceLink() .id(function(d) {return d.id;}) .distance(function(d) { return scale([100, 50], d.weight[wIndex], [newMin, newMax])/force_factor; }) .strength(function(d) { return scale([0.3, 1], d.weight[wIndex], [newMin, newMax]); })); }; //////////////// Threshold settings //////////////// var filterThres = function() { var wIndex = d3.select("#networkSelect").property("value"); var wMin = getWeightExtrema(json0.links, wIndex, "min"), wMax = getWeightExtrema(json0.links, wIndex, "max"); // Filter links based on weight if (wMin > 0 || wMax > 0) { var similarity_thres = d3.select("#sThresRange").property("value"), sThres = scale([0, wMax], similarity_thres, [0, 100]), sTxt = ">"+ Math.round(sThres*100)/100; } else { var sThres = 0; }; json.links = []; json0.links.forEach(function (d) { if (Number(d.weight[wIndex]) > Number(sThres)) { json.links.push(JSON.parse(JSON.stringify(d))); }; }); // Update links legend linkLegend = linkLegend.data([]); linkLegend.exit().remove(); linkLegend = linkLegend .data([sim_color]) .enter().append("li") .attr("id", function(d) { return d[1];}) .attr("class", "legend legend-no-interaction") .attr("selected", 0) .attr("title", function(d) { return d[1];}) linkLegendSpan = linkLegend.append("span") linkLegendSpanSvg = linkLegendSpan.append("svg") .attr("width", "25px") .attr("height", "10px") .style("margin-right", "5px") .style("overflow", "visible") linkLegendSpanSvg.append("rect") .attr("width", 25) .attr("height", 5) .attr("y", 2.5) .attr("stroke", "none") .attr("fill", function(d){return d[0]}) linkLegendSpan.append("span") .html(function(d, i) { return d[1] +" "+sTxt;}) }; //////////////// Restart function //////////////// var restart = function() { filterThres(); // Update links link = link.data([]); link.exit().remove(); link = link .data(json.links); link = link.enter() .append("line") .attr("class", "link-line") .attr("stroke-linecap", "round") .attr("stroke", sim_color[0]); // Update nodes node = node.data([]); node.exit().remove(); node = node .data(json.nodes); node = node.enter() .append("g") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)) .append("path") .attr("class", "coloured-symbol symbol") .attr("stroke", "white"); // Update labels nodeLabel = nodeLabel.data([]); nodeLabel.exit().remove(); nodeLabel = nodeLabel .data(json.nodes); nodeLabel = nodeLabel.enter() .append("g") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)) .append("g") .attr("class", "node-label"); nodeLabel.append("path") .attr("class", "symbol") .attr("stroke", "#333333") .attr("fill-opacity", 0) - .style("stroke-opacity", 0); + .attr("stroke-opacity", 0); nodeLabel.append("text") .text(function (d) { var label = d.name.split(";"); if (label.length > 1) { return label[label.length-2] +";"+ label[label.length-1]; } else { return label[0]; }; }) .attr("text-anchor", "start") .attr("font-family", font_family) .attr("display", "none") .attr("selected", false); - displayLabels("node-label"); + displayLabels(".node-label"); showLabels(); // Apply display settings setSymbolColor(); setSymbolShape(); setLinkWidth(); simulation = simulation .nodes(json.nodes) .on("tick", ticked); simulation.force("link") .links(json.links); simulation.alpha(0.5).restart(); }; //////////////// Control buttons //////////////// // Display labels button var showLabels = function() { var label_sym = d3.selectAll(".node-label").select(function(){ return this.childNodes[0];}), label_text = d3.selectAll(".node-label").select(function(){ return this.childNodes[1];}); if ($("#labelButton").is(':checked')) { label_text.attr("display", "inline"); label_text.attr("selected", true); - label_sym.style("stroke-opacity", 1); } else { label_text.attr("display", "none"); label_text.attr("selected", false); - label_sym.style("stroke-opacity", 0); }; }; + appendLabelCheckBox(buttons, "Show labels", "Labels", "labelButton", showLabels) - + // Search in labels + var searchLabels = function() { + searchLabels2("#labelButton", "#searchInput", ".node-label") + } + appendSearchInput(buttons, "Search", "searchInput", searchLabels); + + /* var searchLabels = function() { $("#labelButton").attr("checked", false); var key = $("#searchInput").val().toUpperCase(); if (key != '') { var selected = d3.selectAll(".node-label").filter(function(){return this.__data__.name.toUpperCase().indexOf(key.toUpperCase()) != -1 }); non_selected = d3.selectAll(".node-label").filter(function(){return this.__data__.name.toUpperCase().indexOf(key.toUpperCase()) == -1 }); selected.select(function(){ return this.childNodes[1];}).attr("display", "inline"); selected.select(function(){ return this.childNodes[1];}).attr("selected", true); selected.select(function(){ return this.childNodes[0];}).style("stroke-opacity", 1); non_selected.select(function(){ return this.childNodes[1];}).attr("display", "none"); non_selected.select(function(){ return this.childNodes[1];}).attr("selected", false); non_selected.select(function(){ return this.childNodes[0];}).style("stroke-opacity", 0); } else { to_free = d3.selectAll(".node-label"); to_free.select(function(){return this.childNodes[1];}).attr("display", "none"); to_free.select(function(){return this.childNodes[1];}).attr("selected", false); to_free.select(function(){ return this.childNodes[0];}).style("stroke-opacity", 0); }; }; appendSearchInput(buttons, "Search", "searchInput", searchLabels); +*/ // Select network var networkSelect = buttons.append("div") .attr("title", "Chose network to show.") .attr("class", "form-group") networkSelect.append("label") .html("Network") networkSelect.append("div") .attr("class", "multiselect-btn-container figtool-multiselect-btn-container") .append("select") .attr("id", "networkSelect") .attr("class", "form-control multiselect figtool-multiselect") .on("change", restart) .selectAll("option") .data(networkSelector) .enter().append("option") .attr("value", function (d, i){ return i;}) .text(function (d){ return d;}); document.getElementById("networkSelect").value = 0; // Button for link weight threshold var sThresRange = buttons.append("span") .attr("title", "Cut-off for similarity links.") sThresRange.append("label") .append("p") .html("Similarity link cut-off ().") sThresRange.append("input") .attr("id", "sThresRange") .attr("type", "range") .attr("class", "full-width") .attr("min", 0) .attr("max", 100) .attr("value", 75) .on("change", restart); setMultiselect('.figtool-multiselect'); //resizeMultiselect('#d3-buttons', 1, '#d3-buttons', false); $("#networkSelect").on("change", restart) $("#colorSelect").on("change", setSymbolColor) $("#symbolSelect").on("change", setSymbolShape) + // Nodes labels functions + function displayLabels (labels) { + $(labels).on("mouseenter", function() { + d3.select(this.childNodes[1]).attr("display", "inline"); + d3.select(this.childNodes[0]).attr("stroke-opacity", 1); + }); + $(labels).on("mouseleave", function() { + if (this.childNodes[1].getAttribute("selected") == "false") { + d3.select(this.childNodes[1]).attr("display", "none"); + }; + d3.select(this.childNodes[0]).attr("stroke-opacity", 0); + }); + $(labels).on("click", function() { + if (this.childNodes[1].getAttribute("selected") == "false") { + d3.select(this.childNodes[1]).attr("display", "inline"); + d3.select(this.childNodes[1]).attr("selected", true); + } else { + d3.select(this.childNodes[1]).attr("display", "none"); + d3.select(this.childNodes[1]).attr("selected", false); + } + }); + }; + restart(); - // Nodes labels functions + /* function displayLabels (id) { $("."+id).on("mouseenter", function(d) { d3.select(this.childNodes[1]).attr("display", "inline"); d3.select(this.childNodes[0]).style("stroke-opacity", 1); }); $("."+id).on("mouseleave", function(d) { if (this.childNodes[1].getAttribute("selected") == "false") { d3.select(this.childNodes[1]).attr("display", "none"); d3.select(this.childNodes[0]).style("stroke-opacity", 0); }; }); $("."+id).on("click", function(d) { if (this.childNodes[1].getAttribute("selected") == "false") { d3.select(this.childNodes[1]).attr("display", "inline"); d3.select(this.childNodes[1]).attr("selected", true); d3.select(this.childNodes[0]).style("stroke-opacity", 1); } else { d3.select(this.childNodes[1]).attr("display", "none"); d3.select(this.childNodes[1]).attr("selected", false); } }); }; - //}); + */ }; diff --git a/app/assets/stylesheets/figures.css.scss b/app/assets/stylesheets/figures.css.scss index 05f3796..9202482 100644 --- a/app/assets/stylesheets/figures.css.scss +++ b/app/assets/stylesheets/figures.css.scss @@ -1,293 +1,317 @@ +#fig-description-container.active { + border:1px solid rgba(0,0,0,.125); + border-radius:.25rem; +} + +#fig-description-btn { + + color:#333; + cursor: pointer; + float:right; + + &:hover, + &:focus, + &:active, + &.active { + color:#014c8c; + } +} + +.network-well { + border:1px solid rgba(0,0,0,.125); +} + + .figure-layout { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; flex-wrap: wrap; min-height:100vh; } .fig-container { min-height:600px; max-height:90vh; height:auto; padding:10px; #fig { width:100%; height:100%; overflow:auto; } .svg-figure { display:table; margin:auto; } } .btn-container { margin-top:10px; margin-bottom:10px; } .btn-sep { border-top:1px solid #666; display:inline-block; width:100%; margin-bottom:5px } .fig-legend-container { max-height:90vh; height:auto; padding:10px; #fig-legend { width:100%; height:100%; overflow:auto; } .columns-1 { columns: 1; -webkit-columns: 1; -moz-columns: 1; } .columns-2 { columns: 2; -webkit-columns: 2; -moz-columns: 2; } .columns-4 { columns: 4; -webkit-columns: 4; -moz-columns: 4; } } // BAR FOR FIGURE MODIFICATION/SEARCH TOOLS .figure-btn-container { padding-top:7px; padding-bottom:7px; background-color:#333; color:#fff; display: flex; flex-direction: column; overflow:visible; .export .dropdown>ul>li { cursor:pointer; } .figure-btn { border-top:1px solid #666; display:inline-block; .search-input{ margin-top:5px; i { color:#666; } } .icon-sim-link { float:left; margin-right:5px; } .icon-dissim-link { float:left; margin-right:5px; } } } // TOP AND BOTTOM SCROLL BARS .scrollbar-top-container, .double-scrolled-container { overflow-x: auto; overflow-y:hidden; } .scrollbar-top-container { height: 20px; } .double-scrolled-container { height: 100%; } .scrollbar-top { height: 100%; } .double-scrolled { height:100%; overflow: visible; } // THE FIGURE ITSELF (PLOTS, LEGEND, ETC...) .figure-container { background-color:#fff; display: flex; flex-direction: column; overflow:hidden; height:auto; min-height:100vh; .figure-container-margin { padding-bottom:25px; margin-right:5px; margin-left:-15px; } .label-sm { font-size:12px; } .svg-figure { display:table; margin:auto; } .network-well { padding:0; } // FIGURE LEGEND .legend-container { padding:5px; padding-bottom:15px; height:100%; overflow:hidden; overflow-y:auto; overflow-x:auto; .legend-container-margin { padding:10px; } p { margin-top:10px; font-weight:bold; } .sidebar-sub-title { font-style: italic; } .svg-legend { padding:0; padding-left:20px; margin:5px; height:100%; } ul { padding:5px; &>li { cursor:pointer; padding:5px; } .legend-no-interaction { cursor:auto; } } .svg-legend-with-title { margin-top:40px; } ul>li>span { width:100%; display:inline; white-space:nowrap; i { margin-right:5px; } } } } // MOVING SIDEBAR FOR THE DESCRIPTION OF THE FIGURE .sidebar { position:absolute; right:0px; width:20px; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; background-color:rgba(255, 0, 102, 0.9); padding:2px; -webkit-transition:width .3s ease-out, padding .3s ease; -moz-transition:width .3s ease-out, padding .3s ease; -ms-transition:width .3s ease-out, padding .3s ease; transition:width .3s ease-out, padding .3s ease; overflow:hidden; border-left:4px solid #e6005c; -webkit-box-shadow: -2px 0px 3px -2px #888888; -moz-box-shadow: -2px 0px 3px -2px #888888; box-shadow: -2px 0px 3px -2px #888888; .sidebar-icon { transform:rotate(0deg); color:#fff; -webkit-transition:transform .3s linear .4s; -moz-transition:transform .3s linear .4s; -ms-transition:transform .3s linear .4s; transition:transform .3s linear .4s; } .description{ visibility:hidden; opacity:0; -webkit-transition-delay:visibility .3s; -moz-transition-delay:visibility .3s; -ms-transition-delay:visibility .3s; transition-delay:visibility .3s; -webkit-transition:opacity .1s ease .3s; -moz-transition:opacity .1s ease .3s; -ms-transition:opacity .1s ease .3s; transition:opacity .1s ease .3s; word-break: normal; text-align:justify; h3 { margin-top:4px; color:#fff; small { color:#fff; } } } } // LOGS .log-title { text-align:center; color:#fff; margin-right: auto; margin-left: auto; display: table; text-shadow: 3px 3px 3px #333; small { color:#fff; } } .log-pre { background-color: rgba(0, 0, 0, 0.4); color:#fff; width:75%; margin-left:auto; margin-right:auto; } diff --git a/app/assets/stylesheets/form.css.scss b/app/assets/stylesheets/form.css.scss index 7999b27..94705d2 100644 --- a/app/assets/stylesheets/form.css.scss +++ b/app/assets/stylesheets/form.css.scss @@ -1,476 +1,455 @@ .important { color:red } div.missing_field input{ border-color:red } div.missing_field span.custom-file-control{ border-color:red } .margin_addon { margin-right:10px } .card-text { margin-bottom:15px; } label { margin-bottom:2px; } .form-bool { margin-left:1.25rem; } .group_field { margin-bottom:5px; } .field_group_content{ padding:5px } .fold_bar { background-color:grey; text-align:right } .fold_bar i { color:white; text-align:right } .form-number-field { max-width:80px; overflow:hidden; text-align:right; } .form_card { height:50vh; overflow-y:auto; } #customFile .custom-file-control:lang(en)::after { content: "Select file..."; } #customFile .custom-file-control:lang(en)::before { content: "Click me"; } /*when a value is selected, this class removes the content */ .custom-file-control.selected:lang(en)::after { content: "" !important; } .custom-file { overflow: hidden; } .custom-file-control { white-space: nowrap; } .form-categ { position:absolute; border: solid 1px #999; border-bottom: solid 4px #999; padding:7px; background-color:#f2f2f2; color:#666; height:90vh; width:93%; overflow:hidden; overflow-y:auto; -webkit-transition:max-height .6s ease, background-color .2s ease, border .2s ease; -moz-transition:max-height .6s ease, background-color .2s ease, border .2s ease; -ms-transition:max-height .6s ease, background-color .2s ease, border .2s ease; transition:max-height .6s ease, background-color .2s ease, border .2s ease; .indent-field { margin-left:10px; } .bordered-top-field { border-top:solid 1px #ccc; padding-top:5px; } .form-tabs-selector { margin-bottom:15px; } &:hover { z-index:100; height:auto; max-height:110%; overflow-y:auto; border: solid 1px #333; background-color: #fff; color:#333; -webkit-box-shadow: -1px 1px 2px 1px #B3B3B3; -moz-box-shadow: -1px 1px 2px 1px #B3B3B3; box-shadow: -1px 1px 2px 1px #B3B3B3; } } .file_download { margin-bottom:32px; display:none; &:hover, &:focus, &:active, &.active { color:#333; } } -.help-button { - - .label-text, - .help-icon { - cursor:pointer; - color:#666; - - &:hover, - &:focus, - &:active, - &.active { - color:#333; - } - } - - .help-icon { - float:right; - margin-right:10px; - } -} - .help-block { padding-top:8px; max-height:0; height:auto; opacity:0; visibility: hidden; -webkit-transition: max-height 0.4s ease, opacity 0.4s ease, visibility 0.8s; -moz-transition: max-height 0.4s ease, opacity 0.4s ease, visibility 0.8s; -ms-transition: max-height 0.4s ease, opacity 0.4s ease, visibility 0.8s; transition: max-height 0.4s ease, opacity 0.4s ease, visibility 0.8s; .file_example { margin-top:2px; display:block; } } .expandable { display:block; border: 1px solid #333; border-radius:5px; padding: 3px; margin-top:2px; margin-bottom:5px; max-height:100%; height:auto; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; -webkit-transition:max-height 1.5s ease; -moz-transition:max-height 1.5s ease; -ms-transition:max-height 1.5s ease; transition:max-height 1.5s ease; .expandable-image { position:absolute; right:90px; margin-left:2px; background-color:#fff; border: 1px solid #d9d9d9; height:auto; max-height:40px; -webkit-box-shadow: 0; -moz-box-shadow: 0; box-shadow: 0; -webkit-transition:max-height 0.7s ease, transform 0.7s ease, webkit-box-shadow 0.7s ease; -moz-transition:max-height 0.7s ease, transform 0.7s ease, -moz-box-shadow 0.7s ease; -ms-transition:max-height 0.7s ease, transform 0.7s ease, box-shadow 0.7s ease; transition:max-height 0.7s ease, transform 0.7s ease, box-shadow 0.7s ease; &:hover { z-index:200; max-height:120px; transform:translate(0, -40px); -webkit-box-shadow: -1px 1px 2px 1px #B3B3B3; -moz-box-shadow: -1px 1px 2px 1px #B3B3B3; box-shadow: -1px 1px 2px 1px #B3B3B3; } } a { color: #666; &:hover, &:focus, &:active, &.active { color:#333; } } .slide-open, .slide-close { padding:3px; float:right; margin-right:7px; } .slider { cursor: pointer; } .slide { display: block; max-height:0; height:auto; opacity:0; visibility: hidden; margin-top: -15px; width: 98%; padding:7px; -webkit-transition:max-height 0.5s ease, opacity 0.5s ease, visibility 0.9s; -moz-transition:max-height 0.5s ease, opacity 0.5s ease, visibility 0.9s; -ms-transition:max-height 0.5s ease, opacity 0.5s ease, visibility 0.9s; transition:max-height 0.5s ease, opacity 0.5s ease, visibility 0.9s; } } .multiselect-btn-container { height:35px; position:relative; display:block; } .form-text { width:auto; max-width:90%; } .input-group-number { width:auto; max-width:110px; } .form-number { text-align:right; } .input-group-addon { width:auto; max-width:50%; } .text-field { color:#333; overflow: hidden; display: inline-block; padding: 0.4em 0.4em; border:solid #999999 1px; border-radius: 5px; background-color: rgba(255, 255, 255, 1); box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; text-decoration: none; font-weight: normal; font-family: verdana, arial, helvetica, sans-serif; white-space: nowrap; outline: none; } .big-text-field { width: 100%; font-size: 20px; line-height: 22px; } input[type=number] {-moz-appearance: textfield;} ::-webkit-inner-spin-button { -webkit-appearance: none;} .form-text-field { width:80%; } .form-select-field, .select-from-file-row, .select-from-field { width:50%; } .text-field:hover, .text-field:focus, .text-field:active, .text-field.active { -webkit-box-shadow: 0px 0px 1px 1px #999999; -moz-box-shadow: 0px 0px 1px 1px #999999; box-shadow: 0px 0px 1px 1px #999999; } .parameters { width: 400px; border: solid 2px #599bdc; margin:0; padding: 7px; background-color: #fff; -webkit-box-shadow: -1px 3px 3px -1px #888888; -moz-box-shadow: -1px 3px 3px -1px #888888; box-shadow: -1px 3px 3px -1px #888888; h1 { color: #fff; background-color:#599bdc; padding: 1px 1px 1px 15px; font-size: 22px; margin-left: -7px; margin-right: -7px; margin-top: -7px; margin-bottom: 15px; } p { padding: 1px 15px 1px 15px; font-size: 14px; margin: -7px; margin-bottom: 0px; } .select-from-file-category { overflow: hidden; padding:0; border:none; background-color:#fff; width:100%; height:38px; option { line-height: 25px; border:1px solid #666666; border-radius:50%; padding:0px; margin:3px; width:25px; height:25px; text-align:center; display:inline-block; float:right; background-color:#F4F4F4; } option:checked, option:hover { color:#fff; background-color:#599bdc; } } .select-from-file-category:disabled { option { width:auto; border:none; border-radius:0px; color:#8F9499; background-color:#fff; background-image: none; } } .field_categ { padding: 7px; .field { margin:0px; width: 100%; overflow: hidden; display: inline-block; padding: 7px; border:solid #999999 1px; border-radius: 5px; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; } h1 { padding: 0px 0px 0px 15px; font-size: 18px; margin: -7px; background-color: #808080; color: #fff; -webkit-box-shadow: -1px 1px 2px 1px #B3B3B3; -moz-box-shadow: -1px 1px 2px 1px #B3B3B3; box-shadow: -1px 1px 2px 1px #B3B3B3; } .legend { display:inline-block; max-width:200px; width:auto; vertical-align:top; background-color: #F3F4FF; font-size:12px; line-height: 18px; border-radius:2px; padding:2px; overflow:hidden; .legend-text { display:inline; } } .test-file { display:inline-block; width:250px; line-height: 22px; margin-top:5px; margin-left:2px; .test-select { font-size:14px; float:right; } .test-text { display:inline-block; font-size:14px; float:left; } } .comment { margin-top:10px; } } } diff --git a/app/assets/stylesheets/general.css.scss b/app/assets/stylesheets/general.css.scss index dad9259..cbf86e4 100644 --- a/app/assets/stylesheets/general.css.scss +++ b/app/assets/stylesheets/general.css.scss @@ -1,301 +1,301 @@ #main{ margin:5px; height:100vh; } .main-container { min-height:100vh; height:auto; padding-top:3px; } .doc-table tr td, .doc-table tr th, .version-table tr td { text-align:left } .border-left { border-left:2px solid grey; } .border-right { border-right:2px solid grey; } .navbar-bottom { display:table; width:100%; margin: 0; } .navbar-bottom > li { float:none; display:table-cell; text-align:center; } .side-index-container { padding:0; z-index:0; .side-index { height:100vh; overflow:auto; width:100%; .side-index-list { margin-bottom:50vh; padding-left:5px; padding-right:5px; } } } .topbar-padding { padding-top:56px } .topbar-margin { margin-top:56px } .dropdown-item { cursor:pointer; } .step-card{ margin-bottom:5px } .float-right { float:right } .align-center { text-align:center; } .align-left { text-align:left; } .align-right { text-align:right; } ul.no-bullets li { - list-style-type: none + list-style-type:none; } .full-width { width:100%; } .full-height { height:100%; } .align-items-center { align-items:center; } .hidden { display:none; } .video-wrapper { position: relative; padding-bottom: 56.25%; /* 16:9 */ padding-top: 25px; height: 0; iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } } #popup_window { position: absolute; z-index:100000; padding:15px; display: none; background: #ccc; border: 1px solid; } #popup_window_close { float:right; font-size:20px; margin-top:-19px; margin-right:-10px; cursor:pointer; } .tip_window { position: fixed; z-index:100000; padding:15px; display: none; background: #e6e6e6; border: 1px solid; } .tip_window_close { padding-top:10px; cursor:pointer; } .title_popup {white-space:nowrap;font-weight:bold} .infos { position:static; } .background-col-1 { background-color:#e6e6e6; } .background-col-2 { color:#fff; background-color:#999; } .row-padding-10 { padding-top:10vh; padding-bottom:10vh; } .row-padding-5 { padding-top:5vh; padding-bottom:5vh; } .row-padding-2 { padding-top:2vh; padding-bottom:2vh; } .row-padding-bottom-20 { padding-bottom:20vh; } .v-align-middle { height:100%; transform:translate(0, 35%); } .back-to-top { color:#999; text-decoration: none; &:hover, &:focus, &:active { color:#ccc; text-decoration: none; } } .content { position:static; display: table; margin-right: auto; margin-left: auto; margin-top: 60px; margin-bottom: 60px; .left { float:left; margin-right:20px; } .right { float:right; margin-left:20px; } .align-left { text-align:left; } .align-right { text-align:right; } .centered-div { width: 260px; line-height: 300px; } .centered-span { display: inline-block; vertical-align: middle; line-height: normal; } } a { color: #333; &.white { color: #fff; } &:link, &:visited { text-decoration: none; } &:hover { text-decoration: underline; &.button, &.tab, &.subtab, &.help-button, &.slider, &.slide-open, &.slide-close, &.test-button { text-decoration: none; } &.slide-open, &.slide-close { text-shadow: 1px 0px #333, -1px -0px #333, 0px -1px #333, 0px 1px #333; } } } h1 { font-weight: 500; font-size: 28px; line-height: 34px; } h2 { font-weight: 500; font-size: 24px; line-height: 30px; } h3 { font-weight: 500; font-size: 20px; line-height: 25px; } pre { font-family: verdana, arial, helvetica, sans-serif; background-color: #eee; padding: 10px; font-size: 12px; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word; } diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controller.rb index d66ed0b..70dc24d 100644 --- a/app/controllers/jobs_controller.rb +++ b/app/controllers/jobs_controller.rb @@ -1,771 +1,772 @@ class JobsController < ApplicationController before_action :set_job, only: [:show, :edit, :update, :destroy, :serve, :view, :refresh, :clone] # before_action :authenticate_user!, only: [:show, :edit, :update, :destroy, :serve] def kill_job j tmp_dir = Pathname.new(APP_CONFIG[:data_dir]) + "users" + j.user_id.to_s + j.key main_pid = File.read(tmp_dir + ".pid") if File.exist?(tmp_dir + ".pid") # kill job if one already running existing_main_job = `ps -ef | grep #{j.key} | grep #{main_pid} | grep -v 'grep'` puts "Existing main job: " + existing_main_job.to_json # pids=[] if main_pid and !existing_main_job.empty? lines = `ps -ef | grep #{j.key} | grep -v 'grep'`.split("\n").select{|l| !l.empty?} pids = lines.map{|l| t= l.split(/\s+/); t[1]} pids.unshift(main_pid) if pids.size > 0 pids.each do |pid| cmd = "kill #{pid}" `#{cmd}` end end end # delete log.json log_json = tmp_dir + "output" + "log.json" logger.debug("DEBUG: " + log_json.to_s) File.delete log_json if File.exist? log_json end def clone #if current_user tmp_dir = Pathname.new(APP_CONFIG[:data_dir]) + "users" + @job.user_id.to_s + @job.key @new_job = nil if current_user @new_job = @job.dup session[:current_key] = create_key() @new_job.key = session[:current_key] else current_job = Job.where(:key => session[:current_key]).first if current_job ### kill current job kill_job(current_job) @new_job = current_job @new_job.update_attributes(:form_json => @job.form_json, :output_json => @job.output_json, :name => @job.name, :description => @job.description, :status => @job.status) else @new_job = @job.dup @new_job.key = session[:current_key] # @new_job.read_access = true # @new_job.write_access = true end end # kill_job(@job) ### clone @new_job.sandbox = (current_user) ? false : true @new_job.user_id = (current_user) ? current_user.id : 1 new_tmp_dir = Pathname.new(APP_CONFIG[:data_dir]) + "users" + @new_job.user_id.to_s Dir.mkdir(new_tmp_dir) if !File.exist? new_tmp_dir new_tmp_dir += @new_job.key if File.exist? new_tmp_dir FileUtils.rm_r new_tmp_dir Dir.mkdir new_tmp_dir end FileUtils.cp_r tmp_dir.to_s + "/.", new_tmp_dir ### rename cloned tar.gz FileUtils.mv (new_tmp_dir + (@job.key + ".tar.gz")), (new_tmp_dir + (@new_job.key + ".tar.gz")) if File.exist?(new_tmp_dir + (@job.key + ".tar.gz")) ### change filepaths in the result file cmd = "perl -i -pe 's/\\/#{@job.user_id}\\/#{@job.key}\\//\\/#{@new_job.user_id}\\/#{@new_job.key}\\//g' #{new_tmp_dir}/output/log.json" logger.debug("CMD: #{cmd}") `#{cmd}` ### delete tmp_dir FileUtils.rm_r new_tmp_dir + "tmp" if File.exist?(new_tmp_dir + "tmp") @new_job.name += " cloned" if @new_job.save redirect_to job_path(@new_job.key) end #else # render :nothing => true # end end def serve if readable? @job tmp_dir = Pathname.new(APP_CONFIG[:data_dir]) + 'users' + @job.user.id.to_s + @job.key #params[:key] # + params[:step] # tmp_dir += params[:item_id].to_s if params[:item_id] filename = params[:filename] #|| 'dl_output.tab' filepath = tmp_dir + filename send_file filepath.to_s, type: params[:content_type] || 'text', disposition: (!params[:display]) ? ("attachment; filename=" + filename.gsub("/", "_")) : '' end end def read_file_header if params[:file_key] new_data = [] user_id = (current_user) ? current_user.id.to_s : "1" dir = Pathname.new(APP_CONFIG[:data_dir]) + "users" + user_id + session[:current_key] + 'input' filename = params[:file_key] + ".txt" filepath = dir + filename test = "" if File.exist? filepath lines = [] File.open(filepath, "r") do |f| while(l = f.gets) do lines.push(l.chomp) end end j=0 (0 .. lines.size-1).to_a.each do |i| if !lines[i].match(/^#/) j=i break end end i = (j > 1) ? j-1 : 0 headers = lines[i].split("\t") if params[:add_blank] new_data.push({label:"", value:""}); end if headers.size > 0 headers.each do |header| new_data.push({:label => header, :value => header}); end end end render :text => new_data.to_json else render :nothing => true end end def read_file_column if params[:file_key] new_data = [] user_id = (current_user) ? current_user.id.to_s : "1" dir = Pathname.new(APP_CONFIG[:data_dir]) + "users" + user_id + session[:current_key] + 'input' filename = params[:file_key] + ".txt" filepath = dir + filename if File.exist? filepath lines = File.readlines(filepath) j=0 (0 .. lines.size-1).each do |i| if !lines[i].match(/^#/) j = i break end end i = (j > 1) ? j-1 : 0 header_els = lines[i].chomp.split("\t") pos_col = 0 (0 .. header_els.size).each do |k| if header_els[k] == params[:col_name] pos_col = k break end end max_val=0 (i .. lines.size-1).each do |j| t = lines[j].split("\t") n = t[pos_col].split(";").size if n > max_val max_val = n end end if max_val > 0 (1 .. max_val+1).each do |j| new_data.push({:label => j, :value => j}); end end end render :text => new_data.to_json else render :nothing => true end end # BUILD THE INDEX OF JOBS FROM CURRENT USER OR ALL (ADMIN ONLY) def index if current_user if current_user.role == "admin" #and session[:which] == "all" @jobs = Job.all @users = User.all else#if current_user @jobs = current_user.jobs.all end end respond_to do |format| format.html{ if !current_user redirect_to "/welcome" end } end end def get_views @h_views = {} View.all.each do |v| @h_views[v.name]= v end end def get_statuses log_file = Pathname.new(APP_CONFIG[:data_dir]) + "users" + @user.id.to_s + @job.key + 'output' + 'log.json' #stdout_file = Pathname.new(APP_CONFIG[:data_dir]) + "users" + @user.id.to_s + @job.key + 'output' + 'stdout.log' #stderr_file = Pathname.new(APP_CONFIG[:data_dir]) + "users" + @user.id.to_s + @job.key + 'output' + 'stderr.log' #if File.exist? stdout_file and !File.size(stdout_file) != 0 # @stdout_log = JSON.parse(File.read(stdout_file)).select {|e| e[0] != nil} #end #if File.exist? stderr_file and !File.size(stderr_file) != 0 # @stderr_log = JSON.parse(File.read(stderr_file)).select {|e| e[0] != nil} #end @h_statuses = {}#'completed' => 1, 'pending' => 2, 'running' => 3, 'failed' => 4} Status.all.each do |s| @h_statuses[s.name]= s end if File.exist? log_file and !File.size(log_file) != 0 @log_json = JSON.parse(File.read(log_file)) @final_json = { :global_status => nil, :status_by_step => {}, :status_by_substep => {}, :global_status_by_step => {}, :messages_by_step => {} } @h_icons={ - 'description' => '', + 'description' => 'info-circle', + 'comment' => 'info-circle', 'output' => 'file-text-o', 'warning' => 'exclamation-triangle icon-warning', 'error' => 'exclamation-triangle icon-danger' } @test = "" ### datasets @log_json.select{|e| ['dataset', 'map'].include?(e['type'])}.each do |cat| @final_json[:status_by_step][cat['name']] ||= [] @final_json[:global_status_by_step][cat['name']] ||= nil # @final_json[:messages_by_step][cat['name']] ||= [] cat['log'].select{|e| e['type'] != 'file'}.each do |el| @final_json[:status_by_substep][el['name']]=[] step_key = cat['name'] + "_" + el['name'] @final_json[:messages_by_step][step_key] ||= [] tmp_global_status = nil # @test += el['operations'].to_json if el['operations'] el['operations'].each do |el2| # @test += el2.to_json @final_json[:status_by_substep][el['name']].push({:name => el2['name'], :status => el2['status'], :execution_time => el2['execution_time'], :cat => cat['name']}) @final_json[:messages_by_step][step_key].push({:name => el2['name'], :messages => el2['messages'], :cat => cat['name']}) if !tmp_global_status or (@h_statuses[el2['status']] and @h_statuses[el2['status']].precedence > @h_statuses[tmp_global_status].precedence) tmp_global_status = el2['status'] # @test += el2['status'].to_json end end end @final_json[:status_by_step][cat['name']].push({:name => el['name'], :status => (el['status'] || tmp_global_status), :execution_time => el['execution_time']}) if !@final_json[:global_status_by_step][cat['name']] or (@h_statuses[el['status']] and @h_statuses[el['status']].precedence > @h_statuses[@final_json[:global_status_by_step][cat['name']]].precedence) @final_json[:global_status_by_step][cat['name']] = el['status'] end if !@final_json[:global_status] or (@h_statuses[el['status']] and @h_statuses[el['status']].precedence > @h_statuses[@final_json[:global_status]].precedence) @final_json[:global_status] = el['status'] end @final_json[:messages_by_step][step_key].push({:name => el['name'], :messages => el['messages']}) end # @final_json[:messages_by_step][cat['name']].push({:name => cat['name'], :messages => cat['messages']}) end ### analyses analyses = @log_json.select{|e| e['type'] == 'analysis'} if analyses.size > 0 analyses.first['log'].each do |el| @final_json[:global_status_by_step][el['name']] ||= nil @final_json[:status_by_step][el['name']] ||= [] # @final_json[:status_by_substep][el['name']]=[] @final_json[:messages_by_step][el['name']] ||= [] tmp_global_status = nil if el['levels'] el['levels'].each do |el2| @final_json[:status_by_step][el['name']].push({:name => el2['name'], :status => el2['status'], :execution_time => el2['execution_time']}) @final_json[:messages_by_step][el['name']].push({:name => el2['name'], :messages => el2['messages']}) if !tmp_global_status or (@h_statuses[el2['status']] and @h_statuses[el2['status']].precedence > @h_statuses[tmp_global_status].precedence) tmp_global_status = el2['status'] end end end if !@final_json[:global_status_by_step][el['name']] or (@h_statuses[el['status']] and @h_statuses[el['status']].precedence > @h_statuses[@final_json[:global_status_by_step][cat['name']]].precedence) @final_json[:global_status_by_step][el['name']] = el['status'] end if !@final_json[:global_status] or (@h_statuses[el['status']] and @h_statuses[el['status']].precedence > @h_statuses[@final_json[:global_status]].precedence) @final_json[:global_status] = el['status'] end @final_json[:status_by_step][el['name']].push({:name => el['name'], :status => (el['status'] || tmp_global_status), :execution_time => el['execution_time']}) @final_json[:messages_by_step][el['name']].push({:name => el['name'], :messages => el['messages']}) end end @update = 0 @status_vector = [] if @final_json if ['primary_dataset', 'map', 'secondary_dataset'].include? session[:selected_view] if @final_json[:status_by_step][session[:selected_view]] @status_vector += @final_json[:status_by_step][session[:selected_view]].map{|e| @h_statuses[e[:status]].id} @final_json[:status_by_substep].keys.sort.each do |k| @status_vector += @final_json[:status_by_substep][k].select{|e| e[:cat] == session[:selected_view]}.map{|e| @h_statuses[e[:status]].id} end end @final_json[:messages_by_step].keys.sort.each do |k| @status_vector += @final_json[:messages_by_step][k].select{|e| e[:cat] == session[:selected_view] and e[:messages]}.map{|e| e[:messages].size} end else if @final_json[:status_by_step][session[:selected_view]] @status_vector += @final_json[:status_by_step][session[:selected_view]].map{|e| @h_statuses[e[:status]].id} end if @final_json[:messages_by_step][session[:selected_view]] @status_vector += @final_json[:messages_by_step][session[:selected_view]].select{|e| e[:messages]}.map{|e| e[:messages].size} end end end # @final_json[:status_by_step].keys.sort.each do |name| # @status_vector.push(@final_json[:status_by_step][name].map{|e| @h_statuses[e[:status]].id}) # end # @final_json[:messages_by_step].keys.sort.each do |name| # @status_vector.push(@final_json[:messages_by_step][name].select{|e| e[:messages]}.map{|e| e[:messages].size}) # end if session[:status_vector] != @status_vector @update = 1 end end ## end file exist end def refresh # @user = (current_user) ? current_user : User.where(:username => 'guest').first # @user = @project. get_basic_info() get_statuses() render :partial => "refresh" end def view # @user = (current_user) ? current_user : User.where(:username => 'guest').first get_basic_info() get_statuses() get_views() @form_json = JSON.parse @job.form_json if @job.form_json and !@job.form_json.empty? if !session[:current_level] session[:current_level] = (@form_json['bin_levels'] and @form_json['bin_levels'].size > 0) ? @form_json['bin_levels'][0] : nil end session[:current_level] = params[:current_level].to_i if params[:current_level] @data_json = nil @filename = nil @imagename = nil @description = '' @i = 0 @log = '' if !['primary_dataset', 'secondary_dataset', 'map'].include?(params[:partial]) e = @log_json.select{|el| el['type'] == 'analysis'}.first['log'].select{|el| el['name'] == params[:partial]}.first if e['levels'] #and @form_json['bin_levels'] @i = @form_json['bin_levels'].index(session[:current_level].to_s) @form_json['bin_levels'].each_index do |li| l = @form_json['bin_levels'][li] @log += ">" + l.to_s + ";" if l == session[:current_level].to_s @i = li @log += '!!' end end e2 = e['levels'][@i] @filename = e2['messages'].select{|el| el['output'] and el['output'].match(/#{@h_views[params[:partial]].data_format}$/)}.map{|el| el['output']}.first @imagename = e2['messages'].select{|el| el['output'] and el['output'].match(/pdf|png|jpg$/)}.map{|el| el['output']}.first @description = e2['messages'].select{|el| el['description']}.map{|el| el['description']} else @i = -1 @filename = e['messages'].select{|el| el['output'] and el['output'].match(/#{@h_views[params[:partial]].data_format}$/)}.map{|el| el['output']}.first @imagename = e['messages'].select{|el| el['output'] and el['output'].match(/pdf|png|jpg$/)}.map{|el| el['output']}.first @description = e['messages'].select{|el| el['description']}.map{|el| el['description']} end end #end if @filename and File.exist? @filename @data_json = File.read(@filename) end session[:selected_view] = params[:partial] json_file = Rails.root.join('lib', 'genocrunch_console', 'etc', 'genocrunchlib.json') file_content = File.read(json_file) h = JSON.parse(file_content) @h_form_choices = h['choices'] render :partial => "view_" + params[:partial] end # ALLOW SESSION VARIABLE UPDATE ON SHOW # THIS ALLOW CONDITIONAL RENDERING OF EITHER FIGURES OR EDIT FORM PARTIALS def show # @user = (current_user) ? current_user : User.where(:username => 'guest').first # if current_user.role == "admin" and session[:which] == "all" # @jobs = Job.all # @users = User.all # else#if current_user # @jobs = current_user.jobs.all # end # session[:context] = params[:context] # logger.debug("JOB: " + @job.to_json) #check_box belongs_to get_basic_info() get_statuses() get_views() @analyses = [] @inputs = [] @h_job_form = JSON.parse(@job.form_json) session[:current_level] = (@h_job_form['bin_levels'] && @h_job_form['bin_levels'][0]) || nil @h_form['fields']['Inputs'].select{|f| f['type'] == 'file'}.each do |f| if @h_job_form[f['id']] @inputs.push({:id => f['id'], :label => f['label'] || f['id'].gsub(/_/, ' ').capitalize}) end end @h_form['fields']['Analysis'].select{|f| f['type'] == 'check_box' and !f['belongs_to']}.each do |f| if @h_job_form[f['id']] #and @h_job_form[f['id']]==true @analyses.push({:id => f['id'], :label => f['label'] || f['id'].gsub(/_/, ' ').capitalize}) end end if !readable? @job redirect_to root_path, notice: 'Cannot access this resource.' end end # ALLOW UPDATE OF INDEX UPON CHANGE AND ADMIN TO ACCESS ALL JOBS AND USERS def manage # session[:which] = params[:which] # session[:what] = params[:what] @previous_jobs = nil if current_user.role == "admin" #and params[:which] == "all" @jobs = Job.all @users = User.all @partial = "/admins/index" if defined?(params[:previous_jobs]) @previous_jobs = params[:previous_jobs] end else @jobs = current_user.jobs @users = current_user @partial = "/jobs/index" if defined?(params[:previous_jobs]) @previous_jobs = params[:previous_jobs] end end @id = "indexPartial" respond_to do |format| format.js end end def get_basic_info json_file = Rails.root.join('lib', 'genocrunch_console', 'etc', 'genocrunchlib.json') file_content = File.read(json_file) @h_form = JSON.parse(file_content) @h_tips = JSON.parse(File.read(Rails.root.join('public', 'app', 'tips.json'))) @h_help = {} @h_field_groups = {} - @h_form['fields'].keys.map{|card| @h_form['fields'][card]}.flatten.map{|field| - @h_help[field['id']]=field['help'] - ### add the fields with belongs_to to the @h_field_groups - if field['belongs_to'] - @h_field_groups[field['belongs_to']]||=[] - @h_field_groups[field['belongs_to']].push(field) - end + + @h_form['fields'].each_key{|card_title| + @h_form['fields'][card_title] = @h_form['fields'][card_title].select{|field| field['scope'] != 'cli_only'} + + @h_form['fields'][card_title].map{|field| + @h_help[field['id']] = field['help'] + if field['belongs_to'] + @h_field_groups[field['belongs_to']]||=[] + @h_field_groups[field['belongs_to']].push(field) + end + } } end # SET A NEW JOB def new @user = (current_user) ? current_user : User.where(:username => 'guest').first @job = @user.jobs.new -# if current_user session[:current_key] = create_key() - @job.key = session[:current_key] #(Job.where(:key => session[:current_key])) ? create_key() : session[:current_key] -# end + @job.key = session[:current_key] get_basic_info @default = {} end def set_fields p @missing_fields = [] @present_fields = [] get_basic_info @h_fields ={} - @h_form['fields'].each_key do |card_title| - @h_form['fields'][card_title].each do |f| - @h_fields[f['id']]=f - end - end + @h_form['fields'].each_key{|card_title| + @h_form['fields'][card_title].map{|field| + @h_fields[field['id']]=field + } + } @log = '' flag=0 if p flag=1 ### check if some parameters are not allowed p.each_key do |k| if !@h_fields[k] flag = 0 - # @log += k + " is missing!!!! FUCK" break end end ### check if all parameters required are submitted @h_fields.keys.select{|k| @h_fields[k]['optional'] == false}.each do |k| logger.debug("EXPLORE field " + k) - if (@h_fields[k]['type']== 'file' and ((p[k] and !p[k].blank? #p[k].original_filename and !p[k].original_filename.empty? + if (@h_fields[k]['type']== 'file' and ((p[k] and !p[k].blank? ) or !params[:p2][k].empty?) ) or (p[k] and !p[k].empty?) @present_fields.push(@h_fields[k]) else @missing_fields.push(@h_fields[k]) flag = 0 - # @log += k + " is missing!!!! FUCK2" end end end return flag end def create_dirs user_id = (current_user) ? current_user.id.to_s : "1" dir = Pathname.new(APP_CONFIG[:data_dir]) + "users" + user_id Dir.mkdir dir if !File.exist? dir dir = Pathname.new(APP_CONFIG[:data_dir]) + "users" + user_id + @job.key Dir.mkdir dir if !File.exist? dir end def write_files p user_id = (current_user) ? current_user.id.to_s : "1" dir = Pathname.new(APP_CONFIG[:data_dir]) + "users" + user_id + @job.key + 'input' Dir.mkdir dir if !File.exist? dir form_json = JSON.parse @job.form_json if @job.form_json and !@job.form_json.empty? # fields = @h_fields.keys.select{|k| @h_fields[k]['type'] == 'file' and p[k]} fields = ['primary_dataset', 'map', 'secondary_dataset'] fields.each do |k| logger.debug("Key:" + k) filepath = dir + ( k + '.txt') content = (p[k]) ? p[k].read : nil if content and !content.empty? params[:p2][k] = p[k].original_filename File.open(filepath, 'w') do |f| logger.debug("CONTENT FILE: " + content) f.write content end `dos2unix #{filepath}` `mac2unix #{filepath}` elsif form_json and form_json[k.to_s] params[:p2][k] = form_json[k.to_s]['original_filename'] params[:p][k] = form_json[k.to_s] end end end # CREATE A NEW JOB def create # @job = Job.new(job_params) @job = Job.new(:key => params[:tmp_key]) create_dirs() write_files(params[:p]) @valid_job = set_fields params[:p] @job.name = params[:p][:name] @job.description = params[:p][:description] @job.form_json = params[:p].to_json @default = params[:p] if current_user @job.user_id = current_user.id @job.sandbox = false end ## not possible to create a new job with an existing key @valid_job = 0 if !@job.key.empty? and Job.where(:key => @job.key).first respond_to do |format| if @valid_job==1 and @job.save @job.delay.perform # run analysis as delayed job session[:agree_with_terms] = true session[:current_key]=create_key() if current_user format.html { redirect_to job_path(@job.key) } format.json { render action: 'new', status: :created, location: @job } else format.html { render action: 'new' } format.json { render json: @job.errors, status: :unprocessable_entity } end end end def edit get_basic_info @default = JSON.parse(@job.form_json) params[:p2]={} ['primary_dataset', 'map', 'secondary_dataset'].select{|k| @default[k]}.each do |k| params[:p2][k] = @default[k]['original_filename'] end end # PATCH/PUT /jobs/1 # PATCH/PUT /jobs/1.json def update write_files(params[:p]) valid_job = set_fields params[:p] h_job = { :name => params[:p][:name], :description => params[:p][:description], :form_json => params[:p].to_json } @default = JSON.parse(@job.form_json) @default = params[:p] respond_to do |format| if valid_job==1 and @job.update_attributes(h_job) #kill job kill_job(@job) @job.delay.perform # run analysis as delayed job session[:agree_with_terms] = true # if current_user.role == "admin" and @job.user.role != "admin" # format.html { redirect_to jobs_url } # else # format.html { redirect_to jobs_url } format.html {redirect_to job_path(@job.key)} # end format.json { head :no_content } else format.html { render action: 'edit'} format.json { render json: @job.errors, status: :unprocessable_entity } end end end # DELETE /jobs/1 # DELETE /jobs/1.json def destroy @job.destroy respond_to do |format| format.html { redirect_to jobs_url } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_job # if current_user.role == "admin" # @job = Job.all.find(params[:key]) # else # @job = current_user.jobs.find(params[:key]) # end @job = Job.where(:key => params[:key] || params[:tmp_key]).first session[:current_key] = @job.key if action_name != 'clone' #if current_user @user = @job.user logger.debug("JOB: " + @job.to_json) # @job = nil if !readable? @job end # Never trust parameters from the scary internet, only allow the white list through. def job_params params.require(:job).permit() end end diff --git a/app/helpers/jobs_helper.rb b/app/helpers/jobs_helper.rb index adcbb90..c557694 100644 --- a/app/helpers/jobs_helper.rb +++ b/app/helpers/jobs_helper.rb @@ -1,160 +1,109 @@ module JobsHelper def display_duration t parts = [] tab = [] if t tab = t.split(":") parts.push(tab[0] + "h") if tab[0].to_i > 0 parts.push(tab[1] + "m") if tab[1].to_i > 0 sec = tab[2].to_f parts.push(sec.round(1).to_s + "s") if sec > 0.0 end return (parts.size > 0) ? "" + parts.join(" ") + "" : '' end def help_button f - html = "" + html = "" if f['trigger'] && f['trigger'] == 'drop_down' html += "" end return html end def field_label f label = (f['label']) ? f['label'] : f['id'].gsub(/_/, ' ').capitalize css_classes = [] - css_classes.push('form-check-label') if f['type'] == 'check_box' #) ? 'form-check-label' : '' + css_classes.push('form-check-label') if f['type'] == 'check_box' html = "" return [html, html2] end def field_check_box f val = '' if f['trigger'] && f['trigger'] == 'drop_down' val = (@default[f['db_field']] && @default[f['db_field']].include?(f['id'])) || @default[f['id']] || false - # val = @default[f['id']] || f['default'] else val = @default[f['id']] || false end -# id = (f['trigger'] && f['trigger'] == 'drop_down') ? "p[#{f['db_field']}][#{f['id']}]" : "p[#{f['id']}]" + id = "p[#{f['id']}]" label = field_label f html = "
    " html += label.first html += check_box_tag id, 1, val, {:class => 'form-check-input'} html += label.second html += "
    " -# if f['trigger'] && f['trigger'] == 'drop_down' -# html += "" -# end - return html end def field_bool f checked = @default[f['id']] || f['default'] label = field_label f html = "
    " html += label.first html += hidden_field_tag "p[#{f['id']}]", 'FALSE' html += check_box_tag "p[#{f['id']}]", 'TRUE', checked, {:class => 'form-check-input'} html += label.second html += "
    " return html end def field_integer f val = @default[f['id']] || f['default'] return text_field_tag "p[#{f['id']}]", val, {:placeholder => (f['placeholder'] || ''), :class => 'form-control' } end def field_hidden f val = @default[f['id']] || f['default'] return hidden_field_tag "p[#{f['id']}]", val end def field_text f val = @default[f['id']] || f['default'] css_classes = ["form-control full-width"] css_classes.push('belongs_to') if f['belongs_to'] return text_field_tag "p[#{f['id']}]", val, {:placeholder => (f['placeholder'] || ''), :class => css_classes.join(' ') } end def field_textarea f val = @default[f['id']] || f['default'] css_classes = ["form-control full-width"] css_classes.push('belongs_to') if f['belongs_to'] return text_area_tag "p[#{f['id']}]", val, {:placeholder => (f['placeholder'] || ''), :class => css_classes.join(' ') } end def field_select f, h_c val = @default[f['id']] || f['default'] l = (h_c) ? h_c.map{|e| [e['label'], e['value']]} : [] - css_class=(f['belongs_to']) ? 'belongs_to' : '' - html = "
    " + css_class=(f['belongs_to']) ? 'belongs_to' : '' + container_css_classes=['multiselect-btn-container'] + container_css_classes.push('hidden') if l.length == 0 + placeholder_css_classes=(l.length != 0) ? 'hidden' : '' + html = f['placeholder'] ? "#{f["placeholder"]}" : '' + html += "
    " html += select_tag "p[#{f['id']}]", options_for_select(l, val), {:placeholder => (f['placeholder'] || ''), multiple: (f["multiple"] && f["multiple"] == true), :class => "form-control full-width multiselect #{css_class}" } html += "
    " return html end - def toto - -# -# <%= f.text_field(key.to_sym, value: default_value, placeholder: ((field["options"].key?("placeholder"))? field["options"]["plac#eholder"] : ''), class:"form-control form-text") %> - - end - - - # Build a hash of paramters (sort of 'flatten' the app JSON) - # element_h (hash) A hash containing fields - # params_h (hash) Parameter hash to build - # type (array) Type of field to search for - def set_params(element_h, params_h, type) - for t in type - if element_h.keys.include? t - element_h[t].each do |key, field| - # Check if field represents a parameter to be set in the form - if field.keys.include? "param" and ["all", "web"].include? field["scope"] - # If field is not included in the parameter hash already, ad it - if !params_h.keys.include? field["param"] - params_h[field["param"].to_sym] = Hash.new - params_h[field["param"].to_sym][:optional] = field["optional"] - params_h[field["param"].to_sym][:scope] = field["scope"] - if field.keys.include? "type" - params_h[field["param"].to_sym][:type] = field["type"] - end - if field.keys.include? "options" - if field["options"].keys.include? "default" - params_h[field["param"].to_sym][:default] = field["options"]["default"] - end - if field["options"].keys.include? "hidden_if" - params_h[field["param"].to_sym][:hidden_if] = field["options"]["hidden_if"] - end - end - if ["text-if-file", "bool-if-file", "select-from-field", "select-from-file-row", "select-from-file-category", "select-heatmap-sidebar"].include? field["type"] - params_h[field["param"].to_sym][:options] = field["options"] - end - end - end - end - end - end - end end diff --git a/app/views/jobs/_card_form.html.erb b/app/views/jobs/_card_form.html.erb index 277c0bd..3fd85b0 100644 --- a/app/views/jobs/_card_form.html.erb +++ b/app/views/jobs/_card_form.html.erb @@ -1,20 +1,20 @@ <% list_fields.each do |field| %> <% css_classes = [] %> -<% css_classes.push('hidden') if (field['type'] == 'select' and !field['values'] and !@default[field['id']]) %> +<% css_classes.push("ml-#{field['increment']}") if field['increment'] %> <% css_classes.push('missing_field') if @missing_fields and @missing_fields.map{|e| e['id']}.include?(field['id']) %>
    ' class='card-text <%= css_classes.join(" ") %>'> <% label = field_label(field).join("") if !['check_box', 'model_type', 'bool'].include?field['type'] and !field['hidden_field'] %> <%= raw label %> <% if field['type'] %> <%= render :partial => "field_" + field['type'], :locals => {:field => field} %> <% else %> <%= field.to_json %> <% end %>
    <% end %> diff --git a/app/views/jobs/_field_basic_model.html.erb b/app/views/jobs/_field_basic_model.html.erb deleted file mode 100644 index 8b13789..0000000 --- a/app/views/jobs/_field_basic_model.html.erb +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/views/jobs/_field_batch_effect_suppression.html.erb b/app/views/jobs/_field_batch_effect_suppression.html.erb deleted file mode 100644 index d7364e6..0000000 --- a/app/views/jobs/_field_batch_effect_suppression.html.erb +++ /dev/null @@ -1 +0,0 @@ -bl diff --git a/app/views/jobs/_field_category_column.html.erb b/app/views/jobs/_field_category_column.html.erb deleted file mode 100644 index ef07ddc..0000000 --- a/app/views/jobs/_field_category_column.html.erb +++ /dev/null @@ -1 +0,0 @@ -cat diff --git a/app/views/jobs/_field_drop_down_select.html.erb b/app/views/jobs/_field_drop_down_select.html.erb deleted file mode 100644 index a7f8d9e..0000000 --- a/app/views/jobs/_field_drop_down_select.html.erb +++ /dev/null @@ -1 +0,0 @@ -bla diff --git a/app/views/jobs/_field_file.html.erb b/app/views/jobs/_field_file.html.erb index 91af0f5..a77fc6a 100644 --- a/app/views/jobs/_field_file.html.erb +++ b/app/views/jobs/_field_file.html.erb @@ -1,57 +1,61 @@ <%= javascript_tag do %> $(document).ready(function() { -//var default_values = <%= raw @default.to_json %>; - function change_<%= field['id'] %>(where){ -//alert("WHERE: " + where) + <% if field['id'] == 'primary_dataset' %> var url = (where == 'server') ? '<%= read_file_header_jobs_path() %>?file_key=<%= field['id'] %>' : null; setSelectFromFileRow("category_column", $("#p_primary_dataset")[0].files[0], '<%= @default['category_column'] || '' -%>', null, url); <% elsif field['id'] == 'map' %> var url = (where == 'server') ? '<%= read_file_header_jobs_path() %>?file_key=<%= field['id'] %>' : null; + setSelectFromFileRow("prim_batch_effect_suppression", $("#p_map")[0].files[0], '<%= @default['prim_batch_effect_suppression'] || '' -%>', null, url); + setSelectFromFileRow("sec_batch_effect_suppression", $("#p_map")[0].files[0], '<%= @default['sec_batch_effect_suppression'] || '' -%>', null, url); setSelectFromFileRow("basic_model", $("#p_map")[0].files[0], '<%= @default['basic_model'] || '' -%>', null, url); - setSelectFromFileRow("sample_name", $("#p_map")[0].files[0], '<%= @default['sample_name'] || '' -%>', null, url); - var basic_model_help = $("#field-basic_model_help") - - basic_model_help.addClass("hidden") <% end %> } $("#p_<%= field['id'] %>").on('change',function(){ var filename = $(this).val().replace(/.*?fakepath\\/, ''); $(this).next('.form-control-file').addClass("selected").html(filename); // the file on the server is obsolete: $("#p2_<%= field['id'] %>").val("") // reset the default value of bin_levels $("#default_bin_levels").val("[]"); // refresh the fields depending on the file field change_<%= field['id'] %>("client"); -//alert("t"); }) -<%# if @default and @default[field['id']] %> - var ori_filename = $("#p2_<%= field['id'] %>").val(); if (ori_filename != ''){ - $("#p_<%= field['id'] %>").next('.form-control-file').addClass("selected").html(ori_filename); //'<%# @default[field['id']].original_filename %>'); + $("#p_<%= field['id'] %>").next('.form-control-file').addClass("selected").html(ori_filename); change_<%= field['id'] %>("server"); <% if field['id'] == 'map' %> - $("#field-basic_model_help").addClass("hidden"); + $('#p_prim_batch_effect_suppression_fun-container').removeClass("hidden"); + $('#p_prim_batch_effect_suppression_fun-placeholder').addClass("hidden"); + $('#p_sec_batch_effect_suppression_fun-container').removeClass("hidden"); + $('#p_sec_batch_effect_suppression_fun-placeholder').addClass("hidden"); + $('#p_basic_model-container').removeClass("hidden"); + $('#p_basic_model-placeholder').addClass("hidden"); + <% end %> + <% if field['id'] == 'primary_dataset' %> + $('#p_category_column-container').removeClass("hidden"); + $('#p_category_column-placeholder').addClass("hidden"); + $('#p_bin_levels-container').removeClass("hidden"); + $('#p_bin_levels-placeholder').addClass("hidden"); <% end %> } -<%# end %> + }); <% end %> diff --git a/app/views/jobs/_field_model_type.html.erb b/app/views/jobs/_field_model_type.html.erb index 37160cd..7ed3a0c 100644 --- a/app/views/jobs/_field_model_type.html.erb +++ b/app/views/jobs/_field_model_type.html.erb @@ -1,65 +1,50 @@ <% val = @default[field['id']] || field['default'] %> -<%# val %>
    <%= field['label'] %>
    -
    Select a map file
    <%= render :partial => 'card_form', :locals => {:list_fields => @h_field_groups['model_type_basic']} %>
    <%= javascript_tag do %> -/* - -$("a.nav-link").click(function(){ -var radio = $(this).children().first().children().first(); -radio.prop('checked', true); -$(this).addClass("active"); -alert("active: " + this.id) -var inactive = (this.id == 'p_model_type_basic') ? 'p_model_type_advanced' : 'model_type_basic'; -$("#" + inactive).parent().parent().removeClass("active"); -alert("inactive : " + inactive) -}); - -*/ function set_tab(e){ if (e == 'advanced'){ $("#model-basic").addClass("hidden"); $("#model-advanced").removeClass("hidden"); }else{ $("#model-basic").removeClass("hidden"); $("#model-advanced").addClass("hidden"); } } $(".model_radio_button").change(function(){ set_tab($(this).val()); }); set_tab($(".model_radio_button:checked").val()); <% end %> diff --git a/app/views/jobs/_form.html.erb b/app/views/jobs/_form.html.erb index 04b442b..77f2574 100644 --- a/app/views/jobs/_form.html.erb +++ b/app/views/jobs/_form.html.erb @@ -1,98 +1,92 @@ <% @h_tips['form'].each_with_index do |tip, index| %> <% end %> <%= form_for(@job, :url => ((action_name == 'edit') ? job_path(@job.key) : jobs_path), :html => {:multipart => true}) do |f| %> - <%# @default['primary_dataset']['original_filename'] %> - <%# @default.to_json %> <% ['primary_dataset', 'map', 'secondary_dataset'].each do |k| %> <%= hidden_field_tag "p2[" + k + "]", (params[:p2] && params[:p2][k]) || '' %> <% end %> <%= hidden_field_tag "default_bin_levels", @default['bin_levels'].to_json %> <%= hidden_field_tag "url_read_file_column", read_file_column_jobs_path() %> <%= hidden_field_tag 'tmp_key', @job.key %>
    <% @h_form['fields'].each_key do |card_title| %>

    <%= card_title %> <% if card_title == 'Inputs' %> <% l = ['', ''] %> - <%# select_tag 'input_test', {:class => 'form-inline'} %> <% end %>

    <%= render :partial => 'card_form', :locals => {:list_fields => @h_form['fields'][card_title].reject{|e| e['belongs_to']}} %> - <%# @h_form['fields'][card_title].reject{|e| e['belongs_to']}.to_json %>
    <% end %>
    <% type_job = (action_name == 'edit') ? 'Restart' : 'Start' %> <% if !current_user and !session[:agree_with_terms] %> <%= f.submit type_job + " Analysis", data: { confirm: "Before using Genocrunch, you must agree with the following Terms of Service:\n\n"+File.read('public/app/TERMS_OF_SERVICE.txt') }, :class => "btn btn-success btn-lg col-md-12", :style=>'margin-top:10px' %> <% else %> <%= f.submit type_job + " Analysis", :class => "btn btn-success btn-lg col-md-12", :style=>'margin-top:10px' %> <% end %>
    <%= javascript_tag do %> $(document).ready(function() { - //var default_values = <%= raw @default.to_json %>; - - var l = ['category_column', 'sample_name', 'bin_levels', 'basic_model']; + var l = ['category_column', 'bin_levels', 'basic_model']; for (var i =0; i< l.length; i++){ $("#p_" + l[i]).change(function(){ - if ($("#p_" + l[i] + " option").length == 0) - $("#field-" + l[i]).addClass("hidden"); + if ($("#p_" + l[i] + " option").length == 0) + $("#p_" + l[i] + "-container").addClass("hidden"); }); } $("#p_category_column").change(function(){ var ori_filename = $("#p2_primary_dataset").val(); var url = (ori_filename != '') ? "<%= read_file_column_jobs_path() + '?file_key=primary_dataset' %>" : null; var val = <%= raw (@default["bin_levels"]) ? @default["bin_levels"].to_json : "[]" %> update_bin_levels($(this).val(), val, url) }); $("select.belongs_to").change(function(){ var d = $(this).parent().parent().parent().parent().parent().parent().children().filter('.card-header').first().children().filter('.form-check').first().children().filter('.form-check-label').first().children().filter('.form-check-input').first(); d.prop("checked", "checked"); }); $(".belongs_to").keyup(function(){ var d = $(this).parent().parent().parent().parent().children().filter('.card-header').first().children().filter('.form-check').first().children().filter('.form-check-label').first().children().filter('.form-check-input').first(); d.prop("checked", "checked"); }); }); <% end %> <% end %> diff --git a/app/views/jobs/_jobs.html.erb b/app/views/jobs/_jobs.html.erb index 03eb895..83ecb15 100644 --- a/app/views/jobs/_jobs.html.erb +++ b/app/views/jobs/_jobs.html.erb @@ -1,145 +1,135 @@

    Analyses

    <% if current_user.role == "admin" %> <% end %> <% @jobs.compact.each do |job| %> <% date = job.updated_at.year.to_s[-2..-1].to_s + "-" + job.updated_at.month.to_s.rjust(2, '0') + "-" + job.updated_at.day.to_s.rjust(2, '0') + " " + job.updated_at.hour.to_s.rjust(2, '0') + ":" + job.updated_at.min.to_s.rjust(2, '0') + " " + job.updated_at.zone.to_s %> <% if current_user.role == "admin" %> <% end %> <% destroy_text = (['pending', 'running'].include? job.status) ? 'abort' : 'delete' %> <% end %>
    Key Name DateUser TypeStatus Data Map Results
    <%= link_to job.key, job_path(job.key) %>
    <%= link_to job.name, job_path(job.key) %>
    <%= link_to date, job_path(job.key) %> <%= (job.user) ? job.user.username : 'NA' %> <%= (Example.where(:job_key => job.key).all.size > 0) ? 'Example' : 'Std' %> <% if job.status %> <% end %> <%= link_to raw(''), serve_job_path(job.key, :filename => 'input/primary_dataset.txt'), title: "primary dataset" %> <% if File.exist? Pathname.new(APP_CONFIG[:data_dir]) + 'users' + job.user_id.to_s + job.key + 'input' + 'secondary_dataset.txt' %> | <%= link_to raw(''), serve_job_path(job.key, :filename => 'input/secondary_dataset.txt'), title: "secondary dataset" %> <% end %> <%= link_to raw(''), serve_job_path(job.key, :filename => 'input/map.txt') %> <%= link_to raw(''), job_path(job.key), :title => 'show' %> <% if (File.exist? Pathname.new(APP_CONFIG[:data_dir]) + 'users' + job.user_id.to_s + job.key + (job.key + '.tar.gz').to_s) and (!['pending', 'running'].include? job.status) %> | <%= link_to raw(''), serve_job_path(job.key, :filename => job.key + '.tar.gz'), :title => 'download archive' %> <% end %> <%= link_to raw(''), clone_job_path(job.key) %> | <%= link_to raw(''), edit_job_path(job.key) %> | <% destroy_text = (['pending', 'running'].include? job.status) ? 'abort' : 'delete' %> <%= link_to(job_path(job.key), method: :delete, data: { confirm: 'Are you sure you want to ' + destroy_text + ' "' + (job.name || 'NA') + '" ?'}) do %> <% end %>
    -
    -

    Status summary

    - - - - - - - - - - - - - - - - - - - -
    completed ()pending ()running ()failed ()total
    <%= @jobs.select{ |i| i.status == "completed" }.length %><%= @jobs.select{ |i| i.status == "pending" }.length %><%= @jobs.select{ |i| i.status == "running" }.length %><%= @jobs.select{ |i| i.status == "failed" }.length %><%= @jobs.length %>
    + +
    +
    +

    Status summary

    +
    <%= @jobs.select{ |i| i.status == "completed" }.length %> completed ()
    +
    <%= @jobs.select{ |i| i.status == "pending" }.length %> pending ()
    +
    <%= @jobs.select{ |i| i.status == "running" }.length %> running ()
    +
    <%= @jobs.select{ |i| i.status == "failed" }.length %> failed ()
    +
    Total: <%= @jobs.length %>
    +
    +
    +
    <%= javascript_tag do %> $(document).ready(function(){ var table = $('#myTable').DataTable({ sDom: 'ltp', "aLengthMenu": [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]], "iDisplayLength" : 10, "order": [[ 2, 'desc' ], [ 1, 'asc' ]] }); $('#filter-input').insertAfter('#myTable_length') }); function filterTable() { var input = document.getElementById("filter-input"), filter = input.value.toUpperCase(), table = document.getElementById("myTable"), tr = table.getElementsByTagName("tr"), td, kept; for (var i = 1; i < tr.length; i++) { td = tr[i].getElementsByTagName("td"); kept = false; for (var j = 0; j < td.length; j++) { if (td[j].innerHTML.toUpperCase().indexOf(filter) > -1) { tr[i].style.display = ""; kept = true; break; } } if (kept == false) { tr[i].style.display = "none"; } } } <% end %> diff --git a/app/views/jobs/_list_messages.html.erb b/app/views/jobs/_list_messages.html.erb index 313acbd..2c72d22 100644 --- a/app/views/jobs/_list_messages.html.erb +++ b/app/views/jobs/_list_messages.html.erb @@ -1,52 +1,50 @@ <% if @final_json[:status_by_step][k] %> -<% @final_json[:status_by_step][k].each do |e| %> -
    +<% @final_json[:status_by_step][k].select{|e| e[:status] != 'skipped'}.each do |e| %> + +
    <%= raw display_duration(e[:execution_time]) %>
    - <%# image_tag @h_statuses[e[:status] || 'pending'].filename, :alt => '', :id => "status-#{e[:id]}", :class => 'status_image' %> <%= e[:name].capitalize %>
    <% step_key = "primary_dataset_" + e[:name] %> <% if @final_json[:messages_by_step][step_key] and @final_json[:messages_by_step][step_key].select{|e| e[:messages] and e[:messages].select{|h| h.keys.size > 0}.compact.size > 0}.size > 0 %>
    -<%# @final_json[:messages_by_step][step_key].to_json %>
      <% @final_json[:messages_by_step][step_key].select{|e| e[:messages] and e[:messages].select{|h| h.keys.size > 0}.compact.size > 0}.each do |e2| %>
    • <% status_substep = (@final_json[:status_by_substep][e[:name]]) ? @final_json[:status_by_substep][e[:name]].select{|e3| e3[:name] == e2[:name]}.first : nil %>
      <%= raw display_duration(status_substep[:execution_time]) if status_substep %>
      <%if status_substep %> <% end %> - <%# image_tag(@h_statuses[status_substep[:status] || 'pending'].filename, :alt => '', :id => "status-#{e[:id]}", :class => 'status_image') if status_substep %> -<%= e2[:name].capitalize %> +<%= e2[:name].capitalize if status_substep %> <% e2[:messages].each do |e3| %> <% k = e3.keys.first %>

      <% if k != 'output' %> <%= e3[k] %> <% else %> <% t = e3[k].gsub(/.+?\/#{@job.key}\//, '') %> <%= link_to 'Download file', serve_job_path(@job.key, :filename => t), :class => 'btn btn-secondary btn-sm' %> <% end %>

      <% end %>
    • <% end %>
    <% end %>
    <% end %> <% end %> diff --git a/app/views/jobs/_show_map.html.erb b/app/views/jobs/_show_map.html.erb deleted file mode 100644 index 9fef816..0000000 --- a/app/views/jobs/_show_map.html.erb +++ /dev/null @@ -1 +0,0 @@ -Map diff --git a/app/views/jobs/_show_primary_dataset.html.erb b/app/views/jobs/_show_primary_dataset.html.erb deleted file mode 100644 index 24c6916..0000000 --- a/app/views/jobs/_show_primary_dataset.html.erb +++ /dev/null @@ -1,8 +0,0 @@ -

    Primary dataset

    -ldksjflskdjf -<%= @final_json[:status_by_step].to_json %> -<% @final_json[:status_by_step]['primary_dataset'].each do |e| %> -
    -<%= e['name'] %> <%= image_tag @h_status[e['status']].filename %> -
    -<% end %> diff --git a/app/views/jobs/_standard_fig_layout.html.erb b/app/views/jobs/_standard_fig_layout.html.erb index e7ba508..9d0b0c5 100644 --- a/app/views/jobs/_standard_fig_layout.html.erb +++ b/app/views/jobs/_standard_fig_layout.html.erb @@ -1,69 +1,94 @@
    <% if @form_json['bin_levels'] %>
    <% l = @form_json['bin_levels'].map{|e| [e, e]} %> <%= select_tag 'current_level', options_for_select(l, session[:current_level].to_s), {:class => 'form-control multiselect figtool-multiselect'} %>
    <% end %> <%= render :partial => 'export_btn' %>
    + + <% if @description and !@description.empty? %> +
    + + + + +
    + <% end %> +
    <%= javascript_tag do %> + $('#fig-description-btn').click(function(e) { + $('#fig-description-container').toggleClass('active'); + $('#fig-description').toggleClass('hidden'); + }); + $("#current_level").change(function(){ var div = $('#show_content'); var url = '<%= view_job_path(:key => @job.key) %>?partial=<%= params[:partial] %>¤t_level=' + $(this).val(); $.ajax({ url: url, type: "get", beforeSend: function(){ }, success: function(returnData){ div.empty(); div.html(returnData); }, error: function(e){ } }); }); setMultiselect('#current_level'); <% end %> diff --git a/app/views/jobs/_summary.html.erb b/app/views/jobs/_summary.html.erb deleted file mode 100644 index d7235f3..0000000 --- a/app/views/jobs/_summary.html.erb +++ /dev/null @@ -1 +0,0 @@ -Summary diff --git a/app/views/jobs/_view_adonis.html.erb b/app/views/jobs/_view_adonis.html.erb index e742d04..9c2e3e4 100644 --- a/app/views/jobs/_view_adonis.html.erb +++ b/app/views/jobs/_view_adonis.html.erb @@ -1,10 +1,9 @@ -<%= render :partial => 'view_description' %> <%= render :partial => 'standard_fig_layout' %> <%= javascript_tag do %> var fig_data = <%= raw @data_json %>; adonisPieChart('fig', 'fig-legend', fig_data, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif"); <% end %> diff --git a/app/views/jobs/_view_change.html.erb b/app/views/jobs/_view_change.html.erb index 5571983..d4203fb 100644 --- a/app/views/jobs/_view_change.html.erb +++ b/app/views/jobs/_view_change.html.erb @@ -1,10 +1,9 @@ -<%= render :partial => 'view_description' %> <%= render :partial => 'standard_fig_layout' %> <%= javascript_tag do %> var fig_data = <%= raw @data_json %>; foldChange('fig', 'fig-legend', fig_data, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif", color_palette = d3.schemeCategory10); <% end %> diff --git a/app/views/jobs/_view_clustering.html.erb b/app/views/jobs/_view_clustering.html.erb index 4d93913..65c1f80 100644 --- a/app/views/jobs/_view_clustering.html.erb +++ b/app/views/jobs/_view_clustering.html.erb @@ -1 +1,66 @@ -<%= render :partial => 'view_description' %> +
    +
    +
    + + <% if @form_json['bin_levels'] %> +
    + + + +
    + <% l = @form_json['bin_levels'].map{|e| [e, e]} %> + <%= select_tag 'current_level', options_for_select(l, session[:current_level].to_s), {:class => 'form-control multiselect figtool-multiselect'} %> +
    + +
    + + <% end %> + + <% if @description and !@description.empty? %> +
    + + + + + <% if @description.length() > 1 %> +
      + <% @description.each do |d| %> +
    • <%= d %>
    • + <% end %> +
    + <% else %> + <%= @description.first %> + <% end %> +
    +
    + <% end %> + +
    +
    + +<%= javascript_tag do %> + +$("#current_level").change(function(){ +var div = $('#show_content'); +var url = '<%= view_job_path(:key => @job.key) %>?partial=<%= params[:partial] %>¤t_level=' + $(this).val(); + + $.ajax({ + url: url, + type: "get", + beforeSend: function(){ + }, + success: function(returnData){ + div.empty(); + div.html(returnData); + }, + error: function(e){ + } + }); + +}); + +setMultiselect('#current_level'); + +<% end %> diff --git a/app/views/jobs/_view_correlation_network.html.erb b/app/views/jobs/_view_correlation_network.html.erb index 206c52c..5d854f5 100644 --- a/app/views/jobs/_view_correlation_network.html.erb +++ b/app/views/jobs/_view_correlation_network.html.erb @@ -1,10 +1,9 @@ -<%= render :partial => 'view_description' %> <%= render :partial => 'standard_fig_layout' %> <%= javascript_tag do %> var fig_data = <%= raw @data_json %>; correlationNetwork('fig', 'fig-legend', fig_data, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif", color_palette = d3.schemeCategory10); <% end %> diff --git a/app/views/jobs/_view_description.html.erb b/app/views/jobs/_view_description.html.erb deleted file mode 100644 index 96ac535..0000000 --- a/app/views/jobs/_view_description.html.erb +++ /dev/null @@ -1,16 +0,0 @@ -<% if @description and !@description.empty? %> -
    -
    - - <% if @description.length() > 1 %> -
      - <% @description.each do |d| %> -
    • <%= d %>
    • - <% end %> -
    - <% else %> - <%= @description.first %> - <% end %> -
    -
    -<% end %> diff --git a/app/views/jobs/_view_diversity.html.erb b/app/views/jobs/_view_diversity.html.erb index 761c91f..188b857 100644 --- a/app/views/jobs/_view_diversity.html.erb +++ b/app/views/jobs/_view_diversity.html.erb @@ -1,12 +1,11 @@ -<%= render :partial => 'view_description' %> <%= render :partial => 'standard_fig_layout' %> <%= javascript_tag do %> var fig_data = <%= raw @data_json %>, RfunctionsDiversity = <%= @h_form_choices['diversity'].to_json.html_safe %>; diversity('fig', 'fig-legend', fig_data, RfunctionsDiversity, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif", color_palette = d3.schemeCategory10); <% end %> diff --git a/app/views/jobs/_view_heatmap.html.erb b/app/views/jobs/_view_heatmap.html.erb index d08754e..ff37118 100644 --- a/app/views/jobs/_view_heatmap.html.erb +++ b/app/views/jobs/_view_heatmap.html.erb @@ -1,11 +1,10 @@ -<%= render :partial => 'view_description' %> <%= render :partial => 'standard_fig_layout' %> <%= javascript_tag do %> var fig_data = <%= raw @data_json %>; heatMap('fig', 'fig-legend', fig_data, W = 750, H = 750, font_family = "verdana, arial, helvetica, sans-serif"); <% end %> diff --git a/app/views/jobs/_view_map.html.erb b/app/views/jobs/_view_map.html.erb index 8b1b9a1..581ee9d 100644 --- a/app/views/jobs/_view_map.html.erb +++ b/app/views/jobs/_view_map.html.erb @@ -1,6 +1,5 @@ <% if @final_json and @final_json[:status_by_step]['map'] %> -<%= render :partial => 'view_description' %> <%= render :partial => 'list_messages', :locals => {:k => 'map'} %> <% end %> diff --git a/app/views/jobs/_view_pca.html.erb b/app/views/jobs/_view_pca.html.erb index 7008978..8c8fc2b 100644 --- a/app/views/jobs/_view_pca.html.erb +++ b/app/views/jobs/_view_pca.html.erb @@ -1,10 +1,9 @@ -<%= render :partial => 'view_description' %> <%= render :partial => 'standard_fig_layout' %> <%= javascript_tag do %> var fig_data = <%= raw @data_json %>; pca('fig', 'fig-legend', fig_data, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif", color_palette = d3.schemeCategory10); <% end %> diff --git a/app/views/jobs/_view_pcoa.html.erb b/app/views/jobs/_view_pcoa.html.erb index 403ba0d..f8e626a 100644 --- a/app/views/jobs/_view_pcoa.html.erb +++ b/app/views/jobs/_view_pcoa.html.erb @@ -1,10 +1,9 @@ -<%= render :partial => 'view_description' %> <%= render :partial => 'standard_fig_layout' %> <%= javascript_tag do %> var fig_data = <%= raw @data_json %>; pcoa('fig', 'fig-legend', fig_data, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif", color_palette = d3.schemeCategory10); <% end %> diff --git a/app/views/jobs/_view_primary_dataset.html.erb b/app/views/jobs/_view_primary_dataset.html.erb index aacc091..e9bb54f 100644 --- a/app/views/jobs/_view_primary_dataset.html.erb +++ b/app/views/jobs/_view_primary_dataset.html.erb @@ -1,6 +1,5 @@ <% if @final_json and @final_json[:status_by_step]['primary_dataset'] %> -<%= render :partial => 'view_description' %> <%= render :partial => 'list_messages', :locals => {:k => 'primary_dataset'} %> <% end %> diff --git a/app/views/jobs/_view_proportions.html.erb b/app/views/jobs/_view_proportions.html.erb index 80f8b91..5073bfe 100644 --- a/app/views/jobs/_view_proportions.html.erb +++ b/app/views/jobs/_view_proportions.html.erb @@ -1,11 +1,10 @@ -<%= render :partial => 'view_description' %> <%= render :partial => 'standard_fig_layout' %> <%= javascript_tag do %> var fig_data = <%= raw @data_json %>; barchart('fig', 'fig-legend', fig_data, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif", color_palette = d3.schemeCategory10); <% end %> diff --git a/app/views/jobs/_view_similarity_network.html.erb b/app/views/jobs/_view_similarity_network.html.erb index 1753c5f..4035c46 100644 --- a/app/views/jobs/_view_similarity_network.html.erb +++ b/app/views/jobs/_view_similarity_network.html.erb @@ -1,10 +1,9 @@ -<%= render :partial => 'view_description' %> <%= render :partial => 'standard_fig_layout' %> <%= javascript_tag do %> var fig_data = <%= raw @data_json %>; similarityNetwork('fig', 'fig-legend', fig_data, W = 600, H = 600, font_family = "verdana, arial, helvetica, sans-serif", color_palette = d3.schemeCategory10); <% end %> diff --git a/app/views/jobs/show.html.erb b/app/views/jobs/show.html.erb index 6417098..1dc3ffa 100644 --- a/app/views/jobs/show.html.erb +++ b/app/views/jobs/show.html.erb @@ -1,177 +1,176 @@

    <%= @job.name %> <%= link_to 'Edit', edit_job_path(:key => @job.key), :class => 'btn btn-primary float-right' %> <%= link_to serve_job_path(@job.key, :filename => @job.key + '.tar.gz'), :id => 'archive-btn', :title => 'Download archive', :class => 'btn btn-secondary float-right mr-1 hidden' do %> <% end %>

    -
    - +
    <%= render :partial => 'view_primary_dataset' %>
    <% @h_tips['show'].each_with_index do |tip, index| %> <% end %>
    <%= javascript_tag do %> $(document).ready(function() { $('#job-description-btn').click(function(e) { $('#job-description').toggleClass('hidden'); }); var window_width = $(window).width(); <% @h_tips['show'].each_with_index do |tip, index| %> <% if !current_user and !session["tip#{tip['id']}".to_sym] %> $("#tip_window<%= index %>").removeClass('hidden'); $("#tip_window<%= index %>").css({<%= raw tip['css_string'] %>}).stop().show(100); $('#tip_window<%= index %>_text').html("<%= raw tip['html'] %>") <% session["tip#{tip['id']}".to_sym] = true %> <% end %> <% end %> }); $(".menu").click(function(){ var name = this.id.split("-")[1]; $("#selected_view").val(name); $(".menu").removeClass('selected'); $(this).addClass('selected'); }); var timer = setInterval(function(){ refresh_data() }, 5000); function refresh_data(){ var url = '<%= refresh_job_path(:key => @job.key) %>'; $.ajax({ url: url, type: "get", dataType: "script", beforeSend: function(){ }, success: function(returnData){ // div.empty(); // div.html(returnData); }, error: function(e){ } }); } $(".menu").click(function(){ var t = this.id.split("-"); var div = $("#show_content"); var url = '<%= view_job_path(:key => @job.key) %>?partial=' + t[1]; $.ajax({ url: url, type: "get", beforeSend: function(){ }, success: function(returnData){ div.empty(); div.html(returnData); }, error: function(e){ } }); }); refresh_data(); <% end %> diff --git a/app/views/jobs/update.html.erb b/app/views/jobs/update.html.erb deleted file mode 100644 index a7f8d9e..0000000 --- a/app/views/jobs/update.html.erb +++ /dev/null @@ -1 +0,0 @@ -bla diff --git a/app/views/users/sessions/new.html.erb b/app/views/users/sessions/new.html.erb index a3e35a0..6421aed 100644 --- a/app/views/users/sessions/new.html.erb +++ b/app/views/users/sessions/new.html.erb @@ -1,92 +1,91 @@
    - + <%# WELCOME TEXT %>

    Analyse your data online

    We offer a user-friendly data mining pipeline for metagenomics and metataxonomics

    -
    <%# SIGNIN %> <%# GO DOWN %>
    <%# INFOS %> <%= render :partial => "infos" %>
    diff --git a/db/seeds.rb.keep b/db/seeds.rb.keep index 7f64986..4e3dbdf 100755 --- a/db/seeds.rb.keep +++ b/db/seeds.rb.keep @@ -1,101 +1,101 @@ User.create!([{username: 'guest', role: 'guest', email: 'guest@guestmailbox.com', # <- HERE confirmed_at: '2017-01-01 00:00:00.000000', password: 'guest_account_password'}, # <- HERE {username: 'admin', role: 'admin', email: 'admin@adminmailbox.com', # <- HERE confirmed_at: '2017-01-01 00:00:00.000000', password: 'admin_account_password'}]) # <- AND THERE Status.create!([{name: 'completed', icon: 'fa fa-check icon-success', precedence: '1'}, {name: 'pending', icon: 'fa fa-ellipsis-h icon-inactive', precedence: '2'}, {name: 'running', icon: 'fa fa-circle-o-notch fa-pulse', precedence: '3'}, {name: 'failed', icon: 'fa fa-exclamation-triangle icon-warning', precedence: '4'}, {name: 'skipped', icon: 'fa fa-check icon-success', - precedence: '5'}]) + precedence: '0'}]) View.create!([{name: 'map', category: 'input_data_preparation', icon: 'fa fa-file-text-o', position: 1, graphical: false}, {name: 'primary_dataset', category: 'input_data_preparation', icon: 'fa fa-file-text-o', position: 2, graphical: false}, {name: 'secondary_dataset', category: 'input_data_preparation', icon: 'fa fa-file-text-o', position: 3, graphical: false}, {name: 'proportions', category: 'analysis', icon: 'fa fa-bar-chart', position: 1, graphical: true, data_format: 'json'}, {name: 'diversity', category: 'analysis', icon: 'fa fa-line-chart', position: 2, graphical: true, data_format: 'json'}, {name: 'adonis', category: 'analysis', icon: 'fa fa-pie-chart', position: 3, graphical: true, data_format: 'json'}, {name: 'pca', category: 'analysis', icon: 'fa fa-bar-chart', position: 4, graphical: true, data_format: 'json'}, {name: 'pcoa', category: 'analysis', icon: 'fa fa-bar-chart', position: 5, graphical: true, data_format: 'json'}, {name: 'heatmap', category: 'analysis', icon: 'fa fa-th', position: 6, graphical: true, data_format: 'json'}, {name: 'change', category: 'analysis', icon: 'fa fa-bar-chart', position: 7, graphical: true, data_format: 'json'}, {name: 'correlation_network', category: 'analysis', icon: 'fa fa-share-alt', position: 8, graphical: true, data_format: 'json'}, {name: 'similarity_network', category: 'analysis', icon: 'fa fa-share-alt', position: 9, graphical: true, data_format: 'json'}, {name: 'clustering', category: 'analysis', icon: 'fa fa-align-justify', position: 10, graphical: false}]) diff --git a/lib/genocrunch_console/bin/analyse_table.R b/lib/genocrunch_console/bin/analyse_table.R index 146f3fc..3f79809 100755 --- a/lib/genocrunch_console/bin/analyse_table.R +++ b/lib/genocrunch_console/bin/analyse_table.R @@ -1,326 +1,324 @@ #!/usr/bin/env Rscript #analyse_table.R # Generic script to analyse a table # Set environment libname <- 'genocrunchlib' args <- commandArgs(trailingOnly=FALSE) dir <- gsub('--file=', '', dirname(args[4])) file <- gsub('--file=', '', args[4]) lib <- normalizePath(path=paste(dir, '/../lib/', libname, '.R', sep=''), mustWork=TRUE) holynetlib <- normalizePath(path=paste(dir,'/../lib/holynetlib.R', sep=''), mustWork=FALSE) friedmanlib <- normalizePath(path=paste(dir,'/../lib/friedman_test_with_post_hoc.R', sep=''), mustWork=FALSE) source(lib) source(friedmanlib) suppressMessages(library('optparse')) suppressMessages(library('rjson')) options(scipen=999) set.seed(2) # Set whether output can be graphical or not graphical <- list(clustering= TRUE, proportions= TRUE, diversity= TRUE, adonis= TRUE, pca= TRUE, ca= TRUE, pcoa= TRUE, cca= TRUE, change= TRUE, heatmap= TRUE, correlation_network=FALSE, similarity_network= FALSE) # Set options. Note: there is a small bug with '-g', do not use it option_list <- list(make_option(c('-t', '--table'), # General options type='character', default=NULL, help='Path to data table'), make_option(c('-o', '--output'), type='character', default=paste(file, '_out', sep=''), help='Path to output file'), make_option(c('-v', '--verbose'), type='character', default='TRUE', help='Print a description?'), make_option('--graphical', type='logical', default=TRUE, help='Generate graphics?'), make_option('--json', type='character', default=ReadJson(file=normalizePath(paste(dir, '/../etc/', libname, '.json', sep=''), mustWork=TRUE), rel=FALSE), help='A json string of characters with function-specific information'), make_option(c('-m', '--method'), type='character', default=NULL, help=paste('Analysis method. Valid choices: ', paste(names(graphical), collapse=', '), sep='')), make_option('--category', type='character', default='taxonomy', help='Name of the category column in table'), make_option('--width', type='numeric', default=5, help='Figure width in cm'), make_option('--height', type='numeric', default=5, help='Figure height in cm'), make_option('--fun', type='character', default=NULL, help='Function to use (generic)'), make_option('--map', type='character', default=NULL, help='Path to mapping file'), make_option('--stats', type='character', default='anova', help='Statistycal test to use.'), make_option('--model', type='character', default=NULL, help='An R formula representing the model for stats. Terms must refer to names(map). Multiple comma-separated formulae are accepted.'), make_option('--adonis_model', type='character', default=NULL, help='An R formula representing the model for adonis. Terms must refer to names(map). Multiple comma-separated formulae are accepted.'), make_option('--strata', type='character', default=NULL, help='Strata parameter for adonis. IF multiple models are specified, then multiple comma-separated strata are accepted.'), make_option('--metadata', type='character', default=NULL, help='Path to metadata table'), make_option('--nrar', type='numeric', default=50, help='Number of rarefaction to perform'), make_option('--clust', type='character', default="walktrap", help='Graph-based clustering algorithm'), make_option('--column', type='character', default=NULL, help='Name of a column'), make_option('--compare_diversity', type='logical', default=FALSE, help='Compare diversity between groups?'), make_option('--log', type='character', default=NULL, help='Path to log file')) opt_parser <- OptionParser(option_list=option_list) opt <- parse_args(opt_parser) log_fp <- opt$log # Convert the library parameters from json string to list json <- fromJSON(json_str=opt$json) # Set general inputs table <- read.table(file=opt$table, sep='\t', header=1, row.names=1) if (opt$category %in% names(table)) { if (length(unique(unlist(table[, opt$category]))) == nrow(table)) { row.names(table) <- table[, opt$category] } else { row.names(table) <- paste(table[, opt$category], '(', row.names(table) ,')', sep='') } table <- table[, names(table) != opt$category] } if (!is.null(opt$map)) { map <- read.table(file=opt$map, sep='\t', header=1, row.names=1) } else { map <- NULL } if (!is.null(opt$stats) && opt$stats != '') { stats <- strsplit(opt$stats, ',')[[1]] } else { stats <- NULL } if (!is.null(opt$model) && opt$model != '') { model <- strsplit(opt$model, ',')[[1]] } else { model <- NULL } if (!is.null(opt$fun)) { fun <- unlist(strsplit(opt$fun, ',')) } else { fun <- NULL } if (!is.null(opt$metadata)) { metadata <- read.table(file=opt$metadata, sep='\t', header=1, row.names=1) } else { metadata <- NULL } # Set graphical output if (graphical[[opt$method]] == TRUE && opt$graphical == TRUE) { fig.fp <- paste(opt$output, '.pdf', sep='') pdf(file=fig.fp, width=opt$width, height=opt$height) } # Call the analysis function ################### # Analysis function output should be one of the following element or a list # containing one or more of the following elements: # 'txt' A matrix or data-frame to be written in a tab-delimited file # 'json' A json string to be written in a json file # 'csv' A matrix or data-frame to be written in a comma-delimited file ################### data <- list() if (opt$method == 'clustering') { - data[['txt']] <- PerformClustering(table=table, fun=fun, json=json, verbose=opt$verbose, graphical=opt$graphical) if (!is.null(data[['txt']])) { names(data[['txt']]) <- basename(opt$output) } } else if (opt$method == 'proportions') { - data[['json']] <- AnalyseProportions(table=table, verbose=opt$verbose, graphical=opt$graphical) } else if (opt$method == 'diversity') { data[['json']] <- AnalyseDiversity(table=table, map=map, fun=fun, nrar=opt$nrar, compare_diversity=opt$compare_diversity, stats=stats, model=model, json=json, verbose=opt$verbose, graphical=opt$graphical) } else if (opt$method == 'adonis') { if (!is.null(opt$adonis_model) && opt$adonis_model != '') { adonis_model <- unlist(strsplit(opt$adonis_model, ',')) } else { adonis_model <- model } if (!is.null(opt$strata) && opt$strata != '') { strata <- unlist(strsplit(opt$strata, ',')) } else { strata <- NULL } data[['json']] <- PerformAdonis(table=table, map=map, fun=fun, model=adonis_model, strata=strata, json=json, verbose=opt$verbose, graphical=opt$graphical) } else if (opt$method == 'pca') { data[['json']] <- PerformPCA(table=table, map=map, verbose=opt$verbose, graphical=opt$graphical) } else if (opt$method == 'ca') { data[['json']] <- PerformCA(table=table, map=map, verbose=opt$verbose, graphical=opt$graphical) } else if (opt$method == 'pcoa') { data[['json']] <- PerformPCoA(table=table, map=map, fun=fun, json=json, verbose=opt$verbose, graphical=opt$graphical) } else if (opt$method == 'cca') { data[['json']] <- PerformCCA(table=table, map=map, column=opt$column, verbose=opt$verbose, graphical=opt$graphical) } else if (opt$method == 'change') { data[['json']] <- AnalyseChange(table=table, map=map, stats=stats, model=model, json=json, verbose=opt$verbose, graphical=opt$graphical) } else if (opt$method == 'heatmap') { data[['json']] <- BuildHeatMap(table=table, map=map, stats=stats, model=model, metadata=metadata, fun=fun, json=json, verbose=opt$verbose, graphical=opt$graphical) } else if (opt$method == 'correlation_network') { data[['json']] <- BuildCorrelationNetwork(table=table, map=map, stats=stats, model=model, json=json, metadata=metadata, fun=fun, verbose=opt$verbose) } else if (opt$method == 'similarity_network') { if (!is.null(metadata)) { tables <- list(table, metadata) clust.names <- c('data', 'metadata', 'fusion') } else { tables <- list(table) clust.names <- 'data' } data <- BuildSimilarityNetwork(table=tables, map=map, clust=opt$clust, clust.names=paste(basename(opt$output), clust.names, sep='_'), funs=fun, json=json, verbose=opt$verbose, lib=holynetlib) } # Write data if (graphical[[opt$method]] == TRUE && opt$graphical == TRUE) { PrintMsg(paste('"output":"', fig.fp, '"', sep=''), opt$verbose) graphics.off() } f.type <- names(data) for (i in 1:length(f.type)) { if (!is.null(data[[f.type[i]]])) { output.fp <- paste(opt$output, '.', f.type[i], sep='') PrintMsg(paste('"output":"', output.fp, '"', sep=''), opt$verbose) if (f.type[i] == 'json') { write(x=data[[f.type[i]]], file=output.fp, append=FALSE) } else if (f.type[i] == 'txt') { WriteTable(data[[f.type[i]]], output.fp, name='', sep='\t') } else if (f.type[i] == 'csv') { WriteTable(data[[f.type[i]]], output.fp, name='name', sep=',') } } } rm(list=ls()) diff --git a/lib/genocrunch_console/etc/genocrunchlib.json b/lib/genocrunch_console/etc/genocrunchlib.json index d1dc103..fee1fa1 100644 --- a/lib/genocrunch_console/etc/genocrunchlib.json +++ b/lib/genocrunch_console/etc/genocrunchlib.json @@ -1,442 +1,470 @@ { "choices":{ "statistics":[ {"value":"anova", "label":"ANOVA"}, {"value":"ttest", "label":"t-test"}, {"value":"ttest_paired", "label":"Paired t-test"}, {"value":"kruskal", "label":"Kruskal-Wallis rank sum test"}, {"value":"friedman", "label":"Friedman test"}, {"value":"wilcox", "label":"Wilcoxon rank sum test"}, {"value":"wilcox_paired", "label":"Wilcoxon signed rank test"} ], "binning":[ {"value":"sum", "label":"Sum values within categories"}, {"value":"mean", "label":"Average values within categories"} ], "transformation":[ {"value":"none", "label":"No transformation"}, {"value":"log2cpm", "label":"log2 of count per million"}, {"value":"log2", "label":"Log2"}, {"value":"percent", "label":"Percent"} ], + "batch_effect":[ + {"value":"none", "label":"None"}, + {"value":"combat", "label":"ComBat"} + ], "diversity":[ {"value":"richness", "label":"Richness", "pkg":""}, {"value":"shannon", "label":"Shannon Diversity Index", "pkg":"vegan"}, {"value":"simpson", "label":"Simpson Diversity Index", "pkg":"vegan"}, {"value":"invsimpson", "label":"inverse Simpson Diversity Index", "pkg":"vegan"}, {"value":"Gini", "label":"Gini coefficient", "pkg":"ineq"}, {"value":"chao1", "label":"chao1 estimator", "pkg":"fossil"}, {"value":"RS", "label":"Ricci-Schutz coefficient", "pkg":"ineq"}, {"value":"Atkinson", "label":"Atkinson measure", "pkg":"ineq"}, {"value":"Theil", "label":"Theil entropy measure", "pkg":"ineq"}, {"value":"Kolm", "label":"Kolm measure", "pkg":"ineq"}, {"value":"var", "label":"Coefficient of variation", "pkg":"ineq"} ], "correlation":[ {"value":"spearman", "label":"Spearman Correlation Coefficient", "pkg":""}, {"value":"pearson", "label":"Pearson Correlation Coefficient", "pkg":""} ], "distance":[ {"value":"jaccard", "label":"Jaccard Index", "pkg":"vegan"}, {"value":"bray", "label":"Bray-Curtis Distance", "pkg":"vegan"}, {"value":"euclidean", "label":"Euclidean Distance", "pkg":"vegan"}, {"value":"manhattan", "label":"Manhattan Distance", "pkg":"vegan"}, {"value":"spearman", "label":"Spearman Correlation Coefficient", "pkg":""}, {"value":"pearson", "label":"Pearson Correlation Coefficient", "pkg":""}, {"value":"canberra", "label":"Canberra Distance", "pkg":"vegan"}, {"value":"kulczynski", "label":"Kulczynski Similarity Measure", "pkg":"vegan"}, {"value":"gower", "label":"Gower Similarity Coefficient", "pkg":"vegan"}, {"value":"morisita", "label":"Morisita Overlap Index", "pkg":"vegan"}, {"value":"horn", "label":"Horn-Morisita Index", "pkg":"vegan"}, {"value":"mountford", "label":"Mountford Index", "pkg":"vegan"}, {"value":"raup", "label":"Raup-Crick dissimilarity", "pkg":"vegan"}, {"value":"binomial", "label":"Binomial distance", "pkg":"vegan"}, {"value":"chao", "label":"Chao Index", "pkg":"vegan"} ], "clustering":[ {"value":"none", "label":"", "pkg":""}, {"value":"pam", "label":"k-medoids", "pkg":"fpc"}, {"value":"pam-bray", "label":"k-medoids on Bray-Curtis dissimilarity", "pkg":"fpc"}, {"value":"pam-jaccard", "label":"k-medoids on Jaccard distance", "pkg":"fpc"}, {"value":"kmeans", "label":"k-means", "pkg":"fpc"} ], "graph_clustering":[ {"value":"none", "label":"", "pkg":""}, {"value":"walktrap", "label":"Short random walks algorithm", "pkg":"igraph"}, {"value":"fastgreedy", "label":"Greedy optimization of modularity algorithm", "pkg":"igraph"}, {"value":"louvain", "label":"Multi-level optimization of modularity algorithm", "pkg":"igraph"}, {"value":"labelpropagation", "label":"Label propagation method of Raghavan et al.", "pkg":"igraph"} ] }, "fields":{ "Inputs":[ {"id":"name", "scope":"form_only", "help":"This will be the name of the new job. Choose something relevent and avoid special characters. Ex: Exp01_Seq01.", "type":"text", "optional":false, "placeholder":"New job name" }, {"id":"description", "scope":"form_only", "help":"This will be the description of the new job. Give enough details for your analysis to be understood by someone else (if you want to share).", "type":"textarea", "optional":true, "placeholder":"Some details" }, {"id":"primary_dataset", "help":"Data file. Format must be tab-delimited text with one column per sample and one row per OTU/gene. Last column must be 'taxonomy' and contain ';'-delimited categories. No row or column names duplicate are allowed. First row must include samples names. Comments can be included at the beginning of the file and start with '#'. [File format]", "type":"file", "optional":false }, {"id":"category_column", - "help":"This must be the name of the column in your input file that contains the category (i.e. taxonomy, pathways, etc.).", + "help":"This must be the name of the column in your input file that contains the category (i.e. taxonomy, pathways, etc.). The selected column will be used in categorical binning.", "type":"select", - "optional":false, - "depends_on":"primary_dataset" + "placeholder":"Select Primary dataset first", + "increment":"3", + "optional":false }, {"id":"map", "help":"Mapping file. It describes the experimental design. Columns represent factors (ex: treatment,sex,etc.) and rows indicates samples. Format must be tab-delimited text. First row must contain column names. At least one column must contain the sample names,matching the first row of data file (Input). [File format]", "type":"file", "optional":false }, {"id":"secondary_dataset", "help":"Optional metadata file. It can include related data from a different type of analysis that was performed on the same samples. Format must be the same as data file (Input) but without any category column and without any comment. Column names must match column names in data file (Input) in spelling and order. [File format]", "type":"file" } ], "Pre-processing":[ {"id":"abundance_threshold", "help":"Abundance threshold for filtering.", "type":"threshold", "default_type":"percent", "default_integer":"10", "default_percent":"0.03", "placeholder_integer":">0", "placeholder_percent":"0-100" }, {"id":"abundance_threshold_type", "type":"threshold_type", "default":"percent", "hidden_field":1 }, {"id":"presence_threshold", "help":"Presence threshold for filtering.", "type":"threshold", "default_type":"integer", "default_integer":"2", "default_percent":"10", "placeholder_integer":">0", "placeholder_percent":"0-100" }, {"id":"presence_threshold_type", "type":"threshold_type", "default":"int", "hidden_field":1 }, {"id":"bin_levels", "label":"Category binning levels", "help":"The level(s) at which the categories will be binned.", "type":"select", + "placeholder":"Select Primary dataset first", "multiple":true }, {"id":"bin_fun", "label":"Category binning function", "help":"Function to apply for category binning.", "type":"select", + "increment":"3", "values":"binning", "default":"sum" } ], "Transformations":[ {"id":"prim_rarefaction", "label":"Rarefaction (Primary dataset)", "help":"Sub-sampling (random drawings without replacement).", "type":"check_box", "db_field":"prim_rarefaction", "trigger":"drop_down", "default":false }, {"id":"prim_sampling_depth", "label":"Sampling depth", "help":"Number of elements (counts) to draw in each sample. Must be a positive integer. The value cannot exceed the minimum count per sample in the data set (it will be corrected automatically if needed).", "belongs_to":"prim_rarefaction", "type":"integer", "default":"500000", "placeholder":">0" }, {"id":"prim_nsampling", "label":"N samplings", "help":"Number of repeated rarefactions to perform (the result will be an average of these rarefactions).", "belongs_to":"prim_rarefaction", "type":"integer", "default":"1", "placeholder":">0" }, {"id":"prim_trans_method", "label":"Transformation (Primary dataset)", "help":"Choose a data transformation method.", "type":"select", "values":"transformation", "default":"none" }, {"id":"prim_batch_effect_suppression", + "scope":"cli_only", "label":"Batch effect suppression (Primary dataset)", "help":"Batch effect(s) can be suppressed to facilitate statistical tests. Example: In an experiment where 20 samples from experimental group A and 20 samples from experimental group B where distributed into two batches for processing,the batch-effect can be supressed. Make sure it is consitent with Model,in the Experimental design section.", + "placeholder":"Select Map first", + "type":"select" + }, + {"id":"prim_batch_effect_suppression_fun", + "scope":"cli_only", + "label":"Batch effect suppression method (Primary dataset)", + "help":"Method to apply to suppress batch-effect in primary dataset.", "type":"select", - "multiple":true + "values":"batch_effect", + "increment":"3", + "default":"none" }, {"id":"sec_rarefaction", "label":"Rarefaction (Secondary dataset)", "help":"Sub-sampling (random drawings without replacement).", "type":"check_box", "db_field":"sec_rarefaction", "trigger":"drop_down", "default":false }, {"id":"sec_sampling_depth", "label":"Sampling depth", "help":"Number of elements (counts) to draw in each sample. Must be a positive integer. The value cannot exceed the minimum count per sample in the data set (it will be corrected automatically if needed).", "belongs_to":"sec_rarefaction", "type":"integer", "default":"500000", "placeholder":">0" }, {"id":"sec_nsampling", "label":"N samplings", "help":"Number of repeated rarefactions to perform (the result will be an average of these rarefactions).", "belongs_to":"sec_rarefaction", "type":"integer", "default":"1", "placeholder":">0" }, {"id":"sec_trans_method", "label":"Transformation (Secondary dataset)", "help":"Choose a data transformation method.", "type":"select", "values":"transformation", "default":"none" }, {"id":"sec_batch_effect_suppression", + "scope":"cli_only", "label":"Batch effect suppression (Secondary dataset)", "help":"Batch effect(s) can be suppressed to facilitate statistical tests. Example: In an experiment where 20 samples from experimental group A and 20 samples from experimental group B where distributed into two batches for processing,the batch-effect can be supressed. Make sure it is consitent with Model,in the Experimental design section.", + "placeholder":"Select Map first", + "type":"select" + }, + {"id":"sec_batch_effect_suppression_fun", + "scope":"cli_only", + "label":"Batch effect suppression method (Secondary dataset)", + "help":"Method to apply to suppress batch-effect in secondary dataset.", "type":"select", - "multiple":true + "increment":"3", + "values":"batch_effect", + "default":"none" } ], "Analysis":[ {"id":"model_type", "label":"Experimental design", "help":"Choose a basic or advanced model.", "type":"model_type", "default":"basic" }, {"id":"basic_model", "label":"Model", "help":"This will define the statistical model to use for the analysis. An Anova will be used to compare groups.", "type":"select", + "placeholder":"Select Map first", "belongs_to":"model_type_basic" }, {"id":"advanced_stats", "label":"Statistics", "help":"Define which statistics will be used to compare groups. You need to make sure that the syntax of the Model formula fits the statistics requirements.", "type":"select", "belongs_to":"model_type_advanced", "values":"statistics", "default":"anova" }, {"id":"advanced_model", "label":"Model", "help":"This will define the statistical model to use for the analysis. It should be a formula with terms refering to columns in the mapping file. Simple example: In an experiment where column A defines the subjects genders and column B defines subjects diets,a model testing both the effect of gender and diet and their interactions could be writen A*B. Example with nested variables: In an experiment where column A defines individuals and column B defines replicated sampling within each individuals,a model testing the effect of B could be writen A/B.", "type":"text", "placeholder":"ex:ExperimentalGroup*Gender", "belongs_to":"model_type_advanced" }, {"id":"proportions", "scope":"form_only", "label":"Proportions", "help":"Display proportions in a stacked bar-chart.", "type":"check_box", "db_field":"analysis", "default":true }, {"id":"diversity", "scope":"form_only", "help":"This will assess the diversity within each sample. It will generate rarefaction curves for each selected diversity metric that will allow you to compare diversity between samples and between the groups defined in your model.", "type":"check_box", "color":"tomato", "db_field":"analysis", "trigger":"drop_down", "default":false }, {"id":"diversity_metric", "label":"Metric", "belongs_to":"diversity", "help":"Choose metric(s).", "type":"select", "multiple":true, "values":"diversity", "default":"richness" }, {"id":"compare_diversity", "label":"Compare groups", "belongs_to":"diversity", "help":"Compare experimental groups using model and statistical test?", "type":"bool", "default":false }, {"id":"adonis", "scope":"form_only", "label":"perMANOVA", "help":"This will perform a Permutational Multivariate Analysis of Variance using a distance matrix (Adonis method). It will show how your model explains differences based on a similarity matrix that is computed using the chosen similarity metric.", "type":"check_box", "db_field":"analysis", "trigger":"drop_down", "default":false }, {"id":"adonis_distfun", "label":"Distance metric", "belongs_to":"adonis", "help":"Distance metric.", "type":"select", "multiple":true, "values":"distance", "default":"jaccard" }, {"id":"adonis_model", "label":"Model", "belongs_to":"adonis", "help":"Model for Adonis.", "type":"text", "placeholder":"ex:ExperimentalGroup*Gender" }, {"id":"adonis_strata", "label":"Strata", "belongs_to":"adonis", "help":"Adonis strata parameter.", "type":"text", "placeholder":"ex:Subject" }, {"id":"pca", "scope":"form_only", "label":"PCA", "help":"Principal component analysis (PCA).", "type":"check_box", "db_field":"analysis", "default":false }, {"id":"pcoa", "scope":"form_only", "label":"PCoA", "help":"Principal coordinate analysis (PCoA).", "type":"check_box", "db_field":"analysis", "trigger":"drop_down", "default":false }, {"id":"pcoa_distfun", "label":"Distance metric", "belongs_to":"pcoa", "help":"Distance metric.", "type":"select", "multiple":true, "values":"distance", "default":"jaccard" }, {"id":"heatmap", "scope":"form_only", "label":"Heatmap", "help":"Abundance heatmap.", "type":"check_box", "db_field":"analysis", "default":false }, {"id":"change", "scope":"form_only", "label":"Changes", "help":"Differential analysis including fold-change and visualization with MA/volcano plots.", "type":"check_box", "db_field":"analysis", "default":false }, {"id":"correlation_network", "scope":"form_only", "label":"Correlation network", "help":"Correlation network.", "type":"check_box", "db_field":"analysis", "trigger":"drop_down", "default":false }, {"id":"correlation_network_fun", "label":"Correlation method", "belongs_to":"correlation_network", "help":"Correlation method.", "type":"select", "values":"correlation", "default":"spearman" }, {"id":"clustering", "scope":"form_only", "label":"Clustering", "help":"Perform categorical clustering on the primary dataset.", "type":"check_box", "db_field":"analysis", "trigger":"drop_down", "default":false }, {"id":"clustering_fun", "label":"Algorithm", "belongs_to":"clustering", "help":"Choose clustering algorithm(s).", "type":"select", "multiple":true, "values":"clustering", "default":"pam" }, {"id":"similarity_network", "scope":"form_only", "label":"Similarity network", "help":"Similarity network, graph-based clustering and similarity network fusion (SNF).", "type":"check_box", "db_field":"analysis", "trigger":"drop_down", "default":false }, {"id":"similarity_network_fun1", "label":"Metric for primary dataset", "belongs_to":"similarity_network", "help":"Similarity metric for primary dataset.", "type":"select", "values":"distance", "default":"jaccard" }, {"id":"similarity_network_fun2", "label":"Metric for secondary dataset", "belongs_to":"similarity_network", "help":"Similarity metric for secondary dataset.", "type":"select", "values":"distance", "default":"manhattan" }, {"id":"similarity_network_clust", "label":"Clustering algorithm", "belongs_to":"similarity_network", "help":"Graph-based clustering algorithm.", "type":"select", "values":"graph_clustering", "default":"walktrap" } ] } } diff --git a/lib/genocrunch_console/lib/genocrunchlib.R b/lib/genocrunch_console/lib/genocrunchlib.R index ac6d21d..7ec7ace 100755 --- a/lib/genocrunch_console/lib/genocrunchlib.R +++ b/lib/genocrunch_console/lib/genocrunchlib.R @@ -1,2864 +1,2864 @@ #genocrunchlib.R ################ # Print message in a formatted way so it can be decomposed into categories of # logs. Can also be used to issue errors # Args: # (1) label (character) A label for the message (ex: title, log, warning...) # (2) text (character) Message to print # (3) verbose (character) A statement: 'TRUE' or same value as 'label':print # to stdout; 'FALSE':don't print; # Prints to stdout: # Tab-separated: Path to executed script, date and time, 'label' and 'text' ################ log_fp <- NULL PrintMsg <- function(text='default message', verbose=TRUE, stop=FALSE, log=log_fp) { if (!is.null(log)) { f <- file(log, open="at") sink(file=f, append=TRUE) sink(type="message") } if (as.logical(verbose) == TRUE) { cat(text) } if (!is.null(log)) { sink() sink(type="message") close(f) } if (as.logical(stop) == TRUE) { stop(text) } } ################ # Write a table to an output location, with the option of having indented # columns name if 'name'=''. # Args: # (1) table Table object # (2) output.fp Path to the output file # (3) name Name of first column # (4) sep Separator character # (5) na na # (6) dec dec # (7) quote quote # Outputs: # Writes 'table' to 'output.fp', starting first line with an indentation ('\t') ################ WriteTable <- function(table=NULL, output.fp='', name='name', sep='\t', na='', dec='.', quote=FALSE) { write.table(t(c(name, names(table))), file=output.fp, append=FALSE, row.names=FALSE, col.names=FALSE, sep=sep, na=na, dec=dec, quote=quote) write.table(table, file=output.fp, append=TRUE, row.names=TRUE, col.names=FALSE, sep=sep, na=na, dec=dec, quote=quote) } ################ # Reads the "Rfunctions" section of a json file as a json string # Args: # (1) json Path to the json file # Outputs: # A json string as found in the "Rfunctions" section of the json file ################ ReadJson <- function(file='/../etc/genocrunchlib.json', rel=TRUE) { suppressMessages(library('rjson')) if (rel) { # Consider file as a relative path with respect to this library args <- commandArgs(trailingOnly=FALSE) dir <- dirname(sub('--file=', '', args[grep('--file=', args)])) file <- normalizePath(paste(dir, file, sep=''), mustWork=TRUE) } if (!file.exists(file)) { PrintMsg(paste('"error":"File not found: ', file, '."', sep=''), verbose=TRUE, stop=TRUE) } json_str <- fromJSON(file=file) return (toJSON(json_str$choices)) } ################################################################################ ################################################################################ # MANIPULATION ################################################################# # MANIPULATION ################################################################# # MANIPULATION ################################################################# # MANIPULATION ################################################################# ################################################################################ ################################################################################ ################ # Convert a data frame of factors to a numeric data frame. Unlike data.matrix, # it will force characters to numeric. # Args: # (1) x A data frame # Returns: # A numeric version of 'x' ################ ForceNumericDataFrame <- function(x) { x <- as.matrix(x) for (i in 1:ncol(x)) { x[, i] <- as.numeric(as.vector(unlist(x[, i]))) } return (as.data.frame(x)) } ################ # Convert values of a numeric vector to percents of its sum # Args: # (1) x A numeric vector # Returns: # A percent version of 'x' ################ Numeric2Percent <- function(x){ return (100*x/sum(x)) } ################ # Scale a numeric vector on a range of values # Args: # (1) x A numeric vector # (2) range A numeric vector containing 2 elements # (3) force A logical indicating wheter infinite values should be forced into # 'range' # Returns: # A scaled version of 'x', within 'range' ################ RangeScale <- function(x=NULL, range=c(0, 1), force=TRUE) { scaled <- range[1]*(1-((x-min(x, na.rm=TRUE))/(max(x, na.rm=TRUE)-min(x, na.rm=TRUE))))+range[2]*((x-min(x, na.rm=TRUE))/(max(x, na.rm=TRUE)-min(x, na.rm=TRUE))) if (force == TRUE) { scaled[scaled == -Inf] <- min(range) scaled[scaled == Inf] <- max(range) } return (scaled) } ################ # Scale by standard deviation and center on mean # Args: # (1) x A numeric vector # Returns: # A scaled version of 'x' ################ cScale <- function(x=NULL) { sd <- sd(x, na.rm=TRUE) mean <- mean(x, na.rm = TRUE) if (is.na(sd)) { sd <- 1 } else if (sd == 0) { sd <- 1 } if (is.na(mean)) { mean <- 0 } return (x/sd-mean) } ################################################################################ ################################################################################ # JSON HANDELING ############################################################### # JSON HANDELING ############################################################### # JSON HANDELING ############################################################### # JSON HANDELING ############################################################### ################################################################################ ################################################################################ ################ # Build a json string to store figure data # Args: # (1) data (data-frame) # (2) xmetadata (data-frame) With nrow=ncol(data) # Returns: # A json string ################ Dataframe2DataJson <- function(data=NULL, xmetadata=NULL){ # Rules: # colname(data) -> [{'name'}] # rowname(data) and data -> [{'data':['rowname(data)':'data']}] # xmetadata -> [{'rowname(xmetadata)'}] json <- c(1:ncol(data)) for (i in 1:ncol(data)) { xmetadata.json <- NULL if (!is.null(xmetadata)) { xmetadata.json <- c(1:ncol(xmetadata)) for (j in 1:ncol(xmetadata)) { xmetadata.json[j] <- paste('"', names(xmetadata)[j], '":"', xmetadata[i, j], '"', sep='') } xmetadata.json <- paste(xmetadata.json, collapse=',') } data.json.el <- c(1:nrow(data)) for (j in 1:nrow(data)) { data.json.el[j] <- paste('"', row.names(data)[j], '":"', data[j, i], '"', sep='') } data.json <- paste('{', paste(data.json.el, collapse=','), '}',sep='') json[i] <- paste('{', paste(c(paste('"name":"', names(data)[i], '"', sep=''), xmetadata.json, paste('"data":', data.json, sep='')), collapse=','), '}', sep='') } return (paste('[', paste(json, collapse=','), ']', sep='')) } ################ # Build a json string to store diversity values # Args: # (1) diversity (list) # (2) p.value (list) # (3) map (matrix) # Returns: # A json string ################ diversity2json <- function(diversity=NULL, p.value=NULL, map=NULL){ rar <- row.names(diversity) nsample <- ncol(diversity) sample <- c(1:nsample) for (j in 1:nsample) { ndata <- nrow(diversity) data <- c(1:ndata) for (k in 1:ndata) { data[k] <- paste('"', rar[k], '":', diversity[k, j], sep='') } metadata <- c(1:ncol(map)) for (k in 1:ncol(map)) { metadata[k] <- paste('"', names(map[k]), '":"', map[j, k], '"', sep='') } sample[j] <- paste('{"name":"', row.names(map)[j], '",', paste(metadata, collapse=','), ',"data":{', paste(data, collapse=','), '}}', sep='') } if (!is.null(p.value)) { stat <- c(1:length(p.value)) for (j in 1:length(p.value)) { s <- c(1:length(p.value[[j]])) for (k in 1:length(p.value[[j]])) { s[k] <- paste('{"name":"', names(p.value[[j]])[k], '", "p-value":"', p.value[[j]][1, k], '"}', sep='') } stat[j] <- paste(s, collapse=',') } json <- paste('{"data":[', paste(sample, collapse=','), '],"stat":[', paste(stat, collapse=','), ']}', sep='') } else { json <- paste('{"data":[', paste(sample, collapse=','), ']}', sep='') } return (json) } ################ # Build a json tree from dendrogram # Args: # (1) dendrogram (list) A dendrogram (nested lists) # Returns: # A json string ################ dendrogram2json <- function(dendrogram=NULL, json=NULL, it=0) { start <- FALSE if (is.null(json)) { start <- TRUE json <- paste('{"name":"node', it, '", "children":[', sep='') } json.vect <- c(1:length(dendrogram)) for (i in 1:length(dendrogram)) { if (length(dendrogram[[i]]) > 1) { it <- it+1 json.vect[i] <- paste('{"name":"node', it, '", "children":[', sep='') dendrogram2json.out <- dendrogram2json(dendrogram[[i]], json.vect[i], it) json.vect[i] <- dendrogram2json.out[[1]] it <- dendrogram2json.out[[2]] json.vect[i] <- paste(json.vect[i], ']}', sep='') } else { json.vect[i] <- paste('{"name":"', dendrogram[[i]], '"}', sep='') } } if (start) { json <- paste(json, paste(json.vect, collapse=','), ']}', sep='') } else { json <- paste(json, paste(json.vect, collapse=','), sep='') } out <- list() out[1] <- json out[2] <- it return (out) } ################ # Build a json network from similarity matrix # Args: # (1) mat list of numeric matrix # Returns: # A json string ################ buildFusionNetworkJson <- function(mat=NULL, map=NULL){ names <- colnames(mat[[1]]) ncol <- ncol(mat[[1]]) if (!is.null(map)) { ncol.map <- ncol(map) } nodes <- c(1:ncol) for (i in 1:ncol){ if (!is.null(map)) { map.row <- c(1:ncol.map) for (j in 1:ncol.map) { map.row[j] <- paste('"', names(map)[j], '":"', map[i, j], '"', sep='') } } else { map.row <- '"none":"none"' } nodes[i] <- paste('{"id":', i-1, ',"name":"', names[i], '",', paste(map.row, collapse=','), '}', sep='') } links <- c(1:(ncol*(ncol/2-1))) k <- 1 for (i in 1:(ncol-1)) { for (j in (i+1):ncol) { weights <- c(1:length(mat)) for (l in 1:length(mat)) { weights[l] <- mat[[l]][i,j] } links[k] <- paste('{"source":', i-1, ',"target":', j-1, ',"weight":[', paste(weights,collapse = ","), ']}', sep='') k <- k+1 } } return(paste('{"nodes":[', paste(nodes, collapse = ","),'],"links":[', paste(links, collapse = ","), ']}', sep='')) } ################################################################################ ################################################################################ # MODIFICATION ################################################################# # MODIFICATION ################################################################# # MODIFICATION ################################################################# # MODIFICATION ################################################################# ################################################################################ ################################################################################ ################ # Sort a data-frame columns according to a column of another # Args: # (1) table (matrix-like object) Table to be sorted # (2) map (matrix-like object) Map to extract the sorting column from # (3) column (numeric) Index of the column in map to use for sorting # (4) verbose (bool) Print messages? see PrintMsg() options # Returns: # A sorted version of table # Print to stdout: # A description ################ SortTable <- function(table=NULL, map=NULL, verbose=TRUE) { # print(row.names(map)) # print(map) # print(map[, column]) PrintMsg('"description":"Sort table columns according to map"', verbose) return(as.data.frame(table[, as.vector(row.names(map))])) } ################ # Transpose a data frame (or a matrix-like object) # Args: # (1) x (matrix-like object) # Returns: # A transposed version of 'x' (numeric dataframe) ################ Transpose <- function(x=NULL) { if (is.data.frame(x)) { names <- names(x) row.names <- row.names(x) x <- as.data.frame(t(x)) names(x) <- row.names row.names(x) <- names } else { x <- t(x) } return (x) } ################ # Filter numeric table (with category-specific options for OTU-type tables) # Args: # (1) table (numeric dataframe) Count table # (2) category (character) Name of the (optional) category column # (3) abundance_threshold (numeric) Min % abundance per column [0,100] # (4) presence_threshold (numeric) Min % presence per row [0,100] # (5) rm_unassigned (logical) Remove unassigned category ? # (6) verbose (character) Print messages? see PrintMsg() options # Returns: # An filtered version of 'table' (numeric dataframe) # Prints to stdout: # A description of the function ################ FilterTable <- function(table=NULL, category='taxonomy', abundance_threshold=0.03, abundance_threshold_type='percent', presence_threshold=1, presence_threshold_type='int', verbose=TRUE) { nrow.init <- nrow(table) # First filter based on minimal abundance msg <- paste('"description":"Removed rows not exceeding ', abundance_threshold, sep='') filter <- table[, names(table) != category] minmax.a <- paste(min(filter), max(filter), sep='-') if (abundance_threshold_type == 'percent') { filter <- as.data.frame(apply(filter, 2, Numeric2Percent)) msg <- paste(msg, '% ', sep='') minmax.a <- paste(min(filter), '-', max(filter), '%', sep='') } max.abundance <- apply(filter, 1, max) table.filtered <- table[max.abundance >= abundance_threshold, ] nrow.filtered.a <- nrow(table.filtered) msg <- paste(msg, 'in at least one column: ', nrow.init-nrow.filtered.a, ' rows were removed out of ', nrow.init, '. Then, removed rows with presence (number of values not equal to zero) lower than ', presence_threshold, sep='') # Then filter based on minimal presence filter <- table.filtered[, names(table.filtered) != category] presence <- rowSums(filter != 0) minmax.p <- paste(min(presence), max(presence), sep='-') if (presence_threshold_type == 'percent') { presence <- Numeric2Percent(presence) msg <- paste(msg, '%', sep='') minmax.p <- paste(min(presence), '-', max(presence), '%', sep='') } table.filtered <- table.filtered[presence >= presence_threshold, ] nrow.filtered.p <- nrow(table.filtered) nstop <- nrow(table.filtered) ab.min <- round(min(filter), digits=4) ab.max <- round(max.abundance, digits=4) msg <- paste(msg, ': ', nrow.filtered.a-nrow.filtered.p, ' rows were removed out of ', nrow.filtered.a, ' (final number of rows = ', nrow.filtered.p, '). Min-max abundance found was ', minmax.a, '. Min-max presence found was ', minmax.p, '."', sep='') PrintMsg(msg, verbose) if (nrow.filtered.p == 0) { PrintMsg(paste('"error":"All the rows were filtered out. Please chose an abundance threshold within ', minmax.a, ' and a presence threshold within ', minmax.p, '."', sep=''), verbose, TRUE) } return (table.filtered) } ################ # Bin numeric table by category # Args: # (1) table (numeric dataframe) Count table # (2) aggr.col (character) Name of the column in 'table' to consider for data # aggregation # (3) fun (character) Function to use for data aggregation. Valid # choices: 'mean', 'sum' # (4) verbose (character) Print messages? see PrintMsg() options # Returns: # An aggregated version of 'table' (numeric dataframe) # Prints to stdout: # A description of the function ################ BinTableByCategory <- function(table=NULL, category_col='taxonomy', fun='sum', level='NA', verbose=TRUE) { PrintMsg(paste('"description":"Bin table rows by category (column=', category_col, ', function=', fun, ', level=', level, ')."', sep=''), verbose) category_is_row_names <- FALSE if ((category_col == '') || !(category_col %in% names(table)) ) { category_is_row_names <- TRUE category <- row.names(table) } else { category <- paste(table[, names(table) == category_col], '; (', row.names(table), ')', sep='') table <- table[, names(table) != category_col] } category <- strsplit(category, ';') for (i in 1:length(category)) { category[[i]] <- gsub('^ *.__ *$', '', category[[i]]) category[[i]] <- gsub('^ *', '', category[[i]]) category[[i]] <- gsub(' *$', '', category[[i]]) category[[i]] <- category[[i]][! category[[i]] %in% c('', ' ')] if (length(category[[i]]) >= level) { category[[i]] <- paste(category[[i]][1:level], collapse='; ') } else { category[[i]] <- paste(category[[i]], collapse='; ') } } category <- list(unlist(category)) name <- 'category' while (name %in% names(table)) { name <- paste(name,paste(sample(c(0:9, letters, LETTERS), 3), collapse=''),sep='_') } names(category) <- name table.binned <- aggregate(table, by=category, FUN=fun) row.names(table.binned) <- table.binned[, name] table.binned <- table.binned[, names(table.binned) != name] return(table.binned) } ################ # Divide a data frame (or a matrix-like object) by a vector # Args: # (1) table (numeric dataframe) Count table # (2) vect (vector) Vector to divide 'table' # (3) verbose (character) Print messages? see PrintMsg() options # Returns: # A divided (per row) version of 'table' (numeric dataframe) # Prints to stdout: # A description of the function ################ DivTable <- function(table=NULL, vect=NULL, verbose='TRUE') { PrintMsg('log', 'Divide tables rows by a vector.', verbose=verbose) return (as.matrix(table) / unlist(vect)) } ################ # Transform to log2 count per milion # Args: # (1) table (numeric dataframe) Count table # (2) verbose (character) Print messages? see PrintMsg() options # Returns: # A transformed version of 'table' (numeric dataframe) # Prints to stdout: # A description of the function ################ ApplyLog2Cpm <- function(table=NULL, verbose='TRUE') { if (length(table[table < 0]) > 0) { PrintMsg('"error":"Log2Cpm was not applied because negative values were detected."', verbose, TRUE) return () } if (length(table[table%%1 > 0]) > 0) { PrintMsg('"warning":"Non-integer values were detected."', verbose) table <- round(table, digits=0) } PrintMsg('"description":"Converted values into per-million per column and applied log2(x+1)."', verbose) transformed.table <- as.data.frame(log2(1+(apply(as.matrix(table), 2, Numeric2Percent))*10000)) names(transformed.table) <- names(table) row.names(transformed.table) <- row.names(table) return (transformed.table) } ################ # Transform to log2 # Args: # (1) table (numeric dataframe) Count table # (2) verbose (character) Print messages? see PrintMsg() options # Returns: # A transformed version of 'table' (numeric dataframe) # Prints to stdout: # A description of the function ################ ApplyLog2 <- function(table=NULL, verbose=TRUE) { if (length(table[table < 0]) > 0) { PrintMsg('"error":"log2(x+1) was not applied because negative values were detected."', verbose, TRUE) return () } PrintMsg('"description":"Applied log2(x+1)."', verbose) transformed.table <- as.data.frame(log2(1+(as.matrix(table)))) names(transformed.table) <- names(table) row.names(transformed.table) <- row.names(table) return (transformed.table) } ################ # Transform to percent (per column) # Args: # (1) table (numeric dataframe) Count table # (2) verbose (character) Print messages? see PrintMsg() options # Returns: # A transformed version of 'table' (numeric dataframe) # Prints to stdout: # A description of the function ################ ApplyCount2Percent <- function(table, verbose=TRUE) { PrintMsg('"description":"Transformed values into percentages per column."', verbose=verbose) transformed.table <- as.data.frame(apply(as.matrix(table), 2, Numeric2Percent)) names(transformed.table) <- names(table) row.names(transformed.table) <- row.names(table) return (transformed.table) } ################ # Apply rarefaction # Args: # (1) table (numeric dataframe) Count table # (2) sample (positive integer or 'max') Sampling depth # (3) nsampling (positive integer) Number of repeated sampling to performe # (4) verbose (character) Print messages? see PrintMsg() options # Returns: # A rarefied version of 'table' (a numeric dataframe) # Prints to stdout: # A description of the function ################ RarefyTable <- function(table=NULL, sample='max', nsampling=1, verbose=TRUE) { suppressMessages(library('vegan')) # Round counts if not integers if (length(table[table < 0]) > 0) { PrintMsg('"error":"Rarefaction is not applicable because negative values were detected."', verbose, TRUE) return () } if (length(table[table%%1 > 0]) > 0) { PrintMsg('"warning":"Non-integer values were detected. In order to permit rarefaction, these values were rounded to the nearest integer."', verbose) table <- round(table, digits=0) } # Define/check rarefaction depth min.sample <- min(colSums(table)) if (sample == 'max' || as.numeric(sample) > min.sample) { sample <- min.sample } if (sample < 100) { PrintMsg(paste('"warning":"Very low counts detected in the following sample(s): ', paste(names(table)[which(colSums(table) < 100)], collapse=','), '."', sep=''), verbose) } else if (sample < 1000) { PrintMsg(paste('"warning":"Low counts detected in the following sample(s): ', paste(names(table)[which(colSums(table) < 1000)], collapse=','), '."', sep=''), verbose) } # Apply rarefaction table <- t(table) table.list <- list() for (i in 1:as.numeric(nsampling)) { table.list[[i]] <- rrarefy(table, sample=sample) } rarefied.table <- apply(simplify2array(table.list), c(1, 2), mean) # element-wise mean across rarefied tables PrintMsg(paste('"description":"Equalized number of observations per samples using the rarefaction method with sampling depth=', sample, ' (random sampling without replacement) (R package vegan). New values were calculated as the means of ', nsampling, ' independent samplings that were rounded to the nearest integer."', sep=''), verbose=verbose) return (data.frame(t(round(rarefied.table, digits=0)))) } ################ # Args: # (1) table (numeric dataframe) Count table # (2) map (dataframe) Design # (3) effect (character) comma-delimited list of random effects to # suppress. Elements must be names of columns in mapping file # (4) verbose (character) Print messages? see PrintMsg() options # Returns: # A adjusted version of 'table' (a numeric dataframe) # Prints to stdout: # A description of the function ################ SuppressBatchEffect <- function(table=NULL, map=NULL, effect=NULL, fun='combat', verbose=TRUE) { - method <- list(combat='COMBAT', mean_centering='mean centering') + method <- list(combat='ComBat', mean_centering='mean centering') PrintMsg(paste('"description":"Batch effect suppression on ', effect, ' using', method[[fun]], '"'), verbose) effect <- unlist(strsplit(effect, split=',')) if (fun == 'mean_centering') { table <- t(table) for (i in 1:length(effect)) { fact <- map[effect[i]] fact.name <- as.vector(unique(unlist(fact))) nfact <- length(fact.name) for (j in 1:ncol(table)) { data <- data.frame(d=table[, j], fact=fact) means <- NULL for (k in 1:nfact) { means <- append(means, mean(data[fact == fact.name[k], 'd'])) } for (k in 1:nfact) { adj <- max(means)-means[k] data[fact == fact.name[k], 'd'] <- data[fact == fact.name[k], 'd']+adj } table[, j] <- data$d } } table <- t(table) } else if (fun == 'combat') { suppressMessages(library('sva')) table <- ComBat(table, map[, effect]) } return (table) } ################ # Filter mapping file to keep only terms in model # Args: # (1) map (dataframe) # (2) model (formula vector) # Returns: # A filtered map ################ FilterMap <- function(map=NULL, model=NULL) { # keep only columns of map that are used in the model if (!is.null(model)) { term <- list() for (i in 1:length(model)) { term[[i]] <-as.vector(unique(unlist(strsplit(model[i], split=c('[^A-Za-z0-9_]'))))) term[[i]] <- term[[i]][! term[[i]] %in% c("", " ", "1", "Error")] } term <- as.vector(unlist(term)) row.names <- row.names(map) if (length(term) > 1) { map <- as.data.frame(map[, names(map) %in% term]) } else { map <- as.data.frame(map[, names(map) == term]) names(map) <- term } if (ncol(map) < length(unique(term))) { PrintMsg(paste('"error":"Some terms in model ', model, ' were not found in the map. Please make sure that all model terms refer to columns headers in the map."', sep=''), verbose=TRUE, TRUE) } } return (map) } ################ # Remove constant rows from table # Args: # (1) table (dataframe) # Returns: # A filtered table ################ RemoveConstantRows <- function(table=NULL, verbose=TRUE) { filter <- rep(TRUE, nrow(table)) for (i in 1:nrow(table)) { if (length(as.vector(unique(unlist(table[i, ])))) < 2) { filter[i] <- FALSE PrintMsg(paste('"warning":"Row ', i, ' (', row.names(table)[i], ') contained essentially constant data and was removed."', sep=''), verbose=verbose) } } return (table[filter == TRUE, ]) } ################################################################################ ################################################################################ # ANALYSIS ##################################################################### # ANALYSIS ##################################################################### # ANALYSIS ##################################################################### # ANALYSIS ##################################################################### ################################################################################ ################################################################################ ################ # Apply a clustering algorithm # Args: # (1) table (numeric dataframe) Count table # (2) fun (character) Clustering function (algorithme) # (3) json (character) Json string specifying packages # (4) verbose (character) Print messages? see PrintMsg() options # (5) graphical (logical) Generate graphics? # Returns: # A table of clusters # Output to device: # Depending on 'fun', a figure # Prints to stdout: # A description of the function ################ PerformClustering <- function(table=NULL, fun=c('kmeans', 'pam', 'pam-bray', 'pam-jaccard'), json=libJson, verbose=TRUE, graphical=TRUE) { if (ncol(table) < 10 || nrow(table) < 3) { PrintMsg('"warning":"Not enough data to perform clustering (min 10 column and 3 rows)."', verbose) return (NULL) } else if (ncol(table) > 65536) { PrintMsg('"warning":"Sorry, the dataset is too large for clustering algorithms (max 65536 columns)."', verbose) return (NULL) } - spec <- as.data.frame(do.call('rbind', json$clustering)) spec <- spec[spec$value == fun, ] package <- '' pkg <- unlist(strsplit(as.character(unlist(spec$pkg)), split=',')) if (length(pkg) > 0) { for (i in 1:length(pkg)) { suppressMessages(library(pkg[i], character.only=TRUE)) } package <- paste(' (R package {', unlist(spec$pkg), '})', sep='') } - PrintMsg(paste('"description":"Performed clustering using ', spec$label, package, '."', sep=''), verbose) table <- t(table) if (fun == 'kmeans') { if (graphical == TRUE) { kmean.out <- kmeansruns(table, criterion='asw', plot=TRUE)$cluster } else { kmean.out <- kmeansruns(table, criterion='asw')$cluster } cluster <- paste('kmean', kmean.out, sep ='') } else if (fun == 'pam') { - pam.out <- pamk(table, usepam=FALSE) if (graphical == TRUE) { plot(pam.out$pamobject, which.plots=2) } - cluster <- paste('pam', pam.out$pamobject$cluster, sep ='') - } else if (fun == 'pam-bray') { dist.data <- ComputeDistance(table=table, fun='bray', json=json, verbose=verbose) pam.out <- pamk(dist.data, usepam=TRUE) if (graphical == TRUE) { plot(pam.out$pamobject, which.plots=2) } cluster <- paste('pam', pam.out$pamobject$cluster, sep ='') } else if (fun == 'pam-jaccard') { dist.data <- ComputeDistance(table=table, fun='jaccard', json=json, verbose=verbose) pam.out <- pamk(dist.data, usepam=TRUE) if (graphical == TRUE) { plot(pam.out$pamobject, which.plots=2) } cluster <- paste('pam', pam.out$pamobject$cluster, sep ='') } else { return(NULL) } - + cluster <- as.data.frame(cluster) + row.names(cluster) <- row.names(table) names(cluster) <- 'Clustering' + PrintMsg(paste('"description":"Clusters: ', + paste(paste(row.names(cluster), unlist(cluster), sep=':'), collapse=', '), + '."', sep=''), + verbose) return (cluster) } ################ # Build a relative abundance stacked barplot # Args: # (1) table (numeric dataframe) Count table # (2) threshold (numeric) Abundance cutoff for display in percent [0;100] # (3) verbose (character) Print messages? see PrintMsg() options # (4) graphical (logical) Generate graphics? # Returns: # Figure data as a json string # Output to device: # A figure # Prints to stdout: # A description of the function ################ AnalyseProportions <- function(table=NULL, map=NULL, verbose=TRUE, graphical=TRUE) { # Convert to relative abundances per column PrintMsg('"description":"Converted data to relative abundance (%) per sample."', verbose) table <- as.data.frame(apply(table, 2, Numeric2Percent)) # Order by decreasing values table <- as.data.frame(table[order(rowMeans(table), decreasing=TRUE), ]) # Build stacked barchart if (as.logical(graphical) == TRUE) { col <- rainbow(nrow(table)) par(mar=c(4, 4, 2, 4), xpd=TRUE) barplot(as.matrix(table), ylim=c(0, max(colSums(table))), space=0, border=NA, names.arg=names(table), ylab='%', col=col, las=2, bty="n", cex.names=1.84906*ncol(table)^-0.2898) # Build legend in a separate figure plot.new() legend("topleft", row.names(table), cex=0.8, fill=col, horiz=FALSE) } return (Dataframe2DataJson(data=table, xmetadata=map)) } ################ # Calculate diversity per column # Args: # (1) table (numeric dataframe) Count table # (2) fun (character) The diversity function to use # (3) json (character) Json string specifying packages # (4) verbose (character) Print messages? see PrintMsg() options # Returns: # A vector of length=ncol(table) # Prints to stdout: # A description of the function ################ ComputeDiversity <- function(table=NULL, fun='shannon', json=libJson, verbose=TRUE) { spec <- as.data.frame(do.call('rbind', json$diversity)) spec <- spec[spec$value == fun, ] package <- '' pkg <- unlist(strsplit(as.character(unlist(spec$pkg)), split=',')) if (length(pkg) > 0) { for (i in 1:length(pkg)) { suppressMessages(library(pkg[i], character.only=TRUE)) } package <- paste(' (R package {', unlist(spec$pkg), '})', sep='') } else { pkg <- '' } PrintMsg(paste('"description":"Calculated ', spec$label, package, ' per sample."', sep=''), verbose) diversity <- NULL if (pkg == 'vegan') { for (i in 1:ncol(table)) { diversity <- append(diversity, diversity(table[, i], index=fun, MARGIN=0, base=2), after=length(diversity)) } } else if (pkg == 'ineq') { for (i in 1:ncol(table)) { diversity <- append(diversity, ineq(table[, i], parameter=NULL, type=fun, na.rm=TRUE), after=length(diversity)) } } else if (fun == 'richness') { for (i in 1:ncol(table)) { diversity <- append(diversity, sum(table[, i] != 0), after=length(diversity)) } } else if (fun == 'chao1') { diversity <- chao1(table, taxa.row = TRUE) } else { PrintMsg(paste('"error":"Unknown argument to fun (2):', fun, '"', sep=' '), verbose, TRUE) } return (diversity) } ################ # Compute correlations # Args: # (1) table (numeric dataframe) Count table # (2) fun (character) The correlation methode to use # (3) json (character) Json string specifying packages # (4) verbose (character) Print messages? see PrintMsg() options # Returns: # A vector of length=ncol(table) # Prints to stdout: # A description of the function ################ ComputeCorrelation <- function(table=NULL, fun='pearson', test=FALSE, json=libJson, verbose=TRUE) { spec <- as.data.frame(do.call('rbind', json$correlation)) spec <- spec[spec$value == fun, ] package <- '' pkg <- unlist(strsplit(as.character(unlist(spec$pkg)), split=',')) if (length(pkg) > 0) { for (i in 1:length(pkg)) { suppressMessages(library(pkg[i], character.only=TRUE)) } package <- paste(' (R package {', unlist(spec$pkg), '})', sep='') } PrintMsg(paste('"description":"Calculate ', spec$label, package, '."', sep=''), verbose=verbose) if (as.logical(test) == TRUE) { cor.output <- list() cor.output[['estimate']] <- matrix(ncol=nrow(table), nrow=nrow(table)) cor.output[['p.value']] <- matrix(ncol=nrow(table), nrow=nrow(table)) for (i in 2:nrow(table)) { for (j in 1:(i-1)) { out <- cor.test(unlist(table[i, ]), unlist(table[j, ]), method=fun) cor.output[['estimate']][i, j] <- out$estimate cor.output[['p.value']][i, j] <- out$p.value } } cor.output[['estimate']][upper.tri(cor.output[['estimate']])] <- t(cor.output[['estimate']])[upper.tri(cor.output[['estimate']])] diag(cor.output[['estimate']]) <- 1 cor.output[['p.value']][upper.tri(cor.output[['p.value']])] <- t(cor.output[['p.value']])[upper.tri(cor.output[['p.value']])] diag(cor.output[['p.value']]) <- 0 return (cor.output) } if (fun == 'pearson') { return (cor(t(table), method=fun, use='pairwise.complete.obs')) } else { return (cor(t(table), method=fun, use='na.or.complete')) } } ################ # Compute distances # Args: # (1) table (numeric dataframe) Count table # (2) fun (character) The distance function to use # (3) json (character) Json string specifying packages # (4) verbose (character) Print messages? see PrintMsg() options # Returns: # A vector of length=ncol(table) # Prints to stdout: # A description of the function ################ ComputeDistance <- function(table=NULL, fun='bray', json=libJson, verbose=TRUE) { spec <- as.data.frame(do.call('rbind', json$distance)) spec <- spec[spec$value == fun, ] package <- '' pkg <- unlist(strsplit(as.character(unlist(spec$pkg)), split=',')) if (length(pkg) > 0) { for (i in 1:length(pkg)) { suppressMessages(library(pkg[i], character.only=TRUE)) } package <- paste(' (R package {', unlist(spec$pkg), '})', sep='') } if (fun %in% c('pearson', 'spearman')) { PrintMsg(paste('"description":"Calculate distance matrix based on ', spec$label, package, '."', sep=''), verbose=verbose) return (as.dist(as.matrix(ComputeCorrelation(table, fun=fun, json=json, verbose=FALSE)))) } else { # Correct for negative entries by adding a scalar (Caillez correction method) if (min(table) < 0) { table <- table+abs(min(table)) PrintMsg(paste('"description":"Apply a Caillez correction and calculate distance matrix based on ', spec$label, package, '."', sep=''), verbose=verbose) } else { PrintMsg(paste('"description":"Calculate distance matrix based on ', spec$label, package, '."', sep=''), verbose=verbose) } if ('vegan' %in% pkg) { return (vegdist(table, method=fun, binary=FALSE, diag=TRUE, upper=TRUE)) } } } ################ # Test if model is balanced # Args: # (1) map (data-frame) Experimental design table # (2) model (character) A formula representing the model for stats. Terms # must refer to names(map) # (3) verbose (character) Print messages? see PrintMsg() options # Returns: # A bool indicating whether the design is balanced or not ################ ModelIsBalanced <- function(map=NULL, model=NULL, verbose='TRUE') { # keep only columns of map that are used in the model map <- FilterMap(map=map, model=model) for (i in 1:ncol(map)) { fact <- as.vector(unique(unlist(map[, i]))) n <- length(map[map[, i] == fact[1], i]) if (length(fact) > 1) { for (j in 2:length(fact)) { if (length(map[map[, i] == fact[j], i]) != n) { PrintMsg('log', paste('Model ',model , ' is not balanced.', sep=''), verbose=verbose) return (FALSE) } } } } PrintMsg('note', paste('Model ',model , ' is balanced.', sep=''), verbose=verbose) return (TRUE) } ################ # Test if model's variables could be nested # Args: # (1) map (data-frame) Experimental design table # (2) model (character) A formula representing the model for stats. Terms # must refer to names(map) # (3) verbose (character) Print messages? see PrintMsg() options # Returns: # A list of nested variables ################ ModelIsNested <- function(map=NULL, model=NULL, verbose='TRUE') { # keep only columns of map that are used in the model map <- FilterMap(map=map, model=model) if (ncol(map) < 2) { return (NULL) } out <- list() out.txt <- NULL for (i in 1:ncol(map)) { fact <- as.vector(unique(unlist(map[, i]))) test <- as.data.frame(map[, -i]) names(test) <- names(map)[-i] out[[names(map)[i]]] <- NULL for (j in 1:ncol(test)) { group1 <- as.vector(test[map[, i] == fact[1], j]) nested <- TRUE if (length(as.vector(unique(map[, i]))) >= length(as.vector(unique(test[, j])))) { nested <- FALSE } else if (length(as.vector(unique(group1))) != length(group1)) { nested <- FALSE } else { for (k in 2:length(fact)) { if (!identical(group1, as.vector(test[map[, i] == fact[k], j]))) { nested <- FALSE break } } } if (nested == TRUE) { out[[names(map)[i]]] <- append(out[[names(map)[i]]], names(test)[j], after=length(out[[names(map)[i]]])) out.txt <- append(out.txt, paste(names(test)[i], names(map)[j], sep='/'), after=length(out.txt)) } } } if (length(out) == 0) { return (NULL) } PrintMsg('log', paste('The following nesting was detected in model ', model, ': ', paste(out.txt, collapse=', '), ' Please control that the model is the one you want.', sep=''), verbose=verbose) return (out) } ################ # Compute statistics # Args: # (1) response (numeric vector) # (2) map (data-frame) Experimental design table with # nrow=length(response) # (3) method (character) Statistics to use # (4) model (character) A formula representing the model for stats. Terms # must refer to names(map) # (5) pairwise (bool) Perform pairwise comparision? # (6) verbose (character) Print messages? see PrintMsg() options # Returns: # A data-frame # Prints to stdout: # A description of the function ################ ComputeStats <- function(response=NULL, map=NULL, method='anova', model=NULL, pairwise=TRUE, verbose=TRUE) { if (is.null(method)) { method <- 'anova' PrintMsg('"warning":"The statistical method was not specified. An ANOVA will be performed by default."', verbose) } # If no model is specified, assume all columns in map are fixed variables if (is.null(model)) { model <- paste(names(map), collapse='+') PrintMsg(paste('"warning":"Model was not specified. Based on the map, the following model will be used by default: ', model, '."', sep=''), verbose) } # Test that each factor has >= 2 levels term <- as.vector(unique(unlist(strsplit(model, split=c('[^A-Za-z0-9_]'))))) term <- term[! term %in% c("", " ", "1", "Error")] for (i in 1:length(term)) { if (!term[i] %in% names(map)) { PrintMsg(paste('"error":"The following model factor was not found in map: ', term[i], '. Please correct the model or correct the map."', sep=''), verbose=TRUE, stop=TRUE) } levels <- as.vector(unique(unlist(map[, term[i]]))) if (length(levels) < 2) { PrintMsg(paste('"error":"The following model factor has less than 2 levels: ', term[i], '. Please correct the model or correct the map."', sep=''), verbose=TRUE, stop=TRUE) } for (j in 1:length(levels)) { if (length(map[map[, term[i]] == levels[j], ]) == 1) { PrintMsg(paste('"warning":"The following group contains only one element (variance is null): ', levels[j], '. This will impact on statistics."', sep=''), TRUE) } } } response.name <- 'response' while (response.name %in% names(map)) { response.name <- paste('response', paste(sample(c(letters, LETTERS, c(0:9)), 4), collapse=''), sep='') } names(response) <- response.name formula <- paste(response.name, ' ~ ', model, sep='') # Build the dataframe data <- map data <- cbind(map, response) pairwise.output <- NULL if (method == 'anova' || is.null(method)) { # PARAMETRIC, ACCEPTS MODELS WITH >2 FACTORS, MULTIPLE FIXED VARIABLES AND NESTING # MODEL CAN BE OF THE FORM "a*b+c" PrintMsg(paste('"description":"Fit an analysis of variance (ANOVA) using model: ', model, '."', sep=''), verbose) aov.output <- aov(formula=as.formula(formula), data=data) if (pairwise == TRUE) { pairwise.output <- TukeyHSD(aov.output) } summary <- as.data.frame(summary(aov.output)[[1]]) if ('Pr(>F)' %in% names(summary)) { p.value <- summary[, 'Pr(>F)'] } else { PrintMsg(paste('"warning":"Impossible to calculate a p-value. Please make sure that the model ', model, ' is not saturated."', sep=''), verbose) p.value <- rep('NA', nrow(summary)) } summary <- data.frame(row.names=gsub('[[:space:]]', '', row.names(summary)), p.value=p.value) #explained=summary[, 'Sum Sq']/sum(summary[, 'Sum Sq'])) } else if (method == 'friedman') { # NON-PARAMETRIC, ACCEPTS MODELS WITH >2 FACTORS BUT REQUIRES EXACTLY 2 NESTED VARIABLE # MODEL HAS TO BE OF THE FORM "a | b" friedman.terms <- unlist(strsplit(model, split=' ')) if (length(friedman.terms) != 3 || friedman.terms[2] != '|') { PrintMsg(paste('"error":"Model must be of the form a | b when using Friedman test. Please provide an appropriate model. Current model: ', model, '."', sep=''), verbose, TRUE) } friedman.output <- friedman.test.with.post.hoc(formula=as.formula(formula), data=data) PrintMsg(paste('"description":"Performed Friedman test using model: ', model, '."', sep=''), verbose) if (pairwise == TRUE) { pairwise.output <- list() pairwise.output[[term[1]]] <- data.frame(row.names=paste(unique(unlist(map[, term[1]])), collapse='-'), p=friedman.output$PostHoc.Test) PrintMsg('"description":"Performed post-hoc test with correction for multiple comparisions."', verbose) } summary <- data.frame(row.names=term[1], p.value=friedman.output$Friedman.Test) } else if (method == 'kruskal') { # NON-PARAMETRIC, ACCEPTS MODELS WITH >2 FACTORS BUT ONLY 1 FIXED VARIABLE # MODEL CAN BE OF THE FORM "a" if (length(term) > 1) { PrintMsg(paste('"error":"Model cannot include more than 1 variable when using Kruskal-Wallis rank sum test. Please provide an appropriate model. Current model: ', model, ' (', length(term), ' variables)."', sep=''), verbose, TRUE) } suppressMessages(library('dunn.test')) dunn.output <- dunn.test(data[, response.name], g=map[, term[1]], kw=FALSE, table=FALSE, list=FALSE) PrintMsg(paste('"description":"Performed Kruskal-Wallis rank sum test using model: ', model, '."', sep=''), verbose) if (pairwise == TRUE) { pairwise.output <- list() pairwise.output[[term[1]]] <- data.frame(row.names=paste(unique(unlist(map[, term[1]])), collapse='-'), p=dunn.output$P) PrintMsg('"description":"Performed post-hoc test with correction for multiple comparisions (R library {dunn.test})."', verbose) } summary <- data.frame(row.names=term[1], p.value=pchisq(dunn.output$chi2, nrow(data)-1, lower.tail=FALSE)) } else if (method == 'wilcox') { # NON-PARAMETRIC, ACCEPTS MODELS WITH 2 FACTORS ONLY AND ONE FIXED VARIABLES # MODEL HAS TO BE OF THE FORM "a" if (length(term) > 1) { PrintMsg(paste('"error":"Model cannot include more than 1 variable when using Wilcoxon rank sum test. Please provide an appropriate model. Current model: ', model, ' (', length(term), ' variables)."', sep=''), verbose, TRUE) } samples <- unique(unlist(data[, model])) if (length(samples) != 2) { PrintMsg(paste('"error":"Wilcoxon rank sum test can only perform two samples comparison. Please provide an appropriate model. Current model includes following samples: ', sample, '."', sep=''), verbose, TRUE) } wilcox.output <- wilcox.test(formula=as.formula(formula), data=data) PrintMsg(paste('"description":"Performed Wilcoxon rank sum test using model: ', model, '."', sep=''), verbose) summary <- data.frame(row.names=paste(samples, collapse='-'), p.value=wilcox.output$p.value) pairwise.output <- list() pairwise.output[[model]] <- as.data.frame(wilcox.output$p.value) names(pairwise.output[[model]]) <- model row.names(pairwise.output[[model]]) <-paste(samples, collapse='-') } else if (method == 'wilcox_paired') { # NON-PARAMETRIC, ACCEPTS MODELS WITH 2 FACTORS ONLY AND ONE FIXED VARIABLES. NESTING IS ASSUMED FROM THE ORDER OF THE DATA! # MODEL HAS TO BE OF THE FORM "a" if (length(term) > 1) { PrintMsg(paste('"error":"Model cannot include more than 1 variable when using Wilcoxon signed rank test. Please provide an appropriate model. Current model: ', model, ' (', length(term), ' variables)."', sep=''), verbose, TRUE) } samples <- unique(unlist(data[, model])) if (length(samples) != 2) { PrintMsg(paste('"error":"Wilcoxon signed rank test can only perform two samples paired comparison. Please provide an appropriate model. Current model includes following samples: ', paste(sample, collapse=', '), '."', sep=''), verbose, TRUE) } x <- data[data[, model] == samples[1], model] y <- data[data[, model] == samples[2], model] if (length(x) != length(y)) { PrintMsg(paste('"error":"Paired comparison in Wilcoxon signed rank test is possible only if samples have same size. Please provide an appropriate model, correct the map or use different statistics. Current samples: ', samples[1], '(n=', length(x), '), ', samples[2], '(n=', length(y), ')."', sep=''), verbose, TRUE) } wilcox.output <- wilcox.test(formula=as.formula(formula), data=data, paired=TRUE) PrintMsg(paste('"description":"Performed Wilcoxon signed rank test using model: ', model, '. Paires: ', paste(paste(row.names(data)[data[, model] == samples[1]], row.names(data)[data[, model] == samples[2]], sep='-'), collapse=', '), '."', sep=''), verbose) summary <- data.frame(row.names=paste(samples, collapse='-'), p.value=wilcox.output$p.value) pairwise.output <- list() pairwise.output[[model]] <- as.data.frame(wilcox.output$p.value) names(pairwise.output[[model]]) <- model row.names(pairwise.output[[model]]) <-paste(samples, collapse='-') } else if (method == 'ttest') { # PARAMETRIC, ACCEPTS MODELS WITH 2 FACTORS ONLY AND ONE FIXED VARIABLES. # MODEL HAS TO BE OF THE FORM "a" if (length(term) > 1) { PrintMsg(paste('"error":"Model cannot include more than 1 variable when using t-test. Please provide an appropriate model. Current model: ', model, ' (', length(term), ' variables)."', sep=''), verbose, TRUE) } samples <- unique(unlist(data[, model])) if (length(samples) != 2) { PrintMsg(paste('"error":"T-test can only perform two samples comparison. Please provide an appropriate model. Current model includes following samples: ', paste(sample, collapse=', '), '."', sep=''), verbose, TRUE) } ttest.output <- t.test(formula=as.formula(formula), data=data) PrintMsg(paste('"description":"Performed paired t-test using model: ', model, '."', sep=''), verbose) summary <- data.frame(row.names=paste(samples, collapse='-'), p.value=ttest.output$p.value) pairwise.output <- list() pairwise.output[[model]] <- as.data.frame(ttest.output$p.value) names(pairwise.output[[model]]) <- model row.names(pairwise.output[[model]]) <-paste(samples, collapse='-') } else if (method == 'ttest_paired') { # PARAMETRIC, ACCEPTS MODELS WITH 2 FACTORS ONLY AND ONE FIXED VARIABLES. NESTING IS ASSUMED FROM THE ORDER OF THE DATA! # MODEL HAS TO BE OF THE FORM "a" if (length(term) > 1) { PrintMsg(paste('"error":"Model cannot include more than 1 variable when using t-test. Please provide an appropriate model. Current model: ', model, ' (', length(term), ' variables)."', sep=''), verbose, TRUE) } samples <- unique(unlist(data[, model])) if (length(samples) != 2) { PrintMsg(paste('"error":"Paired t-test can only perform two samples comparison. Please provide an appropriate model. Current model includes following samples: ', paste(sample, collapse=', '), '."', sep=''), verbose, TRUE) } x <- data[data[, model] == samples[1], model] y <- data[data[, model] == samples[2], model] if (length(x) != length(y)) { PrintMsg(paste('"error":"Paired comparison in t-test is possible only if samples have same size. Please provide an appropriate model, correct the map or use different statistics. Current samples: ', samples[1], '(n=', length(x), '), ', samples[2], '(n=', length(y), ')."', sep=''), verbose, TRUE) } ttest.output <- t.test(formula=as.formula(formula), data=data, paired=TRUE) PrintMsg(paste('"description":"Performed paired t-test using model: ', model, '. Paires: ', paste(paste(row.names(data)[data[, model] == samples[1]], row.names(data)[data[, model] == samples[2]], sep='-'), collapse=', '), '."', sep=''), verbose) summary <- data.frame(row.names=paste(samples, collapse='-'), p.value=ttest.output$p.value) pairwise.output <- list() pairwise.output[[model]] <- as.data.frame(ttest.output$p.value) names(pairwise.output[[model]]) <- model row.names(pairwise.output[[model]]) <-paste(samples, collapse='-') } stat <- list(summary=summary, pairwise=pairwise.output) return (stat) } ################ # Plot rarefaction curves # Args: # (1) data (numeric matrix) rarefaction table # Output to device: # A figure ################ PlotRarefactionCurves <- function(data=NULL, xlab='Rarefaction depth', ylab='') { color.palette <- rainbow(ncol(data)) plot(x=row.names(data), y=c(1:nrow(data)), type="n", ylim=c(min(data), max(data)), xlab=xlab, ylab=ylab) for (i in 1:ncol(data)) { lines(x=row.names(data), y=data[, i], col=color.palette[i]) } return () } ################ # Perform a diversity analysis # Args: # (1) table (numeric dataframe) Count table # (2) map (data-frame) Experimental design table with nrow=ncol(table) # (3) fun (character) The diversity function to use # (4) model (formula vector) A formula representing the model for stats. Terms # must refer to names(map) # (5) json (character) Json string specifying packages # (6) verbose (character) Print messages? see PrintMsg() options # (7) graphical (logical) Generate graphics? # Returns: # Figure data as a json string # Output to device: # A figure # Prints to stdout: # A description of the function ################ AnalyseDiversity <- function(table=NULL, map=NULL, fun=c('richness', 'shannon'), nrar=20, compare_diversity=FALSE, stats=NULL, model=NULL, json=libJson, verbose=TRUE, graphical=TRUE) { if (is.null(fun)) { fun <- 'richness' } suppressMessages(library('vegan')) fun <- fun[fun != ''] # Compute rarefaction curves for diversity colsums <- colSums(table) min.colsum <- floor(min(colsums)) ncol <- ncol(table) sample <- matrix(rep(floor(seq(from=0, to=min.colsum, length.out=nrar)), ncol), ncol=ncol, byrow=FALSE) diversity <- list() map <- FilterMap(map=map, model=model) rarefied.table <- list() for (i in 1:nrar) { rarefied.table[[i]] <- rrarefy(t(table), sample=apply(rbind(sample[i, ], colsums), 2, min)) } PrintMsg(paste('"description":"Generated rarefactions to compute diversity until ', min.colsum, ' counts per sample"', sep=''), verbose) data.json <- c(1:length(fun)) for (i in 1:length(fun)) { div <- matrix(ncol=ncol, nrow=0) v <- TRUE for (j in 1:nrar) { div <- rbind(div, ComputeDiversity(table=t(rarefied.table[[j]]), fun=fun[i], json=json, verbose=v)) v <- FALSE } colnames(div) <- colnames(table) row.names(div) <- sample[, 1] diversity[[i]] <- as.matrix(div) # Build figures if (graphical == TRUE) { PlotRarefactionCurves(div, ylab=fun[i]) } # Calculate statistics if (compare_diversity == TRUE) { PrintMsg(paste('"description":"Compared diversity between groups at a rarefaction depth of ', min.colsum, ' counts per sample"', sep=''), verbose) p.value <- list() for (j in 1:length(model)) { if (length(stats) == length(model)) { method <- stats[j] } else { method <- stats[1] } stat <- ComputeStats(response=div[nrar, ], map=map, method=method, model=model[j], pairwise=FALSE, verbose=TRUE)$summary p <- as.data.frame(t(as.matrix(stat$p.value))) names(p) <- row.names(stat) p <- as.data.frame(p[, names(p) != 'Residuals']) # the following is usefull in case model contains only one element names(p) <- row.names(stat)[row.names(stat) != 'Residuals'] row.names(p) <- fun[i] p.value[[j]] <- p } } else { p.value <- NULL } data.json[i] <- paste('"', fun[i], '":', diversity2json(diversity[[i]], p.value, map), sep='') } return (paste('{', paste(data.json, collapse=','), '}', sep='')) } ################ # Permutational Multivariate Analysis of Variance Using Distance Matrices # Args: # (1) model (character) # (2) map (data-frame) Experimental design table with nrow=ncol(table) # (3) fun (character) The distance function to use # (4) graphical (logical) Generate graphics? # Returns: # A summary (data-frame) # Prints to stdout: # A description of the function ################ Adonis <- function(table=NULL, model=NULL, strata=NULL, map=NULL, fun='bray', graphical=TRUE) { if (!is.null(model)) { formula <- paste('table ~ ', model, sep='') } else { formula <- paste('table ~ ', names(map), sep='*') } if (!is.null(strata)) { if (strata == '') { strata == NULL } } adonis.output <- adonis(formula=as.formula(formula), data=as.data.frame(map), strata=strata, method=fun, permutations=1000) summary <- data.frame(row.names=row.names(adonis.output$aov.tab)) summary <- data.frame(row.names=gsub('[[:space:]]', '', row.names(adonis.output$aov.tab)), p.value=adonis.output$aov.tab[, 'Pr(>F)'], explained=adonis.output$aov.tab[, 'R2']) summary <- summary[row.names(summary) != 'Total', ] # Build the figure if (graphical == TRUE) { pie(summary$explained, labels=row.names(summary), col=rainbow(length(summary$explained)), init.angle=0, radius=0.7, cex=0.7, xpd=TRUE) legend(x=-1, y=-1, xjust=1, yjust=0.5, title='p-value', legend=round(summary$p.value, digits=4), fill=rainbow(nrow(summary)), cex=0.7, xpd=TRUE) } return (summary) } ################ # Permutational Multivariate Analysis of Variance Using Distance Matrices # Args: # (1) table (numeric dataframe) Count table # (2) map (data-frame) Experimental design table with nrow=ncol(table) # (3) fun (character) The distance function to use # (4) model (character vector) A formula representing the model for stats. Terms # must refer to names(map) # must refer to names(map) # (5) verbose (character) Print messages? see PrintMsg() options # (6) graphical (logical) Generate graphics? # Returns: # Figure data as a json string # Output to device: # A figure # Prints to stdout: # A description of the function ################ PerformAdonis <- function(table=NULL, map=NULL, fun='bray', model=NULL, strata=NULL, json=libJson, verbose=TRUE, graphical=TRUE) { suppressMessages(library('vegan')) #out.json <- c(1:length(fun)) #for (k in 1:length(fun)) { spec <- as.data.frame(do.call('rbind', json$distance)) spec <- spec[spec$value == fun, ] package <- '' pkg <- unlist(strsplit(as.character(unlist(spec$pkg)), split=',')) if (length(pkg) > 0) { package <- paste(' (R package {', unlist(spec$pkg), '})', sep='') } PrintMsg(paste('"description":"Performed a permutational multivariate analysis of variance based on the ', spec$label, package, ' using the Adonis method (R package {vegan})."', sep=''), verbose) # Correct for negative entries by adding a scalar (Caillez correction method) if (min(table) < 0) { c <- abs(min(table)) table <- table+c PrintMsg(paste('"description":"Corrected for negative values using a Caillez correction (', c, ' added to all values)."', sep=''), verbose) } if (is.null(strata) || strata == '') { strata <- rep(NULL, length(model)) } if (!is.null(strata) && length(strata) != length(model)) { PrintMsg(paste('"warning":"Number of strata (', length(strata), ') differs from number of models(', length(model), '). No strata will be used."', sep=''), verbose) strata <- rep(NULL, length(model)) } # Perform perMANOVA map <- FilterMap(map=map, model=model) data.json <- c(1:length(model)) for (i in 1:length(model)) { stat <- Adonis(table=t(table), model=model[i], strata=strata[i], map=map, fun=fun, graphical=graphical) stat.json <- c(1:nrow(stat)) for (j in 1:nrow(stat)) { stat.json[j] <- paste('{"name":"', row.names(stat)[j], '","p-value":"', stat$p.value[j], '","explained":"', stat$explained[j], '"}', sep='') } data.json[i] <- paste('"', model[i], '":[', paste(stat.json, collapse=','), ']', sep='') } #out.json[k] <- paste('"', # fun[k], # '":"{', # paste(data.json, collapse=','), # '}', # sep='') #} #return (paste('{',paste(out.json, collapse=','), '}', sep='')) return (paste('{',paste(data.json, collapse=','), '}', sep='')) } ################ # Principal Component Analysis (PCA) # Args: # (1) table (numeric dataframe) Count table # (2) map (data-frame) Experimental design table with nrow=ncol(table) # (3) model (character vector) A formula representing the model for stats. Terms # must refer to names(map) # (4) col (character) Name of a column in map to use to define colors # (5) biplot (numeric) Show n=biplot factors with biggest norms on the # biplot # (6) verbose (character) Print messages? see PrintMsg() options # (7) graphical (logical) Generate graphics? # Returns: # Figure data as a json string # Output to device: # A figure # Prints to stdout: # A description of the function ################ PerformPCA <- function(table=NULL, map=NULL, biplot=TRUE, verbose=TRUE, graphical=TRUE) { suppressMessages(library('FactoMineR')) ncp <- min(max(2, ncol(table)), 5) pca.output <- PCA(t(table), ncp=ncp, scale.unit=FALSE, graph=FALSE) if (graphical == TRUE) { plot(pca.output, new.plot=FALSE) } # Pack data into a json string data.ind <- as.data.frame(t(as.data.frame(pca.output$ind$coord))) row.names(data.ind) <- gsub('Dim.', 'PC', row.names(data.ind)) data.json <- Dataframe2DataJson(data=data.ind, xmetadata=map) eig.json <- c(1:ncp) for (i in 1:ncp) { eig.json[i] <- paste('"PC', i, '":"', pca.output$eig[i, 2], '"', sep='') } if (biplot == TRUE) { - data.var <- t(as.data.frame(pca.output$var$coord)) + data.var <- as.data.frame(t(as.data.frame(pca.output$var$coord))) row.names(data.var) <- gsub('Dim.', 'PC', row.names(data.var)) data.json <- paste('{"ind":', data.json, ',"var":', Dataframe2DataJson(data=data.var), ',"eig":{', paste(eig.json, collapse=','), '}}', sep='') } else { data.json <- paste('{"data":', data.json, ',"eig":{', paste(eig.json, collapse=','), '}}', sep='') } PrintMsg('"description":"Performed principal component analysis (PCA) (R package {FactoMineR})."', verbose) return (data.json) } ################ # Correspondence Analysis (CA) # Args: # (1) table (numeric dataframe) Count table # (2) map (data-frame) Experimental design table with nrow=ncol(table) # (3) model (character vector) A formula representing the model for stats. Terms # must refer to names(map) # (4) col (character) Name of a column in map to use to define colors # (5) biplot (numeric) Show n=biplot factors with biggest norms on the # biplot # (6) verbose (character) Print messages? see PrintMsg() options # (7) graphical (logical) Generate graphics? # Returns: # Figure data as a json string # Output to device: # A figure # Prints to stdout: # A description of the function ################ PerformCA <- function(table=NULL, map=NULL, verbose=TRUE, graphical=TRUE) { suppressMessages(library('FactoMineR')) ncp <- min(max(2, ncol(table)), 5) ca.output <- CA(table, ncp=ncp, graph=FALSE) if (graphical == TRUE) { plot(ca.output, new.plot=FALSE) } # Pack data into a json string data.ind <- as.data.frame(t(as.data.frame(ca.output$col$coord))) data.json <- Dataframe2DataJson(data=data.ind, xmetadata=map) eig.json <- c(1:ncp) for (i in 1:ncp) { eig.json[i] <- paste('"Dim', i, '":"', ca.output$eig[i, 2], '"', sep='') } data.var <- t(as.data.frame(ca.output$row$coord)) data.json <- paste('{"ind":', data.json, ',"var":', Dataframe2DataJson(data=data.var), ',"eig":{', paste(eig.json, collapse=','), '}}', sep='') PrintMsg('"description":"Performed correspondence analysis (CA) (R package {FactoMineR})."', verbose) return (data.json) } ################ # Principal Coordinates Analysis (PCoA) # Args: # (1) table (numeric dataframe) Count table # (2) map (data-frame) Experimental design table with nrow=ncol(table) # (3) fun (character) The distance function to use # (4) model (character vector) A formula representing the model for stats. Terms # must refer to names(map) # (5) col (character) Name of a column in map to use to define colors # (6) json (character) Json string specifying packages # (7) verbose (character) Print messages? see PrintMsg() options # (8) graphical (logical) Generate graphics? # Returns: # Figure data as a json string # Output to device: # A figure # Prints to stdout: # A description of the function ################ PerformPCoA <- function(table=NULL, map=NULL, fun='bray', json=libJson, verbose=TRUE, graphical=TRUE) { spec <- as.data.frame(do.call('rbind', json$distance)) spec <- spec[spec$value == fun, ] package <- '' pkg <- unlist(strsplit(as.character(unlist(spec$pkg)), split=',')) if (length(pkg) > 0) { for (i in 1:length(pkg)) { suppressMessages(library(pkg[i], character.only=TRUE)) } package <- paste(' (R package {', unlist(spec$pkg), '})', sep='') } dist <- as.matrix(ComputeDistance(table=Transpose(table), fun=fun, json=json, verbose=FALSE)) row.names(dist) <- names(table) data.json <- PerformPCA(table=dist, map=map, biplot=FALSE, verbose=FALSE, graphical=graphical) PrintMsg(paste('"description":"Performed principal coordinate analysis (PCoA) (R package {FactoMineR}) based on ', spec$label, package, '."', sep=''), verbose) return (data.json) } ################ # Canonical Correlation Analysis (CCA) # Args: # (1) table (numeric dataframe) Count table # (2) map (data-frame) Experimental design table with nrow=ncol(table) # (3) model (character vector) A formula representing the model for stats. Terms # must refer to names(map) # (4) col (character) Name of a column in map to use to define colors # (5) biplot (numeric) Show n=biplot factors with biggest norms on the # biplot # (6) verbose (character) Print messages? see PrintMsg() options # (7) graphical (logical) Generate graphics? # Returns: # Figure data as a json string # Output to device: # A figure # Prints to stdout: # A description of the function ################ PerformCCA <- function(table=NULL, map=NULL, column=NULL, verbose=TRUE, graphical=TRUE) { suppressMessages(library('vegan')) table <- as.data.frame(t(table)) names <- unlist(strsplit(column, split=',')) categs <- as.data.frame(map[, names]) names(categs) <- names row.names(categs) <- row.names(map) cca.output <- cca(table ~ ., data=categs) if (graphical == TRUE) { plot(cca.output) } # Pack data into a json string data.ind <- as.data.frame(t(cca.output$CCA$u)) data.var <- as.data.frame(t(cca.output$CCA$v)) data.biplot <- as.data.frame(t(cca.output$CCA$biplot)) data.json <- paste('{"ind":', Dataframe2DataJson(data=data.ind, xmetadata=map), ',"var":', Dataframe2DataJson(data=data.var), ',"biplot":', Dataframe2DataJson(data=data.biplot), '}', sep='') PrintMsg(paste('"description":"Performed a canonical correspondence analysis (CCA) (R package {vegan}) with the following constraining variable(s):', column, '."', sep=''), verbose) return (data.json) } ################ # Compute fold-change # Args: # (1) response (numeric vector) Data # (2) factor (vector) Experimental design vector with # nrow=length(response) # (3) by (dataframe) A nested variable to use for calculating # fold-changes # (4) balanced (bool) assume that model balanced? If NULL, balance will be tested # (5) verbose (character) Print messages? see PrintMsg() options # Returns: # A data-frame containing fold-changes for each variable in model # Prints to stdout: # A description of the function ################ ComputeFoldChange <- function(group1=NULL, group2=NULL, names=NULL, verbose=TRUE) { out <- list() out[['logfc']] <- log2(mean(group2))-log2(mean(group1)) out[['ab']] <- mean(c(group2, group1)) PrintMsg(paste('"description":"Calculated fold-change as log2(mean(', names[2], '))-log2(mean(', names[1], ')) and abundance as (mean(', names[1], ')+mean(', names[2], '))/2."', sep=''), verbose) return(out) } ################ # Extract group of samples from map # Args: # (1) map (data-frame) Experimental design table with nrow=ncol(table) # Returns: # Prints to stdout: ################ ExtractGroupFromMap <- function(map=NULL, column=NULL, group=NULL, verbose) { map <- as.data.frame(FilterMap(map=map, model=column)) group <- unlist(strsplit(group, split=':')) if (ncol(map) != length(column)) { PrintMsg('"error":"Unable to determine groups for fold-change analysis. Make sure no - or : character is present in the mapping file."' , verbose) } keep <- 1 for (i in 1:ncol(map)) { keep <- keep*(map[, i] == group[i]) } return(keep) } ################ # Analyse changes (fold-changes, p-values etc.) # Args: # (1) table (numeric dataframe) Count table # (2) map (data-frame) Experimental design table with nrow=ncol(table) # (3) model (character vector) A formula representing the model for stats. Terms # must refer to names(map) # (4) json (character) Json string specifying packages # (5) verbose (character) Print messages? see PrintMsg() options # Returns: # Figure data as a json string # Prints to stdout: # A description of the function ################ AnalyseChange <- function(table=NULL, map=NULL, stats=NULL, model=NULL, json=libJson, verbose=TRUE, graphical=FALSE) { PrintMsg('"description":"Analysed changes (fold-changes and p-values)."', verbose) # Calculate statistics if (is.null(model)) { model <- paste(names(map), sep='+') } nmodel <- length(model) data.json <- c(1:nmodel) for (i in 1:nmodel) { if (length(stats) != length(model)) { method = stats[1] } else { method = stats[i] } nrow <- nrow(table) effect.json <- list() v <- verbose graphic.data <- list() for (j in 1:nrow) { stat <- ComputeStats(response=as.vector(unlist(table[j, ])), map=map, method=method, model=model[i], pairwise=TRUE, verbose=v)$pairwise effect.names <- names(stat) neffect <- length(effect.names) for (k in 1:neffect) { if (j == 1 && k == 1) { graphic.data[[effect.names[k]]] <- list() } p <- unlist(stat[[k]][, ncol(stat[[k]])]) if (length(effect.json) == 0) { effect.json[[effect.names[k]]] <- list() } comp.names <- row.names(stat[[k]]) ncomp <- length(comp.names) for (l in 1:ncomp) { if (j == 1 && l == 1) { graphic.data[[effect.names[k]]][[comp.names[l]]] <- data.frame(logfc=c(1:nrow), ab=c(1:nrow)) row.names(graphic.data[[effect.names[k]]][[comp.names[l]]]) <- row.names(table) } if (length(effect.json[[effect.names[k]]]) == 0) { effect.json[[effect.names[k]]][[comp.names[l]]] <- list() effect.json[[effect.names[k]]][[comp.names[l]]] <- c(1:nrow) } group.names <- unlist(strsplit(row.names(stat[[k]])[l], split='-')) if (length(group.names) != 2) { PrintMsg('"error":"Unable to determine groups for fold-change analysis. Make sure no - or : character is present in the mapping file."', verbose) } group1 <- ExtractGroupFromMap(map=map, column=names(stat)[k], group=group.names[1], verbose) group2 <- ExtractGroupFromMap(map=map, column=names(stat)[k], group=group.names[2], verbose) fc <- ComputeFoldChange(as.vector(unlist(table[j, group1 == 1])), as.vector(unlist(table[j, group2 == 1])), names=group.names, verbose=v) effect.json[[effect.names[k]]][[comp.names[l]]][j] <- paste('{"mean abundance":"', fc$ab, '","log2(fold-change)":"', fc$logfc, '","-log2(p-value)":"', -log2(p[l]), '"}', sep='') graphic.data[[effect.names[k]]][[comp.names[l]]]$logfc[j] <- fc$logfc graphic.data[[effect.names[k]]][[comp.names[l]]]$ab[j] <- fc$ab } } v <- FALSE } for (k in 1:length(effect.json)) { for (l in 1:length(effect.json[[k]])) { effect.json[[k]][[l]] <- paste('"', names(effect.json[[k]])[l], '":[', paste(effect.json[[k]][[l]], collapse=','), ']', sep='') if (graphical == TRUE) { plot(graphic.data[[k]][[l]]$ab, graphic.data[[k]][[l]]$logfc, main=names(effect.json[[k]])[l], xlab='mean abundance', ylab='log2(fold-change)') } } effect.json[[k]] <- paste('"', names(effect.json)[k], '":{', paste(effect.json[[k]], collapse=','), '}', sep='') } data.json[i] <- paste('"', model[i], '":{', paste(effect.json, collapse=','), '}', sep='') } return(paste('{"data":{', paste(data.json, collapse=','), '},"names":["', paste(row.names(table), collapse='","'), '"]}', sep='')) } ################ # Build a heatmap of abundances # Args: # (1) table (numeric dataframe) Count table # (2) map (data-frame) Experimental design table with nrow=ncol(table) # (3) model (character vector) A formula representing the model for stats. Terms # must refer to names(map) # (4) metadata (numeric dataframe) Metadata table # (5) fun (character) Function to use in hierarchical clustering # (6) json (character) Json string specifying packages # (7) verbose (character) Print messages? see PrintMsg() options # (8) graphical (logical) Generate graphics? # Returns: # Figure data as a json string # Output to device: # A figure # Prints to stdout: # A description of the function ################ BuildHeatMap <- function(table=NULL, map=NULL, stats='anova', model=NULL, metadata=NULL, fun='spearman', json=libJson, verbose=TRUE, graphical=TRUE) { suppressMessages(library('gplots')) PrintMsg('"description":"Build a heatmap of proportions."', verbose=verbose) # Check that there is no constant data RemoveConstantRows(table=table, verbose=TRUE) if ((nrow(table) < 3) || (ncol(table) < 3)) { PrintMsg('"error":"Not enough data to draw a heatmap (min 3 rows and 3 columns)."', TRUE, TRUE) } # Center/scale by row data <- as.data.frame(Transpose(apply(table, 1, cScale))) names(data) <- names(table) row.names(data) <- row.names(table) PrintMsg('"description":"Scale on standard deviation and center on mean abundance per row (variables)."', verbose=verbose) # keep only columns of map that are used in the model map <- FilterMap(map=map, model=model) # Set function for hierarchical clustering DistFun <- function(x) { return (ComputeDistance(table=x, fun=fun, json=json, verbose=FALSE)) } if (graphical == FALSE) { pdf(file = NULL) } # Set the color palette col <- colorRampPalette(c("navy", "seashell", "red2"))(n=51) heatmap.out <- heatmap.2(as.matrix(data), scale='row', na.rm=TRUE, Rowv=TRUE, Colv=TRUE, dendrogram='both', #distfun=DistFun, col=col, symm=FALSE, symkey=FALSE, key=TRUE, keysize=1.5, density.info='density', trace='none', labCol=names(data), labRow=row.names(data), cexCol=0.2+1/log10(ncol(data)), mar=c(5, 10)) if (graphical == FALSE) { dev.off() } # Order data according to dendrogram data <- data[heatmap.out$rowInd, ] data <- data[, heatmap.out$colInd] names <- names(map) map <- as.data.frame(map[heatmap.out$colInd, ]) names(map) <- names if (!is.null(metadata)) { metadata <- metadata[, heatmap.out$colInd] } # Store heatmap data into a json string ncol <- ncol(data) heatmap.json <- c(1:ncol) data.names <- names(data) for (i in 1:ncol) { heatmap.json[i] <- paste('{"name":"', data.names[i], '","value":["', paste(data[, i], collapse='","'), '"]}', sep='') } # Add categories to topbar topbar.json <- list() ncateg <- ncol(map) category.json <- c(1:ncateg) categ.names <- names(map) for (i in 1:ncateg) { category.json[i] <- paste('{"name":"', categ.names[i], '","value":["', paste(map[, i], collapse='","'), '"]}', sep='') } topbar.json[['category']] <- paste('"category":[', paste(category.json, collapse=','), ']', sep='') # Add p-values to sidebar sidebar.json <- list() if (is.null(model)) { model <- paste(names(map), sep='+') } nmodel <- length(model) stat.json <- c(1:nmodel) for (i in 1:nmodel) { if (length(stats) != length(model)) { method = stats[1] } else { method = stats[i] } nrow <- nrow(data) v <- verbose p.value <- list() for (j in 1:nrow) { stat <- ComputeStats(response=as.vector(unlist(data[j, ])), map=map, method=method, model=model[i], pairwise=FALSE, verbose=TRUE)$summary stat.rownames <- row.names(stat)[row.names(stat) != 'Residuals'] stat <- as.data.frame(stat[row.names(stat) != 'Residuals', ]) nstat <- nrow(stat) for (k in 1:nstat) { if (is.null(p.value[[stat.rownames[k]]])) { p.value[[stat.rownames[k]]] <- c(1:nrow) } p.value[[stat.rownames[k]]][j] <- stat[k, ] } v <- FALSE } for (j in 1:length(p.value)) { p.value[[j]] <- paste('{"name":"', names(p.value)[j], '","value":["', paste(p.value[[j]], collapse='","'), '"]}', sep='') } stat.json[i] <- paste('"', model[i], '":[', paste(p.value, collapse=','), ']', sep='') } sidebar.json[['p-values']] <- paste('"p-values":{', paste(stat.json, collapse=','), '}', sep='') # Add correlations with metadata to sidebar if (! is.null(metadata)) { PrintMsg('"description":"Calculate Spearman rank correlation with metadata."', verbose=verbose) correlation <- as.data.frame(cor(x=t(data), y=t(metadata), use='na.or.complete', method ="spearman")) ncor <- ncol(correlation) cor.json <- c(1:ncor) for (i in 1:ncor) { cor.json[i] <- paste('{"name":"', names(correlation)[i], '","value":["', paste(correlation[, i], collapse='","'), '"]}', sep='') } sidebar.json[['correlations']] <- paste('"correlations":[', paste(cor.json, collapse=','), ']', sep='') } data.json <- paste('{"heatmap":[', paste(heatmap.json, collapse=','), '],"colnames":["', paste(names(data), collapse='","'), '"],"rownames":["', paste(row.names(data), collapse='","'), '"],"topbar":{', paste(topbar.json, collapse=','), '},"sidebar":{', paste(sidebar.json, collapse=','), '},"colDendrogram":', dendrogram2json(heatmap.out$colDendrogram)[[1]], ',"rowDendrogram":', dendrogram2json(heatmap.out$rowDendrogram)[[1]], '}', sep='') return (data.json) } ################ # Get the groups with the highest mean # Args: # (1) data (numeric vector) The data # (2) map (data-frame) Experimental design table with nrow=length(data) # (3) model (character) A formula representing the model for stats. Terms # must refer to names(map) # (4) verbose (character) Print messages? see PrintMsg() options # Returns: # A character vector with length=numer of terms in model # Prints to stdout: # A description of the function ################ getGroupWithHighestMean <- function(data=NULL, map=NULL, column=NULL, verbose=TRUE) { PrintMsg('"description":"Determine groups with highest mean abundance."', verbose=verbose) map <- as.data.frame(map[, unlist(strsplit(column, split=':'))]) if (ncol(map) == 1) { names(map) <- column } group <- as.data.frame(unique(map)) if (ncol(group) == 1) { names(group) <- column } mean <- c(1:nrow(group)) for (i in 1:nrow(group)) { keep <- 1 for (j in 1:ncol(map)) { keep <- keep*(map[, j] %in% group[i, names(map)[j]]) } mean[i] <- mean(unlist(data[as.logical(keep)])) } out <- list() out$value <- max(mean) out$id <- paste(unlist(group[which(mean == max(mean)), ]), collapse=':') out$group <- apply(group, 1, paste, collapse=':') return (out) } ################ # Build a network of correlations for variables # Args: # (1) table (numeric dataframe) Count table # (2) map (data-frame) Experimental design table with nrow=ncol(table) # (3) model (character) A formula representing the model for stats. Terms # must refer to names(map) # (4) metadata (numeric dataframe) Metadata table # (5) fun (character) Distance, correlation or similarity function # (6) json (character) Json string specifying packages # (7) verbose (character) Print messages? see PrintMsg() options # Returns: # Figure data as a json string # Prints to stdout: # A description of the function ################ BuildCorrelationNetwork <- function(table=NULL, map=NULL, stats='anova', model=NULL, metadata=NULL, fun='spearman', json=libJson, verbose=TRUE) { spec <- as.data.frame(do.call('rbind', json$correlation)) spec <- spec[spec$value == fun, ] pkg <- unlist(strsplit(as.character(unlist(spec$pkg)), split=',')) package <- '' if (length(pkg) > 0) { for (i in 1:length(pkg)) { suppressMessages(library(pkg[i], character.only=TRUE)) } package <- paste(' (R package {', unlist(spec$pkg), '})', sep='') } PrintMsg(paste('"description":"Build network of variables based on ', spec$label, package, '."', sep=''), verbose=verbose) suppressMessages(library(igraph)) # keep only columns of map that are used in the model map <- FilterMap(map=map, model=model) # Combine with metadata data <- rbind(table, metadata) data.type <- rep('data', nrow(data)) if (!is.null(metadata)) { data.type[(nrow(table)+1):nrow(data)] <- "metadata" } # Check that there is no constant data RemoveConstantRows(table=data, verbose=TRUE) if ((nrow(data) < 3) || (ncol(data) < 3)) { PrintMsg('"error":"Not enough data to compute a network (min 3 rows and 3 columns)."', TRUE, TRUE) } # Calculate the correlation matrix weight <- ComputeCorrelation(table=data, fun=fun, test=TRUE, json=json, verbose=FALSE) nmodel <- length(model) nnodes <- nrow(data) node.json <- c(1:nnodes) link.json <- c(1:(nnodes*(nnodes/2-1))) l <- 1 legend.json <- list() for (i in 1:nnodes) { model.json <- c(1:nmodel) for (j in 1:nmodel) { stat <- ComputeStats(response=as.vector(unlist(data[i, ])), map=map, method=stats, model=model[j], pairwise=FALSE, verbose=FALSE)$summary neffect <- length(row.names(stat)[row.names(stat) != 'Residuals']) effect.json <- c(1:neffect) if (length(legend.json[[model[j]]]) == 0) { legend.json[[model[j]]] <- c(1:neffect) } for (k in 1:neffect) { gwhm <- getGroupWithHighestMean(data=data[i, ], map=map, column=row.names(stat)[k], verbose=FALSE) effect.json[k] <- paste('"', row.names(stat)[k], '":{"p-value":"', stat[k, ], '","highest-mean":"', gwhm$id, '"}', sep='') if (i == 1) { legend.json[[model[j]]][k] <- paste('"', row.names(stat)[k], '":["', paste(gwhm$group, collapse='","'), '"]', sep='') } } model.json[j] <- paste('"', model[j], '":{', paste(effect.json, collapse=','), '}', sep='') if (i == 1) { legend.json[[model[j]]] <- paste('"', model[j], '":{', paste(legend.json[[model[j]]], collapse=','), '}', sep='') } } node.json[i] <- paste('{"id":"', i-1, '","name":"', row.names(data)[i], '","data-type":"', data.type[i], '","stat":{', paste(model.json, collapse=','), '},"mean":"', mean(unlist(data[i, ])), '","min":"', min(unlist(data[i, ])), '","max":"', max(unlist(data[i, ])), '"}', sep='') if (i < nnodes) { for (j in (i+1):nnodes) { link.json[l] <- paste('{"source":"', i-1, '","target":"', j-1, '","weight":"', weight$estimate[i, j], '","p-value":"', weight$p.value[i, j], '"}', sep='') l <- l+1 } } } return (paste('{"nodes":[', paste(node.json, collapse=','), '],"links":[', paste(link.json, collapse=','), '],"legend":{', paste(unlist(legend.json), collapse=','), '}}', sep='')) } ################ # Build a network of similarity for samples # Args: # (1) tables (list of numeric dataframes) # (2) map (data-frame) Experimental design table with nrow=ncol(table) # (3) model (character) A formula representing the model for stats. Terms # must refer to names(map) # (4) funs (list of characters) Distance or similarity functions # (5) clust (character) Network clustering algorithm # (6) json (character) Json string specifying packages # (7) verbose (character) Print messages? see PrintMsg() options # (8) lib (character) Path to holynet library # Returns: # List [1] json string (fusion network) [2] data-frame (clusters) # Prints to stdout: # A description of the function ################ BuildSimilarityNetwork <- function(table=NULL, map=NULL, funs=NULL, clust="walktrap", clust.names=NULL, json=libJson, verbose=TRUE, lib='holynetlib.R') { source(lib) spec <- as.data.frame(do.call('rbind', json$distance)) for (i in 1:length(table)) { fun <- funs[1] if ((length(funs) == length(table)) && (length(funs) > 1)) { fun <- funs[i] } spec <- spec[spec$value == fun, ] pkg <- unlist(strsplit(as.character(unlist(spec$pkg)), split=',')) package <- '' if (length(pkg) > 0) { for (j in 1:length(pkg)) { suppressMessages(library(pkg[j], character.only=TRUE)) } package <- paste(' (R package {', unlist(spec$pkg), '})', sep='') } PrintMsg(paste('"description":"Build similarity network based on ', spec$label, package, '."', sep=''), verbose=verbose) } # Calculate similarity matrices mat <- list() for (i in 1:length(table)) { fun <- funs[1] if (length(funs) == length(table)) { fun <- funs[i] } mat[[i]] <- CreateAdjacencyMatrix(CreateSimilarityMatrix(table[[i]], fun)) } # If multiple matrices exist, fuse them if (length(mat) > 1) { fusmat <- FuseMatrices(mat) if (is.null(fusmat)) { PrintMsg('"error":"Trying to fuse matrices but dimensions are not matching."', TRUE, TRUE) } mat[[(length(mat)+1)]] <- fusmat } out <- list() # Compute clustering on similarity matrices if (!is.null(clust) && (clust != 'none')) { spec <- as.data.frame(do.call('rbind', json$graph_clustering)) spec <- spec[spec$value == clust, ] pkg <- unlist(strsplit(as.character(unlist(spec$pkg)), split=',')) package <- '' if (length(pkg) > 0) { for (i in 1:length(pkg)) { suppressMessages(library(pkg[i], character.only=TRUE)) } package <- paste(' (R package {', unlist(spec$pkg), '})', sep='') } PrintMsg(paste('"meassage":"Apply clustering using the ', spec$label, package, '."', sep=''), verbose=verbose) clusters <- matrix(ncol=length(mat), nrow=nrow(mat[[1]])) for (i in 1:length(mat)) { clusters[, i] <- paste(clust, as.character(ApplyNetworkClustering(CreateNetwork(mat[[i]]), clust_method=clust)), sep='') } clusters <- as.data.frame(clusters) if (!is.null(clust.names)) { names(clusters) <- clust.names } out[['txt']] <- clusters map <- cbind(map, clusters) } # Pack data into a json string json.data <- buildFusionNetworkJson(mat, map) out[['json']] <- json.data return (out) } ################################# # (\/)(°,,,°)(\/) # ################################# libJson <- ReadJson() diff --git a/lib/genocrunch_console/lib/genocrunchlib.py b/lib/genocrunch_console/lib/genocrunchlib.py index 898a0c7..cb5d8db 100755 --- a/lib/genocrunch_console/lib/genocrunchlib.py +++ b/lib/genocrunch_console/lib/genocrunchlib.py @@ -1,996 +1,1001 @@ # -*- coding: utf-8 -*- #genocrunchlib.py import os, sys, argparse, tarfile from json import load, loads, dump from subprocess import Popen, PIPE from shutil import copyfile, move, rmtree from itertools import izip from collections import OrderedDict from tempfile import mkdtemp, NamedTemporaryFile from random import choice from string import ascii_uppercase, digits from copy import copy from datetime import datetime __dir__ = os.path.dirname(__file__) main_dir = '/'.join(__dir__.split('/')[0:len(__dir__.split('/'))-1]) def Error(f=__file__, msg='', to_rm=[]): print 'Error ['+ os.path.basename(f) +']: '+ msg if len(to_rm) > 0: for t in to_rm: rmtree(t) raise SystemExit(1) def Warning(f=__file__, msg=''): print 'Warning ['+ os.path.basename(f) +']: '+ msg def mkdir(dp=''): """Create a directory if not already present Args: dp Directory path """ if not os.path.exists(dp): os.makedirs(dp) return 0 def rand(n=1): """Generate a random string n Length of the random string """ return ''.join(choice(ascii_uppercase + digits) for _ in range(n)) class Parameters(object): """Parameters retrieved from genocrunchlib.json, from user's json and passed in command line""" def __init__(self, json_fp=''): """Populate the object json_fp Json file path """ # Set authorized arguments for cli argument_parser = argparse.ArgumentParser() argument_parser._action_groups.pop() required_arguments = argument_parser.add_argument_group('required arguments') optional_arguments = argument_parser.add_argument_group('optional arguments') optional_arguments.add_argument('--params_fp', help='Path to a JSON file containing parameters.', nargs=1, type=str) optional_arguments.add_argument('--analysis', help='List of analysis to perform (comma-separated). Valid choices: proportions,diversity,adonis,pca,pcoa,heatmap,change,correlation_network,clustering,similarity_network', nargs=1, default='proportions,pcoa', type=str) required_arguments.add_argument('--output', help='Path to output directory.', nargs=1, type=str) # Define parameters based on the json self.json_fp = json_fp with open(self.json_fp, 'r') as f: self.json_s = load(f) self.fps = [] required = [] for key, value in self.json_s.get('fields').iteritems(): for val in value: if 'scope' not in val.keys() or val.get('scope') != 'form_only': if 'default' in val.keys(): default = val.get('default') else: default=None # Add authorized cli arguments from the json if 'optional' in val.keys() and val.get('optional') == False: required.append(val.get('id')) required_arguments.add_argument('--'+val.get('id'), help=val.get('help'), nargs=1, default=default, type=str) else: optional_arguments.add_argument('--'+val.get('id'), help=val.get('help'), nargs=1, default=default, type=str) if val.get('type') == 'file': self.fps.append(val.get('id')) # Retrieve cli arguments self.params = argument_parser.parse_args().__dict__ # Set params values from user's json if provided (does not overwrite parameters passed in command line) # Careful with booleans as form checkboxes return 1 or nothing... for key, value in self.params.iteritems(): if isinstance(value, list): self.params[key] = value[0] if self.params['params_fp'] is not None and os.path.exists(self.params['params_fp']): with open(self.params.get('params_fp'), 'r') as f: params = load(f) for key, value in params.items(): if '--'+key not in sys.argv[1:]: if value in ['', [], ['']]: self.params[key] = None else: self.params[key] = value # Handle booleans for e in ['prim_rarefaction', 'sec_rarefaction']: if e not in params.keys(): self.params[e] = False elif params[e] in ['', ' ', '0']: self.params[e] = False else: self.params[e] = bool(params[e]) if '--analysis' in sys.argv[1:]: for a in self.params['analysis'].split(','): self.params[a] = 1 self.params.pop('analysis') for key, value in self.params.iteritems(): if value is not None: if isinstance(value, str) and len(value.split(',')) > 1: self.params[key] = value.split(',') elif key in required: Error(msg='argument --'+key+' is requireds') # Check that all files exist missing = [] for fp in self.fps: if self.params[fp] is not None and not os.path.exists(self.params[fp]): missing.append('--'+fp+' '+self.params[fp]) if len(missing) > 0: Error(msg='The following file(s) were not found: '+' ,'.join(missing)) def write(self, fp=''): """Write all parameters to a file fp File path """ with open(fp, 'w') as f: dump(self.params, f, indent=2) def table2R(fp): """Adapt file format for easy loading in R: Remove comments from the top of the file (also empty first element of first line) """ sub = Popen([main_dir+'/bin/modify_table.sh', fp, 'table2R', fp]) sub.wait() def stdoutLog2json(stdout): """Convert stdout to json string""" return loads('[{'+'"},{"'.join(stdout.read().split('""'))+'}]') class Log(object): """A log file""" def __init__(self, fp): self.fp = fp self.data = [] self.update() def update(self): """Write log data to file""" with open(self.fp, 'w') as f: dump(self.data, f, indent=2) self.secure(self.fp+'.bkp') def secure(self, fp=None): """Remove absolute paths from the log""" if fp is None: fp = self.fp path_to_hide = os.path.dirname(self.fp) f = open(self.fp, 'r') content = f.read() f.close() new_content = content.replace(path_to_hide,'') f = open(fp, 'w') f.write(new_content) f.close() def wrapup(self): """Update pending and runing status to failed and update""" f = open(self.fp, 'r') content = f.read() f.close() new_content = content.replace('running','failed').replace('pending','failed') f = open(self.fp, 'w') f.write(new_content) f.close() self.secure(self.fp+'.bkp') class Archive(object): """An archive""" def __init__(self, target_fp='', name=None): self.fp = target_fp self.source = [] self.add(name) def add(self, name=None, update=False, generate=True): """Add files to the archive""" if name is not None: if isinstance(name, list): self.source = list(set(self.source + name)) else: self.source = list(set(self.source + [name])) if update and generate: self.update() elif generate: with tarfile.open(self.fp, "w:gz") as archive: archive.add(name) def update(self): """Update files in archive""" if os.path.exists(self.fp): tmp = NamedTemporaryFile(delete=False, dir=os.path.dirname(self.fp)) os.rename(self.fp, tmp.name) with tarfile.open(self.fp, "w:gz") as archive: for e in self.source: archive.add(e) class Map(object): """Mapping file""" def __init__(self, fp, output): self.fp = output+'/map.txt' copyfile(fp, self.fp) + table2R(self.fp) self.log = [] self.log.append({'name':'file', 'type':'file', 'path':self.fp}) self.log.append({'name':'validation', 'type':'validation', 'status':'pending', 'messages':[]}) def validate(self): log = [e for e in self.log if e['name'] == 'validation'][0] log['status'] = 'running' start_time = datetime.now() log['start_time'] = str(start_time) sub = Popen([main_dir+'/bin/validate_format.R', '-m', 'validate_map', '--map', self.fp], stdout=PIPE, stderr=PIPE) sub.wait() log['status'] = 'completed' log['messages'] = stdoutLog2json(sub.stdout) if len([e for e in log['messages'] if 'error' in e.keys()]) > 0: log['status'] = 'failed' Error(msg='Error detected in '+self.fp) log['status'] = 'completed' log['execution_time'] = str(datetime.now() - start_time) class DataSet(object): """Dataset""" _pipeline = [{'name':'pre-processing', 'fun':'preProcess', 'operations':[ 'filtering', 'binning' ]}, {'name':'transformation', 'fun':'transform', 'operations':[ 'rarefaction', 'transformation', 'batch_effect_suppression' ]} ] def __init__(self, json_fp = '', pipeline = [], data_fp = '', output = '', map = '', category_column = '', abundance_threshold = '', abundance_threshold_type = '', presence_threshold = '', presence_threshold_type = '', bin_levels = [], bin_fun = '', rarefy = False, rarefaction_depth = '', nsampling = '', transformation = '', - batch_effect = ''): + batch_effect = '', + batch_effect_fun = ''): self.json_fp = json_fp self.pipeline = self._pipeline if pipeline != []: self.pipeline = pipeline self.output = output self.data_fp = [self.output+'/'+os.path.basename(data_fp)] self.tmpdir = os.path.normpath(self.output+'/../'+mkdtemp()) mkdir(self.tmpdir) copyfile(data_fp, self.data_fp[0]) table2R(self.data_fp[0]) self.map = map self.category_column = category_column self.abundance_threshold = abundance_threshold self.abundance_threshold_type = abundance_threshold_type self.presence_threshold = presence_threshold self.presence_threshold_type = presence_threshold_type self.bin_levels = bin_levels self.bin_fun = bin_fun self.rarefy = rarefy self.rarefaction_depth = rarefaction_depth self.nsampling = nsampling self.transformation = transformation self.batch_effect = batch_effect + self.batch_effect_fun = batch_effect_fun self.log = [] self.log.append({'name':'file', 'type':'file', 'path':self.data_fp[0]}) self.log.append({'name':'validation', 'type':'validation', 'status':'pending', 'messages':[]}) self.log.append({'name':'sorting', 'type':'operation', 'status':'pending', 'messages':[]}) for p in self.pipeline: op = [] for e in p['operations']: op.append({'name':e, 'status':'pending', 'messages':[]}) self.log.append({'name':p['name'], 'type':'step', 'operations':op}) op = [] self.stdout = [] self.stderr = [] def validate(self): log = [e for e in self.log if e['name'] == 'validation'][0] log['status'] = 'running' start_time = datetime.now() log['start_time'] = str(start_time) for fp in self.data_fp: sub = Popen([main_dir+'/bin/validate_format.R', '-t', fp, '-m', 'validate_dataset', '--map', self.map.fp, '--category_column', str(self.category_column)], stdout=PIPE, stderr=PIPE) sub.wait() log['messages'].extend(stdoutLog2json(sub.stdout)) log['status'] = 'completed' log['execution_time'] = str(datetime.now() - start_time) def sort(self): log = [e for e in self.log if e['name'] == 'sorting'][0] log['status'] = 'running' start_time = datetime.now() log['start_time'] = str(start_time) tmp = NamedTemporaryFile(delete=False, dir=self.tmpdir) for fp in self.data_fp: sub = Popen([main_dir+'/bin/modify_table.R', '-t', fp, '-m', 'sorting', '--map', self.map.fp, '--ignore', str(self.category_column), '-o', fp, '--log', str(tmp.name)], stdout=PIPE, stderr=PIPE) sub.wait() self.appendStdout(sub.stdout.read()) self.appendStderr(sub.stderr.read()) if os.path.exists(tmp.name): with open(tmp.name, 'r') as t: log['messages'].extend(stdoutLog2json(t)) os.remove(tmp.name) tmp.close() log['status'] = 'completed' log['execution_time'] = str(datetime.now() - start_time) def preProcess(self, fun=''): log = [e for e in [o for o in self.log if o['name'] == 'pre-processing'][0]['operations'] if e['name'] == fun][0] log['status'] = 'running' start_time = datetime.now() log['start_time'] = str(start_time) tmp = NamedTemporaryFile(delete=False, dir=self.tmpdir) args = [main_dir+'/bin/modify_table.R', '-m', fun, '--log', str(tmp.name)] if fun == 'filtering': if (self.abundance_threshold is None or self.abundance_threshold == 0) and (self.presence_threshold is None or self.presence_threshold == 0): log['status'] = 'skipped' return(0) args.extend(['--column', str(self.category_column), '--abundance_threshold', self.abundance_threshold, '--abundance_threshold_type', self.abundance_threshold_type, '--presence_threshold', self.presence_threshold, '--presence_threshold_type', self.presence_threshold_type]) elif fun == 'binning': if self.bin_levels is None: log['status'] = 'skipped' return(0) args.extend(['--column', str(self.category_column), '--fun', self.bin_fun]) output_fps = [] for fp in self.data_fp: args.extend(['-t', fp]) if fun == 'filtering': output_fp = fp +'_filtered.txt' output_fps.append(output_fp) args.extend(['-o', output_fp]) sub = Popen(args, stdout=PIPE, stderr=PIPE) sub.wait() self.appendStdout(sub.stdout.read()) self.appendStderr(sub.stderr.read()) elif fun == 'binning': for level in self.bin_levels: output_fp = fp+'_lvl'+level+'.txt' output_fps.append(output_fp) args.extend(['--level', level, '-o', output_fp]) sub = Popen(args, stdout=PIPE, stderr=PIPE) sub.wait() self.appendStdout(sub.stdout.read()) self.appendStderr(sub.stderr.read()) if os.path.exists(tmp.name): with open(tmp.name, 'r') as t: log['messages'].extend(stdoutLog2json(t)) os.remove(tmp.name) tmp.close() log['status'] = 'completed' log['execution_time'] = str(datetime.now() - start_time) self.data_fp = output_fps def transform(self, fun=''): log = [e for e in [o for o in self.log if o['name'] == 'transformation'][0]['operations'] if e['name'] == fun][0] log['status'] = 'running' start_time = datetime.now() log['start_time'] = str(start_time) tmp = NamedTemporaryFile(delete=False, dir=self.tmpdir) args = [main_dir+'/bin/modify_table.R', '--log', str(tmp.name), '--ignore', str(self.category_column)] if fun == 'rarefaction': if self.rarefy is None or self.rarefy == False: log['status'] = 'skipped' return(0) args.extend(['-m', 'rarefaction', '--sample', self.rarefaction_depth, '--nsampling', self.nsampling]) elif fun == 'transformation': if self.transformation is None or self.transformation in ['none', '', ' ']: log['status'] = 'skipped' return(0) args.extend(['-m', self.transformation]) elif fun == 'batch_effect_suppression': if self.batch_effect is None: log['status'] = 'skipped' return(0) args.extend(['-m', 'batch_effect_suppression', '--map', self.map.fp, '--effect', self.batch_effect, '--fun', - 'combat']) + self.batch_effect_fun]) suffix = {'rarefaction':'rar', 'transformation':'trans', 'batch_effect_suppression':'bes'} output_fps = [] for fp in self.data_fp: output_fp = ('.').join(fp.split('.')[:-1])+'_'+suffix[fun]+'.txt' output_fps.append(output_fp) args.extend(['-t', fp, '-o', output_fp]) sub = Popen(args, stdout=PIPE, stderr=PIPE) sub.wait() self.appendStdout(sub.stdout.read()) self.appendStderr(sub.stderr.read()) if os.path.exists(tmp.name): with open(tmp.name, 'r') as t: log['messages'].extend(stdoutLog2json(t)) os.remove(tmp.name) tmp.close() log['status'] = 'completed' log['execution_time'] = str(datetime.now() - start_time) self.data_fp = output_fps def appendStdout(self, stdout): if stdout is not None and stdout != '': self.stdout.append(stdout) def appendStderr(self, stderr): if stderr is not None and stderr != '': self.stderr.append(stderr) class Analysis(object): """THE ANALYSIS""" def __init__(self, json_fp): """initialize the analysis pipeline json_fp File path to json """ # INITIALIZE THE PIPELINE self.pipeline = [{'name':'diversity', 'before_step':'transformation', 'status':'pending', 'messages':[]}, {'name':'clustering', 'before_step':'analysis', 'status':'pending', 'messages':[]}, {'name':'proportions', 'status':'pending', 'messages':[]}, {'name':'adonis', 'status':'pending', 'messages':[]}, {'name':'pca', 'status':'pending', 'messages':[]}, {'name':'ca', 'status':'pending', 'messages':[]}, {'name':'pcoa', 'status':'pending', 'messages':[]}, {'name':'cca', 'status':'pending', 'messages':[]}, {'name':'heatmap', 'status':'pending', 'messages':[]}, {'name':'change', 'status':'pending', 'messages':[]}, {'name':'correlation_network', 'status':'pending', 'messages':[]}, {'name':'similarity_network', 'status':'pending', 'messages':[]}] # Set parameters from json self.parameters = Parameters(json_fp) # Make the output directory self.output = self.parameters.params['output'] if os.path.exists(self.output): rmtree(self.output) mkdir(self.output) self.tmpdir = os.path.normpath(self.output+'/../'+mkdtemp()) mkdir(self.tmpdir) self.to_rm = [self.tmpdir] # Set the archive #self.archive = Archive(self.output+'/output.tar.gz') # Store parameters to output directory self.parameters.write(self.output+'/params.json') # Start logging self.log = Log(self.output+'/log.json') self.stdout = Log(self.output+'/stdout.log') self.stderr = Log(self.output+'/stderr.log') def run(self): """Run the analysis""" # Create data sets self.map = Map(self.parameters.params['map'], self.output) self.log.data.append({'name':'map', 'type':'map', 'log':self.map.log}) self.log.update() #self.archive.add(self.log.fp+'.bkp', update=False, generate=False) log = [e for e in self.map.log if e['name'] == 'validation'][0] log['status'] = 'running' self.log.update() self.map.validate() if len([e for e in log['messages'] if 'error' in e.keys()]) > 0: log['status'] = 'failed' self.log.update() Error(msg='Error detected in '+self.map.fp, to_rm=self.to_rm) self.log.update() if self.parameters.params['bin_levels'] is None or isinstance(self.parameters.params['bin_levels'], list): bin_levels = self.parameters.params['bin_levels'] else: bin_levels = self.parameters.params['bin_levels'].split(',') self.primary_dataset = DataSet(json_fp = self.parameters.json_fp, data_fp = self.parameters.params['primary_dataset'], output = self.output, map = self.map, category_column = self.parameters.params['category_column'], abundance_threshold = self.parameters.params['abundance_threshold'], abundance_threshold_type = self.parameters.params['abundance_threshold_type'], presence_threshold = self.parameters.params['presence_threshold'], presence_threshold_type = self.parameters.params['presence_threshold_type'], bin_levels = bin_levels, bin_fun = self.parameters.params['bin_fun'], rarefy = bool(self.parameters.params['prim_rarefaction']), rarefaction_depth = self.parameters.params['prim_sampling_depth'], nsampling = self.parameters.params['prim_nsampling'], transformation = self.parameters.params['prim_trans_method'], - batch_effect = self.parameters.params['prim_batch_effect_suppression']) + batch_effect = self.parameters.params['prim_batch_effect_suppression'], + batch_effect_fun = self.parameters.params['prim_batch_effect_suppression_fun']) self.log.data.append({'name':'primary_dataset', 'type':'dataset', 'log':self.primary_dataset.log}) self.log.update() self.stdout.data.append({'primary_dataset':self.primary_dataset.stdout}) self.stderr.data.append({'primary_dataset':self.primary_dataset.stderr}) self.to_rm.append(self.primary_dataset.tmpdir) #self.archive.add(self.primary_dataset.data_fp, update=False, generate=False) log = [e for e in self.primary_dataset.log if e['name'] == 'validation'][0] log['status'] = 'running' self.log.update() self.primary_dataset.validate() if len([e for e in log['messages'] if 'error' in e.keys()]) > 0: log['status'] = 'failed' self.log.update() Error(msg='Error detected in primary_dataset.validate()', to_rm=self.to_rm) self.log.update() log = [e for e in self.primary_dataset.log if e['name'] == 'sorting'][0] log['status'] = 'running' self.log.update() self.primary_dataset.sort() self.stdout.update() self.stderr.update() if len([e for e in log['messages'] if 'error' in e.keys()]) > 0: log['status'] = 'failed' self.log.update() Error(msg='See'+self.log.fp+'.', to_rm=self.to_rm) self.log.update() if self.parameters.params['secondary_dataset'] is not None: self.secondary_dataset = DataSet(json_fp = self.parameters.json_fp, pipeline = [{'name':'transformation', 'fun':'transform', 'operations':['rarefaction', 'transformation', 'batch_effect_suppression']}], data_fp = self.parameters.params['secondary_dataset'], output = self.output, map = self.map, category_column = '', rarefy = bool(self.parameters.params['sec_rarefaction']), rarefaction_depth = self.parameters.params['sec_sampling_depth'], nsampling = self.parameters.params['sec_nsampling'], transformation = self.parameters.params['sec_trans_method'], - batch_effect = self.parameters.params['sec_batch_effect_suppression']) + batch_effect = self.parameters.params['sec_batch_effect_suppression'], + batch_effect_fun = self.parameters.params['prim_batch_effect_suppression_fun']) self.log.data.append({'name':'secondary_dataset', 'type':'dataset', 'log':self.secondary_dataset.log}) self.log.update() self.stdout.data.append({'secondary_dataset':self.secondary_dataset.stdout}) self.stderr.data.append({'secondary_dataset':self.secondary_dataset.stderr}) self.to_rm.append(self.secondary_dataset.tmpdir) log = [e for e in self.secondary_dataset.log if e['name'] == 'validation'][0] log['status'] = 'running' self.log.update() self.secondary_dataset.validate() if len([e for e in log['messages'] if 'error' in e.keys()]) > 0: log['status'] = 'failed' self.log.update() Error(msg='Error detected in secondary_dataset.validate()', to_rm=self.to_rm) self.log.update() log = [e for e in self.secondary_dataset.log if e['name'] == 'sorting'][0] log['status'] = 'running' self.log.update() self.secondary_dataset.sort() self.stdout.update() self.stderr.update() if len([e for e in log['messages'] if 'error' in e.keys()]) > 0: log['status'] = 'failed' self.log.update() Error(msg='See'+self.log.fp+'.', to_rm=self.to_rm) self.log.update() # RUN DATASET PIPELINE self.log.data.append({'name':'analysis', 'type':'analysis', 'log':self.pipeline}) self.stdout.data.append({'analysis':[]}) self.stderr.data.append({'analysis':[]}) datasets = [self.primary_dataset] if self.parameters.params['secondary_dataset'] is not None: datasets.append(self.secondary_dataset) for dataset in datasets: for step in dataset.pipeline: if dataset == datasets[0]: analyses = [a for a in [e for e in self.pipeline if 'before_step' in e.keys()] if a['before_step'] == step['name']] for analysis in analyses: eval('self.analysis("'+analysis['name']+'")') for operation in step['operations']: log = [o for o in [ s for s in dataset.log if s['name'] == step['name']][0]['operations'] if o['name'] == operation][0] log['status'] = 'running' self.log.update() eval('dataset.'+step['fun']+'("'+operation+'")') self.stdout.update() self.stderr.update() if len([e for e in log['messages'] if 'error' in e.keys()]) > 0: log['status'] = 'failed' self.log.update() Error(msg='See'+self.log.fp+'.', to_rm=self.to_rm) self.log.update() # COMPLETE ANALYSIS PIPELINE analyses = [a for a in [e for e in self.pipeline if 'before_step' in e.keys()] if a['before_step'] == 'analysis'] for analysis in analyses: eval('self.analysis("'+analysis['name']+'")') analyses = [a for a in [e for e in self.pipeline if 'before_step' not in e.keys()]] for analysis in analyses: eval('self.analysis("'+analysis['name']+'")') self.cleanup() def cleanup(self): self.log.wrapup() self.stdout.update() self.stderr.update() #self.archive.update() for t in self.to_rm: rmtree(t) def analysis(self, method=''): """Run R scripts to generate figures and stats""" log = [e for e in [l for l in self.log.data if l['name'] == 'analysis'][0]['log'] if e['name'] == method][0] stdout = [ e for e in self.stdout.data if e.keys()[0] == 'analysis'][0]['analysis'] stderr = [ e for e in self.stderr.data if e.keys()[0] == 'analysis'][0]['analysis'] if method not in self.parameters.params.keys() or self.parameters.params[method] == 0: log['status'] = 'skipped' self.log.update() return(0) log['status'] = 'running' start_time = datetime.now() log['start_time'] = str(start_time) self.log.update() append_to_map = False output = self.output+'/'+method mkdir(output) tmp = NamedTemporaryFile(delete=False, dir=self.tmpdir) # Set general arguments args = [main_dir+'/bin/analyse_table.R', '--graphical', 'TRUE', '--log', str(tmp.name), '--method', method, '--map', self.map.fp, '--category', self.primary_dataset.category_column] # Set analysis-specific arguments if method == 'diversity': if self.parameters.params['diversity_metric'] is not None: args.extend(['--fun', ','.join(self.parameters.params['diversity_metric'])]) if self.parameters.params['compare_diversity'] is not None: args.extend(['--compare_diversity', self.parameters.params['compare_diversity']]) elif method == 'clustering': append_to_map = True if self.parameters.params['clustering_fun'] is not None: clustering_fun = self.parameters.params['clustering_fun'] if isinstance(clustering_fun, list): clustering_fun = ','.join(clustering_fun) args.extend(['--fun', clustering_fun]) elif method == 'adonis': if self.parameters.params['adonis_model'] is not None: adonis_model = self.parameters.params['adonis_model'] if isinstance(adonis_model, list): adonis_model = ','.join(adonis_model) args.extend(['--adonis_model', adonis_model]) if self.parameters.params['adonis_distfun'] is not None: adonis_distfun = self.parameters.params['adonis_distfun'] if isinstance(adonis_distfun, list): adonis_distfun = ','.join(adonis_distfun) args.extend(['--fun', adonis_distfun]) if self.parameters.params['adonis_strata'] is not None: adonis_strata = self.parameters.params['adonis_strata'] if isinstance(adonis_strata, list): adonis_strata = ','.join(adonis_strata) args.extend(['--strata', adonis_strata]) elif method == 'pcoa': pcoa_distfun = self.parameters.params['pcoa_distfun'] if pcoa_distfun is not None: if isinstance(pcoa_distfun, list): pcoa_distfun = ','.join(pcoa_distfun) args.extend(['--fun', str(pcoa_distfun)]) elif method == 'cca': if self.parameters.params['cca_categ'] is not None: cca_categ = self.parameters.params['cca_categ'] if isinstance(cca_categ, list): cca_categ = ','.join(cca_categ) args.extend(['--column', cca_categ]) else: log['messages'].append({'warning':'No categorical data was given in CCA. Please provide the name(s) of column(s) in map that contain(s) categorical data to be used in CCA.'}) log['status'] = 'skipped' self.log.update() return(0) elif method == 'heatmap' and self.parameters.params['secondary_dataset'] is not None: args.extend(['--metadata', self.secondary_dataset.data_fp[0]]) elif method == 'correlation_network': if self.parameters.params['correlation_network_fun'] is not None: correlation_network_fun = self.parameters.params['correlation_network_fun'] if isinstance(correlation_network_fun, list): correlation_network_fun = ','.join(correlation_network_fun) args.extend(['--fun', correlation_network_fun]) if self.parameters.params['secondary_dataset'] is not None: args.extend(['--metadata', self.secondary_dataset.data_fp[0]]) elif method == 'similarity_network': append_to_map = True if self.parameters.params['similarity_network_fun1'] is not None: fun = self.parameters.params['similarity_network_fun1'] if self.parameters.params['secondary_dataset'] is not None and self.parameters.params['similarity_network_fun2'] is not None: fun = fun+','+self.parameters.params['similarity_network_fun2'] args.extend(['--fun', fun]) if self.parameters.params['similarity_network_clust'] is not None: args.extend(['--clust', self.parameters.params['similarity_network_clust']]) if self.parameters.params['secondary_dataset'] is not None: args.extend(['--metadata', self.secondary_dataset.data_fp[0]]) if len(self.primary_dataset.data_fp) > 1: if len(self.primary_dataset.data_fp) != len(self.parameters.params['bin_levels']): Error(msg='Analysis cannot be performed at specified binning levels because corresponding data files were not found.', to_rm=self.to_rm) log['levels'] = log.pop('messages') for i in range(0, len(self.parameters.params['bin_levels'])): log['levels'].append({'level':self.parameters.params['bin_levels'][i], 'status':'pending', 'messages':[]}) for i in range(0, len(self.primary_dataset.data_fp)): l = log output_fp = output+'/'+method if len(self.primary_dataset.data_fp) > 1: l = [e for e in log['levels'] if e['level'] == self.parameters.params['bin_levels'][i]][0] output_fp = output_fp+'_level_'+self.parameters.params['bin_levels'][i] l['status'] = 'running' args_cp = copy(args) args_cp.extend(['-t', self.primary_dataset.data_fp[i], '-o', output_fp]) if self.parameters.params['model_type'] == 'basic': if isinstance(self.parameters.params['basic_model'], list): m = ','.join(self.parameters.params['basic_model']) else: m = self.parameters.params['basic_model'] args_cp.extend(['--model', m]) else: if isinstance(self.parameters.params['advanced_model'], list): m = ','.join(self.parameters.params['advanced_model']) else: m = self.parameters.params['advanced_model'] args_cp.extend(['--stats', self.parameters.params['advanced_stats'], '--model', m]) sub = Popen(args_cp, stdout=PIPE, stderr=PIPE) sub.wait() s = sub.stdout.read() print s if s is not None and s != '': stdout.append(s) s = sub.stderr.read() if s is not None and s != '': stderr.append(s) if os.path.exists(tmp.name): with open(tmp.name, 'r') as t: l['messages'].extend(stdoutLog2json(t)) os.remove(tmp.name) tmp.close() self.log.update() if append_to_map: if os.path.exists(output_fp+'.txt') and os.stat(output_fp+'.txt').st_size != 1: tmpmap = NamedTemporaryFile(delete=False, dir=self.tmpdir) with open(tmpmap.name, 'w') as new, open(self.map.fp) as m, open(output_fp+'.txt') as c: i = 0 for line_m, line_c in zip(m, c): to_add = line_c.rstrip().split('\t') to_add.pop(0) if i == 0: while to_add in line_m.rstrip().split('\t'): to_add = to_add+'_'+rand(3) model_to_add = to_add new.write('%s\t%s\n' % (line_m.rstrip(), '\t'.join(to_add))) i = i+1 move(tmpmap.name, self.map.fp) if self.parameters.params['model_type'] == 'basic': if not isinstance(self.parameters.params['basic_model'], list): self.parameters.params['basic_model'] = [self.parameters.params['basic_model']] self.parameters.params['basic_model'].extend(model_to_add) else: if not isinstance(self.parameters.params['basic_model'], list): self.parameters.params['advanced_model'] = [self.parameters.params['advanced_model']] self.parameters.params['advanced_model'].extend(model_to_add) else: Warning(msg='In '+method+': output file not found ('+output_fp+'.txt'+').') if len([e for e in l['messages'] if 'error' in e.keys()]) > 0: l['status'] = 'failed' log['execution_time'] = str(datetime.now() - start_time) log['status'] = 'failed' self.log.update() Error(msg='See'+self.log.fp+'.', to_rm=self.to_rm) else: l['status'] = 'completed' self.log.update() log['execution_time'] = str(datetime.now() - start_time) log['status'] = 'completed' self.log.update() diff --git a/lib/genocrunch_console/lib/genocrunchlib.pyc b/lib/genocrunch_console/lib/genocrunchlib.pyc index bcf83cd..c9d173a 100644 Binary files a/lib/genocrunch_console/lib/genocrunchlib.pyc and b/lib/genocrunch_console/lib/genocrunchlib.pyc differ diff --git a/public/examples/mock_map.txt b/public/examples/mock_map.txt new file mode 100644 index 0000000..c767eb7 --- /dev/null +++ b/public/examples/mock_map.txt @@ -0,0 +1,21 @@ +#Scontrol_dietmpleID Diet +Smpl1 control_diet +Smpl2 control_diet +Smpl3 control_diet +Smpl4 control_diet +Smpl5 control_diet +Smpl6 control_diet +Smpl7 control_diet +Smpl8 control_diet +Smpl9 control_diet +Smpl10 control_diet +Smpl11 special_diet +Smpl12 special_diet +Smpl13 special_diet +Smpl14 special_diet +Smpl15 special_diet +Smpl16 special_diet +Smpl17 special_diet +Smpl18 special_diet +Smpl19 special_diet +Smpl20 special_diet diff --git a/public/examples/mock_otus_table.txt b/public/examples/mock_otus_table.txt new file mode 100644 index 0000000..9e932cb --- /dev/null +++ b/public/examples/mock_otus_table.txt @@ -0,0 +1,482 @@ +#OTU ID Smpl1 Smpl2 Smpl3 Smpl4 Smpl5 Smpl6 Smpl7 Smpl8 Smpl9 Smpl10 Smpl11 Smpl12 Smpl13 Smpl14 Smpl15 Smpl16 Smpl17 Smpl18 Smpl19 Smpl20 taxonomy +16186 0 0 0 0 0 0 0 0 0 0 32 48 37 36 15 45 12 21 15 23 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Clostridiaceae; g__Clostridium; s__ +183681 60 36 49 31 25 115 5 44 0 9 88 1 41 89 96 55 30 50 49 54 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +580090 509 515 876 997 289 962 877 466 891 867 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +308333 0 0 0 0 0 0 0 0 0 0 377 293 425 735 584 444 79 446 416 397 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +184285 0 2 0 15 0 3 0 0 0 5 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +193572 0 0 0 0 0 0 0 0 0 0 69 36 42 14 52 4 12 66 20 52 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +191218 0 13 0 3 11 43 14 17 21 7 46 30 14 14 2 15 10 26 16 42 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Peptostreptococcaceae; g__; s__ +323135 0 1 3 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +198118 159 67 83 166 46 139 184 98 160 112 168 121 143 27 94 197 62 155 96 106 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__; g__; s__ +186775 63 29 75 51 19 52 107 86 71 135 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia +175520 63 15 0 61 0 34 13 26 24 31 38 29 49 17 0 16 27 19 0 39 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +187225 0 0 0 0 0 0 0 0 0 0 114 136 0 172 105 159 55 114 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae +367627 1085 796 1280 38 380 1596 1785 302 1057 372 1434 1368 667 1241 1190 916 435 804 1551 1494 k__Bacteria; p__Firmicutes; c__Erysipelotrichi; o__Erysipelotrichales; f__[Coprobacillaceae]; g__Catenibacterium; s__ +298716 805 263 518 901 339 500 624 490 880 1082 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +368127 0 0 0 0 0 0 0 0 0 0 74 171 97 31 51 165 33 134 73 88 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +189849 240 80 113 206 61 180 185 142 258 149 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +329884 319 48 213 234 76 260 246 136 131 178 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Oscillospira; s__ +175497 58 27 67 50 9 47 78 48 66 63 47 73 82 73 56 66 20 69 73 69 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Oscillospira; s__ +187123 15 22 22 18 6 26 31 10 34 22 10 6 25 6 20 36 7 41 22 31 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Oscillospira; s__ +302632 0 0 0 4 0 4 0 0 16 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +4371880 0 0 0 0 0 0 0 0 0 0 3 0 8 8 4 17 7 7 24 33 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Veillonellaceae; g__Veillonella +192708 1239 673 842 2344 319 970 1152 306 800 1340 1204 2005 990 1864 2078 1720 356 51 1508 1616 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +182890 946 409 780 555 109 605 687 339 778 811 891 946 1079 1149 1206 875 395 741 971 344 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +190708 0 0 0 0 0 0 0 0 0 0 3276 3369 3310 2359 2419 2890 1247 2558 3887 3372 k__Bacteria; p__Firmicutes; c__Clostridia +319153 85 50 107 72 9 137 76 68 56 12 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__; f__; g__; s__ +537219 476 420 1175 871 296 1000 396 791 1479 881 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +3876673 51 1 22 18 13 25 53 2 30 24 30 7 75 50 83 18 25 62 104 53 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Clostridiaceae; g__Clostridium; s__ +230232 3 1 1 2 0 2 3 3 3 3 5 3 3 1 2 5 0 5 2 6 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +192893 258 115 186 184 70 79 2 13 160 171 230 172 43 96 288 109 79 125 107 160 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +186092 0 0 0 0 0 0 0 0 0 0 16 6 2 0 10 11 8 6 8 19 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Peptostreptococcaceae; g__; s__ +175585 206 198 177 323 97 212 273 145 318 237 198 342 123 328 197 236 111 197 225 243 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +310301 4 0 0 15 0 20 24 4 44 13 25 18 5 23 0 8 0 32 35 26 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +205934 25 7 19 6 10 27 25 6 30 11 1 13 11 45 13 28 1 17 9 2 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +189866 269 187 209 324 151 258 355 230 166 212 301 408 489 570 529 708 75 463 456 323 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +180903 17 15 9 59 0 16 28 0 0 20 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__[Ruminococcus]; s__ +195807 0 0 0 0 0 0 0 0 0 0 212 320 317 463 320 240 130 400 444 365 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__[Ruminococcus]; s__ +186061 9 3 4 7 0 29 0 3 14 7 16 15 6 23 25 13 3 28 8 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +190260 0 0 0 0 0 0 0 0 0 0 27 0 0 34 0 43 1 0 10 34 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +195752 876 516 1024 849 38 952 633 475 61 601 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__; f__; g__; s__ +316497 33 1 6 24 0 15 0 17 23 29 66 116 0 128 138 144 43 117 92 71 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +333949 19 14 29 30 4 3 18 9 0 12 0 39 0 16 10 5 9 31 0 18 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +4318098 100 79 10 114 33 0 0 77 25 92 0 151 39 65 103 4 29 0 31 55 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Veillonellaceae; g__Veillonella; s__dispar +178991 60 54 95 56 14 52 44 43 45 58 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +341322 59 53 63 64 16 0 72 5 46 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Bacilli; o__Turicibacterales; f__Turicibacteraceae; g__Turicibacter; s__ +173883 344 224 314 292 115 302 461 85 142 283 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Peptostreptococcaceae; g__; s__ +295478 37 33 99 36 0 14 75 13 63 10 0 107 33 46 34 22 24 86 72 60 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales +230263 812 302 480 750 291 677 236 181 961 465 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Erysipelotrichi; o__Erysipelotrichales; f__Erysipelotrichaceae; g__p-75-a5; s__ +355120 140 89 66 238 58 96 78 50 216 58 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +175279 75 55 63 36 21 53 60 28 92 104 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +370086 138 83 36 106 37 38 97 27 106 51 98 127 105 35 52 56 39 123 4 59 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +179686 269 161 286 226 43 312 280 168 336 181 282 250 197 312 117 310 58 193 294 183 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +198243 332 356 548 450 157 296 352 234 572 495 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__[Ruminococcus]; s__ +356445 0 0 0 0 0 0 1 0 0 0 2929 941 3472 3272 2985 2783 728 3828 3176 1912 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +173811 0 0 6 0 0 0 2 3 9 12 0 2 13 0 14 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia +326271 2376 1760 1831 1334 579 860 216 681 324 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +311370 813 233 490 646 194 1088 671 231 66 599 873 880 800 1184 757 725 186 695 158 466 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +187816 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +183886 252 45 99 156 47 190 49 62 102 191 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +185988 46 13 27 0 19 0 15 11 28 13 36 28 15 5 6 28 14 23 20 19 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +186596 12 4 0 7 2 0 0 10 0 1 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +174943 0 9 42 0 0 9 20 0 8 46 54 19 6 30 0 22 11 0 65 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +325326 0 0 0 0 0 0 0 0 0 0 327 181 341 246 400 223 43 357 70 153 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Clostridiaceae; g__; s__ +174078 35 0 25 14 0 41 20 25 44 34 41 61 7 56 42 10 6 42 20 18 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +4464717 167 89 118 126 42 2 22 57 139 94 6 38 98 119 38 118 66 44 178 2 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +177478 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__Coriobacteriaceae; g__; s__ +323473 208 138 116 466 63 1 421 124 174 283 123 484 326 262 94 341 101 201 19 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__; f__; g__; s__ +132829 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Lactobacillaceae; g__; s__ +192462 58 25 46 38 11 22 14 16 35 25 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +184745 0 0 0 0 0 0 0 0 0 0 631 491 838 546 618 683 179 829 574 810 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__Coriobacteriaceae; g__Collinsella; s__aerofaciens +355102 0 1 0 6 0 0 0 2 0 1 0 0 8 0 0 4 0 0 0 16 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Roseburia; s__ +180654 0 0 0 0 0 0 0 0 0 0 0 38 0 62 98 27 48 0 0 109 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Lachnospira; s__ +367889 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 2 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +177905 146 137 233 92 62 57 110 59 242 58 0 0 0 0 0 4 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__; g__; s__ +186259 117 61 26 32 32 126 139 24 24 64 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +176645 0 30 79 26 33 59 8 16 76 57 95 188 115 124 87 156 49 57 78 144 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +344783 103 52 101 60 29 106 98 45 96 82 83 103 110 103 82 106 34 88 94 84 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__Coriobacteriaceae; g__; s__ +178961 219 142 357 255 144 241 354 272 183 240 136 51 9 9 110 45 0 99 95 55 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +197708 28 13 38 30 11 54 45 0 28 23 4 44 0 0 0 23 8 0 7 30 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +4358818 0 0 0 0 1 0 0 0 0 0 0 18 0 0 0 0 0 0 7 0 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Lactobacillaceae; g__Lactobacillus; s__ +760544 42 40 24 40 29 83 65 32 58 78 65 48 47 64 36 69 21 46 62 83 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +196905 0 0 0 0 0 0 0 0 0 0 26 0 18 51 18 0 7 19 35 16 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +422295 0 0 0 0 0 0 0 0 0 0 16 111 5 55 94 127 50 18 182 146 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Clostridiaceae; g__; s__ +182497 0 20 44 0 9 12 0 15 0 15 0 17 19 26 11 0 6 9 21 15 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +4422456 28 7 6 12 0 7 21 30 0 27 7 30 14 0 19 2 0 6 47 57 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Veillonellaceae; g__Veillonella +190351 20 0 25 20 7 23 23 2 18 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Coprococcus; s__ +531436 4 0 15 15 0 6 0 1 16 0 2 0 5 0 9 10 0 6 18 12 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +661266 217 85 153 200 72 183 171 91 194 227 256 223 192 164 218 224 85 190 167 191 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +327704 15 0 1 16 3 9 17 8 13 26 0 1 28 3 14 8 2 14 7 10 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +184553 156 54 111 110 59 73 111 59 159 173 859 668 576 335 895 1078 355 205 752 826 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Coprococcus; s__ +26978 976 529 782 824 276 628 1038 510 777 949 51 162 75 186 84 123 41 62 146 108 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +185883 0 0 0 0 0 0 0 0 0 0 36 13 15 16 0 14 8 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +185745 0 0 0 0 0 0 0 0 0 0 259 315 128 233 102 206 58 271 266 209 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +197757 0 0 0 9 0 0 1 0 0 0 7 50 26 0 15 0 37 79 47 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +145801 0 0 13 12 2 7 0 0 0 0 7 26 3 5 0 0 7 5 10 0 k__Bacteria; p__Firmicutes; c__Erysipelotrichi; o__Erysipelotrichales; f__[Coprobacillaceae]; g__; s__ +137026 0 0 41 6 6 16 0 15 14 34 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Lactobacillaceae; g__Lactobacillus; s__agilis +522135 0 0 0 6 0 0 2 11 17 0 1 19 0 5 31 23 0 23 0 40 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Roseburia; s__ +179987 0 0 0 0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 2 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +189334 280 205 461 465 136 504 352 269 0 427 32 42 16 4 0 2 10 38 24 24 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +178839 37 10 91 90 14 106 55 33 85 82 23 69 75 94 64 70 15 13 47 45 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +191180 478 512 576 537 170 771 550 266 516 578 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +184025 240 224 219 220 88 510 301 181 221 492 115 111 60 317 137 488 166 174 364 283 k__Bacteria; p__Firmicutes; c__Clostridia +295705 1663 604 720 1630 260 805 560 504 932 978 755 867 948 715 687 846 289 1056 1104 964 k__Bacteria; p__Firmicutes; c__Erysipelotrichi; o__Erysipelotrichales; f__[Coprobacillaceae]; g__Catenibacterium; s__ +178121 385 185 241 4 0 255 261 58 288 238 302 0 88 459 441 265 91 466 218 246 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +326136 0 1 7 4 0 9 0 1 0 9 0 0 0 20 0 0 0 11 2 1 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +308544 0 0 0 0 0 0 0 0 0 0 883 895 893 1318 856 936 94 982 611 814 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +187883 52 73 121 0 38 12 87 0 108 164 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae +334864 640 310 736 727 120 699 1024 420 692 394 588 414 371 1091 867 127 109 216 970 750 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +180155 7 0 0 1 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +181828 0 0 0 0 0 0 0 0 0 0 81 41 50 43 20 42 1 10 55 53 k__Bacteria; p__Firmicutes; c__Clostridia; o__; f__; g__; s__ +189085 193 72 177 113 72 154 157 83 184 214 98 179 145 92 110 126 49 148 174 216 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Roseburia; s__ +184678 0 0 12 0 1 0 0 3 13 15 0 0 6 7 28 24 4 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +359044 18 4 24 26 7 13 13 2 10 7 3 16 6 24 23 1 8 15 5 11 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +183116 0 40 62 63 22 32 55 41 10 26 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Coprococcus; s__ +180846 0 0 0 0 0 0 0 0 0 0 358 352 193 379 298 268 102 385 223 254 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +197524 0 0 4 0 0 0 0 0 0 0 3 0 0 0 0 0 2 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +189064 34 13 0 0 0 1 4 8 0 16 15 15 19 0 19 0 0 15 23 7 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +182989 85 62 220 62 42 91 46 67 150 156 33 162 104 132 206 104 51 88 337 250 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +517282 76 26 0 23 17 50 46 31 21 43 86 0 0 88 51 96 7 67 28 12 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +321902 0 0 0 0 0 0 0 0 0 0 104 134 124 153 51 161 25 124 141 114 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +179677 187 145 233 276 97 256 261 123 230 260 298 142 184 310 312 194 59 126 273 201 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +176211 0 0 0 0 0 0 0 0 0 0 89 87 135 138 137 98 37 118 153 206 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +192563 62 47 115 20 35 79 93 58 155 48 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +187517 18 15 29 40 13 31 32 13 21 32 48 52 37 27 41 36 10 22 24 29 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Clostridiaceae; g__; s__ +4306177 0 5 1 0 4 18 3 0 9 10 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Streptococcaceae; g__Streptococcus; s__ +353155 52 37 31 0 0 39 1 15 74 36 82 6 0 5 31 45 3 0 12 17 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +176312 171 0 180 73 92 121 172 63 49 169 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +522025 36 11 3 0 4 26 0 13 2 0 0 0 0 0 15 3 0 2 0 0 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Streptococcaceae; g__Streptococcus; s__ +86170 0 0 0 0 0 0 0 0 0 0 159 222 94 46 150 195 52 199 127 99 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +174489 441 352 1104 872 261 856 1187 256 813 426 751 928 713 291 747 748 244 410 597 809 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Lachnobacterium; s__ +354517 318 57 235 44 67 339 168 129 298 217 304 293 271 262 273 192 69 148 308 96 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +212532 2733 1017 2976 2079 1036 1914 1670 1407 2833 2176 12 52 17 20 79 70 17 0 88 25 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +518748 52 13 48 54 24 0 46 11 19 8 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Roseburia; s__ +178882 0 0 0 0 0 0 0 0 0 0 4 0 11 0 0 1 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +188820 0 0 0 0 0 0 0 0 0 0 100 333 224 43 62 335 94 199 150 338 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +495095 13 12 18 0 0 11 11 11 8 17 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Clostridiaceae +146586 461 213 411 399 176 317 372 192 370 482 0 0 12 0 20 3 0 0 7 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Oscillospira; s__ +185670 175 159 43 58 79 74 167 51 80 269 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +181056 142 11 155 52 47 123 67 121 122 63 668 453 553 798 485 997 135 761 464 727 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +204126 0 0 0 0 0 0 0 0 0 0 149 187 296 217 277 238 3 346 223 417 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Oscillospira; s__ +357529 0 0 0 0 0 0 0 0 0 0 93 139 162 288 100 96 23 182 249 195 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +174638 102 14 102 250 13 254 158 67 281 141 238 93 381 0 229 241 142 239 229 134 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Veillonellaceae; g__; s__ +183220 0 0 0 0 0 0 0 0 0 0 0 0 9 0 0 0 3 8 6 3 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__[Ruminococcus]; s__ +4387706 158 161 42 147 41 236 163 110 142 255 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +328892 0 0 0 0 0 0 0 0 0 0 34 28 37 30 36 30 9 41 28 32 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +176113 96 17 45 119 39 67 77 18 77 46 63 71 55 59 94 78 19 84 73 60 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Coprococcus; s__ +988202 201 111 240 187 53 214 242 83 230 177 189 266 105 203 263 244 86 183 163 212 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Veillonellaceae; g__Veillonella +2833757 513 143 815 164 140 465 341 172 328 466 521 392 748 312 571 261 170 307 469 574 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +180462 21 7 37 46 3 0 0 15 18 25 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +308386 0 0 0 0 0 0 0 0 0 0 21 1 0 0 0 1 3 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +198273 0 0 0 0 0 0 0 0 0 0 19 2 17 22 9 67 23 25 10 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +591439 0 0 18 7 1 16 4 5 11 0 0 13 14 0 19 0 0 0 0 14 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Clostridiaceae; g__; s__ +183969 163 159 120 134 172 132 272 180 259 396 222 0 207 280 337 293 127 45 310 281 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +355510 174 68 71 123 33 115 73 86 67 107 94 58 98 103 76 112 31 106 92 100 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +193951 0 2 0 0 0 0 6 0 0 8 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +290284 0 0 0 0 2 0 0 0 0 3 0 0 0 0 6 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +182264 28 28 52 70 29 34 59 48 46 41 59 36 31 82 67 59 12 73 40 48 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +180754 106 0 216 84 18 0 170 0 89 175 150 90 44 0 219 0 16 102 133 28 k__Bacteria; p__Firmicutes; c__Clostridia; o__; f__; g__; s__ +190961 0 0 0 7 0 0 7 1 1 0 131 50 92 73 86 42 42 32 85 36 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +187038 0 0 0 0 0 0 0 0 0 0 193 176 294 176 104 185 62 233 84 259 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae +185390 0 0 0 0 0 0 0 0 0 0 6 18 6 0 0 0 7 0 0 5 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +327218 31 25 0 26 14 64 64 31 18 79 23 45 89 31 32 38 14 41 40 16 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +181860 15 1 0 23 1 0 9 0 33 0 77 29 94 76 0 41 27 21 92 60 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +199681 125 71 236 292 22 159 105 120 388 208 136 210 143 65 204 289 82 110 225 230 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +180037 0 0 0 0 0 0 0 0 0 0 0 8 0 7 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +197624 0 0 0 0 0 4 0 0 16 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia +295194 4844 3037 5608 3636 1841 7462 6194 2540 4425 5286 3962 4016 4436 7140 4693 7069 2401 6903 6317 5846 k__Bacteria; p__Firmicutes; c__Erysipelotrichi; o__Erysipelotrichales; f__[Coprobacillaceae]; g__Catenibacterium; s__ +4373641 0 0 3 0 5 0 17 10 6 2 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Leuconostocaceae; g__; s__ +194097 111 44 52 101 20 74 92 57 74 83 81 82 90 110 122 66 35 68 98 71 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae +189887 178 92 258 215 12 285 199 64 188 200 350 230 76 151 283 226 76 145 152 104 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +176080 181 61 108 81 39 97 166 63 187 135 110 109 108 181 150 205 53 99 200 160 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +345730 0 0 11 1 0 0 0 0 5 1 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Peptostreptococcaceae; g__; s__ +366752 0 4 132 64 32 86 119 98 192 0 140 111 174 118 117 89 51 5 64 147 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Coprococcus; s__ +196731 0 0 0 0 0 0 0 0 0 0 70 51 77 67 61 80 12 39 42 41 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +174045 0 40 57 9 36 71 118 63 89 141 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +188789 0 0 0 0 0 0 0 0 0 0 132 97 261 271 249 229 48 336 79 152 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__; g__; s__ +190529 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +296900 0 0 2 0 0 3 14 0 0 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +175148 1161 258 908 1646 396 1066 653 449 1082 940 606 554 330 854 1150 626 183 604 719 714 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae +192347 769 375 825 792 100 621 792 223 401 373 821 307 1255 556 424 486 279 694 885 1008 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +191393 5 11 28 0 0 5 0 0 0 10 6 2 6 0 3 14 5 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +180658 0 6 14 0 2 4 0 0 0 4 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +3530697 29 0 37 51 19 37 0 12 26 13 0 0 0 11 4 6 0 11 0 6 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +193797 0 0 0 0 0 0 0 0 0 0 456 220 336 618 600 309 89 394 4 396 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +194497 31 8 76 100 21 18 0 31 31 68 48 0 65 84 0 60 27 134 15 95 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +347690 2 0 0 0 0 0 2 0 0 1 3 0 0 1 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Oscillospira; s__ +181672 466 74 167 0 57 298 449 118 331 209 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +307238 707 249 510 658 288 539 242 443 82 491 682 999 546 842 137 582 28 398 716 595 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +185759 0 0 0 0 0 0 0 0 0 0 51 80 18 23 4 73 22 79 23 55 k__Bacteria; p__Firmicutes; c__Clostridia; o__; f__; g__; s__ +189478 30 51 3 41 9 18 29 14 6 48 128 67 70 138 148 113 31 191 120 99 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +174607 68 20 22 31 17 74 48 34 43 72 0 0 0 8 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +42931 1437 943 842 1358 622 2046 1853 880 1717 1624 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Streptococcaceae +799573 194 62 139 217 38 115 119 119 113 62 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Peptostreptococcaceae; g__; s__ +175550 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +187796 0 0 0 0 0 0 0 0 0 0 0 0 10 0 0 0 0 16 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +174143 0 2 4 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 2 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +950828 95 47 45 0 22 79 18 22 35 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Streptococcaceae; g__; s__ +365933 0 0 0 0 0 0 0 0 0 0 84 57 62 13 120 30 21 46 93 39 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__Coriobacteriaceae +182986 127 69 142 98 55 148 185 62 136 108 160 105 144 159 141 156 44 129 139 101 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +174379 87 13 70 108 6 0 6 41 8 5 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +4373642 0 2 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Enterococcaceae; g__Enterococcus; s__ +182167 0 0 0 0 0 0 0 0 0 0 76 79 57 47 79 73 28 57 106 63 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +4335578 3573 572 4318 3938 1352 3893 3659 442 2309 3158 107 90 80 92 75 98 59 147 64 139 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Clostridiaceae; g__; s__ +182149 3 0 0 0 0 0 0 0 0 0 0 11 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +328727 574 244 276 414 146 854 691 183 423 452 404 366 438 74 1032 574 170 158 650 397 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Streptococcaceae; g__Lactococcus; s__garvieae +206574 382 383 750 395 152 316 208 381 812 759 889 700 873 292 696 449 58 887 854 890 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +176008 208 64 163 252 45 170 215 93 148 139 218 169 160 151 154 210 45 223 182 105 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +188008 2 97 416 435 187 272 196 71 255 310 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +4341534 19 0 12 0 6 1 0 6 15 9 3 9 5 12 5 1 0 0 0 7 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +76547 0 0 0 0 0 0 0 0 0 0 98 43 94 66 68 79 29 72 31 73 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Clostridiaceae; g__; s__ +182107 292 267 27 504 84 346 309 50 218 284 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +4465284 0 0 0 0 0 0 0 0 0 0 1158 710 1013 888 974 932 230 1133 644 770 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +198910 0 0 0 0 0 0 0 0 0 0 3 0 9 27 0 19 6 0 0 5 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +350297 86 30 180 86 3 30 22 50 73 64 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Oscillospira; s__ +181929 0 0 0 0 0 0 0 0 0 0 18 0 12 0 0 0 6 0 16 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +187355 54 1 55 50 21 58 86 26 45 36 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +2051184 6 0 0 0 0 0 0 5 1 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Peptostreptococcaceae; g__; s__ +183654 0 0 2 0 0 0 0 7 0 0 0 0 0 0 10 6 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +189539 0 0 0 0 0 0 0 0 0 0 0 2 6 0 0 9 0 1 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Coprococcus; s__ +179585 0 39 174 108 28 0 116 6 68 0 178 125 61 86 140 215 28 233 117 106 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Oscillospira; s__ +308057 0 0 0 0 0 0 0 0 0 0 290 94 95 408 75 145 78 196 155 222 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +30872 10 19 0 39 5 12 0 53 81 40 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +178557 42 23 49 67 20 60 50 37 55 64 150 119 0 150 163 136 23 89 88 102 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Coprococcus; s__ +175962 806 351 497 746 265 1068 553 527 679 760 7 43 6 21 12 2 0 33 16 2 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +179137 37 44 123 101 38 81 109 64 109 87 88 113 88 97 107 69 24 71 81 97 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +175280 7 0 6 9 0 6 0 0 0 0 0 2 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +4396292 12 28 30 55 13 0 64 18 15 11 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +195719 586 379 561 382 163 571 600 392 295 653 382 224 216 450 478 0 168 181 270 95 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +180801 1182 601 884 864 331 735 698 420 343 757 985 725 471 718 832 810 418 748 924 873 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +2582660 176 119 244 133 118 257 163 124 130 206 211 185 87 99 67 127 82 215 180 125 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +318569 4 0 0 5 0 0 0 0 0 9 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Coprococcus; s__ +291315 0 0 0 0 0 0 0 0 0 0 56 98 18 35 24 18 7 45 2 70 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Veillonellaceae; g__; s__ +196572 0 0 0 0 0 0 0 0 0 0 45 44 37 39 53 87 26 34 22 68 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +183241 0 0 0 0 0 0 0 0 0 0 60 50 42 47 15 32 20 28 62 34 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__[Ruminococcus]; s__ +174299 0 0 0 0 0 0 0 0 0 0 20 19 43 0 0 29 4 65 53 65 k__Bacteria; p__Firmicutes; c__Clostridia +178420 1127 301 482 614 252 756 635 390 925 620 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +198151 53 22 0 70 14 137 95 22 51 131 0 21 11 50 0 22 0 22 0 15 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +174588 0 0 0 0 0 0 0 0 0 0 146 132 147 100 63 126 37 105 43 49 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +177061 75 31 76 74 25 77 61 42 54 63 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__; f__; g__; s__ +189403 0 13 0 13 0 14 0 0 29 12 17 32 21 12 0 45 1 4 31 32 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +187882 12898 3377 5670 2690 2506 3395 581 0 1817 4681 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +819181 3 4 0 5 3 0 1 0 0 0 3 0 6 6 0 6 1 5 6 4 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae +4387208 733 359 342 977 350 989 650 199 971 775 787 533 799 670 824 188 134 806 796 703 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Enterococcaceae; g__Enterococcus; s__ +181573 0 0 0 0 0 0 0 0 0 0 39 0 66 0 6 14 17 29 0 41 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__[Ruminococcus]; s__ +342467 10 6 12 6 4 6 12 3 6 11 11 9 5 0 2 3 5 4 5 16 k__Bacteria; p__Firmicutes; c__Erysipelotrichi; o__Erysipelotrichales; f__Erysipelotrichaceae; g__[Eubacterium]; s__biforme +254446 0 0 0 0 0 0 0 0 0 0 1566 782 619 224 1236 950 627 979 1070 1570 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Peptostreptococcaceae; g__; s__ +193089 392 123 373 21 151 248 270 65 244 155 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +180285 58 9 30 79 23 36 21 33 61 62 50 70 96 53 67 68 18 57 30 27 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +194567 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +757622 0 0 0 16 0 0 16 0 0 3 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Veillonellaceae; g__Veillonella +184036 0 0 0 0 0 0 0 0 0 0 27 25 7 15 77 16 8 3 0 31 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +188861 30 2 0 12 4 12 19 5 8 19 0 3 11 11 0 28 0 13 16 1 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__; g__; s__ +182456 0 0 0 0 0 0 0 0 0 0 173 118 60 183 139 156 43 71 62 248 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +179727 4 6 1 0 1 0 1 6 0 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +177704 88 11 62 20 14 47 42 23 50 26 48 8 51 54 70 41 22 52 65 24 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +178664 31 2 2 0 0 0 0 0 0 18 26 11 17 17 35 0 6 0 39 24 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +301812 0 0 0 0 0 10 2 5 6 5 0 0 7 8 0 0 0 0 0 18 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +235262 177 41 137 91 34 63 130 43 119 72 82 67 115 141 73 142 38 98 86 134 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__Coriobacteriaceae; g__; s__ +196271 47 65 46 89 29 16 0 8 33 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +175619 0 0 0 0 0 0 0 0 0 0 37 51 6 25 40 13 1 0 1 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus +1079866 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Streptococcaceae; g__Streptococcus; s__ +4342682 561 250 319 324 147 398 420 140 536 369 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae +178675 88 81 22 177 60 176 103 48 163 112 92 120 78 158 191 137 60 166 99 177 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +848615 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 2 1 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__; f__; g__; s__ +257492 0 0 0 0 0 0 0 0 0 0 371 46 548 303 312 638 108 174 255 481 k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Aerococcaceae +336275 160 55 53 223 74 14 51 26 143 127 21 9 49 23 74 23 15 4 49 45 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +188707 23 22 24 27 8 16 40 12 29 7 2775 1657 2771 3663 1441 151 791 2638 3126 1260 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +309795 30 29 14 27 0 77 44 11 8 24 2 66 25 2 56 28 0 77 60 80 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +193446 42 13 46 125 30 65 64 41 64 170 41 60 51 100 103 91 35 122 136 84 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +4381092 0 1 0 0 0 0 10 0 0 0 0 0 8 8 11 0 0 0 0 12 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Clostridiaceae; g__; s__ +210262 49 78 166 179 63 102 108 29 139 158 101 140 129 184 201 131 32 188 182 126 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +515632 41 21 12 22 17 30 17 18 55 57 50 58 59 23 21 4 12 18 25 74 k__Bacteria; p__Firmicutes; c__Clostridia; o__; f__; g__; s__ +228748 0 0 1 0 0 0 0 0 0 0 6 0 0 0 3 0 0 0 3 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +215855 274 69 318 251 93 335 210 60 211 189 169 300 109 258 76 244 98 233 207 215 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +197326 0 5 14 5 0 3 0 0 0 0 1 0 0 0 5 0 0 0 0 5 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +197698 0 0 0 0 0 0 0 0 0 0 331 739 749 599 824 556 419 894 637 1232 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae +525215 0 0 0 0 0 0 0 0 0 0 502 241 436 256 330 231 41 265 252 192 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +190637 16 0 21 1 0 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +191555 0 0 0 0 0 11 8 0 0 5 0 0 0 4 1 11 6 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +182354 0 0 0 0 0 0 0 0 0 0 5991 9322 4017 9533 3768 5418 3137 2538 4501 7648 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Blautia; s__ +177986 33 6 28 12 15 17 0 30 46 53 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__; f__; g__; s__ +180637 1880 858 2219 1970 761 2063 1577 1133 1075 1628 1955 1498 1868 1134 1942 1927 745 1888 2074 1722 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +348642 0 0 0 0 0 0 0 0 0 0 2 0 5 10 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia +929836 47 50 97 74 33 61 0 50 59 68 75 72 31 129 93 34 40 66 97 121 k__Bacteria; p__Firmicutes; c__Erysipelotrichi; o__Erysipelotrichales; f__[Coprobacillaceae]; g__Coprobacillus; s__ +191797 105 77 23 63 47 35 18 59 66 48 53 3 20 99 49 21 0 93 77 71 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__; s__ +181139 18 0 16 12 1 13 27 10 8 6 15 0 11 6 0 0 0 2 0 6 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +312509 0 0 8 1 9 0 0 5 0 0 8 0 2 17 0 5 0 15 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Ruminococcus; s__ +190453 22 5 8 18 2 16 16 20 17 25 15 0 0 0 5 0 6 28 9 5 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +181644 0 0 0 0 0 0 0 0 0 0 8 6 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__Faecalibacterium; s__prausnitzii +4441081 12 1 4 1 7 5 10 1 3 4 0 6 12 15 0 12 0 0 8 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__Coriobacteriaceae +198439 0 0 37 0 3 0 1 5 12 4 3 0 7 0 11 0 4 0 16 22 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +513552 0 0 0 0 0 0 0 0 0 0 37 41 5 12 22 16 16 37 22 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Veillonellaceae; g__Anaerovibrio; s__ +199337 72 19 74 51 17 62 18 12 51 3 0 65 92 62 58 50 8 20 106 108 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__[Ruminococcus]; s__ +196660 16 11 23 37 0 28 37 11 0 43 209 46 59 184 188 71 64 73 76 103 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae +4473664 0 1 46 2 3 6 15 23 22 32 0 25 27 16 0 5 7 0 21 8 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Peptostreptococcaceae; g__Peptostreptococcus; s__anaerobius +175703 467 303 641 719 141 496 606 327 677 500 625 529 466 665 464 590 167 530 531 459 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__; g__; s__ +358914 0 0 9 0 0 24 0 0 14 0 0 8 0 27 6 0 0 20 0 28 k__Bacteria; p__Firmicutes; c__Clostridia; o__Coriobacteriales; f__Coriobacteriaceae; g__; s__ +351880 1057 606 1066 1037 265 1199 1114 544 530 515 58 41 0 30 0 35 0 47 50 31 k__Bacteria; p__Firmicutes; c__Clostridia +181003 42 12 39 30 10 1 9 2 35 26 12 25 41 44 27 39 9 34 58 20 k__Bacteria; p__Firmicutes; c__Clostridia +219959 210 118 177 210 117 243 138 93 188 174 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales +351027 0 0 0 0 0 0 0 0 0 0 6 0 19 0 54 0 6 14 25 15 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Ruminococcaceae; g__; s__ +192857 1 0 1 9 0 0 0 0 0 0 1 4 0 0 0 0 0 3 0 0 k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Roseburia; s__ +186510 0 10 11 39 0 0 11 4 0 5 14 40 3 34 57 13 4 45 0 16 k__Bacteria; p__Firmicutes; c__Clostridia +569889 236 216 389 445 114 490 257 233 355 641 258 659 370 317 433 550 65 171 358 462 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__ +4307094 6 10 17 13 8 12 15 9 27 16 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Paraprevotellaceae]; g__[Prevotella]; s__ +328035 1292 613 984 2124 655 885 1828 845 298 1290 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +4418496 61 18 12 89 10 10 74 41 77 73 78 0 99 22 113 88 9 25 68 29 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Porphyromonadaceae; g__Parabacteroides; s__ +314915 0 0 0 2 0 7 15 0 25 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Porphyromonadaceae; g__Parabacteroides; s__ +174036 45 11 12 37 9 33 23 15 20 35 63 59 28 15 63 73 4 23 0 23 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Odoribacteraceae]; g__Odoribacter; s__ +307571 202 52 383 184 69 374 184 207 123 255 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +181239 23 28 0 4 4 12 17 3 0 48 10 10 25 18 35 0 10 22 0 23 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Paraprevotellaceae]; g__Paraprevotella; s__ +530854 0 0 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 16 12 5 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__ +192684 15 8 45 2 9 16 31 10 17 25 31 5 9 17 26 0 5 20 5 17 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +3579707 0 0 0 0 0 0 0 0 0 0 0 8 23 0 0 8 0 0 0 21 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Odoribacteraceae]; g__Odoribacter; s__ +4330423 14 0 0 15 0 1 3 5 1 0 14 10 0 4 3 0 1 5 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__ +524044 23 31 56 93 0 76 47 25 34 69 51 0 0 26 0 72 13 81 37 4 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__S24-7; g__; s__ +199118 11 3 24 35 0 0 0 12 19 26 1039 966 938 568 897 659 237 734 697 725 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__S24-7; g__; s__ +294270 0 0 0 0 0 0 0 0 0 0 0 55 31 17 13 22 1 18 0 8 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__stercorea +130663 439 67 182 259 84 389 159 246 501 118 206 711 261 301 511 288 37 36 236 219 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__fragilis +173565 13 4 0 0 0 16 1 0 12 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +515798 660 242 409 566 329 834 1150 198 296 431 946 405 0 1121 377 917 163 908 595 850 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +299382 491 306 168 414 155 270 258 206 80 312 426 520 529 414 614 326 88 867 786 100 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__stercorea +181432 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Paraprevotellaceae]; g__Paraprevotella; s__ +516464 0 0 0 0 0 0 0 0 0 0 23 13 23 11 17 16 12 14 20 14 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +328936 58 68 127 86 43 58 123 36 108 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Paraprevotellaceae]; g__[Prevotella]; s__ +312973 265 247 634 153 92 0 589 105 294 316 403 383 455 252 226 332 125 0 426 403 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales +344318 1557 734 1373 1568 213 1537 400 579 2025 943 1160 822 1809 1240 1805 1263 280 1405 1863 1306 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +340819 1815 290 630 435 169 1354 1414 367 1200 1219 0 0 0 1 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +1701964 127 75 170 83 50 138 238 56 217 83 23 117 148 268 181 144 58 189 141 264 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__ +193591 3 0 0 0 0 0 0 0 0 0 9 0 0 20 0 0 0 9 14 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales +176230 125 39 156 133 49 116 240 61 176 90 228 104 135 166 124 26 5 150 146 141 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +2220494 0 0 0 0 3 2 0 1 20 1 18 0 0 7 0 4 0 4 1 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__stercorea +4395774 0 0 0 0 0 0 0 0 0 0 14 0 29 0 0 0 7 0 5 17 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +178387 0 18 102 129 7 55 78 38 86 88 186 63 0 94 65 167 6 116 27 103 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +331043 30 26 74 55 13 88 64 20 61 54 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__S24-7; g__; s__ +577294 0 3 0 0 0 0 0 0 0 0 0 0 2 0 0 2 0 0 0 6 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__; s__ +71331 0 0 0 0 0 10 0 0 5 8 0 5 0 0 0 0 0 2 2 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +495451 28 5 14 0 0 0 27 2 24 0 57 1 0 19 0 28 5 0 0 18 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Porphyromonadaceae; g__Porphyromonas; s__ +741701 0 0 0 0 0 0 0 0 0 0 757 991 800 628 304 816 103 678 1009 825 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__ +214031 10 0 4 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Rikenellaceae; g__; s__ +298451 40 2 10 15 4 30 0 8 0 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella +199534 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Rikenellaceae; g__; s__ +190526 0 7 0 1 0 1 0 0 0 6 0 7 0 10 1 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__stercorea +495017 0 0 0 0 0 0 0 0 0 0 341 444 385 452 343 481 66 485 484 300 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Porphyromonadaceae; g__Porphyromonas; s__ +524318 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +196296 1513 849 1615 1749 585 1809 1593 850 1289 1831 2185 1808 1079 1857 1616 1783 561 1401 1502 1886 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +159026 3 1 33 16 15 19 14 15 25 28 0 16 19 22 38 22 1 15 0 28 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Paraprevotellaceae]; g__; s__ +294196 37 38 193 50 7 122 230 84 210 298 240 227 185 257 248 215 19 108 79 175 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +185730 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__eggerthii +335447 0 0 0 5 0 0 0 0 3 0 4 0 0 0 0 13 3 9 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Porphyromonadaceae; g__Parabacteroides; s__ +519766 13 9 31 0 0 16 38 12 6 29 11 25 0 19 0 29 11 7 51 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Paraprevotellaceae]; g__[Prevotella]; s__ +3028318 73 0 95 114 31 108 71 86 126 118 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Rikenellaceae; g__; s__ +524891 0 0 0 0 0 0 0 0 0 0 0 24 94 61 83 80 50 59 29 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +2442708 0 0 0 0 0 0 0 0 0 0 6 0 0 0 0 0 3 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +300650 81 9 21 33 0 30 43 14 5 0 26 49 38 46 0 34 11 16 6 5 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Paraprevotellaceae]; g__[Prevotella]; s__ +4020502 0 0 0 0 0 0 0 0 0 0 1274 997 1766 780 533 1300 322 767 1466 484 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +299830 69 77 205 161 56 48 70 43 200 97 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__stercorea +182255 144 75 49 99 54 120 123 49 108 19 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +301253 20 29 107 43 17 55 57 18 55 34 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +184610 0 15 12 44 27 23 73 29 106 1 42 59 71 0 27 39 15 17 52 93 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__caccae +216550 0 0 0 0 0 0 0 0 0 0 0 0 16 5 10 46 0 0 11 12 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Rikenellaceae; g__; s__ +849393 13 12 22 26 0 0 0 0 23 4 5 32 11 12 0 25 0 14 17 1 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +531614 53 0 81 0 15 18 19 0 22 36 38 78 56 35 57 15 15 3 36 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +2550241 134 40 87 269 64 63 0 111 114 281 206 174 257 95 273 208 60 281 172 105 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +4318177 0 0 0 0 0 0 0 0 0 0 2191 1914 2390 1045 1843 1750 469 2170 1668 1409 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__; g__; s__ +175099 34 27 94 75 5 15 41 12 0 7 72 42 0 63 95 59 0 0 29 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales +176034 9 10 23 19 0 44 45 6 18 12 0 17 0 20 54 8 10 27 8 13 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +840027 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Porphyromonadaceae; g__Parabacteroides; s__distasonis +521431 29 0 19 31 0 58 26 0 18 48 8 10 3 0 8 3 0 31 12 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +353782 2 0 0 0 0 0 0 4 0 1 0 0 1 0 0 0 0 0 4 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__; s__ +307154 3028 1735 3677 2723 1130 3203 3363 1024 1702 3119 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +2243510 98 27 138 108 44 109 46 76 146 114 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +305946 4 0 18 6 1 7 0 0 0 3 0 0 0 28 0 0 5 11 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +4397200 5 0 0 16 0 0 18 7 8 25 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__ +2821809 2 18 35 8 2 5 60 12 89 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Paraprevotellaceae]; g__[Prevotella]; s__ +178860 97 40 78 45 19 117 97 54 84 73 1763 1093 923 1539 1689 1360 305 2009 1313 2183 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +308507 782 288 375 689 211 442 560 420 335 217 440 524 162 620 565 297 97 491 286 495 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales +4355075 6 5 4 8 5 18 3 1 0 37 3 0 6 29 13 34 16 0 23 12 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__ +347875 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +530865 489 193 368 254 144 500 0 0 636 376 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__S24-7; g__; s__ +4476964 302 316 355 687 194 251 116 259 648 594 317 246 174 460 306 613 123 537 221 487 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__fragilis +176660 7 0 14 0 0 17 0 3 0 36 2 0 28 0 20 0 1 0 33 48 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +3172943 0 0 0 0 0 0 0 0 0 0 0 23 31 2 67 28 9 34 24 33 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__S24-7; g__; s__ +205134 0 0 0 0 0 0 0 0 0 0 92 98 102 142 96 87 34 114 78 109 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__S24-7; g__; s__ +157396 0 28 22 141 42 156 62 38 0 147 0 0 84 94 99 122 23 77 87 94 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +315846 0 0 0 0 4 0 0 0 0 0 7 0 0 6 4 0 5 0 0 7 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Barnesiellaceae]; g__; s__ +183062 28 14 11 12 7 34 14 7 31 21 12 8 0 20 0 17 0 44 13 34 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__stercorea +4457872 0 0 0 0 0 0 0 0 0 0 54 40 46 7 14 50 5 34 34 54 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +196729 526 398 534 759 157 460 408 316 812 970 509 457 780 902 311 691 361 601 944 112 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +1889556 167 125 244 140 50 37 264 44 317 231 257 121 133 214 211 240 33 248 250 229 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales +4307092 104 50 62 215 8 3 136 55 91 96 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__stercorea +180133 0 0 0 0 0 0 0 0 0 0 277 125 366 303 27 161 51 163 212 264 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Barnesiellaceae]; g__; s__ +750071 11 32 87 46 15 62 0 6 22 52 48 102 23 68 25 24 9 49 56 86 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Paraprevotellaceae]; g__; s__ +849704 0 0 0 0 0 0 0 0 0 0 8 43 0 46 13 18 13 45 0 27 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__ +215670 337 148 295 182 79 433 280 155 312 273 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +191483 0 0 0 0 0 0 0 0 0 0 12 4 0 36 46 63 6 48 64 51 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +3940440 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +840279 56 15 44 33 14 34 56 17 24 21 0 0 0 1 0 0 3 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Barnesiellaceae]; g__; s__ +190309 0 0 0 0 0 0 0 0 0 0 9 0 11 10 0 0 0 0 0 3 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella +296082 2 0 42 0 1 19 31 0 29 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella +293717 0 0 0 0 0 0 0 0 0 0 39 45 40 36 9 42 4 80 29 56 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +772282 73 39 42 16 14 23 9 26 66 43 67 22 37 18 20 20 0 71 12 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Rikenellaceae; g__; s__ +4325533 452 148 303 448 182 278 667 310 387 495 897 486 591 133 537 364 190 701 793 343 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Rikenellaceae; g__; s__ +198573 0 0 0 0 0 0 3 0 2 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__stercorea +354850 158 70 198 136 51 110 138 88 184 138 162 293 178 183 123 141 64 176 94 144 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +319810 25 0 13 15 10 0 17 4 0 5 26 16 6 18 6 5 0 0 17 14 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella; s__copri +2617854 1208 746 829 1416 95 480 1247 680 1303 1029 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Rikenellaceae; g__; s__ +3090117 0 0 0 0 0 0 0 0 0 0 77 33 27 27 64 51 10 83 64 30 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Barnesiellaceae]; g__; s__ +4381553 32 56 71 58 18 32 73 44 48 20 50 34 24 97 131 79 40 103 64 79 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +4154872 3 0 0 29 2 3 15 11 16 4 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Flavobacteriia; o__Flavobacteriales; f__Flavobacteriaceae; g__; s__ +4331760 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Rikenellaceae; g__; s__ +195258 0 0 0 0 0 0 0 0 0 0 0 8 6 0 0 0 0 0 0 0 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +348027 0 0 0 0 0 0 0 0 0 0 413 290 219 235 179 218 75 254 301 288 k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Bacteroidaceae; g__Bacteroides; s__ +173654 0 4 17 0 0 0 4 3 0 0 1119 902 863 0 448 1112 153 946 1049 348 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__Escherichia; s__ +246717 44 19 17 5 4 20 17 27 26 16 13 25 49 65 51 55 7 44 2 14 k__Bacteria; p__Proteobacteria; c__Epsilonproteobacteria; o__Campylobacterales; f__Campylobacteraceae; g__Campylobacter; s__ +218267 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pseudomonadales; f__Pseudomonadaceae; g__Pseudomonas; s__ +299267 186 66 176 0 35 115 91 37 99 140 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae +301149 691 221 679 746 218 578 723 252 691 388 317 711 439 303 718 472 103 403 387 801 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__Escherichia; s__ +1974536 0 0 0 0 0 0 0 0 0 0 1146 351 1136 909 930 744 313 608 863 848 k__Bacteria; p__Proteobacteria; c__Betaproteobacteria; o__Burkholderiales; f__Alcaligenaceae; g__Sutterella; s__ +169182 0 0 0 0 0 0 0 0 0 0 1571 2544 3220 2493 1823 2752 557 3201 1706 2012 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__Escherichia; s__ +2924870 5 0 50 0 4 17 39 0 0 0 2 3 0 0 46 17 8 0 2 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__; s__ +301184 1817 1087 1163 1792 953 2689 2192 1286 1302 1586 2352 1338 2073 2187 2823 2315 731 845 1409 1432 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__Escherichia; s__ +4447416 37 15 23 47 1 57 14 15 35 38 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Proteobacteria; c__Betaproteobacteria; o__Neisseriales; f__Neisseriaceae; g__; s__ +329096 0 0 0 0 0 0 0 0 0 0 37 47 59 0 23 33 3 71 45 9 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__; s__ +178885 0 0 0 0 0 0 0 0 0 0 32 14 7 20 33 19 7 18 34 25 k__Bacteria; p__Proteobacteria; c__Betaproteobacteria; o__Burkholderiales; f__Alcaligenaceae; g__Sutterella; s__ +4473129 440 433 603 333 134 430 259 304 184 359 358 116 654 381 329 499 218 497 164 553 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pasteurellales; f__Pasteurellaceae; g__Haemophilus; s__parainfluenzae +817254 11 0 0 3 0 7 25 6 20 19 83 173 133 150 100 115 46 120 148 168 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae +3829957 1 7 0 20 1 3 15 4 19 8 10 7 0 10 4 9 2 11 21 18 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__; s__ +4430561 24 15 88 48 4 25 10 29 70 21 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__Escherichia; s__ +197286 0 0 0 0 0 0 0 0 0 0 257 195 283 192 151 113 81 198 146 225 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pseudomonadales; f__Pseudomonadaceae; g__; s__ +112891 0 0 12 0 3 3 0 4 0 0 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Proteobacteria; c__Betaproteobacteria; o__Burkholderiales; f__Alcaligenaceae; g__Sutterella; s__ +4466616 0 0 0 0 0 0 0 0 0 0 92 33 58 97 72 80 23 62 92 112 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pasteurellales; f__Pasteurellaceae; g__Aggregatibacter; s__segnis +516159 86 71 151 7 43 19 85 31 23 0 0 135 115 225 48 125 68 227 142 79 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Aeromonadales; f__Succinivibrionaceae; g__Succinivibrio; s__ +231787 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__; s__ +1109362 137 14 302 195 33 280 173 101 159 241 295 190 60 165 236 233 47 132 289 264 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae +1086582 0 0 0 0 0 0 0 0 0 0 334 288 194 205 372 284 8 338 278 302 k__Bacteria; p__Proteobacteria; c__Betaproteobacteria; o__Neisseriales; f__Neisseriaceae; g__Neisseria; s__ +4295743 360 0 526 419 82 340 373 225 354 124 375 560 282 576 372 317 110 248 294 143 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pasteurellales; f__Pasteurellaceae; g__Haemophilus; s__parainfluenzae +1105894 6 0 5 5 1 4 10 0 0 33 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__Escherichia; s__ +3441895 31 63 72 56 14 28 81 38 39 82 117 118 80 12 108 61 12 52 143 87 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__; s__ +4453773 72 2 77 90 25 0 32 57 9 110 36 121 101 84 108 15 49 25 84 100 k__Bacteria; p__Proteobacteria; c__Deltaproteobacteria; o__Desulfovibrionales; f__Desulfovibrionaceae; g__Desulfovibrio; s__ +290774 3 1 11 6 2 12 1 0 0 0 10 0 0 14 0 0 0 0 0 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__; s__ +4404220 153 69 101 108 31 144 186 30 17 97 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pasteurellales; f__Pasteurellaceae; g__; s__ +4430639 48 5 0 19 0 43 9 12 0 9 0 0 0 0 0 0 0 10 9 8 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pasteurellales; f__Pasteurellaceae; g__Haemophilus; s__parainfluenzae +4448331 0 0 0 0 0 0 0 0 0 0 1 0 0 5 0 0 0 0 0 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__; s__ +4473176 0 0 0 0 0 0 0 0 0 0 180 115 144 114 186 113 28 160 145 191 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__Enterobacter; s__hormaechei +541119 780 353 869 866 273 831 882 452 945 897 0 6 0 0 4 0 1 0 5 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__; s__ +4375000 687 225 722 566 222 636 798 328 179 623 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__; s__ +832805 3 0 0 0 0 0 18 0 11 0 0 0 2 3 0 0 0 0 0 0 k__Bacteria; p__Proteobacteria; c__Betaproteobacteria; o__Burkholderiales; f__Comamonadaceae; g__Comamonas; s__ +4320756 18 0 30 50 0 0 46 17 0 25 38 28 18 19 0 0 2 19 2 12 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pasteurellales; f__Pasteurellaceae; g__Haemophilus; s__parainfluenzae +4376234 0 3 0 3 0 0 0 0 0 0 9 3 0 0 0 4 5 0 0 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__Enterobacter; s__hormaechei +2295957 14 10 0 13 0 0 0 8 6 18 5 14 0 22 22 18 0 14 16 10 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__Escherichia; s__ +360508 8 11 14 30 9 22 14 3 15 0 56 12 37 31 31 12 8 32 9 35 k__Bacteria; p__Proteobacteria; c__Betaproteobacteria; o__Burkholderiales; f__Oxalobacteraceae; g__Oxalobacter +1109454 11 0 2 15 3 0 2 0 13 11 0 0 22 10 7 15 0 0 0 0 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__Citrobacter; s__ +4388820 7077 2045 4707 4018 1595 2071 3123 589 4854 2145 4969 4885 3661 4700 2686 5770 1103 4914 4582 1813 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__; s__ +1061192 0 0 0 0 0 0 0 0 0 0 210 169 69 35 111 153 36 13 153 133 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Cardiobacteriales; f__Cardiobacteriaceae; g__Cardiobacterium; s__valvarum +4477696 52 60 117 85 22 81 74 40 79 45 166 157 149 186 133 120 70 88 89 90 k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pasteurellales; f__Pasteurellaceae; g__Haemophilus; s__parainfluenzae +283765 2 4 5 30 8 38 0 12 0 39 988 1129 931 925 709 1099 413 983 1113 935 k__Bacteria; p__Actinobacteria; c__Actinobacteria; o__Actinomycetales; f__Micrococcaceae; g__Kocuria +69933 62 0 0 88 49 178 148 23 161 168 177 84 35 70 186 136 2 222 85 97 k__Bacteria; p__Actinobacteria; c__Actinobacteria; o__Bifidobacteriales; f__Bifidobacteriaceae; g__Bifidobacterium; s__ +73471 0 0 0 0 0 0 0 0 0 0 0 0 20 0 0 15 7 0 2 9 k__Bacteria; p__Actinobacteria; c__Actinobacteria; o__Actinomycetales; f__Corynebacteriaceae; g__Corynebacterium; s__ +403918 0 0 0 0 0 0 0 0 0 0 125 70 38 43 79 49 55 140 115 120 k__Bacteria; p__Actinobacteria; c__Actinobacteria; o__Actinomycetales; f__Nocardioidaceae; g__Propionicimonas; s__ +553611 0 0 0 0 0 0 0 0 0 0 42 41 23 27 22 41 3 36 30 39 k__Bacteria; p__Actinobacteria; c__Actinobacteria; o__Bifidobacteriales; f__Bifidobacteriaceae; g__Bifidobacterium +867184 115 21 35 26 19 19 81 11 36 59 0 0 0 8 33 0 0 7 15 0 k__Bacteria; p__Actinobacteria; c__Actinobacteria; o__Actinomycetales; f__Actinomycetaceae; g__Parascardovia; s__ +559527 115 123 157 157 95 204 157 98 224 228 224 192 215 166 222 315 80 259 210 298 k__Bacteria; p__Actinobacteria; c__Actinobacteria; o__Bifidobacteriales; f__Bifidobacteriaceae; g__Bifidobacterium; s__longum +185186 2 2 86 28 2 7 33 0 47 25 22 62 23 40 0 32 7 33 47 70 k__Bacteria; p__Verrucomicrobia; c__Verrucomicrobiae; o__Verrucomicrobiales; f__Verrucomicrobiaceae; g__Akkermansia; s__muciniphila +193829 0 0 0 0 0 0 0 0 0 0 46 21 77 57 64 68 11 44 77 16 k__Bacteria; p__Verrucomicrobia; c__Verrucomicrobiae; o__Verrucomicrobiales; f__Verrucomicrobiaceae; g__Akkermansia; s__muciniphila +180110 0 0 0 0 0 0 0 0 0 0 396 413 640 394 569 1042 349 145 296 577 k__Bacteria; p__Verrucomicrobia; c__Verrucomicrobiae; o__Verrucomicrobiales; f__Verrucomicrobiaceae; g__Akkermansia; s__muciniphila +287660 15 14 31 31 11 37 42 7 0 64 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Fusobacteria; c__Fusobacteria; o__Fusobacteriales; f__Fusobacteriaceae; g__Cetobacterium +518865 0 0 0 0 0 0 0 0 0 0 48 5 0 52 0 54 8 30 0 16 k__Bacteria; p__Fusobacteria; c__Fusobacteria; o__Fusobacteriales; f__Fusobacteriaceae; g__Fusobacterium; s__ +297672 15 0 8 9 1 0 0 0 0 11 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Fusobacteria; c__Fusobacteria; o__Fusobacteriales; f__Fusobacteriaceae; g__Cetobacterium; s__somerae +293351 1238 1163 1032 1041 224 149 1851 470 232 1199 1680 1212 862 1827 1272 1351 440 759 216 478 k__Bacteria; p__Fusobacteria; c__Fusobacteria; o__Fusobacteriales; f__Fusobacteriaceae; g__Cetobacterium; s__somerae +4412860 20 9 16 8 0 11 33 2 27 14 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Cyanobacteria; c__Chloroplast; o__Streptophyta; f__; g__; s__ +820901 41 25 53 44 14 30 23 29 63 43 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Cyanobacteria; c__4C0d-2; o__YS2; f__; g__; s__ +4422313 0 0 0 0 0 0 0 0 0 0 88 124 382 381 391 339 37 178 0 234 k__Bacteria; p__Cyanobacteria; c__Chloroplast; o__Streptophyta; f__; g__; s__ +4359258 49 28 60 47 14 62 52 16 36 52 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Cyanobacteria; c__4C0d-2; o__YS2; f__; g__; s__ +524633 1 0 0 9 4 0 8 0 6 1 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Tenericutes; c__Mollicutes; o__Anaeroplasmatales; f__Anaeroplasmataceae; g__; s__ +225636 70 0 114 60 11 130 140 91 159 109 0 0 0 0 0 0 0 0 0 0 k__Bacteria; p__Tenericutes; c__Mollicutes; o__Anaeroplasmatales; f__Anaeroplasmataceae; g__; s__ +4308127 45 28 80 17 7 54 73 56 43 107 127 132 73 65 138 131 37 97 81 111 k__Bacteria; p__Tenericutes; c__Mollicutes; o__RF39; f__; g__; s__ +4388068 76 47 168 71 83 161 270 176 219 155 216 91 143 169 211 202 88 243 203 134 k__Bacteria; p__Tenericutes; c__Mollicutes; o__RF39; f__; g__; s__ +1129069 12 0 3 0 1 0 0 0 21 22 4 27 0 0 0 4 3 0 0 3 k__Bacteria; p__Tenericutes; c__Mollicutes; o__RF39; f__; g__; s__ +569244 0 67 523 822 65 0 507 127 437 370 556 587 420 779 383 275 114 766 282 922 k__Bacteria; p__Tenericutes; c__Mollicutes; o__RF39; f__; g__; s__ +521033 34 16 31 0 9 12 11 13 0 29 0 0 0 0 10 0 2 19 0 13 k__Bacteria; p__Tenericutes; c__Mollicutes; o__RF39; f__; g__; s__ +4144206 4 0 0 0 9 6 0 1 0 0 0 1 10 1 0 0 0 0 2 0 k__Bacteria; p__Tenericutes; c__Mollicutes; o__Anaeroplasmatales; f__Anaeroplasmataceae; g__; s__