UNPKG

vitessce

Version:

Vitessce app and React component library

240 lines (224 loc) 8.44 kB
import React, { useEffect, useState, useCallback, useMemo, } from 'react'; import TitleInfo from '../TitleInfo'; import { pluralize, capitalize } from '../../utils'; import { useDeckCanvasSize, useReady, useUrls } from '../hooks'; import { mergeCellSets } from '../utils'; import { useCellsData, useCellSetsData, useExpressionMatrixData, } from '../data-hooks'; import { getCellColors } from '../interpolate-colors'; import { useCoordination, useLoaders, useSetComponentHover, useSetComponentViewInfo, } from '../../app/state/hooks'; import { COMPONENT_COORDINATION_TYPES, } from '../../app/state/coordination'; import Heatmap from './Heatmap'; import HeatmapTooltipSubscriber from './HeatmapTooltipSubscriber'; import HeatmapOptions from './HeatmapOptions'; const HEATMAP_DATA_TYPES = ['cells', 'cell-sets', 'expression-matrix']; /** * @param {object} props * @param {number} props.uuid The unique identifier for this component. * @param {object} props.coordinationScopes The mapping from coordination types to coordination * scopes. * @param {function} props.removeGridComponent The callback function to pass to TitleInfo, * to call when the component has been removed from the grid. * @param {string} props.title The component title. * @param {boolean} props.transpose Whether to * render as cell-by-gene or gene-by-cell. * @param {string} props.observationsLabelOverride The singular * form of the name of the observation. * @param {string} props.observationsPluralLabelOverride The * plural form of the name of the observation. * @param {string} props.variablesLabelOverride The singular * form of the name of the variable. * @param {string} props.variablesPluralLabelOverride The plural * form of the name of the variable. * @param {boolean} props.disableTooltip Whether to disable the * tooltip on mouse hover. */ export default function HeatmapSubscriber(props) { const { uuid, coordinationScopes, removeGridComponent, theme, transpose, observationsLabelOverride: observationsLabel = 'cell', observationsPluralLabelOverride: observationsPluralLabel = `${observationsLabel}s`, variablesLabelOverride: variablesLabel = 'gene', variablesPluralLabelOverride: variablesPluralLabel = `${variablesLabel}s`, disableTooltip = false, title = 'Heatmap', } = props; const loaders = useLoaders(); const setComponentHover = useSetComponentHover(); const setComponentViewInfo = useSetComponentViewInfo(uuid); // Get "props" from the coordination space. const [{ dataset, heatmapZoomX: zoomX, heatmapTargetX: targetX, heatmapTargetY: targetY, featureSelection: geneSelection, obsHighlight: cellHighlight, featureHighlight: geneHighlight, obsSetSelection: cellSetSelection, obsSetColor: cellSetColor, additionalObsSets: additionalCellSets, featureValueColormap: geneExpressionColormap, featureValueColormapRange: geneExpressionColormapRange, }, { setHeatmapZoomX: setZoomX, setHeatmapZoomY: setZoomY, setHeatmapTargetX: setTargetX, setHeatmapTargetY: setTargetY, setObsHighlight: setCellHighlight, setFeatureHighlight: setGeneHighlight, setObsSetSelection: setCellSetSelection, setObsSetColor: setCellSetColor, setFeatureValueColormapRange: setGeneExpressionColormapRange, setFeatureValueColormap: setGeneExpressionColormap, }] = useCoordination(COMPONENT_COORDINATION_TYPES.heatmap, coordinationScopes); const observationsTitle = capitalize(observationsPluralLabel); const variablesTitle = capitalize(variablesPluralLabel); const [isRendering, setIsRendering] = useState(false); const [ isReady, setItemIsReady, setItemIsNotReady, // eslint-disable-line no-unused-vars resetReadyItems, ] = useReady( HEATMAP_DATA_TYPES, ); const [urls, addUrl, resetUrls] = useUrls(); const [width, height, deckRef] = useDeckCanvasSize(); // Reset file URLs and loader progress when the dataset has changed. useEffect(() => { resetUrls(); resetReadyItems(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [loaders, dataset]); // Get data from loaders using the data hooks. const [cells] = useCellsData(loaders, dataset, setItemIsReady, addUrl, false); const [expressionMatrix] = useExpressionMatrixData( loaders, dataset, setItemIsReady, addUrl, true, ); const [cellSets] = useCellSetsData( loaders, dataset, setItemIsReady, addUrl, false, { setObsSetSelection: setCellSetSelection, setObsSetColor: setCellSetColor }, { obsSetSelection: cellSetSelection, obsSetColor: cellSetColor }, ); const mergedCellSets = useMemo(() => mergeCellSets( cellSets, additionalCellSets, ), [cellSets, additionalCellSets]); const cellColors = useMemo(() => getCellColors({ // Only show cell set selection on heatmap labels. cellColorEncoding: 'cellSetSelection', geneSelection, cellSets: mergedCellSets, cellSetSelection, cellSetColor, expressionDataAttrs: expressionMatrix, theme, }), [mergedCellSets, geneSelection, theme, cellSetColor, cellSetSelection, expressionMatrix]); const getCellInfo = useCallback((cellId) => { if (cellId) { const cellInfo = cells[cellId]; return { [`${capitalize(observationsLabel)} ID`]: cellId, ...(cellInfo ? cellInfo.factors : {}), }; } return null; }, [cells, observationsLabel]); const getGeneInfo = useCallback((geneId) => { if (geneId) { return { [`${capitalize(variablesLabel)} ID`]: geneId }; } return null; }, [variablesLabel]); const setTrackHighlight = useCallback(() => { // No-op, since the default handler // logs in the console on every hover event. }, []); const cellColorLabels = useMemo(() => ([ `${capitalize(observationsLabel)} Set`, ]), [observationsLabel]); const cellsCount = expressionMatrix && expressionMatrix.rows ? expressionMatrix.rows.length : 0; const genesCount = expressionMatrix && expressionMatrix.cols ? expressionMatrix.cols.length : 0; const selectedCount = cellColors.size; return ( <TitleInfo title={title} info={`${cellsCount} ${pluralize(observationsLabel, observationsPluralLabel, cellsCount)} × ${genesCount} ${pluralize(variablesLabel, variablesPluralLabel, genesCount)}, with ${selectedCount} ${pluralize(observationsLabel, observationsPluralLabel, selectedCount)} selected`} urls={urls} theme={theme} removeGridComponent={removeGridComponent} isReady={isReady && !isRendering} options={( <HeatmapOptions geneExpressionColormap={geneExpressionColormap} setGeneExpressionColormap={setGeneExpressionColormap} geneExpressionColormapRange={geneExpressionColormapRange} setGeneExpressionColormapRange={setGeneExpressionColormapRange} /> )} > <Heatmap ref={deckRef} transpose={transpose} viewState={{ zoom: zoomX, target: [targetX, targetY] }} setViewState={({ zoom, target }) => { setZoomX(zoom); setZoomY(zoom); setTargetX(target[0]); setTargetY(target[1]); }} colormapRange={geneExpressionColormapRange} setColormapRange={setGeneExpressionColormapRange} height={height} width={width} theme={theme} uuid={uuid} expressionMatrix={expressionMatrix} cellColors={cellColors} colormap={geneExpressionColormap} setIsRendering={setIsRendering} setCellHighlight={setCellHighlight} setGeneHighlight={setGeneHighlight} setTrackHighlight={setTrackHighlight} setComponentHover={() => { setComponentHover(uuid); }} updateViewInfo={setComponentViewInfo} observationsTitle={observationsTitle} variablesTitle={variablesTitle} variablesDashes={false} observationsDashes={false} cellColorLabels={cellColorLabels} useDevicePixels /> {!disableTooltip && ( <HeatmapTooltipSubscriber parentUuid={uuid} width={width} height={height} transpose={transpose} getCellInfo={getCellInfo} getGeneInfo={getGeneInfo} cellHighlight={cellHighlight} geneHighlight={geneHighlight} /> )} </TitleInfo> ); }