ucsc-xena-client
Version:
UCSC Xena Client. Functional genomics visualizations.
443 lines (396 loc) • 17.4 kB
JavaScript
;
var _manifest = require('../manifest');
var _manifest2 = _interopRequireDefault(_manifest);
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); } }
var _ = require('../underscore_ext');
var Rx = require('../rx');
var xenaQuery = require('../xenaQuery');
var _require = require('./common'),
userServers = _require.userServers,
setCohort = _require.setCohort,
fetchSamples = _require.fetchSamples,
fetchColumnData = _require.fetchColumnData,
fetchCohortData = _require.fetchCohortData,
fetchSurvival = _require.fetchSurvival,
updateWizard = _require.updateWizard,
clearWizardCohort = _require.clearWizardCohort;
var _require2 = require('../models/fieldSpec'),
setFieldType = _require2.setFieldType;
var _require3 = require('../notifications'),
setNotifications = _require3.setNotifications;
var fetchSamplesFrom = require('../samplesFrom');
var fetch = require('../fieldFetch');
var _require4 = require('../models/searchSamples'),
remapFields = _require4.remapFields;
var _require5 = require('../inlineState'),
fetchInlineState = _require5.fetchInlineState;
var _require6 = require('./utils'),
compose = _require6.compose,
make = _require6.make,
mount = _require6.mount;
var _require7 = require('../dom_helper'),
JSONToqueryString = _require7.JSONToqueryString;
var _require8 = require('../bookmark'),
parseBookmark = _require8.parseBookmark;
function fetchBookmark(serverBus, bookmark) {
serverBus.next(['bookmark', Rx.Observable.ajax({
responseType: 'text',
method: 'GET',
url: '/api/bookmarks/bookmark?id=' + bookmark
}).map(function (r) {
return parseBookmark(r.response);
})]);
}
function fetchManifest(serverBus, url) {
serverBus.next(['manifest', Rx.Observable.ajax({
responseType: 'text',
method: 'GET',
url: url
}).map(function (r) {
return (0, _manifest2.default)(r.response);
})]);
}
function exampleQuery(dsID, count) {
return xenaQuery.datasetFieldExamples(dsID, count).map(function (list) {
return _.pluck(list, 'name');
});
}
function fetchExamples(serverBus, dsID) {
var count = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 2;
serverBus.next(['columnEdit-examples', exampleQuery(dsID, count)]);
}
var featureList = xenaQuery.featureList;
function fetchFeatures(serverBus, dsID) {
return serverBus.next(['columnEdit-features', featureList(dsID)]);
}
var warnZoom = function warnZoom(state) {
return !_.getIn(state, ['notifications', 'zoomHelp']) ? _.assoc(state, 'zoomHelp', true) : state;
};
var zoomHelpClose = function zoomHelpClose(state) {
return _.assocIn(_.dissoc(state, 'zoomHelp'), ['notifications', 'zoomHelp'], true);
};
function getChartOffsets(column) {
return fetchSamplesFrom(column).flatMap(function (samples) {
return fetch(column, samples);
}).map(function (data) {
var fields = _.getIn(data, ['req', 'probes'], column.fields),
values = _.getIn(data, ['req', 'values']);
return _.object(fields, _.map(values, _.meannull));
});
}
function fetchCohortMeta(serverBus) {
serverBus.next(['cohortMeta', xenaQuery.fetchCohortMeta]);
}
function fetchCohortPreferred(serverBus) {
serverBus.next(['cohortPreferred', xenaQuery.fetchCohortPreferred]);
}
function fetchCohortPhenotype(serverBus) {
serverBus.next(['cohortPhenotype', xenaQuery.fetchCohortPhenotype]);
}
function setLoadingState(state, params) {
var pending = _.get(params, 'bookmark') || _.get(params, 'inlineState') ? _.assoc(state, 'loadPending', true) : state;
return pending;
}
function fetchState(serverBus) {
serverBus.next(['inlineState', fetchInlineState()]);
}
function resetWizard(state) {
return state.columnOrder.length > 2 ? _.assoc(state, 'wizardMode', false, 'showWelcome', false) : state;
}
// Use min app width (1280px) if viewport width is currently smaller than min
// app width. (App is responsive above 1280px but components are snapped at a
// minimum width of 1280px)
var defaultWidth = function defaultWidth(viewportWidth) {
var width = viewportWidth < 1280 ? 1280 : viewportWidth;
return Math.floor((width - 48) / 4) - 16; // Allow for 2 x 24px gutter on viewport, plus 16px margin for column
};
// XXX This same info appears in Datapages.js, and in various links.
var getPage = function getPage(path) {
return path === '/transcripts/' ? 'transcripts' : path === '/hub/' ? 'hub' : path === '/datapages/' ? 'datapages' : 'heatmap';
};
// XXX This same info also appears in urlParams.js
var savedParams = function savedParams(params) {
return _.pick(params, 'dataset', 'addHub', 'removeHub', 'hubs', 'host', 'cohort', 'allIdentifiers', 'markdown');
};
var setPage = function setPage(state, path, params) {
return _.assoc(state, 'page', getPage(path), 'params', savedParams(params));
};
var paramList = function paramList(params) {
return _.isEmpty(params) ? '' : '?' + JSONToqueryString(params);
};
var controls = {
init: function init(state) {
var pathname = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '/';
var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var wizardUpate = params.hubs || params.inlineState ? clearWizardCohort : _.identity,
next = setLoadingState(state, params);
return wizardUpate(setPage(next, pathname, params));
},
'init-post!': function initPost(serverBus, state, newState, pathname, params) {
var bookmark = _.get(params, 'bookmark'),
inlineState = _.get(params, 'inlineState'),
manifest = _.get(params, 'manifest');
if (inlineState) {
fetchState(serverBus);
} else if (bookmark) {
fetchBookmark(serverBus, bookmark);
} else {
// 'servers' is in spreadsheet state. After loading a bookmark or inline
// state, we need to update wizard data. Otherwise, we need to load
// wizard data here.
updateWizard(serverBus, state.spreadsheet, newState.spreadsheet, { force: true });
}
// These are independent of server settings.
if (!newState.wizard.cohortMeta) {
fetchCohortMeta(serverBus);
}
if (!newState.wizard.cohortPreferred) {
fetchCohortPreferred(serverBus);
}
if (!newState.wizard.cohortPhenotype) {
fetchCohortPhenotype(serverBus);
}
if (manifest) {
fetchManifest(serverBus, manifest);
}
},
navigate: function navigate(state, page) {
var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
return _.assoc(state, 'page', page, 'params', params);
},
'navigate-post!': function navigatePost(serverBus, state, newState, page, params) {
return history.pushState({}, '', '/' + page + '/' + paramList(params));
},
history: function (_history) {
function history(_x5, _x6) {
return _history.apply(this, arguments);
}
history.toString = function () {
return _history.toString();
};
return history;
}(function (state, history) {
return _.isEmpty(history) ? state : _.Let(function () {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : history,
path = _ref.path,
_ref$params = _ref.params,
params = _ref$params === undefined ? {} : _ref$params;
return _.assoc(state, 'page', getPage(path), 'params', params);
});
}),
cohort: function cohort(state, _cohort, width) {
return clearWizardCohort(_.updateIn(state, ['spreadsheet'], setCohort({ name: _cohort }, width)));
},
'cohort-post!': function cohortPost(serverBus, state, newState) {
return fetchCohortData(serverBus, newState.spreadsheet);
},
cohortReset: function cohortReset(state) {
return clearWizardCohort(_.updateIn(state, ['spreadsheet'], setCohort(undefined, undefined)));
},
'import': function _import(state, newState) {
return clearWizardCohort(_.merge(state, newState));
},
'import-error': function importError(state) {
return _.assoc(state, 'stateError', 'import');
},
'refresh-cohorts': clearWizardCohort,
stateError: function stateError(state, error) {
return _.assoc(state, 'stateError', error);
},
'km-open-post!': function kmOpenPost(serverBus, state, newState) {
return fetchSurvival(serverBus, newState, {});
} // 2nd param placeholder for km.user
};
var spreadsheetControls = {
'import-post!': function importPost(serverBus, state, newState) {
return updateWizard(serverBus, state, newState, { force: true });
},
'refresh-cohorts-post!': function refreshCohortsPost(serverBus, state, newState) {
fetchCohortMeta(serverBus);
fetchCohortPreferred(serverBus);
fetchCohortPhenotype(serverBus);
updateWizard(serverBus, state, newState, { force: true });
},
sampleFilter: function sampleFilter(state, _sampleFilter) {
return _.assoc(state, 'cohort', _.assocIn(state.cohort, ['sampleFilter'], _sampleFilter), 'survival', null);
},
'sampleFilter-post!': function sampleFilterPost(serverBus, state, newState) {
return fetchSamples(serverBus, userServers(newState), newState.cohort, newState.allowOverSamples);
},
'add-column': function addColumn(state, posOrId) {
for (var _len = arguments.length, idSettingsList = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
idSettingsList[_key - 2] = arguments[_key];
}
var columnOrder = state.columnOrder,
columns = state.columns,
sampleSearch = state.sampleSearch,
data = state.data,
isPos = _.isNumber(posOrId),
pos = isPos ? posOrId : columnOrder.indexOf(posOrId) - 1,
ids = _.pluck(idSettingsList, 'id'),
settingsList = _.pluck(idSettingsList, 'settings'),
newOrder = _.splice.apply(_, [columnOrder, pos + 1, isPos ? 0 : 1].concat(_toConsumableArray(ids))),
newState = _.assoc(state, 'columns', _.merge(columns, _.object(ids, settingsList)), 'columnOrder', newOrder, 'sampleSearch', remapFields(columnOrder, newOrder, sampleSearch), 'editing', null, // is editing always off after column add?
'data', _.merge(data, _.object(ids, ids.map(_.constant({ 'status': 'loading' })))));
return resetWizard(newState);
},
'add-column-post!': function addColumnPost(serverBus, state, newState, pos) {
for (var _len2 = arguments.length, idSettingsList = Array(_len2 > 4 ? _len2 - 4 : 0), _key2 = 4; _key2 < _len2; _key2++) {
idSettingsList[_key2 - 4] = arguments[_key2];
}
idSettingsList.forEach(function (_ref2) {
var id = _ref2.id;
return fetchColumnData(serverBus, state.cohortSamples, id, _.getIn(newState, ['columns', id]));
});
},
'edit-column': function editColumn(state, editing) {
return _.assoc(state, 'editing', editing);
},
resize: function resize(state, id, _ref3) {
var width = _ref3.width,
height = _ref3.height;
return _.assocInAll(state, ['zoom', 'height'], Math.round(height), ['columns', id, 'width'], Math.round(width));
},
// If 'editing' is a blank column (isNumber), we need to decrement it
// if it is higher than id index, to preserve its position in the order.
remove: function remove(state, id) {
var columns = state.columns,
columnOrder = state.columnOrder,
data = state.data,
editing = state.editing,
newEditing = _.isNumber(editing) && editing >= state.columnOrder.indexOf(id) ? editing - 1 : editing,
ns = _.assoc(state, 'editing', newEditing, 'columns', _.dissoc(columns, id), 'columnOrder', _.without(columnOrder, id), 'data', _.dissoc(data, id));
return _.assoc(ns, 'sampleSearch', remapFields(state.columnOrder, ns.columnOrder, state.sampleSearch));
},
order: function order(state, _order) {
// Filter out 'editing' columns
var newOrder = _order.filter(function (id) {
return !_.isNumber(id);
}),
editing = _.findIndexDefault(_order.slice(1), _.isNumber, state.editing);
return _.assoc(state, 'columnOrder', newOrder, 'editing', editing, 'sampleSearch', remapFields(state.columnOrder, _order, state.sampleSearch));
},
zoom: function zoom(state, _zoom) {
return warnZoom(_.assoc(state, "zoom", _zoom));
},
'zoom-help-close': zoomHelpClose,
'zoom-help-disable': zoomHelpClose,
'zoom-help-disable-post!': function zoomHelpDisablePost(serverBus, state, newState) {
return setNotifications(newState.notifications);
},
'notifications-disable': function notificationsDisable(state, key) {
return _.assocIn(state, ['notifications', key], true);
},
'notifications-disable-post!': function notificationsDisablePost(serverBus, state, newState) {
return setNotifications(newState.notifications);
},
'notifications-enable': function notificationsEnable(state) {
return _.assoc(state, 'notifications', {});
},
'notifications-enable-post!': function notificationsEnablePost(serverBus, state, newState) {
return setNotifications(newState.notifications);
},
reload: function reload(state, id) {
return _.assocIn(state, ['data', id, 'status'], 'loading');
},
'reload-post!': function reloadPost(serverBus, state, newState, id) {
return fetchColumnData(serverBus, newState.cohortSamples, id, _.getIn(newState, ['columns', id]));
},
fieldType: function fieldType(state, id, _fieldType) {
return _.updateIn(state, ['columns', id], setFieldType(_fieldType), ['data', id, 'status'], function () {
return 'loading';
});
},
'fieldType-post!': function fieldTypePost(serverBus, state, newState, id) {
return fetchColumnData(serverBus, newState.cohortSamples, id, _.getIn(newState, ['columns', id]));
},
vizSettings: function vizSettings(state, column, settings) {
return _.assocIn(state, ['columns', column, 'vizSettings'], settings);
},
'edit-dataset-post!': function editDatasetPost(serverBus, state, newState, dsID, meta) {
if (!_.contains(['mutationVector', 'clinicalMatrix', 'genomicSegment'], meta.type)) {
fetchExamples(serverBus, dsID);
}
if (_.contains(['mutationVector', 'genomicSegment'], meta.type)) {
fetchFeatures(serverBus, dsID);
}
},
'columnLabel': function columnLabel(state, id, value) {
return _.assocIn(state, ['columns', id, 'user', 'columnLabel'], value);
},
'fieldLabel': function fieldLabel(state, id, value) {
return _.assocIn(state, ['columns', id, 'user', 'fieldLabel'], value);
},
'showIntrons': function showIntrons(state, id) {
return _.updateIn(state, ['columns', id, 'showIntrons'], function (v) {
return !v;
});
},
'sortVisible': function sortVisible(state, id, value) {
return _.assocIn(state, ['columns', id, 'sortVisible'], value);
},
'km-open': function kmOpen(state, id) {
return _.assocInAll(state, ['km', 'id'], id, ['km', 'title'], _.getIn(state, ['columns', id, 'user', 'columnLabel']), ['km', 'label'], _.getIn(state, ['columns', id, 'user', 'fieldLabel']), ['km', 'survivalType'], _.intersection([_.getIn(state, ['km', 'survivalType'])], _.keys(_.getIn(state, ['survival'])))[0]);
},
// see km-open-post! in controls, above. Requires wizard.datasets.
'km-close': function kmClose(state) {
return _.assocIn(state, ['km', 'id'], null);
},
'km-cutoff': function kmCutoff(state, value) {
return _.assocIn(state, ['km', 'cutoff'], value);
},
'km-splits': function kmSplits(state, value) {
return _.assocIn(state, ['km', 'splits'], value);
},
'km-survivalType': function kmSurvivalType(state, value) {
return _.assocIn(state, ['km', 'survivalType'], value);
},
'heatmap': function heatmap(state) {
return _.assoc(state, 'mode', 'heatmap');
},
'chart': function chart(state) {
return _.assoc(state, 'mode', 'chart');
},
'chart-set-state': function chartSetState(state, chartState) {
return _.assoc(state, 'chartState', chartState);
},
'chart-set-average-cohort-post!': function chartSetAverageCohortPost(serverBus, state, newState, id, thunk) {
return serverBus.next(['chart-average-data', getChartOffsets(newState.columns[id]), thunk]);
},
'chart-set-average-post!': function chartSetAveragePost(serverBus, state, newState, offsets, thunk) {
return serverBus.next(['chart-average-data', Rx.Observable.of(offsets, Rx.Scheduler.async), thunk]);
},
'sample-search': function sampleSearch(state, text) {
return _.assoc(state, 'sampleSearch', text);
},
'vizSettings-open': function vizSettingsOpen(state, id) {
return _.assoc(state, 'openVizSettings', id);
},
'sortDirection': function sortDirection(state, id, newDir) {
return _.assocIn(state, ['columns', id, 'sortDirection'], newDir);
},
'allowOverSamples': function allowOverSamples(state, aos) {
return _.assoc(state, 'allowOverSamples', aos);
},
'allowOverSamples-post!': function allowOverSamplesPost(serverBus, state, newState, aos) {
return fetchSamples(serverBus, userServers(newState), newState.cohort, aos);
},
showWelcome: function showWelcome(state, show) {
return _.assoc(state, 'showWelcome', show);
},
wizardMode: function wizardMode(state, mode) {
return _.assoc(state, 'wizardMode', mode);
},
viewportWidth: function viewportWidth(state, width) {
return _.assoc(state, 'defaultWidth', defaultWidth(width));
},
// Due to wonky react-bootstrap handlers, xzoom can occur after remove, so
// check that the column exists before updating.
'xzoom': function xzoom(state, id, _xzoom) {
return _.updateIn(state, ['columns', id], function (c) {
return c ? _.assoc(c, 'xzoom', _xzoom) : c;
});
}
};
module.exports = compose(mount(make(spreadsheetControls), ['spreadsheet']), make(controls));