Skip to content

Commit 4fcb9ca

Browse files
committed
Worldefp conversion to routing
1 parent d6702bf commit 4fcb9ca

5 files changed

Lines changed: 244 additions & 209 deletions

File tree

Eplant/config.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import InteractionsViewer from './views/InteractionsViewer'
1010
import NavigatorView from './views/NavigatorView'
1111
import PlantEFP from './views/PlantEFP'
1212
import PublicationViewer from './views/PublicationViewer'
13-
// import WorldEFP from './views/WorldEFP'
13+
import WorldEFP from './views/WorldEFP'
1414
import { type ViewMetadata } from './View'
1515

1616
export type EplantConfig = {
@@ -34,7 +34,7 @@ const userViewMetadata = [
3434
PlantEFP,
3535
CellEFP,
3636
ExperimentEFP,
37-
// WorldEFP,
37+
WorldEFP,
3838
ChromosomeViewerObject,
3939
NavigatorView,
4040
InteractionsViewer,

Eplant/main.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { InteractionsViewObject } from './views/InteractionsViewer/InteractionsV
1616
import { NavigatorViewObject } from './views/NavigatorView/NavigatorView'
1717
import { PlantEFPView } from './views/PlantEFP/PlantEFP'
1818
import { PublicationsViewer } from './views/PublicationViewer/PublicationsView'
19+
import { WorldEFPView } from './views/WorldEFP/WorldEFP'
1920
import { Config, defaultConfig } from './config'
2021
import Eplant from './Eplant'
2122

@@ -65,6 +66,10 @@ const router = createBrowserRouter(
6566
path: 'interactions-view/:geneid?',
6667
element: <InteractionsViewObject></InteractionsViewObject>,
6768
},
69+
{
70+
path: 'world-efp/:geneid?',
71+
element: <WorldEFPView></WorldEFPView>,
72+
},
6873
],
6974
errorElement: <ErrorBoundary></ErrorBoundary>,
7075
},

