Skip to content

Commit

Permalink
feat: 5673 cell guide tissue specific cell type route (#6232)
Browse files Browse the repository at this point in the history
  • Loading branch information
tihuan authored Dec 7, 2023
1 parent 04ca119 commit 35b99a4
Show file tree
Hide file tree
Showing 17 changed files with 564 additions and 205 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["<meta charset=\"utf-8\">","<meta id=\"newsletter-signup-meta\" name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">","<meta name=\"description\" content=\"Find comprehensive information about brain specific &quot;neuron&quot; cell types (synonyms: nerve cell). Neurons: Essential cells in the nervous system responsible for receiving, processing, and transmitting information, enabling vital body functions.\">","<meta name=\"next-head-count\" content=\"17\">","<meta name=\"theme-color\" content=\"#000000\">","<meta property=\"og:creator\" content=\"@cziscience\">","<meta property=\"og:description\" content=\"Find comprehensive information about brain specific &quot;neuron&quot; cell types (synonyms: nerve cell). Neurons: Essential cells in the nervous system responsible for receiving, processing, and transmitting information, enabling vital body functions.\">","<meta property=\"og:image\" content=\"https://cellxgene.cziscience.com/open-graph.jpg\">","<meta property=\"og:site\" content=\"@cziscience\">","<meta property=\"og:site_name\" content=\"Cellxgene Data Portal\">","<meta property=\"og:title\" content=\"Brain Specific Neuron Cell Types - CZ CELLxGENE CellGuide\">","<meta property=\"og:type\" content=\"website\">","<meta property=\"og:url\" content=\"https://cellxgene.cziscience.com/\">","<meta property=\"title\" content=\"Brain Specific Neuron Cell Types - CZ CELLxGENE CellGuide\">","<meta property=\"twitter:card\" content=\"summary\">","<meta property=\"twitter:description\" content=\"Find comprehensive information about brain specific &quot;neuron&quot; cell types (synonyms: nerve cell). Neurons: Essential cells in the nervous system responsible for receiving, processing, and transmitting information, enabling vital body functions.\">","<meta property=\"twitter:image\" content=\"https://cellxgene.cziscience.com/open-graph.jpg\">","<meta property=\"twitter:title\" content=\"Brain Specific Neuron Cell Types - CZ CELLxGENE CellGuide\">"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["<meta charset=\"utf-8\">","<meta id=\"newsletter-signup-meta\" name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">","<meta name=\"description\" content=\"Find comprehensive information about brain specific &quot;neuron&quot; cell types (synonyms: nerve cell). Neurons: Essential cells in the nervous system responsible for receiving, processing, and transmitting information, enabling vital body functions.\">","<meta name=\"next-head-count\" content=\"17\">","<meta name=\"theme-color\" content=\"#000000\">","<meta property=\"og:creator\" content=\"@cziscience\">","<meta property=\"og:description\" content=\"Find comprehensive information about brain specific &quot;neuron&quot; cell types (synonyms: nerve cell). Neurons: Essential cells in the nervous system responsible for receiving, processing, and transmitting information, enabling vital body functions.\">","<meta property=\"og:image\" content=\"https://cellxgene.cziscience.com/open-graph.jpg\">","<meta property=\"og:site\" content=\"@cziscience\">","<meta property=\"og:site_name\" content=\"Cellxgene Data Portal\">","<meta property=\"og:title\" content=\"Brain Specific Neuron Cell Types - CZ CELLxGENE CellGuide\">","<meta property=\"og:type\" content=\"website\">","<meta property=\"og:url\" content=\"https://cellxgene.cziscience.com/\">","<meta property=\"title\" content=\"Brain Specific Neuron Cell Types - CZ CELLxGENE CellGuide\">","<meta property=\"twitter:card\" content=\"summary\">","<meta property=\"twitter:description\" content=\"Find comprehensive information about brain specific &quot;neuron&quot; cell types (synonyms: nerve cell). Neurons: Essential cells in the nervous system responsible for receiving, processing, and transmitting information, enabling vital body functions.\">","<meta property=\"twitter:image\" content=\"https://cellxgene.cziscience.com/open-graph.jpg\">","<meta property=\"twitter:title\" content=\"Brain Specific Neuron Cell Types - CZ CELLxGENE CellGuide\">"]
3 changes: 3 additions & 0 deletions frontend/src/common/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ export enum ROUTES {
WMG_DOCS_DATA_PROCESSING = "/docs/04__Analyze%20Public%20Data/4_2__Gene%20Expression%20Documentation/4_2_3__Gene%20Expression%20Data%20Processing",
SITEMAP = "/sitemap",
CELL_GUIDE = "/cellguide",
CELL_GUIDE_CELL_TYPE = "/cellguide/:cellTypeId",
CELL_GUIDE_TISSUE = "/cellguide/tissues/:tissueId",
CELL_GUIDE_TISSUE_SPECIFIC_CELL_TYPE = "/cellguide/tissues/:tissueId/cell-types/:cellTypeId",
DEPLOYED_VERSION = "/api/deployed_version",
}
23 changes: 18 additions & 5 deletions frontend/src/common/queries/cellGuide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,20 +472,33 @@ export const fetchTissueMetadata =
};

/* ========== Lookup tables for organs ========== */
export function useAllOrgansLookupTables(): Map<string, string> {
const { data: allOrgansData } = useTissueMetadata();
export function useAllOrgansLookupTables(): {
data: Map<string, string>;
isSuccess: boolean;
} {
/**
* (thuang): Expose `isSuccess`, so `CellGuide/components/CellGuideCard/connect.ts`
* can use it to determine if the data is ready and determine if the user should
* be redirected to the tissue agnostic cell type page.
*/
const { data: allOrgansData, isSuccess } = useTissueMetadata();

return useMemo(() => {
if (!allOrgansData) {
return new Map<string, string>();
return { data: new Map<string, string>(), isSuccess };
}

const allOrgansLabelToIdMap = new Map<string, string>();
for (const organId in allOrgansData) {
const organData = allOrgansData[organId];
allOrgansLabelToIdMap.set(organData.name, organData.id);
}
return allOrgansLabelToIdMap;
}, [allOrgansData]);

return {
data: allOrgansLabelToIdMap,
isSuccess,
};
}, [allOrgansData, isSuccess]);
}

