From ef5a0df46ba9a931713bc98998ef658d06a786d6 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 16 Jun 2023 15:18:48 -0300 Subject: [PATCH 01/87] Add loadDownsampledGeneExpression action creator and use it instead of loadGeneExpression when loading for the heatmap, also use it instead of loadMarkerGenes to preserve the gene expressions set up in the interactive heatmap --- .../generic-gene-table/ComponentActions.jsx | 27 +++++- .../data-exploration/heatmap/HeatmapPlot.jsx | 51 +++++++---- src/redux/actions/genes/index.js | 2 + .../genes/loadDownsampledGeneExpression.js | 89 +++++++++++++++++++ src/redux/actions/genes/loadGeneExpression.js | 18 ++-- .../reducers/genes/genesExpressionLoaded.js | 37 ++++---- 6 files changed, 180 insertions(+), 44 deletions(-) create mode 100644 src/redux/actions/genes/loadDownsampledGeneExpression.js diff --git a/src/components/data-exploration/generic-gene-table/ComponentActions.jsx b/src/components/data-exploration/generic-gene-table/ComponentActions.jsx index d90adab3e2..2773aaeb54 100644 --- a/src/components/data-exploration/generic-gene-table/ComponentActions.jsx +++ b/src/components/data-exploration/generic-gene-table/ComponentActions.jsx @@ -7,7 +7,8 @@ import { useSelector, useDispatch } from 'react-redux'; import _ from 'lodash'; import { PlusOutlined, RedoOutlined, MinusOutlined } from '@ant-design/icons'; -import { loadGeneExpression } from 'redux/actions/genes'; +import { loadDownsampledGeneExpression, loadGeneExpression } from 'redux/actions/genes'; +import { getCellSets } from 'redux/selectors'; const geneOperations = { ADD: 'add', @@ -24,6 +25,13 @@ const ComponentActions = (props) => { const selectedGenes = useSelector((state) => state.genes.selected); const displayedGenes = useSelector((state) => state.genes.expression?.views[componentType]?.data); + const componentConfig = useSelector( + (state) => state.componentConfig[componentType]?.config, + _.isEqual, + ) || {}; + + const { hidden: hiddenCellSets } = useSelector(getCellSets()); + const performGeneOperation = (genesOperation) => { let newGenes = _.cloneDeep(selectedGenes); @@ -34,7 +42,22 @@ const ComponentActions = (props) => { newGenes = displayedGenes.filter((gene) => !selectedGenes.includes(gene)); } - dispatch(loadGeneExpression(experimentId, newGenes, componentType, useDownsampledExpression)); + if (useDownsampledExpression) { + const { groupedTracks, selectedCellSet, selectedPoints } = componentConfig; + + const downsampleSettings = { + groupedTracks, + selectedCellSet, + selectedPoints, + hiddenCellSets, + }; + + dispatch( + loadDownsampledGeneExpression(experimentId, newGenes, componentType, downsampleSettings), + ); + } else { + dispatch(loadGeneExpression(experimentId, newGenes, componentType)); + } }; const menu = ( diff --git a/src/components/data-exploration/heatmap/HeatmapPlot.jsx b/src/components/data-exploration/heatmap/HeatmapPlot.jsx index 501fab6120..6b43472121 100644 --- a/src/components/data-exploration/heatmap/HeatmapPlot.jsx +++ b/src/components/data-exploration/heatmap/HeatmapPlot.jsx @@ -9,7 +9,7 @@ import _ from 'lodash'; import { getCellSets } from 'redux/selectors'; -import { loadGeneExpression, loadMarkerGenes } from 'redux/actions/genes'; +import { loadDownsampledGeneExpression, loadGeneExpression, loadMarkerGenes } from 'redux/actions/genes'; import { loadComponentConfig } from 'redux/actions/componentConfig'; import { updateCellInfo } from 'redux/actions/cellInfo'; @@ -162,17 +162,29 @@ const HeatmapPlot = (props) => { const { groupedTracks, selectedCellSet, selectedPoints } = heatmapSettings; - dispatch(loadMarkerGenes( - experimentId, - COMPONENT_TYPE, - { - numGenes: nMarkerGenes, - groupedTracks, - selectedCellSet, - selectedPoints, - hiddenCellSets: cellSets.hidden, - }, - )); + const downsampleSettings = { + groupedTracks, + selectedCellSet, + selectedPoints, + hiddenCellSets: cellSets.hidden, + }; + + // If selectedGenes are not set, load marker genes instead (first load) + if (_.isNil(selectedGenes)) { + dispatch(loadMarkerGenes( + experimentId, + COMPONENT_TYPE, + { numGenes: nMarkerGenes, ...downsampleSettings }, + )); + } else { + // Load current genes + dispatch(loadDownsampledGeneExpression( + experimentId, + selectedGenes, + COMPONENT_TYPE, + downsampleSettings, + )); + } }, [ louvainClustersResolution, cellSets.accessible, @@ -206,9 +218,9 @@ const HeatmapPlot = (props) => { { - if (markerGenesLoadingError) { - const { groupedTracks, selectedCellSet, selectedPoints } = heatmapSettings; + const { groupedTracks, selectedCellSet, selectedPoints } = heatmapSettings; + if (markerGenesLoadingError) { dispatch(loadMarkerGenes( experimentId, COMPONENT_TYPE, @@ -222,7 +234,16 @@ const HeatmapPlot = (props) => { } if ((expressionDataError || viewError) && !_.isNil(selectedGenes)) { - dispatch(loadGeneExpression(experimentId, selectedGenes, COMPONENT_TYPE, true)); + const downsampleSettings = { + groupedTracks, + selectedCellSet, + selectedPoints, + hiddenCellSets: cellSets.hidden, + }; + + dispatch(loadDownsampledGeneExpression( + experimentId, selectedGenes, COMPONENT_TYPE, downsampleSettings, + )); } }} /> diff --git a/src/redux/actions/genes/index.js b/src/redux/actions/genes/index.js index f868f286de..19483f0efa 100644 --- a/src/redux/actions/genes/index.js +++ b/src/redux/actions/genes/index.js @@ -2,10 +2,12 @@ import loadGeneExpression from './loadGeneExpression'; import changeGeneSelection from './changeGeneSelection'; import loadPaginatedGeneProperties from './loadPaginatedGeneProperties'; import loadMarkerGenes from './loadMarkerGenes'; +import loadDownsampledGeneExpression from './loadDownsampledGeneExpression'; export { loadGeneExpression, changeGeneSelection, loadPaginatedGeneProperties, loadMarkerGenes, + loadDownsampledGeneExpression, }; diff --git a/src/redux/actions/genes/loadDownsampledGeneExpression.js b/src/redux/actions/genes/loadDownsampledGeneExpression.js new file mode 100644 index 0000000000..5b0872cb79 --- /dev/null +++ b/src/redux/actions/genes/loadDownsampledGeneExpression.js @@ -0,0 +1,89 @@ +import { SparseMatrix } from 'mathjs'; + +import { + GENES_EXPRESSION_LOADING, GENES_EXPRESSION_ERROR, GENES_EXPRESSION_LOADED, +} from 'redux/actionTypes/genes'; + +import fetchWork from 'utils/work/fetchWork'; +import getTimeoutForWorkerTask from 'utils/getTimeoutForWorkerTask'; + +const loadDownsampledGeneExpression = ( + experimentId, + genes, + componentUuid, + downsampleSettings = null, +) => async (dispatch, getState) => { + const { loading } = getState().genes.expression; + + // If other gene expression data is already being loaded, don't dispatch. + if (loading.length > 0) { + return null; + } + + // Dispatch loading state. + dispatch({ + type: GENES_EXPRESSION_LOADING, + payload: { + experimentId, + componentUuid, + genes, + }, + }); + + console.log('downsampleSettingsDebug'); + console.log(downsampleSettings); + + const body = { + name: 'GeneExpression', + genes, + downsampled: true, + downsampleSettings, + }; + + const timeout = getTimeoutForWorkerTask(getState(), 'GeneExpression'); + + try { + const { + orderedGeneNames, + rawExpression: rawExpressionJson, + truncatedExpression: truncatedExpressionJson, + zScore: zScoreJson, + stats, + cellOrder, + } = await fetchWork( + experimentId, body, getState, dispatch, { timeout }, + ); + + const rawExpression = SparseMatrix.fromJSON(rawExpressionJson); + const truncatedExpression = SparseMatrix.fromJSON(truncatedExpressionJson); + const zScore = SparseMatrix.fromJSON(zScoreJson); + + dispatch({ + type: GENES_EXPRESSION_LOADED, + payload: { + componentUuid, + genes, + newGenes: { + orderedGeneNames, + stats, + rawExpression, + truncatedExpression, + zScore, + }, + downsampledCellOrder: cellOrder, + }, + }); + } catch (error) { + dispatch({ + type: GENES_EXPRESSION_ERROR, + payload: { + experimentId, + componentUuid, + genes, + error, + }, + }); + } +}; + +export default loadDownsampledGeneExpression; diff --git a/src/redux/actions/genes/loadGeneExpression.js b/src/redux/actions/genes/loadGeneExpression.js index 537850d73d..be6800c2b4 100644 --- a/src/redux/actions/genes/loadGeneExpression.js +++ b/src/redux/actions/genes/loadGeneExpression.js @@ -9,11 +9,11 @@ import fetchWork from 'utils/work/fetchWork'; import getTimeoutForWorkerTask from 'utils/getTimeoutForWorkerTask'; const loadGeneExpression = ( - experimentId, genes, componentUuid, useDownsampledExpression = false, + experimentId, + genes, + componentUuid, ) => async (dispatch, getState) => { - const { - loading, matrix, downsampledMatrix, - } = getState().genes.expression; + const { loading, matrix } = getState().genes.expression; // If other gene expression data is already being loaded, don't dispatch. if (loading.length > 0) { @@ -35,14 +35,7 @@ const loadGeneExpression = ( // Check which of the genes we actually need to load. Only do this if // we are not forced to reload all of the data. let genesToFetch = [...genes]; - let genesAlreadyLoaded; - // If we are using the downsampled expression, then check downsampledMatrix as well - // as the normal one (we can use both) - if (useDownsampledExpression) { - genesAlreadyLoaded = downsampledMatrix.getStoredGenes(); - } else { - genesAlreadyLoaded = matrix.getStoredGenes(); - } + const genesAlreadyLoaded = matrix.getStoredGenes(); genesToFetch = genesToFetch.filter( (gene) => !new Set(upperCaseArray(genesAlreadyLoaded)).has(gene.toUpperCase()), @@ -67,6 +60,7 @@ const loadGeneExpression = ( const body = { name: 'GeneExpression', genes: genesToFetch, + downsampled: false, }; const timeout = getTimeoutForWorkerTask(getState(), 'GeneExpression'); diff --git a/src/redux/reducers/genes/genesExpressionLoaded.js b/src/redux/reducers/genes/genesExpressionLoaded.js index 2ed1c8c194..31771e5ab5 100644 --- a/src/redux/reducers/genes/genesExpressionLoaded.js +++ b/src/redux/reducers/genes/genesExpressionLoaded.js @@ -9,6 +9,7 @@ const genesExpressionLoaded = (state, action) => { componentUuid, genes, loadingStatus = _.difference(upperCaseArray(state.expression.loading), upperCaseArray(genes)), newGenes = undefined, + downsampledCellOrder = null, } = action.payload; // If there's any data to load, load it @@ -24,23 +25,28 @@ const genesExpressionLoaded = (state, action) => { const expressionMatrix = state.expression.matrix; const downsampledExpressionMatrix = state.expression.downsampledMatrix; - expressionMatrix.pushGeneExpression( - orderedGeneNames, - rawExpression, - truncatedExpression, - zScore, - stats, - ); - - downsampledExpressionMatrix.pushGeneExpression( - orderedGeneNames, - rawExpression, - truncatedExpression, - zScore, - stats, - ); + if (downsampledCellOrder) { + downsampledExpressionMatrix.setGeneExpression( + orderedGeneNames, + rawExpression, + truncatedExpression, + zScore, + stats, + ); + } else { + expressionMatrix.pushGeneExpression( + orderedGeneNames, + rawExpression, + truncatedExpression, + zScore, + stats, + ); + } } + // console.log('downsampledCellOrderDebug'); + // console.log(downsampledCellOrder); + return { ...state, expression: { @@ -56,6 +62,7 @@ const genesExpressionLoaded = (state, action) => { }, }, loading: loadingStatus, + downsampledCellOrder: downsampledCellOrder ?? state.expression.downsampledCellOrder, }, }; }; From d8bb8fd92b7f0f6b5730af827091b292890a677f Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 16 Jun 2023 15:28:08 -0300 Subject: [PATCH 02/87] Remove console logs --- src/redux/actions/genes/loadDownsampledGeneExpression.js | 3 --- src/redux/reducers/genes/genesExpressionLoaded.js | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/redux/actions/genes/loadDownsampledGeneExpression.js b/src/redux/actions/genes/loadDownsampledGeneExpression.js index 5b0872cb79..29e354b2ea 100644 --- a/src/redux/actions/genes/loadDownsampledGeneExpression.js +++ b/src/redux/actions/genes/loadDownsampledGeneExpression.js @@ -30,9 +30,6 @@ const loadDownsampledGeneExpression = ( }, }); - console.log('downsampleSettingsDebug'); - console.log(downsampleSettings); - const body = { name: 'GeneExpression', genes, diff --git a/src/redux/reducers/genes/genesExpressionLoaded.js b/src/redux/reducers/genes/genesExpressionLoaded.js index 31771e5ab5..7f45fac918 100644 --- a/src/redux/reducers/genes/genesExpressionLoaded.js +++ b/src/redux/reducers/genes/genesExpressionLoaded.js @@ -44,9 +44,6 @@ const genesExpressionLoaded = (state, action) => { } } - // console.log('downsampledCellOrderDebug'); - // console.log(downsampledCellOrder); - return { ...state, expression: { From 4373c1023df7cf495faeff583fe93b7aec8c5b8c Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 22 Jun 2023 14:49:38 -0300 Subject: [PATCH 03/87] Some refactoring and changes to move markers.ETag to expression.downsampledETag so it can be used for all genes loading --- .../generic-gene-table/ComponentActions.jsx | 21 +------- .../data-exploration/heatmap/HeatmapPlot.jsx | 12 +---- src/redux/actionTypes/genes.js | 6 +++ .../genes/loadDownsampledGeneExpression.js | 52 +++++++++++++++---- src/redux/actions/genes/loadMarkerGenes.js | 2 +- .../reducers/genes/downsampledGenesLoading.js | 12 +++++ src/redux/reducers/genes/index.js | 7 ++- .../reducers/genes/markerGenesLoading.js | 2 +- src/utils/work/fetchWork.js | 4 ++ 9 files changed, 76 insertions(+), 42 deletions(-) create mode 100644 src/redux/reducers/genes/downsampledGenesLoading.js diff --git a/src/components/data-exploration/generic-gene-table/ComponentActions.jsx b/src/components/data-exploration/generic-gene-table/ComponentActions.jsx index 2773aaeb54..17c5f573e7 100644 --- a/src/components/data-exploration/generic-gene-table/ComponentActions.jsx +++ b/src/components/data-exploration/generic-gene-table/ComponentActions.jsx @@ -8,7 +8,6 @@ import { useSelector, useDispatch } from 'react-redux'; import _ from 'lodash'; import { PlusOutlined, RedoOutlined, MinusOutlined } from '@ant-design/icons'; import { loadDownsampledGeneExpression, loadGeneExpression } from 'redux/actions/genes'; -import { getCellSets } from 'redux/selectors'; const geneOperations = { ADD: 'add', @@ -25,13 +24,6 @@ const ComponentActions = (props) => { const selectedGenes = useSelector((state) => state.genes.selected); const displayedGenes = useSelector((state) => state.genes.expression?.views[componentType]?.data); - const componentConfig = useSelector( - (state) => state.componentConfig[componentType]?.config, - _.isEqual, - ) || {}; - - const { hidden: hiddenCellSets } = useSelector(getCellSets()); - const performGeneOperation = (genesOperation) => { let newGenes = _.cloneDeep(selectedGenes); @@ -43,18 +35,7 @@ const ComponentActions = (props) => { } if (useDownsampledExpression) { - const { groupedTracks, selectedCellSet, selectedPoints } = componentConfig; - - const downsampleSettings = { - groupedTracks, - selectedCellSet, - selectedPoints, - hiddenCellSets, - }; - - dispatch( - loadDownsampledGeneExpression(experimentId, newGenes, componentType, downsampleSettings), - ); + dispatch(loadDownsampledGeneExpression(experimentId, newGenes, componentType)); } else { dispatch(loadGeneExpression(experimentId, newGenes, componentType)); } diff --git a/src/components/data-exploration/heatmap/HeatmapPlot.jsx b/src/components/data-exploration/heatmap/HeatmapPlot.jsx index 6b43472121..fe19e419e2 100644 --- a/src/components/data-exploration/heatmap/HeatmapPlot.jsx +++ b/src/components/data-exploration/heatmap/HeatmapPlot.jsx @@ -9,7 +9,7 @@ import _ from 'lodash'; import { getCellSets } from 'redux/selectors'; -import { loadDownsampledGeneExpression, loadGeneExpression, loadMarkerGenes } from 'redux/actions/genes'; +import { loadDownsampledGeneExpression, loadMarkerGenes } from 'redux/actions/genes'; import { loadComponentConfig } from 'redux/actions/componentConfig'; import { updateCellInfo } from 'redux/actions/cellInfo'; @@ -182,7 +182,6 @@ const HeatmapPlot = (props) => { experimentId, selectedGenes, COMPONENT_TYPE, - downsampleSettings, )); } }, [ @@ -234,15 +233,8 @@ const HeatmapPlot = (props) => { } if ((expressionDataError || viewError) && !_.isNil(selectedGenes)) { - const downsampleSettings = { - groupedTracks, - selectedCellSet, - selectedPoints, - hiddenCellSets: cellSets.hidden, - }; - dispatch(loadDownsampledGeneExpression( - experimentId, selectedGenes, COMPONENT_TYPE, downsampleSettings, + experimentId, selectedGenes, COMPONENT_TYPE, )); } }} diff --git a/src/redux/actionTypes/genes.js b/src/redux/actionTypes/genes.js index 6d9c8e166b..4b4b281c16 100644 --- a/src/redux/actionTypes/genes.js +++ b/src/redux/actionTypes/genes.js @@ -60,9 +60,15 @@ const MARKER_GENES_LOADED = `${GENES}/markerGenesLoaded`; */ const MARKER_GENES_ERROR = `${GENES}/markerGenesError`; +/** + * Downsampled genes expression load task + */ +const DOWNSAMPLED_GENES_LOADING = `${GENES}/downsampledGenesLoading`; + export { GENES_PROPERTIES_LOADING, GENES_PROPERTIES_LOADED_PAGINATED, GENES_PROPERTIES_ERROR, GENES_SELECT, GENES_DESELECT, GENES_EXPRESSION_LOADING, GENES_EXPRESSION_LOADED, GENES_EXPRESSION_ERROR, MARKER_GENES_LOADING, MARKER_GENES_LOADED, MARKER_GENES_ERROR, + DOWNSAMPLED_GENES_LOADING, }; diff --git a/src/redux/actions/genes/loadDownsampledGeneExpression.js b/src/redux/actions/genes/loadDownsampledGeneExpression.js index 29e354b2ea..257721fa7a 100644 --- a/src/redux/actions/genes/loadDownsampledGeneExpression.js +++ b/src/redux/actions/genes/loadDownsampledGeneExpression.js @@ -1,7 +1,10 @@ import { SparseMatrix } from 'mathjs'; import { - GENES_EXPRESSION_LOADING, GENES_EXPRESSION_ERROR, GENES_EXPRESSION_LOADED, + GENES_EXPRESSION_LOADING, + GENES_EXPRESSION_ERROR, + GENES_EXPRESSION_LOADED, + DOWNSAMPLED_GENES_LOADING, } from 'redux/actionTypes/genes'; import fetchWork from 'utils/work/fetchWork'; @@ -11,14 +14,16 @@ const loadDownsampledGeneExpression = ( experimentId, genes, componentUuid, - downsampleSettings = null, ) => async (dispatch, getState) => { - const { loading } = getState().genes.expression; + const state = getState(); - // If other gene expression data is already being loaded, don't dispatch. - if (loading.length > 0) { - return null; - } + const { + groupedTracks, + selectedCellSet, + selectedPoints, + } = state.componentConfig[componentUuid]?.config; + + const hiddenCellSets = Array.from(state.cellSets.hidden); // Dispatch loading state. dispatch({ @@ -34,12 +39,19 @@ const loadDownsampledGeneExpression = ( name: 'GeneExpression', genes, downsampled: true, - downsampleSettings, + downsampleSettings: { + groupedTracks, + selectedCellSet, + selectedPoints, + hiddenCellSets, + }, }; const timeout = getTimeoutForWorkerTask(getState(), 'GeneExpression'); try { + let requestETag; + const { orderedGeneNames, rawExpression: rawExpressionJson, @@ -48,9 +60,31 @@ const loadDownsampledGeneExpression = ( stats, cellOrder, } = await fetchWork( - experimentId, body, getState, dispatch, { timeout }, + experimentId, body, getState, dispatch, + { + timeout, + onETagGenerated: (ETag) => { + requestETag = ETag; + + // Dispatch loading state. + dispatch({ + type: DOWNSAMPLED_GENES_LOADING, + payload: { + experimentId, + componentUuid, + ETag, + }, + }); + }, + }, ); + // If the ETag is different, that means that a new request was sent in between + // So we don't need to handle this outdated result + if (getState().genes.expression.downsampledETag !== requestETag) { + return; + } + const rawExpression = SparseMatrix.fromJSON(rawExpressionJson); const truncatedExpression = SparseMatrix.fromJSON(truncatedExpressionJson); const zScore = SparseMatrix.fromJSON(zScoreJson); diff --git a/src/redux/actions/genes/loadMarkerGenes.js b/src/redux/actions/genes/loadMarkerGenes.js index 475dfc89b4..9a75c1bf5b 100644 --- a/src/redux/actions/genes/loadMarkerGenes.js +++ b/src/redux/actions/genes/loadMarkerGenes.js @@ -57,7 +57,7 @@ const loadMarkerGenes = ( // If the ETag is different, that means that a new request was sent in between // So we don't need to handle this outdated result - if (getState().genes.markers.ETag !== requestETag) { + if (getState().genes.expression.downsampledETag !== requestETag) { return; } diff --git a/src/redux/reducers/genes/downsampledGenesLoading.js b/src/redux/reducers/genes/downsampledGenesLoading.js new file mode 100644 index 0000000000..a38f271fe8 --- /dev/null +++ b/src/redux/reducers/genes/downsampledGenesLoading.js @@ -0,0 +1,12 @@ +/* eslint-disable no-param-reassign */ +import produce from 'immer'; + +import getInitialState from './getInitialState'; + +const downsampledGenesLoading = produce((draft, action) => { + const { ETag } = action.payload; + + draft.expression.downsampledETag = ETag; +}, getInitialState()); + +export default downsampledGenesLoading; diff --git a/src/redux/reducers/genes/index.js b/src/redux/reducers/genes/index.js index 96297c3421..58800fa44d 100644 --- a/src/redux/reducers/genes/index.js +++ b/src/redux/reducers/genes/index.js @@ -3,7 +3,7 @@ import { GENES_PROPERTIES_LOADING, GENES_PROPERTIES_LOADED_PAGINATED, GENES_PROPERTIES_ERROR, GENES_SELECT, GENES_DESELECT, GENES_EXPRESSION_LOADING, GENES_EXPRESSION_LOADED, GENES_EXPRESSION_ERROR, - MARKER_GENES_LOADING, MARKER_GENES_LOADED, MARKER_GENES_ERROR, + MARKER_GENES_LOADING, DOWNSAMPLED_GENES_LOADING, MARKER_GENES_LOADED, MARKER_GENES_ERROR, } from '../../actionTypes/genes'; import { EXPERIMENT_SETTINGS_QC_START } from '../../actionTypes/experimentSettings'; @@ -19,6 +19,8 @@ import markerGenesLoading from './markerGenesLoading'; import markerGenesError from './markerGenesError'; import markerGenesLoaded from './markerGenesLoaded'; +import downsampledGenesLoading from './downsampledGenesLoading'; + import genesSelect from './genesSelect'; import genesDeselect from './genesDeselect'; @@ -54,6 +56,9 @@ const genesReducer = (state = getInitialState(), action) => { case MARKER_GENES_LOADING: { return markerGenesLoading(state, action); } + case DOWNSAMPLED_GENES_LOADING: { + return downsampledGenesLoading(state, action); + } case MARKER_GENES_LOADED: { return markerGenesLoaded(state, action); } diff --git a/src/redux/reducers/genes/markerGenesLoading.js b/src/redux/reducers/genes/markerGenesLoading.js index a84369504b..294495ecc9 100644 --- a/src/redux/reducers/genes/markerGenesLoading.js +++ b/src/redux/reducers/genes/markerGenesLoading.js @@ -8,7 +8,7 @@ const markerGenesLoading = produce((draft, action) => { draft.markers.loading = true; draft.markers.error = false; - draft.markers.ETag = ETag; + draft.expression.downsampledETag = ETag; }, getInitialState()); export default markerGenesLoading; diff --git a/src/utils/work/fetchWork.js b/src/utils/work/fetchWork.js index 6a2a303996..3df91f066f 100644 --- a/src/utils/work/fetchWork.js +++ b/src/utils/work/fetchWork.js @@ -15,6 +15,7 @@ const fetchGeneExpressionWorkWithoutLocalCache = async ( environment, broadcast, extras, + onETagGenerated, dispatch, getState, ) => { @@ -32,6 +33,8 @@ const fetchGeneExpressionWorkWithoutLocalCache = async ( getState, ); + onETagGenerated(ETag); + // Then, we may be able to find this in S3. const response = await seekFromS3(ETag, experimentId, body.name); @@ -95,6 +98,7 @@ const fetchWork = async ( environment, broadcast, extras, + onETagGenerated, dispatch, getState, ); From 5937a7e19d6b299016ca9555839fd0ad512e8d7c Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 22 Jun 2023 16:02:08 -0300 Subject: [PATCH 04/87] Start using loadDownsampledGeneExpression (instead of loadGeneExpression) in plots and tables heatmap --- .../plots-and-tables/marker-heatmap/index.jsx | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/pages/experiments/[experimentId]/plots-and-tables/marker-heatmap/index.jsx b/src/pages/experiments/[experimentId]/plots-and-tables/marker-heatmap/index.jsx index 2dec680192..4394143089 100644 --- a/src/pages/experiments/[experimentId]/plots-and-tables/marker-heatmap/index.jsx +++ b/src/pages/experiments/[experimentId]/plots-and-tables/marker-heatmap/index.jsx @@ -23,7 +23,7 @@ import { updatePlotConfig, loadPlotConfig } from 'redux/actions/componentConfig' import Header from 'components/Header'; import PlotContainer from 'components/plots/PlotContainer'; import { generateSpec } from 'utils/plotSpecs/generateHeatmapSpec'; -import { loadGeneExpression, loadMarkerGenes } from 'redux/actions/genes'; +import { loadDownsampledGeneExpression, loadMarkerGenes } from 'redux/actions/genes'; import loadGeneList from 'redux/actions/genes/loadGeneList'; import { loadCellSets } from 'redux/actions/cellSets'; import PlatformError from 'components/PlatformError'; @@ -113,16 +113,25 @@ const MarkerHeatmap = ({ experimentId }) => { ) ) return; - dispatch(loadMarkerGenes( - experimentId, - plotUuid, - { - numGenes: config.nMarkerGenes, - groupedTracks: config.groupedTracks, - selectedCellSet: config.selectedCellSet, - selectedPoints: config.selectedPoints, - }, - )); + // On first load no selected genes, so fill in with markers + if (config.selectedGenes.length === 0) { + dispatch(loadMarkerGenes( + experimentId, + plotUuid, + { + numGenes: config.nMarkerGenes, + groupedTracks: config.groupedTracks, + selectedCellSet: config.selectedCellSet, + selectedPoints: config.selectedPoints, + }, + )); + } else { + dispatch(loadDownsampledGeneExpression( + experimentId, + config.selectedGenes, + plotUuid, + )); + } }, [ config?.nMarkerGenes, config?.groupedTracks, @@ -313,7 +322,7 @@ const MarkerHeatmap = ({ experimentId }) => { ]; const onGenesChange = (genes) => { - dispatch(loadGeneExpression(experimentId, genes, plotUuid, true)); + dispatch(loadDownsampledGeneExpression(experimentId, genes, plotUuid)); }; const onGenesSelect = (genes) => { @@ -321,7 +330,7 @@ const MarkerHeatmap = ({ experimentId }) => { if (_.isEqual(allGenes, config?.selectedGenes)) return; - dispatch(loadGeneExpression(experimentId, allGenes, plotUuid, true)); + dispatch(loadDownsampledGeneExpression(experimentId, allGenes, plotUuid)); }; const onReset = () => { @@ -424,9 +433,11 @@ const MarkerHeatmap = ({ experimentId }) => { dispatch(loadGeneExpression(experimentId, config.selectedGenes, plotUuid, true)) - } + onClick={() => { + dispatch( + loadDownsampledGeneExpression(experimentId, config.selectedGenes, plotUuid), + ); + }} /> ); } From 261da99433f394c0aab93415f6e45252ba98d023 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 22 Jun 2023 16:13:01 -0300 Subject: [PATCH 05/87] Remove unnecessary onGenesChange trigger, the genes are replaced right after this call with the marker right after it anyways --- .../[experimentId]/plots-and-tables/marker-heatmap/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/experiments/[experimentId]/plots-and-tables/marker-heatmap/index.jsx b/src/pages/experiments/[experimentId]/plots-and-tables/marker-heatmap/index.jsx index 4394143089..78beb5065d 100644 --- a/src/pages/experiments/[experimentId]/plots-and-tables/marker-heatmap/index.jsx +++ b/src/pages/experiments/[experimentId]/plots-and-tables/marker-heatmap/index.jsx @@ -334,7 +334,6 @@ const MarkerHeatmap = ({ experimentId }) => { }; const onReset = () => { - onGenesChange([]); dispatch(loadMarkerGenes( experimentId, plotUuid, From 2ab922d6ec878b33bfa459d8efc08ff5760d6b22 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 22 Jun 2023 16:41:23 -0300 Subject: [PATCH 06/87] Minor refactoring --- src/redux/reducers/genes/genesExpressionLoaded.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/redux/reducers/genes/genesExpressionLoaded.js b/src/redux/reducers/genes/genesExpressionLoaded.js index 7f45fac918..27089a9878 100644 --- a/src/redux/reducers/genes/genesExpressionLoaded.js +++ b/src/redux/reducers/genes/genesExpressionLoaded.js @@ -22,11 +22,8 @@ const genesExpressionLoaded = (state, action) => { stats, } = newGenes; - const expressionMatrix = state.expression.matrix; - const downsampledExpressionMatrix = state.expression.downsampledMatrix; - if (downsampledCellOrder) { - downsampledExpressionMatrix.setGeneExpression( + state.expression.downsampledMatrix.setGeneExpression( orderedGeneNames, rawExpression, truncatedExpression, @@ -34,7 +31,7 @@ const genesExpressionLoaded = (state, action) => { stats, ); } else { - expressionMatrix.pushGeneExpression( + state.expression.matrix.pushGeneExpression( orderedGeneNames, rawExpression, truncatedExpression, From 6ea720d2447c795bba76ee8f0c7a1df67b196c2f Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 22 Jun 2023 17:27:53 -0300 Subject: [PATCH 07/87] Add mechanism to manage genes onDelete expression reload correctly --- src/components/plots/GeneReorderTool.jsx | 34 ++++++++++++++++++------ 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/components/plots/GeneReorderTool.jsx b/src/components/plots/GeneReorderTool.jsx index 873b539da2..00748c47ec 100644 --- a/src/components/plots/GeneReorderTool.jsx +++ b/src/components/plots/GeneReorderTool.jsx @@ -1,4 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React, { + useCallback, useEffect, useState, +} from 'react'; +import _ from 'lodash'; import { useDispatch, useSelector } from 'react-redux'; import PropTypes from 'prop-types'; @@ -14,10 +17,22 @@ const GeneReorderTool = (props) => { const { plotUuid, onDelete } = props; const dispatch = useDispatch(); - + const config = useSelector((state) => state.componentConfig[plotUuid]?.config); + const genesLoading = useSelector((state) => state.genes.expression.loading); + + const [selectedGenesLocal, setSelectedGenesLocal] = useState([]); + + useEffect(() => { + setSelectedGenesLocal(config?.selectedGenes); + }, [config?.selectedGenes]); + + const debouncedOnDelete = useCallback(_.debounce((newGenes) => { + onDelete(newGenes); + }, 1000), []); - // Tree from antd requires format [{key: , title: }], made from gene names from loadedMarkerGenes and config + // Tree from antd requires format [{key: , title: }], + // made from gene names from loadedMarkerGenes and config const composeGeneTree = (treeGenes) => { if (!treeGenes.length) { return []; @@ -33,8 +48,8 @@ const GeneReorderTool = (props) => { const [geneTreeData, setGeneTreeData] = useState([]); useEffect(() => { - setGeneTreeData(composeGeneTree(config?.selectedGenes)); - }, [config?.selectedGenes]); + setGeneTreeData(composeGeneTree(selectedGenesLocal)); + }, [selectedGenesLocal]); // geneKey is equivalent to it's index, moves a gene from pos geneKey to newPosition // dispatches an action to update selectedGenes in config @@ -47,10 +62,12 @@ const GeneReorderTool = (props) => { }; const onNodeDelete = (geneKey) => { - const genes = geneTreeData.map((treeNode) => treeNode.title); + const genes = [...geneTreeData.map((treeNode) => treeNode.title)]; genes.splice(geneKey, 1); - onDelete(genes); + setSelectedGenesLocal(genes); + + debouncedOnDelete(genes); }; const renderTitles = (data) => { @@ -63,6 +80,7 @@ const GeneReorderTool = (props) => { {treeNode.title}