diff --git a/package.json b/package.json index 01231f24..6c584ad9 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ }, "dependencies": { "@lazarusa/react-tweakpane": "^0.9.0", + "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-slot": "^1.2.3", "@react-spring/three": "^10.0.0", @@ -38,6 +40,7 @@ "@types/webpack": "^5.28.5", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "glslify": "^7.1.1", "jotai": "^2.12.3", "js-colormaps-es": "^0.0.5", @@ -46,8 +49,10 @@ "next-themes": "^0.4.6", "quick-lru": "^7.0.1", "random-words": "^2.0.1", + "rc-slider": "^11.1.8", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-icons": "^5.5.0", "tailwind-merge": "^3.3.0", "three": "^0.176.0", "tweakpane": "^4.0.5", diff --git a/src/components/LandingHome.tsx b/src/components/LandingHome.tsx index d2f7bb1b..e0a33484 100644 --- a/src/components/LandingHome.tsx +++ b/src/components/LandingHome.tsx @@ -1,7 +1,7 @@ 'use client'; import * as THREE from 'three' THREE.Cache.enabled = true; -import { DataStores } from '@/components/zarr/DataStores' +import { GetZarrMetadata, GetVariableNames } from '@/components/zarr/GetMetadata'; import { ZarrDataset, GetStore } from '@/components/zarr/ZarrLoaderLRU'; import { useRef, useState } from 'react'; import VariableScroller from './ui/VariableScroller'; @@ -19,7 +19,7 @@ import { GetTitleDescription } from '@/components/zarr/GetMetadata'; // import { PaneManager } from '@/components/PaneManager'; export function LandingHome() { - const { bgcolor, fullmetadata, variables} = DataStores(); + const [settings, setSettings] = useState({plotType: 'volume', cmap: 'Spectral', flipCmap: false }); const initStore = useGlobalStore(useShallow(state=>state.initStore)) const [zMeta, setZMeta] = useState([]) @@ -31,6 +31,11 @@ export function LandingHome() { GetTitleDescription(GetStore(initStore)).then((result) => { if (isMounted) setTitleDescription(result); }); + const store = GetStore(initStore); + const fullmetadata = GetZarrMetadata(store); + const variables = GetVariableNames(fullmetadata); + fullmetadata.then(e=>setZMeta(e)) + variables.then(e=> {setVariables(e)}) return () => { isMounted = false; }; }, [initStore]); @@ -57,21 +62,14 @@ export function LandingHome() { useEffect(()=>{ setColormap(GetColorMapTexture(colormap, settings.cmap, 1, "#000000", 0, settings.flipCmap)); - },[settings.cmap, colormap, settings.flipCmap, setColormap]) - - useEffect(()=>{ - variables.then(e=> setVariables(e)) - fullmetadata.then(e=> setZMeta(e)) - },[]) + },[settings.cmap, settings.flipCmap]) //These values are passed to the Plot Component const plotObj = useMemo(() => ({ - plotType: settings.plotType, ZarrDS, variable, - bgcolor, canvasWidth - }), [settings.plotType, ZarrDS, variable, bgcolor, canvasWidth]); + }), [settings.plotType, ZarrDS, variable, canvasWidth]); //This is the data being passed down the plot tree const analysisObj = useMemo(() => ({ @@ -84,12 +82,10 @@ export function LandingHome() { return ( <> - {/* */} - {canvasWidth < 10 && variable != "Default" && setCanvasWidth(window.innerWidth*.5)} canvasWidth={canvasWidth} />} {canvasWidth > 10 && } - {canvasWidth > 10 && } + {canvasWidth > 10 && } {variable === "Default" && } {variable != "Default" && } {metadata && } diff --git a/src/components/plots/Analysis.tsx b/src/components/plots/Analysis.tsx index 94b83178..36fd095f 100644 --- a/src/components/plots/Analysis.tsx +++ b/src/components/plots/Analysis.tsx @@ -1,3 +1,6 @@ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +//@ts-nocheck Analysis requires quite a lot of work to mesh with new UI. Will Monkey with that later import React, { useEffect, useMemo, useRef } from 'react' import { useState } from 'react' import { Canvas } from '@react-three/fiber' @@ -19,31 +22,12 @@ interface Array{ shape:number[], stride:number[] } -interface AnalysisParameters { - values: { - ZarrDS: ZarrDataset; - canvasWidth: number; - } - variables: Promise -} // This wrapper handles loading state -export function Analysis({ values, variables }: AnalysisParameters) { - const [optionsVariables, setOptionsVariables] = useState<{ text: string; value: string }[] | null>(null); - - useEffect(() => { - getVariablesOptions(variables).then(setOptionsVariables); - }, [variables]); - - if (!optionsVariables) return null; - - return ; -} // This renders only when data is ready and uses hooks safely -function AnalysisLoaded({ values, optionsVariables }: { +export function Analysis({ values }: { values: { ZarrDS: ZarrDataset; canvasWidth: number }; - optionsVariables: { text: string; value: string }[]; }) { const {dimNames, dimArrays, dimUnits, setDimNames, setDimArrays, setDimUnits} = useGlobalStore( useShallow(state=>({ @@ -55,6 +39,8 @@ function AnalysisLoaded({ values, optionsVariables }: { setDimUnits: state.setDimUnits }))) + + const variables = useGlobalStore(state => state.variables) const {ZarrDS, canvasWidth} = values const scaleObjRef = useRef>({}); const setFlipY = useGlobalStore(state=>state.setFlipY) diff --git a/src/components/plots/DataCube.tsx b/src/components/plots/DataCube.tsx index e1311012..fc88c90e 100644 --- a/src/components/plots/DataCube.tsx +++ b/src/components/plots/DataCube.tsx @@ -3,7 +3,7 @@ import * as THREE from 'three' import { vertexShader, fragmentShader } from '@/components/textures/shaders'; import { usePaneInput, usePaneFolder, useSliderBlade, useTweakpane } from '@lazarusa/react-tweakpane' import { createPaneContainer } from '@/components/ui'; -import { useGlobalStore } from '@/utils/GlobalStates'; +import { useGlobalStore, usePlotStore } from '@/utils/GlobalStates'; import { useShallow } from 'zustand/shallow'; interface DataCubeProps { @@ -12,8 +12,16 @@ interface DataCubeProps { export const DataCube = ({ volTexture }: DataCubeProps ) => { + const {shape, colormap, flipY} = useGlobalStore(useShallow(state=>({shape:state.shape, colormap:state.colormap, flipY:state.flipY}))) //We have to useShallow when returning an object instead of a state. I don't fully know the logic yet + const {valueRange, xRange, yRange, zRange, quality} = usePlotStore(useShallow(state => ({ + valueRange: state.valueRange, + xRange: state.xRange, + yRange: state.yRange, + zRange: state.zRange, + quality: state.quality + }))) const paneContainer = createPaneContainer("plot-pane"); const pane = useTweakpane( { @@ -43,49 +51,6 @@ export const DataCube = ({ volTexture }: DataCubeProps ) => { step: 25, }) - const [xMin] = useSliderBlade(pane, { - label: 'xmin', - value: -1, - min: -1, - max: 1, - step: 0.01, - }) - const [xMax] = useSliderBlade(pane, { - label: 'xmax', - value: 1, - min: -1, - max: 1, - step: 0.01, - }) - const [yMin] = useSliderBlade(pane, { - label: 'ymin', - value: -1, - min: -1, - max: 1, - step: 0.01, - }) - const [yMax] = useSliderBlade(pane, { - label: 'ymax', - value: 1, - min: -1, - max: 1, - step: 0.01, - }) - const [zMin] = useSliderBlade(pane, { - label: 'zmin', - value: -1, - min: -1, - max: 1, - step: 0.01, - }) - const [zMax] = useSliderBlade(pane, { - label: 'zmax', - value: 1, - min: -1, - max: 1, - step: 0.01, - }) - const [flip] = usePaneInput( pane, 'flip', @@ -103,9 +68,9 @@ export const DataCube = ({ volTexture }: DataCubeProps ) => { cameraPos: { value: new THREE.Vector3() }, threshold: {value: threshold}, scale: {value: shape}, - flatBounds:{value: new THREE.Vector4(xMin,xMax,yMin,yMax)}, - vertBounds:{value: new THREE.Vector2(zMin,zMax)}, - steps: { value: steps }, + flatBounds:{value: new THREE.Vector4(xRange[0],xRange[1],zRange[0],zRange[1])}, + vertBounds:{value: new THREE.Vector2(yRange[0],yRange[1])}, + steps: { value: quality }, flip: {value: flip } }, vertexShader, diff --git a/src/components/plots/Plot.tsx b/src/components/plots/Plot.tsx index 2cab7190..33a5d775 100644 --- a/src/components/plots/Plot.tsx +++ b/src/components/plots/Plot.tsx @@ -6,22 +6,19 @@ import { PointCloud, UVCube, DataCube } from '@/components/plots'; import { Canvas, useThree } from '@react-three/fiber'; import { ArrayToTexture, DefaultCubeTexture } from '@/components/textures'; import { ZarrDataset } from '../zarr/ZarrLoaderLRU'; -import { useGlobalStore } from '@/utils/GlobalStates'; +import { useGlobalStore, usePlotStore } from '@/utils/GlobalStates'; import { useShallow } from 'zustand/shallow'; interface PlotParameters{ values:{ - plotType:string; ZarrDS: ZarrDataset; variable:string; - bgcolor:string; canvasWidth:number } setShowLoading: React.Dispatch>; } - const Plot = ({values,setShowLoading}:PlotParameters) => { const { @@ -41,12 +38,13 @@ const Plot = ({values,setShowLoading}:PlotParameters) => { setDimNames:state.setDimNames, setDimUnits:state.setDimUnits} ))) - const colormap = useGlobalStore(state=>state.colormap) - const {plotType,ZarrDS,variable,bgcolor,canvasWidth} = values; + + const {ZarrDS,variable,canvasWidth} = values; + const plotType = usePlotStore(state => state.plotType) const [texture, setTexture] = useState(null) - const [currentBg, setCurrentBg] = useState(bgcolor || 'var(--background)') + // const [currentBg, setCurrentBg] = useState(bgcolor || 'var(--background)') const [show, setShow] = useState(true) const [windowWidth, setWindowWidth] = useState(0); @@ -63,7 +61,7 @@ const Plot = ({values,setShowLoading}:PlotParameters) => { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.attributeName === 'data-theme') { - setCurrentBg('var(--background)') + // setCurrentBg('var(--background)') } }) }) @@ -76,9 +74,9 @@ const Plot = ({values,setShowLoading}:PlotParameters) => { }, []) // Update background when bgcolor changes - useEffect(() => { - setCurrentBg(bgcolor || 'var(--background)') - }, [bgcolor]) + // useEffect(() => { + // setCurrentBg(bgcolor || 'var(--background)') + // }, [bgcolor]) //DATA LOADING useEffect(() => { @@ -151,7 +149,7 @@ const Plot = ({values,setShowLoading}:PlotParameters) => { {/* Volume Plots */} diff --git a/src/components/plots/PointCloud.tsx b/src/components/plots/PointCloud.tsx index f7172b47..06bca147 100644 --- a/src/components/plots/PointCloud.tsx +++ b/src/components/plots/PointCloud.tsx @@ -5,7 +5,8 @@ import { useMemo } from 'react' import { pointFrag, pointVert } from '@/components/textures/shaders' import { usePaneInput, usePaneFolder, useSliderBlade, useTweakpane } from '@lazarusa/react-tweakpane' import { createPaneContainer } from '@/components/ui'; -import { useGlobalStore } from '@/utils/GlobalStates'; +import { useGlobalStore, usePlotStore } from '@/utils/GlobalStates'; +import { useShallow } from 'zustand/shallow'; interface PCProps { texture: THREE.Data3DTexture | THREE.DataTexture | null, @@ -16,39 +17,13 @@ export const PointCloud = ({textures} : {textures:PCProps} )=>{ const {texture, colormap } = textures; const paneContainer = createPaneContainer("plot-cloud"); const flipY = useGlobalStore(state=>state.flipY) - const pane = useTweakpane( - { - scalePoints: false, - }, - { - title: 'Point cloud', - container: paneContainer ?? undefined, - expanded: false, - } - ); + + const {scalePoints, scaleIntensity, pointSize} = usePlotStore(useShallow(state => ({ + scalePoints: state.scalePoints, + scaleIntensity: state.scaleIntensity, + pointSize: state.pointSize + }))) - const [pointScale] = useSliderBlade(pane, { - label: 'Point Size', - value: 1, - min: 1, - max: 100, - step: 1, - }) - const [scalePoints] = usePaneInput( - pane, - 'scalePoints', - { - label: 'Scale Points By Value', - value: false - } - ) - const [scaleIntensity] = useSliderBlade(pane, { - label: 'Scale Intensity', - value: 2, - min: 1, - max: 10, - step: 0.2, - }) //Extract data and shape from Data3DTexture const { data, width, height, depth } = useMemo(() => { if (!(texture instanceof THREE.Data3DTexture)) { @@ -104,7 +79,7 @@ export const PointCloud = ({textures} : {textures:PCProps} )=>{ const shaderMaterial = new THREE.ShaderMaterial({ glslVersion: THREE.GLSL3, uniforms: { - pointSize: {value: pointScale}, + pointSize: {value: pointSize}, cmap: {value: colormap}, scalePoints:{value: scalePoints}, scaleIntensity: {value: scaleIntensity} diff --git a/src/components/textures/colormap.tsx b/src/components/textures/colormap.tsx index 42f0ac81..4b4b26b3 100644 --- a/src/components/textures/colormap.tsx +++ b/src/components/textures/colormap.tsx @@ -43,16 +43,12 @@ export function GetColorMapTexture( colData[lastIndex + 2] = to_nan[2]; colData[lastIndex + 3] = nan_alpha; if (texture) { - // Update the existing texture data - (texture.image.data as Uint8Array).set(colData); - texture.needsUpdate = true; - return texture; - } else { + texture.dispose() + } // Create a new texture if not already available const newTexture = new THREE.DataTexture(colData, rgbv.length + 1, 1, THREE.RGBAFormat); newTexture.needsUpdate = true; return newTexture; - } } // export function genRand(count: number) { diff --git a/src/components/textures/shaders/pointVertex.glsl b/src/components/textures/shaders/pointVertex.glsl index 9788f198..fee67ca4 100644 --- a/src/components/textures/shaders/pointVertex.glsl +++ b/src/components/textures/shaders/pointVertex.glsl @@ -10,7 +10,8 @@ void main() { //If it is nan we just yeet it tf out of the screen space. LMAO I love this solution float pointScale = pointSize/gl_Position.w; pointScale = scalePoints ? pointScale*pow(vValue,scaleIntensity) : pointScale; - if (value == 255. || (pointScale < 0.75 && scalePoints)){ + + if (value == 255. || (pointScale*gl_Position.w < 0.75 && scalePoints)){ gl_Position = vec4(2.0, 2.0, 2.0, 1.0); } diff --git a/src/components/ui/Navbar.tsx b/src/components/ui/Navbar.tsx index 05bae9a0..8394961c 100644 --- a/src/components/ui/Navbar.tsx +++ b/src/components/ui/Navbar.tsx @@ -1,10 +1,20 @@ +"use client"; +import { LuChevronsUpDown } from "react-icons/lu"; +import { IoIosCheckmark } from "react-icons/io"; +import { ZARR_STORES } from "../zarr/ZarrLoaderLRU"; import Image from "next/image"; -import { AboutButton } from "@/components/ui"; +import { AboutButton, PlotTweaker } from "@/components/ui"; import ThemeSwitch from "@/components/ui/ThemeSwitch"; import logo from "@/app/logo.png" import './css/Navbar.css' - +import { useShallow } from "zustand/shallow"; +import { useGlobalStore, usePlotStore } from "@/utils/GlobalStates"; +import { colormaps } from '@/components/textures'; +import { useEffect, useState } from "react"; +import { GetColorMapTexture } from "@/components/textures"; import { Button } from "@/components/ui/button" +import { cn } from "@/lib/utils" + import { DropdownMenu, DropdownMenuContent, @@ -20,6 +30,21 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command" + +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + import { Select, SelectContent, @@ -30,7 +55,75 @@ import { SelectValue, } from "@/components/ui/select" +const ColorMaps = ({cmap, setCmap} : {cmap : string, setCmap : React.Dispatch>}) => { + const [open, setOpen] = useState(false) + return ( + + + + + + + + + No Colormap found. + + {colormaps.map((value) => ( + { + setCmap(currentValue) + setOpen(false); + }} + > + + {value} + + ))} + + + + + + ) + +} + const Navbar = () => { + const {setInitStore, setVariable, setColormap} = useGlobalStore( + useShallow(state=>({ + setInitStore : state.setInitStore, + setVariable : state.setVariable, + setColormap : state.setColormap + + }))) + + const [refresh, setRefresh] = useState(false) + const variables = useGlobalStore(useShallow(state=>state.variables)) + const setPlotType = usePlotStore(state=> state.setPlotType) + const [cmap, setCmap] = useState("Default") + const [flipCmap, setFlipCmap] = useState(false) + const colormap = useGlobalStore(useShallow(state=>state.colormap)) + + useEffect(()=>{ + setColormap(GetColorMapTexture(colormap, cmap === "Default" ? "Spectral" : cmap, 1, "#000000", 0, flipCmap)); + },[cmap, flipCmap]) + + return (