Skip to content

Commit

Permalink
Modify BiosampleTables (#455)
Browse files Browse the repository at this point in the history
* Improve assay wheels, cleanup

* Add param to only fetch certain assays

* Update DataMatrices
  • Loading branch information
jpfisher72 authored Oct 1, 2024
1 parent 90d87a3 commit b395428
Show file tree
Hide file tree
Showing 9 changed files with 1,380 additions and 1,311 deletions.
172 changes: 110 additions & 62 deletions screen2.0/src/app/_biosampleTables/BiosampleTables.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Tooltip, Typography, AccordionSummary, AccordionDetails, TextField, CircularProgress, FormControlLabel, Accordion, FormGroup, Checkbox, IconButton, Menu, MenuItem, InputAdornment, FormControl, FormLabel, Paper, Stack } from "@mui/material"
import { DataTable, DataTableColumn } from "@weng-lab/psychscreen-ui-components"
import { useCallback, useMemo, useState } from "react"
import { CheckboxState, FiltersKey, Props, RegistryBiosample, RegistryBiosamplePlusRNA } from "./types"
import { assay, CheckboxState, FiltersKey, Props, RegistryBiosample, RegistryBiosamplePlusRNA } from "./types"
import KeyboardArrowRightIcon from "@mui/icons-material/KeyboardArrowRight"
import { Check, Close, FilterList } from "@mui/icons-material"
import { Check, Close, FilterList, Launch } from "@mui/icons-material"
import SearchIcon from '@mui/icons-material/Search';
import { useQuery } from "@apollo/client"
import { assayHoverInfo, DownloadBiosamplecCREsButton, filterBiosamples } from "./helpers"
import { assayColors, assayHoverInfo, DownloadBiosamplecCREsButton, filterBiosamples } from "./helpers"
import { BIOSAMPLE_QUERY, RNA_SEQ_QUERY } from "./queries"

