ucsc-xena-client
Version:
UCSC Xena Client. Functional genomics visualizations.
1,584 lines (1,390 loc) • 52.7 kB
JavaScript
'use strict';
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
var getLabel = require('./getLabel');
var _require = require('./color_helper'),
hexToRGB = _require.hexToRGB,
colorStr = _require.colorStr;
var Highcharts = require('highcharts/highstock');
require('highcharts/highcharts-more')(Highcharts);
var highchartsHelper = require('./highcharts_helper');
require('highcharts/modules/boost')(Highcharts);
var _ = require('./underscore_ext');
var colorScales = require('./colorScales');
var customColors = {};
var jStat = require('jStat').jStat;
// Styles
var compStyles = require('./chart.module.css');
var getCustomColor = function getCustomColor(fieldSpecs, fields, dataset) {
return fields.length === 1 ? _.getIn(dataset, ['customcolor', fieldSpecs[0].fields[0]], null) : null;
};
var chartHeight = function chartHeight() {
return window.innerHeight * 0.7 + "px";
};
var chartWidth = function chartWidth() {
return window.innerWidth * 0.7 + "px";
};
function render(root, callback, sessionStorage) {
var xdiv,
ydiv,
xAxisDiv,
yAxisDiv,
// x y axis dropdown
colorDiv,
colorAxisDiv,
// color dropdown
statsDiv,
// statistics
row,
axisContainer,
leftContainer,
chartContainer,
xenaState = sessionStorage.xena ? JSON.parse(sessionStorage.xena) : undefined,
cohort,
samplesLength,
cohortSamples,
updateArgs,
update,
normalizationState = {},
expState = {},
expXState = {},
chart;
if (xenaState) {
cohort = xenaState.cohort;
samplesLength = xenaState.samples.length;
cohortSamples = xenaState.cohortSamples;
normalizationState = _.getIn(xenaState, ['chartState', 'normalizationState'], {});
expState = _.getIn(xenaState, ['chartState', 'expState'], {});
expXState = _.getIn(xenaState, ['chartState', 'expXState'], {});
}
updateArgs = [cohort, samplesLength, cohortSamples];
function setStorage(state) {
sessionStorage.xena = JSON.stringify(state);
callback(['chart-set-state', state.chartState]);
}
function columnLabel(i, colSetting) {
var label = [colSetting.user.fieldLabel, colSetting.user.columnLabel].filter(function (e) {
return e.trim() !== '';
}).join(" - ");
return "column " + getLabel(i) + ": " + label;
}
function buildNormalizationDropdown() {
var dropDownDiv,
option,
dropDown = [{
"value": "none",
"text": "none",
"index": 0
}, //no normalization
{
"value": "subset",
"text": "subtract mean",
"index": 1
}, //selected sample level current heatmap normalization
{
"value": "subset_stdev",
"text": "subtract mean, divide stdev (z-score)",
"index": 2 //selected sample level current heatmap normalization
}],
node = document.createElement("div"),
labelDiv = document.createElement("label");
dropDownDiv = document.createElement("select");
dropDownDiv.setAttribute("id", "ynormalization");
dropDownDiv.setAttribute("class", "form-control");
dropDown.map(function (obj) {
option = document.createElement('option');
option.value = obj.value;
option.textContent = obj.text;
dropDownDiv.appendChild(option);
});
dropDownDiv.selectedIndex = 0; //tocken, action in normalizationUISetting function
dropDownDiv.addEventListener('change', function () {
normalizationState[xenaState.chartState.ycolumn] = dropDownDiv.selectedIndex;
update.apply(this, updateArgs);
});
node.className = compStyles.column;
node.setAttribute("id", "normDropDown");
labelDiv.appendChild(document.createTextNode("Y data linear transform "));
node.appendChild(labelDiv);
node.appendChild(dropDownDiv);
return node;
}
function buildYExpDropdown() {
var dropDownDiv,
option,
dropDown = [{
"value": "none",
"index": 0
}, {
//convert x -> base-2 exponential value of x, for when viewing raw RNAseq data, xena typically converts RNAseq values into log2 space.
"value": "exp2",
"index": 1
}],
node = document.createElement("div"),
labelDiv = document.createElement("label");
dropDownDiv = document.createElement("select");
dropDownDiv.setAttribute("id", "yExponentiation");
dropDownDiv.setAttribute("class", "form-control");
dropDown.map(function (obj) {
option = document.createElement('option');
option.value = obj.value;
option.textContent = obj.text;
dropDownDiv.appendChild(option);
});
dropDownDiv.selectedIndex = 0;
dropDownDiv.addEventListener('change', function () {
expState[xenaState.chartState.ycolumn] = dropDownDiv.selectedIndex;
update.apply(this, updateArgs);
});
node.className = compStyles.column;
node.setAttribute("id", "expYDropDown");
labelDiv.appendChild(document.createTextNode("Y unit "));
node.appendChild(labelDiv);
node.appendChild(dropDownDiv);
return node;
}
function buildXExpDropdown() {
var dropDownDiv,
option,
dropDown = [{
"value": "none",
"index": 0
}, {
//convert x -> base-2 exponential value of x, for when viewing raw RNAseq data, xena typically converts RNAseq values into log2 space.
"value": "exp2",
"index": 1
}],
node = document.createElement("div"),
labelDiv = document.createElement("label");
dropDownDiv = document.createElement("select");
dropDownDiv.setAttribute("id", "xExponentiation");
dropDownDiv.setAttribute("class", "form-control");
dropDown.map(function (obj) {
option = document.createElement('option');
option.value = obj.value;
option.textContent = obj.text;
dropDownDiv.appendChild(option);
});
dropDownDiv.selectedIndex = 0;
dropDownDiv.addEventListener('change', function () {
expXState[xenaState.chartState.xcolumn] = dropDownDiv.selectedIndex;
update.apply(this, updateArgs);
});
node.className = compStyles.column;
node.setAttribute("id", "expXDropDown");
labelDiv.appendChild(document.createTextNode("X unit "));
node.appendChild(labelDiv);
node.appendChild(dropDownDiv);
return node;
}
function buildEmptyChartContainer() {
var chartdiv = document.createElement("div");
chartdiv.setAttribute("id", "myChart");
chartdiv.style.height = chartHeight();
chartdiv.style.width = chartWidth();
return chartdiv;
}
function colUnit(colSettings) {
if (!colSettings.units) {
return "";
}
return colSettings.units.join();
}
function axisReview() {
_.map(xdiv.options, function (option) {
return option.disabled = false;
});
_.map(ydiv.options, function (option) {
return option.disabled = false;
});
var data = _.getIn(xenaState, ['data']),
xcolumn = xdiv.options[xdiv.selectedIndex].value,
ycolumn = ydiv.options[ydiv.selectedIndex].value;
// x single float, disable y multiple float of series > 10
if (xcolumn !== "none" && !data[xcolumn].codes) {
_.map(ydiv.options, function (option) {
var y = option.value;
if (data[y].req.values && data[y].req.values.length > 10) {
option.disabled = true;
}
});
}
// if y is multiple float, disable x single float series > 10
if (data[ycolumn].req.values && data[ycolumn].req.values.length > 10) {
_.map(xdiv.options, function (option) {
var x = option.value;
if (x !== "none" && !data[x].codes) {
option.disabled = true;
}
});
}
}
function axisSelector(selectorID) {
var div, option, column, storedColumn, columns, columnOrder, data;
if (xenaState) {
columnOrder = xenaState.columnOrder;
columns = xenaState.columns;
data = xenaState.data;
}
if (xenaState && xenaState.chartState) {
if (xenaState.cohort && _.isEqual(xenaState.cohort, xenaState.chartState.cohort)) {
if (selectorID === "Xaxis") {
storedColumn = xenaState.chartState.xcolumn;
} else if (selectorID === "Yaxis") {
storedColumn = xenaState.chartState.ycolumn;
} else if (selectorID === "Color") {
storedColumn = xenaState.chartState.colorColumn;
}
}
}
div = document.createElement("select");
div.setAttribute("id", selectorID);
div.className = "form-control"; //"btn btn-default dropdown-toggle";
// color dropdown add none option at the top
if (selectorID === "Color") {
// none -- no color at the beginning
option = document.createElement('option');
option.value = "none";
option.textContent = "None";
div.appendChild(option);
}
_.map(columnOrder, function (column, i) {
if (column === "samples") {
//ignore samples column
return;
}
if (columns[column].valueType === "mutation") {
// to be implemented
return;
}
if (data[column].status !== "loaded") {
//bad column
return;
}
if (data[column].req.values && data[column].req.values.length === 0) {
//bad column
return;
}
if (data[column].req.rows && data[column].req.rows.length === 0) {
//bad column
return;
}
if (data[column].codes && _.uniq(data[column].req.values[0]).length > 100) {
// ignore any coded columns with too many items, like in most cases, the "samples" column
return;
}
if ((selectorID === "Xaxis" || selectorID === "Color") && data[column].req.values && data[column].req.values.length !== 1) {
return;
}
option = document.createElement('option');
option.value = column;
option.textContent = columnLabel(i, columns[column]);
div.appendChild(option);
if (column === storedColumn) {
div.selectedIndex = div.length - 1;
}
});
// x axis add none option at the end-- summary view
if (selectorID === "Xaxis") {
option = document.createElement('option');
option.value = "none";
option.textContent = "Histogram/Distribution";
div.appendChild(option);
if ("none" === storedColumn) {
div.selectedIndex = div.length - 1;
}
}
if (div.length === 0) {
return;
}
// default settings
// trying to use coded column for X and float column for y
if (!storedColumn) {
if (selectorID === "Xaxis") {
div.selectedIndex = div.length - 1;
for (var _i = 0; _i < div.length - 1; _i++) {
column = div.options[_i].value;
if (columns[column].valueType === "coded") {
div.selectedIndex = _i;
break;
}
}
} else if (selectorID === "Yaxis") {
div.selectedIndex = 0;
for (var _i2 = 0; _i2 < div.length; _i2++) {
column = div.options[_i2].value;
if (columns[column].valueType === "float") {
div.selectedIndex = _i2;
break;
}
}
}
if (selectorID === "Yaxis") {
var xvalue = xdiv.options[xdiv.selectedIndex].value,
yvalue = div.options[div.selectedIndex].value;
if (xvalue !== "none" && !data[xvalue].codes && data[yvalue].req.values.length > 2) {
// x if float and y is multi-float series with >2 series
xdiv.options.selectedIndex = xdiv.options.length - 1;
xvalue = "none";
}
if (xvalue === yvalue) {
// x and y axis is the same
var i;
for (i = 0; i < div.length; i++) {
column = div.options[i].value;
if (column !== xvalue) {
div.selectedIndex = i;
break;
}
}
// no good choice for both x and y => set x to none -summary view
if (i === div.length) {
xdiv.options.selectedIndex = xdiv.options.length - 1;
}
}
}
}
div.addEventListener('change', function () {
update.apply(this, updateArgs);
});
var labelDiv = document.createElement("label"),
listDiv = document.createElement("div"),
returnDiv = document.createElement("div");
if (selectorID === "Xaxis") {
xdiv = div;
labelDiv.appendChild(document.createTextNode("X axis"));
} else if (selectorID === "Yaxis") {
ydiv = div;
labelDiv.appendChild(document.createTextNode("Y axis"));
} else if (selectorID === "Color") {
colorDiv = div;
labelDiv.appendChild(document.createTextNode("Color"));
}
listDiv.appendChild(div);
returnDiv.appendChild(labelDiv);
returnDiv.appendChild(listDiv);
returnDiv.className = compStyles.column;
return returnDiv;
}
function normalizationUISetting(visible, ycolumn) {
var dropDown = document.getElementById("normDropDown"),
dropDownDiv = document.getElementById("ynormalization");
if (visible) {
dropDown.style.visibility = "visible";
//check current normalizationState variable
if (normalizationState[ycolumn] !== undefined) {
dropDownDiv.selectedIndex = normalizationState[ycolumn];
}
} else {
dropDown.style.visibility = "hidden";
}
}
function expUISetting(visible, expState, column, colSettings, dropDown, dropDownDiv, data) {
if (visible && colSettings.units) {
dropDown.style.visibility = "visible";
//check current expState variable
if (expState[column] !== undefined) {
dropDownDiv.selectedIndex = expState[column];
} else {
dropDownDiv.selectedIndex = 0;
expState[column] = 0;
}
var notLogScale = _.any(colSettings.units, function (unit) {
return !unit || unit.search(/log/i) === -1;
});
if (notLogScale) {
// unit labels first option
if (_.filter(colSettings.units, function (unit) {
return unit;
}).length === 0) {
dropDownDiv.options[0].text = "unknown";
} else {
dropDownDiv.options[0].text = colSettings.units.join();
}
// unit labels second option
if (_.some(data, function (d) {
return _.some(d, function (v) {
return v < 0;
});
})) {
dropDownDiv.options[1].text = '';
} else {
if (_.filter(colSettings.units, function (unit) {
return unit;
}).length === 0) {
dropDownDiv.options[1].text = '';
} else {
dropDownDiv.options[1].text = "log2(" + colSettings.units.join() + "+1)";
dropDownDiv.options[1].value = "log2";
}
}
} else {
// unit labels
dropDownDiv.options[0].text = colSettings.units.join();
var unitsString = colUnit(colSettings);
var regExp = /\(([^)]+)\)/;
var matches = regExp.exec(unitsString);
matches = matches ? matches[1] : '';
dropDownDiv.options[1].text = matches; // remove log in unit label
}
} else {
dropDown.style.visibility = "hidden";
dropDownDiv.selectedIndex === 0;
dropDownDiv.value = "none";
}
}
function scatterColorUISetting(visible) {
if (visible) {
colorAxisDiv.style.visibility = "visible";
} else {
colorAxisDiv.style.visibility = "hidden";
}
}
// returns key:array
// categorical: key:array ------ key is the category
// float: key: {xcode:array} key is the identifier, xcode is the xcode
function parseYDataElement(yfield, ycodemap, ydataElement, xcategories, xSampleCode) {
var i,
code,
ybinnedSample = {};
if (ycodemap) {
// y: categorical in matrix data
ycodemap.forEach(function (code) {
ybinnedSample[code] = [];
});
// probes by samples
for (i = 0; i < ydataElement.length; i++) {
code = ycodemap[ydataElement[i]];
if (code) {
ybinnedSample[code].push(i);
}
}
// remove empty ycode categories
ycodemap.forEach(function (code) {
if (ybinnedSample[code].length === 0) {
delete ybinnedSample[code];
}
});
} else {
// y: float in matrix data -- binned by xcode
if (xcategories) {
ybinnedSample[yfield] = {};
xcategories.forEach(function (code) {
ybinnedSample[yfield][code] = [];
});
} else {
ybinnedSample[yfield] = [];
}
for (i = 0; i < ydataElement.length; i++) {
if (null != ydataElement[i]) {
if (xSampleCode) {
code = xSampleCode[i];
if (code) {
ybinnedSample[yfield][code].push(ydataElement[i]);
}
} else {
ybinnedSample[yfield].push(ydataElement[i]);
}
}
}
}
return ybinnedSample;
}
function destroy() {
if (chart) {
chart.destroy();
chart = undefined;
}
}
function drawChart(cohort, samplesLength, xfield, xcodemap, xdata, yfields, ycodemap, ydata, offsets, xlabel, ylabel, STDEV, scatterLabel, scatterColorScale, scatterColorData, scatterColorDataCodemap, samplesMatched, columns, xcolumn, ycolumn, colorColumn) {
var yIsCategorical = ycodemap ? true : false,
xIsCategorical = xcodemap ? true : false,
chartOptions = _.clone(highchartsHelper.chartOptions),
xAxisTitle,
ybinnedSample,
dataSeriese,
nNumberSeriese,
yfield,
ydataElement,
showLegend,
xbinnedSample,
xSampleCode,
code,
categories,
i,
k,
average,
stdDev,
pValue,
dof,
total;
destroy();
document.getElementById("myChart").innerHTML = "Generating chart ...";
chartOptions.subtitle = {
text: "cohort: " + _.get(cohort, 'name') + " (n=" + samplesLength + ")"
};
if (xIsCategorical && !yIsCategorical) {
// x : categorical y float
var xCategories = [],
meanMatrix = [],
medianMatrix = [],
// median row is x and column is y
upperMatrix = [],
// 75 percentile row is x and column is y
lowerMatrix = [],
// 25 percentile row is x and column is y
upperwhiskerMatrix = [],
// upperwhisker percentile row is x and column is y
lowerwhiskerMatrix = [],
// lowerwhisker percentile row is x and column is y
stdMatrix = [],
// row is x and column is y
nNumberMatrix = [],
// number of data points (real data points) for dataMatrix
row,
highlightcode = [];
xSampleCode = {};
xbinnedSample = {};
// x data
xcodemap.forEach(function (code) {
xbinnedSample[code] = [];
});
//probes by samples
for (i = 0; i < xdata[0].length; i++) {
code = xcodemap[xdata[0][i]];
if (code) {
xbinnedSample[code].push(i);
xSampleCode[i] = code;
}
}
// remove empty xcode categories
xcodemap.map(function (code) {
if (xbinnedSample[code].length === 0) {
delete xbinnedSample[code];
} else {
xCategories.push(code);
}
});
// highlight categories identification : if all the samples in the category are part of the highlighted samples, the caterory will be highlighted
if (samplesMatched && samplesMatched.length !== samplesLength) {
xCategories.map(function (code) {
if (xbinnedSample[code].every(function (sample) {
return samplesMatched.indexOf(sample) !== -1;
})) {
highlightcode.push(code);
}
});
}
// init average matrix std matrix // row is x by column y
for (i = 0; i < xCategories.length; i++) {
row = [];
for (k = 0; k < yfields.length; k++) {
row.push(NaN);
}
meanMatrix.push(_.clone(row));
medianMatrix.push(_.clone(row));
upperMatrix.push(_.clone(row));
lowerMatrix.push(_.clone(row));
upperwhiskerMatrix.push(_.clone(row));
lowerwhiskerMatrix.push(_.clone(row));
stdMatrix.push(_.clone(row));
nNumberMatrix.push(_.clone(row));
}
// Y data and fill in the matrix
for (k = 0; k < yfields.length; k++) {
yfield = yfields[k];
ydataElement = ydata[k];
ybinnedSample = parseYDataElement(yfield, ycodemap, ydataElement, xCategories, xSampleCode);
for (i = 0; i < xCategories.length; i++) {
code = xCategories[i];
var data = void 0,
m = void 0;
if (ybinnedSample[yfield][code].length) {
data = ybinnedSample[yfield][code], m = data.length;
data.sort(function (a, b) {
return a - b;
});
average = highchartsHelper.average(data);
stdDev = highchartsHelper.standardDeviation(data, average);
// http://onlinestatbook.com/2/graphing_distributions/boxplots.html
var median = data[Math.floor(m / 2)],
lower = data[Math.floor(m / 4)],
upper = data[Math.floor(3 * m / 4)],
whisker = 1.5 * (upper - lower),
upperwhisker = _.findIndex(data, function (x) {
return x > upper + whisker;
}),
lowerwhisker = _.findLastIndex(data, function (x) {
return x < lower - whisker;
});
upperwhisker = upperwhisker === -1 ? data[data.length - 1] : data[upperwhisker - 1];
lowerwhisker = lowerwhisker === -1 ? data[0] : data[lowerwhisker + 1];
meanMatrix[i][k] = average / STDEV[yfield];
medianMatrix[i][k] = median / STDEV[yfield];
lowerMatrix[i][k] = lower / STDEV[yfield];
upperMatrix[i][k] = upper / STDEV[yfield];
lowerwhiskerMatrix[i][k] = lowerwhisker / STDEV[yfield];
upperwhiskerMatrix[i][k] = upperwhisker / STDEV[yfield];
nNumberMatrix[i][k] = m;
if (!isNaN(stdDev)) {
stdMatrix[i][k] = stdDev / STDEV[yfield];
} else {
stdMatrix[i][k] = NaN;
}
} else {
nNumberMatrix[i][k] = 0;
}
}
}
//add data seriese
var offsetsSeries = [],
cutOffset;
showLegend = true;
// offsets
for (k = 0; k < yfields.length; k++) {
yfield = yfields[k];
offsetsSeries.push(offsets[yfield] / STDEV[yfield]);
}
cutOffset = function cutOffset(_ref) {
var _ref2 = _slicedToArray(_ref, 2),
average = _ref2[0],
offset = _ref2[1];
if (!isNaN(average)) {
return average - offset;
} else {
return "";
}
};
var scale = colorScales.colorScale(columns[xcolumn].colors[0]),
invCodeMap = _.invert(xcodemap);
// column chart setup
chartOptions = highchartsHelper.columnChartFloat(chartOptions, yfields, xlabel, ylabel);
chart = new Highcharts.Chart(chartOptions);
for (i = 0; i < xCategories.length; i++) {
code = xCategories[i];
// http://onlinestatbook.com/2/graphing_distributions/boxplots.html
var medianSeriese = _.zip(medianMatrix[i], offsetsSeries).map(cutOffset),
upperSeriese = _.zip(upperMatrix[i], offsetsSeries).map(cutOffset),
lowerSeriese = _.zip(lowerMatrix[i], offsetsSeries).map(cutOffset),
upperwhiskerSeriese = _.zip(upperwhiskerMatrix[i], offsetsSeries).map(cutOffset),
lowerwhiskerSeriese = _.zip(lowerwhiskerMatrix[i], offsetsSeries).map(cutOffset);
nNumberSeriese = nNumberMatrix[i];
var _color = highlightcode.length === 0 ? scale(invCodeMap[code]) : highlightcode.indexOf(code) === -1 ? '#A9A9A9' : 'gold';
dataSeriese = _.zip(lowerwhiskerSeriese, lowerSeriese, medianSeriese, upperSeriese, upperwhiskerSeriese);
highchartsHelper.addSeriesToColumn(chart, 'boxplot', code, dataSeriese, yIsCategorical, yfields.length * xCategories.length < 30, showLegend, _color, nNumberSeriese);
}
// p value when there is only 2 group comparison student t-test
// https://en.wikipedia.org/wiki/Welch%27s_t-test
if (xCategories.length === 2) {
statsDiv.innerHTML = 'Welch\'s t-test<br>';
_.range(yfields.length).map(function (k) {
if (nNumberMatrix[0][k] > 1 && nNumberMatrix[1][k] > 1) {
yfield = yfields[k];
// p value calculation using Welch's t-test
var x1 = meanMatrix[0][k],
// mean1
x2 = meanMatrix[1][k],
// mean2
v1 = stdMatrix[0][k] * stdMatrix[0][k],
//variance 1
v2 = stdMatrix[1][k] * stdMatrix[1][k],
//variance 2
n1 = nNumberMatrix[0][k],
// number 1
n2 = nNumberMatrix[1][k],
// number 2
vCombined = v1 / n1 + v2 / n2,
// pooled variance
sCombined = Math.sqrt(vCombined),
//pool sd
tStatistics = (x1 - x2) / sCombined,
// t statistics,
cdf = void 0;
dof = vCombined * vCombined / (v1 / n1 * (v1 / n1) / (n1 - 1) + v2 / n2 * (v2 / n2) / (n2 - 1)), // degree of freedom
cdf = jStat.studentt.cdf(tStatistics, dof), pValue = 2 * (cdf > 0.5 ? 1 - cdf : cdf);
statsDiv.innerHTML += (yfields.length > 1 ? '<br>' + yfield + '<br>' : '') + 'p = ' + pValue.toPrecision(4) + ', ' + '(t = ' + tStatistics.toPrecision(4) + ')<br>';
}
});
statsDiv.classList.toggle(compStyles.visible);
}
// p value for >2 groups one-way ANOVA
// https://en.wikipedia.org/wiki/One-way_analysis_of_variance
else if (xCategories.length > 2) {
statsDiv.innerHTML = 'One-way Anova<br>';
_.range(yfields.length).map(function (k) {
yfield = yfields[k];
ydataElement = ydata[k];
ybinnedSample = parseYDataElement(yfield, ycodemap, ydataElement, xCategories, xSampleCode);
var flattenArray = _.flatten(xCategories.map(function (code) {
return ybinnedSample[yfield][code];
})),
// Calculate the overall mean
totalMean = flattenArray.reduce(function (sum, el) {
return sum + el;
}, 0) / flattenArray.length,
//Calculate the "between-group" sum of squared differences
sB = _.range(xCategories.length).reduce(function (sum, index) {
if (nNumberMatrix[index][0] > 0) {
return sum + nNumberMatrix[index][k] * Math.pow(meanMatrix[index][k] - totalMean, 2);
} else {
return sum;
}
}, 0),
// between-group degrees of freedom
fB = _.range(xCategories.length).filter(function (index) {
return nNumberMatrix[index][k] > 0;
}).length - 1,
// between-group mean square differences
msB = sB / fB,
// Calculate the "within-group" sum of squares
sW = _.range(xCategories.length).reduce(function (sum, index) {
if (nNumberMatrix[index][k] > 0) {
return sum + Math.pow(stdMatrix[index][k], 2) * nNumberMatrix[index][k];
} else {
return sum;
}
}, 0),
// within-group degrees of freedom
fW = _.range(xCategories.length).reduce(function (sum, index) {
if (nNumberMatrix[index][k] > 0) {
return sum + nNumberMatrix[index][k] - 1;
} else {
return sum;
}
}, 0),
// within-group mean difference
msW = sW / fW,
// F-ratio
fScore = msB / msW,
// p value
pValue = jStat.ftest(fScore, fB, fW);
statsDiv.innerHTML += (yfields.length > 1 ? '<br>' + yfield + '<br>' : '') + 'p = ' + pValue.toPrecision(4) + ', ' + '(f = ' + fScore.toPrecision(4) + ')<br>';
});
statsDiv.classList.toggle(compStyles.visible);
}
chart.redraw();
} else if (!xfield) {
//summary view --- messsy code
var displayCategories;
dataSeriese = [];
nNumberSeriese = [];
ybinnedSample = {};
xIsCategorical = true;
for (k = 0; k < yfields.length; k++) {
yfield = yfields[k];
ydataElement = ydata[k];
if (yIsCategorical) {
// fields.length ==1
ybinnedSample = parseYDataElement(yfield, ycodemap, ydataElement, undefined, undefined);
} else {
// floats
ybinnedSample[yfield] = parseYDataElement(yfield, ycodemap, ydataElement, undefined, undefined)[yfield];
}
}
total = 0;
if (yIsCategorical) {
categories = Object.keys(ybinnedSample);
categories.forEach(function (code) {
total = total + ybinnedSample[code].length;
});
} else {
categories = yfields;
}
// single parameter float do historgram with smart tick marks
if (!yIsCategorical && yfields.length === 1) {
var valueList = _.values(ybinnedSample)[0],
offset = _.values(offsets)[0],
stdev = _.values(STDEV)[0];
valueList.sort(function (a, b) {
return a - b;
});
var min = valueList[0],
max = valueList[valueList.length - 1],
N = 20,
gap = (max - min) / (N * stdev),
gapRoundedLower = Math.pow(10, Math.floor(Math.log(gap) / Math.LN10)),
// get a sense of the scale the gap, 0.01, 0.1, 1, 10 ...
gapList = [gapRoundedLower, gapRoundedLower * 2, gapRoundedLower * 5, gapRoundedLower * 10],
// within the scale, find the closet to this list of easily readable intervals 1,2,5,10
gapRounded = _.min(gapList, function (x) {
return Math.abs(gap - x);
}),
maxRounded = Math.ceil((max - offset) / stdev / gapRounded) * gapRounded,
minRounded = Math.floor((min - offset) / stdev / gapRounded) * gapRounded;
categories = _.range(minRounded, maxRounded, gapRounded);
categories = categories.map(function (bin) {
return Math.floor(bin * 100) / 100 + ' to ' + Math.floor((bin + gapRounded) * 100) / 100;
});
ybinnedSample = {};
categories.map(function (bin) {
return ybinnedSample[bin] = 0;
});
valueList.map(function (value) {
var binIndex = Math.floor(((value - offset) / stdev - minRounded) / gapRounded),
bin = categories[binIndex];
ybinnedSample[bin] = ybinnedSample[bin] + 1;
});
}
xAxisTitle = xlabel;
showLegend = false;
displayCategories = categories.slice(0);
if (yIsCategorical) {
chartOptions = highchartsHelper.columnChartOptions(chartOptions, categories.map(function (code) {
return code + " (" + ybinnedSample[code].length + ")";
}), xAxisTitle, "Distribution", ylabel, showLegend);
} else if (yfields.length === 1) {
chartOptions = highchartsHelper.columnChartOptions(chartOptions, categories, xAxisTitle, "Histogram", ylabel, showLegend);
} else {
chartOptions = highchartsHelper.columnChartFloat(chartOptions, displayCategories, xAxisTitle, ylabel);
}
chart = new Highcharts.Chart(chartOptions);
//add data to seriese
displayCategories.forEach(function (code) {
var value;
if (yIsCategorical) {
value = ybinnedSample[code].length;
dataSeriese.push(value * 100 / total);
nNumberSeriese.push(value);
} else if (yfields.length === 1) {
value = ybinnedSample[code];
dataSeriese.push(value);
} else {
var data = ybinnedSample[code],
m = data.length;
data.sort(function (a, b) {
return a - b;
});
average = highchartsHelper.average(data);
stdDev = highchartsHelper.standardDeviation(data, average);
// http://onlinestatbook.com/2/graphing_distributions/boxplots.html
var median = data[Math.floor(m / 2)],
lower = data[Math.floor(m / 4)],
upper = data[Math.floor(3 * m / 4)],
whisker = 1.5 * (upper - lower),
upperwhisker = _.findIndex(data, function (x) {
return x > upper + whisker;
}),
lowerwhisker = _.findLastIndex(data, function (x) {
return x < lower - whisker;
});
upperwhisker = upperwhisker === -1 ? data[data.length - 1] : data[upperwhisker - 1];
lowerwhisker = lowerwhisker === -1 ? data[0] : data[lowerwhisker + 1];
median = (median - offsets[code]) / STDEV[code];
lower = (lower - offsets[code]) / STDEV[code];
upper = (upper - offsets[code]) / STDEV[code];
upperwhisker = (upperwhisker - offsets[code]) / STDEV[code];
lowerwhisker = (lowerwhisker - offsets[code]) / STDEV[code];
dataSeriese.push([lowerwhisker, lower, median, upper, upperwhisker]);
nNumberSeriese.push(m);
}
});
// add seriese to chart
var seriesLabel, chartType;
if (yIsCategorical) {
seriesLabel = " ";
chartType = 'column';
} else if (yfields.length === 1) {
seriesLabel = " ";
chartType = 'line';
} else {
seriesLabel = "average";
chartType = 'boxplot';
}
highchartsHelper.addSeriesToColumn(chart, chartType, seriesLabel, dataSeriese, yIsCategorical, categories.length < 30, showLegend, 0, nNumberSeriese);
chart.redraw();
} else if (xIsCategorical && yIsCategorical) {
// x y : categorical --- messsy code
//both x and Y is a single variable, i.e. yfields has array size of 1
xSampleCode = {};
xbinnedSample = {};
// x data
xcodemap.map(function (code) {
return xbinnedSample[code] = [];
});
//probes by samples
for (i = 0; i < xdata[0].length; i++) {
code = xcodemap[xdata[0][i]];
if (code) {
if (xbinnedSample[code]) {
xbinnedSample[code].push(i);
} else {
xbinnedSample[code] = [i];
}
xSampleCode[i] = code;
}
}
// Y data: yfields can only have array size of 1
yfield = yfields[0];
ydataElement = ydata[0];
ybinnedSample = parseYDataElement(yfield, ycodemap, ydataElement, categories, xSampleCode);
var ySamples = _.flatten(_.values(ybinnedSample));
// remove empty xcode categories and recal xbinnedSample[code] with samples actually has values in Y
xcodemap.forEach(function (code) {
xbinnedSample[code] = _.intersection(xbinnedSample[code], ySamples);
if (xbinnedSample[code].length === 0) {
delete xbinnedSample[code];
}
});
// column chart setup
categories = _.keys(xbinnedSample);
xAxisTitle = xlabel;
showLegend = true;
chartOptions = highchartsHelper.columnChartOptions(chartOptions, categories.map(function (code) {
return code + " (" + xbinnedSample[code].length + ")";
}), xAxisTitle, 'Distribution', ylabel, showLegend);
chart = new Highcharts.Chart(chartOptions);
var ycategories = Object.keys(ybinnedSample);
//code
var _scale = colorScales.colorScale(columns[ycolumn].colors[0]),
_invCodeMap = _.invert(ycodemap);
// Pearson's chi-squared test pearson https://en.wikipedia.org/wiki/Pearson's_chi-squared_test
// note, another version of pearson's chi-squared test is G-test, Likelihood-ratio test, https://en.wikipedia.org/wiki/Likelihood-ratio_test
var observed = [],
expected = [],
xRatio = [],
xMargin = [],
yMargin = [];
total = 0.0;
for (i = 0; i < ycategories.length; i++) {
code = ycategories[i];
observed.push(new Array(categories.length));
expected.push(new Array(categories.length));
yMargin.push(ybinnedSample[code].length);
total += yMargin[i];
}
// fill expected matrix
for (k = 0; k < categories.length; k++) {
code = categories[k];
xMargin.push(xbinnedSample[code].length);
xRatio.push(xMargin[k] / total);
}
for (i = 0; i < ycategories.length; i++) {
code = ycategories[i];
for (k = 0; k < categories.length; k++) {
observed[i][k] = _.intersection(ybinnedSample[code], xbinnedSample[categories[k]]).length;
expected[i][k] = xRatio[k] * yMargin[i];
}
}
for (i = 0; i < ycategories.length; i++) {
code = ycategories[i];
var ycodeSeries = new Array(categories.length);
for (k = 0; k < categories.length; k++) {
if (xMargin[k] && observed[i][k]) {
ycodeSeries[k] = parseFloat((observed[i][k] / xMargin[k] * 100).toPrecision(3));
} else {
ycodeSeries[k] = 0;
}
}
highchartsHelper.addSeriesToColumn(chart, 'column', code, ycodeSeries, yIsCategorical, ycodemap.length * categories.length < 30, showLegend, _scale(_invCodeMap[code]), observed[i]);
}
// pearson chi-square test statistics
dof = (ycategories.length - 1) * (categories.length - 1);
if (dof) {
var chisquareStats = 0.0;
for (i = 0; i < ycategories.length; i++) {
for (k = 0; k < categories.length; k++) {
chisquareStats += Math.pow(observed[i][k] - expected[i][k], 2) / expected[i][k];
}
}
pValue = 1 - jStat.chisquare.cdf(chisquareStats, dof);
statsDiv.innerHTML = 'Pearson\'s chi-squared test<br>' + 'p = ' + pValue.toPrecision(4) + ', ' + '(χ2 = ' + chisquareStats.toPrecision(4) + ')';
statsDiv.classList.toggle(compStyles.visible);
}
chart.redraw();
} else {
//scatter plot stats Pearson's rho/r, Spearman rank rho/ρ value
var printPearsonAndSpearmanRho = function printPearsonAndSpearmanRho(div, xlabel, yfields, xVector, ydata) {
[].concat(_toConsumableArray(Array(yfields.length).keys())).forEach(function (i) {
var ylabel = yfields[i],
yVector = ydata[i],
_$unzip = _.unzip(_.filter(_.zip(xVector, yVector), function (x) {
return x[0] != null && x[1] != null;
})),
_$unzip2 = _slicedToArray(_$unzip, 2),
xlist = _$unzip2[0],
ylist = _$unzip2[1],
rho = jStat.corrcoeff(xlist, ylist),
spearmanRho = jStat.spearmancoeff(xlist, ylist); // (spearman's) rank correlation coefficient, rho
if (div.innerHTML !== '') {
div.innerHTML += '<br>' + '<br>';
}
div.innerHTML = div.innerHTML + xlabel + ' ~ ' + ylabel + '<br>' + 'Pearson\'s rho<br>' + 'r = ' + rho.toPrecision(4) + '<br>' + 'Spearman\'s rank rho<br>' + 'ρ = ' + spearmanRho.toPrecision(4);
});
};
// pearson rho value when there are <= 10 series x y scatter plot
// x y float scatter plot
var sampleLabels = cohortSamples,
x,
y;
chartOptions = highchartsHelper.scatterChart(chartOptions, xlabel, ylabel, samplesLength);
if (yfields.length > 1) {
// y multi-subcolumns -- only happen with genomic y data
chart = new Highcharts.Chart(chartOptions);
for (k = 0; k < yfields.length; k++) {
var series = [];
yfield = yfields[k];
for (i = 0; i < xdata[0].length; i++) {
x = xdata[0][i];
y = ydata[k][i];
if (null != x && null != y) {
y = (y - offsets[yfield]) / STDEV[yfield];
series.push({
name: sampleLabels[i],
x: x,
y: y
});
}
}
chart.addSeries({
name: yfield,
data: series
}, false);
}
} else {
// y single subcolumn --- coloring with a 3rd column
var multiSeries = {},
colorScale,
getCodedColor,
highlightSeries = [],
opacity = 0.6,
colorCode,
colorMin,
color,
colorLabel,
useCodedSeries = scatterColorDataCodemap || !scatterColorData,
gray = 'rgba(150,150,150,' + opacity + ')',
bin;
getCodedColor = function getCodedColor(code) {
if ("null" === code) {
return gray;
}
return colorStr(hexToRGB(scatterColorScale(code), opacity));
};
if (!useCodedSeries) {
average = highchartsHelper.average(scatterColorData);
stdDev = highchartsHelper.standardDeviation(scatterColorData, average);
colorMin = _.minnull(scatterColorData);
bin = stdDev * 0.1;
colorScale = function colorScale(v) {
return v == null ? 'gray' : scatterColorScale(v);
};
}
chartOptions.legend.title.text = "";
chart = new Highcharts.Chart(chartOptions);
yfield = yfields[0];
for (i = 0; i < xdata[0].length; i++) {
x = xdata[0][i];
y = ydata[0][i];
if (scatterColorData) {
colorCode = scatterColorData[i];
} else {
colorCode = 0;
}
if (null != x && null != y && null != colorCode) {
y = (y - offsets[yfield]) / STDEV[yfield];
if (useCodedSeries) {
// use multi-seriese
if (!multiSeries[colorCode]) {
multiSeries[colorCode] = {
"data": []
};
}
multiSeries[colorCode].data.push({
colorLabel: scatterColorDataCodemap ? scatterColorDataCodemap[colorCode] || "null (no data)" : '',
name: sampleLabels[i],
x: x,
y: y
});
} else {
// convert float to multi-seriese
colorCode = Math.round((colorCode - colorMin) / bin) * bin + colorMin;
if (!multiSeries[colorCode]) {
multiSeries[colorCode] = {
"data": []
};
}
multiSeries[colorCode].data.push({
colorLabel: scatterColorData[i],
name: sampleLabels[i],
x: x,
y: y
});
}
if (samplesMatched && samplesLength !== samplesMatched.length && samplesMatched.indexOf(i) !== -1) {
highlightSeries.push({
name: sampleLabels[i],
x: x,
y: y
});
}
}
}
if (colorColumn !== "none") {
// custome categorial color
customColors = getCustomColor(columns[colorColumn].fieldSpecs, columns[colorColumn].fields, columns[colorColumn].dataset);
}
_.keys(multiSeries).map(function (colorCode, i) {
var showInLegend;
if (scatterColorData) {
if (useCodedSeries) {
colorLabel = scatterColorDataCodemap[colorCode] || "null (no data)";
color = customColors && customColors[colorLabel] ? customColors[colorLabel] : getCodedColor(colorCode);
showInLegend = true;
} else {
color = colorScale(colorCode);
colorLabel = columns[colorColumn].user.fieldLabel;
showInLegend = i === 0 ? true : false;
}
} else {
color = null;
colorLabel = "sample";
showInLegend = true;
}
chart.addSeries({
name: colorLabel,
showInLegend: showInLegend,
data: multiSeries[colorCode].data,
color: color
}, false);
});
// add highlightSeries color in gold with black border
if (highlightSeries.length > 0) {
chart.addSeries({
name: "highlighted samples",
data: highlightSeries,
marker: {
symbol: 'circle',
lineColor: 'black',
fillColor: 'gold',
lineWidth: 1
}
}, false);
}
}if (yfields.length <= 10 && xdata[0].length > 1) {
if (xdata[0].length >= 5000) {
var btn = document.createElement("BUTTON"); // need to refractor to react style, and material UI css
statsDiv.appendChild(btn);
btn.innerHTML = "Run Stats";
btn.onclick = function () {
printPearsonAndSpearmanRho(statsDiv, xfield, yfields, xdata[0], ydata);
};
} else {
printPearsonAndSpearmanRho(statsDiv, xfield, yfields, xdata[0], ydata);
}
statsDiv.classList.toggle(compStyles.visible);
}
chart.redraw();
}
}
update = function update() {
var oldDiv = document.getElementById("myChart");
oldDiv.parentElement.replaceChild(buildEmptyChartContainer(), oldDiv);
statsDiv.innerHTML = "";
statsDiv.classList.toggle(compStyles.visible, false);
var xcolumn,
ycolumn,
colorColumn,
xfields,
xlabel,
ylabel,
xunit,
yunit,
columns,
normUI = document.getElementById("ynormalization"),
expYUIParent = document.getElementById("expYDropDown"),
expYUI = document.getElementById("yExponentiation"),
expXUIParent = document.getElementById("expXDropDown"),
expXUI = document.getElementById("xExponentiation"),
XdropDownDiv = document.getElementById("Xaxis"),
YdropDownDiv = document.getElementById("Yaxis");
xcolumn = xdiv.options[xdiv.selectedIndex].value;
ycolumn = ydiv.options[ydiv.selectedIndex].value;
colorColumn = colorDiv.options[colorDiv.selectedIndex].value;
//initialization
document.getElementById("myChart").innerHTML = "Querying Xena ...";
normalizationUISetting(false);
expUISetting(false, expState, ycolumn, null, expYUIParent, expYUI, null);
expUISetting(false, expXState, xcolumn, null, expXUIParent, expXUI, null);
scatterColorUISetting(false);
// save state cohort, xcolumn, ycolumn, colorcolumn
if (xenaState) {
xenaState.chartState = {
"cohort": cohort,
"xcolumn": xcolumn,
"ycolumn": ycolumn,
"colorColumn": colorColumn,
"normalizationState": normalizationState,
"expState": expState,
"expXState": expXState
};
columns = xenaState.columns;
setStorage(xenaState);
}
if (!((columns[xcolumn] || xcolumn === "none") && columns[ycolumn])) {
document.getElementById("myChart").innerHTML = "Problem";
return;
}
if (xcolumn !== "none") {
xfields = columns[xcolumn].fields;
xlabel = xdiv.options[xdiv.selectedIndex].text;
} else {
xlabel = "";
}
ylabel = ydiv.options[ydiv.selectedIndex].text;
(function () {
var xcodemap = _.getIn(xenaState, ['data', xcolumn, 'codes']),
xdata = _.getIn(xenaState, ['data', xcolumn, 'req', 'values']),
xdataSegment = _.getIn(xenaState, ['data', xcolumn, 'req', 'rows']),
ycodemap = _.getIn(xenaState, ['data', ycolumn, 'codes']),
ydata = _.getIn(xenaState, ['data', ycolumn, 'req', 'values']),
ydataSegment = _.getIn(xenaState, ['data', ycolumn, 'req', 'rows']),
yProbes = _.getIn(xenaState, ['data', ycolumn, 'req', 'probes']),
yfields = yProbes ? yProbes : ['segmented', 'mutation', 'SV'].indexOf(columns[ycolumn].fieldType) !== -1 ? [columns[ycolumn].fields[0]] : columns[ycolumn].fields,
samplesMatched = _.getIn(xenaState, ['samplesMatched']),
yIsCategorical,
xIsCategorical,
xfield,
offsets = {},
// per y variable
STDEV = {},
// per y variable
doScatter,
scatterLabel,
scatterColorData,
scatterColorDataCodemap,
scatterColorDataSegment,
scatterColorScale,
yNormalization,
xExponentiation,
yExponentiation;
// convert segment data to matrix data
if (ydataSegment) {
ydata = _.getIn(xenaState, ['data', ycolumn, 'avg', 'geneValues']);
}
if (xdataSegment) {
xdata = _.getIn(xenaState, ['data', xcolumn, 'avg', 'geneValues']);
}
//convert binary float to categorical data
if (!xcodemap && xdata && _.flatten(xdata).every(function (c) {
return _.indexOf([0, 1], c) !== -1 || c == null;
})) {
xcodemap = ["0", "1"];
}
if (!ycodemap && ydata && _.flatten(ydata).every(function (c) {
return _.indexOf([0, 1], c) !== -1 || c == null;
})) {
ycodemap = ["0", "1"];
}
// single xfield only
if (xfields && xfields.length > 1) {
document.getElementById("myChart").innerHTML = "not applicable: x axis has more than one variable" + "<br>x: " + xlabel + " : " + xfields;
return;
}
yIsCategorical = ycodemap ? true : false;
xfield = xfields ? xfields[0] : undefined;
xIsCategorical = xcodemap ? true : false;
// set y axis normalization UI
normalizationUISetting(!yIsCategorical, ycolumn);
if (yIsCategorical) {
yNormalization = false;
} else if (normUI.value === "none") {
yNormalization = false;
} else {
yNormalization = normUI.value;
}
// set y axis unit exponentiation UI
expUISetting(!yIsCategorical, expState, ycolumn, columns[ycolumn], expYUIParent, expYUI, ydata);
// set x axis unit exponentiation UI
expUISetting(!xIsCategorical && xcolumn !== "none", expXState, xcolumn, columns[xcolumn], expXUIParent, expXUI, xdata);
// y exponentiation
if (yIsCategorical) {
yExponentiation = false;
} else if (expYUI.value === "none") {
yExponentiation = false;
} else {
yExponentiation = expYUI.value;
}
if (yExponentiation === "exp2") {
ydata = _.map(ydata, function (d) {
return _.map(d, function (x) {
return x != null ? Math.pow(2, x) : null;
});
});
} else if (yExponentiation === "log2") {
ydata = _.map(ydata, function (d) {
return _.map(d, function (x) {
return x != null ? Math.log2(x + 1) : null;
});
});
}
// x exponentiation
if (xIsCategorical) {
xExponentiation = false;
} else if (expXUI.value === "none") {
xExponentiation = false;
} else {
xExponentiation = expXUI.value;
}
if (xExponentiation === "exp2") {
xdata = _.map(xdata, function (d) {
return _.map(d, function (x) {
return x != null ? Math.pow(2, x) : null;
});
});
} else if (xExponentiation === "log2") {
xdata = _.map(xdata, function (d) {
return _.map(d, function (x) {
return x != null ? Math.log2(x + 1) : null;
});
});
}
// set x and y labels based on axis and normalization UI selection
if (xcolumn !== "none") {
xlabel = XdropDownDiv.options[XdropDownDiv.selectedIndex].text;
if (!xIsCategorical) {
xunit = expXUI.options[expXUI.selectedIndex].text;
xlabel += '<br>Unit: ' + xunit;
}
}
ylabel = YdropDownDiv.options[YdropDownDiv.selectedIndex].text;
if (!yIsCategorical) {
yunit = expYUI.options[expYUI.selectedIndex].text;
ylabel += '<br>Unit: ' + yunit;
}
if (normUI.options[normUI.selectedIndex].value === "subset") {
ylabel = ylabel + '<br>mean-centered';
} else if (normUI.options[normUI.selectedIndex].value === "subset_stdev") {
ylabel = ylabel + '<br>z-tranformed';
}
// set scatterPlot coloring UI
doScatter = !xIsCategorical && xfield && yfields.length === 1;
scatterColorUISetting(doScatter);
if (doScatter && colorColumn !== "none") {
var color = _.getIn(xenaState, ['columns', colorColumn, 'colors', 0]);
scatterColorScale = color && colorScales.colorScale(color);
scatterColorData = _.getIn(xenaState, ['data', colorColumn, 'req', 'values']);
scatterColorDataSegment = _.getIn(xenaState, ['data', colorColumn, 'req', 'rows']);
scatterColorDataCodemap = _.getIn(xenaState, ['data', colorColumn, 'codes']);
scatterLabel = columns[colorColumn].user.fieldLabel;
if (scatterColorDataSegment) {
scatterColorData = _.getIn(xenaState, ['data', colorColumn, 'avg', 'geneValues']);
}
scatterColorData = scatterColorData[0];
}
//per y variable stdev
var k, yfield;
for (k = 0; k < yfields.length; k++) {
yfield = yfields[k];
if (yNormalization === "subset_stdev") {
var ydataElement = ydata[k].filter(function (x) {
return x != null;
});
var allAve = highchartsHelper.average(ydataElement);
var allSTDEV = highchartsHelper.standardDeviation(ydataElement, allAve);
STDEV[yfield] = allSTDEV;
} else {
STDEV[yfield] = 1;
}
}
var thunk = function thunk(offsets) {
return drawChart(cohort, samplesLength, xfield, xcodemap, xdata, yfields, ycodemap, ydata, offsets, xlabel, ylabel, STDEV, scatterLabel, scatterColorScale, scatterColorData, scatterColorD