Skip to content

Commit

Permalink
Add Experiment Info (#9)
Browse files Browse the repository at this point in the history
* Initial Cleanup

* Refactor static cell info

* Changes to fix ATAC bar plot

* Revert back to manually breaking displayNames

* Fix build errors

* Add Active Exps

* Sort, better padding

* Map key, Initial cleanup

* Add optional stimulation to getDisplayName
  • Loading branch information
jpfisher72 authored Apr 22, 2024
1 parent 5d22ac7 commit c217072
Show file tree
Hide file tree
Showing 18 changed files with 1,612 additions and 1,211 deletions.
2 changes: 1 addition & 1 deletion immuscreen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
"@types/react": "^18.2.19",
"@types/react-dom": "18.2.7",
"@visx/axis": "^3.8.0",
"@visx/glyph": "latest",
"@visx/event": "latest",
"@visx/glyph": "latest",
"@visx/gradient": "^3.3.0",
"@visx/grid": "^3.5.0",
"@visx/group": "^3.3.0",
Expand Down
28 changes: 15 additions & 13 deletions immuscreen/src/app/celllineage/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ 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";
import { downloadSVG, extractQueryValues, generateCellLineageTreeState } from "./utils";
import { CellQueryValue, CellLineageTreeState, CellName, DynamicCellTypeInfo } from "./types";
import { cellTypeStaticInfo } from "../../common/consts";


type QueryGroup = {
intersect?: string[][],
exclude?: string[][],
union?: string[],
intersect?: CellQueryValue[][],
exclude?: CellQueryValue[][],
union?: CellQueryValue[],
name: string
}

Expand Down Expand Up @@ -101,7 +103,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]) => cellLineageTreeStaticInfo[key].stimulable ? [key, {...value, stimulated: mode}] : [key, value])) as CellLineageTreeState
const newState: CellLineageTreeState = Object.fromEntries(Object.entries(cellTypeState).map(([key, value]: [CellName, DynamicCellTypeInfo]) => cellTypeStaticInfo[key].stimulable ? [key, {...value, stimulated: mode}] : [key, value])) as CellLineageTreeState
setCellTypeState(newState)
}
}
Expand Down Expand Up @@ -197,23 +199,23 @@ export default function UpSet() {
*/
const generateQuery = useCallback((selectedCellsState: Partial<CellLineageTreeState>, classes: CCRE_CLASS[]) => {
//stores extracted relevant information from selectedCells for easier access
let cells: { displayName: string, queryVals: string[] }[] = [];
let cells: { displayName: string, queryVals: CellQueryValue[] }[] = [];

//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(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) })
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) })
})

//Holds the combination of union/intersection/exlude and name for each query
let queryGroups: QueryGroup[] = []

//Union of all cells
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' })
queryGroups.push({ union: Object.entries(selectedCellsState).map(([key, value]: [CellName, DynamicCellTypeInfo]) => extractQueryValues(cellTypeStaticInfo[key], value.stimulated)).flat(2), name: 'Union_All' })
}

//Individual counts
Expand Down Expand Up @@ -425,11 +427,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]) => cellLineageTreeStaticInfo[key].stimulable ? value.stimulated === "U" : true)
.every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellTypeStaticInfo[key].stimulable ? value.stimulated === "U" : true)
const allStimulated = Object.entries(cellTypeState)
.every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellLineageTreeStaticInfo[key].stimulable ? value.stimulated === "S": true)
.every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellTypeStaticInfo[key].stimulable ? value.stimulated === "S": true)
const allBothStimulated = Object.entries(cellTypeState)
.every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellLineageTreeStaticInfo[key].stimulable ? value.stimulated === "B" : true)
.every(([key, value]: [CellName, DynamicCellTypeInfo]) => cellTypeStaticInfo[key].stimulable ? value.stimulated === "B" : true)

