ucsc-xena-client
Version:
UCSC Xena Client. Functional genomics visualizations.
421 lines (362 loc) • 16.4 kB
JavaScript
;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _PureComponent2 = require('../PureComponent');
var _PureComponent3 = _interopRequireDefault(_PureComponent2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var React = require('react');
var _ = require('../underscore_ext');
var CohortOrDisease = require('../views/CohortOrDisease');
var VariableSelect = require('../views/VariableSelect');
var GhostVariableSelect = require('../views/GhostVariableSelect');
var getStepperState = require('./getStepperState');
var _require = require('../models/datasetJoins'),
getColSpec = _require.getColSpec;
var _require2 = require('../heatmapColors'),
defaultColorClass = _require2.defaultColorClass;
var uuid = require('../uuid');
var Rx = require('../rx');
var parsePos = require('../parsePos');
var parseGeneSignature = require('../parseGeneSignature');
var parseInput = require('../parseInput');
var _require3 = require('../models/fieldSpec'),
signatureField = _require3.signatureField;
/*function toWordList(str) {
// Have to wrap trim because it takes a 2nd param.
return _.filter(_.map(str.split(/,| |\n|\t/), s => s.trim(), _.identity));
}*/
var typeWidth = {
matrix: 136,
chrom: 200
};
// 'features' is a problem here, because they are not unique across datasets.
// How do we look up features w/o a dataset?
function getValueType(dataset, features, fields) {
var type = dataset.type,
valuetype = _.getIn(features, [fields[0], 'valuetype']);
if (type === 'mutationVector') {
return 'mutation';
}
if (type === 'genomicSegment') {
return 'segmented';
}
if (type === 'clinicalMatrix') {
return valuetype === 'category' ? 'coded' : 'float';
}
return 'float';
}
function getFieldType(dataset, features, fields, probes, pos) {
if (dataset.type === 'mutationVector') {
return dataset.dataSubType.search(/SV|structural/i) !== -1 ? 'SV' : 'mutation';
}
if (dataset.type === 'genomicSegment') {
return 'segmented';
}
if (dataset.type === 'clinicalMatrix') {
return 'clinical';
}
// We treat probes in chrom view (pos) as geneProbes
return probes ? 'probes' : fields.length > 1 && !pos ? 'genes' : 'geneProbes';
}
function sigFields(fields, _ref) {
var genes = _ref.genes,
weights = _ref.weights;
return {
missing: genes.filter(function (p, i) {
return !fields[i];
}),
genes: fields.filter(function (p) {
return p;
}),
weights: weights.filter(function (p, i) {
return fields[i];
})
};
}
// XXX duplicated in VariableSelect.
var getAssembly = function getAssembly(datasets, dsID) {
return _.getIn(datasets, [dsID, 'assembly'], _.getIn(datasets, [dsID, 'probemapMeta', 'assembly']));
};
// XXX handle position in all genomic datatypes?
function columnSettings(datasets, features, dsID, input, fields, probes) {
var meta = datasets[dsID],
pos = parsePos(input.trim(), getAssembly(datasets, dsID)),
sig = parseGeneSignature(input.trim()),
fieldType = getFieldType(meta, features[dsID], fields, probes, pos),
fieldsInput = sig ? sig.genes : parseInput(input),
normalizedFields = pos ? [pos.chrom + ':' + pos.baseStart + '-' + pos.baseEnd] : (['segmented', 'mutation', 'SV'].indexOf(fieldType) !== -1 ? [fields[0]] : fields).map(function (f, i) {
return f ? f : fieldsInput[i] + " (unknown)";
});
// My god, this is a disaster.
if (sig) {
var _sigFields = sigFields(fields, sig),
missing = _sigFields.missing,
genes = _sigFields.genes,
weights = _sigFields.weights,
missingLabel = _.isEmpty(missing) ? '' : ' (missing terms: ' + missing.join(', ') + ')';
return signatureField('signature' + missingLabel, {
signature: ['geneSignature', dsID, genes, weights],
missing: missing,
fieldType: 'probes',
defaultNormalization: meta.colnormalization,
colorClass: defaultColorClass,
fields: [input],
dsID: dsID
});
}
return _extends({}, fieldType === 'geneProbes' ? { showIntrons: true } : {}, {
fields: normalizedFields,
fetchType: 'xena',
valueType: getValueType(meta, features[dsID], fields),
fieldType: fieldType,
dsID: dsID,
defaultNormalization: meta.colnormalization,
// XXX this assumes fields[0] doesn't appear in features if ds is genomic
//fieldLabel: _.getIn(features, [dsID, fields[0], 'longtitle'], fields.join(', ')),
fieldLabel: _.getIn(features, [dsID, fields[0], 'longtitle']) || normalizedFields.join(', '),
colorClass: defaultColorClass,
assembly: meta.assembly || _.getIn(meta, ['probemapMeta', 'assembly'])
});
}
// Configuration for first and second variable select cards that are displayed during wizard.
var variableSelectConfig = {
'FIRST_COLUMN': {
helpText: {
'Genotypic': 'Add a gene (e.g. RB1) or position (e.g. chr19p), and select a dataset.',
'Phenotypic': 'Add a phenotype (e.g. sample type, age).'
},
pos: 1,
title: 'First Variable'
},
'SECOND_COLUMN': {
helpText: {
'Genotypic': 'Add a gene (e.g. RB1) or position (e.g. chr19p), and select a dataset.',
'Phenotypic': 'Add a phenotype (e.g. sample type, age).'
},
pos: 2,
title: 'Second Variable'
}
};
function wizardColumns(wizardMode, stepperState, cohortSelectProps, datasetSelectProps, width) {
if (wizardMode) {
if (stepperState === 'COHORT') {
return [React.createElement(CohortOrDisease, _extends({ key: 'c1' }, cohortSelectProps)), React.createElement(GhostVariableSelect, _extends({ key: 'c2', width: width }, variableSelectConfig.FIRST_COLUMN)), React.createElement(GhostVariableSelect, _extends({ key: 'c3', width: width }, variableSelectConfig.SECOND_COLUMN))];
}
if (stepperState === 'FIRST_COLUMN') {
return [React.createElement(VariableSelect, _extends({ key: 'c2' }, variableSelectConfig[stepperState], datasetSelectProps)), React.createElement(GhostVariableSelect, _extends({ key: 'c3', width: width }, variableSelectConfig.SECOND_COLUMN))];
}
if (stepperState === 'SECOND_COLUMN') {
return [React.createElement(VariableSelect, _extends({ key: 'c3' }, variableSelectConfig[stepperState], datasetSelectProps))];
}
}
return [];
}
var preferredLabels = {
'gene expression': 'Gene Expression',
'copy number': 'Copy Number',
'simple somatic mutation': 'Somatic Mutation'
};
var activeHubs = function activeHubs(hubs) {
return _.keys(hubs).filter(function (hub) {
return hubs[hub].user;
});
};
var cohortName = function cohortName(cohort) {
return _.get(cohort, 'name');
};
var getCohortPreferred = function getCohortPreferred(table, cohort) {
return _.get(table, cohortName(cohort));
};
function getPreferedDatasets(cohort, cohortPreferred, hubs, datasets) {
var active = activeHubs(hubs),
// Only include datasets on active hubs & real existing datasets (more reliable against mistakes in the .json file)
preferred = _.pick(getCohortPreferred(cohortPreferred, cohort), function (ds) {
return _.contains(active, JSON.parse(ds).host) && _.has(datasets, ds);
});
// filter out key used to support geneset/pathway view
preferred = _.pick(preferred, function (ds, key) {
return key !== "copy number for pathway view";
});
// Use isEmpty to handle 1) no configured preferred datasets or 2) preferred dataset list
// is empty after filtering by active hubs.
return _.isEmpty(preferred) ? [] : _.keys(preferred).map(function (type) {
return { dsID: preferred[type], label: preferredLabels[type] };
});
}
function getPreferredPhenotypes(cohort, cohortPreferredPhenotypes, hubs) {
var active = activeHubs(hubs),
preferred = _.filter(getCohortPreferred(cohortPreferredPhenotypes, cohort), function (_ref2) {
var dsID = _ref2.dsID;
return _.contains(active, JSON.parse(dsID).host);
});
return _.isEmpty(preferred) ? [] : preferred;
}
var consolidateFeatures = function consolidateFeatures(featureSet) {
return _.reduce(featureSet, function (all, features, dsID) {
var strippedFeatures = _.toArray(_.mapObject(features, function (f) {
return _.extend(f, { dsID: dsID, label: f.longtitle || f.name });
}));
return all.concat(strippedFeatures);
}, []);
};
var sortFeatures = function sortFeatures(features) {
return _.sortBy(features, function (f) {
return f.label.toUpperCase();
});
};
var removeSampleID = function removeSampleID(features) {
return _.filter(features, function (f) {
return f.name !== "sampleID";
});
};
var computeSettings = _.curry(function (datasets, features, inputFields, width, dataset, matches) {
var ds = datasets[dataset];
var settings = columnSettings(datasets, features, dataset, inputFields, matches.fields, matches.type === 'probes'),
colSpec = getColSpec([settings], datasets),
columnLabel = (ds.dataSubType && !ds.dataSubType.match(/phenotype/i) ? ds.dataSubType + ' - ' : '') + (ds.dataSubType && ds.dataSubType.match(/phenotype/i) ? '' : ds.label);
return _.assoc(colSpec, 'width', _.contains(['mutationVector', 'segmented'], ds.type) ? typeWidth.chrom : typeWidth.matrix, 'dataset', ds, 'columnLabel', columnLabel, 'user', { columnLabel: columnLabel, fieldLabel: colSpec.fieldLabel });
});
// 1) if appState.editing, then set editing state, and render editor.
// 2) if wizard mode
// add cohort editor, or
// add 1st column editor, or
// add 2nd column editor
function addWizardColumns(Component) {
var _class, _temp;
return _temp = _class = function (_PureComponent) {
_inherits(_class, _PureComponent);
function _class(props) {
_classCallCheck(this, _class);
var _this = _possibleConstructorReturn(this, (_class.__proto__ || Object.getPrototypeOf(_class)).call(this, props));
_this.onCancel = function () {
_this.props.callback(['edit-column', null]);
};
_this.onCohortSelect = function (cohort) {
_this.props.callback(['cohort', cohort, typeWidth.matrix]);
};
_this.onDatasetSelect = function (posOrId, input, datasetList, fieldList) {
var _this$props = _this.props,
_this$props$wizard = _this$props.wizard,
datasets = _this$props$wizard.datasets,
features = _this$props$wizard.features,
defaultWidth = _this$props.appState.defaultWidth,
isPos = _.isNumber(posOrId),
settingsList = _.mmap(datasetList, fieldList, computeSettings(datasets, features, input, defaultWidth));
_this.props.callback(['add-column', posOrId].concat(_toConsumableArray(settingsList.map(function (settings, i) {
return { id: !i && !isPos ? posOrId : uuid(), settings: settings };
}))));
};
var editing = props.editing;
_this.state = { editing: editing };
return _this;
}
_createClass(_class, [{
key: 'componentWillMount',
value: function componentWillMount() {
var callback = this.props.callback;
this.sub = Rx.Observable.of(true).concat(Rx.Observable.fromEvent(window, 'resize')).debounceTime(200).subscribe(function () {
return callback(['viewportWidth', document.documentElement.clientWidth]);
});
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this.sub.unsubscribe();
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(newProps) {
var editing = newProps.editing;
// XXX set timeout here for flipping back, when done.
this.setState({ editing: editing });
// XXX If we had a cohort but lost it (e.g. due to change in servers),
// and the columnEdit is closed: open it.
// if (!this.state.openColumnEdit &&
// this.props.appState.cohort[0] &&
// !newProps.appState.cohort[0]) {
//
// this.setState({openColumnEdit: true});
// }
}
}, {
key: 'addColumns',
value: function addColumns() {
var _props = this.props,
children = _props.children,
appState = _props.appState,
wizard = _props.wizard,
cohort = appState.cohort,
wizardMode = appState.wizardMode,
defaultWidth = appState.defaultWidth,
servers = appState.servers,
cohorts = wizard.cohorts,
cohortPreferred = wizard.cohortPreferred,
cohortMeta = wizard.cohortMeta,
cohortPhenotype = wizard.cohortPhenotype,
datasets = wizard.datasets,
features = wizard.features,
stepperState = getStepperState(appState),
editing = appState.editing,
preferred = cohortPreferred && getPreferedDatasets(cohort, cohortPreferred, servers, datasets),
preferredPhenotypes = cohortPhenotype && getPreferredPhenotypes(cohort, cohortPhenotype, servers),
width = defaultWidth,
cohortSelectProps = {
cohorts: cohorts,
cohortMeta: cohortMeta,
onSelect: this.onCohortSelect,
width: width },
datasetSelectProps = {
datasets: datasets,
features: features && sortFeatures(removeSampleID(consolidateFeatures(features))),
preferred: preferred,
basicFeatures: preferredPhenotypes,
onSelect: this.onDatasetSelect,
width: width },
columns = React.Children.toArray(children),
cancelIcon = React.createElement(
'i',
{ className: 'material-icons', onClick: this.onCancel },
'cancel'
),
withEditor = columns.map(function (el) {
return editing === el.props.id ? React.createElement(VariableSelect, _extends({
key: editing,
actionKey: editing,
pos: editing,
fields: appState.columns[editing].fieldSpecs[0].fields,
dataset: appState.columns[editing].fieldSpecs[0].dsID,
title: 'Edit Variable'
}, datasetSelectProps, {
colId: el.props.label,
controls: cancelIcon })) : el;
}),
withNewColumns = _.flatmap(withEditor, function (el, i) {
return editing === i ? [el, React.createElement(VariableSelect, _extends({ key: i, actionKey: i, pos: i, title: 'Add Variable'
}, datasetSelectProps, { controls: cancelIcon }))] : [el];
});
return withNewColumns.concat(wizardColumns(wizardMode, stepperState, cohortSelectProps, datasetSelectProps, width));
}
}, {
key: 'render',
value: function render() {
var _props2 = this.props,
children = _props2.children,
_props2$appState = _props2.appState,
editing = _props2$appState.editing,
wizardMode = _props2$appState.wizardMode,
columns = editing != null || wizardMode ? this.addColumns() : children;
return React.createElement(
Component,
this.props,
columns
);
}
}]);
return _class;
}(_PureComponent3.default), _class.displayName = 'SpreadsheetWizardColumns', _temp;
}
module.exports = addWizardColumns;