/* ========== Lookup tables for tissues ========== */
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/pages/cellguide/[cellTypeId].tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/**
* IMPORTANT(thuang): `frontend/src/pages/cellguide/tissues/[tissueId]/cell-types/[cellTypeId].tsx`
* imports the exports from this file, since both routes use the same components.
* If we export more things from this file, we need to export them from the other file as well,
* to make sure that /cellguide/tissues/[tissueId]/cell-types/[cellTypeId] route continue
* to work.
*/

import { GetServerSideProps, InferGetServerSidePropsType } from "next";
import CellGuideCard from "src/views/CellGuide/components/CellGuideCard";
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* IMPORTANT(thuang): Make sure the exports from this file is in sync with
* frontend/src/pages/cellguide/[cellTypeId].tsx
*/

import Page, { getServerSideProps } from "src/pages/cellguide/[cellTypeId]";

export default Page;
export { getServerSideProps };
29 changes: 29 additions & 0 deletions frontend/src/views/CellGuide/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,32 @@ export function filterDescendantsOfAncestorTissueId(

return tissueIdList.filter((value) => descendantSet.has(value));
}

import { ROUTES } from "src/common/constants/routes";
import { NO_ORGAN_ID } from "src/views/CellGuide/components/CellGuideCard/components/MarkerGeneTables/constants";