const checkboxLabels: { [key in FiltersKey]: string } = {
Expand All @@ -27,6 +27,7 @@ export const BiosampleTables = <T extends boolean = false>({
selected,
onBiosampleClicked,
preFilterBiosamples,
fetchBiosamplesWith = ["dnase", "h3k4me3", "h3k27ac", "ctcf", "atac"],
showRNAseq,
showDownloads,
slotProps
Expand Down Expand Up @@ -60,7 +61,8 @@ export const BiosampleTables = <T extends boolean = false>({
BIOSAMPLE_QUERY,
{
variables: {
assembly: assembly.toLowerCase() as "grch38" | "mm10"
assembly: assembly.toLowerCase() as "grch38" | "mm10",
assays: fetchBiosamplesWith
}
}
)
Expand Down Expand Up @@ -134,67 +136,113 @@ export const BiosampleTables = <T extends boolean = false>({
},
{
header: "Assays",
//number of assays available
value: (row) => +!!row.dnase + +!!row.atac + +!!row.ctcf + +!!row.h3k27ac + +!!row.h3k4me3,
render: (row) => {
const fifthOfCircle = (2 * 3.1416 * 10) / 5
FunctionalRender: (row: RegistryBiosamplePlusRNA) => {
const [hoveredAssay, setHoveredAssay] = useState<assay>(null)

//Constants used for sizing svg elements
const svgHeight = 50
const svgWidth = 50
const radius = 10
const radiusHovered = 12.5 //If assay is hovered, bump up the radius to create the "poking out" effect
const fifth = (2 * Math.PI * radius) / 5
const fifthHovered = (2 * Math.PI * radiusHovered) / 5

const assays: { id: assay, expID: string, color: string, dashArray: string, radius: number }[] = useMemo(() => {
return [
{
id: "DNase",
expID: row.dnase, //Used to provide link to ENCODE for that experiment
color: row.dnase ? assayColors.DNase : "transparent", //Only color slice if the biosample has data in that assay
dashArray: hoveredAssay === "DNase" ? `${fifthHovered} ${fifthHovered * 4}` : `${fifth} ${fifth * 4}`, //Use dasharray to create a single slice of 1/5th of the circle.
radius: hoveredAssay === "DNase" ? radiusHovered : radius
},
{
id: "H3K27ac",
expID: row.h3k27ac,
color: row.h3k27ac ? assayColors.H3K27ac : "transparent",
dashArray: hoveredAssay === "H3K27ac" ? `0 ${fifthHovered} ${fifthHovered} ${fifthHovered * 3}` : `0 ${fifth} ${fifth} ${fifth * 3}`,
radius: hoveredAssay === "H3K27ac" ? radiusHovered : radius
},
{
id: "H3K4me3",
expID: row.h3k4me3,
color: row.h3k4me3 ? assayColors.H3K4me3 : "transparent",
dashArray: hoveredAssay === "H3K4me3" ? `0 ${fifthHovered * 2} ${fifthHovered} ${fifthHovered * 2}` : `0 ${fifth * 2} ${fifth} ${fifth * 2}`,
radius: hoveredAssay === "H3K4me3" ? radiusHovered : radius
},
{
id: "CTCF",
expID: row.ctcf,
color: row.ctcf ? assayColors.CTCF : "transparent",
dashArray: hoveredAssay === "CTCF" ? `0 ${fifthHovered * 3} ${fifthHovered} ${fifthHovered * 1}` : `0 ${fifth * 3} ${fifth} ${fifth * 1}`,
radius: hoveredAssay === "CTCF" ? radiusHovered : radius
},
{
id: "ATAC",
expID: row.atac,
color: row.atac ? assayColors.ATAC : "transparent",
dashArray: hoveredAssay === "ATAC" ? `0 ${fifthHovered * 4} ${fifthHovered}` : `0 ${fifth * 4} ${fifth}`,
radius: hoveredAssay === "ATAC" ? radiusHovered : radius
},
];
}, [row.dnase, row.h3k27ac, row.h3k4me3, row.ctcf, row.atac, hoveredAssay, fifthHovered, fifth])

return (
<Tooltip
title={assayHoverInfo({
dnase: !!row.dnase,
atac: !!row.atac,
ctcf: !!row.ctcf,
h3k27ac: !!row.h3k27ac,
h3k4me3: !!row.h3k4me3
})}
arrow>
<svg height="50" width="50" viewBox="0 0 50 50">
<circle r="20.125" cx="25" cy="25" fill="#EEEEEE" stroke="black" strokeWidth="0.25" />
<circle
r="10"
cx="25"
cy="25"
fill="transparent"
stroke={`${row.dnase ? "#06DA93" : "transparent"}`}
strokeWidth="20"
strokeDasharray={`${fifthOfCircle} ${fifthOfCircle * 4}`}
/>
<circle
r="10"
cx="25"
cy="25"
fill="transparent"
stroke={`${row.h3k27ac ? "#FFCD00" : "transparent"}`}
strokeWidth="20"
strokeDasharray={`${fifthOfCircle * 0} ${fifthOfCircle} ${fifthOfCircle} ${fifthOfCircle * 3}`}
/>
<circle
r="10"
cx="25"
cy="25"
fill="transparent"
stroke={`${row.h3k4me3 ? "#FF0000" : "transparent"}`}
strokeWidth="20"
strokeDasharray={`${fifthOfCircle * 0} ${fifthOfCircle * 2} ${fifthOfCircle} ${fifthOfCircle * 2}`}
/>
<circle
r="10"
cx="25"
cy="25"
fill="transparent"
stroke={`${row.ctcf ? "#00B0F0" : "transparent"}`}
strokeWidth="20"
strokeDasharray={`${fifthOfCircle * 0} ${fifthOfCircle * 3} ${fifthOfCircle} ${fifthOfCircle * 1}`}
/>
<circle
r="10"
cx="25"
cy="25"
fill="transparent"
stroke={`${row.atac ? "#02C7B9" : "transparent"}`}
strokeWidth="20"
strokeDasharray={`${fifthOfCircle * 0} ${fifthOfCircle * 4} ${fifthOfCircle}`}
/>
title={
<Stack spacing={1}>
<Typography variant="body2">
{assayHoverInfo({
dnase: !!row.dnase,
atac: !!row.atac,
ctcf: !!row.ctcf,
h3k27ac: !!row.h3k27ac,
h3k4me3: !!row.h3k4me3
})}
</Typography>
{hoveredAssay && <>
<Typography variant="body2">Click to view {hoveredAssay} experiment:</Typography>
<Stack direction="row" alignItems={"baseline"}>
<Typography variant="body2">{row[hoveredAssay.toLowerCase()]}</Typography>
<Launch fontSize="inherit" sx={{ ml: 0.5 }} />
</Stack>
</>}
</Stack>
}
arrow
placement="right"
>
<svg height={svgHeight} width={svgWidth} viewBox={`0 0 ${svgWidth} ${svgHeight}`} >
{/* Provides outline */}
<circle r={2 * radius + 0.125} cx={svgWidth / 2} cy={svgHeight / 2} fill="#EEEEEE" stroke="black" strokeWidth={0.25} />
{assays.map((assay) => (
assay.expID &&
<a
key={assay.id}
href={`https://www.encodeproject.org/experiments/${assay.expID}/`}
target="_blank"
rel="noopener noreferrer"
style={{ pointerEvents: 'none' }} // Prevents anchor from interfering with mouse events
>
<circle
cursor={"pointer"}
pointerEvents={"auto"}
r={assay.radius}
cx={svgWidth / 2}
cy={svgHeight / 2}
fill="transparent"
stroke={assay.color}
strokeWidth={hoveredAssay === assay.id ? 2 * radiusHovered : 2 * radius}
strokeDasharray={assay.dashArray}
onMouseEnter={() => setHoveredAssay(assay.id)}
onMouseLeave={() => setHoveredAssay(null)}
onClick={(event) => event.stopPropagation()}
/>
</a>
))}
{/* Provides dead zone in middle to prevent ATAC wheel from capturing mouse events in center due to it being topmost element */}
<circle r={radius} cx={svgWidth / 2} cy={svgHeight / 2} fill="white" stroke="black" strokeWidth={0.25} />
</svg>
</Tooltip>
)
Expand Down
10 changes: 9 additions & 1 deletion screen2.0/src/app/_biosampleTables/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
import { useState, useEffect } from "react"
import { Close, Download } from "@mui/icons-material"
import { CircularProgressProps, Box, CircularProgress, Typography, IconButton } from "@mui/material"
import { RegistryBiosample, RegistryBiosamplePlusRNA, CheckboxState } from "./types"
import { RegistryBiosample, RegistryBiosamplePlusRNA, CheckboxState, assay } from "./types"

export const assayColors: {[key in assay]: string} = {
DNase: "#06DA93",
H3K27ac: "#FFCD00",
H3K4me3: "#FF0000",
CTCF: "#00B0F0",
ATAC: "#02c7b9"
}

/**
*
Expand Down
4 changes: 2 additions & 2 deletions screen2.0/src/app/_biosampleTables/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export const RNA_SEQ_QUERY = gql(`
`)

export const BIOSAMPLE_QUERY = gql(`
query biosamples_1($assembly: String!) {
ccREBiosampleQuery(assembly: $assembly) {
query biosamples_1($assembly: String!, $assays: [String!]) {
ccREBiosampleQuery(assembly: $assembly, assay: $assays) {
biosamples {
name
ontology
Expand Down
20 changes: 16 additions & 4 deletions screen2.0/src/app/_biosampleTables/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,19 @@ export interface Props<T extends boolean = false> {
* If specified, samples will be passed through this function before populating tables
*/
preFilterBiosamples?: (sample: BiosampleData<T>) => boolean,

//Should I change this? Seems like so-so way to handle this behavior
showRNAseq?: T, //I feel like this is fine
/**
* If specified, component will only fetch biosamples which include data for any of specified assays.
* More complex filtering can be done with preFilterBiosamples
* @default ["dnase","h3k4me3","h3k27ac","ctcf","atac"]
*/
fetchBiosamplesWith?: ("dnase" | "h3k4me3"| "h3k27ac" | "ctcf" | "atac")[]
/**
* If true, table will display column with check marks for biosamples with RNA seq data.
*/
showRNAseq?: T,
/**
* If true, table will display columns for assay signal files for each biosample
*/
showDownloads?: boolean, //I feel like this is maybe more appropriate to be something that is user-defined. Allow them to add extra columns?
/**
* Props spread into each slot inside, helpful for changing things such as width and height
Expand Down Expand Up @@ -87,4 +97,6 @@ export type RegistryBiosample = {

export type FiltersKey = "CellLine" | "PrimaryCell" | "Tissue" | "Organoid" | "InVitro" | "Core" | "Partial" | "Ancillary" | "Embryo" | "Adult"

export type CheckboxState = { [key in FiltersKey]: boolean }
export type CheckboxState = { [key in FiltersKey]: boolean }

export type assay = "DNase" | "H3K27ac" | "H3K4me3" | "CTCF" | "ATAC"
2 changes: 1 addition & 1 deletion screen2.0/src/app/applets/gwas/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ export default function GWAS() {
}
<BiosampleTables
assembly={"GRCh38"}
preFilterBiosamples={(sample: RegistryBiosamplePlusRNA) => sample.dnase !== null}
fetchBiosamplesWith={['dnase']}
selected={selectedSample?.name}
onBiosampleClicked={handleSetSelectedSample}
slotProps={{paperStack: {elevation: 0}, headerStack: {mt: 1}}}
Expand Down
2 changes: 1 addition & 1 deletion screen2.0/src/app/downloads/datamatrices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ export function DataMatrices() {
)}
<BiosampleTables
assembly={selectedAssay?.assembly === "Human" ? "GRCh38" : "mm10"}
preFilterBiosamples={(sample: RegistryBiosamplePlusRNA) => sample[selectedAssay.assay.toLowerCase()] !== null}
fetchBiosamplesWith={[selectedAssay.assay.toLowerCase() as ("dnase" | "h3k4me3" | "h3k27ac" | "ctcf")]}
onBiosampleClicked={handleSetSelectedSample}
slotProps={{
paperStack: { overflow: 'hidden', flexGrow: 1 }
Expand Down
4 changes: 2 additions & 2 deletions screen2.0/src/graphql/__generated__/gql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b395428

Please sign in to comment.