diff --git a/app/tools/activity/page.tsx b/app/tools/activity/page.tsx
index 40cfc43..0105788 100644
--- a/app/tools/activity/page.tsx
+++ b/app/tools/activity/page.tsx
@@ -14,7 +14,7 @@ export default function Activity() {
return (
- Mean : {round(mean(data), 2) } || Median : {round(median(data), 2)} || Mode : {round(mode(data), 2)}
+ Mean : {round(mean(data), 2) || ""} || Median : {round(median(data), 2) || ""} || Mode : {round(mode(data), 2) || ""}
)
diff --git a/app/tools/ml/layout.tsx b/app/tools/ml/layout.tsx
index 3349a45..d2fad66 100644
--- a/app/tools/ml/layout.tsx
+++ b/app/tools/ml/layout.tsx
@@ -59,15 +59,20 @@ export default function MLLayout({ children }) {
{screenData.length > 0 && (
<>
- setOneOffSmiles(e.target.value)} placeholder="Input Your SMILES string here">
-
- setOneOffSmiles(smiles)} id="jsme_comp_1" />
-
-
-
-
- Predicted {target.activity_columns[0]}: {oneOffSMILESResult}
+
+
Predict the activity of a single molecule
+ setOneOffSmiles(e.target.value)} placeholder="Input Your SMILES string here">
+
+
+ setOneOffSmiles(smiles)} id="jsme_comp_1" />
+
+
+
+
+ Predicted {target.activity_columns[0]}: {oneOffSMILESResult}
+
+
Mean MAE: {round(mean(screenData[0]), 2)} || Mean R-Squared: {round(mean(screenData[1]), 2)}
diff --git a/app/tools/toc/page.tsx b/app/tools/toc/page.tsx
index 1c60326..a2480d8 100644
--- a/app/tools/toc/page.tsx
+++ b/app/tools/toc/page.tsx
@@ -1,17 +1,28 @@
"use client"
-import { useContext } from "react";
+import { useContext, useState } from "react";
import Table from "../../../components/ui-comps/PaginatedTables";
import LigandContext from "../../../context/LigandContext";
import { usePapaParse } from 'react-papaparse';
+import TargetContext from "../../../context/TargetContext";
+import JSME from "../../../components/tools/toolViz/JSMEComp";
+import Dropdown from "../../../components/tools/toolViz/DropDown";
+import RDKitContext from "../../../context/RDKitContext";
+import { isEmpty } from "lodash";
-export default function TOC(){
+export default function TOC() {
const { ligand } = useContext(LigandContext);
+ const { target } = useContext(TargetContext);
const { jsonToCSV } = usePapaParse();
+ const { rdkit } = useContext(RDKitContext);
+
+
+ const [searchSmi, setSearchSmi] = useState('');
+ const [searchRes, setSearchRes] = useState(ligand);
const results = jsonToCSV(ligand, { delimiter: ';' });
- function downloadCSV(csv: any){
+ function downloadCSV(csv: any) {
const blob = new Blob([csv], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
@@ -22,11 +33,38 @@ export default function TOC(){
a.click();
document.body.removeChild(a);
}
-
- return(
+
+ function searchSubst() {
+ let searchResults = [];
+ let query = rdkit.get_mol(searchSmi);
+ ligand.map((lig) => {
+ let mol = rdkit.get_mol(lig.canonical_smiles);
+ let substructRes = mol.get_substruct_match(query);
+ var substructResJson = JSON.parse(substructRes);
+ if (!isEmpty(substructResJson)){
+ searchResults.push(lig);
+ }
+ mol.delete();
+ })
+ query.delete();
+ setSearchRes(searchResults);
+ }
+
+
+ return (
-
-
+
+
+
+ setSearchSmi(e.target.value)}/>
+
+
+ setSearchSmi(smiles)} id="jsme_comp_2" />
+
+
+
+
+
)
}
\ No newline at end of file
diff --git a/components/dataloader/DataLoader.tsx b/components/dataloader/DataLoader.tsx
index 6830ea0..abe6afd 100644
--- a/components/dataloader/DataLoader.tsx
+++ b/components/dataloader/DataLoader.tsx
@@ -8,6 +8,7 @@ import { useRouter } from "next/navigation";
import ErrorContext from "../../context/ErrorContext";
import ModalComponent from "../ui-comps/ModalComponent";
import TabWrapper, { Tabs } from "../ui-comps/TabbedComponents";
+import SDFFileLoader from "./SDFFileLoader";
export default function DataLoader() {
const { ligand, setLigand } = useContext(LigandContext);
@@ -59,13 +60,20 @@ export default function DataLoader() {
-
+
-
-
+
+
+
+
+
+
+
+
+
-
+
diff --git a/components/dataloader/DataPreProcessToolKit.tsx b/components/dataloader/DataPreProcessToolKit.tsx
index 9a535f2..b3a6a7b 100644
--- a/components/dataloader/DataPreProcessToolKit.tsx
+++ b/components/dataloader/DataPreProcessToolKit.tsx
@@ -19,7 +19,7 @@ type FingerPrintSettings = {
const DataPreProcessToolKit = () => {
const [loaded, setLoaded] = useState(true);
const [stage, setStage] = useState('choose'); // Initial stage is 'choose'
- const [selection, setSelection] = useState(null);
+ const [selection, setSelection] = useState('express');
const { ligand, setLigand } = useContext(LigandContext);
const { rdkit } = useContext(RDKitContext);
const { target, setTarget } = useContext(TargetContext);
@@ -100,6 +100,7 @@ const DataPreProcessToolKit = () => {
value="express"
className='custom-radio'
onChange={(e) => setSelection(e.target.value)}
+ defaultChecked = {true}
/>
Express
diff --git a/components/dataloader/SDFFileLoader.tsx b/components/dataloader/SDFFileLoader.tsx
new file mode 100644
index 0000000..0502e63
--- /dev/null
+++ b/components/dataloader/SDFFileLoader.tsx
@@ -0,0 +1,155 @@
+import { 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 sdfFileParser from "../utils/sdfFileParser";
+import { useForm } from "react-hook-form";
+
+
+export type Inputs = {
+ id_column: string;
+ act_column?: string;
+};
+
+export default function SDFFileLoader() {
+ const { ligand, setLigand } = useContext(LigandContext);
+ const { target, setTarget } = useContext(TargetContext);
+ const { setErrors } = useContext(ErrorContext);
+ const [stage, setStage] = useState(false);
+ const [isHovered, setIsHovered] = useState(false);
+
+ const router = useRouter();
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ } = useForm();
+
+ const handleFileChange = (event) => {
+ const input = event.target;
+ const file = input.files?.[0];
+ if (file && file.name.endsWith(".sdf")) {
+ handleFile(file);
+ } else {
+ setErrors("Hey, please upload a valid SDF File");
+ }
+ };
+
+ const handleFileDrop = (event) => {
+ event.preventDefault();
+ const file = event.dataTransfer.files?.[0];
+ if (file) {
+ handleFile(file);
+ }
+ };
+
+ const handleFile = (file) => {
+ console.log("File uploaded successfully:", file.name);
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ try {
+ const sdfContent = e.target?.result as string;
+ const molecules = sdfContent.split('$$$$').filter(molecule => molecule.trim() !== '');
+ const parsedMolecules = molecules.map(molecule => sdfFileParser(molecule));
+ setLigand(parsedMolecules);
+ setStage(true)
+ } catch (error) {
+ setErrors("Please upload a valid QITB JSON File");
+ }
+ };
+ reader.readAsText(file);
+ };
+
+ const handleDragOver = (event) => {
+ event.preventDefault();
+ setIsHovered(true);
+ };
+
+ const handleDragExit = () => {
+ setIsHovered(false);
+ };
+
+ function fileSubmitHandler(data){
+ let while_process_var = ligand.map((x) => {
+ x["id"] = x[data.id_column];
+ x["canonical_smiles"] = x.molData;
+ x[data.act_column] = parseFloat(x[data.act_column]);
+ delete x[data.id_column], x[data.smi_column];
+ return x;
+ });
+ console.log(while_process_var)
+ setLigand(while_process_var);
+ setTarget({...target, data_source: "sdf", activity_columns: [data.act_column]});
+ router.push("/tools/preprocess/");
+ }
+
+ if (stage) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
{
+ const fileInput = document.getElementById("fileInput");
+ if (fileInput) {
+ fileInput.click();
+ }
+ }}
+ className={`zone ${isHovered ? "zoneHover" : ""}`}
+ >
+
Upload Your SDF File Here With Molecules.
+ You could also drag and drop the file here or Click to browse.
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/components/dataloader/TargetGetter.tsx b/components/dataloader/TargetGetter.tsx
index 62f00d5..3f0d302 100644
--- a/components/dataloader/TargetGetter.tsx
+++ b/components/dataloader/TargetGetter.tsx
@@ -67,7 +67,7 @@ export default function TargetGetter() {
{loading ? (
) : (
-
+
Target Name |
diff --git a/components/tools/toolViz/DropDown.tsx b/components/tools/toolViz/DropDown.tsx
index ae84b63..444728c 100644
--- a/components/tools/toolViz/DropDown.tsx
+++ b/components/tools/toolViz/DropDown.tsx
@@ -15,49 +15,6 @@ const Dropdown = ({ buttonText, children }) => {
{children}
-
);
};
diff --git a/components/ui-comps/PaginatedTables.tsx b/components/ui-comps/PaginatedTables.tsx
index f59d357..6b13417 100644
--- a/components/ui-comps/PaginatedTables.tsx
+++ b/components/ui-comps/PaginatedTables.tsx
@@ -2,8 +2,9 @@ import React, { useState } from "react";
import TableFooter from "./TableFooter";
import useTable from "../../hooks/useTable";
import MoleculeStructure from "../tools/toolComp/MoleculeStructure";
+import { round } from "mathjs";
-const Table = ({ data, rowsPerPage }) => {
+const Table = ({ data, rowsPerPage, act_column = [] }) => {
const [page, setPage] = useState(1);
const { slice, range } = useTable(data, page, rowsPerPage);
@@ -15,16 +16,20 @@ const Table = ({ data, rowsPerPage }) => {
ID |
SMILES |
Representation |
- {slice.length > 0 && slice[0].predictions === null ? "Activity" : "Prediction"} |
+ {act_column.map((el, i) => (
+ {el} |
+ ))}
{slice.map((el, i) => (
-
+
{el.id} |
{el.canonical_smiles} |
- |
- {el.predictions} {el.neg_log_activity_column} |
+ |
+ {act_column.map((pl, j) => (
+ {round(el[pl], 2)} |
+ ))}
))}
diff --git a/components/utils/fp_sorter.ts b/components/utils/fp_sorter.ts
index 0585788..fe6eb21 100644
--- a/components/utils/fp_sorter.ts
+++ b/components/utils/fp_sorter.ts
@@ -1,7 +1,13 @@
import bitStringToBitVector from "./bit_vect";
export default function fpSorter(fpType : string, smilesString: string, rdkit, path? : number, nBits?: number){
- const mol = rdkit.get_mol(smilesString);
+ let mol;
+ try {
+ mol = rdkit.get_mol(smilesString);
+ } catch {
+ return null;
+ }
+
let molFP;
if (fpType === "maccs"){
molFP = mol.get_maccs_fp();
@@ -13,5 +19,6 @@ export default function fpSorter(fpType : string, smilesString: string, rdkit, p
throw new Error("Error has happened")
}
mol.delete();
- return bitStringToBitVector(molFP)
+ return bitStringToBitVector(molFP);
+
}
\ No newline at end of file
diff --git a/components/utils/sdfFileParser.ts b/components/utils/sdfFileParser.ts
new file mode 100644
index 0000000..c128ed1
--- /dev/null
+++ b/components/utils/sdfFileParser.ts
@@ -0,0 +1,36 @@
+const sdfFileParser = (molecule) => {
+ const lines = molecule.split('\n');
+ let molData = [];
+ let fields = {};
+ let inMolData = true;
+ let fieldName = null;
+
+ lines.forEach(line => {
+ line = line.trimStart(); // Trim whitespace characters from the beginning of the line
+ if (inMolData) {
+ if (line.startsWith('M END')) {
+ molData.push(line);
+ inMolData = false;
+ } else {
+ molData.push(line);
+ }
+ } else {
+ if (line.startsWith('> <')) {
+ const match = line.match(/> <(.*)>/);
+ if (match) {
+ fieldName = match[1];
+ }
+ } else if (fieldName) {
+ fields[fieldName] = line.trim();
+ fieldName = null;
+ }
+ }
+ });
+
+ return {
+ molData: molData.join('\n'),
+ ...fields
+ };
+};
+
+export default sdfFileParser;
\ No newline at end of file
diff --git a/styles/form-comps.css b/styles/form-comps.css
index a3806bd..b8bf04f 100644
--- a/styles/form-comps.css
+++ b/styles/form-comps.css
@@ -1,3 +1,11 @@
+.input {
+ padding: 12px;
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ margin-top: 8px;
+ min-width:200px;
+}
+
/* Radio buttons with class 'custom-radio' */
input.custom-radio[type=radio] {
--s: 1.5em; /* control the size */
@@ -49,3 +57,35 @@ input.custom-radio[type=radio]:disabled {
margin: 5px 0;
cursor: pointer;
}
+
+.dropdown-container {
+ position: relative;
+ display: inline-block;
+}
+
+.dropdown-icon {
+ background-color: var(--accent-color);
+ border: none;
+ color: white;
+ padding: 12px;
+ margin:5px;
+ font-size: 16px;
+ cursor: pointer;
+}
+
+.dropdown-icon:hover {
+ background-color: #3e8e41;
+}
+
+.dropdown-content {
+ display: none;
+ position: absolute;
+ z-index: 1;
+ top: 100%; /* Position it below the button */
+ left: 50%;
+ transform: translateX(-50%); /* Center it horizontally */
+}
+
+.dropdown-content.visible {
+ display: block;
+}
\ No newline at end of file
diff --git a/styles/index.css b/styles/index.css
index f4cdb05..e0ea2e2 100644
--- a/styles/index.css
+++ b/styles/index.css
@@ -157,14 +157,6 @@ main {
color: var(--text-color);
}
-.input {
- padding: 8px;
- border: 1px solid var(--border-color);
- border-radius: 4px;
- margin-top: 8px;
- width: 80%;
-}
-
.centered-self-container {
height: 100%;
gap: 10px;
diff --git a/styles/tables.css b/styles/tables.css
index d34bcff..dcbe43f 100644
--- a/styles/tables.css
+++ b/styles/tables.css
@@ -1,39 +1,4 @@
-.custom-table {
- width: 100%;
- border-collapse: collapse;
- margin-bottom: 20px;
- margin-top: 10px;
- height: 100%;
- overflow-y: auto;
-}
-
-.custom-table th,
-.custom-table td {
- border: 1px solid var(--border-color);
- padding: 10px;
- text-align: left;
-}
-
-.custom-table th {
- background-color: var(--background-color);
-}
-
-.custom-table tr:hover {
- background-color: var(--secondary-color);
-}
-
-@media screen and (max-width: 600px) {
- .custom-table {
- overflow-x: auto;
- display: block;
- font-size: x-small;
- }
-
- .custom-table th,
- .custom-table td {
- white-space: nowrap;
- }
-}
+/* Table Related Miscellaneous CSS */
.tableFooter {
background-color: #f1f1f1;
@@ -69,49 +34,91 @@
background: #f9f9f9;
}
-/* sdfadfasdfadsfaf */
+/* Table CSS */
.table {
- border-collapse: collapse;
- border: none;
- width: 100%;
- margin-top: 2%;
+ border: 1px solid #ccc;
+ border-collapse: collapse;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ table-layout: fixed;
}
-.tableRowHeader {
- background-color: transparent;
- transition: all 0.25s ease;
- border-radius: 10px;
+.table caption {
+ font-size: 1.5em;
+ margin: .5em 0 .75em;
}
-.tableHeader {
- background-color: var(--tertiary-background-color);
- padding: 12px;
- font-weight: 500;
- text-align: left;
- font-size: 14px;
- color: #2c3e50;
+.table tr {
+ border: 1px solid #ddd;
+ padding: .35em;
}
-.tableHeader:first-child {
- border-top-left-radius: 15px;
+.table th,
+.table td {
+ padding: .625em;
+ text-align: center;
+ word-wrap: break-word;
+ vertical-align: middle;
}
-.tableHeader:last-child {
- border-top-right-radius: 15px;
+.table th {
+ font-size: .85em;
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ background-color: var(--tertiary-background-color);
}
-.tableRowItems {
- cursor: auto;
+.target-table tr:hover{
+ background-color: var(--secondary-color);
}
-.tableRowItems:nth-child(odd) {
- background-color: var(--secondary-color);
-}
+@media screen and (max-width: 1100px) {
+ .table {
+ border: 0;
+ }
-.tableCell {
- padding: 12px;
- font-size: 14px;
- color: var(--text-color);
- vertical-align: middle;
+ .table caption {
+ font-size: 1.3em;
+ }
+
+ .table thead {
+ border: none;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+ }
+
+ .table tr {
+ border-bottom: 3px solid #ddd;
+ display: block;
+ margin-bottom: .625em;
+ }
+
+ .table td {
+ border-bottom: 1px solid #ddd;
+ display: block;
+ font-size: .8em;
+ text-align: right;
+ }
+
+ .table td::before {
+ /*
+ * aria-label has no advantage, it won't be read inside a table
+ content: attr(aria-label);
+ */
+ content: attr(data-label);
+ float: left;
+ font-weight: bold;
+ text-transform: uppercase;
+ }
+
+ .table td:last-child {
+ border-bottom: 0;
+ }
}