UNPKG

ucsc-xena-client

Version:

UCSC Xena Client. Functional genomics visualizations.

443 lines (396 loc) 17.4 kB
'use strict'; 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));