Skip to content

Commit

Permalink
Gene expression app & RAMPAGE tab (#76)
Browse files Browse the repository at this point in the history
* initial upload, added fetch, buttons, title

* added types for gene exp

* added expression plot, fixed buttons, and added gene autocomplete

* fixed gene autocomplete

* added rampage tab and adjusted spacing

* fixed scaling and adjusted dimensions

* changed tab name

* added sort, zero switch, and transcript select

* formatted numbers

* spacing, prettier

* change rampage tab name

* added dropdowns to graph, added drawer, and fixed scaling

* added settings

* replaced details tab with hamburger

* updated error logger

* hyperlinked genes in main table

* added gene expression tab to details

* upated types

* added collapse all, removed outline, and fixed scaling

* added toolbar, added url nav, fixed search, and added collapse all

* added header and updated descriptions

* adjusted buttons / spacing, fixed plot scaling, and added collapse all

* prettier

* fixed link pathname and error utility

* added keys, cleaned types

* fixed hamburger height and width, cleaned types

* changed error util

* changed default export for build

* moved gene-expression tab

* fixed default export

* fixed error handler

* rerun checks

* changed header color

* added theme

* fixed collapse all, changed theme

* added all track colors

* updated theme

* updated theme

* updated theme

* cleaned up code, moved utils

* replaced autocomplete with select

* cleaned up

* updated collapse all
  • Loading branch information
jordanvelezbomba authored Jul 26, 2023
1 parent 5596eaf commit d268284
Show file tree
Hide file tree
Showing 24 changed files with 2,219 additions and 317 deletions.
34 changes: 17 additions & 17 deletions screen2.0/src/app/applets/differential-gene-expression/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default function DifferentialGeneExpression() {
if (!response.ok) {
// throw new Error(response.statusText)
setError(true)
return ErrorMessage(new Error(response.statusText))
return <ErrorMessage error={new Error(response.statusText)} />
}
return response.json()
})
Expand All @@ -99,7 +99,7 @@ export default function DifferentialGeneExpression() {
.catch((error: Error) => {
// logging
// throw error
return ErrorMessage(error)
return <ErrorMessage error={error} />
})
setLoading(true)
}, [])
Expand All @@ -124,7 +124,7 @@ export default function DifferentialGeneExpression() {
if (!response.ok) {
// throw new Error(response.statusText)
setError(true)
return ErrorMessage(new Error(response.statusText))
return <ErrorMessage error={new Error(response.statusText)} />
}
return response.json()
})
Expand Down Expand Up @@ -170,7 +170,7 @@ export default function DifferentialGeneExpression() {
.catch((error: Error) => {
// logging
// throw error
return ErrorMessage(error)
return <ErrorMessage error={error} />
})
setLoadingChart(true)
}, [ct1, ct2, gene])
Expand Down Expand Up @@ -243,7 +243,7 @@ export default function DifferentialGeneExpression() {
// const cellTypes2 = await getCellTypes()

return loading ? (
LoadingMessage()
<LoadingMessage />
) : (
<main>
<Grid2 container spacing={3} sx={{ mt: "2rem" }}>
Expand Down Expand Up @@ -291,9 +291,9 @@ export default function DifferentialGeneExpression() {
<Grid2 xs={9}>
<Box mb={1}>
{errorLoading
? ErrorMessage(new Error("Error loading data"))
? <ErrorMessage error={new Error("Error loading")} />
: loadingChart
? LoadingMessage()
? <LoadingMessage />
: data &&
data.gene &&
data[data.gene] &&
Expand Down Expand Up @@ -429,7 +429,7 @@ export default function DifferentialGeneExpression() {
})
setSlider([dr1, range.x.end + 1200000])
setMin(dr1)
} else ErrorMessage(new Error("invalid range"))
} else return <ErrorMessage error={(new Error("invalid range"))} />
}
}}
/>
Expand Down Expand Up @@ -459,7 +459,7 @@ export default function DifferentialGeneExpression() {
})
setSlider([range.x.start - 1200000, dr2])
setMax(dr2)
} else ErrorMessage(new Error("invalid range"))
} else return <ErrorMessage error={(new Error("invalid range"))} />
}
}}
/>
Expand Down Expand Up @@ -587,24 +587,24 @@ export default function DifferentialGeneExpression() {
{!toggleFC ? (
<></>
) : (
data[data.gene].nearbyDEs.data.map((point, i: number) =>
BarPoint(point, i, range, dimensions)
data[data.gene].nearbyDEs.data.map(
(point, i: number) => BarPoint(point, i, range, dimensions)
// <BarPoint point={point} i={i} range={range} dimensions={dimensions} />
)
)}
{!toggleccres ? (
<></>
) : (
data[data.gene].diffCREs.data.map((point, i: number) =>
Point(point, i, range, dimensions)
data[data.gene].diffCREs.data.map(
(point, i: number) => Point(point, i, range, dimensions)
// <Point point={point} i={i} range={range} dimensions={dimensions} />
)
)}
{!toggleGenes ? (
<></>
) : (
data[data.gene].nearbyDEs.genes.map((point, i: number) =>
GenePoint(point, i, range, dimensions, toggleGenes)
data[data.gene].nearbyDEs.genes.map(
(point, i: number) => GenePoint(point, i, range, dimensions, toggleGenes)
// <GenePoint point={point} i={i} range={range} dimensions={dimensions} toggleGenes={toggleGenes} />
)
)}
Expand Down Expand Up @@ -674,8 +674,8 @@ export default function DifferentialGeneExpression() {
</text>
</g>
<g className="data">
{data[data.gene].nearbyDEs.genes.map((point, i: number) =>
GenePoint(point, i, range, dimensions, false)
{data[data.gene].nearbyDEs.genes.map(
(point, i: number) => GenePoint(point, i, range, dimensions, false)
// <GenePoint point={point} i={i} range={range} dimensions={dimensions} toggleGenes={false} />
)}
</g>
Expand Down
177 changes: 177 additions & 0 deletions screen2.0/src/app/applets/gene-expression/gene-autocomplete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
"use client"
import React, { useState, useEffect, useCallback } from "react"
import { useRouter } from "next/navigation"

import { Autocomplete, TextField, Box, Button, debounce, Typography } from "@mui/material"

import Grid2 from "@mui/material/Unstable_Grid2/Grid2"
import { gene } from "./types"
import { QueryResponse } from "../../../../types/types"
import { Dispatch, SetStateAction } from "react"

const GENE_AUTOCOMPLETE_QUERY = `
query ($assembly: String!, $name_prefix: [String!], $limit: Int) {
gene(assembly: $assembly, name_prefix: $name_prefix, limit: $limit) {
name
id
coordinates {
start
chromosome
end
}
}
}
`

export default function GeneAutoComplete(props: {
assembly: string
gene: string
pathname: string
setGene: Dispatch<SetStateAction<string>>
}) {
const router = useRouter()

const [options, setOptions] = useState<string[]>([])
const [geneDesc, setgeneDesc] = useState<{ name: string; desc: string }[]>()
const [geneList, setGeneList] = useState<gene[]>([])
const [geneID, setGeneID] = useState<string>(props.gene ? props.gene : "OR51AB1P")
const [assembly, setAssembly] = useState<string>(props.assembly)
// const [current_gene, setGene] = useState<string>(props.gene ? props.gene : "OR51AB1P")

// gene descriptions
useEffect(() => {
const fetchData = async () => {
let f = await Promise.all(
options.map((gene) =>
fetch("https://clinicaltables.nlm.nih.gov/api/ncbi_genes/v3/search?authenticity_token=&terms=" + gene.toUpperCase())
.then((x) => x && x.json())
.then((x) => {
const matches = (x as QueryResponse)[3] && (x as QueryResponse)[3].filter((x) => x[3] === gene.toUpperCase())
return {
desc: matches && matches.length >= 1 ? matches[0][4] : "(no description available)",
name: gene,
}
})
.catch(() => {
return { desc: "(no description available)", name: gene }
})
)
)
setgeneDesc(f)
}

options && fetchData()
}, [options])

// gene list
const onSearchChange = async (value: string) => {
setOptions([])
const response = await fetch("https://ga.staging.wenglab.org/graphql", {
method: "POST",
body: JSON.stringify({
query: GENE_AUTOCOMPLETE_QUERY,
variables: {
assembly: props.assembly,
name_prefix: value,
limit: 100,
},
}),
headers: { "Content-Type": "application/json" },
})
const genesSuggestion = (await response.json()).data?.gene
if (genesSuggestion && genesSuggestion.length > 0) {
const r = genesSuggestion.map((g) => g.name)
const g = genesSuggestion.map((g) => {
return {
chrom: g.coordinates.chromosome,
start: g.coordinates.start,
end: g.coordinates.end,
id: g.id,
name: g.name,
}
})
setOptions(r)
setGeneList(g)
} else if (genesSuggestion && genesSuggestion.length === 0) {
setOptions([])
setGeneList([])
}
}

// delay fetch
const debounceFn = useCallback(debounce(onSearchChange, 500), [])

return (
<Box>
<Autocomplete
disablePortal
freeSolo={true}
id="gene-ids"
noOptionsText="e.g. Gm25142"
options={options}
size="small"
sx={{ width: 200 }}
ListboxProps={{
style: {
maxHeight: "120px",
},
}}
onChange={(event: React.ChangeEvent<HTMLInputElement>, value: string) => {
if (value != "") debounceFn(value)
setGeneID(value)
}}
onInputChange={(event: React.ChangeEvent<HTMLInputElement>, value: string) => {
if (value != "") debounceFn(value)
setGeneID(value)
}}
onKeyDown={(e) => {
if (e.key == "Enter") {
for (let g of geneList) {
if (g.name === geneID && g.end - g.start > 0) {
props.setGene(g.name)
// replace url if ge applet
if (props.pathname.split("/").includes("gene-expression")) router.replace(props.pathname + "?gene=" + g.name)
break
}
}
}
}}
renderInput={(props) => <TextField {...props} label={geneID} />}
renderOption={(props, opt) => {
return (
<li {...props} key={props.id}>
<Grid2 container alignItems="center">
<Grid2 sx={{ width: "calc(100% - 44px)" }}>
<Box component="span" sx={{ fontWeight: "regular" }}>
{opt}
</Box>
{geneDesc && geneDesc.find((g) => g.name === opt) && (
<Typography variant="body2" color="text.secondary">
{geneDesc.find((g) => g.name === opt)?.desc}
</Typography>
)}
</Grid2>
</Grid2>
</li>
)
}}
/>
<Button
variant="text"
onClick={() => {
for (let g of geneList) {
if (g.name === geneID && g.end - g.start > 0) {
props.setGene(g.name)
// replace url if ge applet
if (props.pathname.split("/").includes("gene-expression")) router.replace(props.pathname + "?gene=" + g.name)
break
}
}
}}
color="primary"
>
Search
</Button>
</Box>
)
}
Loading

0 comments on commit d268284

Please sign in to comment.