Eplant/views/WorldEFP/WorldEFP.tsx

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { useEffect, useState } from 'react'
2+
import { useOutletContext } from 'react-router-dom'
3+
4+
import GeneticElement from '@eplant/GeneticElement'
5+
import { useURLState } from '@eplant/state/URLStateProvider'
6+
import LoadingPage from '@eplant/UI/Layout/ViewContainer/LoadingPage'
7+
import { ViewContext } from '@eplant/UI/Layout/ViewContainer/types'
8+
import { ViewDataError } from '@eplant/View'
9+
import { useQuery } from '@tanstack/react-query'
10+
import { APIProvider } from '@vis.gl/react-google-maps'
11+
12+
import { EFPData, EFPGroup } from '../eFP/types'
13+
import MaskModal from '../eFP/Viewer/MaskModal'
14+
15+
import MapContainer from './MapContainer'
16+
import {
17+
Coordinates,
18+
WorldEFPAction,
19+
WorldEFPData,
20+
WorldEFPMicroArrayResponse,
21+
WorldEFPState,
22+
WorldEFPStateSchema,
23+
} from './types'
24+
import WorldEFP from '.'
25+
26+
export const WorldEFPView = () => {
27+
const { geneticElement } = useOutletContext<ViewContext>()
28+
const { state, setState, initializeState } = useURLState<WorldEFPState>()
29+
const [loadAmount, setLoadAmount] = useState(0)
30+
31+
const { data, isLoading, isError, error } = useQuery<
32+
WorldEFPData,
33+
ViewDataError
34+
>({
35+
queryKey: [`world-efp-${geneticElement?.id}`],
36+
queryFn: async () => {
37+
return worldEFPLoader(geneticElement, setLoadAmount)
38+
},
39+
})
40+
useEffect(() => {
41+
// On mount, initialize state
42+
initializeState(WorldEFPStateSchema)
43+
}, [])
44+
45+
if (!geneticElement) {
46+
return (
47+
<LoadingPage
48+
loadingAmount={loadAmount}
49+
gene={geneticElement}
50+
view={WorldEFP}
51+
error={ViewDataError.UNSUPPORTED_GENE}
52+
></LoadingPage>
53+
)
54+
} else if (isError) {
55+
return (
56+
<LoadingPage
57+
loadingAmount={loadAmount}
58+
gene={geneticElement}
59+
view={WorldEFP}
60+
error={error}
61+
></LoadingPage>
62+
)
63+
} else if (isLoading && loadAmount < 100) {
64+
return (
65+
<LoadingPage
66+
loadingAmount={loadAmount}
67+
gene={geneticElement}
68+
view={WorldEFP}
69+
error={null}
70+
></LoadingPage>
71+
)
72+
} else if (!data || !state) return <></>
73+
74+
return (
75+
<>
76+
<APIProvider apiKey={import.meta.env.VITE_MAPS_API_KEY} version='beta'>
77+
<MapContainer
78+
activeData={data}
79+
state={state}
80+
dispatch={(action: WorldEFPAction) => {
81+
// TODO: implement reducer logic
82+
}}
83+
/>
84+
</APIProvider>
85+
<MaskModal
86+
isVisible={state.maskModalVisible}
87+
threshold={state.maskThreshold}
88+
onClose={() => {
89+
setState({ ...state, maskModalVisible: !state.maskModalVisible })
90+
}}
91+
onSubmit={(threshold) => {
92+
setState({
93+
...state,
94+
maskThreshold: threshold,
95+
maskingEnabled: !state.maskingEnabled,
96+
maskModalVisible: !state.maskModalVisible,
97+
})
98+
}}
99+
/>
100+
</>
101+
)
102+
}
103+
104+
export const worldEFPLoader = async (
105+
geneticElement: GeneticElement | null,
106+
loadEvent: (loaded: number) => void
107+
) => {
108+
if (!geneticElement) throw ViewDataError.UNSUPPORTED_GENE
109+
110+
const microArrayDataURL = `https://bar.utoronto.ca/api_dev/microarray_gene_expression/world_efp/arabidopsis/${geneticElement.id}`
111+
const microArrayData: WorldEFPMicroArrayResponse = await fetch(
112+
microArrayDataURL
113+
)
114+
.then((response) => {
115+
if (!response.ok) {
116+
throw new Error(`HTTP error! Status: ${response.status}`)
117+
}
118+
return response.json()
119+
})
120+
.catch((error) => {
121+
console.error('Error fetching map marker data:', error)
122+
throw error
123+
})
124+
125+
const positions: Coordinates[] = []
126+
const groupData: EFPGroup[] = []
127+
Object.entries(microArrayData.data).forEach(([key, marker]) => {
128+
// Coordinates
129+
positions.push({
130+
lat: parseFloat(marker.position.lat),
131+
lng: parseFloat(marker.position.lng),
132+
})
133+
const samples = Object.values(marker.values)
134+
// Sample data
135+
const mean = samples.reduce((sum, value) => sum + value, 0) / samples.length
136+
const efpGroupData = {
137+
name: marker.id,
138+
tissues: [],
139+
mean: mean,
140+
min: Math.min(...samples),
141+
max: Math.max(...samples),
142+
std: Math.sqrt(
143+
samples.reduce((sum, value) => Math.pow(value - mean, 2)) /
144+
(samples.length - 1)
145+
),
146+
samples: samples.length,
147+
}
148+
149+
groupData.push(efpGroupData)
150+
})
151+
152+
const totalSamples = groupData.reduce((sum, group) => sum + group.samples, 0)
153+
const totalMean =
154+
groupData.reduce((sum, group) => sum + group.mean * group.samples, 0) /
155+
totalSamples
156+
157+
const efpData = {
158+
groups: groupData,
159+
mean: totalMean,
160+
min: Math.min(...groupData.map((group) => group.min)),
161+
max: Math.max(...groupData.map((group) => group.max)),
162+
std: 0, // This isn't needed, just set to 0 for convenience
163+
samples: totalSamples,
164+
} as EFPData
165+
166+
return {
167+
positions: positions,
168+
efpData: efpData,
169+
}
170+
}

0 commit comments

Comments
 (0)