diff --git a/src/datasets_synchronization/views.py b/src/datasets_synchronization/views.py
index 3e7097f7..ccfe9ab0 100644
--- a/src/datasets_synchronization/views.py
+++ b/src/datasets_synchronization/views.py
@@ -14,6 +14,7 @@
from .models import CGDSStudy, CGDSDatasetSynchronizationState, CGDSStudySynchronizationState, CGDSDataset
from rest_framework import generics, permissions, filters
from django_filters.rest_framework import DjangoFilterBackend
+from django_filters.rest_framework import DjangoFilterBackend
from user_files.models_choices import FileType
from .serializers import CGDSStudySerializer
from django.shortcuts import render, get_object_or_404
diff --git a/src/frontend/static/frontend/package-lock.json b/src/frontend/static/frontend/package-lock.json
index b6e3a75c..bbca754f 100644
--- a/src/frontend/static/frontend/package-lock.json
+++ b/src/frontend/static/frontend/package-lock.json
@@ -16,7 +16,7 @@
"@visx/tooltip": "^3.3.0",
"apexcharts": "^3.52.0",
"axios": "^1.8.4",
- "cytoscape": "^3.30.2",
+ "cytoscape": "^3.33.1",
"d3": "^7.9.0",
"dayjs": "^1.11.13",
"fomantic-ui-css": "^2.9.4",
@@ -10122,9 +10122,10 @@
"integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A=="
},
"node_modules/cytoscape": {
- "version": "3.30.2",
- "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.2.tgz",
- "integrity": "sha512-oICxQsjW8uSaRmn4UK/jkczKOqTrVqt5/1WL0POiJUT2EKNc9STM4hYFHv917yu55aTBMFNRzymlJhVAiWPCxw==",
+ "version": "3.33.1",
+ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
+ "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
+ "license": "MIT",
"engines": {
"node": ">=0.10"
}
@@ -23101,7 +23102,6 @@
"resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-3.3.0.tgz",
"integrity": "sha512-3PT9dW7IbIfN7JWGr4YxxFQnbN5MRaB36qIKF/eF0iC9l0/MuGSlMlgRgI7Uu8vYuGxX6AjLwsBBRYTPG7NFSA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@plotly/d3": "3.8.2",
"@plotly/d3-sankey": "0.7.2",
diff --git a/src/frontend/static/frontend/package.json b/src/frontend/static/frontend/package.json
index fd73478c..76dd01fa 100644
--- a/src/frontend/static/frontend/package.json
+++ b/src/frontend/static/frontend/package.json
@@ -59,7 +59,7 @@
"@visx/tooltip": "^3.3.0",
"apexcharts": "^3.52.0",
"axios": "^1.8.4",
- "cytoscape": "^3.30.2",
+ "cytoscape": "^3.33.1",
"d3": "^7.9.0",
"dayjs": "^1.11.13",
"fomantic-ui-css": "^2.9.4",
diff --git a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/CurrentMoleculeDetails.tsx b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/CurrentMoleculeDetails.tsx
index 4f463eeb..1f173352 100644
--- a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/CurrentMoleculeDetails.tsx
+++ b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/CurrentMoleculeDetails.tsx
@@ -11,6 +11,7 @@ import { GeneAssociationsNetworkPanel } from './genes/GeneAssociationsNetworkPan
import { MiRNADrugsPanel } from '../../../pipeline/experiment-result/gene-gem-details/MiRNADrugsPanel'
import { MiRNADiseasesPanel } from '../../../pipeline/experiment-result/gene-gem-details/MiRNADiseasesPanel'
import { ActionableCancerGenesPanel } from './genes/ActionableCancerGenesPanel'
+import { GeneExpressionRegulationNetworkPanel } from './genes/GeneAssociationsNetwork'
// const MENU_DEFAULT: ActiveBiomarkerMoleculeItemMenu = ActiveBiomarkerMoleculeItemMenu.DETAILS // TODO: use this
const MENU_DEFAULT: ActiveBiomarkerMoleculeItemMenu = ActiveBiomarkerMoleculeItemMenu.DETAILS
@@ -52,6 +53,8 @@ export const CurrentMoleculeDetails = (props: CurrentMoleculeDetailsProps) => {
return
case ActiveBiomarkerMoleculeItemMenu.GENE_ONTOLOGY:
return
+ case ActiveBiomarkerMoleculeItemMenu.GENE_REGULATION_ASSOCIATIONS:
+ return
case ActiveBiomarkerMoleculeItemMenu.DISEASES:
return
case ActiveBiomarkerMoleculeItemMenu.DRUGS:
diff --git a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/MoleculesDetailsMenu.tsx b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/MoleculesDetailsMenu.tsx
index 5a98008b..ec0b914c 100644
--- a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/MoleculesDetailsMenu.tsx
+++ b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/MoleculesDetailsMenu.tsx
@@ -63,6 +63,13 @@ export const MoleculesDetailsMenu = (props: MoleculesDetailsMenuProps) => {
popupInfo: 'Gene Ontology (GO) is a powerful tool for understanding the biological processes, molecular functions, and cellular components associated with a gene',
isVisible: isGene
},
+ {
+ name: 'Gene regulation associations',
+ onClick: () => props.setActiveItem(ActiveBiomarkerMoleculeItemMenu.GENE_REGULATION_ASSOCIATIONS),
+ isActive: props.activeItem === ActiveBiomarkerMoleculeItemMenu.GENE_REGULATION_ASSOCIATIONS,
+ popupInfo: 'Gene regulation associations provide insights into the regulatory relationships between genes, helping to unravel the complex mechanisms that control gene expression and cellular function',
+ isVisible: isGene
+ },
// TODO: implement
// {
// name: 'Actionable/Cancer genes',
diff --git a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/gene-ontology/GeneOntologyCytoscapeChart.tsx b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/gene-ontology/GeneOntologyCytoscapeChart.tsx
index 9b277bba..af7df28e 100644
--- a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/gene-ontology/GeneOntologyCytoscapeChart.tsx
+++ b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/gene-ontology/GeneOntologyCytoscapeChart.tsx
@@ -78,7 +78,7 @@ export const GeneOntologyCytoscapeChart = (props: GeneOntologyCytoscapeChartProp
style: {
'curve-style': 'bezier',
'target-arrow-shape': 'triangle',
- 'line-color': function (edge) {
+ 'line-color': function (edge): any {
// Sets the color of the edge depending on the relation_type attribute
const relationType: OntologyRelationTermToTermFilter = edge.data('relation_type')
diff --git a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/genes/GeneAssociationsNetwork.tsx b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/genes/GeneAssociationsNetwork.tsx
new file mode 100644
index 00000000..ad7f0e0d
--- /dev/null
+++ b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/genes/GeneAssociationsNetwork.tsx
@@ -0,0 +1,341 @@
+import React, { useEffect, useMemo, useRef } from 'react'
+import cytoscape, { Core, ElementDefinition } from 'cytoscape'
+
+type NodeKind = 'mRNA' | 'miRNA' | 'CNA' | 'Methylation' | 'Drug'
+
+type Props = {
+ height?: number | string;
+ width?: number | string;
+}
+
+const NODE_COLORS: Record = {
+ mRNA: '#4f46e5',
+ miRNA: '#db2777',
+ CNA: '#f59e0b',
+ Methylation: '#10b981',
+ Drug: '#64748b',
+}
+
+const getEdgeColor = (correlation: number) => {
+ if (correlation <= -0.5) { return '#dc2626' }
+
+ if (correlation >= 0.5) { return '#2563eb' }
+
+ return '#cbd5e1'
+}
+
+const getEdgeOpacity = (correlation: number) => {
+ const abs = Math.abs(correlation)
+
+ if (abs >= 0.8) { return 0.95 }
+ if (abs >= 0.6) { return 0.8 }
+ if (abs >= 0.5) { return 0.65 }
+
+ return 0.22
+}
+
+const getEdgeWidth = (correlation: number) => {
+ const abs = Math.abs(correlation)
+
+ if (abs >= 0.9) { return 6 }
+ if (abs >= 0.8) { return 5 }
+ if (abs >= 0.7) { return 4 }
+ if (abs >= 0.5) { return 3 }
+
+ return 1.5
+}
+
+export const GeneExpressionRegulationNetworkPanel = ({
+ height = 650,
+ width = '100%',
+}: Props) => {
+ const containerRef = useRef(null)
+ const cyRef = useRef(null)
+
+ const elements = useMemo(() => {
+ const nodes: ElementDefinition[] = [
+ { data: { id: 'mrna_pcna', label: 'PCNA', type: 'mRNA', size: 58 } },
+ { data: { id: 'mrna_fen1', label: 'FEN1', type: 'mRNA', size: 54 } },
+ { data: { id: 'mrna_rad51', label: 'RAD51', type: 'mRNA', size: 36 } },
+ { data: { id: 'mrna_pold1', label: 'POLD1', type: 'mRNA', size: 32 } },
+ { data: { id: 'mrna_lig1', label: 'LIG1', type: 'mRNA', size: 30 } },
+ { data: { id: 'mir_21', label: 'miR-21', type: 'miRNA', size: 28 } },
+ { data: { id: 'mir_34a', label: 'miR-34a', type: 'miRNA', size: 28 } },
+ { data: { id: 'mir_155', label: 'miR-155', type: 'miRNA', size: 28 } },
+ { data: { id: 'mir_200c', label: 'miR-200c', type: 'miRNA', size: 28 } },
+ { data: { id: 'cna_8q24', label: 'CNA 8q24', type: 'CNA', size: 34 } },
+ { data: { id: 'cna_17p_loss', label: '17p loss', type: 'CNA', size: 34 } },
+ { data: { id: 'cna_1q_gain', label: '1q gain', type: 'CNA', size: 34 } },
+ { data: { id: 'meth_mlh1', label: 'MLH1 meth', type: 'Methylation', size: 32 } },
+ { data: { id: 'meth_mgmt', label: 'MGMT meth', type: 'Methylation', size: 32 } },
+ { data: { id: 'drug_olaparib', label: 'Olaparib', type: 'Drug', size: 34 } },
+ { data: { id: 'drug_cisplatin', label: 'Cisplatin', type: 'Drug', size: 34 } },
+ { data: { id: 'drug_temozolomide', label: 'Temozolomide', type: 'Drug', size: 34 } },
+ { data: { id: 'mrna_apex2', label: 'APEX2', type: 'mRNA', size: 32 } },
+ ]
+
+ const rawEdges = [
+ ['mrna_pcna', 'mrna_fen1', 0.92],
+ ['mrna_pcna', 'mrna_rad51', 0.87],
+ ['mrna_pcna', 'mrna_pold1', 0.83],
+ ['mrna_pcna', 'mrna_lig1', 0.79],
+ ['mrna_pcna', 'mrna_apex2', 0.72],
+ ['mrna_fen1', 'mrna_rad51', 0.76],
+ ['mrna_fen1', 'mrna_pold1', 0.81],
+ ['mrna_fen1', 'mrna_apex2', 0.69],
+ ['mrna_rad51', 'mrna_apex2', 0.64],
+ ['mrna_pold1', 'mrna_lig1', 0.71],
+ ['mrna_lig1', 'mrna_apex2', 0.58],
+
+ ['mir_21', 'mrna_pcna', -0.74],
+ ['mir_21', 'mrna_fen1', -0.68],
+ ['mir_21', 'mrna_rad51', -0.61],
+ ['mir_34a', 'mrna_pcna', -0.71],
+ ['mir_34a', 'mrna_pold1', -0.66],
+ ['mir_155', 'mrna_rad51', -0.78],
+ ['mir_155', 'mrna_apex2', -0.57],
+ ['mir_200c', 'mrna_lig1', -0.63],
+ ['mir_200c', 'mrna_fen1', -0.55],
+
+ ['cna_8q24', 'mrna_pcna', 0.67],
+ ['cna_8q24', 'mrna_fen1', 0.54],
+ ['cna_17p_loss', 'mrna_rad51', -0.58],
+ ['cna_17p_loss', 'mrna_apex2', -0.52],
+ ['cna_1q_gain', 'mrna_pold1', 0.62],
+ ['cna_1q_gain', 'mrna_lig1', 0.57],
+ ['meth_mlh1', 'mrna_pcna', -0.53],
+ ['meth_mlh1', 'mrna_apex2', -0.64],
+ ['meth_mgmt', 'mrna_rad51', -0.59],
+ ['meth_mgmt', 'mrna_fen1', -0.51],
+
+ ['drug_olaparib', 'mrna_rad51', -0.69],
+ ['drug_olaparib', 'mrna_fen1', -0.56],
+ ['drug_cisplatin', 'mrna_pcna', -0.52],
+ ['drug_cisplatin', 'mrna_lig1', -0.55],
+ ['drug_temozolomide', 'meth_mgmt', 0.73],
+ ['drug_temozolomide', 'mrna_apex2', -0.51],
+
+ ['mir_21', 'cna_8q24', 0.22],
+ ['mir_34a', 'meth_mlh1', -0.18],
+ ['drug_olaparib', 'drug_cisplatin', 0.31],
+ ['mrna_pcna', 'drug_temozolomide', -0.27],
+ ['cna_1q_gain', 'mrna_apex2', 0.33],
+ ] as const
+
+ const edges: ElementDefinition[] = rawEdges.map(([source, target, correlation], index) => ({
+ data: {
+ id: `e_${index + 1}`,
+ source,
+ target,
+ correlation,
+ edgeColor: getEdgeColor(correlation),
+ edgeOpacity: getEdgeOpacity(correlation),
+ edgeWidth: getEdgeWidth(correlation),
+ },
+ classes: Math.abs(correlation) < 0.5 ? 'weak-edge' : '',
+ }))
+
+ return [...nodes, ...edges]
+ }, [])
+
+ useEffect(() => {
+ if (!containerRef.current) { return }
+
+ const cy = cytoscape({
+ container: containerRef.current,
+ elements,
+ wheelSensitivity: 0.18,
+ minZoom: 0.4,
+ maxZoom: 2,
+ style: [
+ {
+ selector: 'node',
+ style: {
+ label: 'data(label)',
+ width: 'data(size)',
+ height: 'data(size)',
+ shape: 'ellipse',
+ 'background-color': '#64748b',
+ color: '#ffffff',
+ 'font-size': 12,
+ 'font-weight': 600,
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'text-wrap': 'wrap',
+ 'text-max-width': 90 as any,
+ 'text-outline-color': '#475569',
+ 'text-outline-width': 3,
+ 'border-width': 2,
+ 'border-color': '#ffffff',
+ },
+ },
+ {
+ selector: 'node[type = "mRNA"]',
+ style: {
+ 'background-color': NODE_COLORS.mRNA,
+ 'text-outline-color': NODE_COLORS.mRNA,
+ },
+ },
+ {
+ selector: 'node[type = "miRNA"]',
+ style: {
+ 'background-color': NODE_COLORS.miRNA,
+ 'text-outline-color': NODE_COLORS.miRNA,
+ },
+ },
+ {
+ selector: 'node[type = "CNA"]',
+ style: {
+ 'background-color': NODE_COLORS.CNA,
+ 'text-outline-color': NODE_COLORS.CNA,
+ },
+ },
+ {
+ selector: 'node[type = "Methylation"]',
+ style: {
+ 'background-color': NODE_COLORS.Methylation,
+ 'text-outline-color': NODE_COLORS.Methylation,
+ },
+ },
+ {
+ selector: 'node[type = "Drug"]',
+ style: {
+ 'background-color': NODE_COLORS.Drug,
+ 'text-outline-color': NODE_COLORS.Drug,
+ },
+ },
+ {
+ selector: 'edge',
+ style: {
+ width: 'data(edgeWidth)',
+ 'line-color': 'data(edgeColor)',
+ opacity: 'data(edgeOpacity)' as any,
+ 'curve-style': 'bezier',
+ },
+ },
+ {
+ selector: 'edge.weak-edge',
+ style: {
+ 'line-style': 'dashed',
+ },
+ },
+ {
+ selector: ':selected',
+ style: {
+ 'border-color': '#f8fafc',
+ 'border-width': 5,
+ 'line-color': '#0f172a',
+ 'target-arrow-color': '#0f172a',
+ 'source-arrow-color': '#0f172a',
+ },
+ },
+ {
+ selector: '.faded',
+ style: {
+ opacity: 0.12,
+ },
+ },
+ ],
+ layout: {
+ name: 'cose',
+ animate: true,
+ randomize: true,
+ fit: true,
+ padding: 40,
+ gravity: 1,
+ nodeRepulsion: 9000,
+ idealEdgeLength: 90,
+ componentSpacing: 80,
+ },
+ })
+
+ cyRef.current = cy
+
+ cy.on('tap', 'node', (evt) => {
+ const node = evt.target
+ const neighborhood = node.closedNeighborhood()
+
+ cy.elements().addClass('faded')
+ neighborhood.removeClass('faded')
+ })
+
+ cy.on('tap', (evt) => {
+ if (evt.target === cy) {
+ cy.elements().removeClass('faded')
+ cy.elements().unselect()
+ }
+ })
+
+ return () => {
+ cy.destroy()
+ cyRef.current = null
+ }
+ }, [elements])
+
+ return (
+
+ )
+}
+
+const LegendDot = ({ color, label }: { color: string; label: string }) => (
+
+
+ {label}
+
+)
+
+const LegendLine = ({ color, label }: { color: string; label: string }) => (
+
+
+ {label}
+
+)
diff --git a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/genes/GeneAssociationsNetworkPanel.tsx b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/genes/GeneAssociationsNetworkPanel.tsx
index a7d5db85..4a4e00ab 100644
--- a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/genes/GeneAssociationsNetworkPanel.tsx
+++ b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/genes/GeneAssociationsNetworkPanel.tsx
@@ -1,6 +1,11 @@
-import React, { useEffect, useRef } from 'react'
+import React, { useEffect, useRef, useState } from 'react'
import ky from 'ky'
-import cytoscape from 'cytoscape'
+import cytoscape, {
+ Core,
+ ElementDefinition,
+ EventObjectNode,
+ NodeSingular
+} from 'cytoscape'
import { BiomarkerMolecule } from '../../../types'
import { Form, Grid, Input } from 'semantic-ui-react'
import { alertGeneralError } from '../../../../../utils/util_functions'
@@ -11,240 +16,314 @@ import '../../../../../css/cytoscape.css'
// Defined in biomarkers.html
declare const urlGeneAssociationsNetwork: string
-/** Colors for the String standard: https://string-db.org/cgi/help ("Network" section). */
const COLORS_BY_STRING_RELATION = {
fusion: ['Fusion', '#d90429'],
coOccurrence: ['Co-occurrence', '#3a86ff'],
experimental: ['Experimental', '#7209b7'],
textMining: ['Text mining', '#d5ae5d'],
database: ['Database', '#4cc9f0'],
- coExpression: ['Co-expression', '#10002b'] // In the FAQ says 'white', but we have a white background, so it's not visible
-}
+ coExpression: ['Co-expression', '#10002b']
+} as const
-/**
- * Renders the legends for the Cytoscape instance.
- * @returns Component.
- */
const CytoscapeLegends = () => (
Relations
{Object.entries(COLORS_BY_STRING_RELATION).map(([_, [relationDescription, color]]) => (
- - {relationDescription}
+ -
+
+ {relationDescription}
+
))}
)
-/** GeneAssociationsNetworkPanel props. */
interface GeneAssociationsNetworkPanelProps {
- /** Selected BiomarkerMolecule instance to show the options. */
- selectedGene: BiomarkerMolecule,
+ selectedGene: BiomarkerMolecule
}
-export const GeneAssociationsNetworkPanel = (props: GeneAssociationsNetworkPanelProps) => {
- const abortControllerTerm = useRef(new AbortController())
- const [minCombinedScore, setMinCombinedScore] = React.useState(950)
+type CytoscapeResponseData =
+ | ElementDefinition[]
+ | {
+ nodes?: ElementDefinition[]
+ edges?: ElementDefinition[]
+ }
+
+export const GeneAssociationsNetworkPanel = ({ selectedGene }: GeneAssociationsNetworkPanelProps) => {
+ const cyContainerRef = useRef(null)
+ const cyRef = useRef(null)
+ const abortControllerRef = useRef(null)
+ const positionsRef = useRef>({})
+ const [minCombinedScore, setMinCombinedScore] = useState(950)
+
+ const cytoscapeStyles = [
+ {
+ selector: 'core',
+ style: {
+ 'selection-box-color': '#AAD8FF',
+ 'selection-box-border-color': '#8BB0D0',
+ 'selection-box-opacity': 0.5
+ } as any
+ },
+ {
+ selector: 'node',
+ style: {
+ width: 'mapData(score, 0, 0.006769776522008331, 20, 60)',
+ height: 'mapData(score, 0, 0.006769776522008331, 20, 60)',
+ content: 'data(name)',
+ 'font-size': '12px',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'background-color': '#555',
+ 'text-outline-color': '#555',
+ 'text-outline-width': '2px',
+ color: '#fff',
+ 'overlay-padding': '6px',
+ 'z-index': 10
+ }
+ },
+ {
+ selector: 'node[?attr]',
+ style: {
+ shape: 'rectangle',
+ 'background-color': '#aaa',
+ 'text-outline-color': '#aaa',
+ width: '16px',
+ height: '16px',
+ 'font-size': '6px',
+ 'z-index': 1
+ }
+ },
+ {
+ selector: 'node[?query]',
+ style: {
+ 'background-clip': 'none',
+ 'background-fit': 'contain'
+ }
+ },
+ {
+ selector: 'node:selected',
+ style: {
+ 'border-width': '6px',
+ 'border-color': '#AAD8FF',
+ 'border-opacity': 0.5,
+ 'background-color': '#77828C',
+ 'text-outline-color': '#77828C'
+ }
+ },
+ {
+ selector: 'edge',
+ style: {
+ 'curve-style': 'haystack',
+ 'haystack-radius': 0.5,
+ opacity: 0.4,
+ 'line-color': '#bbb',
+ width: 'mapData(weight, 0, 1, 1, 8)',
+ 'overlay-padding': '3px'
+ }
+ },
+ {
+ selector: 'node.unhighlighted',
+ style: {
+ opacity: 0.2
+ }
+ },
+ {
+ selector: 'edge.unhighlighted',
+ style: {
+ opacity: 0.05
+ }
+ },
+ {
+ selector: '.highlighted',
+ style: {
+ 'z-index': 999999
+ }
+ },
+ {
+ selector: 'node.highlighted',
+ style: {
+ 'border-width': '6px',
+ 'border-color': '#AAD8FF',
+ 'border-opacity': 0.5,
+ 'background-color': '#394855',
+ 'text-outline-color': '#394855'
+ }
+ },
+ {
+ selector: 'edge.filtered',
+ style: {
+ opacity: 0
+ }
+ },
+ {
+ selector: 'edge[group="fusion"]',
+ style: {
+ 'line-color': COLORS_BY_STRING_RELATION.fusion[1]
+ }
+ },
+ {
+ selector: 'edge[group="coOccurrence"]',
+ style: {
+ 'line-color': COLORS_BY_STRING_RELATION.coOccurrence[1]
+ }
+ },
+ {
+ selector: 'edge[group="experimental"]',
+ style: {
+ 'line-color': COLORS_BY_STRING_RELATION.experimental[1]
+ }
+ },
+ {
+ selector: 'edge[group="textMining"]',
+ style: {
+ 'line-color': COLORS_BY_STRING_RELATION.textMining[1]
+ }
+ },
+ {
+ selector: 'edge[group="database"]',
+ style: {
+ 'line-color': COLORS_BY_STRING_RELATION.database[1]
+ }
+ },
+ {
+ selector: 'edge[group="coExpression"]',
+ style: {
+ 'line-color': COLORS_BY_STRING_RELATION.coExpression[1]
+ }
+ }
+ ]
+
+ const normalizeElements = (elements: CytoscapeResponseData): ElementDefinition[] => {
+ if (Array.isArray(elements)) {
+ return elements
+ }
+
+ return [...(elements.nodes || []), ...(elements.edges || [])]
+ }
+
+ const createCy = () => {
+ if (cyRef.current || !cyContainerRef.current) { return }
- /**
- * Initializes the Cytoscape instance with the given elements.
- * @param elements Cytoscape elements to initialize the instance.
- */
- const initCytoscape = (elements: any /* TODO: type */) => {
const cy = cytoscape({
- container: document.getElementById('cy'),
+ container: cyContainerRef.current,
minZoom: 0.5,
maxZoom: 1.5,
- randomize: true,
- animate: true,
- nodeSpacing: 15,
- edgeLengthVal: 45,
- style: [
- {
- selector: 'core',
- style: {
- 'selection-box-color': '#AAD8FF',
- 'selection-box-border-color': '#8BB0D0',
- 'selection-box-opacity': '0.5'
- }
- },
- {
- selector: 'node',
- style: {
- width: 'mapData(score, 0, 0.006769776522008331, 20, 60)',
- height: 'mapData(score, 0, 0.006769776522008331, 20, 60)',
- content: 'data(name)',
- 'font-size': '12px',
- 'text-valign': 'center',
- 'text-halign': 'center',
- 'background-color': '#555',
- 'text-outline-color': '#555',
- 'text-outline-width': '2px',
- color: '#fff',
- 'overlay-padding': '6px',
- 'z-index': '10'
- }
- },
- {
- selector: 'node[?attr]',
- style: {
- shape: 'rectangle',
- 'background-color': '#aaa',
- 'text-outline-color': '#aaa',
- width: '16px',
- height: '16px',
- 'font-size': '6px',
- 'z-index': '1'
- }
- },
- {
- selector: 'node[?query]',
- style: {
- 'background-clip': 'none',
- 'background-fit': 'contain'
- }
- },
- {
- selector: 'node:selected',
- style: {
- 'border-width': '6px',
- 'border-color': '#AAD8FF',
- 'border-opacity': '0.5',
- 'background-color': '#77828C',
- 'text-outline-color': '#77828C'
- }
- },
- {
- selector: 'edge',
- style: {
- 'curve-style': 'haystack',
- 'haystack-radius': '0.5',
- opacity: '0.4',
- 'line-color': '#bbb',
- width: 'mapData(weight, 0, 1, 1, 8)',
- 'overlay-padding': '3px'
- }
- },
- {
- selector: 'node.unhighlighted',
- style: {
- opacity: '0.2'
- }
- },
- {
- selector: 'edge.unhighlighted',
- style: {
- opacity: '0.05'
- }
- },
- {
- selector: '.highlighted',
- style: {
- 'z-index': '999999'
- }
- },
- {
- selector: 'node.highlighted',
- style: {
- 'border-width': '6px',
- 'border-color': '#AAD8FF',
- 'border-opacity': '0.5',
- 'background-color': '#394855',
- 'text-outline-color': '#394855'
- }
- },
- {
- selector: 'edge.filtered',
- style: {
- opacity: '0'
- }
- },
- // Types of relations
- {
- selector: 'edge[group="fusion"]',
- style: {
- 'line-color': COLORS_BY_STRING_RELATION.fusion[1]
- }
- },
- {
- selector: 'edge[group="coOccurrence"]',
- style: {
- 'line-color': COLORS_BY_STRING_RELATION.coOccurrence[1]
- }
- },
- {
- selector: 'edge[group="experimental"]',
- style: {
- 'line-color': COLORS_BY_STRING_RELATION.experimental[1]
- }
- },
- {
- selector: 'edge[group="textMining"]',
- style: {
- 'line-color': COLORS_BY_STRING_RELATION.textMining[1]
- }
- },
- {
- selector: 'edge[group="database"]',
- style: {
- 'line-color': COLORS_BY_STRING_RELATION.database[1]
- }
- },
- {
- selector: 'edge[group="coExpression"]',
- style: {
- 'line-color': COLORS_BY_STRING_RELATION.coExpression[1]
- }
+ elements: [],
+ style: cytoscapeStyles,
+ layout: {
+ name: 'preset'
+ }
+ })
+
+ cy.on('dragfreeon', 'node', (evt: EventObjectNode) => {
+ const node = evt.target
+ positionsRef.current[node.id()] = node.position()
+ })
+
+ cyRef.current = cy
+ }
+
+ const applyElements = (rawElements: CytoscapeResponseData) => {
+ const cy = cyRef.current
+
+ if (!cy) { return }
+
+ const incomingElements = normalizeElements(rawElements)
+
+ cy.nodes().forEach((node: NodeSingular) => {
+ positionsRef.current[node.id()] = node.position()
+ })
+
+ const mergedElements = incomingElements.map((element) => {
+ const elementId = element?.data?.id as string | undefined
+ const savedPosition = elementId ? positionsRef.current[elementId] : undefined
+ const isNode = !('source' in (element.data || {})) && !('target' in (element.data || {}))
+
+ if (isNode && savedPosition) {
+ return {
+ ...element,
+ position: savedPosition
}
- ],
- elements
+ }
+
+ return element
})
- const layout = cy.elements().layout({
- name: 'random'
+ cy.batch(() => {
+ cy.elements().remove()
+ cy.add(mergedElements)
})
- layout.run()
+ const hasSavedPositions = cy
+ .nodes()
+ .toArray()
+ .some((node: NodeSingular) => Boolean(positionsRef.current[node.id()]))
+
+ if (hasSavedPositions) {
+ cy.layout({
+ name: 'preset',
+ fit: true,
+ padding: 30,
+ animate: false
+ }).run()
+ } else {
+ cy.layout({
+ name: 'cose',
+ fit: true,
+ padding: 30,
+ animate: false,
+ randomize: true
+ }).run()
+ }
+
+ cy.resize()
+ cy.fit(undefined, 30)
}
- /**
- * Gets all the related genes to the given gene.
- * @param selectedGene Gene object.
- */
- const getRelatedGenes = (selectedGene: BiomarkerMolecule) => {
- const searchParams = { gene_id: selectedGene.identifier, min_combined_score: minCombinedScore }
-
- ky.get(urlGeneAssociationsNetwork, { searchParams: searchParams as any, signal: abortControllerTerm.current.signal }).then((response) => {
- response.json().then((data: { data: any /* TODO: type */ }) => {
- // The response is a CytoscapeElements object already
- initCytoscape(data.data)
- }).catch((err) => {
- alertGeneralError()
- console.log('Error parsing JSON ->', err)
+ const getRelatedGenes = async (gene: BiomarkerMolecule, score: number) => {
+ abortControllerRef.current?.abort()
+ abortControllerRef.current = new AbortController()
+
+ try {
+ const response = await ky.get(urlGeneAssociationsNetwork, {
+ searchParams: {
+ gene_id: gene.identifier,
+ min_combined_score: score
+ } as any,
+ signal: abortControllerRef.current.signal
})
- }).catch((err) => {
- if (!abortControllerTerm.current.signal.aborted) {
+
+ const data = await response.json<{ data: CytoscapeResponseData }>()
+ applyElements(data.data)
+ } catch (err) {
+ if (!abortControllerRef.current?.signal.aborted) {
alertGeneralError()
}
console.log('Error getting experiment', err)
- })
+ }
}
useEffect(() => {
- getRelatedGenes(props.selectedGene)
- }, [props.selectedGene])
+ createCy()
- /** On unmount, cancels the request */
- useEffect(() => {
return () => {
- // Cleanup: cancel the ongoing request when component unmounts
- abortControllerTerm.current.abort()
+ abortControllerRef.current?.abort()
+ cyRef.current?.destroy()
+ cyRef.current = null
}
}, [])
+ useEffect(() => {
+ if (!cyRef.current) { return }
+
+ getRelatedGenes(selectedGene, minCombinedScore)
+ }, [selectedGene, minCombinedScore])
+
return (
@@ -257,27 +336,41 @@ export const GeneAssociationsNetworkPanel = (props: GeneAssociationsNetworkPanel
value={minCombinedScore}
min={900}
max={1000}
- // TODO: implement with debounce
onChange={(_e, { value }) => setMinCombinedScore(Number(value))}
/>
+
- The combined score is computed by combining the probabilities from the different evidence channels and corrected for the probability of randomly observing an interaction. For a more detailed description please see von Mering, et al. Nucleic Acids Res. 2005
+ The combined score is computed by combining the probabilities from the different evidence channels and corrected for the probability of randomly observing an interaction. For a more detailed description please see{' '}
+
+ von Mering, et al. Nucleic Acids Res. 2005
+
)}
onTop={false}
onEvent='click'
/>
+
-
+
diff --git a/src/frontend/static/frontend/src/components/biomarkers/types.ts b/src/frontend/static/frontend/src/components/biomarkers/types.ts
index 131ae12d..98d2b66f 100644
--- a/src/frontend/static/frontend/src/components/biomarkers/types.ts
+++ b/src/frontend/static/frontend/src/components/biomarkers/types.ts
@@ -389,6 +389,7 @@ enum ActiveBiomarkerMoleculeItemMenu {
PATHWAYS,
GENE_ASSOCIATIONS_NETWORK,
GENE_ONTOLOGY,
+ GENE_REGULATION_ASSOCIATIONS,
INFERENCE,
DISEASES,
DRUGS,
diff --git a/src/frontend/static/frontend/src/components/differential-expression/VolcanoPlot.tsx b/src/frontend/static/frontend/src/components/differential-expression/VolcanoPlot.tsx
index f1a666c9..5be970cd 100644
--- a/src/frontend/static/frontend/src/components/differential-expression/VolcanoPlot.tsx
+++ b/src/frontend/static/frontend/src/components/differential-expression/VolcanoPlot.tsx
@@ -74,64 +74,64 @@ export const VolcanoPlot = ({
},
]
: []
+ return null
+ // return (
+ // d.log2FC),
+ // y: significant.map((d) => transformP(d.pValue)),
+ // text: significant.map((d) => d.label ?? d.id),
+ // mode: 'markers',
+ // type: 'scattergl',
+ // name: 'Significant',
+ // marker: { size: 6 },
+ // hovertemplate:
+ // 'Significant
' +
+ // 'log2FC: %{x:.2f}
' +
+ // '-log10(p): %{y:.2f}
' +
+ // '%{text}' +
+ // '',
+ // },
+ // {
+ // x: nonsignificant.map((d) => d.log2FC),
+ // y: nonsignificant.map((d) => transformP(d.pValue)),
+ // text: nonsignificant.map((d) => d.label ?? d.id),
+ // mode: 'markers',
+ // type: 'scattergl',
+ // name: 'Not significant',
+ // marker: { size: 4, opacity: 0.6 },
+ // hovertemplate:
+ // 'Not significant
' +
+ // 'log2FC: %{x:.2f}
' +
+ // '-log10(p): %{y:.2f}
' +
+ // '%{text}' +
+ // '',
+ // },
+ // ]}
+ // layout={{
+ // title: 'Volcano Plot',
+ // hovermode: 'closest',
+ // showlegend: true,
+ // margin: { l: 90, r: 40, t: 50, b: 70 },
- return (
- d.log2FC),
- y: significant.map((d) => transformP(d.pValue)),
- text: significant.map((d) => d.label ?? d.id),
- mode: 'markers',
- type: 'scattergl',
- name: 'Significant',
- marker: { size: 6 },
- hovertemplate:
- 'Significant
' +
- 'log2FC: %{x:.2f}
' +
- '-log10(p): %{y:.2f}
' +
- '%{text}' +
- '',
- },
- {
- x: nonsignificant.map((d) => d.log2FC),
- y: nonsignificant.map((d) => transformP(d.pValue)),
- text: nonsignificant.map((d) => d.label ?? d.id),
- mode: 'markers',
- type: 'scattergl',
- name: 'Not significant',
- marker: { size: 4, opacity: 0.6 },
- hovertemplate:
- 'Not significant
' +
- 'log2FC: %{x:.2f}
' +
- '-log10(p): %{y:.2f}
' +
- '%{text}' +
- '',
- },
- ]}
- layout={{
- title: 'Volcano Plot',
- hovermode: 'closest',
- showlegend: true,
- margin: { l: 90, r: 40, t: 50, b: 70 },
-
- // Axis labels (normal, not floating)
- xaxis: {
- title: { text: 'log2(Fold Change)', standoff: 20 },
- zeroline: false,
- },
- yaxis: {
- title: { text: '-log10(p-value)', standoff: 10 },
- zeroline: false,
- },
- shapes: thresholdShapes,
- }}
- config={{
- responsive: true,
- displayModeBar: true,
- }}
- style={{ width: '100%', height: '500px' }}
- />
- )
+ // // Axis labels (normal, not floating)
+ // xaxis: {
+ // title: { text: 'log2(Fold Change)', standoff: 20 },
+ // zeroline: false,
+ // },
+ // yaxis: {
+ // title: { text: '-log10(p-value)', standoff: 10 },
+ // zeroline: false,
+ // },
+ // shapes: thresholdShapes,
+ // }}
+ // config={{
+ // responsive: true,
+ // displayModeBar: true,
+ // }}
+ // style={{ width: '100%', height: '500px' }}
+ // />
+ // )
}
diff --git a/src/frontend/static/frontend/src/components/pipeline/experiment-result/BiomarkerFromCorrelationModal.tsx b/src/frontend/static/frontend/src/components/pipeline/experiment-result/BiomarkerFromCorrelationModal.tsx
new file mode 100644
index 00000000..a6123bb0
--- /dev/null
+++ b/src/frontend/static/frontend/src/components/pipeline/experiment-result/BiomarkerFromCorrelationModal.tsx
@@ -0,0 +1,1606 @@
+import React from 'react'
+// Update the import path to the correct location of Base component
+import { Modal, DropdownItemProps, Icon, Form, Button, Confirm } from 'semantic-ui-react'
+import { DjangoCGDSStudy, DjangoMRNAxGEMResultRow, DjangoSurvivalColumnsTupleSimple, DjangoTag, DjangoUserFile } from '../../../utils/django_interfaces'
+import ky, { Options } from 'ky'
+import { getDjangoHeader, cleanRef, getFilenameFromSource, getDefaultSource } from '../../../utils/util_functions'
+import { NameOfCGDSDataset, Nullable, CustomAlert, CustomAlertTypes, SourceType, ConfirmModal, ExperimentInfo, ExperimentResultTableControl } from '../../../utils/interfaces'
+import { Biomarker, BiomarkerType, BiomarkerOrigin, FormBiomarkerData, MoleculesSectionData, MoleculesTypeOfSelection, SaveBiomarkerStructure, SaveMoleculeStructure, FeatureSelectionPanelData, SourceStateBiomarker, FeatureSelectionAlgorithm, FitnessFunction, FitnessFunctionParameters, BiomarkerState, AdvancedAlgorithm as AdvancedAlgorithmParameters, BBHAVersion, BiomarkerSimple, CrossValidationParameters } from '../../biomarkers/types'
+import { ManualForm } from '../../biomarkers/modalContentBiomarker/manualForm/ManualForm'
+import { PaginationCustomFilter } from '../../common/PaginatedTable'
+import { isEqual } from 'lodash'
+import { getDefaultClusteringParameters, getDefaultRFParameters, getDefaultSvmParameters } from '../../biomarkers/utils'
+import { BiomarkerDetailsModal } from '../../biomarkers/BiomarkerDetailsModal'
+import { Alert } from '../../common/Alert'
+
+// URLs defined in gem.html
+declare const urlBiomarkersCRUD: string
+declare const urlBiomarkersSimpleUpdate: string
+declare const urlBiomarkersCreate: string
+declare const urlMiRNACodes: string
+declare const urlGeneSymbols: string
+declare const urlMethylationSites: string
+declare const urlMiRNACodesFinder: string
+declare const urlMethylationSitesFinder: string
+declare const urlGeneSymbolsFinder: string
+
+const REQUEST_TIMEOUT = 120000 // 2 minutes in milliseconds
+type SelectedOption = 'selectAll' | 'selectWithFilters'
+
+/** A matched molecule with the search query and the validated alias. */
+type MoleculeFinderResult = { molecule: string, standard: string }
+
+/** Extremely simple struct of a Biomarker (useful for simple updates). */
+type BiomarkerNameAndDesc = {
+ name: string,
+ description: string
+}
+
+/** Some flags to validate the Biomarkers form. */
+type ValidationForm = {
+ haveAmbiguous: boolean,
+ haveInvalid: boolean
+}
+
+interface BiomarkerFromCorrelationModalProps {
+ experimentInfo: ExperimentInfo;
+ tableControl: ExperimentResultTableControl;
+}
+
+/** BiomarkersPanel's state */
+interface BiomarkerFromCorrelationModalState {
+ biomarkers: BiomarkerSimple[],
+ newBiomarker: Biomarker,
+ /** PK of the Biomarker that's being loaded. */
+ loadingFullBiomarkerId: Nullable,
+ selectedBiomarkerToDeleteOrSync: Nullable,
+ checkedIgnoreProposedAlias: boolean,
+ showDeleteBiomarkerModal: boolean,
+ /** Indicates if there's a Biomarker being deleted. */
+ deletingBiomarker: boolean,
+ /** Indicates if there's a Biomarker being stopped. */
+ stoppingExperiment: boolean,
+ /** Biomarker to stop. */
+ biomarkerToStop: Nullable,
+ addingOrEditingBiomarker: boolean,
+ biomarkerTypeSelected: BiomarkerOrigin,
+ formBiomarker: FormBiomarkerData,
+ confirmModal: ConfirmModal
+ tags: DjangoTag[],
+ /** Indicates if the modal to create or edit a Biomarker is open. */
+ openCreateEditBiomarkerModal: boolean,
+ /** Indicates if the modal to clone a Biomarker is open. Contains the pk of the Biomarker to clone. */
+ biomarkerToClone: Nullable,
+ /** Indicates if there's a Biomarker being cloned. */
+ cloningBiomarker: boolean,
+ /** Indicates if the modal to get the details of a Biomarker is open. */
+ openDetailsModal: boolean,
+ /** Selected Biomarker instance to show its details. */
+ selectedBiomarker: Nullable,
+ alert: CustomAlert,
+ featureSelection: FeatureSelectionPanelData,
+ submittingFSExperiment: boolean,
+ openDetailsModal2: boolean,
+ selectedOption: SelectedOption,
+ openSelectOptionModal: boolean,
+ experimentInfoWithoutFilters: ExperimentInfo,
+ modalReady: boolean,
+}
+
+/**
+ * Renders a CRUD panel for a Biomarker.
+ */
+export class BiomarkerFromCorrelationModal extends React.Component {
+ abortController = new AbortController()
+ constructor (props) {
+ super(props)
+ this.state = {
+ biomarkers: [],
+ newBiomarker: this.getDefaultNewBiomarker(),
+ loadingFullBiomarkerId: null,
+ biomarkerTypeSelected: BiomarkerOrigin.BASE,
+ checkedIgnoreProposedAlias: false,
+ showDeleteBiomarkerModal: false,
+ stoppingExperiment: false,
+ biomarkerToStop: null,
+ selectedBiomarkerToDeleteOrSync: null,
+ deletingBiomarker: false,
+ addingOrEditingBiomarker: false,
+ formBiomarker: this.getDefaultFormBiomarker(),
+ confirmModal: this.getDefaultConfirmModal(),
+ tags: [],
+ openCreateEditBiomarkerModal: false,
+ cloningBiomarker: false,
+ biomarkerToClone: null,
+ openDetailsModal: false,
+ selectedBiomarker: null,
+ alert: this.getDefaultAlertProps(),
+ featureSelection: this.getDefaultFeatureSelectionProps(),
+ submittingFSExperiment: false,
+ openDetailsModal2: false,
+ selectedOption: 'selectAll',
+ openSelectOptionModal: false,
+ modalReady: false,
+ experimentInfoWithoutFilters: {
+ ...props.experimentInfo,
+ rows: [...props.experimentInfo.rows]
+ }
+ }
+ }
+
+ /**
+ * Abort controller if component is render
+ */
+
+ componentWillUnmount () {
+ this.abortController.abort()
+ }
+
+ /**
+ * Generates default feature selection creation structure
+ * @returns Default the default Alert
+ */
+ getDefaultFeatureSelectionProps = (): FeatureSelectionPanelData => {
+ return {
+ step: 1,
+ biomarker: null,
+ selectedBiomarker: null,
+ clinicalSource: getDefaultSource(),
+ mRNASource: getDefaultSource(),
+ mirnaSource: getDefaultSource(),
+ methylationSource: getDefaultSource(),
+ cnaSource: getDefaultSource(),
+ algorithm: FeatureSelectionAlgorithm.BLIND_SEARCH,
+ fitnessFunction: FitnessFunction.CLUSTERING,
+ fitnessFunctionParameters: this.getDefaultFitnessFunctionParameters(),
+ advancedAlgorithmParameters: this.getDefaultAdvancedAlgorithmParameters(),
+ crossValidationParameters: { folds: 10 }
+ }
+ }
+
+ /**
+ * Handle changes in the checkedIgnoreProposedAlias value.
+ * @param checkedIgnoreProposedAlias New checkedIgnoreProposedAlias value.
+ */
+ handleChangeIgnoreProposedAlias = (checkedIgnoreProposedAlias: boolean) => {
+ // Clear all the proposed molecules as they are not valid anymore (they are computed on search only)
+ const formBiomarker = this.state.formBiomarker
+ formBiomarker.moleculesSymbolsFinder.data = []
+
+ this.setState({ checkedIgnoreProposedAlias, formBiomarker })
+ }
+
+ /**
+ * Generates default settings for advance Algorithm data.
+ * @returns Default structure of all advance algorithms.
+ */
+ getDefaultAdvancedAlgorithmParameters = (): AdvancedAlgorithmParameters => ({
+ isActive: false,
+ BBHA: {
+ useSpark: true,
+ numberOfStars: 60,
+ numberOfIterations: 10,
+ BBHAVersion: BBHAVersion.ORIGINAL,
+ coeff1: 2.2,
+ coeff2: 0.1
+ },
+ GA: {
+ useSpark: true,
+ numberOfIterations: 10,
+ populationSize: 50,
+ mutationRate: 0.01
+ },
+ coxRegression: {
+ useSpark: true,
+ topN: 5
+ }
+ })
+
+ /**
+ * Generates default settings for all the fitness functions.
+ * @returns Default structure for all the fitness functions.
+ */
+ getDefaultFitnessFunctionParameters = (): FitnessFunctionParameters => ({
+ clusteringParameters: { ...getDefaultClusteringParameters(), lookForOptimalNClusters: false }, // TODO: Change to default when implemented in backend
+ svmParameters: getDefaultSvmParameters(),
+ rfParameters: getDefaultRFParameters()
+ })
+
+ /**
+ * Generates a default alert structure
+ * @returns Default the default Alert
+ */
+ getDefaultAlertProps = (): CustomAlert => {
+ return {
+ message: '', // This have to change during cycle of component
+ isOpen: false,
+ type: CustomAlertTypes.SUCCESS,
+ duration: 500
+ }
+ }
+
+ /**
+ * Generates a default confirm modal structure
+ * @returns Default confirmModal object
+ */
+ getDefaultConfirmModal = (): ConfirmModal => {
+ return {
+ confirmModal: false,
+ headerText: '',
+ contentText: '',
+ onConfirm: () => console.log('DefaultConfirmModalFunction, this should change during cycle of component')
+ }
+ }
+
+ handleCloseStopFSExperiment = () => {
+ this.setState({ biomarkerToStop: null })
+ }
+
+ /**
+ * Reset the confirm modal, to be used again
+ */
+ handleCloseAlert = () => {
+ const alert = this.state.alert
+ alert.isOpen = false
+ this.setState({ alert })
+ }
+
+ /**
+ * Reset the confirm modal, to be used again
+ */
+ handleCancelConfirmModalState () {
+ this.setState({ confirmModal: this.getDefaultConfirmModal() })
+ }
+
+ /**
+ * Toggle if advance is active or not
+ */
+ handleSwitchAdvanceAlgorithm = () => {
+ const featureSelection = this.state.featureSelection
+ featureSelection.advancedAlgorithmParameters.isActive = !featureSelection.advancedAlgorithmParameters.isActive
+ this.setState({ featureSelection })
+ }
+
+ /**
+ * Change the value of the advance algorithm and prop selected
+ * @param advanceAlgorithm Advance algorithm selected
+ * @param name name of prop to change
+ * @param value value to set
+ */
+ handleChangeAdvanceAlgorithm = (advanceAlgorithm: string, name: string, value: any) => {
+ const featureSelection = this.state.featureSelection
+ featureSelection.advancedAlgorithmParameters[advanceAlgorithm][name] = value
+ this.setState({ featureSelection })
+ }
+
+ /**
+ * Callback when a new file is selected in the uncontrolled component
+ * (input type=file)
+ */
+ selectNewFile = () => { this.updateSourceFilenamesAndCommonSamples() }
+
+ /**
+ * Select the algorithm, initialize the state of the selected and clean the others states
+ * @param algorithm algorithm selected
+ */
+ handleChangeAlgorithm = (algorithm: FeatureSelectionAlgorithm) => {
+ const featureSelection = this.state.featureSelection
+ featureSelection.algorithm = algorithm
+ this.setState({ featureSelection })
+ }
+
+ /**
+ * Select the algorithm, initialize the state of the selected and clean the others states
+ * @param fitnessFunction Fitness function selected
+ */
+ handleChangeFitnessFunction = (fitnessFunction: FitnessFunction) => {
+ const featureSelection = this.state.featureSelection
+ featureSelection.fitnessFunction = fitnessFunction
+ this.setState({ featureSelection })
+ }
+
+ /**
+ * Manage changes of Feature Selection process parameters.
+ * @param fitnessFunction name of fitness function to change
+ * @param key name of the fitnessFunction object that have changed
+ * @param value value selected typed depends of what fitness function and key is being changing
+ */
+ handleChangeFitnessFunctionOption = (fitnessFunction: T, key: M, value: FitnessFunctionParameters[T][M]) => {
+ const featureSelection = this.state.featureSelection
+ featureSelection.fitnessFunctionParameters[fitnessFunction][key] = value
+ this.setState({ featureSelection })
+ }
+
+ /**
+ * Manage changes of CrossValidation parameters.
+ * @param key name of the crossValidationParameters object that have changed.
+ * @param value value selected.
+ */
+ handleChangeCrossValidation = (key: T, value: any) => {
+ const featureSelection = this.state.featureSelection
+ featureSelection.crossValidationParameters[key] = value
+ this.setState({ featureSelection })
+ }
+
+ /**
+ * Selects a CGDS Study as a source
+ * @param selectedStudy Selected Study as Source
+ * @param sourceStateName Source's name in state object to update
+ */
+ selectStudy = (selectedStudy: DjangoCGDSStudy, sourceStateName: SourceStateBiomarker) => {
+ // Selects source to update
+ const featureSelection = this.state.featureSelection
+ const source = featureSelection[sourceStateName]
+
+ source.type = SourceType.CGDS
+ source.CGDSStudy = selectedStudy
+ this.setState({ featureSelection }, this.updateSourceFilenamesAndCommonSamples)
+ }
+
+ /**
+ * Selects a User's file as a source
+ * @param selectedFile Selected file as Source
+ * @param sourceStateName Source's name in state object to update
+ */
+ selectUploadedFile = (selectedFile: DjangoUserFile, sourceStateName: SourceStateBiomarker) => {
+ // Selects source to update
+ const featureSelection = this.state.featureSelection
+ const source = featureSelection[sourceStateName]
+
+ source.type = SourceType.UPLOADED_DATASETS
+ source.selectedExistingFile = selectedFile
+ this.setState({ featureSelection }, this.updateSourceFilenamesAndCommonSamples)
+ }
+
+ /**
+ * Change the source state to submit a pipeline
+ * @param sourceType New selected Source
+ * @param sourceStateName Source's name in state object to update
+ */
+ handleChangeSourceType = (sourceType: SourceType, sourceStateName: SourceStateBiomarker) => {
+ // Selects source to update
+ const featureSelection = this.state.featureSelection
+ const source = featureSelection[sourceStateName]
+ // Change source type
+ source.type = sourceType
+
+ // Resets all source values
+ source.selectedExistingFile = null
+ source.CGDSStudy = null
+ cleanRef(source.newUploadedFileRef)
+
+ // After update state
+ this.setState({ featureSelection }, this.updateSourceFilenamesAndCommonSamples)
+ }
+
+ /**
+ * Updates Sources' filenames and common examples counter
+ */
+ updateSourceFilenamesAndCommonSamples = () => {
+ this.updateSourceFilenames()
+ // this.checkCommonSamples() TODO: check function and dependencies functions in file Pipeline.tsx
+ }
+
+ /**
+ * Handles file input changes to set data to show in form
+ * IMPORTANT: this is necessary because the file inputs are uncontrolled components
+ * and doesn't trigger an update of the state fields
+ */
+ updateSourceFilenames = () => {
+ // Updates state filenames
+ const featureSelection = this.state.featureSelection
+ featureSelection.mRNASource.filename = getFilenameFromSource(featureSelection.mRNASource)
+ featureSelection.clinicalSource.filename = getFilenameFromSource(featureSelection.clinicalSource)
+ featureSelection.cnaSource.filename = getFilenameFromSource(featureSelection.cnaSource)
+ featureSelection.methylationSource.filename = getFilenameFromSource(featureSelection.methylationSource)
+ featureSelection.mirnaSource.filename = getFilenameFromSource(featureSelection.mirnaSource)
+ this.setState({ featureSelection })
+ }
+
+ /**
+ * Changes confirm modal state
+ * @param setOption New state of option
+ * @param headerText Optional text of header in confirm modal, by default will be empty
+ * @param contentText optional text of content in confirm modal, by default will be empty
+ * @param onConfirm Modal onConfirm callback
+ */
+ handleChangeConfirmModalState = (setOption: boolean, headerText: string, contentText: string, onConfirm: () => void) => {
+ const confirmModal = this.state.confirmModal
+ confirmModal.confirmModal = setOption
+ confirmModal.headerText = headerText
+ confirmModal.contentText = contentText
+ confirmModal.onConfirm = onConfirm
+ this.setState({ confirmModal })
+ }
+
+ /**
+ * Disambiguate the selected molecule for the yellow buttons.
+ * @param moleculeToDisambiguate Molecule to disambiguate.
+ * @param section Molecule section.
+ * @param selectedOption Selected option.
+ */
+ handleSelectOptionMolecule = (moleculeToDisambiguate: MoleculesSectionData, section: BiomarkerType, selectedOption: string) => {
+ const formBiomarker = this.state.formBiomarker
+ const indexToSelect = formBiomarker.moleculesSection[section].data.findIndex((item) => isEqual(item.value, moleculeToDisambiguate.value))
+
+ // Checks if the molecule is already a valid one
+ const exists = formBiomarker.moleculesSection[section].data.some((item) => item.value === selectedOption)
+ const data: MoleculesSectionData[] = formBiomarker.moleculesSection[section].data
+ data.splice(indexToSelect, 1)
+
+ if (!exists) {
+ data.push({
+ isValid: true,
+ value: selectedOption
+ })
+ }
+
+ this.setState({
+ ...this.state,
+ formBiomarker: {
+ ...this.state.formBiomarker,
+ moleculesSection: {
+ ...this.state.formBiomarker.moleculesSection,
+ [section]: {
+ ...this.state.formBiomarker.moleculesSection[section],
+ data
+ }
+ }
+ }
+ })
+ }
+
+ /**
+ * Method that select how the user is going to create a Biomarker
+ * @param type Select the way to create a Biomarker
+ */
+ handleSelectModal = (type: BiomarkerOrigin) => {
+ this.setState({ biomarkerTypeSelected: type })
+ }
+
+ getBiomarkerFullInstance = (biomarkerSimple: BiomarkerSimple): Promise => {
+ return new Promise((resolve, reject) => {
+ this.setState({ loadingFullBiomarkerId: biomarkerSimple.id })
+ ky.get(urlBiomarkersCRUD + '/' + biomarkerSimple.id + '/', { signal: this.abortController.signal }).then((response) => {
+ response.json().then((jsonResponse) => {
+ resolve(jsonResponse)
+ }).catch((err) => {
+ console.error('Error parsing JSON on Biomarker retrieval:', err)
+ reject(err)
+ })
+ }).catch((err) => {
+ console.error('Error getting Biomarker:', err)
+
+ if (!this.abortController.signal.aborted) {
+ reject(err)
+ }
+ }).finally(() => {
+ if (!this.abortController.signal.aborted) {
+ this.setState({ loadingFullBiomarkerId: null })
+ }
+ })
+ })
+ }
+
+ /**
+ * Opens the modal to show all the Biomarker details.
+ * @param selectedBiomarker Selected Biomarker instance.
+ */
+ openBiomarkerDetailsModal = (selectedBiomarker: BiomarkerSimple) => {
+ this.getBiomarkerFullInstance(selectedBiomarker).then((biomarker) => {
+ this.setState({
+ selectedBiomarker: {
+ ...selectedBiomarker,
+ cnas: biomarker.cnas,
+ mirnas: biomarker.mirnas,
+ methylations: biomarker.methylations,
+ mrnas: biomarker.mrnas
+ },
+ openDetailsModal: true
+ })
+ })
+ }
+
+ /** Closes the modal of Biomarker's details. */
+ closeBiomarkerDetailsModal = () => {
+ this.setState({
+ selectedBiomarker: null,
+ openDetailsModal: false
+ })
+ }
+
+ /**
+ * Checks if the user can edit the Biomarker (i.e. It was not used for an Inference experiment, Statistical Validation or Trained Model).
+ * @param biomarker Biomarker to check.
+ * @returns True if the Biomarker can be edited, false otherwise.
+ */
+ canEditBiomarker = (biomarker: BiomarkerSimple): boolean => !biomarker.was_already_used && biomarker.state === BiomarkerState.COMPLETED
+
+ /**
+ * Method that select how the user is going to create a Biomarker
+ * @param selectedBiomarker Biomarker selected to update
+ */
+ handleOpenEditBiomarker = (selectedBiomarker: BiomarkerSimple) => {
+ this.getBiomarkerFullInstance(selectedBiomarker).then((biomarker) => {
+ this.setState({
+ biomarkerTypeSelected: BiomarkerOrigin.MANUAL,
+ openCreateEditBiomarkerModal: true,
+ formBiomarker: {
+ id: biomarker.id,
+ canEditMolecules: this.canEditBiomarker(biomarker),
+ biomarkerName: biomarker.name,
+ biomarkerDescription: biomarker.description,
+ tag: biomarker.tag,
+ moleculeSelected: BiomarkerType.MRNA,
+ moleculesTypeOfSelection: MoleculesTypeOfSelection.INPUT,
+ moleculesSection: {
+ [BiomarkerType.CNA]: {
+ isLoading: false,
+ data: biomarker.cnas.map(item => ({ isValid: true, value: item.identifier }))
+ },
+ [BiomarkerType.MIRNA]: {
+ isLoading: false,
+ data: biomarker.mirnas.map(item => ({ isValid: true, value: item.identifier }))
+ },
+ [BiomarkerType.METHYLATION]: {
+ isLoading: false,
+ data: biomarker.methylations.map(item => ({ isValid: true, value: item.identifier }))
+ },
+ [BiomarkerType.MRNA]: {
+ isLoading: false,
+ data: biomarker.mrnas.map(item => ({ isValid: true, value: item.identifier }))
+ }
+ },
+ validation: {
+ haveAmbiguous: false,
+ haveInvalid: false,
+ isLoading: false,
+ checkBox: false
+ },
+ moleculesSymbolsFinder: {
+ isLoading: false,
+ data: []
+ }
+ }
+ })
+ })
+ }
+
+ handleSelectAllBiomarker = () => {
+ const allMolecules = this.state.experimentInfoWithoutFilters.rows
+
+ this.setState({
+ biomarkerTypeSelected: BiomarkerOrigin.MANUAL,
+ openCreateEditBiomarkerModal: true,
+ formBiomarker: {
+ id: null,
+ canEditMolecules: true,
+ biomarkerName: '',
+ biomarkerDescription: '',
+ tag: '',
+ moleculeSelected: BiomarkerType.MRNA,
+ moleculesTypeOfSelection: MoleculesTypeOfSelection.INPUT,
+ moleculesSection: {
+ [BiomarkerType.CNA]: { isLoading: false, data: [] },
+ [BiomarkerType.MIRNA]: {
+ isLoading: false,
+ data: allMolecules.map(item => ({
+ isValid: true,
+ value: item.gem
+ }))
+ },
+ [BiomarkerType.METHYLATION]: { isLoading: false, data: [] },
+ [BiomarkerType.MRNA]: {
+ isLoading: false,
+ data: allMolecules.map(item => ({
+ isValid: true,
+ value: item.gene
+ }))
+ }
+ },
+ validation: {
+ haveAmbiguous: false,
+ haveInvalid: false,
+ isLoading: false,
+ checkBox: false
+ },
+ moleculesSymbolsFinder: {
+ isLoading: false,
+ data: []
+ }
+ }
+ })
+ }
+
+ buildFormBiomarkerFromCurrentPageFilteredRows = () => {
+ const filteredRows = this.getFilteredMoleculesCurrentPage()
+ const { experimentInfo, tableControl } = this.props
+
+ // Verify if there are filters applied
+ const hasFilters = Object.values(tableControl.filters).some(f => f.value !== null && f.value !== undefined && f.value !== '')
+
+ // If there are filters applied, use the filtered rows, otherwise use all the rows
+ const rowsToUse = hasFilters ? filteredRows : experimentInfo.rows
+
+ const genes = rowsToUse.map(r => r.gene)
+ const gems = rowsToUse.map(r => r.gem)
+
+ return {
+ ...this.getDefaultFormBiomarker(),
+ moleculesSection: {
+ [BiomarkerType.MRNA]: { isLoading: false, data: genes.map(g => ({ value: g, isValid: true })) },
+ [BiomarkerType.MIRNA]: { isLoading: false, data: gems.map(g => ({ value: g, isValid: true })) },
+ [BiomarkerType.CNA]: { isLoading: false, data: [] },
+ [BiomarkerType.METHYLATION]: { isLoading: false, data: [] }
+ }
+ }
+ }
+
+ getFilteredMoleculesCurrentPage = (): DjangoMRNAxGEMResultRow[] => {
+ const { experimentInfo, tableControl } = this.props
+ const rows = experimentInfo.rows
+
+ return rows.filter(row => {
+ return Object.entries(tableControl.filters).every(([key, filter]) => {
+ const filterValue = filter.value
+
+ if (filterValue === null || filterValue === undefined || filterValue === '') { return true }
+
+ return row[key as keyof DjangoMRNAxGEMResultRow] === filterValue
+ })
+ })
+ }
+
+ /**
+ * Method that get symbols while user is writing in Select molecules input
+ * @param query string that is sending to the api
+ */
+ handleGenesSymbolsFinder = (query: string): void => {
+ // loading aca
+ const formBiomarkerPreLoad = this.state.formBiomarker
+ formBiomarkerPreLoad.moleculesSymbolsFinder.isLoading = true
+ let urlToFind = urlGeneSymbolsFinder
+
+ switch (this.state.formBiomarker.moleculeSelected) {
+ case BiomarkerType.MIRNA:
+ urlToFind = urlMiRNACodesFinder
+ break
+ case BiomarkerType.METHYLATION:
+ urlToFind = urlMethylationSitesFinder
+ break
+ default:
+ break
+ }
+
+ this.setState({ formBiomarker: formBiomarkerPreLoad })
+ ky.get(urlToFind, { searchParams: { query, limit: 5 }, signal: this.abortController.signal, timeout: REQUEST_TIMEOUT }).then((response) => {
+ response.json().then((jsonResponse) => {
+ const formBiomarker = this.state.formBiomarker
+ const checkedIgnoreProposedAlias = this.state.checkedIgnoreProposedAlias // For short
+
+ formBiomarker.moleculesSymbolsFinder.data = jsonResponse.map(molecule => {
+ const text = checkedIgnoreProposedAlias || molecule.molecule === molecule.standard
+ ? molecule.molecule
+ : `${molecule.molecule} (${molecule.standard})`
+
+ return {
+ key: molecule.molecule,
+ text,
+ value: checkedIgnoreProposedAlias ? molecule.molecule : molecule.standard
+ }
+ })
+ this.setState({ formBiomarker })
+ }).catch((err) => {
+ console.error('Error parsing JSON ->', err)
+ })
+ }).catch((err) => {
+ console.error('Error getting genes ->', err)
+ }).finally(() => {
+ if (!this.abortController.signal.aborted) {
+ const formBiomarker = this.state.formBiomarker
+ formBiomarker.moleculesSymbolsFinder.isLoading = false
+ this.setState({ formBiomarker })
+ }
+ })
+ }
+
+ /**
+ * Method that removes invalid genes of the sector selected
+ * @param sector string of the sector selected to change state
+ */
+ handleRemoveInvalidGenes = (sector: BiomarkerType): void => {
+ this.setState({
+ ...this.state,
+ formBiomarker: {
+ ...this.state.formBiomarker,
+ moleculesSection: {
+ ...this.state.formBiomarker.moleculesSection,
+ [sector]: {
+ ...this.state.formBiomarker.moleculesSection[sector],
+ data: this.state.formBiomarker.moleculesSection[sector].data.filter(gen => gen.isValid || Array.isArray(gen.value))
+ }
+ }
+ }
+ })
+ }
+
+ /**
+ * Method that removes all the molecules of the sector selecterd
+ * @param sector string of the sector selected to change state
+ */
+ handleRestartSection = (sector: BiomarkerType): void => {
+ this.setState({
+ ...this.state,
+ formBiomarker: {
+ ...this.state.formBiomarker,
+ moleculesSection: {
+ ...this.state.formBiomarker.moleculesSection,
+ [sector]: {
+ ...this.state.formBiomarker.moleculesSection[sector],
+ data: []
+ }
+ }
+ }
+ })
+ }
+
+ /**
+ * Order data to show in the section.
+ * @param data Data to order.
+ * @returns Ordered data.
+ */
+ orderData = (data: MoleculesSectionData[]): MoleculesSectionData[] => {
+ return data.sort((a, b) => {
+ const cond = Number(a.isValid) - Number(b.isValid)
+
+ if (cond !== 0) {
+ return cond
+ }
+
+ return Array.isArray(a.value) ? 1 : -1
+ })
+ }
+
+ /**
+ * Sets a list of molecules to the current selected section.
+ * @param moleculesList List of molecules to set.
+ */
+ setMoleculesToSelectedSection = (moleculesList: MoleculesSectionData[]) => {
+ const moleculeTypeSelected = this.state.formBiomarker.moleculeSelected
+
+ // Sets loading in false
+ const moleculesSection = {
+ ...this.state.formBiomarker.moleculesSection,
+ [moleculeTypeSelected]: {
+ isLoading: false,
+ data: this.orderData([...this.state.formBiomarker.moleculesSection[moleculeTypeSelected].data].concat(moleculesList))
+ }
+ }
+
+ const newFormBiomarker: FormBiomarkerData = {
+ ...this.state.formBiomarker,
+ moleculesSection
+ }
+
+ newFormBiomarker.moleculesSymbolsFinder.isLoading = false
+ newFormBiomarker.moleculesSection[moleculeTypeSelected].isLoading = false
+
+ this.setState({ formBiomarker: newFormBiomarker })
+ }
+
+ /**
+ * Method that gets symbols while user is writing in Select molecules input
+ * @param molecules array of strings that is sending to the api
+ */
+ handleGeneSymbols = async (molecules: string[]): Promise => {
+ const moleculesSectionPreload = {
+ ...this.state.formBiomarker.moleculesSection,
+ [this.state.formBiomarker.moleculeSelected]: {
+ isLoading: true,
+ data: [...this.state.formBiomarker.moleculesSection[this.state.formBiomarker.moleculeSelected].data]
+ }
+ }
+ this.setState({
+ formBiomarker: {
+ ...this.state.formBiomarker,
+ moleculesSymbolsFinder: {
+ ...this.state.formBiomarker.moleculesSymbolsFinder,
+ isLoading: true
+ },
+ moleculesSection: moleculesSectionPreload
+ }
+ })
+ let urlToFind: string
+ let json: { [key: string]: string[] }
+ let keyMolecules: string
+
+ switch (this.state.formBiomarker.moleculeSelected) {
+ case BiomarkerType.MIRNA:
+ urlToFind = urlMiRNACodes
+ json = { mirna_codes: molecules }
+ keyMolecules = 'mirna_codes'
+ break
+ case BiomarkerType.METHYLATION:
+ urlToFind = urlMethylationSites
+ json = { methylation_sites: molecules }
+ keyMolecules = 'methylation_sites'
+ break
+ default:
+ urlToFind = urlGeneSymbols
+ json = { gene_ids: molecules }
+ keyMolecules = 'gene_ids'
+ break
+ }
+
+ const genesArray: MoleculesSectionData[] = []
+ ky.post(urlToFind, { headers: getDjangoHeader(), json, timeout: REQUEST_TIMEOUT }).then((response) => {
+ response.json<{ [key: string]: string[] }>().then((jsonResponse) => {
+ const genes = Object.entries(jsonResponse)
+
+ for (const gene of genes) {
+ let condition
+
+ switch (gene[1].length) {
+ case 0:
+ condition = this.state.formBiomarker.moleculesSection[this.state.formBiomarker.moleculeSelected].data.concat(genesArray).filter(item => item.value === gene[0])
+
+ if (!condition.length) {
+ genesArray.push({
+ isValid: false,
+ value: gene[0]
+ })
+ }
+
+ break
+ case 1:
+ condition = this.state.formBiomarker.moleculesSection[this.state.formBiomarker.moleculeSelected].data.concat(genesArray).filter(item => item.value === gene[1][0])
+
+ if (!condition.length) {
+ genesArray.push({
+ isValid: true,
+ value: gene[1][0]
+ })
+ }
+
+ break
+ default:
+ condition = this.state.formBiomarker.moleculesSection[this.state.formBiomarker.moleculeSelected].data.concat(genesArray).filter(
+ item => isEqual(item.value, gene[1])
+ )
+
+ if (!condition.length) {
+ genesArray.push({
+ isValid: false,
+ value: gene[1]
+ })
+ }
+
+ break
+ }
+ }
+ }).catch((err) => {
+ console.error('Error parsing JSON ->', err)
+ console.warn('Setting all molecules as invalid to show warning')
+
+ json[keyMolecules].forEach(molecule => {
+ genesArray.push({
+ isValid: false,
+ value: molecule
+ })
+ })
+ }).finally(() => {
+ this.setMoleculesToSelectedSection(genesArray)
+ })
+ }).catch((err) => {
+ console.error('Error getting molecules ->', err)
+ console.warn('Setting all molecules as invalid to show warning')
+
+ json[keyMolecules].forEach(molecule => {
+ genesArray.push({
+ isValid: false,
+ value: molecule
+ })
+ })
+ }).finally(() => {
+ this.setMoleculesToSelectedSection(genesArray)
+ })
+ }
+
+ /**
+ * Generates a default formBiomarker
+ * @returns Default FormBiomarkerData object
+ */
+ getDefaultFormBiomarker (): FormBiomarkerData {
+ return {
+ id: null,
+ biomarkerName: '',
+ biomarkerDescription: '',
+ canEditMolecules: true,
+ tag: null,
+ moleculeSelected: BiomarkerType.MRNA,
+ moleculesTypeOfSelection: MoleculesTypeOfSelection.INPUT,
+ validation: {
+ haveAmbiguous: false,
+ haveInvalid: false,
+ isLoading: false,
+ checkBox: false
+ },
+ moleculesSection: {
+ [BiomarkerType.CNA]: {
+ isLoading: false,
+ data: []
+ },
+ [BiomarkerType.MIRNA]: {
+ isLoading: false,
+ data: []
+ },
+ [BiomarkerType.METHYLATION]: {
+ isLoading: false,
+ data: []
+ },
+ [BiomarkerType.MRNA]: {
+ isLoading: false,
+ data: []
+ }
+ },
+ moleculesSymbolsFinder: {
+ isLoading: false,
+ data: []
+ }
+ }
+ }
+
+ /**
+ * Updates checkbox status
+ * @param value new value to set
+ */
+ handleChangeCheckBox = (value: boolean) => {
+ const formBiomarker = this.state.formBiomarker
+ formBiomarker.validation.checkBox = value
+ this.setState({ formBiomarker })
+ }
+
+ /**
+ * Validates if the form is correct, if not change state of labels alerts bars
+ * @returns Some flags indicating if the form is valid or not
+ */
+ handleValidateForm = (): ValidationForm => {
+ let haveAmbiguous = false
+ let haveInvalid = false
+
+ for (const option of Object.values(BiomarkerType)) {
+ if (!haveAmbiguous) {
+ const indexOfAmbiguous = this.state.formBiomarker.moleculesSection[option].data.findIndex(item => !item.isValid && Array.isArray(item.value))
+
+ if (indexOfAmbiguous >= 0) {
+ haveAmbiguous = true
+ }
+ }
+
+ if (!haveInvalid && !this.state.formBiomarker.validation.checkBox) {
+ const indexOfInvalid = this.state.formBiomarker.moleculesSection[option].data.findIndex(item => !item.isValid && !Array.isArray(item.value))
+
+ if (indexOfInvalid >= 0) {
+ haveInvalid = true
+ }
+ }
+ }
+
+ return {
+ haveAmbiguous,
+ haveInvalid
+ }
+ }
+
+ /**
+ * Checks if it's a valid structure to send the molecule to backend and create the Biomarker.
+ * @param item Molecule to send.
+ * @returns True if it's valid, false if not.
+ */
+ moleculeIdentifierIsValid = (item: MoleculesSectionData): boolean => !Array.isArray(item.value) && item.isValid
+
+ /**
+ * Generates a valid structure to send the molecule to backend and create the Biomarker
+ * @param item Molecule to send
+ * @returns Correct structure to send
+ */
+ moleculeIdentified = (item: MoleculesSectionData): SaveMoleculeStructure => ({
+ identifier: item.value as string
+ })
+
+ /**
+ * Generates a valid structure to send the molecules to backend and create the Biomarker checking if the
+ * "Ignore errors" checkbox is checked or not.
+ * @param molecules Molecules to send.
+ * @returns Correct structure to send.
+ */
+ getMoleculesData = (molecules: MoleculesSectionData[]): SaveMoleculeStructure[] => {
+ const ignoreErrors = this.state.formBiomarker.validation.checkBox
+
+ if (ignoreErrors) {
+ return molecules.map(this.moleculeIdentified)
+ } else {
+ return molecules.filter(this.moleculeIdentifierIsValid).map(this.moleculeIdentified)
+ }
+ }
+
+ /**
+ * Makes the request to create a Biomarker
+ */
+ handleSendForm = () => {
+ const formBiomarker = this.state.formBiomarker
+ formBiomarker.validation.isLoading = true
+ this.setState({ formBiomarker })
+
+ // Gets name and description
+ const simpleBiomarker: BiomarkerNameAndDesc = {
+ name: formBiomarker.biomarkerName,
+ description: formBiomarker.biomarkerDescription
+ }
+
+ // Adds molecules if needed
+ const biomarkerToSend: SaveBiomarkerStructure | BiomarkerNameAndDesc = formBiomarker.canEditMolecules
+ ? {
+ ...simpleBiomarker,
+ mrnas: this.getMoleculesData(formBiomarker.moleculesSection.mRNA.data),
+ mirnas: this.getMoleculesData(formBiomarker.moleculesSection.miRNA.data),
+ cnas: this.getMoleculesData(formBiomarker.moleculesSection.CNA.data),
+ methylations: this.getMoleculesData(formBiomarker.moleculesSection.Methylation.data)
+ }
+ : simpleBiomarker
+
+ const settings: Options = {
+ headers: getDjangoHeader(),
+ json: biomarkerToSend,
+ timeout: REQUEST_TIMEOUT
+ }
+
+ // Checks if it's a creation or an update
+ if (!formBiomarker.id) {
+ ky.post(urlBiomarkersCreate, settings).then((response) => {
+ response.json().then((_jsonResponse) => {
+ this.closeModalWithSuccessMsg('Biomarker created successfully')
+ }).catch((err) => {
+ console.log('Error parsing JSON ->', err)
+ })
+ }).catch((err) => {
+ console.log('Error adding Biomarker ->', err)
+ const alert = this.state.alert
+ alert.isOpen = true
+ alert.type = CustomAlertTypes.ERROR
+ alert.message = 'Error creating biomarker!'
+ this.setState({ alert })
+ }).finally(() => {
+ formBiomarker.validation.isLoading = false
+ this.setState({ formBiomarker })
+ })
+ } else {
+ const url = formBiomarker.canEditMolecules ? urlBiomarkersCRUD : urlBiomarkersSimpleUpdate
+ ky.patch(`${url}/${formBiomarker.id}/`, settings).then((response) => {
+ response.json().then((_jsonResponse) => {
+ this.closeModalWithSuccessMsg('Biomarker edited successfully')
+ }).catch((err) => {
+ console.log('Error parsing JSON ->', err)
+ })
+ }).catch((err) => {
+ console.log('Error getting genes ->', err)
+ const alert = this.state.alert
+ alert.isOpen = true
+ alert.type = CustomAlertTypes.ERROR
+ alert.message = 'Error editing biomarker!'
+ this.setState({ alert })
+ }).finally(() => {
+ formBiomarker.validation.isLoading = false
+ this.setState({ formBiomarker })
+ })
+ }
+ }
+
+ /**
+ * change name or description of manual form
+ * @param value new value for input form
+ * @param name type of input to change
+ */
+ handleChangeInputForm = (value: string, name: 'biomarkerName' | 'biomarkerDescription') => {
+ const formBiomarker = this.state.formBiomarker
+ formBiomarker[name] = value
+ this.setState({ formBiomarker })
+ }
+
+ /**
+ * Handles the table's control filters, select, etc changes
+ * @param value Value to set to the state moleculeSelected in formBiomarkerState
+ */
+ handleChangeMoleculeSelected = (value: BiomarkerType) => {
+ const formBiomarker = this.state.formBiomarker
+ formBiomarker.moleculeSelected = value
+ formBiomarker.moleculesSymbolsFinder.data = []
+ this.setState({
+ formBiomarker
+ })
+ }
+
+ /**
+ * Handles the table's control filters, select, etc changes
+ * @param value Value to set to the state moleculesTypeOfSelection in formBiomarkerState
+ */
+ handleChangeMoleculeInputSelected = (value: MoleculesTypeOfSelection) => {
+ this.setState({
+ formBiomarker: {
+ ...this.state.formBiomarker,
+ moleculesTypeOfSelection: value
+ }
+ })
+ }
+
+ /**
+ * Handles the table's control filters, select, etc changes
+ * @param value Value to add to the molecules section that is selected
+ */
+ handleAddMoleculeToSection = (value: MoleculesSectionData) => {
+ console.debug('handleAddMoleculeToSection', value)
+ const genesSymbolsFinder = this.state.formBiomarker.moleculesSymbolsFinder
+ genesSymbolsFinder.data = []
+ this.setState({
+ formBiomarker: {
+ ...this.state.formBiomarker,
+ moleculesSymbolsFinder: genesSymbolsFinder
+ }
+ })
+
+ const sectionFound = this.state.formBiomarker.moleculesSection[this.state.formBiomarker.moleculeSelected].data.find((item: MoleculesSectionData) => value.value === item.value)
+
+ if (sectionFound !== undefined) {
+ return
+ }
+
+ const moleculesSection = {
+ ...this.state.formBiomarker.moleculesSection,
+ [this.state.formBiomarker.moleculeSelected]: {
+ isLoading: false,
+ data: [...this.state.formBiomarker.moleculesSection[this.state.formBiomarker.moleculeSelected].data, value]
+ }
+ }
+ this.setState({
+ formBiomarker: {
+ ...this.state.formBiomarker,
+ moleculesSection
+ }
+ })
+ }
+
+ /**
+ * Handles the table's control filters, select, etc changes
+ * @param section Value to add to the molecules section that is selected
+ * @param molecule molecule to remove of the array
+ */
+ handleRemoveMolecule = (section: BiomarkerType, molecule: MoleculesSectionData) => {
+ // keeps the molecules that are not the one that is going to be removed
+ const data = this.state.formBiomarker.moleculesSection[section].data.filter((item: MoleculesSectionData) => {
+ return item.value !== molecule.value
+ })
+
+ this.setState({
+ ...this.state,
+ formBiomarker: {
+ ...this.state.formBiomarker,
+ moleculesSection: {
+ ...this.state.formBiomarker.moleculesSection,
+ [section]: {
+ isLoading: false,
+ data
+ }
+ }
+ }
+ })
+ }
+
+ /**
+ * Generates a default new file form
+ * @returns An object with all the field with default values
+ */
+ getDefaultNewBiomarker (): Biomarker {
+ return {
+ id: null,
+ name: '',
+ description: '',
+ tag: null,
+ number_of_mrnas: 0,
+ number_of_mirnas: 0,
+ number_of_cnas: 0,
+ number_of_methylations: 0,
+ has_fs_experiment: false,
+ was_already_used: false,
+ origin: BiomarkerOrigin.BASE,
+ state: BiomarkerState.COMPLETED,
+ contains_nan_values: false,
+ column_used_as_index: '',
+ methylations: [],
+ mirnas: [],
+ cnas: [],
+ mrnas: [],
+ is_public: false,
+ user: {
+ id: 0,
+ username: ''
+ }
+ }
+ }
+
+ /**
+ * Cleans the new/edit biomarker form
+ */
+ cleanForm = () => {
+ this.setState({
+ openCreateEditBiomarkerModal: true,
+ formBiomarker: this.getDefaultFormBiomarker(),
+ confirmModal: this.getDefaultConfirmModal()
+ })
+ }
+
+ /**
+ * Show a modal to confirm a Biomarker deletion
+ * @param biomarker Selected Biomarker to delete
+ */
+ confirmBiomarkerDeletion = (biomarker: BiomarkerSimple) => {
+ this.setState({
+ selectedBiomarkerToDeleteOrSync: biomarker,
+ showDeleteBiomarkerModal: true
+ })
+ }
+
+ /** Closes the deletion confirm modals. */
+ handleClose = () => {
+ this.setState({ showDeleteBiomarkerModal: false })
+ }
+
+ /**
+ * Check if can submit the new Biomarker form
+ * @returns True if everything is OK, false otherwise
+ */
+ canSubmitBiomarkerForm = (): boolean => {
+ return !this.state.addingOrEditingBiomarker &&
+ this.state.newBiomarker.name.trim().length > 0
+ }
+
+ /**
+ * Handles Biomarker form changes
+ * @param name Name of the state field to modify
+ * @param value Value to set to the state field
+ */
+ handleFormChanges = (name: string, value) => {
+ const newBiomarker = this.state.newBiomarker
+ newBiomarker[name] = value
+ this.setState({ newBiomarker })
+ }
+
+ /**
+ * TODO: Check if needed
+ * Adds a Survival data tuple for a CGDSDataset
+ * @param datasetName Name of the edited CGDS dataset
+ */
+ addSurvivalFormTuple = (datasetName: NameOfCGDSDataset) => {
+ const newBiomarker = this.state.newBiomarker
+ const dataset = newBiomarker[datasetName]
+
+ if (dataset !== null) {
+ const newElement: DjangoSurvivalColumnsTupleSimple = { event_column: '', time_column: '' }
+
+ if (dataset.survival_columns === undefined) {
+ dataset.survival_columns = []
+ }
+
+ dataset.survival_columns.push(newElement)
+ this.setState({ newBiomarker })
+ }
+ }
+
+ /**
+ * TODO: Check if needed
+ * Removes a Survival data tuple for a CGDSDataset
+ * @param datasetName Name of the edited CGDS dataset
+ * @param idxSurvivalTuple Index in survival tuple
+ */
+ removeSurvivalFormTuple = (datasetName: NameOfCGDSDataset, idxSurvivalTuple: number) => {
+ const newBiomarker = this.state.newBiomarker
+ const dataset = newBiomarker[datasetName]
+
+ if (dataset !== null && dataset.survival_columns !== undefined) {
+ dataset.survival_columns.splice(idxSurvivalTuple, 1)
+ this.setState({ newBiomarker })
+ }
+ }
+
+ /**
+ * TODO: Check if needed
+ * Handles CGDS Dataset form changes in fields of Survival data tuples
+ * @param datasetName Name of the edited CGDS dataset
+ * @param idxSurvivalTuple Index in survival tuple
+ * @param name Field of the CGDS dataset to change
+ * @param value Value to assign to the specified field
+ */
+ handleSurvivalFormDatasetChanges = (
+ datasetName: NameOfCGDSDataset,
+ idxSurvivalTuple: number,
+ name: string,
+ value: any
+ ) => {
+ const newBiomarker = this.state.newBiomarker
+ const dataset = newBiomarker[datasetName]
+
+ if (dataset !== null && dataset.survival_columns !== undefined) {
+ dataset.survival_columns[idxSurvivalTuple][name] = value
+ this.setState({ newBiomarker })
+ }
+ }
+
+ /**
+ * Checks if the form is entirely empty. Useful to enable 'Cancel' button
+ * @returns True is any of the form's field contains any data. False otherwise
+ */
+ isFormEmpty = (): boolean => isEqual(this.state.formBiomarker, this.getDefaultFormBiomarker())
+
+ /**
+ * Callback to mark a Biomarker as selected
+ * @param biomarker Selected biomarker to mark
+ */
+ markBiomarkerAsSelected = (biomarker: Biomarker) => {
+ const featureSelection = this.state.featureSelection
+ featureSelection.selectedBiomarker = biomarker
+ this.setState({ featureSelection })
+ }
+
+ /**
+ * Function to complete step 2
+ */
+ handleCompleteStep2 = () => {
+ const featureSelection = this.state.featureSelection
+ featureSelection.step = 3
+ this.setState({ featureSelection })
+ }
+
+ /**
+ * Function to go back to step 1
+ */
+ handleGoBackStep1 = () => {
+ const featureSelection = this.state.featureSelection
+ featureSelection.clinicalSource = getDefaultSource()
+ featureSelection.mRNASource = getDefaultSource()
+ featureSelection.mirnaSource = getDefaultSource()
+ featureSelection.methylationSource = getDefaultSource()
+ featureSelection.cnaSource = getDefaultSource()
+ featureSelection.step = 1
+ this.setState({ featureSelection })
+ }
+
+ /** Closes the modal to confirm a Biomarker cloning. */
+ closeModalToClone = () => { this.setState({ biomarkerToClone: null }) }
+
+ /**
+ * Function to go back to step 2
+ */
+ handleGoBackStep2 = () => {
+ const featureSelection = this.state.featureSelection
+ featureSelection.step = 2
+ featureSelection.algorithm = FeatureSelectionAlgorithm.BLIND_SEARCH
+ this.setState({ featureSelection })
+ }
+
+ handleConfirm = () => {
+ const { selectedOption } = this.state
+
+ this.setState({ openSelectOptionModal: false }, () => {
+ if (selectedOption === 'selectAll') {
+ // ver de si hacer un endpoint que traiga todos los genes y no solo los de la pagina
+ this.handleSelectAllBiomarker()
+ } else {
+ // Select with Filters: usamos solo la página actual y filtros
+ this.setState({
+ openCreateEditBiomarkerModal: true,
+ formBiomarker: this.buildFormBiomarkerFromCurrentPageFilteredRows()
+ })
+ }
+ })
+ }
+
+ /**
+ * Closes the modal and shows a successful Semantic-UI Alert message.
+ * @param msg Message to show.
+ */
+ closeModalWithSuccessMsg = (msg: string) => {
+ const alert = this.state.alert
+ alert.isOpen = true
+ alert.type = CustomAlertTypes.SUCCESS
+ alert.message = msg
+ this.setState({
+ alert,
+ formBiomarker: this.getDefaultFormBiomarker(),
+ openCreateEditBiomarkerModal: false,
+ confirmModal: this.getDefaultConfirmModal(),
+ biomarkerTypeSelected: BiomarkerOrigin.BASE
+ })
+ }
+
+ /**
+ * Generates default table's Filters.
+ * @returns Default object for table's Filters
+ */
+ getDefaultFilters (): PaginationCustomFilter[] {
+ const tagOptions: DropdownItemProps[] = this.state.tags.map((tag) => {
+ const id = tag.id as number
+ return { key: id, value: id, text: tag.name }
+ })
+
+ tagOptions.unshift({ key: 'no_tag', text: 'No tag' })
+
+ // TODO: refactor Tag key as it's the same as AllExperimentsView.tsx and UserFilesView.tsx
+ return [
+ { label: 'Tag', keyForServer: 'tag', defaultValue: '', placeholder: 'Select an existing Tag', options: tagOptions, width: 3 }
+ ]
+ }
+
+ closeBiomarkerModal = () => {
+ this.setState({
+ formBiomarker: this.getDefaultFormBiomarker(),
+ featureSelection: this.getDefaultFeatureSelectionProps(),
+ openCreateEditBiomarkerModal: false,
+ confirmModal: this.getDefaultConfirmModal(),
+ biomarkerTypeSelected: BiomarkerOrigin.BASE,
+ modalReady: false
+ })
+ }
+
+ buildFormBiomarkerFromFilteredRows = () => {
+ const { rows } = this.props.experimentInfo
+
+ return {
+ ...this.getDefaultFormBiomarker(),
+ moleculesSection: {
+ [BiomarkerType.MRNA]: {
+ isLoading: false,
+ data: rows.map(item => ({ value: item.gene, isValid: true }))
+ },
+ [BiomarkerType.MIRNA]: {
+ isLoading: false,
+ data: rows.map(item => ({ value: item.gem, isValid: true }))
+ },
+ // los otros tipos vacíos
+ [BiomarkerType.CNA]: { isLoading: false, data: [] },
+ [BiomarkerType.METHYLATION]: { isLoading: false, data: [] }
+ }
+ }
+ }
+
+ render () {
+ const { openSelectOptionModal, selectedOption } = this.state
+ return (
+ <>
+ this.setState({
+ formBiomarker: this.getDefaultFormBiomarker(),
+ openSelectOptionModal: true
+ })}
+ />
+
+ {/* Modal con opciones */}
+ this.setState({ openSelectOptionModal: false })}
+ >
+ Create Biomarker
+
+ Select how you want to build the biomarker:
+
+ this.setState({ selectedOption: value as SelectedOption })}
+ />
+ this.setState({ selectedOption: value as SelectedOption })}
+ />
+
+
+
+
+
+
+
+
+
+ {/* Modal con ManualForm */}
+ }
+ closeOnEscape={false}
+ closeOnDimmerClick={false}
+ closeOnDocumentClick={false}
+ className={this.state.biomarkerTypeSelected !== BiomarkerOrigin.BASE ? 'space-modal large-modal' : undefined}
+ style={this.state.biomarkerTypeSelected === BiomarkerOrigin.BASE ? { width: '60%', minHeight: '60%' } : undefined}
+ onClose={() => this.closeBiomarkerModal()}
+ onOpen={() => {
+ this.setState({ modalReady: true }, () => {
+ window.dispatchEvent(new Event('resize'))
+ })
+ }}
+ >
+
+
+
+
+ {/* Biomarker details modal. */}
+ }
+ closeOnEscape={false}
+ closeOnDimmerClick={false}
+ closeOnDocumentClick={false}
+ centered={false}
+ onClose={this.closeBiomarkerDetailsModal}
+ open={this.state.openDetailsModal}
+ >
+
+
+
+ this.handleCancelConfirmModalState()}
+ onConfirm={() => this.state.confirmModal.onConfirm()}
+ />
+
+
+
+ >
+ )
+ }
+}
diff --git a/src/frontend/static/frontend/src/components/pipeline/experiment-result/CreateBiomarkerButton.tsx b/src/frontend/static/frontend/src/components/pipeline/experiment-result/CreateBiomarkerButton.tsx
new file mode 100644
index 00000000..79e21bfb
--- /dev/null
+++ b/src/frontend/static/frontend/src/components/pipeline/experiment-result/CreateBiomarkerButton.tsx
@@ -0,0 +1,89 @@
+import React, { useState } from 'react'
+import { Form, Modal, Button, Icon, ButtonProps } from 'semantic-ui-react'
+import { ExperimentInfo } from '../../../utils/interfaces'
+
+type BiomarkerOption = 'selectAll' | 'selectWithFilters'
+
+interface CreateBiomarkerButtonProps extends Partial {
+ experimentInfo: ExperimentInfo
+ onCreateBiomarker: (options: {
+ experimentInfo: ExperimentInfo;
+ selectAll: boolean;
+ }) => void;
+}
+
+const CreateBiomarkerButton: React.FC = ({
+ experimentInfo,
+ onCreateBiomarker,
+ ...buttonProps
+}) => {
+ const [open, setOpen] = useState(false)
+ const [selectedOption, setSelectedOption] = useState('selectAll')
+
+ const handleConfirm = () => {
+ onCreateBiomarker({
+ experimentInfo,
+ selectAll: selectedOption === 'selectAll',
+ })
+ setOpen(false)
+ }
+
+ return (
+ <>
+ {/* Button that opens the modal */}
+
+
+ {/* Modal with options */}
+ setOpen(false)}
+ >
+ Create Biomarker
+
+ Select how you want to build the biomarker:
+
+ setSelectedOption(value as BiomarkerOption)}
+ />
+ setSelectedOption(value as BiomarkerOption)}
+ />
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export default CreateBiomarkerButton
diff --git a/src/frontend/static/frontend/src/components/pipeline/experiment-result/ResultTableControlForm.tsx b/src/frontend/static/frontend/src/components/pipeline/experiment-result/ResultTableControlForm.tsx
index 3f1fd12c..651c5cf1 100644
--- a/src/frontend/static/frontend/src/components/pipeline/experiment-result/ResultTableControlForm.tsx
+++ b/src/frontend/static/frontend/src/components/pipeline/experiment-result/ResultTableControlForm.tsx
@@ -5,6 +5,7 @@ import { InfoPopup } from './gene-gem-details/InfoPopup'
import { generatesOrderingQueryMultiField } from '../../../utils/util_functions'
import { DjangoExperiment } from '../../../utils/django_interfaces'
import { SingleRangeSlider } from 'neo-react-semantic-ui-range'
+import { BiomarkerFromCorrelationModal } from './BiomarkerFromCorrelationModal'
declare const urlDownloadResultWithFilters: string
@@ -29,9 +30,9 @@ interface ResultTableControlFormProps {
/** Callback for changes in (adjusted) p-values precision only */
changePrecisionState: (showHighPrecision: boolean) => void,
/** Callback for reset filters and sorting */
- resetFiltersAndSorting: (experiment: DjangoExperiment) => void
+ resetFiltersAndSorting: (experiment: DjangoExperiment) => void,
/** Callback for reset only filters */
- resetFilters: (experiment: ExperimentInfo) => void
+ resetFilters: (experiment: ExperimentInfo) => void,
}
/**
@@ -70,140 +71,145 @@ export const ResultTableControlForm = (props: ResultTableControlFormProps) => {
}
return (
-
- {/* Number of showing/total combinations */}
-
-
-
-
-
- {props.numberOfShowingCombinations} / {props.totalNumberOfCombinations}
-
-
- SHOWING/TOTAL
-
-
-
-
-
- {/* mRNA/MiRNA search */}
- props.onHandleTableControlChanges(name, value)}
- />
-
- {/* Correlation threshold */}
-
-
-
- props.onHandleTableControlChanges('coefficientThreshold', value)}
- />
-
-
-
-
-
- {/* Correlation type */}
- props.onHandleTableControlChanges(name, value)}
- />
-
- {/* Page size */}
- props.onHandleTableControlChanges(name, value)}
- />
-
- props.changePrecisionState(!isShowingHighPrecision)}
- />
-
- props.resetFilters(props.experimentInfo)}
- />
-
- props.resetFiltersAndSorting(props.experimentInfo.experiment)}
- />
-
- window.open(generateDownloadWithFiltersQuery(), '_blank')}
- disabled={!props.experimentInfo.rows.length}
- />
-
-
-
- )}
- />
-
-
-
+ <>
+
+
+
+ {/* Number of showing/total combinations */}
+
+
+
+
+
+ {props.numberOfShowingCombinations} / {props.totalNumberOfCombinations}
+
+
+ SHOWING/TOTAL
+
+
+
+
+
+ {/* mRNA/MiRNA search */}
+ props.onHandleTableControlChanges(name, value)}
+ className='no-margin-right-form-field'
+ />
+
+ {/* Correlation threshold */}
+
+
+
+ props.onHandleTableControlChanges('coefficientThreshold', value)}
+ />
+
+
+
+
+
+ {/* Correlation type */}
+ props.onHandleTableControlChanges(name, value)}
+ className='no-margin-right-form-field'
+ />
+
+ {/* Page size */}
+ props.onHandleTableControlChanges(name, value)}
+ className='no-margin-right-form-field'
+ />
+
+ props.changePrecisionState(!isShowingHighPrecision)}
+ />
+
+
+
+ props.resetFiltersAndSorting(props.experimentInfo.experiment)}
+ />
+
+ window.open(generateDownloadWithFiltersQuery(), '_blank')}
+ disabled={!props.experimentInfo.rows.length}
+ />
+
+
+
+
+
+
+ >
+
)
}
diff --git a/src/frontend/static/frontend/src/css/gem.css b/src/frontend/static/frontend/src/css/gem.css
index 27c3edfb..94bb6fb2 100644
--- a/src/frontend/static/frontend/src/css/gem.css
+++ b/src/frontend/static/frontend/src/css/gem.css
@@ -206,3 +206,90 @@
opacity: 1;
text-decoration: underline;
}
+
+.biomarkers--side--bar--box {
+ margin: 1rem 0;
+}
+
+.biomarkers--side--bar--buttons-group {
+ width: 100%;
+}
+
+.biomarkers--side--bar--input--selection {
+ margin: 0.5rem 0;
+ width: 100%;
+}
+
+.biomarkers--modal--container {
+ height: 100%;
+}
+
+.large-modal {
+ min-height: 92% !important;
+ width: 92% !important;
+}
+
+.space-modal:nth-child(1) {
+ display: flex !important;
+ flex-direction: column;
+ justify-content: space-between;
+}
+
+.biomarkers--side--bar--container {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
+}
+
+.biomarkers--side--bar--container--item--margin {
+ margin: 0.5rem 0;
+}
+
+.biomarkers--side--bar--buttons--box {
+ align-self: flex-end;
+ display: flex;
+ justify-self: flex-end;
+ margin-top: 0.5rem;
+}
+
+.biomarkers--side--bar--validation--items {
+ margin: 8px 0px 8px 0px;
+ width: 100%;
+}
+
+.biomarkers--molecules--container {
+ max-width: 100%;
+ height: 32vh;
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ margin-top: 0.5rem;
+}
+
+.biomarkers--molecules--container--item {
+ margin: 0.5rem;
+ flex: 1;
+ display: flex;
+ justify-content: center;
+ align-self: center;
+}
+
+.biomarkers--molecules--container--grid {
+ width: 100%;
+}
+
+.biomarker--section--icon {
+ padding: 0 0.5rem;
+ cursor: pointer;
+}
+
+.biomarker--section--button {
+ cursor: default !important;
+}
+
+.row-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: row;
+}
\ No newline at end of file
diff --git a/src/frontend/templates/frontend/gem.html b/src/frontend/templates/frontend/gem.html
index b2f1c11c..918fa6a1 100644
--- a/src/frontend/templates/frontend/gem.html
+++ b/src/frontend/templates/frontend/gem.html
@@ -18,6 +18,15 @@
const downloadFileURL = "{% url 'download_user_file' %}"
const urlRunExperiment = "{% url 'run_experiment' %}"
const urlUserExperiments = "{% url 'mrna_gem_experiment' %}"
+ const urlGeneSymbols = "{% url 'gene_symbols' %}"
+ const urlGeneSymbolsFinder = "{% url 'gene_symbols_finder' %}"
+ const urlBiomarkersCRUD = "{% url 'biomarkers_api' %}"
+ const urlBiomarkersSimpleUpdate = "{% url 'biomarkers_api_simple_update' %}"
+ const urlMiRNACodes = "{% url 'mirna_codes' %}"
+ const urlMiRNACodesFinder = "{% url 'mirna_codes_finder' %}"
+ const urlBiomarkersCreate = "{% url 'biomarkers_create' %}"
+ const urlMethylationSites = "{% url 'methylation_sites' %}"
+ const urlMethylationSitesFinder = "{% url 'methylation_sites_finder' %}"
const urlGetFullUserExperiment = "{% url 'get_full_experiment' %}"
const urlGetUsersCandidatesLimited = "{% url 'user_candidates_limited_institution' %}"
const urlGetInstitutionsNonInExperiment = "{% url 'institution-non-experiments-list' %}"
diff --git a/src/frontend/views.py b/src/frontend/views.py
index 7243b6c3..5a9a181f 100644
--- a/src/frontend/views.py
+++ b/src/frontend/views.py
@@ -1,7 +1,9 @@
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.conf import settings
-
+from django.conf import settings
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import render
def index_action(request):
"""Index view"""
diff --git a/src/user_files/models.py b/src/user_files/models.py
index 35f9ee75..04cf1b24 100644
--- a/src/user_files/models.py
+++ b/src/user_files/models.py
@@ -15,6 +15,7 @@
from institutions.models import Institution
from tags.models import Tag
from tissues.models import Tissue
+from tissues.models import Tissue
from user_files.models_choices import FileType, FileDecimalSeparator
from user_files.utils import get_decimal_separator_and_numerical_data, read_excel_in_chunks