export function getCellTypeLink({
/**
* (thuang): `queryTissueId` can be undefined when a user is on `/cellguide/:cellTypeId` route
* instead of `/cellguide/tissues/:tissueId/cell-types/:cellTypeId` route.
*
* NOTE: `tissue` and `organ` in variable names are interchangeable here.
*/
tissueId = NO_ORGAN_ID,
cellTypeId,
}: {
tissueId: string;
cellTypeId: string;
}) {
const urlCellTypeId = cellTypeId.replace(":", "_") ?? "";
const urlTissueId = tissueId.replace(":", "_") || NO_ORGAN_ID;

if (tissueId === NO_ORGAN_ID) {
return ROUTES.CELL_GUIDE_CELL_TYPE.replace(":cellTypeId", urlCellTypeId);
} else {
return ROUTES.CELL_GUIDE_TISSUE_SPECIFIC_CELL_TYPE.replace(
":tissueId",
urlTissueId
).replace(":cellTypeId", urlCellTypeId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ import { ALL_TISSUES, HOMO_SAPIENS, NO_ORGAN_ID } from "../constants";
export function useOrganAndOrganismFilterListForCellType(cellTypeId: string): {
organsMap: Map<string, string>;
organismsList: string[];
isSuccess: boolean;
} {
const { data: computationalMarkers } = useComputationalMarkers(cellTypeId);
const {
data: computationalMarkers,
isSuccess: isComputationalMarkersSuccess,
} = useComputationalMarkers(cellTypeId);

const organLabelToIdMap = useAllOrgansLookupTables();
const { data: organLabelToIdMap, isSuccess } = useAllOrgansLookupTables();

// eslint-disable-next-line sonarjs/cognitive-complexity
return useMemo(() => {
Expand Down Expand Up @@ -66,6 +70,17 @@ export function useOrganAndOrganismFilterListForCellType(cellTypeId: string): {
return {
organsMap: sortedFilteredOrganMap,
organismsList: sortedOrganismList,
/**
* (thuang): Expose `isSuccess`, so `CellGuide/components/CellGuideCard/connect.ts`
* can use it to determine if the data is ready and determine if the user should
* be redirected to the tissue agnostic cell type page.
*/
isSuccess: isComputationalMarkersSuccess && isSuccess,
};
}, [computationalMarkers, organLabelToIdMap]);
}, [
computationalMarkers,
organLabelToIdMap,
isSuccess,
isComputationalMarkersSuccess,
]);
}
166 changes: 166 additions & 0 deletions frontend/src/views/CellGuide/components/CellGuideCard/connect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { DefaultDropdownMenuOption } from "@czi-sds/components";
import { throttle } from "lodash";
import { useRouter } from "next/router";
import { useState, useRef, useCallback, useMemo, useEffect } from "react";
import { ROUTES } from "src/common/constants/routes";
import {
ALL_TISSUES,
NO_ORGAN_ID,
TISSUE_AGNOSTIC,
} from "src/views/CellGuide/components/CellGuideCard/components/MarkerGeneTables/constants";
import { useOrganAndOrganismFilterListForCellType } from "src/views/CellGuide/components/CellGuideCard/components/MarkerGeneTables/hooks/common";
import { SKINNY_MODE_BREAKPOINT_WIDTH } from "src/views/CellGuide/components/CellGuideCard/constants";
import { LEFT_RIGHT_PADDING_PX_XXL } from "src/views/CellGuide/components/CellGuideCard/style";
import { SDSOrgan } from "src/views/CellGuide/components/CellGuideCard/types";
import { CellType } from "src/views/CellGuide/components/common/OntologyDagView/common/types";
import { Gene } from "src/views/WheresMyGeneV2/common/types";

export function useConnect() {
const router = useRouter();

const [pageNavIsOpen, setPageNavIsOpen] = useState(false);
const [selectedGene, setSelectedGene] = useState<string | undefined>(
undefined
);

const [skinnyMode, setSkinnyMode] = useState<boolean>(false);

// Navigation
const sectionRef0 = useRef<HTMLDivElement>(null);
const sectionRef1 = useRef<HTMLDivElement>(null);
const sectionRef2 = useRef<HTMLDivElement>(null);
const sectionRef3 = useRef<HTMLDivElement>(null);

const selectGene = useCallback(
(gene: string) => {
if (gene === selectedGene) {
setSelectedGene(undefined);
} else {
setSelectedGene(gene);
if (sectionRef1.current) {
window.scrollTo({
top:
sectionRef1.current.getBoundingClientRect().top +
window.scrollY -
50,
behavior: "smooth",
});
}
}
},
[selectedGene]
);

// Set the mobile tooltip view content
const [tooltipContent, setTooltipContent] = useState<{
title: string;
element: JSX.Element;
} | null>(null);

const { cellTypeId: queryCellTypeId, tissueId: queryTissueId } = router.query;
const cellTypeId = (queryCellTypeId as string)?.replace("_", ":") ?? "";

/**
* (thuang): `queryTissueId` can be undefined when a user is on `/cellguide/:cellTypeId` route
* instead of `/cellguide/tissues/:tissueId/cell-types/:cellTypeId` route.
*
* NOTE: `tissue` and `organ` in variable names are interchangeable here.
*/
const tissueId = (queryTissueId as string)?.replace("_", ":") || NO_ORGAN_ID;

const handleResize = useCallback(() => {
setSkinnyMode(
window.innerWidth <
SKINNY_MODE_BREAKPOINT_WIDTH + 2 * LEFT_RIGHT_PADDING_PX_XXL
);
}, [setSkinnyMode]);

const throttledHandleResize = useMemo(() => {
return throttle(handleResize, 100);
}, [handleResize]);

useEffect(() => {
throttledHandleResize();
window.addEventListener("resize", throttledHandleResize);

return () => window.removeEventListener("resize", throttledHandleResize);
}, [throttledHandleResize]);

const [geneInfoGene, setGeneInfoGene] = useState<Gene["name"] | null>(null);
const [cellInfoCellType, setCellInfoCellType] = useState<CellType | null>(
null
);

const { organismsList, organsMap, isSuccess } =
useOrganAndOrganismFilterListForCellType(cellTypeId);

const sdsOrganismsList = useMemo(
() =>
organismsList.map((organism) => ({
name: organism,
})),
[organismsList]
);

const sdsOrgansList = useMemo<SDSOrgan[]>(
() =>
Array.from(organsMap).map(([name, id]) => ({
name: name === ALL_TISSUES ? TISSUE_AGNOSTIC : name,
id,
})),
[organsMap]
);

const selectedOrgan = sdsOrgansList.find((organ) => organ.id === tissueId);

/**
* (thuang): Push the user to tissue agnostic cell type page if the user is on
* a tissue-specific cell type page and the tissue is not in the filter list.
*/
useEffect(() => {
if (isSuccess && tissueId !== NO_ORGAN_ID && !selectedOrgan) {
router.replace(
ROUTES.CELL_GUIDE_CELL_TYPE.replace(
":cellTypeId",
queryCellTypeId as string
)
);
}
}, [queryCellTypeId, router, selectedOrgan, tissueId, isSuccess]);

const [selectedOrganism, setSelectedOrganism] =
useState<DefaultDropdownMenuOption>(sdsOrganismsList[0]);

useEffect(() => {
setSelectedGene(undefined);
}, [selectedOrgan, selectedOrganism, setSelectedGene]);

return {
router,
pageNavIsOpen,
setPageNavIsOpen,
selectedGene,
sectionRef0,
sectionRef1,
sectionRef2,
sectionRef3,
skinnyMode,
selectGene,
tooltipContent,
setTooltipContent,
queryCellTypeId,
cellTypeId,
geneInfoGene,
setGeneInfoGene,
cellInfoCellType,
setCellInfoCellType,
organismsList,
organsMap,
sdsOrganismsList,
sdsOrgansList,
selectedOrgan,
selectedOrganId: tissueId,
selectedOrganism,
setSelectedOrganism,
};
}
Loading

0 comments on commit 35b99a4

Please sign in to comment.