const groupCheckbox = (group: CCRE_CLASS, key: number) => {
return (
Expand Down
173 changes: 173 additions & 0 deletions immuscreen/src/app/celllineage/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
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"
| "pHSC"
| "LSC"
| "Blast"

export type CellDisplayName =
"Monocyte"
| "Myeloid dendritic cell"
| "Plasmacytoid dendritic cell"
| "Bulk B cell"
| "Naïve B cell"
| "Memory B cell"
| "Plasmablast"
| "Regulatory CD4+ T cell"
| "Naïve T regulatory cell"
| "Memory T regulatory cell"
| "Effector CD4+ T cell"
| "Naïve T effector cell"
| "Memory T effector cell"
| "Th1 precursor"
| "Th2 precursor"
| "Th17 precursor"
| "T follicular helper cell"
| "CD8+ T cell"
| "Naïve CD8+ T cell"
| "Central memory CD8+ T cell"
| "Effector memory CD8+ T cell"
| "Gamma-delta T cell"
| "Immature NK cell"
| "Mature NK cell"
| "Memory NK cell"
| "Hematopoetic stem cell"
| "Multipotent progenitor"
| "Common myeloid progenitor"
| "Megakaryocyte-erythroid progenitor"
| "Erythrocyte"
| "Granulocyte-monocyte progenitors"
| "Lymphocyte-primed multipotent progenitor"
| "Common lymphoid progenitor"
| "CD4+ T cell"
| "NK cell"
| "Preleukemic Hematopoetic Stem Cells"
| "Leukemia Stem Cells"
| "Leukemic Blast Cells";

export type CellQueryValue =
"Myeloid_DCs-U"
| "pDCs-U"
| "Naive_B-U"
| "Naive_B-S"
| "Mem_B-U"
| "Mem_B-S"
| "Plasmablasts-U"
| "Regulatory_T-U"
| "Regulatory_T-S"
| "Naive_Tregs-U"
| "Naive_Tregs-S"
| "Memory_Tregs-U"
| "Memory_Tregs-S"
| "Effector_CD4pos_T-U"
| "Effector_CD4pos_T-S"
| "Naive_Teffs-U"
| "Naive_Teffs-S"
| "Memory_Teffs-U"
| "Memory_Teffs-S"
| "Th1_precursors-U"
| "Th1_precursors-S"
| "Th2_precursors-U"
| "Th2_precursors-S"
| "Th17_precursors-U"
| "Th17_precursors-S"
| "Follicular_T_Helper-U"
| "Follicular_T_Helper-S"
| "Naive_CD8_T-U"
| "Naive_CD8_T-S"
| "Central_memory_CD8pos_T-U"
| "Central_memory_CD8pos_T-S"
| "Effector_memory_CD8pos_T-U"
| "Effector_memory_CD8pos_T-S"
| "Gamma_delta_T-U"
| "Gamma_delta_T-S"
| "Immature_NK-U"
| "Mature_NK-U"
| "Mature_NK-S"
| "Memory_NK-U"
| "HSC"
| "CD34_Cord_Blood"
| "CD34_Bone_Marrow"
| "MPP"
| "CMP"
| "MEP"
| "Ery"
| "GMP"
| "LMPP"
| "CLP"
| "CD4Tcell"
| "NKcell"
| "Monocytes-U"
| "Mono"
| "Monocytes-S"
| "Bulk_B-U"
| "Bcell"
| "Bulk_B-S"
| "CD8pos_T-U"
| "CD8Tcell"
| "CD8pos_T-S"
| "pHSC"
| "LSC"
| "Blast"

// Static information for each cell
export type CellTypeStaticInfo = {
readonly id: CellName;
readonly displayName: CellDisplayName;
/**
* Display name used in the Cell Lineage Tree. Insert '/' to break name onto multiple lines
*/
readonly treeDisplayName: string;
readonly unstimImagePath: string;
readonly stimImagePath?: string;
readonly unstimCount: number
readonly stimCount?: number
readonly stimulable: boolean;
readonly queryValues?: {
readonly unstimulated: { Calderon?: CellQueryValue | CellQueryValue[], Corces?: CellQueryValue | CellQueryValue[] };
readonly stimulated?: { Calderon: CellQueryValue | CellQueryValue[] }
}
readonly color: 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 }
118 changes: 118 additions & 0 deletions immuscreen/src/app/celllineage/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { cellTypeStaticInfo } from "../../common/consts";
import { CellDisplayName, CellLineageTreeState, CellName, CellQueryValue, CellTypeStaticInfo } from "./types";

export const getCellColor = (cell: CellName | CellQueryValue | CellDisplayName): string => {
return Object.values(cellTypeStaticInfo).find((x: CellTypeStaticInfo) => x.id === cell || x.displayName === cell || extractQueryValues(x, "B").includes(cell as CellQueryValue))?.color ?? "#000000"
}

//In some situations, want to be able to display "stimulated" after. In bar plot, the output is used to get Cell color which would break that
//In these cases, would be extracting from cell query value where that info exists
export const getCellDisplayName = (cell: CellName | CellQueryValue, appendStim = false): string => {
let name = Object.values(cellTypeStaticInfo).find((x: CellTypeStaticInfo) => x.id === cell || extractQueryValues(x, "B").includes(cell as CellQueryValue))?.displayName ?? cell
if (name === cell) console.log("Unable to find display name for " + cell)
if (appendStim && cell.slice(-2) == "-S") name += " (stimulated)"
return name
}

/**
*
* @param cell CellTypeInfo
* @param want "S" | "U" | "B" The query value(s) wanted
* @returns array of query values
*/
export const extractQueryValues = (cell: CellTypeStaticInfo, want: "S" | "U" | "B"): (CellQueryValue[]) => {
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 (cell.queryValues?.unstimulated ? Object.values(cell.queryValues.unstimulated).flat() : []).concat(cell.queryValues?.stimulated ? (Object.values(cell.queryValues.stimulated).flat()) : [])
}
}

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"
]

/**
* 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: CellQueryValue[], 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(cellTypeStaticInfo[cellName], "U").includes(cell))
const stimSelected = initialSelectedCells.some(cell => extractQueryValues(cellTypeStaticInfo[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 = '<?xml version="1.0" standalone="no"?>';
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<SVGSVGElement>, filename: string) => {
ref.current && downloadData(svgData(ref.current!), filename, 'image/svg;charset=utf-8');
}
Loading

0 comments on commit c217072

Please sign in to comment.