11"use client" ;
22
3- import React , { useMemo , useEffect , useRef , useCallback } from 'react'
3+ import React , { useMemo , useEffect , useRef , useState } from 'react'
44import * as THREE from 'three'
55import { useAnalysisStore , useGlobalStore , usePlotStore } from '@/utils/GlobalStates'
66import { vertShader } from '@/components/computation/shaders'
77import { flatFrag3D , fragmentFlat } from '../textures/shaders' ;
88import { useShallow } from 'zustand/shallow'
99import { ThreeEvent } from '@react-three/fiber' ;
10- import { GetCurrentArray } from '@/utils/HelperFuncs' ;
10+ import { GetCurrentArray , GetTimeSeries , parseUVCoords } from '@/utils/HelperFuncs' ;
11+ import { ZarrDataset } from '../zarr/ZarrLoaderLRU' ;
12+ import { evaluate_cmap } from 'js-colormaps-es' ;
1113
1214interface InfoSettersProps {
1315 setLoc : React . Dispatch < React . SetStateAction < number [ ] > > ;
@@ -21,29 +23,33 @@ function Rescale(value: number, scales: {minVal: number, maxVal: number}){
2123 return value * range + scales . minVal
2224}
2325
24-
25- const FlatMap = ( { textures, infoSetters} : { textures : THREE . DataTexture | THREE . Data3DTexture [ ] , infoSetters : InfoSettersProps } ) => {
26+ const FlatMap = ( { textures, infoSetters, ZarrDS} : { textures : THREE . DataTexture | THREE . Data3DTexture [ ] , infoSetters : InfoSettersProps , ZarrDS : ZarrDataset } ) => {
2627 const { setLoc, setShowInfo, val, coords} = infoSetters ;
27- const { flipY, colormap, valueScales, dimArrays, isFlat, dataShape, textureArrayDepths} = useGlobalStore ( useShallow ( state => ( {
28- flipY : state . flipY ,
29- colormap : state . colormap ,
30- valueScales : state . valueScales ,
31- dimArrays : state . dimArrays ,
32- isFlat : state . isFlat ,
33- dataShape : state . dataShape ,
34- textureArrayDepths : state . textureArrayDepths
28+ const { flipY, colormap, valueScales, dimArrays, dimNames, dimUnits,
29+ isFlat, dataShape, textureArrayDepths, strides, timeSeries,
30+ setPlotDim, updateDimCoords, updateTimeSeries} = useGlobalStore ( useShallow ( state => ( {
31+ flipY : state . flipY , colormap : state . colormap ,
32+ valueScales : state . valueScales , dimArrays : state . dimArrays ,
33+ dimNames :state . dimNames , dimUnits : state . dimUnits ,
34+ isFlat : state . isFlat , dataShape : state . dataShape ,
35+ textureArrayDepths : state . textureArrayDepths ,
36+ strides : state . strides , timeSeries : state . timeSeries ,
37+ setPlotDim :state . setPlotDim ,
38+ updateDimCoords :state . updateDimCoords ,
39+ updateTimeSeries : state . updateTimeSeries
3540 } ) ) )
36- const { cScale, cOffset, animProg, nanTransparency, nanColor, zSlice, ySlice, xSlice} = usePlotStore ( useShallow ( state => ( {
37- cOffset : state . cOffset ,
38- cScale : state . cScale ,
39- resetAnim : state . resetAnim ,
40- animate : state . animate ,
41- animProg : state . animProg ,
42- nanTransparency : state . nanTransparency ,
43- nanColor : state . nanColor ,
44- zSlice : state . zSlice ,
45- ySlice : state . ySlice ,
46- xSlice : state . xSlice
41+
42+ const { cScale, cOffset, animProg, nanTransparency, nanColor,
43+ zSlice, ySlice, xSlice, selectTS,
44+ getColorIdx, incrementColorIdx} = usePlotStore ( useShallow ( state => ( {
45+ cOffset : state . cOffset , cScale : state . cScale ,
46+ resetAnim : state . resetAnim , animate : state . animate ,
47+ animProg : state . animProg , nanTransparency : state . nanTransparency ,
48+ nanColor : state . nanColor , zSlice : state . zSlice ,
49+ ySlice : state . ySlice , xSlice : state . xSlice ,
50+ selectTS : state . selectTS ,
51+ getColorIdx : state . getColorIdx ,
52+ incrementColorIdx : state . incrementColorIdx
4753 } ) ) )
4854 const { axis, analysisMode, analysisArray} = useAnalysisStore ( useShallow ( state => ( {
4955 axis : state . axis ,
@@ -71,19 +77,114 @@ const FlatMap = ({textures, infoSetters} : {textures : THREE.DataTexture | THREE
7177 const infoRef = useRef < boolean > ( false )
7278 const lastUV = useRef < THREE . Vector2 > ( new THREE . Vector2 ( 0 , 0 ) )
7379 const rotateMap = analysisMode && axis == 2 ;
74- const sampleArray = useMemo ( ( ) => analysisMode ? analysisArray : GetCurrentArray ( ) , [ analysisMode , analysisArray ] )
80+ const sampleArray = useMemo ( ( ) => analysisMode ? analysisArray : GetCurrentArray ( ) , [ analysisMode , analysisArray , textures ] )
7581 const analysisDims = useMemo ( ( ) => dimArrays . length > 2 ? dimSlices . filter ( ( _e , idx ) => idx != axis ) : dimSlices , [ dimSlices , axis ] )
82+ useEffect ( ( ) => {
83+ geometry . dispose ( )
84+ } , [ geometry ] )
85+
86+ // ----- MOUSE MOVE ----- //
87+ const eventRef = useRef < ThreeEvent < PointerEvent > | null > ( null ) ;
88+ const handleMove = ( e : ThreeEvent < PointerEvent > ) => {
89+ if ( infoRef . current && e . uv ) {
90+ eventRef . current = e ;
91+ setLoc ( [ e . clientX , e . clientY ] ) ;
92+ lastUV . current = e . uv ;
93+ const { x, y } = e . uv ;
94+ const xSize = isFlat ? ( analysisMode ? analysisDims [ 1 ] . length : dimSlices [ 1 ] . length ) : dimSlices [ 2 ] . length ;
95+ const ySize = isFlat ? ( analysisMode ? analysisDims [ 0 ] . length : dimSlices [ 0 ] . length ) : dimSlices [ 1 ] . length ;
96+ const xIdx = Math . round ( x * xSize - .5 )
97+ const yIdx = Math . round ( y * ySize - .5 )
98+ let dataIdx = xSize * yIdx + xIdx ;
99+ dataIdx += isFlat ? 0 : Math . floor ( ( dimSlices [ 0 ] . length - 1 ) * animProg ) * xSize * ySize
100+ const dataVal = sampleArray ? sampleArray [ dataIdx ] : 0 ;
101+ val . current = dataVal ;
102+ coords . current = isFlat ? analysisMode ? [ analysisDims [ 0 ] [ yIdx ] , analysisDims [ 1 ] [ xIdx ] ] : [ dimSlices [ 0 ] [ yIdx ] , dimSlices [ 1 ] [ xIdx ] ] : [ dimSlices [ 1 ] [ yIdx ] , dimSlices [ 2 ] [ xIdx ] ]
103+ }
104+ }
105+
106+
107+ // ----- TIMESERIES ----- //
108+ const [ boundsObj , setBoundsObj ] = useState < Record < string , THREE . Vector4 > > ( { } )
109+ const [ bounds , setBounds ] = useState < THREE . Vector4 [ ] > ( new Array ( 10 ) . fill ( new THREE . Vector4 ( - 1 , - 1 , - 1 , - 1 ) ) )
110+ const [ height , width ] = [ dataShape [ dataShape . length - 2 ] , dataShape [ dataShape . length - 1 ] ]
111+
112+ useEffect ( ( ) => { //This goes through the list of highlighted squares and removes those that aren't included in the timeseries object.
113+ let boundIDs = Object . keys ( boundsObj )
114+ const tsIDs = Object . keys ( timeSeries )
115+ boundIDs = boundIDs . filter ( ( val ) => tsIDs . includes ( val ) )
116+ const pointValues = boundIDs . map ( id => boundsObj [ id ] ) ;
117+ const paddedArray = [
118+ ...pointValues ,
119+ ...Array ( Math . max ( 0 , 10 - pointValues . length ) ) . fill ( new THREE . Vector4 ( - 1 , - 1 , - 1 , - 1 ) )
120+ ] ;
121+ setBounds ( paddedArray )
122+ } , [ boundsObj , timeSeries ] )
123+
124+ function addBounds ( uv : THREE . Vector2 , tsID : string ) { //This adds the bounds in UV space of a selected square on the sphere.
125+ const widthID = Math . floor ( uv . x * ( width ) ) + .5 ;
126+ const heightID = Math . ceil ( uv . y * height ) - .5 ;
127+ const delX = 1 / width ;
128+ const delY = 1 / height ;
129+ const xBounds = [ widthID / width - delX / 2 , widthID / width + delX / 2 ]
130+ const yBounds = [ heightID / height - delY / 2 , heightID / height + delY / 2 ]
131+ const bounds = new THREE . Vector4 ( ...xBounds , ...yBounds )
132+ const newBoundObj = { [ tsID ] : bounds }
133+ setBoundsObj ( prev => { return { ...newBoundObj , ...prev } } )
134+ }
135+ function HandleTimeSeries ( event : THREE . Intersection ) {
136+ const uv = event . uv ;
137+ const normal = new THREE . Vector3 ( 0 , 0 , 1 )
138+ if ( ZarrDS && uv ) {
139+ const tsUV = flipY ? new THREE . Vector2 ( uv . x , 1 - uv . y ) : uv
140+ const tempTS = GetTimeSeries ( { data :analysisMode ? analysisArray : GetCurrentArray ( ) , shape :dataShape , stride :strides } , { uv :tsUV , normal} )
141+ setPlotDim ( 0 ) //I think this 2 is only if there are 3-dims. Need to rework the logic
142+
143+ const coordUV = parseUVCoords ( { normal :normal , uv :uv } )
144+ let dimCoords = coordUV . map ( ( val , idx ) => val ? dimSlices [ idx ] [ Math . round ( val * dimSlices [ idx ] . length ) ] : null )
145+ const thisDimNames = dimNames . filter ( ( _ , idx ) => dimCoords [ idx ] !== null )
146+ const thisDimUnits = dimUnits . filter ( ( _ , idx ) => dimCoords [ idx ] !== null )
147+ dimCoords = dimCoords . filter ( val => val !== null )
148+ const tsID = `${ dimCoords [ 0 ] } _${ dimCoords [ 1 ] } `
149+ const tsObj = {
150+ color :evaluate_cmap ( getColorIdx ( ) / 10 , "Paired" ) ,
151+ data :tempTS
152+ }
153+ incrementColorIdx ( ) ;
154+ updateTimeSeries ( { [ tsID ] : tsObj } )
155+ const dimObj = {
156+ first :{
157+ name :thisDimNames [ 0 ] ,
158+ loc :dimCoords [ 0 ] ?? 0 ,
159+ units :thisDimUnits [ 0 ]
160+ } ,
161+ second :{
162+ name :thisDimNames [ 1 ] ,
163+ loc :dimCoords [ 1 ] ?? 0 ,
164+ units :thisDimUnits [ 1 ]
165+ } ,
166+ plot :{
167+ units :dimUnits [ 0 ]
168+ }
169+ }
170+ updateDimCoords ( { [ tsID ] : dimObj } )
171+ addBounds ( uv , tsID ) ;
172+ }
173+ }
174+ // ----- SHADER MATERIAL ----- //
76175 const shaderMaterial = useMemo ( ( ) => new THREE . ShaderMaterial ( {
77176 glslVersion : THREE . GLSL3 ,
78177 uniforms :{
79178 cScale : { value : cScale } ,
80179 cOffset : { value : cOffset } ,
180+ selectTS : { value : selectTS } ,
181+ selectBounds : { value : bounds } ,
81182 map : { value : textures } ,
82183 textureDepths : { value : new THREE . Vector3 ( textureArrayDepths [ 2 ] , textureArrayDepths [ 1 ] , textureArrayDepths [ 0 ] ) } ,
83184 cmap : { value : colormap } ,
84185 animateProg : { value :animProg } ,
85186 nanColor : { value : new THREE . Color ( nanColor ) } ,
86- nanAlpha : { value : 1 - nanTransparency }
187+ nanAlpha : { value : 1 - nanTransparency } ,
87188 } ,
88189 vertexShader : vertShader ,
89190 fragmentShader : isFlat ? fragmentFlat : flatFrag3D ,
@@ -100,29 +201,12 @@ const FlatMap = ({textures, infoSetters} : {textures : THREE.DataTexture | THREE
100201 uniforms . nanColor . value = new THREE . Color ( nanColor ) ;
101202 uniforms . nanAlpha . value = 1 - nanTransparency ;
102203 uniforms . cScale . value = cScale ;
204+ uniforms . selectBounds . value = bounds ;
205+ uniforms . selectTS . value = selectTS
103206 }
104- } , [ cScale , cOffset , textures , colormap , animProg , nanColor , nanTransparency ] )
105- useEffect ( ( ) => {
106- geometry . dispose ( )
107- } , [ geometry ] )
108- const eventRef = useRef < ThreeEvent < PointerEvent > | null > ( null ) ;
109- const handleMove = useCallback ( ( e : ThreeEvent < PointerEvent > ) => {
110- if ( infoRef . current && e . uv ) {
111- eventRef . current = e ;
112- setLoc ( [ e . clientX , e . clientY ] ) ;
113- lastUV . current = e . uv ;
114- const { x, y } = e . uv ;
115- const xSize = isFlat ? ( analysisMode ? analysisDims [ 1 ] . length : dimSlices [ 1 ] . length ) : dimSlices [ 2 ] . length ;
116- const ySize = isFlat ? ( analysisMode ? analysisDims [ 0 ] . length : dimSlices [ 0 ] . length ) : dimSlices [ 1 ] . length ;
117- const xIdx = Math . round ( x * xSize - .5 )
118- const yIdx = Math . round ( y * ySize - .5 )
119- let dataIdx = xSize * yIdx + xIdx ;
120- dataIdx += isFlat ? 0 : Math . floor ( ( dimSlices [ 0 ] . length - 1 ) * animProg ) * xSize * ySize
121- const dataVal = sampleArray ? sampleArray [ dataIdx ] : 0 ;
122- val . current = isFlat && ! analysisMode ? Rescale ( dataVal , valueScales ) : dataVal ;
123- coords . current = isFlat ? analysisMode ? [ analysisDims [ 0 ] [ yIdx ] , analysisDims [ 1 ] [ xIdx ] ] : [ dimSlices [ 0 ] [ yIdx ] , dimSlices [ 1 ] [ xIdx ] ] : [ dimSlices [ 1 ] [ yIdx ] , dimSlices [ 2 ] [ xIdx ] ]
124- }
125- } , [ sampleArray , dimSlices , animProg ] ) ;
207+ } , [ cScale , cOffset , textures , colormap , animProg , nanColor , nanTransparency , bounds , selectTS ] )
208+
209+
126210 return (
127211 < >
128212 < mesh
@@ -133,6 +217,7 @@ const FlatMap = ({textures, infoSetters} : {textures : THREE.DataTexture | THREE
133217 onPointerEnter = { ( ) => { setShowInfo ( true ) ; infoRef . current = true } }
134218 onPointerLeave = { ( ) => { setShowInfo ( false ) ; infoRef . current = false } }
135219 onPointerMove = { handleMove }
220+ onClick = { selectTS && HandleTimeSeries }
136221 />
137222 </ >
138223 )
0 commit comments