From d922f41db60b5627fa54cc2077f602ebee1e7441 Mon Sep 17 00:00:00 2001 From: "S.Z. Masud" Date: Mon, 11 Mar 2024 14:23:28 +0000 Subject: [PATCH] Few Updates --- app/tools/layout.tsx | 153 +++-- app/tools/scaff_net/page.tsx | 54 ++ app/tools/tanimoto/page.tsx | 128 ++-- app/tools/toc/page.tsx | 32 + components/dataloader/CSVLoader.tsx | 92 +-- components/dataloader/CompoundGetter.tsx | 111 +++- components/dataloader/DataLoader.tsx | 106 ++-- components/dataloader/LoadFromWork.tsx | 153 +++-- components/dataloader/TargetGetter.tsx | 112 ++-- components/tools/toolComp/MMA.tsx | 310 +++++----- .../tools/toolComp/MoleculeStructure.tsx | 23 +- components/tools/toolComp/ScaffNetDets.tsx | 85 +++ .../toolComp/ScaffoldNetworkWholeGraph.tsx | 139 +++++ components/tools/toolViz/Histogram.tsx | 120 ++-- components/ui-comps/FAQComp.tsx | 8 + components/ui-comps/Loader.tsx | 21 +- components/ui-comps/ModalComponent.tsx | 48 +- components/ui-comps/PaginatedTables.tsx | 8 +- components/ui-comps/TabbedComponents.tsx | 44 ++ components/utils/loadGraphFromScaffNet.ts | 58 ++ components/utils/nsga2.ts | 552 +++++++++--------- components/utils/rdkit_loader.ts | 46 +- package-lock.json | 231 +++++++- package.json | 8 +- public/RDKit_minimal.js | 2 +- public/RDKit_minimal.wasm | Bin 6343740 -> 6417533 bytes styles/corner-menu.css | 4 +- styles/index.css | 406 ++++++------- styles/tables.css | 133 +++-- styles/tabs.css | 34 ++ 30 files changed, 2079 insertions(+), 1142 deletions(-) create mode 100644 app/tools/scaff_net/page.tsx create mode 100644 app/tools/toc/page.tsx create mode 100644 components/tools/toolComp/ScaffNetDets.tsx create mode 100644 components/tools/toolComp/ScaffoldNetworkWholeGraph.tsx create mode 100644 components/ui-comps/FAQComp.tsx create mode 100644 components/ui-comps/TabbedComponents.tsx create mode 100644 components/utils/loadGraphFromScaffNet.ts create mode 100644 styles/tabs.css diff --git a/app/tools/layout.tsx b/app/tools/layout.tsx index 07ada81..d9de4c7 100644 --- a/app/tools/layout.tsx +++ b/app/tools/layout.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { useContext, useEffect, useState } from "react"; import CornerMenu, { MenuItem } from "../../components/ui-comps/CornerMenu"; @@ -8,119 +8,156 @@ import { initRDKit } from "../../components/utils/rdkit_loader"; import Loader from "../../components/ui-comps/Loader"; import Script from "next/script"; import LigandContext from "../../context/LigandContext"; -import { useRouter } from 'next/navigation' +import { useRouter } from "next/navigation"; import ModalComponent from "../../components/ui-comps/ModalComponent"; import ErrorContext from "../../context/ErrorContext"; const menuItems: MenuItem[] = [ { label: "Pre-Processing", - link: "/tools/preprocess" + link: "/tools/preprocess", }, { - label: 'Distributions', link: '#', subMenuItems: [{ - label: 'Activity', - link: '/tools/activity' - }, { - label: 'Tanimoto', - link: '/tools/tanimoto' - }] - }, { - label: 'Dimensionality Reduction', link: '#', subMenuItems: [{ - label: 'PCA', - link: '/tools/dim-red#pca' - }, { - label: 'tSNE', - link: '/tools/dim-red#tsne' - }] + label: "Distributions", + link: "#", + subMenuItems: [ + { + label: "Activity", + link: "/tools/activity", + }, + { + label: "Tanimoto", + link: "/tools/tanimoto", + }, + { + label: "Table Of Compounds", + link: "/tools/toc", + }, + ], }, { - label: 'Scaffold Analysis', link: '#', subMenuItems: [{ - label: 'MMA', - link: '/tools/mma' - }] + label: "Dimensionality Reduction", + link: "#", + subMenuItems: [ + { + label: "PCA", + link: "/tools/dim-red#pca", + }, + { + label: "tSNE", + link: "/tools/dim-red#tsne", + }, + ], }, { - label: 'Machine Learning', link: '#', subMenuItems: [{ - label: 'Random Forest', - link: '/tools/ml#rf' - }, { - label: 'XGBoost', - link: '/tools/ml#xgboost' - } - ] + label: "Scaffold Analysis", + link: "#", + subMenuItems: [ + { + label: "MMA", + link: "/tools/mma", + }, + { + label: "Scaffold Network", + link: "/tools/scaff_net", + }, + ], }, { - label: 'Virtual Screening', link: '#', subMenuItems: [ + label: "Machine Learning", + link: "#", + subMenuItems: [ { - label: 'Full Data', - link: '/tools/screen' + label: "Random Forest", + link: "/tools/ml#rf", }, { - label: 'Coverage Score', - link: '/tools/screen/cov_score' + label: "XGBoost", + link: "/tools/ml#xgboost", }, - ] + ], + }, + { + label: "Virtual Screening", + link: "#", + subMenuItems: [ + { + label: "Full Data", + link: "/tools/screen", + }, + { + label: "Coverage Score", + link: "/tools/screen/cov_score", + }, + ], }, ]; export default function DashboardLayout({ children, }: { - children: React.ReactNode + children: React.ReactNode; }) { const { setPyodide } = useContext(PyodideContext); const { setRDKit } = useContext(RDKitContext); const [loading, setLoading] = useState(true); - const [loadingText, setLoadingText] = useState('Loading Pyodide...') + const [loadingText, setLoadingText] = useState("Loading Pyodide..."); const { ligand } = useContext(LigandContext); const { errors, setErrors } = useContext(ErrorContext); const [modalState, setModalState] = useState(false); const router = useRouter(); - useEffect(() => { if (errors) { - setModalState(true) + setModalState(true); } - }, [errors]) + }, [errors]); useEffect(() => { if (ligand.length < 1) { - router.push('/load_data'); + router.push("/load_data"); } - }, []) + }, []); async function loadRDKit() { const RDK = await initRDKit(); - return RDK + return RDK; } async function pyodideLoaded() { try { await globalThis.loadPyodide().then((pyodide) => { - pyodide.loadPackage(['scikit-learn', 'numpy']).then(() => { - setPyodide(pyodide) - }) - }) - setLoadingText("Loading RDKit") - await loadRDKit().then(RDK => { setRDKit(RDK); setLoading(false) }); + pyodide.loadPackage(["scikit-learn", "numpy"]).then(() => { + setPyodide(pyodide); + }); + }); + setLoadingText("Loading RDKit"); + await loadRDKit().then((RDK) => { + setRDKit(RDK); + setLoading(false); + }); } catch (e) { - console.log(e); - setErrors("Pyodide and RDKit had problems loading") + console.error(e); + setErrors("Pyodide and RDKit had problems loading"); } } if (ligand.length > 1) { return ( -
- +
+ {loading ? ( ) : ( <> - setModalState(false)}> + setModalState(false)} + > {errors} {children} @@ -129,8 +166,6 @@ export default function DashboardLayout({
); } else { - return ( - <> - ) + return <>; } -} \ No newline at end of file +} diff --git a/app/tools/scaff_net/page.tsx b/app/tools/scaff_net/page.tsx new file mode 100644 index 0000000..14b2fc4 --- /dev/null +++ b/app/tools/scaff_net/page.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useContext, useEffect, useState } from "react"; +import RDKitContext from "../../../context/RDKitContext"; +import LigandContext from "../../../context/LigandContext"; + +import "@react-sigma/core/lib/react-sigma.min.css"; +import { scaffold_net_chunking_method } from "../../../components/utils/rdkit_loader"; +import Loader from "../../../components/ui-comps/Loader"; +import ScaffoldNetworkWholeGraph from "../../../components/tools/toolComp/ScaffoldNetworkWholeGraph"; +import loadGraphFromScaffNet from "../../../components/utils/loadGraphFromScaffNet"; +import TabWrapper, { + Tabs, +} from "../../../components/ui-comps/TabbedComponents"; +import ScaffNetDets from "../../../components/tools/toolComp/ScaffNetDets"; + +export default function DisplayGraph() { + const { rdkit } = useContext(RDKitContext); + const { ligand } = useContext(LigandContext); + const [loaded, setLoaded] = useState(false); + const [graph, setGraph] = useState(); + + useEffect(() => { + setLoaded(false); + setTimeout(() => { + let smiles_list = ligand.map((x) => x.canonical_smiles); + const network = scaffold_net_chunking_method(smiles_list, 50, rdkit); + const graph = loadGraphFromScaffNet(network, smiles_list); + setGraph(graph); + setLoaded(true); + }, 80); + }, []); + + if (!loaded) { + return ( +
+ +
+ ); + } + + return ( +
+ + + + + + + + +
+ ); +} diff --git a/app/tools/tanimoto/page.tsx b/app/tools/tanimoto/page.tsx index 7524d11..585af99 100644 --- a/app/tools/tanimoto/page.tsx +++ b/app/tools/tanimoto/page.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { useState, useEffect, useRef, useContext } from "react"; import LigandContext from "../../../context/LigandContext"; @@ -8,69 +8,73 @@ import TanimotoSimilarity from "../../../components/utils/tanimoto"; import fpSorter from "../../../components/utils/fp_sorter"; import ErrorContext from "../../../context/ErrorContext"; -export default function Tanimoto(){ - const {ligand} = useContext(LigandContext); - const {rdkit} = useContext(RDKitContext); - const {setErrors} = useContext(ErrorContext); +export default function Tanimoto() { + const { ligand } = useContext(LigandContext); + const { rdkit } = useContext(RDKitContext); + const { setErrors } = useContext(ErrorContext); + const containerRef = useRef(null); + const [taniData, setTaniData] = useState([]); + const [anchorMol, setAnchorMol] = useState("CCO"); - - - const containerRef = useRef(null); - const [taniData, setTaniData] = useState([]); - const [anchorMol, setAnchorMol] = useState("CCO"); - - function tanimotoDist(){ - try{ - const mol_fp = fpSorter( - localStorage.getItem("fingerprint"), - anchorMol, - rdkit, - parseInt(localStorage.getItem("path")), - parseInt(localStorage.getItem("nBits")), - ) - const data = ligand.map((x) => { - const tanimoto = TanimotoSimilarity(x.fingerprint, mol_fp) - return tanimoto - }) - setTaniData(data) - }catch (e) { - console.log(e) - setErrors("Most probably there is problem with your SMILES string") - } - + function tanimotoDist() { + try { + const mol_fp = fpSorter( + localStorage.getItem("fingerprint"), + anchorMol, + rdkit, + parseInt(localStorage.getItem("path")), + parseInt(localStorage.getItem("nBits")), + ); + const data = ligand.map((x) => { + const tanimoto = TanimotoSimilarity(x.fingerprint, mol_fp); + return tanimoto; + }); + setTaniData(data); + } catch (e) { + console.error(e); + setErrors("Most probably there is problem with your SMILES string"); } + } - useEffect(() => { - tanimotoDist(); - }, []); - - + useEffect(() => { + tanimotoDist(); + }, []); - return( -
-
- What does this mean? -

- Tanimoto indexes are a similarity score between two molecules. - The index is between 0 and 1 where a value closer to one indicated two molecules are very similar. - If we have a reference molecule, which in this case is a random molecule, we could compare this molecule, - to all the molecule in your database. This will help us understand, the diversity of our database, - compared to a reference. This is still not the full picture, but adds a piece to the puzzle. -

-
- - setAnchorMol(e.target.value)}/> - - {taniData.length != 0 && - <> - - - } -
- ) -} \ No newline at end of file + return ( +
+
+ What does this mean? +

+ Tanimoto indexes are a similarity score between two molecules. The + index is between 0 and 1 where a value closer to one indicated two + molecules are very similar. If we have a reference molecule, which in + this case is a random molecule, we could compare this molecule, to all + the molecule in your database. This will help us understand, the + diversity of our database, compared to a reference. This is still not + the full picture, but adds a piece to the puzzle. +

+
+ + setAnchorMol(e.target.value)} + /> + + {taniData.length != 0 && ( + <> + + + )} +
+ ); +} diff --git a/app/tools/toc/page.tsx b/app/tools/toc/page.tsx new file mode 100644 index 0000000..e244467 --- /dev/null +++ b/app/tools/toc/page.tsx @@ -0,0 +1,32 @@ +"use client" + +import { useContext } from "react"; +import Table from "../../../components/ui-comps/PaginatedTables"; +import LigandContext from "../../../context/LigandContext"; +import { usePapaParse } from 'react-papaparse'; + +export default function TOC(){ + const { ligand } = useContext(LigandContext); + const { jsonToCSV } = usePapaParse(); + + const results = jsonToCSV(ligand, { delimiter: ',' }); + + function downloadCSV(csv: any){ + const blob = new Blob([csv], { type: 'text/csv' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.setAttribute('hidden', ''); + a.setAttribute('href', url); + a.setAttribute('download', 'ligand_data.csv'); + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } + + return( +
+ + + + ) +} \ No newline at end of file diff --git a/components/dataloader/CSVLoader.tsx b/components/dataloader/CSVLoader.tsx index 9a84740..327902d 100644 --- a/components/dataloader/CSVLoader.tsx +++ b/components/dataloader/CSVLoader.tsx @@ -1,10 +1,17 @@ -import { Dispatch, SetStateAction, useState } from 'react'; -import { useCSVReader, lightenDarkenColor, formatFileSize } from 'react-papaparse'; -import { useForm, SubmitHandler } from 'react-hook-form'; -import convertToJSON from '../utils/arrayToJson'; +import { Dispatch, SetStateAction, useState } from "react"; +import { + useCSVReader, + lightenDarkenColor, + formatFileSize, +} from "react-papaparse"; +import { useForm, SubmitHandler } from "react-hook-form"; +import convertToJSON from "../utils/arrayToJson"; -const DEFAULT_REMOVE_HOVER_COLOR = '#A01919'; -const REMOVE_HOVER_COLOR_LIGHT = lightenDarkenColor(DEFAULT_REMOVE_HOVER_COLOR, 40); +const DEFAULT_REMOVE_HOVER_COLOR = "#A01919"; +const REMOVE_HOVER_COLOR_LIGHT = lightenDarkenColor( + DEFAULT_REMOVE_HOVER_COLOR, + 40, +); export type Inputs = { id_column: string; @@ -18,12 +25,22 @@ interface Props { act_col?: boolean; } -const CSVLoader: React.FC = ({ callofScreenFunction, csvSetter, act_col }) => { +const CSVLoader: React.FC = ({ + callofScreenFunction, + csvSetter, + act_col, +}) => { const { CSVReader } = useCSVReader(); const [zoneHover, setZoneHover] = useState(false); const [headers, setHeader] = useState([]); - const [removeHoverColor, setRemoveHoverColor] = useState(DEFAULT_REMOVE_HOVER_COLOR); - const { register, handleSubmit, formState: { errors } } = useForm(); + const [removeHoverColor, setRemoveHoverColor] = useState( + DEFAULT_REMOVE_HOVER_COLOR, + ); + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); const [csvData, setCsvData] = useState[]>([{}]); return ( @@ -37,7 +54,6 @@ const CSVLoader: React.FC = ({ callofScreenFunction, csvSetter, act_col } const mimi = headers.findIndex((head, i) => { data[0][head].toLowerCase().includes("smiles" || "smi") ? i : null; }); - console.log(mimi); }} onDragOver={(event: DragEvent) => { event.preventDefault(); @@ -55,26 +71,26 @@ const CSVLoader: React.FC = ({ callofScreenFunction, csvSetter, act_col } getRemoveFileProps, Remove, }: any) => ( -
+
{acceptedFile ? (
-
-
- +
+
+ {formatFileSize(acceptedFile.size)} - {acceptedFile.name} + {acceptedFile.name}
-
+
{ event.preventDefault(); setRemoveHoverColor(REMOVE_HOVER_COLOR_LIGHT); @@ -90,9 +106,7 @@ const CSVLoader: React.FC = ({ callofScreenFunction, csvSetter, act_col }
) : ( <> -

- Upload Your CSV File Here With SMILES Strings. -

+

Upload Your CSV File Here With SMILES Strings.

You could also drag and drop the file here or Click to browse.

@@ -103,14 +117,14 @@ const CSVLoader: React.FC = ({ callofScreenFunction, csvSetter, act_col } {acceptedFile ? (
- +
- + {headers.map((head, key) => ( @@ -146,9 +160,9 @@ const CSVLoader: React.FC = ({ callofScreenFunction, csvSetter, act_col } )}
{errors.id_column?.message} @@ -164,4 +178,4 @@ const CSVLoader: React.FC = ({ callofScreenFunction, csvSetter, act_col } ); }; -export default CSVLoader; \ No newline at end of file +export default CSVLoader; diff --git a/components/dataloader/CompoundGetter.tsx b/components/dataloader/CompoundGetter.tsx index 735d911..6432355 100644 --- a/components/dataloader/CompoundGetter.tsx +++ b/components/dataloader/CompoundGetter.tsx @@ -2,17 +2,19 @@ import { useContext, useState } from "react"; import TargetContext from "../../context/TargetContext"; import LigandContext from "../../context/LigandContext"; import Link from "next/link"; +import FAQComp from "../ui-comps/FAQComp"; export default function CompoundGetter() { const [unit, setUnit] = useState("Ki"); const [binding, setBinding] = useState("B"); const { target } = useContext(TargetContext); const { ligand, setLigand } = useContext(LigandContext); - + const [ligandSearch, setLigandSearch] = useState(false); const [loading, setLoading] = useState(false); const [progress, setProgress] = useState(0); async function getFullActivityData(url: string) { + setLigandSearch(false); setLoading(true); const chembl_url = "https://www.ebi.ac.uk"; @@ -27,34 +29,72 @@ export default function CompoundGetter() { nextUrl = chembl_url + data.page_meta.next; - const newProgress = - (results.length / data.page_meta.total_count) * 100; + const newProgress = (results.length / data.page_meta.total_count) * 100; setProgress(newProgress); } - + setLigandSearch(true); return results.slice(1); } function hehe() { getFullActivityData( - `/chembl/api/data/activity?format=json&target_chembl_id=${target.target_id}&type=${unit}&target_organism=Homo%20sapiens&assay_type=${binding}&relation==` + `/chembl/api/data/activity?format=json&target_chembl_id=${target.target_id}&type=${unit}&target_organism=Homo%20sapiens&assay_type=${binding}&relation==`, ).then((data) => { - data.map(x => { + data.map((x) => { x["activity_column"] = x["standard_value"]; x["id"] = x["molecule_chembl_id"]; delete x["standard_value"]; - return x - }) + return x; + }); setLigand(data); }); - - localStorage.setItem("dataSource", "chembl") + localStorage.setItem("dataSource", "chembl"); } return ( -
+
+

Small Molecule Getter

+ + For your specific target select, small molecules will be searched that + have been tested in assays against your target is filtered. You could + select the type of assay they have been tested in and the units + provided. Generally speaking it is best to avoid  + + mixing various forms of data types + + . Since, Binding Assays are more prevalent with Ki being the preferred + unit, they are the default. + + + - { + setUnit(e.target.value); + }} + > @@ -64,25 +104,36 @@ export default function CompoundGetter() { - - - - - {loading &&
- - {(Math.min(progress, 100)).toFixed(2)} % -
} + + {loading && ( +
+ +

+ + {Math.min(progress, 100).toFixed(2)} % + +
+ )}

- {ligand.length > 0 &&
- Pre-Process Molecules -
} + {ligand.length > 0 ? ( +
+ + Pre-Process Molecules + +
+ ) : ( + + {ligandSearch && + "There are zero compounds for this target using this filtration criteria"} + + )}
); } diff --git a/components/dataloader/DataLoader.tsx b/components/dataloader/DataLoader.tsx index 5b6a55e..17f58b5 100644 --- a/components/dataloader/DataLoader.tsx +++ b/components/dataloader/DataLoader.tsx @@ -9,52 +9,66 @@ import ErrorContext from "../../context/ErrorContext"; import ModalComponent from "../ui-comps/ModalComponent"; export default function DataLoader() { - const { ligand, setLigand } = useContext(LigandContext); - const router = useRouter(); + const { ligand, setLigand } = useContext(LigandContext); + const router = useRouter(); - const { errors, setErrors } = useContext(ErrorContext); - const [modalState, setModalState] = useState(false); - - useEffect(() => { - if (errors) { - setModalState(true) - } - }, [errors]) + const { errors, setErrors } = useContext(ErrorContext); + const [modalState, setModalState] = useState(false); - const onSubmit: SubmitHandler = (data) => { - let while_process_var = ligand.map((x) => { - x['id'] = x[data.id_column]; - x['activity_column'] = parseFloat(x[data.act_column || '']); - x['canonical_smiles'] = x[data.smi_column]; - delete x[data.act_column || ''], x[data.id_column], x[data.smi_column]; - return x; - }); - setLigand(while_process_var); - localStorage.setItem('dataSource', 'csv'); - router.push('/tools/preprocess/'); - }; + useEffect(() => { + if (errors) { + setModalState(true); + } + }, [errors]); - return ( -
-
-

Fetch the data

-

Starting off with analysis, we need data. - For now, you could use the in-built program to fetch data format - ChEMBL, or you can use your own dataset in a CSV format. If you are new here - and just want to try the program out, I'd suggest the ChEMBL program with the CHEMBL223 Target. - ChEMBL is a bioactivity database, and you could find details about it  - here -

- -
-
-
- - -
- {setModalState(false); setErrors("")}}> - {errors} - -
- ) -} \ No newline at end of file + const onSubmit: SubmitHandler = (data) => { + let while_process_var = ligand.map((x) => { + x["id"] = x[data.id_column]; + x["activity_column"] = parseFloat(x[data.act_column || ""]); + x["canonical_smiles"] = x[data.smi_column]; + delete x[data.act_column || ""], x[data.id_column], x[data.smi_column]; + return x; + }); + setLigand(while_process_var); + localStorage.setItem("dataSource", "csv"); + router.push("/tools/preprocess/"); + }; + + return ( +
+
+

Fetch the data

+

+ Starting off with analysis, we need data. For now, you could use the + in-built program to fetch data format ChEMBL, or you can use your own + dataset in a CSV format. If you are new here and just want to try the + program out, I'd suggest the ChEMBL program with the CHEMBL223 Target. + ChEMBL is a bioactivity database, and you could find details about + it  + + here + +

+
+
+
+ + + +
+ { + setModalState(false); + setErrors(""); + }} + > + {errors} + +
+ ); +} diff --git a/components/dataloader/LoadFromWork.tsx b/components/dataloader/LoadFromWork.tsx index b275862..443a7bb 100644 --- a/components/dataloader/LoadFromWork.tsx +++ b/components/dataloader/LoadFromWork.tsx @@ -1,93 +1,88 @@ // FileUploadComponent.tsx -import React, { useContext, useState } from 'react'; -import LigandContext from '../../context/LigandContext'; -import TargetContext from '../../context/TargetContext'; -import { useRouter } from 'next/navigation'; -import ErrorContext from '../../context/ErrorContext'; +import React, { useContext, useState } from "react"; +import LigandContext from "../../context/LigandContext"; +import TargetContext from "../../context/TargetContext"; +import { useRouter } from "next/navigation"; +import ErrorContext from "../../context/ErrorContext"; const LoadFromWork: React.FC = () => { - const { setLigand } = useContext(LigandContext); - const { setTarget } = useContext(TargetContext); - const { setErrors } = useContext(ErrorContext); - const [isHovered, setIsHovered] = useState(false); // Add state for hover + const { setLigand } = useContext(LigandContext); + const { setTarget } = useContext(TargetContext); + const { setErrors } = useContext(ErrorContext); + const [isHovered, setIsHovered] = useState(false); // Add state for hover - const router = useRouter(); + const router = useRouter(); - const handleFileChange = (event: React.ChangeEvent) => { - const input = event.target; - const file = input.files?.[0]; - console.log(file); - if (file.name.endsWith('.json')) { - handleFile(file); - } else { - setErrors('Hey, please upload a valid QITB JSON File'); - } - }; - - const handleFileDrop = (event: React.DragEvent) => { - event.preventDefault(); - const file = event.dataTransfer.files?.[0]; + const handleFileChange = (event: React.ChangeEvent) => { + const input = event.target; + const file = input.files?.[0]; + if (file.name.endsWith(".json")) { + handleFile(file); + } else { + setErrors("Hey, please upload a valid QITB JSON File"); + } + }; - if (file) { - handleFile(file); - } - }; + const handleFileDrop = (event: React.DragEvent) => { + event.preventDefault(); + const file = event.dataTransfer.files?.[0]; - const handleFile = (file: File) => { - console.log('File uploaded successfully:', file.name); - const reader = new FileReader(); + if (file) { + handleFile(file); + } + }; - reader.onload = (e) => { - try { - const jsonContent = JSON.parse(e.target?.result as string); - setLigand(jsonContent.ligand_data); - setTarget(jsonContent.target_data); - localStorage.setItem("dataSource", jsonContent.source); - localStorage.setItem("path", jsonContent.fpPath); - localStorage.setItem("nBits", jsonContent.nBits); - localStorage.setItem("fingerprint", jsonContent.fp_type) - router.push("/tools/preprocess/") - } catch (error) { - setErrors('Please upload a valid QITB JSON File'); - } - }; + const handleFile = (file: File) => { + console.log("File uploaded successfully:", file.name); + const reader = new FileReader(); - reader.readAsText(file); - }; - const handleDragOver = (event: React.DragEvent) => { - event.preventDefault(); - setIsHovered(true); + reader.onload = (e) => { + try { + const jsonContent = JSON.parse(e.target?.result as string); + setLigand(jsonContent.ligand_data); + setTarget(jsonContent.target_data); + localStorage.setItem("dataSource", jsonContent.source); + localStorage.setItem("path", jsonContent.fpPath); + localStorage.setItem("nBits", jsonContent.nBits); + localStorage.setItem("fingerprint", jsonContent.fp_type); + router.push("/tools/preprocess/"); + } catch (error) { + setErrors("Please upload a valid QITB JSON File"); + } }; - return ( -
-
setIsHovered(false)} - onClick={() => { - const fileInput = document.getElementById('fileInput'); - if (fileInput) { - fileInput.click(); - } - }} - className={`zone ${isHovered ? 'zoneHover' : ''}`} - > -

- Upload Previous Work (A JSON file from QITB) -

-

- You could also drag and drop the file here or Click to browse. -

- -
-
- ); + reader.readAsText(file); + }; + const handleDragOver = (event: React.DragEvent) => { + event.preventDefault(); + setIsHovered(true); + }; + + return ( +
+
setIsHovered(false)} + onClick={() => { + const fileInput = document.getElementById("fileInput"); + if (fileInput) { + fileInput.click(); + } + }} + className={`zone ${isHovered ? "zoneHover" : ""}`} + > +

Upload Previous Work (A JSON file from QITB)

+

You could also drag and drop the file here or Click to browse.

+ +
+
+ ); }; export default LoadFromWork; diff --git a/components/dataloader/TargetGetter.tsx b/components/dataloader/TargetGetter.tsx index fbe9326..83ee889 100644 --- a/components/dataloader/TargetGetter.tsx +++ b/components/dataloader/TargetGetter.tsx @@ -6,30 +6,40 @@ import ModalComponent from "../ui-comps/ModalComponent"; import Loader from "../ui-comps/Loader"; export default function TargetGetter() { - const [targetQuery, setTargetQuery] = useState(''); + const [targetQuery, setTargetQuery] = useState(""); const [targetDetails, setTargetDetails] = useState(dummyData.targets); const [loading, setLoading] = useState(false); const { target, setTarget } = useContext(TargetContext); - const [modalState, setModalState] = useState(target.target_name === ""); + const [modalState, setModalState] = useState(false); function fetchTarget(e) { setLoading(true); - fetch(`https://www.ebi.ac.uk/chembl/api/data/target/search?format=json&q=${targetQuery}`) + fetch( + `https://www.ebi.ac.uk/chembl/api/data/target/search?format=json&q=${targetQuery}`, + ) .then((response) => response.json()) .then((data) => { - let target_data = data.targets + let target_data = data.targets; setTargetDetails(target_data); - setLoading(false) + setLoading(false); }) .catch((error) => { console.error("Error:", error); }); - e.preventDefault() + e.preventDefault(); } return ( -
- fetchTarget(e)} style={{ width: "90%", display: "flex", gap: "10px", flexDirection: "column" }}> +
+ fetchTarget(e)} + style={{ + width: "90%", + display: "flex", + gap: "10px", + flexDirection: "column", + }} + >

ChEMBL Data Fetcher

setTargetQuery(e.target.value)} defaultValue={target.target_name} required={true} + pattern=".{3,}" + /> + - - {target.target_name === "" ? ( -
- {loading ? : -
- - - - - +
+ {loading ? ( + + ) : ( +
Target NameChEMBL IDOrganism
+ + + + + + + + + {targetDetails.map((tars) => ( + { + setTarget({ + target_id: tars.target_chembl_id, + target_name: tars.pref_name, + target_organism: tars.organism, + }); + setModalState(true); + }} + > + + + - - - {targetDetails.map((tars) => ( - { setTarget({target_id : tars.target_chembl_id, target_name : tars.pref_name, target_organism : tars.organism}) }}> - - - - - ))} - - -
Target NameChEMBL IDOrganism
{tars.pref_name}{tars.target_chembl_id}{tars.organism}
{tars.pref_name}{tars.target_chembl_id}{tars.organism}
- } -
- ) : ( + + )} +
+ setModalState(false)} - height="40" - width="30" + height="55" + width="35" > - - )} + + - ) -} \ No newline at end of file + ); +} diff --git a/components/tools/toolComp/MMA.tsx b/components/tools/toolComp/MMA.tsx index f7835f4..412e8f0 100644 --- a/components/tools/toolComp/MMA.tsx +++ b/components/tools/toolComp/MMA.tsx @@ -1,160 +1,178 @@ import { useContext, useState, useEffect } from "react"; import LigandContext from "../../../context/LigandContext"; -import { initRDKit } from '../../utils/rdkit_loader' -import Card from '../toolViz/Card'; +import { initRDKit } from "../../utils/rdkit_loader"; +import Card from "../toolViz/Card"; import MoleculeStructure from "./MoleculeStructure"; -import Loader from '../../ui-comps/Loader'; +import Loader from "../../ui-comps/Loader"; import ModalComponent from "../../ui-comps/ModalComponent"; import kstest from "@stdlib/stats-kstest"; import cdf from "@stdlib/stats-base-dists-normal-cdf"; import { ksTest } from "../../utils/ks_test"; export default function MMA() { - const { ligand } = useContext(LigandContext); - const [RDKit, setRDKit] = useState(null); - const [stateOfRDKit, setStateOfRDKit] = useState(false); - const [scaffCores, setScaffCores] = useState([]); - const [scaffCoreLoaded, setScaffCoresLoaded] = useState(false); - const [isModalOpen, setModalOpen] = useState(false); - const [specificMolArray, setSpecificMolArray] = useState([]); - - - const openModal = () => { - setModalOpen(true); - }; - const closeModal = () => { - setModalOpen(false); - }; - - useEffect(() => { - async function loadRDKit() { - const RDK = await initRDKit() - setRDKit(RDK); - setStateOfRDKit(true); - } - loadRDKit(); - }, [ligand]) - - useEffect(() => { - if (stateOfRDKit) { - const scaff_cores = scaffoldArrayGetter(ligand); - setScaffCores(scaff_cores); - setScaffCoresLoaded(true); - } - }, [stateOfRDKit]) - - function scaffoldArrayGetter(row_list_s) { - let neg_log_activity_column = ligand.map((obj) => obj.neg_log_activity_column); - let massive_array = []; - - row_list_s.map((x, i) => { - const mol = RDKit.get_mol(x.canonical_smiles); - let sidechains_smiles_list = [] - let cores_smiles_list = [] - try { - const mol_frags = mol.get_mmpa_frags(1, 1, 20); - while (!mol_frags.sidechains.at_end()) { - var m = mol_frags.sidechains.next(); - var { molList, _ } = m.get_frags(); - try { - let fragments = []; - while (!molList.at_end()) { - var m_frag = molList.next(); - fragments.push(m_frag.get_smiles()) - m_frag.delete(); - } - molList.delete() - cores_smiles_list.push(fragments.at(0)) - sidechains_smiles_list.push(fragments.at(1)) - massive_array.push([x.canonical_smiles, fragments.at(0), fragments.at(1), x.id, x.neg_log_activity_column]) - m.delete() - mol_frags.cores.delete() - mol_frags.sidechains.delete() - } catch { - console.log("For Some Reason There are Null Values") - } - } - } catch { - console.log('Problem') - } - row_list_s[i]['Cores'] = cores_smiles_list; - row_list_s[i]['R_Groups'] = sidechains_smiles_list; - mol.delete() - }) - - let countArray = {}; - - for (let i = 0; i < massive_array.length; i++) { - if (massive_array[i].length >= 5) { // Ensure there are at least 5 elements in the subarray - let secondElement = massive_array[i][1]; - let fifthElement = massive_array[i][4]; // Assuming the fifth element is at index 4 - - if (!countArray[secondElement]) { - countArray[secondElement] = [0, []]; - } - - countArray[secondElement][0]++; - countArray[secondElement][1].push(fifthElement); + const { ligand } = useContext(LigandContext); + const [RDKit, setRDKit] = useState(null); + const [stateOfRDKit, setStateOfRDKit] = useState(false); + const [scaffCores, setScaffCores] = useState([]); + const [scaffCoreLoaded, setScaffCoresLoaded] = useState(false); + const [isModalOpen, setModalOpen] = useState(false); + const [specificMolArray, setSpecificMolArray] = useState([]); + + const openModal = () => { + setModalOpen(true); + }; + const closeModal = () => { + setModalOpen(false); + }; + + useEffect(() => { + async function loadRDKit() { + const RDK = await initRDKit(); + setRDKit(RDK); + setStateOfRDKit(true); + } + loadRDKit(); + }, [ligand]); + + useEffect(() => { + if (stateOfRDKit) { + const scaff_cores = scaffoldArrayGetter(ligand); + setScaffCores(scaff_cores); + setScaffCoresLoaded(true); + } + }, [stateOfRDKit]); + + function scaffoldArrayGetter(row_list_s) { + let neg_log_activity_column = ligand.map( + (obj) => obj.neg_log_activity_column, + ); + let massive_array = []; + + row_list_s.map((x, i) => { + const mol = RDKit.get_mol(x.canonical_smiles); + let sidechains_smiles_list = []; + let cores_smiles_list = []; + try { + const mol_frags = mol.get_mmpa_frags(1, 1, 20); + while (!mol_frags.sidechains.at_end()) { + var m = mol_frags.sidechains.next(); + var { molList, _ } = m.get_frags(); + try { + let fragments = []; + while (!molList.at_end()) { + var m_frag = molList.next(); + fragments.push(m_frag.get_smiles()); + m_frag.delete(); } + molList.delete(); + cores_smiles_list.push(fragments.at(0)); + sidechains_smiles_list.push(fragments.at(1)); + massive_array.push([ + x.canonical_smiles, + fragments.at(0), + fragments.at(1), + x.id, + x.neg_log_activity_column, + ]); + m.delete(); + mol_frags.cores.delete(); + mol_frags.sidechains.delete(); + } catch { + console.error("For Some Reason There are Null Values"); + } + } + } catch (e) { + console.error("Problem: ", e); + } + row_list_s[i]["Cores"] = cores_smiles_list; + row_list_s[i]["R_Groups"] = sidechains_smiles_list; + mol.delete(); + }); + + let countArray = {}; + + for (let i = 0; i < massive_array.length; i++) { + if (massive_array[i].length >= 5) { + // Ensure there are at least 5 elements in the subarray + let secondElement = massive_array[i][1]; + let fifthElement = massive_array[i][4]; // Assuming the fifth element is at index 4 + + if (!countArray[secondElement]) { + countArray[secondElement] = [0, []]; } - let scaffoldArray = Object.entries(countArray); - let filteredArrayOfScaffolds = scaffoldArray.filter(([key, count]) => - typeof count[0] === 'number' && - count[0] >= 2 && - key.length > 9 - ); - - - filteredArrayOfScaffolds = filteredArrayOfScaffolds.map(x => { - return [x[0], [x[1][0], ksTest(x[1][1], neg_log_activity_column)]] - }) - - filteredArrayOfScaffolds.sort((a, b) => a[1][1] - b[1][1]); - - console.log(filteredArrayOfScaffolds) - return [filteredArrayOfScaffolds, massive_array]; - } - - function scaffoldFinder(cores) { - const selectedArrays = scaffCores[1].filter(array => { - return array[1] === cores; - }); - setSpecificMolArray(selectedArrays) - openModal(); + countArray[secondElement][0]++; + countArray[secondElement][1].push(fifthElement); + } } - if (scaffCoreLoaded) { - return ( -
-
- {scaffCores[0].map((cores, key) => ( - - -
- Count : {cores[1][0]} -

- -
- ))} -
- - {specificMolArray.map((cores, key) => ( - - -

- Activity : {cores[4]} - {console.log(cores[3])} -
- ))} -
-
- ) - } else { - return ( -
- -
- ) - } -} \ No newline at end of file + let scaffoldArray = Object.entries(countArray); + let filteredArrayOfScaffolds = scaffoldArray.filter( + ([key, count]) => + typeof count[0] === "number" && count[0] >= 2 && key.length > 9, + ); + + filteredArrayOfScaffolds = filteredArrayOfScaffolds.map((x) => { + return [x[0], [x[1][0], ksTest(x[1][1], neg_log_activity_column)]]; + }); + + filteredArrayOfScaffolds.sort((a, b) => a[1][1] - b[1][1]); + + console.log(filteredArrayOfScaffolds); + return [filteredArrayOfScaffolds, massive_array]; + } + + function scaffoldFinder(cores) { + const selectedArrays = scaffCores[1].filter((array) => { + return array[1] === cores; + }); + setSpecificMolArray(selectedArrays); + openModal(); + } + + if (scaffCoreLoaded) { + return ( +
+
+ {scaffCores[0].map((cores, key) => ( + + +
+ Count : {cores[1][0]} +
+
+ +
+ ))} +
+ + {specificMolArray.map((cores, key) => ( + + +

+ Activity : {cores[4]} +
+ ))} +
+
+ ); + } else { + return ( +
+ +
+ ); + } +} diff --git a/components/tools/toolComp/MoleculeStructure.tsx b/components/tools/toolComp/MoleculeStructure.tsx index 1a50195..78b3c39 100644 --- a/components/tools/toolComp/MoleculeStructure.tsx +++ b/components/tools/toolComp/MoleculeStructure.tsx @@ -21,7 +21,10 @@ interface MoleculeStructureState { rdKitError: boolean; } -class MoleculeStructure extends Component { +class MoleculeStructure extends Component< + MoleculeStructureProps, + MoleculeStructureState +> { static propTypes = { id: PropTypes.string.isRequired, className: PropTypes.string, @@ -94,7 +97,9 @@ class MoleculeStructure extends Component ({ atoms: [...acc.atoms, ...atoms], bonds: [...acc.bonds, ...bonds], }), - { bonds: [], atoms: [] } + { bonds: [], atoms: [] }, ) : subStructHighlightDetails; @@ -140,11 +149,11 @@ class MoleculeStructure extends Component { - console.log(err); + console.error(err); this.setState({ rdKitError: true }); }); } diff --git a/components/tools/toolComp/ScaffNetDets.tsx b/components/tools/toolComp/ScaffNetDets.tsx new file mode 100644 index 0000000..87c1de8 --- /dev/null +++ b/components/tools/toolComp/ScaffNetDets.tsx @@ -0,0 +1,85 @@ +import React, { useState } from "react"; +import MoleculeStructure from "./MoleculeStructure"; +import Card from "../toolViz/Card"; + +const GraphComponent: React.FC = ({ graph }) => { + const nodesPerPage = 12; // Adjust as needed + const [currentPage, setCurrentPage] = useState(1); + + const nodesArray: { node: any, smiles: string, size: number }[] = []; + + graph.forEachNode((node, attributes) => { + nodesArray.push({ node, smiles: attributes.smiles, size: attributes.molCounts }); + }); + + nodesArray.sort((a, b) => b.size - a.size); + + const totalPages = Math.ceil(nodesArray.length / nodesPerPage); + + const getDisplayedPages = () => { + const range = 2; // Adjust as needed + const displayedPages: number[] = []; + let start = Math.max(1, currentPage - range); + let end = Math.min(currentPage + range, totalPages); + + if (currentPage - start < range) { + end = Math.min(end + range - (currentPage - start), totalPages); + } + + if (end - currentPage < range) { + start = Math.max(1, start - (range - (end - currentPage))); + } + + for (let i = start; i <= end; i++) { + displayedPages.push(i); + } + + return displayedPages; + }; + + const handlePageChange = (pageNumber: number) => { + setCurrentPage(pageNumber); + }; + + return ( +
+
+ {nodesArray + .slice((currentPage - 1) * nodesPerPage, currentPage * nodesPerPage) + .map(({ node, smiles, size }) => ( + +

Node ID: {node}

+ +

Scaffold Matches: {size}

+
+ + ))} +
+
+ ........ + {getDisplayedPages().map((pageNumber) => ( + + ))}....... + +
+
+ ); +}; + +export default GraphComponent; diff --git a/components/tools/toolComp/ScaffoldNetworkWholeGraph.tsx b/components/tools/toolComp/ScaffoldNetworkWholeGraph.tsx new file mode 100644 index 0000000..02187f4 --- /dev/null +++ b/components/tools/toolComp/ScaffoldNetworkWholeGraph.tsx @@ -0,0 +1,139 @@ +import "@react-sigma/core/lib/react-sigma.min.css"; +import getNodeProgramImage from "sigma/rendering/webgl/programs/node.image"; +import { + SigmaContainer, + useRegisterEvents, + useSetSettings, + useSigma, +} from "@react-sigma/core"; +import { useEffect, useState } from "react"; +import { Attributes } from "graphology-types"; +import { circular } from "graphology-layout"; +import { subgraph } from "graphology-operators"; + +const GraphEvents: React.FC = () => { + const registerEvents = useRegisterEvents(); + const sigma = useSigma(); + + const [draggedNode, setDraggedNode] = useState(null); + const [hoveredNode, setHoveredNode] = useState(null); + + const setSettings = useSetSettings(); + + useEffect(() => { + setSettings({ + nodeReducer: (node, data) => { + const graph = sigma.getGraph(); + const newData: Attributes = { + ...data, + highlighted: data.highlighted || false, + }; + + if (hoveredNode) { + if ( + node === hoveredNode || + graph.neighbors(hoveredNode).includes(node) + ) { + newData.highlighted = true; + newData.size = 40; + (newData.type = "image"), (newData.image = "/logo.svg"); + } else { + newData.color = "#E2E2E2"; + newData.highlighted = false; + } + } + return newData; + }, + edgeReducer: (edge, data) => { + const graph = sigma.getGraph(); + const newData = { ...data, hidden: false }; + + if (hoveredNode && !graph.extremities(edge).includes(hoveredNode)) { + newData.hidden = true; + } + return newData; + }, + }); + + // Register the events + registerEvents({ + enterNode: (event) => setHoveredNode(event.node), + leaveNode: () => setHoveredNode(null), + downNode: (e) => { + setDraggedNode(e.node); + sigma.getGraph().setNodeAttribute(e.node, "highlighted", true); + }, + mouseup: (e) => { + if (draggedNode) { + setDraggedNode(null); + sigma.getGraph().removeNodeAttribute(draggedNode, "highlighted"); + } + }, + mousedown: (e) => { + // Disable the autoscale at the first down interaction + if (!sigma.getCustomBBox()) sigma.setCustomBBox(sigma.getBBox()); + }, + mousemove: (e) => { + if (draggedNode) { + // Get new position of node + const pos = sigma.viewportToGraph(e); + sigma.getGraph().setNodeAttribute(draggedNode, "x", pos.x); + sigma.getGraph().setNodeAttribute(draggedNode, "y", pos.y); + + // Prevent sigma to move camera: + e.preventSigmaDefault(); + e.original.preventDefault(); + e.original.stopPropagation(); + } + }, + touchup: (e) => { + if (draggedNode) { + setDraggedNode(null); + sigma.getGraph().removeNodeAttribute(draggedNode, "highlighted"); + } + }, + touchdown: (e) => { + // Disable the autoscale at the first down interaction + if (!sigma.getCustomBBox()) sigma.setCustomBBox(sigma.getBBox()); + }, + touchmove: (e) => { + if (draggedNode) { + // Get new position of node + const pos = sigma.viewportToGraph(e); + sigma.getGraph().setNodeAttribute(draggedNode, "x", pos.x); + sigma.getGraph().setNodeAttribute(draggedNode, "y", pos.y); + + // Prevent sigma to move camera: + e.preventSigmaDefault(); + e.original.preventDefault(); + e.original.stopPropagation(); + } + }, + }); + }, [hoveredNode, setSettings, sigma]); + + return null; +}; + +export default function ScaffoldNetworkWholeGraph({ graph }) { + const sub = subgraph(graph, function (_, attr) { + return attr.nodeType === "whole"; + }); + const positions = circular(sub, { scale: 100 }); + circular.assign(graph, positions); + + return ( +
+ + + +
+ ); +} diff --git a/components/tools/toolViz/Histogram.tsx b/components/tools/toolViz/Histogram.tsx index eefa067..dd1e7f5 100644 --- a/components/tools/toolViz/Histogram.tsx +++ b/components/tools/toolViz/Histogram.tsx @@ -8,13 +8,17 @@ const MARGIN = { top: 30, right: 30, bottom: 40, left: 50 }; const BUCKET_NUMBER = 70; const BUCKET_PADDING = 1; -type d_bin = typeof d3.bins[number]; - -export default function Histogram({ data, xLabel = "", yLabel = "", toolTipData = [] }) { - +type d_bin = (typeof d3.bins)[number]; + +export default function Histogram({ + data, + xLabel = "", + yLabel = "", + toolTipData = [], +}) { const [dimensions, setDimensions] = useState({ width: 300, height: 300 }); const [modalState, setModalState] = useState(false); - const [modalDets, setModalDets] = useState([]) + const [modalDets, setModalDets] = useState([]); const svgRef = useRef(null); const parentRef = useRef(null); @@ -32,7 +36,7 @@ export default function Histogram({ data, xLabel = "", yLabel = "", toolTipData return () => window.removeEventListener("resize", getSvgContainerSize); }, []); - if (dimensions.width === 0 && dimensions.height === 0){ + if (dimensions.width === 0 && dimensions.height === 0) { getSvgContainerSize(); } @@ -66,7 +70,11 @@ export default function Histogram({ data, xLabel = "", yLabel = "", toolTipData // Create Y scale based on the histogram buckets const yScale = useMemo(() => { const max = d3.max(buckets, (bucket: d_bin) => bucket.length); - return d3.scaleLinear().range([boundsHeight, 0]).domain([0, max || 0]).nice(); + return d3 + .scaleLinear() + .range([boundsHeight, 0]) + .domain([0, max || 0]) + .nice(); }, [buckets, boundsHeight]); // Effect to update the chart when the scales or data change @@ -88,7 +96,10 @@ export default function Histogram({ data, xLabel = "", yLabel = "", toolTipData // Add X-axis label svgElement .append("text") - .attr("transform", `translate(${width / 2},${height - MARGIN.bottom + 30})`) + .attr( + "transform", + `translate(${width / 2},${height - MARGIN.bottom + 30})`, + ) .style("text-anchor", "middle") .text(xLabel); @@ -116,61 +127,72 @@ export default function Histogram({ data, xLabel = "", yLabel = "", toolTipData .data(buckets) .join("rect") .attr("x", (d: d_bin) => xScale(d.x0) + BUCKET_PADDING / 2) - .attr("width", (d: d_bin) => Math.max(0, xScale(d.x1) - xScale(d.x0) - BUCKET_PADDING)) + .attr("width", (d: d_bin) => + Math.max(0, xScale(d.x1) - xScale(d.x0) - BUCKET_PADDING), + ) .attr("y", (d: d_bin) => yScale(d.length)) .attr("height", (d: d_bin) => boundsHeight - yScale(d.length)) .attr("fill", "#69b3a2") .on("click", (d: d_bin) => handleClick(d)); - function handleClick(event) { - if (toolTipData.length > 0) { - const clickedData = d3.select(event.currentTarget).data()[0]; - const indexesWithinRange = data.reduce((acc, value, index) => { - if (value >= clickedData.x0 && value <= clickedData.x1) { - acc.push(index); - } - return acc; - }, []); - let resultArr = indexesWithinRange.map(i => toolTipData[i]); - setModalDets(resultArr) - console.log(resultArr) - setModalState(true) - } - + function handleClick(event) { + if (toolTipData.length > 0) { + const clickedData = d3.select(event.currentTarget).data()[0]; + const indexesWithinRange = data.reduce((acc, value, index) => { + if (value >= clickedData.x0 && value <= clickedData.x1) { + acc.push(index); + } + return acc; + }, []); + let resultArr = indexesWithinRange.map((i) => toolTipData[i]); + setModalDets(resultArr); + setModalState(true); + } } - - - - }, [xScale, yScale, buckets, dimensions]); - if (data === undefined) { - return ( - <> - ) + return <>; } else { return (
- - + - {modalState && setModalState(false)}> - <> - {modalDets.map((x, i) => - - - Activity: {x.neg_log_activity_column} - - ID: {localStorage.getItem("dataSource") === "chembl" ? - {x.id} : x.id} - - )} - - } + {modalState && ( + setModalState(false)} + > + <> + {modalDets.map((x, i) => ( + + + Activity: {x.neg_log_activity_column} + + + ID:{" "} + {localStorage.getItem("dataSource") === "chembl" ? ( + + {x.id} + + ) : ( + x.id + )} + + + ))} + + + )}
); } - -} \ No newline at end of file +} diff --git a/components/ui-comps/FAQComp.tsx b/components/ui-comps/FAQComp.tsx new file mode 100644 index 0000000..13c0f7b --- /dev/null +++ b/components/ui-comps/FAQComp.tsx @@ -0,0 +1,8 @@ +export default function FAQComp({ children, title = "What does this mean?" }) { + return ( +
+ {title} + {children} +
+ ); +} diff --git a/components/ui-comps/Loader.tsx b/components/ui-comps/Loader.tsx index a362f5a..d91cbfc 100644 --- a/components/ui-comps/Loader.tsx +++ b/components/ui-comps/Loader.tsx @@ -1,11 +1,10 @@ - - -export default function Loader({ loadingText = 'Processsing...'}){ - return( -
- {loadingText} -
-
- - ) -} \ No newline at end of file +export default function Loader({ loadingText = "Processsing..." }) { + return ( +
+
+
+ {loadingText}... +
+
+ ); +} diff --git a/components/ui-comps/ModalComponent.tsx b/components/ui-comps/ModalComponent.tsx index 55715ef..5dbbad9 100644 --- a/components/ui-comps/ModalComponent.tsx +++ b/components/ui-comps/ModalComponent.tsx @@ -1,29 +1,42 @@ -import { FC, ReactNode } from 'react'; +import { FC, ReactNode } from "react"; interface ModalComponentProps { isOpen: boolean; children: ReactNode; closeModal: () => void; - width?: string, - height?: string + width?: string; + height?: string; } -const ModalComponent: FC = ({ isOpen, children, closeModal, width = "80", height = "80" }) => { +const ModalComponent: FC = ({ + isOpen, + children, + closeModal, + width = "80", + height = "80", +}) => { + const handleOverlayClick = ( + e: React.MouseEvent, + ) => { + // Close the modal only if the click is on the overlay, not on the modal content + if (e.target === e.currentTarget) { + closeModal(); + } + }; + if (!isOpen) { return null; } return ( <> -
+
{children} -
-
+
-