Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NickAkhmetov/CAT-1010 Integrate Cellpop into organ page #3674

Merged
merged 19 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@
"cwd": "${workspaceFolder}/context"
},
"command": "npx --yes kill-port 5000 5001 6006"
},
{
"label": "npm test:watch",
"type": "npm",
"script": "test:watch",
"options": {
"cwd": "${workspaceFolder}/context"
},
}
]
}
1 change: 1 addition & 0 deletions CHANGELOG-cat-1010-cellpop-organ-page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add scalable cell population plots to organ pages with compatible labeled datasets.
4 changes: 2 additions & 2 deletions context/app/static/js/components/Header/Header/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import useVisualizationStore from 'js/stores/useVisualizationStore';
import useEntityStore from 'js/stores/useEntityStore';

const pathsWithEntityHeader = ['/browse', '/cell-types', '/genes'];
const pathsWithEntityHeader = ['/browse', '/cell-types', '/genes', '/organ/'];

const exceptionPaths = ['/browse/collection'];

Expand All @@ -11,7 +11,7 @@ function locationStartsWithPath(path: string) {

export function useEntityHeaderVisibility() {
const { summaryInView, summaryEntry } = useEntityStore((state) => state.summaryComponentObserver);
const vizIsFullscreen = useVisualizationStore((state) => state.vizIsFullscreen);
const vizIsFullscreen = useVisualizationStore((state) => Boolean(state.fullscreenVizId));

const matchesPath = pathsWithEntityHeader.some(locationStartsWithPath);
const isExceptionPath = exceptionPaths.some(locationStartsWithPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import SummaryBody from '../../summary/SummaryBody';
const AnimatedPaper = animated(StyledPaper);

const visualizationSelector = (state: VisualizationStore) => ({
vizIsFullscreen: state.vizIsFullscreen,
vizIsFullscreen: Boolean(state.fullscreenVizId),
});

function Header() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Divider from '@mui/material/Divider';

import VizualizationThemeSwitch from 'js/components/detailPage/visualization/VisualizationThemeSwitch';
import VisualizationThemeSwitch from 'js/components/detailPage/visualization/VisualizationThemeSwitch';
import VisualizationCollapseButton from 'js/components/detailPage/visualization/VisualizationCollapseButton';
import VisualizationWorkspaceButton from 'js/components/detailPage/visualization/VisualizationWorkspaceButton';
import { AllEntityTypes, entityIconMap } from 'js/shared-styles/icons/entityIconMap';
Expand Down Expand Up @@ -188,8 +188,9 @@ const entityStoreSelector = (state: EntityStore) => ({
});

const visualizationSelector = (state: VisualizationStore) => ({
vizIsFullscreen: state.vizIsFullscreen,
vizIsFullscreen: Boolean(state.fullscreenVizId),
vizNotebookId: state.vizNotebookId,
isVitessce: Boolean(state.vitessceVisualization),
});

function EntityHeaderContent({ view, setView }: { view: SummaryViewsType; setView: (v: SummaryViewsType) => void }) {
Expand All @@ -201,7 +202,7 @@ function EntityHeaderContent({ view, setView }: { view: SummaryViewsType; setVie
const { entity } = useFlaskDataContext();
const { hubmap_id, entity_type } = entity;

const { vizIsFullscreen, vizNotebookId } = useVisualizationStore(visualizationSelector);
const { vizIsFullscreen, vizNotebookId, isVitessce } = useVisualizationStore(visualizationSelector);

const styles = useSpring({
from: { opacity: 1 },
Expand Down Expand Up @@ -238,8 +239,8 @@ function EntityHeaderContent({ view, setView }: { view: SummaryViewsType; setVie
{vizIsFullscreen ? (
<>
{vizNotebookId && <VisualizationWorkspaceButton />}
<VisualizationShareButtonWrapper />
<VizualizationThemeSwitch />
{isVitessce && <VisualizationShareButtonWrapper />}
<VisualizationThemeSwitch />
<VisualizationCollapseButton />
</>
) : (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import useVisualizationStore, { VisualizationStore } from 'js/stores/useVisualizationStore';
import { bodyExpandedCSS } from './style';

const visualizationStoreSelector = (state: VisualizationStore) => state.fullscreenVizId;

interface BodyExpandedCSSProps {
id: string;
}

export default function BodyExpandedCSS({ id }: BodyExpandedCSSProps) {
const fullscreenVizId = useVisualizationStore(visualizationStoreSelector);
const vizIsFullscreen = fullscreenVizId === id;
return <style type="text/css">{vizIsFullscreen && bodyExpandedCSS}</style>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import BodyExpandedCSS from './BodyExpandedCSS';

export default BodyExpandedCSS;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const bodyExpandedCSS = `
body {
overflow: hidden;
}
`;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useCallback } from 'react';
import React, { useEffect, useMemo, useCallback, useId } from 'react';
import { Vitessce } from 'vitessce';

import Paper from '@mui/material/Paper';
Expand All @@ -19,19 +19,19 @@ import VisualizationThemeSwitch from 'js/components/detailPage/visualization/Vis
import VisualizationFooter from 'js/components/detailPage/visualization/VisualizationFooter';
import VisualizationTracker from 'js/components/detailPage/visualization/VisualizationTracker';

import BodyExpandedCSS from 'js/components/detailPage/visualization/BodyExpandedCSS';
import { useCanvasScrollFix, useCollapseViz, useFirefoxWarning, useVitessceConfig } from './hooks';
import {
ExpandButton,
ExpandableDiv,
SelectionButton,
StyledDetailPageSection,
StyledSectionHeader,
bodyExpandedCSS,
vitessceFixedHeight,
} from './style';

const visualizationStoreSelector = (state: VisualizationStore) => ({
vizIsFullscreen: state.vizIsFullscreen,
fullscreenVizId: state.fullscreenVizId,
expandViz: state.expandViz,
collapseViz: state.collapseViz,
vizTheme: state.vizTheme,
Expand All @@ -57,9 +57,12 @@ function Visualization({
shouldMountVitessce = true,
markerGene,
}: VisualizationProps) {
const { vizIsFullscreen, expandViz, vizTheme, setVitessceState, setVitessceStateDebounced, setVizNotebookId } =
const { fullscreenVizId, expandViz, vizTheme, setVitessceState, setVitessceStateDebounced, setVizNotebookId } =
useVisualizationStore(visualizationStoreSelector);

const id = useId();
const vizIsFullscreen = fullscreenVizId === id;

// Add event listeners to the document to handle the full screen mode.
useCollapseViz();
// Show a warning to Firefox users that Vitessce may be slower in Firefox.
Expand Down Expand Up @@ -103,9 +106,17 @@ function Visualization({
markerGene,
});

function setSelectionAndClearErrors({ i }: { i: number }) {
setVitessceSelection(i);
}
const setSelectionAndClearErrors = useCallback(
({ i }: { i: number }) => {
setVitessceSelection(i);
},
[setVitessceSelection],
);

const expandVisualization = useCallback(() => {
expandViz(id);
trackEntityPageEvent({ action: 'Vitessce / Full Screen' });
}, [expandViz, id, trackEntityPageEvent]);

const isMultiDataset = Array.isArray(vitessceConfig);

Expand Down Expand Up @@ -139,14 +150,7 @@ function Visualization({
<VisualizationShareButton />
<VisualizationThemeSwitch />
<SecondaryBackgroundTooltip title="Switch to Fullscreen">
<ExpandButton
size="small"
onClick={() => {
expandViz();
trackEntityPageEvent({ action: 'Vitessce / Full Screen' });
}}
variant="contained"
>
<ExpandButton size="small" onClick={expandVisualization} variant="contained">
<FullscreenRoundedIcon color="primary" />
</ExpandButton>
</SecondaryBackgroundTooltip>
Expand Down Expand Up @@ -182,7 +186,7 @@ function Visualization({
</ExpandableDiv>
</Paper>
<VisualizationFooter />
<style type="text/css">{vizIsFullscreen && bodyExpandedCSS}</style>
<BodyExpandedCSS id={id} />
</StyledDetailPageSection>
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,27 @@ const SelectionButton = styled(Button)(({ theme }) => ({
interface ExpandableDivProps {
$isExpanded: boolean;
$theme: 'dark' | 'light';
$nonExpandedHeight?: number;
}

const ExpandableDiv = styled('div')<ExpandableDivProps>(({ $isExpanded, theme, $theme }) => ({
top: $isExpanded ? `${totalHeightOffset}px` : 'auto',
left: $isExpanded ? '0' : 'auto',
position: $isExpanded ? 'fixed' : 'relative',
height: $isExpanded ? `calc(100vh - ${totalHeightOffset}px)` : `${vitessceFixedHeight}px`,
backgroundColor: $theme === 'dark' ? '#333333' : theme.palette.white.main,
width: '100%',
overflow: 'scroll',
'.vitessce-container': {
display: 'block',
height: $isExpanded ? `calc(100vh - ${totalHeightOffset}px)` : 'auto',
const ExpandableDiv = styled('div')<ExpandableDivProps>(
({ $isExpanded, theme, $theme, $nonExpandedHeight = vitessceFixedHeight }) => ({
top: $isExpanded ? `${totalHeightOffset}px` : 'auto',
left: $isExpanded ? '0' : 'auto',
position: $isExpanded ? 'fixed' : 'relative',
zIndex: $isExpanded ? theme.zIndex.modal : 'auto',
height: $isExpanded ? `calc(100vh - ${totalHeightOffset}px)` : `${$nonExpandedHeight}px`,
backgroundColor: $theme === 'dark' ? '#333333' : theme.palette.white.main,
width: '100%',
position: 'static',
},
}));
overflow: 'scroll',
'.vitessce-container': {
display: 'block',
height: $isExpanded ? `calc(100vh - ${totalHeightOffset}px)` : 'auto',
width: '100%',
position: 'static',
},
}),
);

interface StyledDetailPageSectionProps {
$vizIsFullscreen: boolean;
Expand All @@ -63,15 +67,8 @@ const StyledDetailPageSection = styled(DetailPageSection)<StyledDetailPageSectio
}),
);

const bodyExpandedCSS = `
body {
overflow: hidden;
}
`;

export {
vitessceFixedHeight,
bodyExpandedCSS,
StyledHeader,
StyledSectionHeader,
ExpandButton,
Expand Down
3 changes: 2 additions & 1 deletion context/app/static/js/components/organ/Assays/Assays.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useDatasetTypeMap } from 'js/components/home/HuBMAPDatasetsChart/hooks'

import { CollapsibleDetailPageSection } from 'js/components/detailPage/DetailPageSection';
import { DatasetIcon } from 'js/shared-styles/icons';
import withShouldDisplay from 'js/helpers/withShouldDisplay';
import { getSearchURL } from '../utils';

interface AssaysProps {
Expand Down Expand Up @@ -70,4 +71,4 @@ function Assays({ organTerms, bucketData, id: sectionId }: AssaysProps) {
);
}

export default Assays;
export default withShouldDisplay(Assays);
3 changes: 2 additions & 1 deletion context/app/static/js/components/organ/Azimuth/Azimuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import OutboundLinkButton from 'js/shared-styles/Links/OutboundLinkButton';
import VisualizationWrapper from 'js/components/detailPage/visualization/VisualizationWrapper/VisualizationWrapper';

import { CollapsibleDetailPageSection } from 'js/components/detailPage/DetailPageSection';
import withShouldDisplay from 'js/helpers/withShouldDisplay';
import ReferenceBasedAnalysis from './ReferenceBasedAnalysis';
import { AzimuthConfig } from '../types';

Expand Down Expand Up @@ -33,4 +34,4 @@ function Azimuth({ config, id }: AzimuthProps) {
);
}

export default Azimuth;
export default withShouldDisplay(Azimuth);
39 changes: 39 additions & 0 deletions context/app/static/js/components/organ/CellPop/CellPopActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { useCallback } from 'react';
import Stack from '@mui/material/Stack';
import { ExpandButton } from 'js/components/detailPage/visualization/Visualization/style';
import { SecondaryBackgroundTooltip } from 'js/shared-styles/tooltips';
import FullscreenRoundedIcon from '@mui/icons-material/FullscreenRounded';
import VisualizationThemeSwitch from 'js/components/detailPage/visualization/VisualizationThemeSwitch';
import { SpacedSectionButtonRow } from 'js/shared-styles/sections/SectionButtonRow';
import useVisualizationStore, { VisualizationStore } from 'js/stores/useVisualizationStore';
import { trackEvent } from 'js/helpers/trackers';

interface CellPopActionsProps {
id: string;
}

function visualizationSelector(store: VisualizationStore) {
return store.expandViz;
}

export default function CellPopActions({ id }: CellPopActionsProps) {
const expandViz = useVisualizationStore(visualizationSelector);
const expand = useCallback(() => {
trackEvent({ category: 'Organ Page', action: 'Expand Cell Population Plot' });
expandViz(id);
}, [expandViz, id]);
return (
<SpacedSectionButtonRow
buttons={
<Stack direction="row" spacing={1}>
<VisualizationThemeSwitch />
<SecondaryBackgroundTooltip title="Switch to Fullscreen">
<ExpandButton size="small" onClick={expand} variant="contained">
<FullscreenRoundedIcon color="primary" />
</ExpandButton>
</SecondaryBackgroundTooltip>
</Stack>
}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { SectionDescription } from 'js/shared-styles/sections/SectionDescription';
import LabelledSectionText from 'js/shared-styles/sections/LabelledSectionText';

// TODO: Once there is a cellpop tutorial to link to,
// uncomment the below code and add RelevantPages to the SectionDescription addendum

// import OutlinedButton from 'js/shared-styles/buttons/OutlinedButton';
// import Link from '@mui/icons-material/Link';
// function RelevantPages() {
// return (
// <LabelledSectionText label="Relevant Pages" key="relevant-pages">
// <OutlinedButton
// color="info"
// endIcon={
// <Link />
// }
// >
// Tutorial
// </OutlinedButton>
// </LabelledSectionText>
// );
// }

export default function CellPopDescription() {
return (
<SectionDescription
addendum={[
<LabelledSectionText label="Basic Exploration" key="basic-exploration">
Hover over any items in the plot to reveal additional information about the data. Toggle to select either
columns or rows to adjust the plot as needed. Toggle between bar charts and violin plots to display either the
total count of cells or their fractional distributions.
</LabelledSectionText>,
<LabelledSectionText label="Plot Controls" key="plot-controls">
Use the plot controls to modify sorting preferences or display options. Display options include toggling the
visibility of a specific column or row, or embedding a bar chart to compare the amounts of cell types within a
dataset.
</LabelledSectionText>,
]}
>
This interactive heatmap visualizes cell populations in datasets from this organ. Cell type annotations are from
Azimuth. Key features are highlighted below and a tutorial is available.
</SectionDescription>
);
}
Loading
Loading