diff --git a/.github/workflows/sync-vercel-preview-branch.yml b/.github/workflows/sync-vercel-preview-branch.yml new file mode 100644 index 0000000..920b515 --- /dev/null +++ b/.github/workflows/sync-vercel-preview-branch.yml @@ -0,0 +1,25 @@ +name: Sync master to vercel-preview + +on: + push: + branches: + - master + +jobs: + sync-branches: + runs-on: ubuntu-latest + name: Syncing branches + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Node + uses: actions/setup-node@v1 + with: + node-version: 12 + - name: Opening pull request + id: pull + uses: tretuna/sync-branches@1.4.0 + with: + GITHUB_TOKEN: ${{secrets.VERCEL_TOKEN}} + FROM_BRANCH: 'master' + TO_BRANCH: 'vercel-preview' diff --git a/immuscreen/src/app/celllineage/page.tsx b/immuscreen/src/app/celllineage/page.tsx index a276dab..6b75739 100644 --- a/immuscreen/src/app/celllineage/page.tsx +++ b/immuscreen/src/app/celllineage/page.tsx @@ -17,64 +17,8 @@ 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 { StaticCellTypeInfo, CellLineageTreeState, downloadSVG, extractQueryValues, generateCellLineageTreeState, DynamicCellTypeInfo, CellName, cellLineageTreeStaticInfo } from "./utils"; -//Info for each cell type -export interface CellTypeInfo { - readonly id: string; // used to set state of correct celltype afer its content is spread (...) into tree data and stripped of key name. Needs to match key exactly - readonly displayName: string; - readonly unstimImagePath: string; - readonly stimImagePath?: string; - selected: boolean; - stimulated: "S" | "U" | "B"; - readonly selectable: boolean; - readonly stimulable: boolean; - readonly queryValues?: { - readonly unstimulated: { Calderon?: string | string[], Corces?: string | string[] }; - readonly stimulated?: { Calderon: string | string[] } - } - //Counts are currently hardcoded below. - readonly unstimCount: number - readonly stimCount?: number -} - -export interface CellTypes { - Myeloid_DCs: CellTypeInfo, - pDCs: CellTypeInfo, - Naive_B: CellTypeInfo, - Mem_B: CellTypeInfo, - Plasmablasts: CellTypeInfo, - Regulatory_T: CellTypeInfo, - Naive_Tregs: CellTypeInfo, - Memory_Tregs: CellTypeInfo, - Effector_CD4pos_T: CellTypeInfo, - Naive_Teffs: CellTypeInfo, - Memory_Teffs: CellTypeInfo, - Th1_precursors: CellTypeInfo, - Th2_precursors: CellTypeInfo, - Th17_precursors: CellTypeInfo, - Follicular_T_Helper: CellTypeInfo, - Naive_CD8_T: CellTypeInfo, - Central_memory_CD8pos_T: CellTypeInfo, - Effector_memory_CD8pos_T: CellTypeInfo, - Gamma_delta_T: CellTypeInfo, - Immature_NK: CellTypeInfo, - Mature_NK: CellTypeInfo, - Memory_NK: CellTypeInfo, - HSC: CellTypeInfo, //Hematopoetic Stem Cell - MPP: CellTypeInfo, //Multipotent Progenitor - CMP: CellTypeInfo, //Common Myeloid Progenitor - MEP: CellTypeInfo, //Megakaryocyte Erythroid Progenitor - Ery: CellTypeInfo, //Erythrocyte - GMP: CellTypeInfo, //Granulocyte-Monocyte Progenitors - LPMP: CellTypeInfo, //Lymphocyte-Primed Multipotent Progenitor - CLP: CellTypeInfo, //Common Lymphoid Progenitor - CD4Tcell: CellTypeInfo, //CD4+ Tcell - Nkcell: CellTypeInfo, //NK cell - //These are the cells which have data from both Calderon and Corces - Monocytes: CellTypeInfo, //Using Calderon "name". Using Calderon's Stimulated/Unstimulated. In Corces it is "Mono", and no stimulation info - Bulk_B: CellTypeInfo, //Using Calderon "name". Using Calderon's Stimulated/Unstimulated. In Corces it is "Bcell", and no stimulation info - CD8pos_T: CellTypeInfo, //Using Calderon "name". Using Calderon's Stimulated/Unstimulated. In Corces it is "CD8Tcell", and no stimulation info -} type QueryGroup = { intersect?: string[][], @@ -96,535 +40,16 @@ const classDisplaynames: { [key in CCRE_CLASS]: string } = { "PLS": "Promoter-Like Signature" } - - /** * Initial configuration of the cell type tree * To break displayName into multiple lines in the tree, use '/' instead of a space */ -const cellTypeInitialState: CellTypes = { - Monocytes: { - id: 'Monocytes', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - displayName: "Myeloid/dendritic cell", - unstimImagePath: '/cellTypes/Myeloid_DCs-U.png', - stimulable: false, - queryValues: { - unstimulated: { Calderon: "Myeloid_DCs-U" } - }, - unstimCount: 173394 - }, - pDCs: { - id: 'pDCs', - selected: false, - stimulated: "U", - selectable: true, - displayName: "Plasmacytoid/dendritic cell", - unstimImagePath: '/cellTypes/pDCs-U.png', - stimulable: false, - queryValues: { - unstimulated: { Calderon: 'pDCs-U' } - }, - unstimCount: 146515 - }, - Bulk_B: { - id: 'Bulk_B', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - displayName: "Plasmablast", - unstimImagePath: '/cellTypes/Plasmablasts-U.png', - stimulable: false, - queryValues: { - unstimulated: { Calderon: 'Plasmablasts-U' } - }, - unstimCount: 123042 - }, - Regulatory_T: { - id: 'Regulatory_T', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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 - }, - CD8pos_T: { - id: 'CD8pos_T', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - 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', - selected: false, - stimulated: "U", - selectable: true, - displayName: "Memory/NK cell", - unstimImagePath: '/cellTypes/Memory_NK-U.png', - stimulable: false, - queryValues: { - unstimulated: { Calderon: 'Memory_NK-U' } - }, - unstimCount: 135352 - }, - HSC: { - id: 'HSC', - selected: false, - stimulated: "U", - selectable: true, - 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", - selected: false, - stimulated: "U", - selectable: true, - displayName: "Multipotent/progenitor", - unstimImagePath: '/cellTypes/MPP.png', - stimulable: false, - queryValues: { - unstimulated: { Corces: "MPP" } - }, - unstimCount: 158945 - }, - CMP: { - id: "CMP", - selected: false, - stimulated: "U", - selectable: true, - displayName: "Common myeloid/progenitor", - unstimImagePath: '/cellTypes/CMP.png', - stimulable: false, - queryValues: { - unstimulated: { Corces: "CMP" } - }, - unstimCount: 159706 - }, - MEP: { - id: "MEP", - selected: false, - stimulated: "U", - selectable: true, - displayName: "Megakaryocyte-erythroid/progenitor", - unstimImagePath: '/cellTypes/MEP.png', - stimulable: false, - queryValues: { - unstimulated: { Corces: "MEP" } - }, - unstimCount: 152044 - }, - Ery: { - id: "Ery", - selected: false, - stimulated: "U", - selectable: true, - displayName: "Erythrocyte", - unstimImagePath: '/cellTypes/Erythrocyte.png', - stimulable: false, - queryValues: { - unstimulated: { Corces: "Ery" } - }, - unstimCount: 56267 - }, - GMP: { - id: "GMP", - selected: false, - stimulated: "U", - selectable: true, - displayName: "Granulocyte-monocyte/progenitors", - unstimImagePath: '/cellTypes/GMP.png', - stimulable: false, - queryValues: { - unstimulated: { Corces: "GMP" } - }, - unstimCount: 158558 - }, - LPMP: { - id: "LPMP", - selected: false, - stimulated: "U", - selectable: true, - displayName: "Lymphocyte-primed/multipotent progenitor", - unstimImagePath: '/cellTypes/LMP.png', - stimulable: false, - queryValues: { - unstimulated: { Corces: "LMPP" } - }, - unstimCount: 128494 - }, - CLP: { - id: "CLP", - selected: false, - stimulated: "U", - selectable: true, - displayName: "Common lymphoid/progenitor", - unstimImagePath: '/cellTypes/CLP.png', - stimulable: false, - queryValues: { - unstimulated: { Corces: "CLP" } - }, - unstimCount: 93170 - }, - CD4Tcell: { - id: "CD4Tcell", - selected: false, - stimulated: "U", - selectable: true, - displayName: "CD4+ T cell", - unstimImagePath: '/cellTypes/CD4posT.png', - stimulable: false, - queryValues: { - unstimulated: { Corces: "CD4Tcell" } - }, - unstimCount: 121034 - }, - Nkcell: { - id: "Nkcell", - selected: false, - stimulated: "U", - selectable: true, - displayName: "NK cell", - unstimImagePath: '/cellTypes/Nkcell.png', - stimulable: false, - queryValues: { - unstimulated: { Corces: "NKcell" } - }, - unstimCount: 116626 - } -} export default function UpSet() { - const [cellTypeState, setCellTypeState] = useState(cellTypeInitialState) //state of tree + const [cellTypeState, setCellTypeState] = useState(generateCellLineageTreeState([], true)) //state of tree const [stimulateMode, setStimulateMode] = useState(false) //determines whether a click on the tree selects or stimulates cell //Modifications to tree and checkboxes wipe needed info for download, so store when generating: - const [upSetCells, setUpSetCells] = useState([]) //stores array of selected cells when generating + const [upSetCells, setUpSetCells] = useState>({}) //stores array of selected cells when generating const [upSetClasses, setUpSetClasses] = useState(null) //stores array of selected classes when generating const [upSetQueryGroups, setUpSetQueryGroups] = useState<{ [key: string]: QueryGroup }>(null) //stores groupings used to generate query (for DL) const [downloading, setDownloading] = useState(false) @@ -670,17 +95,14 @@ export default function UpSet() { */ const handleStimulateAll = (mode: "U" | "S" | "B") => { const currentlySelected = Object.values(cellTypeState) - .filter((x: CellTypeInfo) => x.selected) - .reduce((accumulator, current: CellTypeInfo) => current.stimulated === "B" ? accumulator + 2 : accumulator + 1, 0) + .filter((x) => x.selected) + .reduce((accumulator, current) => current.stimulated === "B" ? accumulator + 2 : accumulator + 1, 0) //If applying "B" stimulation status would exceed selection limit, stop and send alert to user. if (mode === "B" && (currentlySelected * 2) > cellTreeSelectionLimit) { handleOpenSnackbar("Unable to apply \"Both\" stimulation status due to selection limit (6)") } else { - let newObj = { ...cellTypeState } - for (let cellName in newObj) { - newObj[cellName].stimulable && (newObj[cellName].stimulated = mode) - } - setCellTypeState(newObj) + 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) } } @@ -765,70 +187,33 @@ export default function UpSet() { }, [setDownloading, upSetQueryGroups, upSetClasses, getiCREFileURL]); const svgRef = useRef(null) - - 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(); - }; - - const downloadSVG = (ref: React.MutableRefObject, filename: string) =>{ - ref.current && downloadData(svgData(ref.current!), filename, 'image/svg;charset=utf-8'); - } - /** - * - * @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. - */ - const extractQueryValues = (cell: CellTypeInfo, want: "S" | "U" | "B"): (string[]) => { - switch (want) { - case "U": return [...Object.values(cell.queryValues.unstimulated).flat()] - case "S": return [...Object.values(cell.queryValues.stimulated).flat()] - case "B": return Object.values(cell.queryValues.unstimulated).flat().concat((Object.values(cell.queryValues.stimulated).flat())) - } - } - /** * Programmatically generates a gql document node with the needed queries for generating the UpSet plot * @param selectedCells * @param classes the selected cCRE classes * @returns gql query string for UpSet plot */ - const generateQuery = useCallback((selectedCells: CellTypeInfo[], classes: CCRE_CLASS[]) => { - //stores extracted relevant information from selectedCells + const generateQuery = useCallback((selectedCellsState: Partial, classes: CCRE_CLASS[]) => { + //stores extracted relevant information from selectedCells for easier access 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 - selectedCells.forEach(cell => { - if (cell.stimulated == "B") { - cells.push({ displayName: cell.id.replace('-', '_') + '_U', queryVals: extractQueryValues(cell, "U") }) - cells.push({ displayName: cell.id.replace('-', '_') + '_S', queryVals: extractQueryValues(cell, "S") }) - } else cells.push({ displayName: cell.id.replace('-', '_') + '_' + cell.stimulated, queryVals: extractQueryValues(cell, cell.stimulated) }) + Object.entries(selectedCellsState).forEach(([key, value]: [CellName, DynamicCellTypeInfo]) => { + const name = key.replace('-', '_') + if (value.stimulated == "B") { + 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 let queryGroups: QueryGroup[] = [] //Union of all cells - if (selectedCells.length > 0) { - queryGroups.push({ union: selectedCells.map(cell => extractQueryValues(cell, cell.stimulated)).flat(2), name: 'Union_All' }) + if (Object.keys(selectedCellsState).length > 0) { + queryGroups.push({ union: Object.entries(selectedCellsState).map(([key, value]: [CellName, DynamicCellTypeInfo]) => extractQueryValues(cellLineageTreeStaticInfo[key], value.stimulated)).flat(2), name: 'Union_All' }) } //Individual counts @@ -955,13 +340,13 @@ export default function UpSet() { * Stores selected cells and classes, and begins the fetch */ const handleGenerateUpSet = () => { - setUpSetCells(Object.values(cellTypeState).filter((x: CellTypeInfo) => x.selected)) + setUpSetCells(Object.fromEntries(Object.entries(cellTypeState).filter(([key, value]: [CellName, DynamicCellTypeInfo]) => value.selected))) setUpSetClasses(Object.entries(checkboxClasses).filter((x: [string, boolean]) => x[1]).map((y: [string, boolean]) => y[0] as CCRE_CLASS)) getCountData() } const COUNT_QUERY = useMemo(() => { - if (upSetCells.length > 0) { + if (Object.keys(upSetCells).length > 0) { return ( generateQuery(upSetCells, upSetClasses) ) @@ -1037,12 +422,14 @@ export default function UpSet() { UpSetWithRef.displayName = "UpSetPlot" - //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.values(cellTypeState).filter(x => x.stimulable).map(x => x.stimulated).every(x => x === "U") - const allStimulated = Object.values(cellTypeState).filter(x => x.stimulable).map(x => x.stimulated).every(x => x === "S") - const allBothStimulated = Object.values(cellTypeState).filter(x => x.stimulable).map(x => x.stimulated).every(x => x === "B") + const noneStimulated = Object.entries(cellTypeState) + .every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellLineageTreeStaticInfo[key].stimulable ? value.stimulated === "U" : true) + const allStimulated = Object.entries(cellTypeState) + .every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellLineageTreeStaticInfo[key].stimulable ? value.stimulated === "S": true) + const allBothStimulated = Object.entries(cellTypeState) + .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 35699b3..337c4af 100644 --- a/immuscreen/src/app/icres/page.tsx +++ b/immuscreen/src/app/icres/page.tsx @@ -1,16 +1,16 @@ "use client" -import React, {useState} from "react" +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 { 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 { 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" @@ -23,7 +23,9 @@ import { ICRES_CT_ZSCORES_QUERY, ICRES_BYCT_ZSCORES_QUERY, ICRES_QUERY, EBI_ASSO import { cellColors } from "./consts"; -import {IcresByRegion} from "./icresbyregion" +import { IcresByRegion } from "./icresbyregion" +import CellTypeTree from "../../common/components/cellTypeTree" +import { generateCellLineageTreeState } from "../celllineage/utils" //Need better text styling @@ -36,32 +38,32 @@ export default function Icres() { const [searchvalue, setSearchValue] = useState("") const [study, setStudy] = useState("Calderon") const [selectedPortal, setSelectedPortal] = useState("Genomic Region"); - const [zscoreValue, setzscoreValue] = useState(0) + const [zscoreValue, setzscoreValue] = useState(0) const handleSelectedPortal = (event: SelectChangeEvent) => { setSelectedPortal(event.target.value); }; - - 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] + study: [study] }, skip: !searchParams.get('accession'), fetchPolicy: "cache-and-network", nextFetchPolicy: "cache-first", client, }) - const {loading: icrebyctzscoreloading, data: icrebyctzscoredata} = useQuery(ICRES_BYCT_ZSCORES_QUERY,{ + const { loading: icrebyctzscoreloading, data: icrebyctzscoredata } = useQuery(ICRES_BYCT_ZSCORES_QUERY, { variables: { accession: searchParams.get('accession'), - study:[study] + study: [study] }, skip: !searchParams.get('accession'), fetchPolicy: "cache-and-network", nextFetchPolicy: "cache-first", client, }) - + const { loading: aloading, data: adata } = useQuery(ICRES_QUERY, { variables: { @@ -75,27 +77,27 @@ export default function Icres() { // console.log(aloading,adata) -let barplotdata = icrezscoredata && icrezscoredata.calderoncorcesAtacQuery.map(ic=>{ - return { - ...ic, - color: cellColors[ic.celltype] || stringToColour(ic.celltype), - value: ic.value + let barplotdata = icrezscoredata && icrezscoredata.calderoncorcesAtacQuery.map(ic => { + return { + ...ic, + color: cellColors[ic.celltype] || stringToColour(ic.celltype), + value: ic.value - } -}) -let barplotbyctdata = icrebyctzscoredata && icrebyctzscoredata.calderoncorcesByCtAtacQuery.map(ic=>{ - return { - ...ic, - color: cellColors[ic.celltype] ||stringToColour(ic.celltype), - value: ic.value + } + }) + let barplotbyctdata = icrebyctzscoredata && icrebyctzscoredata.calderoncorcesByCtAtacQuery.map(ic => { + return { + ...ic, + color: cellColors[ic.celltype] || stringToColour(ic.celltype), + value: ic.value - } -}) + } + }) -barplotdata = !icrezscoreloading && icrezscoredata && icrezscoredata.calderoncorcesAtacQuery.length>0 && barplotdata.sort((a, b) => a.order-b.order); -barplotbyctdata = !icrebyctzscoreloading && icrebyctzscoredata && icrebyctzscoredata.calderoncorcesByCtAtacQuery.length>0 && barplotbyctdata.sort((a, b) => a.order-b.order); + barplotdata = !icrezscoreloading && icrezscoredata && icrezscoredata.calderoncorcesAtacQuery.length > 0 && barplotdata.sort((a, b) => a.order - b.order); + barplotbyctdata = !icrebyctzscoreloading && icrebyctzscoredata && icrebyctzscoredata.calderoncorcesByCtAtacQuery.length > 0 && barplotbyctdata.sort((a, b) => a.order - b.order); -const {data: ebidata} = useQuery(EBI_ASSO_QUERY,{ + const { data: ebidata } = useQuery(EBI_ASSO_QUERY, { variables: { accession: searchParams.get('accession') }, @@ -104,17 +106,17 @@ const {data: ebidata} = useQuery(EBI_ASSO_QUERY,{ nextFetchPolicy: "cache-first", client, }) - + const handleChange = (_, newValue: number) => { setValue(newValue) -} -const handleZscoreChange = (_, newValue: number) => { + } + const handleZscoreChange = (_, newValue: number) => { setzscoreValue(newValue) -} -const handleSearchChange = (event: { target: { value: React.SetStateAction } }) => { + } + const handleSearchChange = (event: { target: { value: React.SetStateAction } }) => { setSearchValue(event.target.value) -} -function handleSubmit() { + } + function handleSubmit() { //if submitted with empty value, use default search if (searchvalue == "") { router.push(`/icres?chromosome=chr11&start=5205263&end=5381894`) @@ -126,224 +128,242 @@ function handleSubmit() { const start = coordinates[0] const end = coordinates[1] router.push(`/icres?chromosome=${chromosome}&start=${start}&end=${end}`) -} + } -// console.log("coordinates", adata && adata.iCREQuery[0].coordinates) - 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') ? ( - - ) : ( + // console.log("coordinates", adata && adata.iCREQuery[0].coordinates) + console.log("active cells: ", adata && adata.iCREQuery[0].celltypes) + return !searchParams.get('accession') && !searchParams.get('chromosome') ? (
- - - - {searchParams.get("accession") && Accession Details: {searchParams.get("accession")}} - - - - - - - - - - + + + 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 && + + + + } + {value === 1 && searchParams.get("accession") && + <> + + - - {value===0 && adata && + + + } + {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 + }, { - name: adata.iCREQuery[0].accession, - start: adata.iCREQuery[0].coordinates.start, - end: adata.iCREQuery[0].coordinates.end, + header: "mapped_trait", + value: (row) => row.mapped_trait + }, + { + header: "Pubmed Id", + value: (row) => row.pubmedid + } - } - 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") && - <> - - - - - } - { value===2 && ebidata && + ]} + tableTitle={`EBI Associations for ${searchParams.get('accession')}:`} + rows={ebidata.ebiAssociationsQuery || []} + + + 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')} /> + } + + {study === 'Corces' && - - 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 || []} - - - itemsPerPage={10} - /> - - + {barplotdata.filter(b => b.grouping === 'Leukemic') && + b.grouping === 'Leukemic')} /> + } + + {barplotdata.filter(b => b.grouping === 'Progenitors') && + b.grouping === 'Progenitors')} /> + } + } - { 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')}/> - } - - { 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 === 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 + : + + {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 0e8f1d5..47fc2cb 100644 --- a/immuscreen/src/common/components/cellTypeTree.tsx +++ b/immuscreen/src/common/components/cellTypeTree.tsx @@ -3,8 +3,8 @@ import { Group } from '@visx/group'; import { Tree, hierarchy } from '@visx/hierarchy'; import { HierarchyPointNode, HierarchyPointLink } from '@visx/hierarchy/lib/types'; import { LinkHorizontal, LinkVertical } from '@visx/shape'; -import { CellTypeInfo, CellTypes } from '../../app/celllineage/page'; import { defaultStyles as defaultTooltipStyles, useTooltip, TooltipWithBounds } from '@visx/tooltip'; +import { CellLineageTreeState, DynamicCellTypeInfo, StaticCellTypeInfo, cellLineageTreeStaticInfo } from '../../app/celllineage/utils'; const linkStroke = '#000000'; const background = 'transparent'; @@ -12,7 +12,7 @@ const fontSize = 12 const fadedCellOpacity = 0.3 -interface CellNode extends CellTypeInfo { +interface CellNode extends StaticCellTypeInfo, DynamicCellTypeInfo { children?: CellNode[]; } @@ -35,18 +35,21 @@ interface TooltipData { type CellTypeTreeProps = { width: number height: number - cellTypeState: CellTypes - setCellTypeState: React.Dispatch> - stimulateMode: boolean - setStimulateMode: React.Dispatch> + cellTypeState: CellLineageTreeState + setCellTypeState?: React.Dispatch> + stimulateMode?: boolean + setStimulateMode?: React.Dispatch> orientation: "vertical" | "horizontal" - selectionLimit?: number - triggerAlert: (message: string) => void + selectionLimit?: number | "none" + triggerAlert?: (message: string) => void } const stimulateCursor = "url(/stimulateCursor.png) 5 30, cell" -export default function CellTypeTree({ width: totalWidth, height: totalHeight, orientation, cellTypeState, setCellTypeState, stimulateMode, setStimulateMode, selectionLimit, triggerAlert }: CellTypeTreeProps) { +/** + * Cell Lineage Tree. Optional Props only optional if the tree isn't interactive (generateCellLineageTreeState called with param #2 set to false). + */ +export default function CellTypeTree({ width: totalWidth, height: totalHeight, orientation, cellTypeState, setCellTypeState, stimulateMode = false, setStimulateMode, selectionLimit = "none", triggerAlert }: CellTypeTreeProps) { const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip, updateTooltip } = useTooltip(); @@ -79,10 +82,10 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o /** * Rotates the stimulation between U/S/B in that order */ - const toggleStimulation = (current: "U" | "S" | "B", numberSelected: number, selectionLimit: number): "U" | "S" | "B" => { + const toggleStimulation = (current: "U" | "S" | "B", numberSelected: number, selectionLimit: number | "none"): "U" | "S" | "B" => { switch (current) { - case ("U"): {if (numberSelected === selectionLimit){return "S"} else {return "S"}} - case ("S"): {if (numberSelected === selectionLimit){return "U"} else {return "B"}} + case ("U"): {if (selectionLimit === "none" || numberSelected === selectionLimit){return "S"} else {return "S"}} + case ("S"): {if (selectionLimit === "none" || numberSelected === selectionLimit){return "U"} else {return "B"}} case ("B"): return "U" } } @@ -91,13 +94,13 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Alt" || event.key === "Meta") { - setStimulateMode(true); + setStimulateMode && setStimulateMode(true); } }; const handleKeyUp = (event: KeyboardEvent) => { if (event.key === "Alt" || event.key === "Meta" || event.key === "Escape") { - setStimulateMode(false); + setStimulateMode && setStimulateMode(false); } }; @@ -116,28 +119,35 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o return ( { ...cellTypeState.HSC, + ...cellLineageTreeStaticInfo.HSC, children: [ { ...cellTypeState.MPP, + ...cellLineageTreeStaticInfo.MPP, children: [ { ...cellTypeState.CMP, + ...cellLineageTreeStaticInfo.CMP, children: [ { ...cellTypeState.GMP, + ...cellLineageTreeStaticInfo.GMP, children: [ // { // displayName: 'Neutrophil', // ...uninteractiveNode, // }, { - ...cellTypeState.pDCs + ...cellTypeState.pDCs, + ...cellLineageTreeStaticInfo.pDCs, }, { - ...cellTypeState.Myeloid_DCs + ...cellTypeState.Myeloid_DCs, + ...cellLineageTreeStaticInfo.Myeloid_DCs, }, { - ...cellTypeState.Monocytes + ...cellTypeState.Monocytes, + ...cellLineageTreeStaticInfo.Monocytes, } ] } @@ -145,17 +155,21 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o }, { ...cellTypeState.MEP, + ...cellLineageTreeStaticInfo.MEP, children: [ { ...cellTypeState.Ery, + ...cellLineageTreeStaticInfo.Ery, } ] }, { - ...cellTypeState.LPMP, + ...cellTypeState.LMPP, + ...cellLineageTreeStaticInfo.LMPP, children: [ { ...cellTypeState.CLP, + ...cellLineageTreeStaticInfo.CLP, children: [ { displayName: 'Double-negative cell', @@ -163,15 +177,19 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o children: [ { ...cellTypeState.Nkcell, + ...cellLineageTreeStaticInfo.Nkcell, children: [ { ...cellTypeState.Immature_NK, + ...cellLineageTreeStaticInfo.Immature_NK, children: [ { ...cellTypeState.Mature_NK, + ...cellLineageTreeStaticInfo.Mature_NK, children: [ { ...cellTypeState.Memory_NK, + ...cellLineageTreeStaticInfo.Memory_NK, } ] } @@ -181,6 +199,7 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o }, { ...cellTypeState.Gamma_delta_T, + ...cellLineageTreeStaticInfo.Gamma_delta_T, }, { displayName: 'CD4 immature/single-positive cell', @@ -192,27 +211,35 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o children: [ { ...cellTypeState.CD4Tcell, + ...cellLineageTreeStaticInfo.CD4Tcell, children: [ { ...cellTypeState.Effector_CD4pos_T, + ...cellLineageTreeStaticInfo.Effector_CD4pos_T, children: [ { ...cellTypeState.Naive_Teffs, + ...cellLineageTreeStaticInfo.Naive_Teffs, children: [ { ...cellTypeState.Memory_Teffs, + ...cellLineageTreeStaticInfo.Memory_Teffs, children: [ { - ...cellTypeState.Th1_precursors + ...cellTypeState.Th1_precursors, + ...cellLineageTreeStaticInfo.Th1_precursors, }, { - ...cellTypeState.Th2_precursors + ...cellTypeState.Th2_precursors, + ...cellLineageTreeStaticInfo.Th2_precursors, }, { - ...cellTypeState.Th17_precursors + ...cellTypeState.Th17_precursors, + ...cellLineageTreeStaticInfo.Th17_precursors, }, { - ...cellTypeState.Follicular_T_Helper + ...cellTypeState.Follicular_T_Helper, + ...cellLineageTreeStaticInfo.Follicular_T_Helper, } ] } @@ -222,12 +249,15 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o }, { ...cellTypeState.Regulatory_T, + ...cellLineageTreeStaticInfo.Regulatory_T, children: [ { ...cellTypeState.Naive_Tregs, + ...cellLineageTreeStaticInfo.Naive_Tregs, children: [ { - ...cellTypeState.Memory_Tregs + ...cellTypeState.Memory_Tregs, + ...cellLineageTreeStaticInfo.Memory_Tregs, } ] } @@ -237,15 +267,19 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o }, { ...cellTypeState.CD8pos_T, + ...cellLineageTreeStaticInfo.CD8pos_T, children: [ { ...cellTypeState.Naive_CD8_T, + ...cellLineageTreeStaticInfo.Naive_CD8_T, children: [ { - ...cellTypeState.Central_memory_CD8pos_T + ...cellTypeState.Central_memory_CD8pos_T, + ...cellLineageTreeStaticInfo.Central_memory_CD8pos_T, }, { - ...cellTypeState.Effector_memory_CD8pos_T + ...cellTypeState.Effector_memory_CD8pos_T, + ...cellLineageTreeStaticInfo.Effector_memory_CD8pos_T, } ] } @@ -259,15 +293,19 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o }, { ...cellTypeState.Bulk_B, + ...cellLineageTreeStaticInfo.Bulk_B, children: [ { ...cellTypeState.Naive_B, + ...cellLineageTreeStaticInfo.Naive_B, children: [ { ...cellTypeState.Mem_B, + ...cellLineageTreeStaticInfo.Mem_B, children: [ { - ...cellTypeState.Plasmablasts + ...cellTypeState.Plasmablasts, + ...cellLineageTreeStaticInfo.Plasmablasts, } ] } @@ -326,13 +364,13 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o cellType.selected === false)) ? 1 : fadedCellOpacity} onClick={() => { - const numberSelected = Object.values(cellTypeState).reduce((count, cellInfo: CellTypeInfo) => cellInfo.selected ? cellInfo.stimulated === "B" ? count + 2 : count + 1 : count, 0) + const numberSelected = Object.values(cellTypeState).reduce((count, cellInfo: DynamicCellTypeInfo) => cellInfo.selected ? cellInfo.stimulated === "B" ? count + 2 : count + 1 : count, 0) if (stimulateMode) { if (node.data.stimulable) { - setCellTypeState({ + setCellTypeState && setCellTypeState({ ...cellTypeState, [node.data.id]: { ...cellTypeState[node.data.id], @@ -342,13 +380,13 @@ export default function CellTypeTree({ width: totalWidth, height: totalHeight, o } } else if (node.data.selectable) { //If there is room for selection, select it. Or always allow deselection - if (((node.data.stimulated === "B" ? numberSelected + 2 : numberSelected + 1) <= selectionLimit) || node.data.selected) { - setCellTypeState({ + if (selectionLimit === "none" || ((node.data.stimulated === "B" ? numberSelected + 2 : numberSelected + 1) <= selectionLimit) || node.data.selected) { + setCellTypeState && setCellTypeState({ ...cellTypeState, [node.data.id]: { ...cellTypeState[node.data.id], selected: !cellTypeState[node.data.id].selected } }) - } else triggerAlert("Maximum cell selection reached (6)") - } + } else triggerAlert && triggerAlert("Maximum cell selection reached (6)") + } }} onMouseEnter={ (event: React.MouseEvent) => {