From 603a2e6cb31e3e6736ae8db2a8f195aad74cda5e Mon Sep 17 00:00:00 2001 From: screenumass <165808708+screenumass@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:19:57 -0400 Subject: [PATCH] sync: master to vercel-preview (#7) * Create sync-vercel-preview-branch.yml * Cell Activity in Lineage Tree (#5) * Separate static info from dynamic state * Add tab for activity in cell lineage * Small cleanup --------- Co-authored-by: Jonathan Fisher <49597360+jpfisher72@users.noreply.github.com> --- immuscreen/src/app/celllineage/page.tsx | 28 +- immuscreen/src/app/celllineage/utils.tsx | 511 ++++++++++++++++++ immuscreen/src/app/icres/page.tsx | 504 ++++++++--------- .../src/common/components/cellTypeTree.tsx | 93 ++-- 4 files changed, 808 insertions(+), 328 deletions(-) create mode 100644 immuscreen/src/app/celllineage/utils.tsx diff --git a/immuscreen/src/app/celllineage/page.tsx b/immuscreen/src/app/celllineage/page.tsx index 75ffb6f..6b75739 100644 --- a/immuscreen/src/app/celllineage/page.tsx +++ b/immuscreen/src/app/celllineage/page.tsx @@ -17,15 +17,13 @@ import FlashAutoIcon from '@mui/icons-material/FlashAuto'; import UndoOutlinedIcon from '@mui/icons-material/UndoOutlined'; import LoadingButton from '@mui/lab/LoadingButton'; import { Download, Sync } from "@mui/icons-material"; -import { downloadSVG, extractQueryValues, generateCellLineageTreeState } from "./utils"; -import { CellQueryValue, CellLineageTreeState, CellName, DynamicCellTypeInfo } from "./types"; -import { cellTypeStaticInfo } from "../../common/consts"; +import { StaticCellTypeInfo, CellLineageTreeState, downloadSVG, extractQueryValues, generateCellLineageTreeState, DynamicCellTypeInfo, CellName, cellLineageTreeStaticInfo } from "./utils"; type QueryGroup = { - intersect?: CellQueryValue[][], - exclude?: CellQueryValue[][], - union?: CellQueryValue[], + intersect?: string[][], + exclude?: string[][], + union?: string[], name: string } @@ -103,7 +101,7 @@ export default function UpSet() { if (mode === "B" && (currentlySelected * 2) > cellTreeSelectionLimit) { handleOpenSnackbar("Unable to apply \"Both\" stimulation status due to selection limit (6)") } else { - const newState: CellLineageTreeState = Object.fromEntries(Object.entries(cellTypeState).map(([key, value]: [CellName, DynamicCellTypeInfo]) => cellTypeStaticInfo[key].stimulable ? [key, {...value, stimulated: mode}] : [key, value])) as CellLineageTreeState + const newState: CellLineageTreeState = Object.fromEntries(Object.entries(cellTypeState).map(([key, value]: [CellName, DynamicCellTypeInfo]) => cellLineageTreeStaticInfo[key].stimulable ? [key, {...value, stimulated: mode}] : [key, value])) as CellLineageTreeState setCellTypeState(newState) } } @@ -199,15 +197,15 @@ export default function UpSet() { */ const generateQuery = useCallback((selectedCellsState: Partial, classes: CCRE_CLASS[]) => { //stores extracted relevant information from selectedCells for easier access - let cells: { displayName: string, queryVals: CellQueryValue[] }[] = []; + let cells: { displayName: string, queryVals: string[] }[] = []; //Out of selectedCells, extract relevant information. Create two entries for cells with "B" stimulation to iterate through more easily later Object.entries(selectedCellsState).forEach(([key, value]: [CellName, DynamicCellTypeInfo]) => { const name = key.replace('-', '_') if (value.stimulated == "B") { - cells.push({ displayName: name + '_U', queryVals: extractQueryValues(cellTypeStaticInfo[key], "U") }) - cells.push({ displayName: name + '_S', queryVals: extractQueryValues(cellTypeStaticInfo[key], "S") }) - } else cells.push({ displayName: name + '_' + value.stimulated, queryVals: extractQueryValues(cellTypeStaticInfo[key], value.stimulated) }) + cells.push({ displayName: name + '_U', queryVals: extractQueryValues(cellLineageTreeStaticInfo[key], "U") }) + cells.push({ displayName: name + '_S', queryVals: extractQueryValues(cellLineageTreeStaticInfo[key], "S") }) + } else cells.push({ displayName: name + '_' + value.stimulated, queryVals: extractQueryValues(cellLineageTreeStaticInfo[key], value.stimulated) }) }) //Holds the combination of union/intersection/exlude and name for each query @@ -215,7 +213,7 @@ export default function UpSet() { //Union of all cells if (Object.keys(selectedCellsState).length > 0) { - queryGroups.push({ union: Object.entries(selectedCellsState).map(([key, value]: [CellName, DynamicCellTypeInfo]) => extractQueryValues(cellTypeStaticInfo[key], value.stimulated)).flat(2), name: 'Union_All' }) + queryGroups.push({ union: Object.entries(selectedCellsState).map(([key, value]: [CellName, DynamicCellTypeInfo]) => extractQueryValues(cellLineageTreeStaticInfo[key], value.stimulated)).flat(2), name: 'Union_All' }) } //Individual counts @@ -427,11 +425,11 @@ export default function UpSet() { //These boolean values are used to disable buttons in certain situaions const noneSelected = !Object.values(cellTypeState).map(x => x.selected).find(x => x) const noneStimulated = Object.entries(cellTypeState) - .every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellTypeStaticInfo[key].stimulable ? value.stimulated === "U" : true) + .every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellLineageTreeStaticInfo[key].stimulable ? value.stimulated === "U" : true) const allStimulated = Object.entries(cellTypeState) - .every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellTypeStaticInfo[key].stimulable ? value.stimulated === "S": true) + .every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellLineageTreeStaticInfo[key].stimulable ? value.stimulated === "S": true) const allBothStimulated = Object.entries(cellTypeState) - .every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellTypeStaticInfo[key].stimulable ? value.stimulated === "B" : true) + .every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellLineageTreeStaticInfo[key].stimulable ? value.stimulated === "B" : true) const groupCheckbox = (group: CCRE_CLASS, key: number) => { return ( diff --git a/immuscreen/src/app/celllineage/utils.tsx b/immuscreen/src/app/celllineage/utils.tsx new file mode 100644 index 0000000..6947723 --- /dev/null +++ b/immuscreen/src/app/celllineage/utils.tsx @@ -0,0 +1,511 @@ +export type CellName = "Myeloid_DCs" | "pDCs" | "Naive_B" | "Mem_B" | "Plasmablasts" | "Regulatory_T" | "Naive_Tregs" | "Memory_Tregs" | "Effector_CD4pos_T" | "Naive_Teffs" | "Memory_Teffs" | "Th1_precursors" | "Th2_precursors" | "Th17_precursors" | "Follicular_T_Helper" | "Naive_CD8_T" | "Central_memory_CD8pos_T" | "Effector_memory_CD8pos_T" | "Gamma_delta_T" | "Immature_NK" | "Mature_NK" | "Memory_NK" | "HSC" | "MPP" | "CMP" | "MEP" | "Ery" | "GMP" | "LMPP" | "CLP" | "CD4Tcell" | "Nkcell" | "Monocytes" | "Bulk_B" | "CD8pos_T"; + +const cellNames: CellName[] = ["Myeloid_DCs", "pDCs", "Naive_B", "Mem_B", "Plasmablasts", "Regulatory_T", "Naive_Tregs", "Memory_Tregs", "Effector_CD4pos_T", "Naive_Teffs", "Memory_Teffs", "Th1_precursors", "Th2_precursors", "Th17_precursors", "Follicular_T_Helper", "Naive_CD8_T", "Central_memory_CD8pos_T", "Effector_memory_CD8pos_T", "Gamma_delta_T", "Immature_NK", "Mature_NK", "Memory_NK", "HSC", "MPP", "CMP", "MEP", "Ery", "GMP", "LMPP", "CLP", "CD4Tcell", "Nkcell", "Monocytes", "Bulk_B", "CD8pos_T"] + +// Static information for each cell +export type StaticCellTypeInfo = { + readonly id: CellName; + readonly displayName: string; + readonly unstimImagePath: string; + readonly stimImagePath?: string; + readonly unstimCount: number + readonly stimCount?: number + readonly stimulable: boolean; + readonly queryValues?: { + readonly unstimulated: { Calderon?: string | string[], Corces?: string | string[] }; + readonly stimulated?: { Calderon: string | string[] } + } +} + +// Dynamic information that changes depending on use case +export type DynamicCellTypeInfo = { + selected: boolean; + stimulated: "S" | "U" | "B"; + readonly selectable: boolean; +} + +export type CellLineageTreeState = { [key in CellName]: DynamicCellTypeInfo } + + + +export const cellLineageTreeStaticInfo: { [key in CellName]: StaticCellTypeInfo } = { + //Using Calderon "name". Using Calderon's Stimulated/Unstimulated. In Corces it is "Mono", and no stimulation info + Monocytes: { + id: 'Monocytes', + displayName: "Monocyte", + unstimImagePath: '/cellTypes/Monocytes-U.png', + stimImagePath: '/cellTypes/Monocytes-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Monocytes-U', Corces: 'Mono' }, + stimulated: { Calderon: 'Monocytes-S' } + }, + unstimCount: 130780, + stimCount: 100461 + }, + Myeloid_DCs: { + id: 'Myeloid_DCs', + displayName: "Myeloid/dendritic cell", + unstimImagePath: '/cellTypes/Myeloid_DCs-U.png', + stimulable: false, + queryValues: { + unstimulated: { Calderon: "Myeloid_DCs-U" } + }, + unstimCount: 173394 + }, + pDCs: { + id: 'pDCs', + displayName: "Plasmacytoid/dendritic cell", + unstimImagePath: '/cellTypes/pDCs-U.png', + stimulable: false, + queryValues: { + unstimulated: { Calderon: 'pDCs-U' } + }, + unstimCount: 146515 + }, + //Using Calderon "name". Using Calderon's Stimulated/Unstimulated. In Corces it is "Bcell", and no stimulation info + Bulk_B: { + id: 'Bulk_B', + displayName: "Bulk/B cell", + unstimImagePath: '/cellTypes/Bulk_B-U.png', + stimImagePath: '/cellTypes/Bulk_B-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Bulk_B-U', Corces: "Bcell" }, + stimulated: { Calderon: 'Bulk_B-S' } + }, + unstimCount: 138138, + stimCount: 124969 + }, + Naive_B: { + id: 'Naive_B', + displayName: "Naïve/B cell", + unstimImagePath: '/cellTypes/Naive_B-U.png', + stimImagePath: '/cellTypes/Naive_B-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Naive_B-U' }, + stimulated: { Calderon: 'Naive_B-S' } + }, + unstimCount: 120624, + stimCount: 128979 + }, + Mem_B: { + id: 'Mem_B', + displayName: "Memory/B cell", + unstimImagePath: '/cellTypes/Mem_B-U.png', + stimImagePath: '/cellTypes/Mem_B-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Mem_B-U' }, + stimulated: { Calderon: 'Mem_B-S' } + }, + unstimCount: 122662, + stimCount: 129491 + }, + Plasmablasts: { + id: 'Plasmablasts', + displayName: "Plasmablast", + unstimImagePath: '/cellTypes/Plasmablasts-U.png', + stimulable: false, + queryValues: { + unstimulated: { Calderon: 'Plasmablasts-U' } + }, + unstimCount: 123042 + }, + Regulatory_T: { + id: 'Regulatory_T', + displayName: "Regulatory/CD4+ T cell", + unstimImagePath: '/cellTypes/Regulatory_T-U.png', + stimImagePath: '/cellTypes/Regulatory_T-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Regulatory_T-U' }, + stimulated: { Calderon: 'Regulatory_T-S' } + }, + unstimCount: 124481, + stimCount: 126696 + }, + Naive_Tregs: { + id: 'Naive_Tregs', + displayName: "Naïve T/regulatory cell", + unstimImagePath: '/cellTypes/Naive_Tregs-U.png', + stimImagePath: '/cellTypes/Naive_Tregs-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Naive_Tregs-U' }, + stimulated: { Calderon: 'Naive_Tregs-S' } + }, + unstimCount: 95731, + stimCount: 100068 + }, + Memory_Tregs: { + id: 'Memory_Tregs', + displayName: "Memory T/regulatory cell", + unstimImagePath: '/cellTypes/Memory_Tregs-U.png', + stimImagePath: '/cellTypes/Memory_Tregs-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Memory_Tregs-U' }, + stimulated: { Calderon: 'Memory_Tregs-S' } + }, + unstimCount: 125459, + stimCount: 121029 + }, + Effector_CD4pos_T: { + id: 'Effector_CD4pos_T', + displayName: "Effector/CD4+ T cell", + unstimImagePath: '/cellTypes/Effector_CD4pos_T-U.png', + stimImagePath: '/cellTypes/Effector_CD4pos_T-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Effector_CD4pos_T-U' }, + stimulated: { Calderon: 'Effector_CD4pos_T-S' } + }, + unstimCount: 123382, + stimCount: 137982 + }, + Naive_Teffs: { + id: 'Naive_Teffs', + displayName: "Naïve T/effector cell", + unstimImagePath: '/cellTypes/Naive_Teffs-U.png', + stimImagePath: '/cellTypes/Naive_Teffs-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Naive_Teffs-U' }, + stimulated: { Calderon: 'Naive_Teffs-S' } + }, + unstimCount: 117212, + stimCount: 137523 + }, + Memory_Teffs: { + id: 'Memory_Teffs', + displayName: "Memory T/effector cell", + unstimImagePath: '/cellTypes/Memory_Teffs-U.png', + stimImagePath: '/cellTypes/Memory_Teffs-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Memory_Teffs-U' }, + stimulated: { Calderon: 'Memory_Teffs-S' } + }, + unstimCount: 137523, + stimCount: 148833 + }, + Th1_precursors: { + id: 'Th1_precursors', + displayName: "Th1/precursor", + unstimImagePath: '/cellTypes/Th1_precursors-U.png', + stimImagePath: '/cellTypes/Th1_precursors-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Th1_precursors-U' }, + stimulated: { Calderon: 'Th1_precursors-S' } + }, + unstimCount: 121879, + stimCount: 145297 + }, + Th2_precursors: { + id: 'Th2_precursors', + displayName: "Th2/precursor", + unstimImagePath: '/cellTypes/Th2_precursors-U.png', + stimImagePath: '/cellTypes/Th2_precursors-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Th2_precursors-U' }, + stimulated: { Calderon: 'Th2_precursors-S' } + }, + unstimCount: 122826, + stimCount: 141664 + }, + Th17_precursors: { + id: 'Th17_precursors', + displayName: "Th17/precursor", + unstimImagePath: '/cellTypes/Th17_precursors-U.png', + stimImagePath: '/cellTypes/Th17_precursors-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Th17_precursors-U' }, + stimulated: { Calderon: 'Th17_precursors-S' } + }, + unstimCount: 128606, + stimCount: 147883 + }, + Follicular_T_Helper: { + id: 'Follicular_T_Helper', + displayName: "T follicular/helper cell", + unstimImagePath: '/cellTypes/Follicular_T_helper-U.png', + stimImagePath: '/cellTypes/Follicular_T_helper-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Follicular_T_Helper-U' }, + stimulated: { Calderon: 'Follicular_T_Helper-S' } + }, + unstimCount: 122084, + stimCount: 136992 + }, + //Using Calderon "name". Using Calderon's Stimulated/Unstimulated. In Corces it is "CD8Tcell", and no stimulation info + CD8pos_T: { + id: 'CD8pos_T', + displayName: "CD8+ T cell", + unstimImagePath: '/cellTypes/CD8pos_T-U.png', + stimImagePath: '/cellTypes/CD8pos_T-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'CD8pos_T-U', Corces: "CD8Tcell" }, + stimulated: { Calderon: 'CD8pos_T-S' } + }, + unstimCount: 151004, + stimCount: 127042 + }, + Naive_CD8_T: { + id: 'Naive_CD8_T', + displayName: "Naïve CD8+/T cell", + unstimImagePath: '/cellTypes/Naive_CD8_T-U.png', + stimImagePath: '/cellTypes/Naive_CD8_T-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Naive_CD8_T-U' }, + stimulated: { Calderon: 'Naive_CD8_T-S' } + }, + unstimCount: 100250, + stimCount: 113028 + }, + Central_memory_CD8pos_T: { + id: 'Central_memory_CD8pos_T', + displayName: "Central/memory/CD8+ T cell", + unstimImagePath: '/cellTypes/Central_memory_CD8pos_T-U.png', + stimImagePath: '/cellTypes/Central_memory_CD8pos_T-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Central_memory_CD8pos_T-U' }, + stimulated: { Calderon: 'Central_memory_CD8pos_T-S' } + }, + unstimCount: 125778, + stimCount: 136023 + }, + Effector_memory_CD8pos_T: { + id: 'Effector_memory_CD8pos_T', + displayName: "Effector/memory/CD8+ T cell", + unstimImagePath: '/cellTypes/Effector_Memory_CD8pos_T-U.png', + stimImagePath: '/cellTypes/Effector_memory_CD8pos_T-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Effector_memory_CD8pos_T-U' }, + stimulated: { Calderon: 'Effector_memory_CD8pos_T-S' } + }, + unstimCount: 145641, + stimCount: 132761 + }, + Gamma_delta_T: { + id: 'Gamma_delta_T', + displayName: "Gamma-delta/T cell", + unstimImagePath: '/cellTypes/Gamma_delta_T-U.png', + stimImagePath: '/cellTypes/Gamma_delta_T-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Gamma_delta_T-U' }, + stimulated: { Calderon: 'Gamma_delta_T-S' } + }, + unstimCount: 133605, + stimCount: 116220 + }, + Immature_NK: { + id: 'Immature_NK', + displayName: "Immature/NK cell", + unstimImagePath: '/cellTypes/Immature_NK-U.png', + stimulable: false, + queryValues: { + unstimulated: { Calderon: 'Immature_NK-U' } + }, + unstimCount: 130554 + }, + Mature_NK: { + id: 'Mature_NK', + displayName: "Mature/NK cell", + unstimImagePath: '/cellTypes/Mature_NK-U.png', + stimImagePath: '/cellTypes/Mature_NK-S.png', + stimulable: true, + queryValues: { + unstimulated: { Calderon: 'Mature_NK-U' }, + stimulated: { Calderon: 'Mature_NK-S' } + }, + unstimCount: 119958, + stimCount: 110082 + }, + Memory_NK: { + id: 'Memory_NK', + displayName: "Memory/NK cell", + unstimImagePath: '/cellTypes/Memory_NK-U.png', + stimulable: false, + queryValues: { + unstimulated: { Calderon: 'Memory_NK-U' } + }, + unstimCount: 135352 + }, + HSC: { + id: 'HSC', + displayName: "Hematopoetic/stem cell", + unstimImagePath: '/cellTypes/HSC.png', + stimulable: false, + queryValues: { + unstimulated: { Corces: ["HSC", "CD34_Cord_Blood", "CD34_Bone_Marrow"] } + }, + unstimCount: 173583 + }, + MPP: { + id: "MPP", + displayName: "Multipotent/progenitor", + unstimImagePath: '/cellTypes/MPP.png', + stimulable: false, + queryValues: { + unstimulated: { Corces: "MPP" } + }, + unstimCount: 158945 + }, + CMP: { + id: "CMP", + displayName: "Common myeloid/progenitor", + unstimImagePath: '/cellTypes/CMP.png', + stimulable: false, + queryValues: { + unstimulated: { Corces: "CMP" } + }, + unstimCount: 159706 + }, + MEP: { + id: "MEP", + displayName: "Megakaryocyte-erythroid/progenitor", + unstimImagePath: '/cellTypes/MEP.png', + stimulable: false, + queryValues: { + unstimulated: { Corces: "MEP" } + }, + unstimCount: 152044 + }, + Ery: { + id: "Ery", + displayName: "Erythrocyte", + unstimImagePath: '/cellTypes/Erythrocyte.png', + stimulable: false, + queryValues: { + unstimulated: { Corces: "Ery" } + }, + unstimCount: 56267 + }, + GMP: { + id: "GMP", + displayName: "Granulocyte-monocyte/progenitors", + unstimImagePath: '/cellTypes/GMP.png', + stimulable: false, + queryValues: { + unstimulated: { Corces: "GMP" } + }, + unstimCount: 158558 + }, + LMPP: { + id: "LMPP", + displayName: "Lymphocyte-primed/multipotent progenitor", + unstimImagePath: '/cellTypes/LMP.png', + stimulable: false, + queryValues: { + unstimulated: { Corces: "LMPP" } + }, + unstimCount: 128494 + }, + CLP: { + id: "CLP", + displayName: "Common lymphoid/progenitor", + unstimImagePath: '/cellTypes/CLP.png', + stimulable: false, + queryValues: { + unstimulated: { Corces: "CLP" } + }, + unstimCount: 93170 + }, + CD4Tcell: { + id: "CD4Tcell", + displayName: "CD4+ T cell", + unstimImagePath: '/cellTypes/CD4posT.png', + stimulable: false, + queryValues: { + unstimulated: { Corces: "CD4Tcell" } + }, + unstimCount: 121034 + }, + Nkcell: { + id: "Nkcell", + displayName: "NK cell", + unstimImagePath: '/cellTypes/Nkcell.png', + stimulable: false, + queryValues: { + unstimulated: { Corces: "NKcell" } + }, + unstimCount: 116626 + } +} + +/** + * + * @param cell CellTypeInfo + * @param want "S" | "U" | "B" The query value(s) wanted + * @returns array of strings or string arrays. If values are within a nested array, they need to be unioned. + */ +export const extractQueryValues = (cell: StaticCellTypeInfo, want: "S" | "U" | "B"): (string[]) => { + switch (want) { + case "U": return cell.queryValues?.unstimulated ? [... Object.values(cell.queryValues.unstimulated).flat()] : [] + case "S": return cell.queryValues?.stimulated ? [... Object.values(cell.queryValues.stimulated).flat()] : [] + case "B": return Object.values(cell.queryValues.unstimulated).flat().concat((Object.values(cell.queryValues.stimulated).flat())) + case "B": return(cell.queryValues?.unstimulated ? Object.values(cell.queryValues.unstimulated).flat() : []).concat(cell.queryValues?.stimulated ? (Object.values(cell.queryValues.stimulated).flat()) : []) + } +} + +/** + * Initial Selected Cells being query values not cell names is convenient right now, but confusing. Consider changing in future to plain names, like {name: cellName, stim: "B" | "U" | "S"} + * @param initialSelectedCells cells to select initially, needs to match query values like Bulk_B-U (NOT CELL NAMES). + * @param interactive disables interacting with nodes + */ +export const generateCellLineageTreeState = (initialSelectedCells: string[], interactive: boolean): CellLineageTreeState => { + //Some iCREs are active in celltypes (Ex: Blast) that are not in the tree. Need to handle this case. Ex: EH38E0243977 is active in Blast + let cellLineageTreeState = {} as CellLineageTreeState + + for (const cellName of cellNames) { + //Try to find matches in the query values of cellLineageTreeStaticInfo + const unstimSelected = initialSelectedCells.some(cell => extractQueryValues(cellLineageTreeStaticInfo[cellName], "U").includes(cell)) + const stimSelected = initialSelectedCells.some(cell => extractQueryValues(cellLineageTreeStaticInfo[cellName], "S").includes(cell)) + cellLineageTreeState[cellName] = { + selected: unstimSelected || stimSelected, + selectable: interactive, + stimulated: + (unstimSelected && stimSelected) ? "B" : + (unstimSelected && !stimSelected) ? "U" : + (!unstimSelected && stimSelected) ? "S" : + "U" + } + } + + return cellLineageTreeState +} + +const svgData = (_svg): string => { + let svg = _svg.cloneNode(true); + svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + let preface = ''; + return preface + svg.outerHTML.replace(/\n/g, '').replace(/[ ]{8}/g, ''); +}; + +const downloadData = (text: string, filename: string, type: string = 'text/plain') => { + const a = document.createElement('a'); + document.body.appendChild(a); + a.setAttribute('style', 'display: none'); + const blob = new Blob([text], { type }); + const url = window.URL.createObjectURL(blob); + a.href = url; + a.download = filename; + a.click(); + window.URL.revokeObjectURL(url); + a.remove(); +}; + +export const downloadSVG = (ref: React.MutableRefObject, filename: string) =>{ + ref.current && downloadData(svgData(ref.current!), filename, 'image/svg;charset=utf-8'); +} \ No newline at end of file diff --git a/immuscreen/src/app/icres/page.tsx b/immuscreen/src/app/icres/page.tsx index 980ba17..337c4af 100644 --- a/immuscreen/src/app/icres/page.tsx +++ b/immuscreen/src/app/icres/page.tsx @@ -1,38 +1,37 @@ - "use client" +"use client" import React, { useState } from "react" import { useRouter } from "next/navigation" import { StyledTab } from "../../common/utils" import { client } from "../../common/utils" import SearchIcon from "@mui/icons-material/Search" -import { CircularProgress, Stack, ToggleButtonGroup, Typography } from "@mui/material" +import { CircularProgress, Stack, Typography } from "@mui/material" import { Tabs } from "@mui/material" import Grid2 from "@mui/material/Unstable_Grid2/Grid2" import { TextField, IconButton, InputAdornment } from "@mui/material" import { FormControl, MenuItem } from "@mui/material" import { useQuery } from "@apollo/client" - -import Select, { SelectChangeEvent } from "@mui/material/Select"; -import { ReadonlyURLSearchParams, useSearchParams} from "next/navigation" +import { ReadonlyURLSearchParams, useSearchParams } from "next/navigation" import { GenomeBrowserView } from "../../common/gbview/genomebrowserview" import { CcreAutoComplete } from "../../common/components/mainsearch/CcreAutocomplete" import { DataTable } from "@weng-lab/psychscreen-ui-components" - -import {IcresByRegion} from "./icresbyregion" -import { ATAC_UMAP_QUERY, EBI_ASSO_QUERY, ICRES_BYCT_ZSCORES_QUERY, ICRES_CT_ZSCORES_QUERY, ICRES_QUERY } from "./queries" -import InputLabel from "@mui/material/InputLabel"; -import { stringToColour } from "../../common/utils"; +import { ATACUMAP } from "./atacumap" import { AtacBarPlot } from "./atacbarplot" -import { cellTypeStaticInfo } from "../../common./../common/consts"; -import { UmapPlot } from "../../common/components/umapplot"; +import InputLabel from "@mui/material/InputLabel"; +import Select, { SelectChangeEvent } from "@mui/material/Select"; +import { stringToColour } from "./utils"; +import { ICRES_CT_ZSCORES_QUERY, ICRES_BYCT_ZSCORES_QUERY, ICRES_QUERY, EBI_ASSO_QUERY } from "./queries"; +import { cellColors } from "./consts"; + + +import { IcresByRegion } from "./icresbyregion" import CellTypeTree from "../../common/components/cellTypeTree" -import { generateCellLineageTreeState, getCellColor } from "../celllineage/utils" +import { generateCellLineageTreeState } from "../celllineage/utils" //Need better text styling -import ToggleButton from '@mui/material/ToggleButton'; +export default function Icres() { -export default function Icres() { const searchParams: ReadonlyURLSearchParams = useSearchParams()! const [value, setValue] = useState(0) const router = useRouter() @@ -43,16 +42,8 @@ export default function Icres() { const handleSelectedPortal = (event: SelectChangeEvent) => { setSelectedPortal(event.target.value); }; - const { loading: atacumaploading, data: atacumapdata } = useQuery(ATAC_UMAP_QUERY, { - variables: { - accession: searchParams.get("accession") - }, - skip: !searchParams.get("accession"), - fetchPolicy: "cache-and-network", - nextFetchPolicy: "cache-first", - client, - }) - const {loading: icrezscoreloading, data: icrezscoredata} = useQuery(ICRES_CT_ZSCORES_QUERY,{ + + const { loading: icrezscoreloading, data: icrezscoredata } = useQuery(ICRES_CT_ZSCORES_QUERY, { variables: { accession: searchParams.get('accession'), study: [study] @@ -83,27 +74,21 @@ export default function Icres() { nextFetchPolicy: "cache-first", client, }) + // console.log(aloading,adata) let barplotdata = icrezscoredata && icrezscoredata.calderoncorcesAtacQuery.map(ic => { return { ...ic, - color: getCellColor(ic.celltype), + color: cellColors[ic.celltype] || stringToColour(ic.celltype), value: ic.value + } }) - - const [colorScheme, setcolorScheme] = useState('Zscore'); - const handleColorSchemeChange = ( - event: React.MouseEvent, - newScheme: string, - ) => { - setcolorScheme(newScheme); - }; let barplotbyctdata = icrebyctzscoredata && icrebyctzscoredata.calderoncorcesByCtAtacQuery.map(ic => { return { ...ic, - color: getCellColor(ic.celltype), + color: cellColors[ic.celltype] || stringToColour(ic.celltype), value: ic.value } @@ -143,254 +128,243 @@ export default function Icres() { const start = coordinates[0] const end = coordinates[1] router.push(`/icres?chromosome=${chromosome}&start=${start}&end=${end}`) -} -return !searchParams.get('accession') && !searchParams.get('chromosome') ? ( -
- - - iCRE Portal -
- - - -
-
- {selectedPortal === "Genomic Region" ? - { - if (event.code === "Enter") { - handleSubmit() - } - }} - InputProps={{ - endAdornment: ( - - handleSubmit()} sx={{ color: "black" }}> - - - - ), - style: { color: "black" }, - }} - sx={{ mr: "1em", ml: "1em", fieldset: { borderColor: "black" } }} - /> - : - } -
-
-
) : searchParams.get('chromosome') ? ( - - ) : ( -
- - - - {searchParams.get("accession") && Accession Details: {searchParams.get("accession")}} - - - - - - - - - - - + } - {value === 0 && adata && - - + // console.log("coordinates", adata && adata.iCREQuery[0].coordinates) + console.log("active cells: ", adata && adata.iCREQuery[0].celltypes) + return !searchParams.get('accession') && !searchParams.get('chromosome') ? ( +
+ + + iCRE Portal +
+ + + +
+
+
+ {selectedPortal === "Genomic Region" ? + { + if (event.code === "Enter") { + handleSubmit() + } + }} + InputProps={{ + endAdornment: ( + + handleSubmit()} sx={{ color: "black" }}> + + + + ), + style: { color: "black" }, + }} + sx={{ mr: "1em", ml: "1em", fieldset: { borderColor: "black" } }} + /> + : + }
- } - {value === 1 && searchParams.get("accession") && !atacumaploading && atacumapdata && atacumapdata.calderonAtacUmapQuery.length>0 && - +
+
) : searchParams.get('chromosome') ? ( + + ) : ( +
+ + - Color Scheme: -

- - Zscore - CellType Cluster - -
-
- -
- + {searchParams.get("accession") && Accession Details: {searchParams.get("accession")}} +
+ + + + + + + + + +
- } - {value === 2 && ebidata && - + {value === 0 && adata && - row.chromosome, - }, - { - header: "Position", - value: (row) => row.position, - }, - { - header: "Strongest snp risk allele", - value: (row) => row.strongest_snp_risk_allele, - }, - { - header: "Risk Allele Frequency", - value: (row) => row.risk_allele_frequency, + name: adata.iCREQuery[0].accession, + start: adata.iCREQuery[0].coordinates.start, + end: adata.iCREQuery[0].coordinates.end, + } + } + assembly={"GRCh38"} + coordinates={{ start: adata.iCREQuery[0].coordinates.start, end: adata.iCREQuery[0].coordinates.end, chromosome: adata.iCREQuery[0].coordinates.chromosome }} + defaultcelltypes={adata.iCREQuery[0].celltypes} + /> + + } + {value === 1 && searchParams.get("accession") && + <> + + + + - }, - { - header: "P-Value", - value: (row) => row.p_value && row.p_value || 0, - }, - { - header: "Study", - value: (row) => row.study, - }, - { - header: "Region", - value: (row) => row.region, - }, - { - header: "Immu screen trait", - value: (row) => row.immu_screen_trait - }, - { - header: "mapped_trait", - value: (row) => row.mapped_trait - }, - { - header: "Pubmed Id", - value: (row) => row.pubmedid + } + {value === 2 && ebidata && + + + row.chromosome, + }, + { + header: "Position", + value: (row) => row.position, + }, + { + header: "Strongest snp risk allele", + value: (row) => row.strongest_snp_risk_allele, + }, + { + header: "Risk Allele Frequency", + value: (row) => row.risk_allele_frequency, - } + }, + { + header: "P-Value", + value: (row) => row.p_value && row.p_value || 0, + }, + { + header: "Study", + value: (row) => row.study, + }, + { + header: "Region", + value: (row) => row.region, + }, + { + header: "Immu screen trait", + value: (row) => row.immu_screen_trait + }, + { + header: "mapped_trait", + value: (row) => row.mapped_trait + }, + { + header: "Pubmed Id", + value: (row) => row.pubmedid - ]} - tableTitle={`EBI Associations for ${searchParams.get('accession')}:`} - rows={ebidata.ebiAssociationsQuery || []} + } + ]} + tableTitle={`EBI Associations for ${searchParams.get('accession')}:`} + rows={ebidata.ebiAssociationsQuery || []} - itemsPerPage={10} - /> + + itemsPerPage={10} + /> + - - } - {value === 3 && - - - Study - - -
-
-
} - {value === 3 && - - - - {study === 'Calderon' && } - - - } - {value === 3 && zscoreValue === 0 && icrezscoredata && icrezscoredata.calderoncorcesAtacQuery.length > 0 && barplotdata && - <> - - {barplotdata.filter(b => b.grouping === 'Lymphoid') && - b.grouping === 'Lymphoid')} /> - } - - {barplotdata.filter(b => b.grouping === 'Myeloid') && - b.grouping === 'Myeloid')} /> - } + } + {value === 3 && + + + Study + + +
+
+
} + {value === 3 && + + + + {study === 'Calderon' && } + - {study === 'Corces' && + } + {value === 3 && zscoreValue === 0 && icrezscoredata && icrezscoredata.calderoncorcesAtacQuery.length > 0 && barplotdata && + <> - {barplotdata.filter(b => b.grouping === 'Leukemic') && - b.grouping === 'Leukemic')} /> + {barplotdata.filter(b => b.grouping === 'Lymphoid') && + b.grouping === 'Lymphoid')} /> } - {barplotdata.filter(b => b.grouping === 'Progenitors') && - b.grouping === 'Progenitors')} /> + {barplotdata.filter(b => b.grouping === 'Myeloid') && + b.grouping === 'Myeloid')} /> } - } - - } - { - value === 3 && zscoreValue === 1 && icrebyctzscoredata && icrebyctzscoredata.calderoncorcesByCtAtacQuery.length > 0 && barplotbyctdata && - - - + {study === 'Corces' && + + {barplotdata.filter(b => b.grouping === 'Leukemic') && + b.grouping === 'Leukemic')} /> + } + + {barplotdata.filter(b => b.grouping === 'Progenitors') && + b.grouping === 'Progenitors')} /> + } + + } + + } + { + value === 3 && zscoreValue === 1 && icrebyctzscoredata && icrebyctzscoredata.calderoncorcesByCtAtacQuery.length > 0 && barplotbyctdata && + + + + - - } - {value === 4 && - (aloading ? - - : - (adata?.iCREQuery[0].celltypes.length === 0) ? - Not identified as active in immune cells + } + {value === 4 && + (aloading ? + : - - {searchParams.get('accession')} is determined to be active in the following cells: - - - ) - } - -
-) + (adata?.iCREQuery[0].celltypes.length === 0) ? + Not identified as active in immune cells + : + + {searchParams.get('accession')} is determined to be active in the following cells: + + + ) + } +
+
+ ) } diff --git a/immuscreen/src/common/components/cellTypeTree.tsx b/immuscreen/src/common/components/cellTypeTree.tsx index c3bbc20..47fc2cb 100644 --- a/immuscreen/src/common/components/cellTypeTree.tsx +++ b/immuscreen/src/common/components/cellTypeTree.tsx @@ -4,8 +4,7 @@ import { Tree, hierarchy } from '@visx/hierarchy'; import { HierarchyPointNode, HierarchyPointLink } from '@visx/hierarchy/lib/types'; import { LinkHorizontal, LinkVertical } from '@visx/shape'; import { defaultStyles as defaultTooltipStyles, useTooltip, TooltipWithBounds } from '@visx/tooltip'; -import { CellDisplayName, CellLineageTreeState, CellTypeStaticInfo, DynamicCellTypeInfo } from '../../app/celllineage/types'; -import { cellTypeStaticInfo } from '../consts'; +import { CellLineageTreeState, DynamicCellTypeInfo, StaticCellTypeInfo, cellLineageTreeStaticInfo } from '../../app/celllineage/utils'; const linkStroke = '#000000'; const background = 'transparent'; @@ -13,7 +12,7 @@ const fontSize = 12 const fadedCellOpacity = 0.3 -interface CellNode extends CellTypeStaticInfo, DynamicCellTypeInfo { +interface CellNode extends StaticCellTypeInfo, DynamicCellTypeInfo { children?: CellNode[]; } @@ -24,9 +23,7 @@ const uninteractiveNode = { selectable: false, stimulated: "U" as "U" | "S" | "B", stimulable: false, - unstimCount: 0, - color: null, - displayName: null + unstimCount: 0 } interface TooltipData { @@ -122,19 +119,19 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o return ( { ...cellTypeState.HSC, - ...cellTypeStaticInfo.HSC, + ...cellLineageTreeStaticInfo.HSC, children: [ { ...cellTypeState.MPP, - ...cellTypeStaticInfo.MPP, + ...cellLineageTreeStaticInfo.MPP, children: [ { ...cellTypeState.CMP, - ...cellTypeStaticInfo.CMP, + ...cellLineageTreeStaticInfo.CMP, children: [ { ...cellTypeState.GMP, - ...cellTypeStaticInfo.GMP, + ...cellLineageTreeStaticInfo.GMP, children: [ // { // displayName: 'Neutrophil', @@ -142,15 +139,15 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o // }, { ...cellTypeState.pDCs, - ...cellTypeStaticInfo.pDCs, + ...cellLineageTreeStaticInfo.pDCs, }, { ...cellTypeState.Myeloid_DCs, - ...cellTypeStaticInfo.Myeloid_DCs, + ...cellLineageTreeStaticInfo.Myeloid_DCs, }, { ...cellTypeState.Monocytes, - ...cellTypeStaticInfo.Monocytes, + ...cellLineageTreeStaticInfo.Monocytes, } ] } @@ -158,41 +155,41 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o }, { ...cellTypeState.MEP, - ...cellTypeStaticInfo.MEP, + ...cellLineageTreeStaticInfo.MEP, children: [ { ...cellTypeState.Ery, - ...cellTypeStaticInfo.Ery, + ...cellLineageTreeStaticInfo.Ery, } ] }, { ...cellTypeState.LMPP, - ...cellTypeStaticInfo.LMPP, + ...cellLineageTreeStaticInfo.LMPP, children: [ { ...cellTypeState.CLP, - ...cellTypeStaticInfo.CLP, + ...cellLineageTreeStaticInfo.CLP, children: [ { - treeDisplayName: 'Double-negative cell', + displayName: 'Double-negative cell', ...uninteractiveNode, children: [ { - ...cellTypeState.NKcell, - ...cellTypeStaticInfo.NKcell, + ...cellTypeState.Nkcell, + ...cellLineageTreeStaticInfo.Nkcell, children: [ { ...cellTypeState.Immature_NK, - ...cellTypeStaticInfo.Immature_NK, + ...cellLineageTreeStaticInfo.Immature_NK, children: [ { ...cellTypeState.Mature_NK, - ...cellTypeStaticInfo.Mature_NK, + ...cellLineageTreeStaticInfo.Mature_NK, children: [ { ...cellTypeState.Memory_NK, - ...cellTypeStaticInfo.Memory_NK, + ...cellLineageTreeStaticInfo.Memory_NK, } ] } @@ -202,47 +199,47 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o }, { ...cellTypeState.Gamma_delta_T, - ...cellTypeStaticInfo.Gamma_delta_T, + ...cellLineageTreeStaticInfo.Gamma_delta_T, }, { - treeDisplayName: 'CD4 immature single-positive cell', + displayName: 'CD4 immature/single-positive cell', ...uninteractiveNode, children: [ { - treeDisplayName: 'Double-positive cell', + displayName: 'Double-positive/cell', ...uninteractiveNode, children: [ { ...cellTypeState.CD4Tcell, - ...cellTypeStaticInfo.CD4Tcell, + ...cellLineageTreeStaticInfo.CD4Tcell, children: [ { ...cellTypeState.Effector_CD4pos_T, - ...cellTypeStaticInfo.Effector_CD4pos_T, + ...cellLineageTreeStaticInfo.Effector_CD4pos_T, children: [ { ...cellTypeState.Naive_Teffs, - ...cellTypeStaticInfo.Naive_Teffs, + ...cellLineageTreeStaticInfo.Naive_Teffs, children: [ { ...cellTypeState.Memory_Teffs, - ...cellTypeStaticInfo.Memory_Teffs, + ...cellLineageTreeStaticInfo.Memory_Teffs, children: [ { ...cellTypeState.Th1_precursors, - ...cellTypeStaticInfo.Th1_precursors, + ...cellLineageTreeStaticInfo.Th1_precursors, }, { ...cellTypeState.Th2_precursors, - ...cellTypeStaticInfo.Th2_precursors, + ...cellLineageTreeStaticInfo.Th2_precursors, }, { ...cellTypeState.Th17_precursors, - ...cellTypeStaticInfo.Th17_precursors, + ...cellLineageTreeStaticInfo.Th17_precursors, }, { ...cellTypeState.Follicular_T_Helper, - ...cellTypeStaticInfo.Follicular_T_Helper, + ...cellLineageTreeStaticInfo.Follicular_T_Helper, } ] } @@ -252,15 +249,15 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o }, { ...cellTypeState.Regulatory_T, - ...cellTypeStaticInfo.Regulatory_T, + ...cellLineageTreeStaticInfo.Regulatory_T, children: [ { ...cellTypeState.Naive_Tregs, - ...cellTypeStaticInfo.Naive_Tregs, + ...cellLineageTreeStaticInfo.Naive_Tregs, children: [ { ...cellTypeState.Memory_Tregs, - ...cellTypeStaticInfo.Memory_Tregs, + ...cellLineageTreeStaticInfo.Memory_Tregs, } ] } @@ -270,19 +267,19 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o }, { ...cellTypeState.CD8pos_T, - ...cellTypeStaticInfo.CD8pos_T, + ...cellLineageTreeStaticInfo.CD8pos_T, children: [ { ...cellTypeState.Naive_CD8_T, - ...cellTypeStaticInfo.Naive_CD8_T, + ...cellLineageTreeStaticInfo.Naive_CD8_T, children: [ { ...cellTypeState.Central_memory_CD8pos_T, - ...cellTypeStaticInfo.Central_memory_CD8pos_T, + ...cellLineageTreeStaticInfo.Central_memory_CD8pos_T, }, { ...cellTypeState.Effector_memory_CD8pos_T, - ...cellTypeStaticInfo.Effector_memory_CD8pos_T, + ...cellLineageTreeStaticInfo.Effector_memory_CD8pos_T, } ] } @@ -296,19 +293,19 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o }, { ...cellTypeState.Bulk_B, - ...cellTypeStaticInfo.Bulk_B, + ...cellLineageTreeStaticInfo.Bulk_B, children: [ { ...cellTypeState.Naive_B, - ...cellTypeStaticInfo.Naive_B, + ...cellLineageTreeStaticInfo.Naive_B, children: [ { ...cellTypeState.Mem_B, - ...cellTypeStaticInfo.Mem_B, + ...cellLineageTreeStaticInfo.Mem_B, children: [ { ...cellTypeState.Plasmablasts, - ...cellTypeStaticInfo.Plasmablasts, + ...cellLineageTreeStaticInfo.Plasmablasts, } ] } @@ -351,13 +348,13 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o left={left} > - {node.data.treeDisplayName.split('/').map((str, i) => { + {node.data.displayName.split('/').map((str, i) => { return ( {str} ) @@ -510,7 +507,7 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o style={{ ...defaultTooltipStyles, backgroundColor: '#283238', color: 'white' }} >
- {tooltipData.name.replace(' ', '\u00A0').replace('-', '\u2011')} + {tooltipData.name.replace('/', '\u00A0').replace(' ', '\u00A0').replace('-', '\u2011')}

Unstimulated Active iCREs: {tooltipData.unstimCount.toLocaleString()}